From 590220d8a93cf28a60e175ab3ab654bdf170a46e Mon Sep 17 00:00:00 2001 From: Shai Snir Date: Tue, 28 Jul 2026 23:57:09 +0300 Subject: [PATCH] fix(widget): restore the unread flag for EVERY launcher, not just the pill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.2.1 taught the widget to remember an unread message across a navigation. It only ever remembered it for tenants who had opted into the pill launcher — which is nobody by default. `restoreUnread()` was called from inside applyTheme's `launcherStyle === "pill"` branch. So on the default circle the flag was WRITTEN on every inbound message and never READ back: `krispy_unread_` accumulated in localStorage while the launcher went quiet on the next page, which is the exact failure persisting it was supposed to fix. My bug, in the commit that added the feature. It now runs at boot, next to renderMute(), for every launcher. OUTSIDE applyTheme for a second reason, not just to escape the branch: the flag has nothing to do with the theme, and applyTheme only runs if the boot config fetch SUCCEEDS. A visitor returning to a page whose config request failed — a blip, an ad blocker, a cold edge — should still be told that somebody answered them. Verified as an A/B on ONE origin, so both builds share the localStorage the test is actually about. No theme at all, i.e. the default circle: write the key, reload, read the launcher. before (master) class="btn" kunread absent dot hidden after (this) class="btn kunread" kunread present dot visible and the key is still stored in both, which is what makes it a read bug rather than a write bug. `bun run check` green: 201 edge tests, 0 fail, 0 lint errors. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 4 ++++ packages/widget/widget.js | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 225e891..b043b69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ entry under `[Unreleased]` (see `AGENTS.md` §7 — Documentation sync). ## [Unreleased] +### Fixed + +- Widget: **the remembered unread flag never came back for the default launcher.** `restoreUnread()` shipped in v0.2.1 inside `applyTheme`'s `launcherStyle === "pill"` branch, so every tenant on the circle — which is every tenant that has not opted into the pill — wrote `krispy_unread_` on an inbound message and never read it back. The notice still died on the next navigation, which is the one thing persisting it was for. It now runs at boot for every launcher, and outside `applyTheme` for a second reason: the flag has nothing to do with the theme, and `applyTheme` only runs when the boot config fetch succeeds — a visitor returning to a page whose config request failed should still be told somebody answered them. + ## [0.2.1] — 2026-07-28 ### Added diff --git a/packages/widget/widget.js b/packages/widget/widget.js index 92e0129..7adfb32 100644 --- a/packages/widget/widget.js +++ b/packages/widget/widget.js @@ -641,7 +641,6 @@ // The label is decoration; the button is already named by its aria-label. pillBtn.querySelector(".blabel").setAttribute("aria-hidden", "true"); if (panel.classList.contains("open")) pillBtn.classList.add("kshut"); - restoreUnread(); } var r = clampRadius(th.radius); if (r != null) host.style.setProperty("--k-radius", r + "px"); @@ -824,6 +823,19 @@ muteBtn.setAttribute("aria-label", muted ? "Unmute notifications" : "Mute notifications"); } renderMute(); + // AND THE UNREAD FLAG, restored for EVERY launcher. + // + // This was a bug the day it shipped: the call lived inside applyTheme's + // `launcherStyle === "pill"` branch, so a tenant on the default circle — which + // is every tenant that has not opted in — kept writing the key on an inbound + // message and never read it back. The notice still died on the next + // navigation, which is the exact thing persisting it was for. + // + // Here instead of in applyTheme for a second reason: the flag has nothing to do + // with the theme, and applyTheme only runs if the boot config fetch SUCCEEDS. A + // visitor coming back to a page whose config request failed should still be told + // somebody answered them. + restoreUnread(); muteBtn.addEventListener("click", function () { muted = !muted; localStorage.setItem(MUTE_KEY, muted ? "1" : "0");