feat(desktop): the sidebar groups plans by repository, each row with its last update, and a repository's + starts a plan on it
Dean's follow-up to pin and archive: "group plans by repo instead of putting repo name below each plan. Also would be good to put last updated time on that row (like '22m')", plus a + on each repository's header for a new plan in it. It builds on #1992 (merged), since it reshapes the same sidebar list.
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.
What changes
Repository groups (Plans section only):
- Rows stand under quiet headers in the "Plans" heading's sentence-case voice.
- A header shows the repository's name, with
owner/namein a Tooltip. Where two repositories share a name, both headers showowner/name. - Groups are ordered by their most recently updated plan, with No repository last.
- Within a group the order stays #1835's newest-created-first, so rows never jump while Luke edits; only a group moves.
- A chevron folds a group. Folds are remembered in the renderer's storage (
folded-groups.ts), per Mac; a fixture run keeps none, as it keeps no sidebar width. ⌥⌘↑/↓ walk the list as drawn and pass folded plans over, except the open plan, which keeps its place.
Rows:
- The repository line is gone from grouped rows. The plan name sits on the left and a compact relative time on the right, muted and tabular:
now,5m,22m,3h,2d,3w, then a date (Sep 1, orSep 6, 2025from another year). - The time is
updatedAt, already on the list wire, so there is no wire change. A Tooltip shows the full moment. - The words move on a minute tick streamed from the renderer's runtime (
plan-time.ts:Stream.fromEffectSchedule(Clock.currentTimeMillis, Schedule.spaced(1 minute)), read throughrendererRuntime.atom). No row arms its own timer, there is no new runtime edge, and a test drives the tick onTestClock.
Pinned and Archived stay flat and ungrouped, keeping each row's repository line in muted text, and show the time too.
Per-repository +:
- Each repository header gets the shared
.icon-buttonwithPlusIconand the Tooltip "New plan in ". It shows on header hover or focus (:focus-within, so always when focus-visible). - Pressing it opens the new-plan page with that repository already chosen in #1836's composer. It goes through a new
newPlan.preset, which the composer takes over any earlier pick, on arrival or in place; a plain New plan leaves the pick alone, and the preset is let go once a plan opens, so it lasts one visit. - The + stands beside the fold button, not inside it, so it never folds or unfolds the group. No repository gets no +.
Right-click menus, rename in place, selected and hover styles, and back and forward are unchanged.
Screenshots
Grouped list, with the + shown on a focused ledger header, Pinned flat with repository lines, times on every row:

After pressing ledger's +: the new-plan page with acme/ledger chosen:

Tests
planning-model.test.ts: grouping and ordering (newest-updated group first, No repository last, in-group order kept); header labels, including a shared name.plan-time.test.ts: the formatter across every unit and the date forms; the tick read now and on each minute on aTestClock.desktop-sidebar.test.ts:- groups under headers, with pinned rows ungrouped and naming their repository, and grouped rows naming none
- a fold persists across a remount, and ⌥⌘↓ skips folded plans
-
- asks for the repository without folding; No repository has no +
new-plan-form.test.ts: a preset chooses the repository on arrival and over an earlier pick.use-plans-tab.test.ts:onNewPlanInleaves the open plan and hands the preset; plain New plan hands none.
./scripts/check.sh passes locally.
🤖 Generated with Claude Code