Chat Signal 2.1, Almost Two Months In

Chat Signal is a Chrome extension that watches YouTube and Twitch live chat and pulls out what matters: the questions people are asking, the mood of the room, and the topics that keep coming up. The analysis engine is Rust compiled to WebAssembly, and it all runs in the browser.

On September 7 I shipped version 2.1.0, followed the same afternoon by 2.1.1. The next day it was live in the Chrome Web Store. I held off on writing about it until I had lived with it for a while. It has been almost two months, and it has been quiet in the best way: no regressions, no surprise permission prompts, nothing to roll back.

Why This Release

The old AI path was the wrong shape for a tool that sits next to a live stream. Optional summaries used WebLLM with a Qwen model, which meant a download of roughly 400 MB, a consent modal to explain that download, and a content security policy that had to allow HuggingFace and a CDN. The semantic clustering model also fetched itself at runtime. The extension also asked for unlimitedStorage.

None of that was malicious, but all of it was surface area. Every extra host in the CSP and every extra permission is something a reviewer has to trust and something an attacker gets to aim at. For a tool whose whole pitch is "your chat data stays on your machine," I wanted the manifest to prove it instead of asking people to take my word for it.

What Changed

  • AI summaries moved to Chrome's built-in Gemini Nano. Summaries and mood analysis now run on-device through the Prompt API, with a fresh session per call so one batch of chat can't steer the next. WebLLM and Qwen are gone, and so is the 400 MB download.
  • The MiniLM encoder ships inside the package. Semantic clustering loads the model from the extension's own origin. No runtime fetch from HuggingFace.
  • The CSP is now connect-src 'self'. The extension makes no external network connections at all.
  • Permissions are down to sidePanel and storage. unlimitedStorage is gone.
  • Prompt-injection hardening stayed in. Chat is untrusted input feeding a language model, so the pipeline keeps sanitizing chat samples, fencing untrusted data, coercing out-of-vocabulary answers, and strictly validating summaries.
  • Supply chain got tighter. MiniLM is pinned to a specific commit, DOMPurify is pinned with its provenance and SHA-256 recorded, the lockfile is committed, and packaging uses npm ci.

The 2.1.1 follow-up fixed two bugs I would have been embarrassed to leave in. Messages containing &, <, >, or quotes were showing up double-escaped (a <3 rendered as &lt;3). And when one DOM update carried several chat messages at once, the observer only captured the first, so fast chats were being undercounted. That release also bumped DOMPurify from 3.3.1 to 3.4.15 to clear a set of sanitizer advisories, and set a minimum Chrome version of 114 so older browsers get a clear message instead of a broken install.

What I Learned

Moving the smart features onto the browser's own model forced a cleaner product boundary. If a browser can't run Gemini Nano, Chat Signal falls back to rule-based analysis and tells you to update Chrome. It does not invent a second download path to paper over the gap. Clustering, sentiment, and topics keep working everywhere.

The permission and CSP cuts were the real win. The AI swap was how I earned them. Once nothing needed to leave the machine, I could write a policy that says so and let the browser enforce it.

What's Next

The 2.1.1 release quietly added a stable ID to every captured chat message. Nothing uses it yet. It is the foundation for handling deleted and moderated messages, so a message a moderator removes can also drop out of the analysis. That work is on the roadmap.


Live chat analysis that never needs to phone home.