Repository navigation
fix(channels): keep a channel's side panels open when returning to it - #2838
Conversation
Leaving a channel retired its channel-menu tabs (Canvas and plugin channelMenu panels), so returning showed the conversation without the panel the viewer had opened. Keep them in the channel's workspace like every other tab: they unmount when the viewer leaves, remount after eligibility is checked again on return, and close only when the viewer closes them, the plugin is disabled, or the channel stops being eligible. Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matthew Boston <mboston@squareup.com>
…witches Channel-header launcher panels (Huddles and Todos in the side column, Terminal in the bottom drawer) held one opening for the whole page and cleared it whenever the channel changed. Keep one opening per channel instead: leaving a channel unmounts its drawer, and returning shows it again until the viewer closes it. Disabling or replacing the plugin still closes it in every channel, and re-enabling starts closed. Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matthew Boston <mboston@squareup.com>
The Canvas journeys asserted that leaving a channel retired its Canvas tab. Assert the new behavior instead: the tab is still open and selected on return and Edit still holds the channel's draft. The assertions do not depend on which Canvas view shows first, since a return can either keep or remount the panel. Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matthew Boston <mboston@squareup.com>
The previous commit kept every launcher drawer open per channel in page state. That restored the Terminal drawer on return, which takes keyboard focus on mount, and it still lost the drawer on a trip to another page. Keep only side-column panels (Huddles, Todos) open, and keep them in the channel's session tab store next to its threads and Canvas, so they also survive leaving the page. The bottom drawer closes on leaving the channel as before. Closing a restored panel whose launcher was replaced focuses the page's fallback target instead of the body. A menu tab whose channel became ineligible while the viewer was away is dropped on return. Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matthew Boston <mboston@squareup.com>
The retained side panel opening carried its launcher element into the session-lifetime tab store, which retains no DOM elements by contract and would keep a detached page alive after the viewer left it. Store only the panel, context and target; the trigger stays in a page-local map, and a close after the page was replaced falls back to the page's focus target. Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matthew Boston <mboston@squareup.com>
… Canvas journey main now labels the channel heading as a Manage members button, not a tab. Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matthew Boston <mboston@squareup.com>
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45bdb82a7d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The history snapshot treated a side pane holding only a retained launcher drawer (Todos, Huddles) as empty and recorded it closed, so Back to that visit hid the drawer even though it was never closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Reviewed at 5672c42. No actionable defects remain; the drawer-only history regression is fixed. Chromium and WebKit lifecycle checks pass. Inactive tabs within the current workspace retain the existing eager-mount behavior; native Huddles/terminal integration and sustained performance benchmarks remain unverified.
…leanup * origin/main: feat(agents): let app-hosted agents read their owner's channels and members (#2745) NEXUS-4271: Expose installed plugin status (#2754) fix(menu): stop clipping lifecycle actions behind a hidden scrollbar (#2716) feat(agents): give approved external apps the selected agent's full Buzz tools (#2707) AIDA-2549: Keep Claude settings drafts in sync (#2841) fix(channels): keep a channel's side panels open when returning to it (#2838) fix(builderlab): clarify remote-agent account availability (#2764) fix(sidebar): rename Remove from Team to Close conversation (#2845) perf(channels): cut header and name-matching work in large channels (#2726) fix(browser): silence a browser page once its tab goes away (#2721) Community Mesh: shared compute plugin (including agent type) (#941) perf(relay): cut main-thread work from profile updates (#2730) fix(composer): lead the channel placeholder with its sidebar icon (#2829) test(activity): scope preview assertions to the request row (#2843) perf(search): fold people names once across palette keystrokes (#2570) fix(github): keep PR watches running when localStorage is full (#2699) Make drafts and sent part of Messages (#2728) fix(canvas): render every Canvas within the save limit (#2839) # Conflicts: # .github/workflows/release.yml
…ime-hint * upstream/main: ci: give the native job room for a cold Rust cache (block#2867) fix(pairing): refuse unknown communities natively and name the requesting process (block#2835) Add per-interest Attention pause and management controls (block#2847) feat(drafts): show where unsent drafts wait (block#2853) feat(agents): let app-hosted agents read their owner's channels and members (block#2745) NEXUS-4271: Expose installed plugin status (block#2754) fix(menu): stop clipping lifecycle actions behind a hidden scrollbar (block#2716) feat(agents): give approved external apps the selected agent's full Buzz tools (block#2707) AIDA-2549: Keep Claude settings drafts in sync (block#2841) fix(channels): keep a channel's side panels open when returning to it (block#2838) fix(builderlab): clarify remote-agent account availability (block#2764) fix(sidebar): rename Remove from Team to Close conversation (block#2845) perf(channels): cut header and name-matching work in large channels (block#2726) fix(browser): silence a browser page once its tab goes away (block#2721) Community Mesh: shared compute plugin (including agent type) (block#941) perf(relay): cut main-thread work from profile updates (block#2730) fix(composer): lead the channel placeholder with its sidebar icon (block#2829) test(activity): scope preview assertions to the request row (block#2843) perf(search): fold people names once across palette keystrokes (block#2570) fix(github): keep PR watches running when localStorage is full (block#2699) Signed-off-by: Cheng Peng <cheng@block.xyz> Co-authored-by: Cursor <cursoragent@cursor.com>
…ions-literal-markdown * upstream/main: (38 commits) fix(composer): let Option+Up/Down switch conversations from an empty composer (block#2832) fix(reactions): Scope timed reactions to the selected video (block#2796) fix(communities): name what the Join dialog is doing instead of "Working…" (block#2822) docs(agents): explain the per-conversation MCP cost of Claude agents (block#2683) fix(avatars): keep unsaved avatar drafts and focus, and validate the URL late (block#2714) fix(agents): offer stream and forum connection setup only to channel members (block#2749) Describe Attention as what the agent does, not its tools (block#2865) Let a local Codex agent join its owner's huddle by voice (block#2321) ci: give the native job room for a cold Rust cache (block#2867) fix(pairing): refuse unknown communities natively and name the requesting process (block#2835) Add per-interest Attention pause and management controls (block#2847) feat(drafts): show where unsent drafts wait (block#2853) feat(agents): let app-hosted agents read their owner's channels and members (block#2745) NEXUS-4271: Expose installed plugin status (block#2754) fix(menu): stop clipping lifecycle actions behind a hidden scrollbar (block#2716) feat(agents): give approved external apps the selected agent's full Buzz tools (block#2707) AIDA-2549: Keep Claude settings drafts in sync (block#2841) fix(channels): keep a channel's side panels open when returning to it (block#2838) fix(builderlab): clarify remote-agent account availability (block#2764) fix(sidebar): rename Remove from Team to Close conversation (block#2845) ... Signed-off-by: Cheng Peng <cheng@block.xyz> Co-authored-by: Cursor <cursoragent@cursor.com>
…ter Back Follow-up review for the Close all tabs side-panel fix. - useConversationTabs: the Close all tabs case now also runs with the pane closed. The side panel stays hidden and its launcher reads unpressed. This fails if closeTabs drops the paneOpen guard. - todos.spec.mjs: the journey goes Back to the channel visit. Since #2838 a channel visit showing Todos saves an open pane, so Back shows Todos with the retained thread tab. Hiding the pane there saves it closed: the next Back hides Todos with the launcher unpressed, one press shows Todos and the thread tab again, and Close all tabs from that channel visit keeps Todos shown, with the launcher pressed. This fails if closeTabs no longer keeps the pane open. - todos.spec.mjs: a channel visit showing Todos beside a retained thread saves an open pane when a second thread opens. Hiding the pane before Close all tabs still returns there with Todos hidden, and one press shows it. This fails if the return reapplies that saved open pane. - shell-design.md: Close all tabs leaves a side panel as it was, shown in an open pane or hidden in a closed one. Hiding the pane hides the panel without closing it. The launcher reads pressed only while the pane shows the panel, and one press brings back the pane with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: mtoohey <mtoohey@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…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 <mtoohey@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…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 <mtoohey@squareup.com> Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Why
Opening a side panel in a channel, going to another channel, and coming back showed the channel without that panel. Threads already stayed open per channel, but Canvas (and any plugin
channelMenupanel) was retired on every channel change, and launcher side panels (Huddles, Todos) were held once for the whole page and cleared whenever the channel changed.What changes
docs/plugin-architecture.md(channel menu panels, channel-header launchers) anddocs/buzz-help.md.Test plan
src/bundled+src/features/panels: 302 files, 5386 tests pass at 45bdb82.tscclean.channel-header-menu,channel-tabs,todos,huddles,terminal,canvas-history,channel-permissions,channel-modal-dismissal— 87 passed before rebasing onto main; after the rebase,channel-header-menu,channel-tabs,todos— 28 passed at 45bdb82. The Canvas journeys were updated from "leaving retires Canvas" to "Canvas is still open on return with its draft"; the new Canvas journey failed before the fix and passes after (Canvas return check repeated 3× per engine: 12/12).todos.spec.mjsnow expects the Todos panel still open after Activity/Bestie → Alpha.Browser cases changed: 2 Canvas journeys and 1 Todos assertion changed to the new behavior; none added or removed.
Noticed, out of scope
docs/plugin-architecture.md.🤖 Generated with Claude Code
Screenshots
Canvas open in design-review

Switched to Beta

Back in design-review: Canvas still open

After Close and a round trip: stays closed

Demo (browser fixture, Chromium)
demo.mp4