Skip to content

Keep Messages selected across its sidebar destinations - #848

Open
wesbillman wants to merge 4 commits into
mainfrom
carl/keep-messages-selected
Open

wesbillman wants to merge 4 commits into
mainfrom
carl/keep-messages-selected

Conversation

@wesbillman

@wesbillman wesbillman commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Messages now stays selected when navigating to Inbox and other pages in its sidebar. The header previously matched only the exact destination, so leaving the conversation page cleared its selection even though the user was still inside Messages.

  • Derive header area selection from registered page placement while preserving exact sidebar selection, routes, and history. Me, Settings, independent header plugins, and unregistered pages remain separate.
  • Keep the active child linked to the Messages tabpanel. Reuse the shared chrome Tabs with opt-in reactivation so clicking Messages, Enter, or Space still returns to the conversation; compact overflow distinguishes the containing area from the exact page.
  • Honor existing navigation focus intent when mounting the channel composer so it cannot steal focus from the header, including Me → Messages.

No new routing state, last-child memory, persistence changes, or sidebar redesign.

Validation

Local macOS checks passed on the final source tree committed as a9e753b26ec9fbadeda2213e7b8554667bd5472a (changed-file hashes verified after commit):

Check Result
Full Vitest suite 654 files, 9,220 tests passed
Design-system unit suite / build 22 files, 140 tests passed / build passed
Page-placement browser file, Chromium + WebKit 4/4 passed
Required commit/push hooks Passed; no bypasses

Browser command: bin/pnpm test:browser tests/browser/page-placement.spec.mjs --project chromium --project webkit --no-deps.

Extended the existing page-placement journey; no browser cases added or removed. Real browser focus, pointer/Enter/Space activation, draft retention, reload/history, and responsive overflow require this integration layer. Both themes, 390px width, and 200% root-font scaling are covered; this is not full browser zoom. Plugin/registration permutations remain in the pure-helper tests, with shell and shared Tabs wiring covered in RTL. Negative control: reverting the AppShell area-selection wiring made the new Inbox assertion fail (aria-selected=false); restoring it passed both engines.

Independent agent reviews found no material blockers: Mordecai reviewed ownership/focus source, Princess Donut reviewed the diff and light/dark captures, and Mongo reviewed coverage and ran Biome on touched files. They did not independently run suites. Documentation and the additional Me → Messages focus assertion address their feedback.

Visual evidence

Synthetic browser fixtures, not a live account or native acceptance. The extra fixture plugins and loading/retry sidebar are test-only content. Captures below wait for the selection indicator to settle.

Light

Messages remains selected while Inbox is the active child, light theme

Dark

Messages remains selected while Inbox is the active child, dark theme

CI timeout repair

b3f8f8d8ced618f3046b80d042ec2f4d8187c559 changes only fixture input in the workflow same-revision draft-replacement test: one fireEvent.change per invalid value instead of per-character typing. Real mounted React, user clicks, both replacement cycles, and every assertion remain. No timeout/retry changes, skipped cases, or production edits.

  • Original hosted JavaScript shard 1: 4,537 tests, one failure at 5,018 ms; 494.20 s wall time and 654.98 s summed test execution (run). The workflow file took 17.43 s; the slowest file was AgentsPage at 70.23 s. This pre-existing test did not change in the Messages feature commit.
  • Local macOS/jsdom, BUZZ_TEST_WORKERS=2 bin/pnpm exec vitest run src/bundled/workflows/WorkflowsPage.test.tsx: 22/22 before and after. Before (a9e753b26): import 0.93 s, environment 0.47 s, wall 6.50 s, summed tests 5.02 s, affected test 1.45 s. After (same head plus the committed test-only patch): import 0.62 s, environment 0.26 s, wall 5.72 s, summed tests 4.75 s, affected test 1.15 s. These single local observations do not establish hosted timing margin.
  • Full Vitest on clean b3f8f8d8c: 654 files / 9,220 tests passed, zero skipped; reporting-wrapper wall 362.12 s, summed test execution 465.57 s. Affected test 1.19 s. Slowest test: Vite-config import 4.60 s; slowest file: ChannelMembersDialog 24.45 s. Required commit/push checks and current-head DCO passed.
  • Negative control: reusing the editor replacement key makes the amended test fail on the second retained invalid value (second-partial). Production source restored before final validation. Independent source review found no coverage blockers. No browser cases added or removed by this repair.

New hosted CI is running: https://github.com/block/buzz-app/actions/runs/38094795996. CI is not yet verified green for this head. Existing human/native acceptance and approval requirements are unchanged; no running app was modified.

Remaining acceptance gate

Human confirmation, native-app acceptance, hosted checks, and required approvals remain acceptance requirements. GitHub currently marks the PR ready for review; this CI-only repair does not attest those gates. No running app or live identity was changed. Full browser/native suites remain with CI; local browser evidence is limited to the page-placement file and screenshot capture.

On this branch in an agreed native test setup:

  1. Leave a draft in a conversation, then open Inbox, Reminders, and Agents. Messages should stay selected while the sidebar marks the exact destination.
  2. Activate Messages by pointer, Enter, and Space. It should return to the conversation with the draft intact and focus on the tab. Me selects Me; Settings selects neither.
  3. Repeat with back/forward and narrow More pages in both themes. Messages should indicate the containing area without changing the destination or losing navigation access.

Implemented by Carl, an automated agent; PR opened through Wes’s GitHub account.

Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
@wesbillman
wesbillman marked this pull request as ready for review October 10, 2026 23:04
@wesbillman
wesbillman requested a review from a team as a code owner October 10, 2026 23:04
Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One change requested: keep keyboard focus on a stable element when retrying a failed Messages opening (inline). Automated source review by Star Lord via Wes’s account, head a9e753b26ec9fbadeda2213e7b8554667bd5472a, base 37dcd836fbcf1613ddd27cece5a99e04d601bd64. Source and both attached images inspected; no tests or app execution performed. The hosted snapshot has required CI failing on a 5-second WorkflowsPage.test.tsx:335 timeout; causality is unestablished, and native acceptance remains unverified.

!current.readOnly && !requestedMessage && !requestedThread
// The bound request arrives after the composer's first render.
(navigation?.focus ??
navigator?.snapshot().attempt.focus) !== "preserve" &&

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Hand off focus when the preserved opening is retried

This suppresses the recovery focus as well as the initial tab-to-page autofocus. If opening Messages from a header tab fails (for example, its channel window errors), App.tsx:253–267 replaces the page with “Retry navigation”. A keyboard user can focus and activate that button, but route.retry() calls navigation.retry(), which retains the original focus: "preserve" (controller.ts:263). The retry button is then unmounted; this new guard prevents the remounted composer from focusing, and the wide shell has no retry focus handoff, leaving focus on the document body rather than the tab or recovered content.

Keep suppression for activation from the persistent header tab, but explicitly hand off focus to a stable main/panel or tab when the retry control itself owns focus, before removing it. Add a failed-opening → keyboard Retry → successful recovery regression asserting that focus remains on a meaningful element; the existing live-update retry already has an explicit handoff. This follows DESIGN.md’s requirement to preserve focus restoration, without changing retry’s stored focus intent.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing retry-focus finding remains unresolved. No new findings in the test-only follow-up: bulk fixture input preserves both replacement cycles and their assertions without changing production code or timeouts.

Star Lord’s automated source review via Wes; head b3f8f8d8ced618f3046b80d042ec2f4d8187c559, base 37dcd836fbcf1613ddd27cece5a99e04d601bd64. No tests/app execution; hosted CI was still running and native acceptance is unverified. Dark attachment inspected; the light attachment could not be freshly fetched (HTTP 504), so its previously saved copy was inspected instead.

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Thanks for this. Blocking: one correctness issue at b3f8f8d8ced618f3046b80d042ec2f4d8187c559. It is the same issue as the open retry-focus thread (r4239536720), confirmed independently from the source.

Blocking: keyboard Retry after a failed Messages opening leaves focus on the page body

  • The new guard at ChannelsPage.tsx:1527-1533 turns off composer mount focus for any opening whose intent is preserve. Header-tab activation and the new Messages reselect both send preserve (PageNavigation.tsx:32-35).
  • If that opening fails, App.tsx:253-274 renders the "Retry navigation" alert. A keyboard user focuses Retry and activates it. route.retry() (navigation.ts:301-318) calls navigation.retry(), which restarts with the stored preserve intent (controller.ts:263).
  • On success the alert and its focused button unmount. The composer mounts without focus, and nothing else takes it, so focus falls to document.body. Before this change the composer took focus on that path.
  • Fix: before the alert unmounts, move focus from the Retry control to a stable element (the Messages tab, the shell panel, or #main-content), or give the retry its own focus intent. Add a test: a failed header opening, then keyboard Retry, then a successful recovery, asserting the final focused element. The existing reconnect test (tests/browser/navigation-repairs.spec.mjs:40-59) checks requests and composer visibility, not focus.

What holds

  • headerPageSelection (presentation.ts:74-93) maps only registered sidebar/default-placement pages to Messages, and only when Messages is a primary topbar page. Me, Settings, toolbar and independent header pages, and unregistered keys keep exact selection. The sidebar still receives the exact selected, so routes and sidebar aria-current="page" are unchanged.
  • onReselect is opt-in. PageNavigation is its only caller, so the other Tabs consumers get no new behavior. Tabs.tsx:97-99 compares against the current render's value, so activating an unselected tab never also reselects. Reselecting the exact Messages destination does nothing.
  • The panel id follows panelKey as an attribute, not a React key, so nothing remounts and aria-controls/aria-labelledby stay paired. Back/forward and channel-row openings keep the content intent, so the composer still autofocuses there. Join completion and the live-status Retry keep their explicit focusRequest handoffs.
  • The WorkflowsPage.test.tsx fixture change still exercises both replacement cycles and every assertion.

Optional

  • The compact browser journey (tests/browser/page-placement.spec.mjs:213-232) checks the Messages area marker in More pages, then dismisses the menu without activating Messages. The RTL compact branch (AppShell.test.tsx:508-516) checks only the callback and immediate main focus against a mocked destination. Activating Messages from compact overflow and asserting the returned conversation, the draft, and the settled focus would cover the real path.
  • CI at this head: Browser journeys (webkit, 1/6) failed me-workspace.spec.mjs:110 (the activity popup button was never found). The previous head has the same production code and passed that shard, so it looks like a flake. The failed jobs are rerunning.
  • The description has no buzz-review-completed.

Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
@wesbillman
wesbillman marked this pull request as draft October 11, 2026 17:01
Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
@wesbillman
wesbillman marked this pull request as ready for review October 11, 2026 21:00

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No actionable changes requested. The retry-focus finding is addressed in source; the keyboard-preview follow-up introduces no new source-demonstrated defect.

Star Lord’s automated source review via Wes’s account; head db46f0202b7fe93dd867b28040a2252606d9341e, base 37dcd836fbcf1613ddd27cece5a99e04d601bd64. Both attachments inspected; no tests or app execution. Required CI still fails on two 5-second ChannelMembersDialog.test.tsx timeouts; causality is unestablished and native acceptance remains unverified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants