Repository navigation
fix(channels): read a channel and its open thread when returning to it - #1835
Conversation
Returning to a channel with its thread pane open moved focus to the thread's tab and skipped the main composer, so only the thread was read and the channel stayed unread. Focus returns to the main composer, and while it stays in that channel's workspace the visible selected thread reads too. Co-authored-by: John Tennant <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Oliver <e78152627ea32fbacb070e5047afcfb8e409fa3343d9e618227baacca5b5e607@buzz.block.builderlab.xyz>
Fold the channel-versus-neighbor assertions into the open-pane branch of the returning-to-a-channel journey instead of repeating its setup. Co-authored-by: John Tennant <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Oliver <e78152627ea32fbacb070e5047afcfb8e409fa3343d9e618227baacca5b5e607@buzz.block.builderlab.xyz>
A link to a top-level message focuses the main timeline, but the already-open selected thread still read beside it. Only a plain channel visit, with no requested message or thread, reads that thread now. Co-authored-by: John Tennant <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Oliver <e78152627ea32fbacb070e5047afcfb8e409fa3343d9e618227baacca5b5e607@buzz.block.builderlab.xyz>
Opening a thread from Activity while its channel's pane was collapsed on that thread tried to focus the tab while the dock was still inert. The pane then opened without a retry, so focus stayed on the body and the thread never read. Hand focus to the selected tab once the pane shows a routed thread, unless something else already took it. Co-authored-by: John Tennant <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Oliver <e78152627ea32fbacb070e5047afcfb8e409fa3343d9e618227baacca5b5e607@buzz.block.builderlab.xyz>
The channel workspace marker comes from the side pane's selected thread, but it also let a main-pane thread read while focus rested in the side pane, so a live reply in main-pane X was read while the person typed in side-pane T. Only a reading surface inside the side pane takes the grant. Co-authored-by: John Tennant <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Oliver <e78152627ea32fbacb070e5047afcfb8e409fa3343d9e618227baacca5b5e607@buzz.block.builderlab.xyz>
loganj
left a comment
There was a problem hiding this comment.
🤖 Larry and Rbot: an independent adversarial review of 812c492. Source review only. Hosted checks were green; neither of us ran the browser specs locally.
Approving. The visit contract now matches the thread discussion. A plain visit puts focus back in the main composer and also reads the open side-pane thread. A link reads only what it focuses. A main-pane thread reads only with focus inside it. We traced the guards: inactive tab panels stay inert and aria-hidden, so the workspace grant cannot read covered tabs. routedThread needs requestedMessage, so a plain visit keeps composer autofocus. requestedMessage comes from the navigation target, so a message link does not start reading the side thread later in the same visit.
Two optional comments below. Neither is must-fix.
Opening a thread from Activity when it was already selected in the main pane focused the side pane's selected thread instead, so the routed thread stayed unread and the other one was read. Focus the routed thread's tab wherever it is visible. Co-authored-by: John Tennant <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Oliver <e78152627ea32fbacb070e5047afcfb8e409fa3343d9e618227baacca5b5e607@buzz.block.builderlab.xyz>
The test links to Thread root 1. Until Alpha's history confirms that root, the link opens its thread instead, which now takes focus and reads it. Wait for confirmed history first, so the link is always the timeline jump the test describes. Co-authored-by: John Tennant <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Oliver <e78152627ea32fbacb070e5047afcfb8e409fa3343d9e618227baacca5b5e607@buzz.block.builderlab.xyz>
Waiting for confirmed history did not stop CI from opening the link to Thread root 1 as that thread, which then takes focus and reads it. Link to an ordinary top-level message instead, as the test intends. It still fails without the link narrowing. Co-authored-by: John Tennant <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Oliver <e78152627ea32fbacb070e5047afcfb8e409fa3343d9e618227baacca5b5e607@buzz.block.builderlab.xyz>
…pen conversation, describe toolbar badges (#2275) ## Summary AIDA-2353. This PR has been cut down so it doesn't overlap two open PRs: - **#2337** owns the Inbox unread count. tulsi left design feedback there. - **#2401** and its stack own finding replies in your threads and old roots. The badge, older-thread discovery and the 30-day discovery window that used to be here were dropped in favor of those PRs. What's left is what neither of them covers, plus one reading fix from beta feedback: - **Solo chats in Inbox.** Ordinary incoming top-level responses in a Solo chat appear as one flat conversation under All. They stay out of DMs, matching the sidebar's separate Solo and Direct messages sections. Its nested threads stay separate, and agent-only coordination and system events stay out. A fresh Solo response reopens an archived flat row, the same way ordinary DM activity does. Opening the flat row reads it in one save (`markTimelineRead`): the channel catch-up mark covers ordinary responses, as at the live bottom, and mentions get individual marks, so thread rows stay unread. (`unread.ts` evidence filter and `markTimelineRead`, `InboxItem.timeline`, `archive.ts` ordinary-activity check.) - **Toolbar page badges are announced.** In the toolbar placement a page's badge sits beside its icon-only button, not inside it. `PageNavigation` now points the button's `aria-describedby` at the badge, so focusing the button announces its count. This applies to #2337's Inbox count and Reminders' due count. - **Live replies in the open conversation are read.** Reported in beta feedback: a conversation open in the Inbox reader kept showing unread after a new reply arrived. Opening a row leaves focus on the list, and the reader's focus gate (`use-reading.ts`) only granted side-pane reading to Channels' plain visits (`data-reading-thread`, #1835). `PageTabs` now sets the same grant while the page shows its own content in the main pane and a selected tab in the side pane. A tab moved to the main pane still reads only by its own focus, and the reply must still be in view. New `inbox-live-read.spec.mjs` fails without the fix. - **Selection survives page navigation (tests only).** This behavior is already on main. New regression coverage: - `inbox-restoration.spec.mjs` (real shell) leaves Inbox for Messages and Reminders and comes back. - An RTL check that restoring the page doesn't replay the row's read. ## Tests - Full Vitest, `tsc --noEmit` and `biome check` pass. - `inbox-live-read` passes 10/10 repeated in Chromium and WebKit, and fails in both without the `PageTabs` change. - Chromium: `inbox-live-read`, `inbox-restoration`, `inbox`, `thread-unread` and `page-placement` pass. Vitest for `bundled/channels`, `bundled/inbox` and `use-reading` passes. - Toolbar description: - Chromium's AX tree (via CDP) reports name "Inbox" and description "3 unread conversations". - Playwright's `toHaveAccessibleDescription` passes in WebKit. - jsdom's accname reads the wrapper text instead, so the unit test asserts the `aria-describedby` association. ## Checklist - Agent review: an independent review of the reading grant found no defects (closed or kept-mounted panes, inert retained tabs and main-pane tabs never get the grant; only Inbox uses `ui.Tabs`). - Agent exercise: Playwright journeys above, in Chromium and WebKit. - Human test: tulsi tested Solo rows under All and the open-conversation read in the desktop dev app (2026-11-04). buzz-review-completed ## Not covered - Older Solo activity outside the retained unread evidence isn't fetched here. #2401's per-channel windows cover it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: tulsi <[email protected]> Co-authored-by: BasilBot <399581b6165ab01343e3a1324774b1024d76942aea563468f93c975e5ee5193c@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <[email protected]>
Why
Opening a channel whose thread pane is open no longer marks the channel read. Buzz reads only in the focused surface, and returning to a channel with a visible selected thread moved focus to that thread's tab and skipped main composer autofocus, so the thread was read but the channel's own messages stayed unread until you clicked into them. This is a bug fix for the channel visit contract: opening a channel reads what it shows.
What
Returning to a channel focuses the main composer again. While focus stays anywhere in that channel's workspace, its visible selected side-pane thread is also a reading surface, so both the channel and the open thread are read. A collapsed pane, another channel's tab, a members or media-review view, and passive restoration still leave the main conversation as the only reading surface. Pane motion completion still wakes observation. A thread in the main pane reads only while focus is inside it.
Only a plain channel visit reads the open thread this way. A link to a message or thread reads only what it focuses. A thread opened from a link or Activity takes focus once its pane shows it, including when the pane was collapsed on that thread or the thread was already selected in the main pane.
Reviewer-reproducible examples
tests/browser/thread-unread.spec.mjs, "returning to a channel reads it and its already-open thread" (1440 px and 1280 px), drives the production UI and read-state engine against isolated relay fixtures: a channel with unread top-level messages and an unread reply in its open thread, switch to another channel, then explicitly return with the pane open.The collapsed-pane case still leaves the hidden thread unread.
Four more regressions in the same file, each failing on the previous head and passing here, in Chromium and WebKit:
Screenshots
After returning to Alpha with Thread root 1 open, from the regression on this build: the main composer has focus, Thread root 1 has no unread dot, and the unopened Thread root 0 and Broadcast reply keep theirs, which is why Alpha keeps its sidebar dot.
🤖 Generated with Claude Code