feat(desktop): side panel tabs appear only once the plan has used them
What
Dean: "Are any tabs always open even if they never got used? ⦠they only get added to the right side panel if they are used for something." Yes: Board, Code, Transcript and Work were open on every plan. Now a fixed tab is in the strip only once the open plan has used it:
| Tab | Used when |
|---|---|
| Board | the board has any element (the developer's or Luke's drawing) |
| Code | Luke showed at least one snippet on the plan (#1956's feed) |
| Transcript | a call on the plan has a line, including one heard live before the record holds it |
| Work | Luke's planning model took at least one turn on the plan |
- Arrivals add the tab. Content arriving live, or read back when the plan opens (#1951's
/workand the existing board and transcript reads), adds its tab in its place in the order. #1851's auto-open still decides whether the panel opens on the first board or code. An added tab never takes the place of the tab you're looking at, even when it lands ahead of it in the strip. - Per plan, with your own choices on top. A tab you open from "+" (or its ā„ā shortcut) stays on that plan even if it's empty, and shows its usual empty state. Opening it counts as using it. A tab you close stays closed on that plan however much more arrives, and doesn't reopen on #1851's first arrival either, until you open it again from "+". Switching plans shows each plan's own tabs.
- Empty plan: no tabs. With the panel open, it shows one quiet line under the "+": "Luke's board, code, transcript, and work appear here as you plan." If you closed every tab on a plan, you still get the "No tabs open" rows to reopen them. The panel toggle (#1892) is unchanged.
- Reads landing one after another don't flip the shown tab. The host leaves a plan's work and code absent both before they're read and when there are none, so any kind going from not-held to held counts as an arrival and keeps the shown tab. Devin found the first-snippet case.
- A plan with no fixed tab but a coding agent shows the agent's tab instead of an empty body.
How it merges with the stored state (least complexity)
The window keeps one global order and one chosen tab, as before. Each plan keeps only what you overrode: plans[planId] = { opened, closed }, capped at the latest 500 plans like closedAgents. The strip for a plan is the global order, filtered to kinds that are opened, or that hold something and aren't closed. A chosen tab the open plan doesn't have reads as that plan's first tab. The kept value stays, so going back to a plan that has it shows it again.
- Migration: no new storage key and no version bump. The stored
tabsfield now means every kind, in order. An older value (all four kinds open, a subset, or the format from before tabs could close) loads as order only: kinds it had closed go last, and nothing is forced open on a plan that hasn't used it. The width, the open/closed panel and the chosen tab carry over. Tests cover all three old formats. - Behaviour change, deliberately: "+" (or a shortcut) reopens a fixed tab in its place in the order, not at the end of the strip. Content-driven tabs have to land in a stable order, and moving a kind to the end on one plan would reorder it on every plan. Dragging still sets the order for every plan.
usePanelArrivalsstopped reopening tabs (onAddis gone). It now skips a tab the developer closed on the plan, and the strip adds arriving tabs by itself.- Fixture and evidence runs stay staged. The staged state is the order, and the fixture plans' own content decides the strip, so it never reads storage.
Screenshots (headless Chrome, the real DesktopShell, useSidePanel and stylesheet, synthetic data)
Empty plan, panel open:

A plan with only a transcript:

A plan that has used every kind:

Tests
use-plans-tab.test.ts(throughusePlansTab):- A new plan has no tabs, and the first transcript line, work turn, snippet and board element each add their tab without taking the shown tab's place.
- A line heard live adds Transcript.
- A first snippet arriving after an absent code read is added ahead of the Transcript tab you're looking at, with a dot, without replacing it.
- A closed tab stays closed as more arrives, opens nothing, and reopens via "+".
- Each plan shows its own tabs.
- Luke's first drawing adds Board, with a dot while another tab is shown.
use-side-panel.test.ts(throughuseSidePanel):- Old stored state, in all three formats, loads as order only.
- A tab opened on an empty plan stays across a launch, on that plan alone.
- An agent-only plan shows its agent.
- Dragging among a plan's few tabs keeps that order on a plan that has them all.
desktop-plans.test.ts:- The empty plan's quiet line, and "+" opening Board on it.
- Reopened tabs stand in their place.
./scripts/check.sh exits 0.
CI could not verify the Mac UI, and ./scripts/verify.sh was not run (Linux cloud VM). The screenshots are headless Chrome, not Electron. LUKE-159 is the manual Mac pass.
š¤ Generated with Claude Code