Bridge SMS unread across the boundary the tool dock sits above

Fixkamo-internal
Shipped
September 6, 2026 at 6:24 PM UTC
Author
Kamo
Commit
297c21f

ToolDock is mounted in ToolProviders ABOVE AuthedChrome, deliberately, so it never unmounts across a navigation -- the single fact that keeps a member's typing alive in every tool window. UnreadProvider lives inside AuthedChrome, which makes it a DESCENDANT of the dock. So useUnread() inside a tool window does not throw. UnreadContext is built with a complete default -- getUnread: () => 0, bySurface.sms: 0 -- and everything built on it renders, looks correct, and reports zero forever. Two live consequences: - The softphone's new TEXTS badge would never once have appeared. - SmsTool's markSmsRead call, added specifically so the SMS badge could go DOWN as well as up, resolved to the context default: an already-resolved promise that did nothing. The fix has been shipped and inert. New smsUnreadStore + SmsUnreadSync are the same bridge ToolWindowUnreadSync and HexHeadUnreadSync already build for the tab strip and the hexheads: a provider-free module store read with useSyncExternalStore, fed by a component mounted inside AuthenticatedToolWrapper where the provider is real. Mark-read travels the other way as a CustomEvent, which is how every dock-to-provider message already works. UnreadContext gains `smsSessions` because a reader that has to ENUMERATE unread conversations cannot do it through getUnread, which answers one key at a time and needs the id it is being asked about. The SmsTool test was pinning the broken mechanism -- it mocked useUnread and asserted markSmsRead was called, which passes whether or not the provider is reachable at runtime. It now drives the store and asserts the outbound event, so it fails if anything reaches back for the dead context.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing