Narrative notes by release. Per-file binaries and assets: GitHub releases.
On main only; not part of the latest tagged release until you tag and publish. Compare against the current tag on GitHub releases.
Released 2026-10-01. Since v1.3.7; compare v1.3.7...v1.3.8. Commits: git log v1.3.7..v1.3.8 --oneline.
- Server: Cap chat
contentat 32 KiB by default (MARCHAT_MAX_MESSAGE_BYTES, orMARCHAT_MAX_MESSAGE_MB). Oversizedtext,dm,edit,search, and command bodies get a System reply and stay connected; nothing is persisted or broadcast. Encrypted ciphertext uses the same byte cap. Plugin chat and command replies over the cap are dropped before broadcast. File uploads keep their own limit. Admin TUI, web admin, and-doctorshow the message cap. - Client: Reject oversized composer, DM, code snippet, edit, and search bodies before send, including E2E wire
contentafter encryption. WebSocket close 1009 is reported as a message-too-big error. - Dependencies: modernc.org/sqlite v1.59.0 (transitive modernc.org/libc v1.75.7; modernc.org/mathutil v1.7.1 and modernc.org/memory v1.12.1 unchanged); github.com/mattn/go-runewidth v0.0.30.
- Packaging: Version strings and URLs for v1.3.8 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.8 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-09-12. Since v1.3.6; compare v1.3.6...v1.3.7. Commits: git log v1.3.6..v1.3.7 --oneline.
- Docs: Pointer to marchat-bot (standalone Go WebSocket bot SDK; not a plugin) in README, PROTOCOL, docs/README, CLIENT_HOOKS, and PLUGIN_ECOSYSTEM.
- Packaging: Drop versioned
scripts/post-release-v*.ps1helpers; pattern is gitignored. Chocolatey remainschoco packinpackaging/chocolatey/(PACKAGING.md). - Dependencies: golang.org/x/crypto v0.57.0; golang.org/x/term v0.46.0 (transitive golang.org/x/sync v0.23.0, golang.org/x/sys v0.48.0, golang.org/x/text v0.42.0, golang.org/x/mod v0.41.0, golang.org/x/tools v0.49.0); modernc.org/sqlite v1.58.0 (transitive modernc.org/libc v1.75.6, modernc.org/mathutil v1.7.1, modernc.org/memory v1.12.1); github.com/jackc/pgx/v5 v5.11.0; github.com/go-sql-driver/mysql v1.10.1; github.com/mattn/go-runewidth v0.0.29.
- Toolchain: Go 1.26.8 in go.mod, nested plugin modules (plugin/sdk, plugin/examples/echo), CI (.github/workflows/go.yml, .github/workflows/release.yml), and Dockerfile; minimum required by golang.org/x/crypto v0.57.0 is 1.26.0 (module
godirective). - Packaging: Version strings and URLs for v1.3.7 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.7 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-09-01. Since v1.3.5; compare v1.3.5...v1.3.6. Commits: git log v1.3.5..v1.3.6 --oneline.
- Server: Fix: file-backed SQLite uses a single-connection writer pool (
MaxOpenConns(1),_txlock=immediate) plus a bounded WAL reader pool (MaxOpenConns(4),_query_only=1) so health/admin/history reads are not serialized behind inserts;:memory:and Postgres/MySQL are unchanged. Close withCloseDB. No schema migration (#126, #127). - Docs: TESTING notes that CI golangci-lint is govet/ineffassign/staticcheck SA* and that gopls
unusedparamsis not in CI; main-module coverage refreshed to 48.3%. - Dependencies: modernc.org/sqlite v1.57.0; charm.land/bubbletea/v2 v2.0.9; charm.land/bubbles/v2 v2.2.1; github.com/mattn/go-runewidth v0.0.28.
- Packaging: Version strings and URLs for v1.3.6 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.6 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh). - Docker: the v1.3.6 image rebuild runs
apk upgradeon Alpine 3.22 and picks up openssl 3.5.8 (no Dockerfile change).
Released 2026-08-15. Since v1.3.4; compare v1.3.4...v1.3.5. Commits: git log v1.3.4..v1.3.5 --oneline.
- Server: Fix:
:kick/:ban(and admin TUI / web user actions) reject self-targets case-insensitively and return clear errors instead of disconnecting the admin and writing a 24h ban;KickUser/BanUserreturn errors so callers claim success only onnil; kicking an already permanently banned user returns an error without claiming success (#115). - Server: Fix:
:kick(and admin TUI / web kick actions) are online-only:KickUserrequires an active WebSocket connection, disconnects the target, and applies a 24h temporary ban; offline or never-connected users returnErrKickNotConnectedwith notempKicksentry orban_historyrow;:ban/BanUserremain offline-capable (#116). - Server: Fix: reject empty or whitespace-only plaintext on
text,dm, andeditwhenencryptedis false (System reply, no persist/broadcast); encrypted opaquecontentis not treated as empty (#117). - Server: Fix: SQLite
InitDBappliesbusy_timeout/ WAL / related pragmas via the DSN on every connection and setsMaxOpenConns(1)/MaxIdleConns(1), so concurrent inserts no longer fail withSQLITE_BUSYfrom one-shotPRAGMA+ the defaultdatabase/sqlpool (#118). - Server: Fix: active permanent bans and unexpired temp kicks load from
ban_historyon hub start (expires_atNULL = permanent; non-NULL = kick expiry), so moderation survives process restart. Pre-upgrade open rows withoutexpires_atload as permanent. Permanent bans are presence-only in memory (no 100-year sentinel expiry). - Server: Fix: schema bootstrap uses versioned
MigrateSchema(schema_version) and hard-fails when required tables orban_history.expires_atare missing instead of warning and continuing. SQLite/Postgres apply each version inside a transaction (mid-migration failure rolls back); MySQL DDL cannot participate in multi-statement transactions (implicit commit), so steps run without a wrapping transaction andschema_versionis recorded only after a successful apply. - Server: Connected-user lookups (
KickUser,kickUser,ForceDisconnectUser,broadcastDM) use an O(1)clientsByUsernamemap underclientsMutex. - Server: Fix:
:ban/:kickclose any openban_historyrow before inserting (at most one open row per user), persist to the DB before updating in-memory enforcement state, and load the latest open row byidon hub start so kick-then-ban (and legacy duplicates) restart cleanly. - Docs: ARCHITECTURE documents
MigrateSchema/schema_version/ban_history.expires_at; TESTING local lint install pins match CI (no@latest); main-module coverage refreshed to 47.8%. - Dependencies: modernc.org/sqlite v1.56.0 (journal-rollback corruption fix; modernc.org/libc v1.74.4); github.com/lucasb-eyer/go-colorful v1.4.1; golang.org/x/crypto v0.55.0; github.com/charmbracelet/x/ansi v0.11.8; charm.land/lipgloss/v2 v2.0.6.
- Toolchain: Go 1.25.13 in go.mod, nested plugin modules, CI, and Dockerfile (stdlib fixes for the 6 reachable findings govulncheck reported on 1.25.12).
- CI:
govulncheck ./...without-show verbose(default symbol scan; unreachable module advisories stay informational). Pin golangci-lint v2.12.2 and govulncheck v1.6.0 (no@latest) in the main and nested-module jobs; add.golangci.yml(v2) enabling govet/ineffassign/staticcheck withallminusST*/QF*(bug-focused checks, not SA*-only). - Packaging: Version strings and URLs for v1.3.5 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.5 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-08-03. Since v1.3.3; compare v1.3.3...v1.3.4. Commits: git log v1.3.3..v1.3.4 --oneline.
- Server: Fix: oversized file uploads that fit under the WebSocket DoS read ceiling (32 MiB) are rejected with a System message and a live connection (app-layer size check);
SetReadLimituses that ceiling above policy wire size so gorilla does not close with empty 1009 before the reply can flush. Residual over-ceiling reads still logErrReadLimitwithout enqueueing a System message (#114 follow-up). - Docs: PROTOCOL / TESTING / README document DoS ceiling vs policy reject; main-module coverage refreshed to 46.3%.
- Packaging: Version strings and URLs for v1.3.4 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.4 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-08-03. Since v1.3.2; compare v1.3.2...v1.3.3. Commits: git log v1.3.2..v1.3.3 --oneline.
- Server: Fix: oversized file WebSocket messages hit explicit read-limit handling with correct rejection logging and a System reply when the connection is still writable; read limit accounts for base64 JSON wire size (not raw bytes only); rejects declared
sizeand actual payload length above the limit (#114). - Client: Fix: WebSocket close 1009 (message too big) shows a file-size error instead of a generic reconnect warning.
- Plugins: Archive extraction uses
os.OpenRootscoped writes andfilepath.IsLocalentry validation so zip-slip static analysis and runtime checks align (CodeQL go/zipslip). - Docs: PROTOCOL and TESTING note file rejection behavior and refreshed coverage (46.4% main module).
- Dependencies: modernc.org/sqlite v1.55.0 (SQLite 3.53.3; modernc.org/libc v1.74.1); github.com/mattn/go-runewidth v0.0.27.
- Packaging: Version strings and URLs for v1.3.3 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.3 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-07-15. Since v1.3.1; compare v1.3.1...v1.3.2. Commits: git log v1.3.1..v1.3.2 --oneline.
- Server: Security: overwrites client-supplied
senderon text and file outbound paths (stampSenderTimedOutbound); rejects NUL bytes in persistable content before insert; does not broadcast when message persistence fails. - Client: Security: desktop notifications hardened: Windows toast XML built in Go with
xml.EscapeTextand shown viapowershell -EncodedCommand; macOSosascriptusesstrconv.Quotestring literals (no shell interpolation of wire content). - Docs: PROTOCOL, SECURITY, ARCHITECTURE, and TESTING document server sender stamping and safe desktop notification paths for untrusted wire content.
- Packaging: Version strings and URLs for v1.3.2 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.2 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-07-14. Since v1.3.0; compare v1.3.0...v1.3.1. Commits: git log v1.3.0..v1.3.1 --oneline.
- Server: Fix: handshake history replay sets
type: "text"on channel messages so reconnect scrollback renders in the client (was omitted due to zero-valueomitempty). - Toolchain: Go 1.25.12 in go.mod, nested plugin modules, CI, and Dockerfile (stdlib fixes for reachable GO-2026-5856 / crypto/tls ECH privacy leak and package-level GO-2026-4970 / os symlink escape reported by govulncheck on 1.25.11).
- Dependencies: charm.land/bubbles/v2 v2.1.1 (textarea prompt styling fix); golang.org/x/crypto v0.54.0; golang.org/x/term v0.45.0 (transitive golang.org/x/sys v0.47.0, golang.org/x/text v0.40.0, github.com/sahilm/fuzzy v0.1.3).
- Packaging: Version strings and URLs for v1.3.1 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.1 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-07-06. Since v1.2.0; compare v1.2.0...v1.3.0. Commits: git log v1.2.0..v1.3.0 --oneline.
- Client: ANSI-aware word-wrap for chat bodies; reaction aliases
thumbsup/thumbsdownand:unreact,:thumbsup,:thumbsdown; when E2E is on and server search returns no matches, aSystemline notes ciphertext-only matching. Charm v2: Bubble Tea, Bubbles, and Lip Gloss oncharm.land/*/v2(tea.View,KeyPressMsg, bubbles setters). Long URLs wrap at path boundaries; wrapped segments keep hyperlink style and OSC 8Style.Hyperlinkhrefs (ASCII hyphens); mouse click-to-open fallback (#103). Fix: composer chrome, multiline keys, placeholder cursor, and scroll-to-tail follow; mouse wheel routes to the active viewport; help/DB overlays suppress typing, URL clicks, and read-receipt flush until closed; ephemeralSystemfeedback uses the banner; reconnect backoff advances on failure; transcript notices, reactions, and read receipts stay channel-scoped; E2E paths do not log plaintext. - Server: Replay up to 50 visible messages on every handshake (including reconnect). Fix: Postgres boolean SQL for search, pin toggle, and pinned listing; MySQL
parseTime=truewhen unset; outbound messages stamped to the sender's channel; typing, reactions, and read receipts channel-scoped;:backupSQLite-only; admin TUI mouse scroll on tabs and tables;:cleardbclearsuser_message_state. - Plugins: Fix: serialized plugin stdin writes so chat fan-out and command RPC cannot corrupt IPC lines.
- Docs: README, TESTING, ARCHITECTURE, and PROTOCOL for channel stamping, reconnect, wrapped URL limitation (#103), SQLite-only
:backup, and plugin IPC; agent skills under.cursor/skills/. - Tooling: Project Agent skills and
.cursor/rules/marchat.mdc;.gitignoretracks shared rules and skills. - Dependencies: charm.land/bubbletea/v2 v2.0.8, charm.land/bubbles/v2 v2.1.0, charm.land/lipgloss/v2 v2.0.5 (replaces Charm v1); github.com/charmbracelet/colorprofile v0.4.3, github.com/charmbracelet/x/ansi v0.11.7, github.com/lucasb-eyer/go-colorful v1.4.0, github.com/mattn/go-runewidth v0.0.24. github.com/jackc/pgx/v5 v5.10.0, golang.org/x/crypto v0.53.0, golang.org/x/term v0.44.0, modernc.org/sqlite v1.53.0.
- Packaging: Version strings and URLs for v1.3.0 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.3.0 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-06-06. Since v1.1.0; compare v1.1.0...v1.2.0. Commits: git log v1.1.0..v1.2.0 --oneline.
- Server: WebSocket Origin checks compare parsed hostnames (no substring matching); optional
MARCHAT_ALLOWED_ORIGINSallowlist.getClientIPand web-admin login rate limiting honorX-Forwarded-For/X-Real-IPonly when the immediate peer is inMARCHAT_TRUSTED_PROXIES(comma-separated IPs or CIDRs). - Client: Direct messages use the same E2E wire path as channel
textwhen encryption is enabled (encryptedplus base64 nonce || ciphertext with the global key). Applies to:dm <user> <msg>, DM mode compose, and code snippets sent while a DM thread is open (:code/ Alt+C). Fix: code snippets in DM mode route through the DM send path, not channeltext. - Plugins: Plugin store downloads validate SHA-256 checksums before extraction (HTTP and
file://), reject oversize archives, parsefile://paths correctly on Linux and Windows (registry and download URLs viaplugin/fileurl), detect archive type from the URL path (including query strings), extract to a staging directory with zip-slip checks, roll back failed updates, set the execute bit on the plugin binary by exact name match after ZIP/TAR extract, and do not leave an empty plugin directory when install download fails. - Toolchain / dependencies: Go 1.25.11 in go.mod, nested plugin modules, CI, and Dockerfile (stdlib fixes for GO-2026-5037, GO-2026-5038, GO-2026-5039 reported by govulncheck on 1.25.10); golang.org/x/crypto v0.52.0; modernc.org/sqlite v1.51.0 (was v1.50.0). Transitive filippo.io/edwards25519 v1.2.0 (MySQL driver).
- Packaging: Version strings and URLs for v1.2.0 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.2.0 manifest set, Chocolatey, AUR) with SHA256 from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh).
Released 2026-05-12. Since v1.0.0; compare v1.0.0...v1.1.0. Commits: git log v1.0.0..v1.1.0 --oneline.
- Server:
messages.channelcolumn (defaultgeneral) with startup migration; channel messages persist and replay on the correct channel. Direct messages storerecipient; reconnect history includes DM rows only for sender and recipient.TypingMessagewith non-emptyrecipientuses the same DM delivery path as chat DMs. - Client: Transcript and typing scoped to the active channel; DM thread sidebar (unread, hide, reappear),
dm_state.jsonunder the client config directory, footer shows the active DM peer, commands:dm/:dm off/:dms/:dmhide. Fix::dmhideand:dmshandled before:dm(prefix collision). Typing: DM compose sends optionalrecipienton the wire; reference TUI hides DM-scoped typing unless that thread is open and hides channel typing while a DM thread is open. - Diagnostics: Client
-doctorreportsdm_state.jsonand E2E key source; server-doctorincludes a DM history note. - Docs / protocol: ARCHITECTURE, PROTOCOL, README, TESTING, CONTRIBUTING, QUICKSTART, PLUGIN_ECOSYSTEM, docs/README for DMs, typing
recipient, and doctor output; optional graphical clients and marchat-plugins discovery where relevant. - Toolchain / dependencies: Go 1.25.10 in go.mod, CI, Dockerfile; golang.org/x/crypto v0.51.0, golang.org/x/term v0.43.0; github.com/jackc/pgx/v5 v5.9.2, modernc.org/sqlite v1.50.0, github.com/go-sql-driver/mysql v1.10.0.
- CI: Downstream AUR publish clones aur.archlinux.org over HTTPS before SSH push.
- Packaging: Version strings and URLs for v1.1.0 in install.ps1, install.sh, build-release.ps1, scripts/build-*.ps1/sh, README, SECURITY.md, .github/workflows/release.yml, and packaging/ (Homebrew, Scoop, winget 1.1.0 manifest set, Chocolatey, AUR). SHA256 fields are placeholders (
000000...) until replaced from published release zips (PACKAGING.md, packaging/ci/render-release-manifests.sh). Regenerate packaging/aur/.SRCINFO on Arch after final PKGBUILD checksums (makepkg --printsrcinfo).
Released 2026-04-17. Since v0.11.0-beta.5; compare v0.11.0-beta.5...v1.0.0. Commits: git log v0.11.0-beta.5..v1.0.0 --oneline.
- Client: Terminal-native footer and banner chrome; read receipts in the transcript; reconnect clears stale transcript state; sending indicator and unread count refinements; rate limit notice when the server throttles; theme loader updates and THEMES.md examples.
- Server: Clearer handling for unknown admin commands over the admin connection; related client/server sending-state fixes after chat writes.
- Docs / protocol: ARCHITECTURE, PROTOCOL, README, TESTING aligned with TUI behavior and coverage.
- Packaging: v1.0.0 templates across Homebrew, Scoop, winget, Chocolatey, and AUR; Chocolatey nuspec iconUrl (repo logo on
main) and clearer title; refresh zip SHA256 values from published release assets beforechoco pack/ local manifest validation (see PACKAGING.md).
Released 2026-04-10. Since v0.11.0-beta.4; compare v0.11.0-beta.4...v0.11.0-beta.5. Commits: git log v0.11.0-beta.4..v0.11.0-beta.5 --oneline.
- Server: RFC 6455 WebSocket close frames on handshake errors; hub stays off plugin IPC with bounded, best-effort, at-most-once plugin chat fan-out.
- Client: Experimental env-driven exthook and
-doctorintegration. - Plugin SDK:
RunStdio/HandlePluginRequeststdio loop; echo sample uses the SDK; docs and README plugin examples aligned (GetConfig, Marshal);plugin/sdk/covgitignored; CI runs nested plugin modules (fmt, govulncheck). - Tests / CI: Server loadverify benches and rate-limit coverage;
-doctortests use the injectableosEnvironhook underenvironMu(no parallelbuildEnvLinestests that swap it); plugin hostStopPluginwaits for stdout/stderr reader goroutines before reuse so-raceis clean on disable/enable; Dependabot Node 20 note in.github/dependabot.yml. - Docs: TESTING bench section; coverage tables refreshed from
go test -coverprofile=mergedcoverage ./...; hook example lives under_example_hook; prose uses ASCII hyphens where edited. - Deps:
golang.org/x/crypto,golang.org/x/term,modernc.org/sqlite.
Released 2026-04-09. Compare from beta.3. E2E edit consistency; deterministic theme cycle; security scanner vs govulncheck docs; .gitattributes LF normalization.
Released 2026-04-09. Compare from beta.2. Keystore v3 and config/path fixes; web admin refresh; plugin SDK context and host fixes; DB smoke CI; Go 1.25.9; demos, E2E docs, and release asset workflow updates.
Go 1.25.8 toolchain/docs; -doctor and env reflection improvements; terminal chrome and :msginfo metadata; license cache and server hardening; static release zips + linux-arm64 for Termux.
- v0.11.0-beta.1: Multi-DB (SQLite / Postgres / MySQL), reactions, read receipts, message state, serialized WS writes, admin TUI (PR #83).
- v0.10.x: Core chat features (edit/delete/pin/search, DMs, channels, E2E files, plugins),
-doctor, Docker, Caddy TLS proxy docs (deploy/CADDY-REVERSE-PROXY.md),config/.envprecedence.