feat(desktop): pin and archive plans, and Delete becomes Permanently delete

Merged
DA
8d ago
+1331-134

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.pin and planning.archive acts, Gateway methods, and host operator rows, over HostedPlanClient#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 asks GET /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):

Plan menu

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

Archived plan

The Permanently delete dialog (dark):

Permanently delete

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; mark sends 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