feat(desktop): the sidebar groups plans by repository, each row with its last update, and a repository's + starts a plan on it

Merged
DA
8d ago
+802-61

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/name in a Tooltip. Where two repositories share a name, both headers show owner/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, or Sep 6, 2025 from 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 through rendererRuntime.atom). No row arms its own timer, there is no new runtime edge, and a test drives the tick on TestClock.

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-button with PlusIcon and 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:

Repository groups

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

New plan in ledger

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 a TestClock.
  • 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: onNewPlanIn leaves the open plan and hands the preset; plain New plan hands none.

./scripts/check.sh passes locally.

🤖 Generated with Claude Code