Context
Mattermost is an open-source team chat platform, a Go server with a React/TypeScript webapp. Like nearly every chat product, it was built assuming the server can read every message. Search depends on that. So does a surprising amount of the rendering path.
This fork removes the assumption. Messages get encrypted in the browser before they ever reach the server, so the server never sees plaintext. Nearly 1,000 commits so far, all landed while the platform kept moving underneath the fork.
The hard part
Greenfield encryption is a design exercise. Retrofitting it onto a mature codebase means digging out assumptions nobody ever wrote down — search, scroll math, and the rendering path all quietly expect plaintext.
The message list was the worst offender. Mattermost's webapp expects post content to exist the moment a component renders, and it uses that content to calculate heights and hold your scroll position. Encrypted posts arrive as ciphertext and resolve asynchronously. Decrypt them in place and the channel jumps around while you're reading it.
And then there's key handling. Decrypt on the main thread and your private keys share a JavaScript context with every third-party dependency and any script that manages to get onto the page. That's a bad neighborhood for key material.
Approach
All decryption runs inside a Web Worker, behind a scheduler that decides what gets decrypted and when. The UI posts ciphertext in and gets plaintext back; key material never touches the main thread.
The scroll problem got its own fix: encrypted-aware scroll position handling, so the message list accounts for content that changes size after it resolves instead of yanking the channel out from under your cursor.
Testing was its own subproject. Jest has no idea what a Web Worker is, so shipping the scheduler meant first building a Jest-compatible Web Worker test harness. On an encryption path, "it seemed to work when I clicked around" is not a test plan.
Beyond encryption
Encryption is why the fork exists, but not where it stopped. A dozen features now ship behind independent flags, each toggled from the System Console and degrading gracefully when off. A selection:
Discord-style replies. Queue posts with a Reply click, then one message links back to all of them with curved SVG connectors. Falls back to plain blockquotes when the flag is off.
Mention highlighting. Posts that @mention you get a colored left border and a tinted row, with priority-tier colors and an inline avatar rendered next to the mention.
Spoiler tags. ||text|| renders as a click-to-reveal redaction box, and any image, video, or file can be tagged a spoiler and blurred until clicked.
View reactions. A "who reacted" dialog that groups reactors by emoji with per-user badges and an All view — attribution the stock client only shows on hover.
Configurable post menu. Each user picks which buttons sit on the hover bar, adds a Shift-held expanded tier, reorders them, and pins quick-reaction emoji — all persisted per user.
Custom channel icons. A searchable icon picker in channel settings, spanning six bundled libraries — Material Design, Lucide, Tabler, Feather, Font Awesome, and brand icons — shown in place of the default glyph.
Guilded-inspired chat sounds. Sound effects for messages and reactions, with a dedicated Sounds settings tab, master volume, and per-event sound pickers.
Guilded chat layout. An optional layout mode with a persistent team rail, a dedicated direct-message page, and an always-on Members/Threads panel — gated behind a flag, a per-user preference, and a viewport check.
Mute for time. Mute a channel for a preset or custom duration; the server persists the expiry and auto-unmutes when it passes.
Error-log dashboard. Every user's API and JavaScript errors stream over WebSocket into a searchable admin console, backed by an in-memory buffer with server-side redaction.
Renameable threads, followed threads nested under their channel in the sidebar, immediate removal of deleted-message placeholders, and full-size image and inline video handling round out the set. Sibling repos extend the same work to the mobile app and the Cloudron packaging that deploys it.
What shipped
Encrypted messaging, working end to end, with the rest of the fork's features live behind their own flags.
The fork is public. The worker scheduler and the scroll handling are the parts worth seeing — happy to walk through either.