feat(desktop): pin and archive plans, and Delete becomes Permanently delete
The desktop half of Dean's ask: "Let's support pinning and archiving plans and rename delete to 'Permanently delete'." It builds on #1987, the service half (columns, PATCH, list filter), which has merged.
CI could not verify this Mac UI, and ./scripts/verify.sh was not run. This was built on a Linux VM, and CI builds nothing for the Mac. LUKE-159 is the manual Mac pass. The screenshots below are the renderer drawn in headless Chrome over the test fixtures (plansControl), not the Electron app.
What changes
Plan menu (toolbar ⋯ and sidebar right-click, plan-actions.tsx):
- Adds Pin / Unpin and Archive, with icons and no "…". An archived plan offers Unarchive alone, with no pin.
- Delete plan becomes Permanently delete. It is still red, last, set apart, and still asks through #1847's ConfirmDialog. The dialog copy now reads: "“X” and its board and transcript will be deleted. This can't be undone. To keep it out of the way instead, archive it." The archive suggestion is left off for a plan that is already archived.
- Keyboard: ⌘⌫ still asks to permanently delete. ⇧⌘A archives the open plan; it was free in #1844's table and is now also in the File menu and on the Keyboard shortcuts page. The shortcut-table label is "Permanently delete plan".
Sidebar:
- A Pinned section stands above Plans whenever anything is pinned, newest pinned first, with the same rows. The rest keep #1835's newest-created order. Both sections share one scroller.
- Archived plans leave the list for a quiet Archived (n) row at its foot, folded by default, which unfolds the archived plans (newest archived first), Codex-style.
- Each archived row opens the plan and has the same right-click menu (Unarchive, Permanently delete, …).
Archived plan page: a quiet line over the document reads "This plan is archived", with Unarchive beside it.
Interactions:
- Archiving the open plan lands on the new-plan home, as delete does, once the service keeps the archive. Leaving ends the call, which a refusal couldn't give back.
- Back and forward (#1921) skip archived plans, including one opened from under Archived that the list never held.
- Rename works on pinned and archived plans alike: the rows are the same
SidebarPlan. - There is no command board on main, so there is nothing to filter there.
Optimistic updates, like rename:
- A pin or archive draws at once and stands until main's view carries it.
- If the service refuses, it is put back with a quiet note (a refused unarchive unfolds Archived so the note is seen): "The plan could not be pinned/unpinned/archived/unarchived. Try again." The note shows under the plan's row and beside the open plan's name, until that plan is next pinned or archived.
Plumbing:
- New
planning.pinandplanning.archiveacts, Gateway methods, and host operator rows, overHostedPlanClient#mark, which is PATCH{pinned}or{archived}. - The host takes the answered plan in place and reads nothing again. The held document keeps any draft ahead of the service's copy.
list()now asksGET /api/plans?archived=include, so archived plans sit in the view beside the rest, flagged, and the renderer sorts them into place (sidebarPlans).- Builds before this one never ask, so they never see archived plans (#1987).
docs/PLANNING.md's plan list and plan menu sections now describe pin, archive, and Permanently delete.
Screenshots
The ⋯ menu on a pinned plan, with the Pinned section, Plans, and the folded Archived (3):

An archived plan opened from the unfolded Archived list, with the banner:

The Permanently delete dialog (dark):

Tests
compose-planning.test.ts: pin and archive redraw the row and the open document in place, read nothing again, and leave the archived plan open; unpin drops the stamp; a refusal leaves everything as it was.plan-client.test.ts: the list asks?archived=include;marksends the change and answers the plan; an empty change never travels.planning-acts.test.ts: the new acts reach the host and answer what it answered.use-plans-tab.test.ts:- sidebar order: pinned first by pin time, then the rest, with archived apart by archive time
- optimistic pin with rollback and a note
- archiving the open plan leaves it (
planning.close) before the archive answers - a fixture marks nothing
desktop-sidebar.test.ts: the Pinned section only while pinned; Archived (n) folds and unfolds and opens a plan; no "No plans yet" when everything is archived.plan-actions.test.ts:- menu items from both doors, including Unpin/Unarchive and no Pin on an archived plan
- the Permanently delete copy for both cases
- the refusal note, the banner and its Unarchive
- ⇧⌘A (unclaimed on an archived plan)
- the existing delete-dialog tests, renamed
navigation-history.test.ts: an archived plan opened from under Archived is skipped both ways. This test fails without the change.
./scripts/check.sh passes locally.
🤖 Generated with Claude Code