Repository navigation
Conversation
Back/Forward preserves thread, Members, Profile and GitHub visits. Individual close controls retire their restoration path without adding a closed-content history stop. Restore read-only panel targets through the existing scoped conversation address and tab owner, retaining session revocation and focus handoffs. Build directly on main without importing the dialog-history branch's Settings policy. Keep terminal sessions, layout and bulk-close policy outside the trial. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Record the default for future dialogs in a canonical contract linked from the modal recipe and contributor instructions. Specify guards, handoffs, bulk close and required regression evidence while distinguishing existing implementation and reload limits. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Rename the policy to navigation-history.md, describe both surfaces from the start, and give content tabs their own section. Update contributor and design links without changing runtime behavior. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Dismiss every closed thread, Profile and GitHub visit in one transaction after the tab owner settles selection. Preserve the kept local tool for its matching navigation return and keep synchronous selection readers stable so restoration does not repeat during closure. Cover mixed inactive tabs, native Back/Forward, Profile bulk close and the Channel settings return boundary. Reload behavior and other dialog families are unchanged. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Apply explicit dismissal to Settings sections and Agents-page provider controls through the existing history drivers. Match logical dialog identity and scope without adding feature-owned history bookkeeping; preserve existing modal close guards. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Keep a surviving conversation tab or local side panel through the matching history return after individual close, reusing the bulk-close handoff. A retained conversation tab stays beside its current channel instead of routing the main workspace to that tab. Align browser assertions with explicit retirement and passive restoration, scope GitHub link triggers to the timeline, and wait for restored presentation ownership before opening another thread. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Address provider controls within Agent setup so Back closes the nested dialog first, Forward restores it, and explicit Close retires only its nested visits. Closing Settings retires the entire scoped presentation. Pass the existing navigation context and one feature callback through Settings without changing shared modal controls. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Reuse the existing geometry-settle helper before pointer-driven thread opening. The hosted WebKit repeat trace shows virtual timeline restoration moving the reply target beneath the pointer after native Forward. Preserve every navigation and reopening assertion without adding retries or extending timeouts. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Merge main ca26041 without rewriting the published experiment. Retain its Mac keep-awake settings, thread conversation links and agent sharing/configuration behavior alongside routed provider controls. Supply the required profile map in the new tab tests; preserve all mainline assertions. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Replace side-experiment framing with integrated behavior and move comparison steps to the PR description. Clarify the existing provider Back/close guard boundary and mark the legacy return/replace option unsuitable for explicit Close. Keep reconnect policy and executable source unchanged. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Reuse the pending return marker for explicit dismissal and retain it through skipped native entries until an eligible visit commits. Repeated Close retires newly closed visits without queuing another traversal. Cover repeated and successive closes with deferred native events, plus resetting the guard after the return. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Use rootOfTab for individual and bulk visit retirement and surviving-thread fallback addresses. Hold exact reply resolution in built-app regressions to cover X, Close all, Close others and keeping the unresolved tab, then exercise native Back/Forward without restoring the closed visit. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Save every panel tab descriptor and defer background content until selection. Rebuild channel context from the current session; retain unavailable tabs and require Resume when a contribution has no safe recovery contract. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Complete pending returns when traversal bounces to the current replacement. Ignore duplicate hashchange before considering retirement so a newly started dismissal keeps one queued traversal. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Declare static panel icons in registration metadata and reuse them in channel menus and workspace tabs. Canvas keeps its existing document icon through deferred mounting and refresh. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Model session storage for browser and native community fixtures. Assert that leaving clears only the departed workspace and a failed device-record save leaves it untouched. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Wait for the queued initial-focus effect before sending Escape. Keep the same focus assertion and restoration expectations. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Keep Profile and GitHub addresses while the relay scope is temporarily unknown or unchanged. Retire them once a different account/community scope is known, while retaining existing session authority revocation. Document the deliberate same-scope reset tradeoff and cover the boundary plus native Profile restoration. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Preserve the reviewed restoration commits while integrating the scope-based reconnect fix from #1767. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Match the existing dormant recovery contract while preserving old callback revocation checks. Prove that unreviewed panel content stays unmounted until Resume creates a fresh context, which is revoked again on disconnect. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
|
🤖 Restacked on #1767 at 05adfe6, with head b006526. Merge fdfe0b3 preserves every original feature commit; range-diff confirms the six original non-merge commits are unchanged. The integration also corrects a pre-existing Context probe expectation in b006526. The probe failed in Chromium and WebKit at aa4c7a1 because restored unreviewed content presents Resume rather than removing the panel. The updated check preserves old-callback rejection, verifies no fresh content mounts before Resume, and verifies the new context is revoked on disconnect. No production recovery policy changed. Validation: all nine page-owner cases and all 24 Profile browser cases pass on the integrated source across Chromium/WebKit. All eight built reload/dismissal cases passed at merge fdfe0b3; the final follow-up changes only the Profile test. Typecheck and mandatory push checks passed. Current-head CI is running: https://github.com/block/buzz-app/actions/runs/40642560897. Independent review and native desktop acceptance remain pending. |
Rebuild workspace, panel and visit descriptors from validated schema fields rather than carrying unknown stored properties into live state. Covers load and write-back with an adversarial snapshot. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Seed device cleanup coverage with the codec-owned storage key so a future version change cannot silently leave workspace data behind. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Terminal and Todos do not restore through content-tab hydration. Remove unused recovery declarations and the unreachable terminal mount branch; retain existing drawer and process lifetime behavior. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]> * commit '9051f1a75d20343301771ad6eeba1376d2e338ea': fix(channels): prune only navigation adapters with retained-window support
Check programmatic departure before writing history; native traversal keeps its existing bounce-and-decide path. Keep the discard decision scoped to the target, and assert cancellation preserves the return window and Forward affordance. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
…ournals Use a dedicated identity-storage key so the restoration follow-up can retain its existing saved-target journal without a name or payload collision. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
…ace restoration Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
|
🤖 Restacked on #1767 The transient-detail policy, guarded Settings traversal, agent dismissal and 100-visit window preserve workspace recovery, live handoff, reset/access-denial release and leave purge. The bounded identity journal remains separate from saved visit targets. The reload setup now explicitly reopens the replaced Profile and verifies each resulting return stop; all original reload, pane, selection and dismissal assertions remain. Validation: 129 owner/storage/purge/controller cases and TypeScript pass at the final integration. The combined source’s complete Profile file passed 28 Chromium/WebKit cases; the final reload file passed all 8 cases at |
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Preserve the plugin sidebar-section fixtures and the opt-in provider fixture together. Keep main’s offscreen focus filtering, sidebar wiring and all newer behavior and assertions. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Carry main’s sidebar and offscreen-focus behavior together with navigation-history fixes. Preserve the formatted tab-state call and all restoration lifetime rules. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> * tho/active-passive-history-main: test(channels): await initial dialog focus before Escape Signed-off-by: Taylor Ho <[email protected]>
|
🤖 Restacked on the current navigation integration without rewriting published commits. Published head: The exact Biome failure at the previous head reproduces before correction. Formatting the The merge preserves main’s sidebar/focus/review contributions and the restoration declarations, writer lifetime, reset/denied release and leave purge. The members-dialog focus synchronization now lives in the base; that test is unchanged from this PR’s previous head. No new restoration behavior or browser assertions changed in this follow-up, and no new restoration browser run is claimed. Earlier browser evidence stays pinned in the description. Fresh hosted CI and current-head human/native acceptance remain unverified. This PR remains draft. |
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> * tho/active-passive-history-main: fix(navigation): skip a retired destination after discarding edits fix(settings): wait for provider plugin activation before resolving Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
|
🤖 Integrated the provider/Discard fixes and repaired pending transient replacement at A normal new detail or thread replaces known transient placeholders as well as mounted details. Version-1 snapshots now preserve optional retention and replacement intent; older descriptors remain readable and are classified when their provider returns. Retained apps, channel tools, explicit panel pushes and the replaced content’s visit addresses remain intact. Malformed metadata is rejected. All 154 cases across the complete App-navigation, controller, tab-state, storage and conversation-panel files pass at this combined checkpoint, with clean TypeScript. The final pending replacement cases fail against the prior published source. Added explicit-push and legacy-thread checks caught an intermediate repair defect and pass with recorded intent. The publication gate caught an App update loop before transfer; keeping the pending state in a ref preserves the opener identity. All six identity assertions fail before correction, then all 66 cases in the complete panel-owner and App Profile files pass with clean TypeScript. Mandatory push lanes pass. Fresh CI, scoped re-check and current-head human/native acceptance remain unverified. This PR remains draft. |
Drop the legacy replaced descriptor flag and deferred classification. Current snapshots keep their keep-open metadata and continue to retire pending transient tabs when a new detail opens. Explicit pushes still append. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
|
🤖 Removed the prerelease workspace-snapshot compatibility path in Validation at that commit: all mandatory push lanes pass—TypeScript/related unit tests (86.06s), design checks (6.86s), Rust lane (0.41s). Three legacy-only unit cases removed; no browser cases changed or rerun. The scoped source-only re-check of |
Integrate main 61695ec after the #1767 squash merge, preserving both histories. Resolve duplicate-ancestry conflicts with the three-way content merge from the last incorporated #1767 head, 1390cf5. Preserve main Settings draft, discard, presenter and navigation test fixes. The added and removed restoration lines across all 34 feature paths are unchanged. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
Integrate main 5b2fdd6 without rewriting published history. Preserve Builderlab's current viewer sign-in and both panel policy declarations. Use the workspace's existing first-mount gate for recovered tabs and main's opt-in deferred panels. Ordinary panels retain eager initialization; restored background panels wait for selection under every panel policy. Keep main's six panel-lifetime cases unchanged and expand refresh coverage across default, explicit eager and deferred policies. Conflict-specific validation: 135 cases pass across seven complete owner files. The old loading rule fails four of main's six lifetime cases. Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz> Signed-off-by: Taylor Ho <[email protected]>
…2562) ## Summary Closing a routed panel tab calls `history.go(-1)` to retire its visit, and that return lands on a later popstate. Until it lands, the navigator still shows the closing visit, but the opener already has focus. If a Profile was reopened from the opener in that window, it was hidden when the return arrived, because entering the channel visit restored that visit's saved selection. This PR treats a reopen made before the return lands as superseding the close, so the panel stays open with a fresh visit of its own. ### fix(channels): keep a panel reopened before its close's return lands **Problem** Two WebKit journeys lost a Profile that was reopened right after its tab closed. Both failed on attempt 1 on unrelated branches, and the same SHA passed on rerun. - `profiles.spec.mjs:321` "profile plumbing: exact avatar/mention targets, thread enrichment, lifecycle and recovery", branch peon/participant-collator, run 41752442750 (webkit 13/14): https://github.com/block/buzz-app/actions/runs/41752442750/job/126320885261. After the Mic tab closed, Space on the focused mention never reopened the Profile, and the test failed at :479. - `solo-workspace.spec.mjs:159` "Solo chat Activity opens the agent's profile locally, restores per conversation and retires on plugin disable", branch peon/dnd-stable-transform, run 41757728323 attempt 1 (webkit 5/14): https://github.com/block/buzz-app/actions/runs/41757728323/job/126338025674. After the agent's Profile closed, Enter on the focused Activity trigger never brought back "Profile details". **Cause** - Closing a routed panel tab removes it locally. `dismissContent` then calls `navigator.dismiss`. The browser driver retires the visit and calls `history.go(-1)`, which lands on a later popstate. Until then the navigator still shows the retiring visit, while `afterClose` has already focused the opener. - A reopen in that window runs `open()` and then `visitPanel`. `visitPanel` sees the navigator already on that Profile's detail, so it doesn't navigate. When the return lands, the route effect enters the channel visit, and `enterVisit` restores that visit's saved selection, which hides the reopened panel. Reopening a different panel pushes its visit on top of the retiring one, and the late return then removes it. - Both traces show this sequence: - Profiles run: the close finished at 80128 ms and Space ran at 80148–80193 ms with the URL still on Mic's detail. By 80383 ms the URL was the channel and the dock was closing. - Solo run: the close finished at 251783 ms and Enter ran at 251808–251872 ms. By 251999 ms the URL was the channel and the dock was hidden. - The dock isn't the cause. `PanelDock` keeps a reopened subtree through its exit (`PanelDock.test.tsx`). **Fix** - When a dismissal retires the current visit and the navigator hasn't moved yet, `dismissContent` records a pending return. A synchronous driver or a replacing dismissal moves the navigator at once, so neither records anything. - While that return is pending, `visitPanel` records the panel as the reopen instead of navigating. - When the return lands on the dismissal's fallback, the route effect checks whether that panel is still selected. If it is, the effect reselects and opens it, which gives it its own visit. If the panel has closed again, or the return landed somewhere else, nothing is revived. - A later close before the return lands, such as closing the thread beneath a closed Profile, moves where the return lands. The record follows the latest fallback and keeps the reopen. - While the return is pending, the route effect no longer reapplies the retiring visit's detail. Otherwise a re-render could reopen the panel that was just closed. - Two new cases in `profiles.spec.mjs` hold `history.go` and pause the dock's exit while a Profile closes. One reopens Mic by click and by Space. The other closes a thread's Profile and then the thread, and opens Mic. Each asserts that the Profile shows while the return is held, the navigator stays on the closed visit with exactly one return held, a fresh Mic visit lands after the release, and Back returns to the channel and closes the panel. The existing journeys are unchanged, and no assertions are loosened. **Residual limit** Thread and other explicit navigation during a pending return is not deferred, so a late return can still undo it. #2113 lists the same limit. **Validation** Local Linux workstation, Playwright 1.63.0, `--no-deps --retries=0`: - New cases without the fix: 4/4 failed. The reopen case failed at :406 (no fresh visit). The thread case failed at :442, with the click stacking a visit on the closed one. With the fix but without the latest-fallback step, the thread case failed 2/2 at :452. With the fix, all pass. - Adversarial ordering, with temporary changes that delayed `history.go` calls from click or key handlers by 250 ms, 6 repeats per engine: - Without the fix: 9/24 failed, seven with the CI signatures. - With the fix: 24/24 passed, twice. - Natural contention, with 4 CPUs shared with 4 busy loops, 2 workers and 10 repeats per engine: 40/40 passed with the fix. - The 38 browser specs that close panel tabs, both engines: all passed at 6 workers. At 12 workers, 42 failed on timeouts because the test server had stopped serving under load, and those 42 passed on rerun. - Vitest for channels, panels, navigation and channel-navigation: 87 files, 1539 tests passed. - Rebased on main `f4805605f5`: - `tsc --noEmit`, `biome check --error-on-warnings` on the changed files and `git diff --check` are clean. - Vitest `src/bundled/channels`: 61 files, 1097 tests passed. - `profiles.spec.mjs` and `solo-workspace.spec.mjs` in full, WebKit and Chromium, `--workers=4 --retries=0`: 48/48 passed. A green run doesn't prove the flake is gone. Hosted CI covers broad validation. ## Coordination - #2415 (open) rewrites the same route effect in `ChannelsPage.tsx` around a `dismissalReturn` record. Its head `9555c2ed` conflicts textually with this branch. Whichever PR lands second should fold this PR's pending `reopen`, and the guard against reapplying the retiring visit's detail, into the other's record. - #2113 (open) restores panel opener focus after history returns. It doesn't overlap these hunks, its head merges cleanly with this branch, and it lists the same residual limit described above. - Draft #1793 (stale since 2026-10-29) also edits `ChannelsPage.tsx`, and its head conflicts textually with this branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: mtoohey <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
…2803) ## Problem Close all tabs (right-click a tab, ⌘⇧W, or the macOS **Close All Tabs** menu item) closes the whole right pane. A launcher's side panel such as Todos goes with it, even though it isn't a tab. - Its launcher still reads pressed (`aria-expanded="true"`), so the first press "closes" the hidden panel and nothing visible happens. Only the second press brings Todos back. - **Toggle tab pane** and **Back** to the channel visit end the same way: Todos is hidden and the launcher reads pressed. Close all tabs from that earlier channel visit also hides Todos. ## Fix Close all tabs now leaves a launcher's side panel as it was: shown in an open pane, hidden in a closed one. The launcher reads pressed only while the pane shows the panel, so one press always brings it back. Production change: 23 lines added and 5 removed, in 4 files. 1. **`closeTabs`** (`useChannelTabState.ts`): `paneOpen: side || (s.paneOpen && !!s.sidePanel)`. A pane showing a launcher's panel stays open, and a closed pane stays closed. 2. **`onCloseTabs`** (`ChannelsPage.tsx`): when nothing is kept and a launcher's side panel is selected, shown or hidden, it dismisses toward the `"side"` pane, not `"main"`. - `dismissContent` then records the existing side-panel survivor from #1767, so the return to the channel visit keeps Todos. That's the same path that keeps Todos when its last tab closes with X. - The kept-return marker records the pane's pre-close visibility: a kept tab shows, and a side panel keeps `paneOpen` as it was. The matching return applies it after `enterVisit`, so a channel visit that saved an open pane (a retained thread or Todos was in it) doesn't reveal the Todos the user had hidden before ⌘⇧W. 3. **Launcher `pressed` and `toggle` via `shown`** (`useChannelPanels.tsx`): a side panel counts as `shown` only while the pane is open, and `pressed` and `toggle` use `shown`. - On a hidden panel, one press goes through `onOpen` (`setPaneOpen(true)`) and shows the pane with the panel. - `selected` is unchanged, so the panel stays retained while the pane is hidden. - `ChannelPanelTabs` gains `paneOpen`. `useConversationTabs.tsx` passes `tabState.paneOpen`, the flag its tab-placed `pressed` already reads. Close other tabs on a main-pane tab now keeps the side panel beside it too. **Preserved:** - [Navigation history](https://github.com/block/buzz-app/blob/main/docs/navigation-history.md): closed thread visits are still retired together. Back and Forward still skip them, and no visit's saved pane state changes. Back to a channel visit still restores that visit's saved pane. Where it was hidden, Todos stays hidden, but the launcher now reads unpressed and one press shows it. - No visual change. Pressed now matches what's on screen. **Docs:** - `shell-design.md` describes Close all tabs and hiding the pane for side panels. - `plugin-architecture.md` defines `pressed` for `channelPlacement: "side"` as "shown in the open pane", not merely selected. ## Tests **Vitest** (`useConversationTabs.test.tsx`; `launcherHarness` gains a `placement` parameter): - "Close all tabs leaves a launcher's side panel as the open/closed pane showed it": - The open case fails on main. - The closed case fails if `closeTabs` drops the `s.paneOpen &&` guard. - "a side panel's launcher reads unpressed while the closed pane hides it, and one press shows it": catches the launcher change. - "Close other tabs on a main-pane tab keeps a launcher's side panel open beside it." **Journey** (`tests/browser/todos.spec.mjs`): "Todos isn't a tab: Close all tabs keeps it, and its launcher tracks the pane". This is one new browser case; none were removed. - **Why it needs a browser:** Close all tabs retires the thread's history visit, and the page's return to the channel visit has to keep the side panel. jsdom page tests don't route visits through navigation. - **What it covers:** 1. Toggle tab pane hides Todos with the launcher unpressed, and one press shows it. 2. Close all tabs from a thread visit keeps Todos. 3. Back to a channel visit that saved a closed pane hides Todos, one press shows it, and Close all tabs from there keeps it. Since #2838 a channel visit showing Todos saves an open pane, so the journey hides the pane there first. 4. ⌘⇧W from a thread visit with the pane closed doesn't reveal Todos, even when the channel visit it returns to saved an open pane. - **What fails without each part:** - On main, it fails at the first launcher check after Toggle tab pane. - With the `closeTabs` change reverted, it fails at Close all tabs from the channel visit. - With only the launcher change, Todos is gone after Close all tabs. - If the return drops the kept-return marker's `paneOpen`, it fails at the ⌘⇧W step from a closed pane. **Fail before, pass after:** - With only the first commit's tests applied to main, Vitest fails 2 of 42. The journey fails in Chromium and WebKit, where `aria-expanded` is "true" after Toggle tab pane. - With the fix: - 139 Vitest tests pass across `useConversationTabs`, `useConversationTabs.panels`, `useChannelPanels`, `useChannelTabState` and `ChannelsPage.tabs`. - `todos.spec.mjs`, `tab-close-others.spec.mjs` and `right-pane-dismissal.spec.mjs` pass in Chromium and WebKit, 32 tests in all. So does the layout.spec case "Todos stacks beside threads". - `tsc --noEmit` and `biome check --error-on-warnings` are clean. - These local runs were on the same three patches before the rebase onto 067db60. That rebase brought in only #2587 (inbox, thread and unread files), which touches none of this PR's files. The later rebase onto be67480 brought in #2838 (retained side panels); the journey's Back setups were reconciled with it in 2a30ea9 and have not been run locally since. ## Not checked - The native ⌘⇧W menu. The browser runs dispatch `buzz:close-all-tabs`, the window event the native menu sends. The page handled it (`preventDefault`), and the result matched the right-click menu. - The desktop app. ## Related - **#2415** reworks the same dismissal-return code. This change depends on `dismissContent` keeping `(pane === "side" && tabState.sidePanel)`. Whichever of the two lands second must keep that condition. - **#1793** (draft, conflicting) rewrites `closeTabs` and changes `onCloseTabs`. If it's revived, it needs the same two rules: - A pane showing a launcher's side panel stays open on Close all tabs. - `onCloseTabs` dismisses toward the side pane while a launcher's side panel is selected, and the return reapplies the pane's pre-close visibility. ## Before and after Both runs used the same scripted steps on the same timeline. Each run checked the expected state at every step, and both passed. | Step (starts at) | Before: main @ 3bcf1c4 | After: this branch @ 2c81a4905 | |---|---|---| | 1. Press **Toggle channel todos**, then open the thread from its reply chip (3.6 s) | Thread tab above Todos. Launcher pressed. | Same. | | 2. Right-click the thread tab, choose **Close all tabs**, hold about 3.5 s (10.0 s) | The whole pane closes, and Todos goes with it. The launcher still reads pressed, so the badge turns red. | The tab closes. Todos stays and fills the pane. Launcher pressed. | | 3. Open the thread again, then ⌘⇧W (16.2 s) | Reopening brings back the thread with Todos, because Todos was never deselected. ⌘⇧W then hides both, and the launcher still reads pressed. | ⌘⇧W closes the tab and Todos stays. Launcher pressed. | | 4. Open the thread again, then **Toggle tab pane** (23.2 s) | Pane and Todos hidden. Launcher **still pressed**. | Pane and Todos hidden. Launcher **not pressed**. | | 5. Press the Todos launcher once (30.0 s) | Nothing visible happens. The launcher flips to not pressed and Todos stays hidden. | The pane comes back with the thread tab above Todos. Launcher pressed. | 2c81a4905 has the same fix and tests as this PR, from before the rebase. The only later commit changes a comment and docs. The video was recorded in Chromium against the fixture relay, with ⌘⇧W sent as the `buzz:close-all-tabs` event. Back, and Close all tabs from the earlier channel visit, are covered by the journey but not shown in the video. https://github.com/user-attachments/assets/c40bd79d-7668-4511-b065-915e533ea6e6 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: mtoohey <[email protected]> Signed-off-by: Matt Toohey <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
…l switches (#3060) ## Why Since #2838 keeps a channel's side panels open when you return to it, the GitHub tab survives a channel switch but its panel is unmounted and remounted. Coming back to the channel therefore started the pull request panel over: **Changes** fell back to **Summary**, and a **Submit review** draft (decision, text, and the head it was written against) was lost. ## What changes The GitHub plugin owns a small per-session panel memory, created once in `apply` beside the pull-state store (`src/bundled/github/panel-memory.ts`): - **View** (Summary/Changes) is remembered per channel + pull request URL. A first open from another channel starts on Summary with the title. - **One review draft per pull request URL**, from any channel, matching GitHub's one pending review per pull request. - The draft **leaves memory when a submit starts** and **returns only if the submit fails** (unless another panel has started a new draft since). A panel remounted mid-submit, or a second tab on the same pull request, cannot offer the in-flight review again. `Use latest commit` is disabled while a submit is in flight and the write effect skips while busy. - **Text-less decisions are not remembered.** A Request changes or Approve with no comment stays in the mounted panel only. - A **text-less Approve still sees the "New commits were pushed" notice** after a push, with Submit disabled, as on main. Only an empty Comment silently follows the latest head. - Memory lasts the **app session** per plugin activation: a reload or turning the plugin off and on forgets it. - **Scroll position is not restored**; that needs a host change. `docs/buzz-help.md` gains a bullet describing this. #1793 (restore tab workspaces across refresh) is rebase-only against this; there is no behavioural interaction. ## Before and after Before is on the left at `origin/main` `a905c4865f` (the fix commit's parent); After is on the right at `a5ad4ba667`. Watch the GitHub panel on the right of each half. https://github.com/user-attachments/assets/713b8646-29e0-4447-a96b-5b27b8a58a72  | Moment | Before (`a905c4865f`) | After (`a5ad4ba667`) | |---|---|---| | Open the PR from a message link in #Alpha, switch to Changes, open Submit review, choose Request changes, type a sentence, Escape, switch to #Beta | Same on both sides | Same on both sides | | Back in #Alpha (~15 s) | **Summary**, description from the top, no diff | **Changes** selected, diff showing | | Reopen Submit review (~18 s) | **Comment**, empty box, Submit disabled | **Request changes** with the sentence, Submit enabled | | Submit (After) or Escape (Before) | Nothing to submit | Submits; "Review submitted" toast | | #Beta and back, reopen Submit review | Empty Comment | Empty Comment: the sent draft is gone | The recording is the production browser build with the plugin's `gh` client and diff reader swapped at build time for an in-memory stub so Changes and Submit review are available outside the desktop app; nothing else is faked. ## Test plan - `GitHubPanel.test.tsx` 52 → 60 passing; GitHub plugin suite 345 → 355 passing across 21 files. `tsc --noEmit` clean; Biome clean on changed files. - Each new test was confirmed to fail with its fix removed: the remount case on main; the mid-submit remount cases without the clear-at-start / put-back-on-failure; the fresh-open-from-another-channel case with a view keyed by URL alone; the whitespace-only draft case with the old empty rule; the text-less Approve push case without the head rule; the two-panel case without the re-read branch; the mid-submit push case with the busy guards removed. - Not checked by hand in the desktop app with `gh` signed in. ## Known limits - Two tabs on the same pull request share one draft; the last one to type wins and the other panel takes over the draft when its popover reopens. - Closing a tab on purpose and reopening the pull request in the same channel still restores the remembered view. - A draft on a pull request that merges or closes first lingers unseen until the session ends, since Submit review goes away once closed. - No scroll restore. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: mtoohey <[email protected]> Signed-off-by: Matt Toohey <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
Overview
Category: improvement
User Impact: Open conversation, thread and panel tabs return after a refresh with their layout and selection preserved.
Problem: Refresh discards the channel workspace and the visit associations needed to keep explicitly closed tabs out of Back/Forward history.
Solution: Save versioned, account/community-scoped addresses and layout in window session storage, then resolve them through current registrations and permissions. Restored background content waits for selection; unavailable providers keep closeable tabs, and providers without a recovery declaration require Resume before mounting.
Changes
File changes
docs/channels.md
Describe same-window workspace recovery, dismissal of pre-refresh visits, and unsupported persistence boundaries.
docs/navigation-history-behavior.md
Document saved visit targets, restored tabs, the 100-visit managed window and best-effort storage limits.
docs/plugin-architecture.md
Document address recovery, static icons, explicit reopening and same-scope handoff responsibilities.
src/bundled/builderlab/apps/index.ts
Declare existing app views safe to recover.
src/bundled/channels/ChannelHeaderMenu.tsx
Use a contribution's declared icon in its menu entry.
src/bundled/channels/ChannelsPage.tabs.test.tsx
Exercise the real page remounting into the same or a different channel, preserving only the intended menu tab lifetime.
src/bundled/channels/ChannelsPage.test.tsx
Cover the connection-generation reset boundary and account/community isolation.
src/bundled/channels/ChannelsPage.tsx
Keep the existing scope/generation boundary; let the tab store retire menu tabs across page changes and refresh.
src/bundled/channels/canvas-panel.tsx
Declare safe view recovery and the existing document icon before mounting the editor.
src/bundled/channels/tab-workspace-storage.test.tsx
Cover reloads into another channel, provider ordering, isolation, stale writers, failed-save cleanup, strict decoding and reset/denied-cache succession before the old workspace unmounts.
src/bundled/channels/tab-workspace-storage.ts
Validate owned tab, panel and visit descriptors, including menu retirement and retention policy; remove stale snapshots after failed writes.
src/bundled/channels/useChannelTabState.test.tsx
Cover restored thread pane placement and saved request boundaries.
src/bundled/channels/useChannelTabState.ts
Persist workspaces, retire menu tabs in other channels, and hand off mounted state from the current scope writer independently of caller order.
src/bundled/channels/useConversationTabs.test.tsx
Verify provider availability, eligibility, retained draft/DOM identity, pending transient replacement, explicit pushes and stable opener identity. Refresh coverage checks default, explicit eager and deferred panel policies.
src/bundled/channels/useConversationTabs.tsx
Defer recovered background tabs through the existing workspace mount gate while preserving ordinary eager initialization and opt-in deferral. Rebuild eligibility/context, retain unavailable tabs and replace transient placeholders while preserving explicit pushes.
src/bundled/github/index.tsx
Declare GitHub view recovery.
src/bundled/huddles/index.tsx
Declare recovery of the Huddles view without joining a call.
src/bundled/profiles/index.tsx
Declare Profile view recovery.
src/features/communities/device-state.test.ts
Check scoped cleanup, late-write rejection and same-window rejoin without inheriting purged state.
src/features/communities/device-state.ts
Invalidate the live tab writer and clear scoped workspace data when leaving a community.
src/features/communities/service.test.ts
Model session storage in browser/native service fixtures; check leave cleanup and preservation when the required device-record save fails.
src/features/messages/tab-workspace-lifetime.ts
Own the window-session storage key, release live handoff on reset, and invalidate the writer before leave cleanup.
src/features/navigation/browser-history.test.ts
Cover persisted return targets, branch truncation and corrupt visit storage.
src/features/navigation/browser-history.ts
Retain the history visit lookup through reload so dismissal can retire older visits.
src/features/relay/service.ts
Release live tab-workspace handoff before reset or access-denied session disposal, preserving saved recovery descriptors.
src/features/panels/PanelView.test.tsx
Verify ordinary, declared-safe and explicit-Resume mounts.
src/features/panels/PanelView.tsx
Require Resume for restored providers without an audited view-recovery declaration.
src/features/panels/PanelWorkspace.test.tsx
Check deferred first mounts, retained state and replacement openings.
src/features/panels/PanelWorkspace.tsx
Gate the first content mount while retaining already-shown content and its drafts.
src/features/panels/service.test.tsx
Reject invalid recovery declarations.
src/features/panels/service.ts
Expose recovery metadata and an optional design-system icon without changing target resolution.
tests/browser/profiles.spec.mjs
Exercise session-bound callback revocation and fresh authority without interrupting a retained panel, including held successor discovery and DOM identity after the new context commits; cover Profile navigation and layout.
tests/browser/tab-workspace-reload.spec.mjs
Exercise real document reloads, pane/selection restoration, pre-reload visit dismissal, Settings, lazy Canvas, stable icons and unavailable providers in Chromium and WebKit.
tests/fixtures/profiles.tsx
Prepare signed discovery before publishing a retained successor, keep the generation unchanged, and expose a discovery gate for adversarial ordering.
Reproduction steps
Validation and limits
97afd1d682a0merges main5b2fdd699into published head2f3044978244without rewriting either history. The feature still changes the same 34 paths relative to its base.97afd1d682a0, all mandatory push gates passed: TypeScript/related unit tests 73.89s, design checks 5.20s, Rust/Clippy 0.44s, plus destination/history policy checks. Origin and hosted PR head match. GitHub reports the PR mergeable against main; fresh hosted CI is pending.d5d0cf528f09; the earlier Profile fixture repeat passed 240 executions at4f2463a20e7b. Those runs do not establish current-head native acceptance.Screenshots
Real built application with disposable fixture data, captured at the icon checkpoint before the lifecycle follow-up. These compare before/after refresh; the current lifecycle changes are exercised by the browser cases above.