One flat, recency-sorted thread list where row size is earned by state — and “settled” becomes a real lifecycle stage users control. Familiar mechanics only: nothing here requires learning a new gesture.
The hybrid the concepts page predicted, filtered through “meet users where they are”: concept 4's adaptive density as the skeleton, concept 1's card layout (trimmed to two structured lines — no generated text) for active rows, and concept 3 reduced to a sort rule (approval-blocked threads pin above the recency flow — no tiers, no inline approve).
ProjectFavicon per-row, not a text chip. Harness (Claude Code / Codex / other) is a tinted glyph on the model; machine shows only when the thread lives on another computer.session.lastError finally gets a red state; today a dead session shows nothing.Hover any card and hit “Settle” — it collapses into the slim tail. That height change is the whole design: it happens only at lifecycle transitions, never from streaming updates, which is what keeps the list calm.
Concept 4 derived “settled” passively. This version makes it an acknowledgment with an act attached — Gmail's archive, GitHub notifications' “Done” — which is what makes row heights stable and the list trustworthy.
Storage is one override, everything else is computed. No background jobs, no sweeper:
effectiveSettled(thread) = override === "settled" → true // manual settle override === "active" → false // manual keep-active (beats auto rules) pr.state ∈ {merged, closed} → true // auto — strongest signal lastActivityAt < now − inactivityThreshold → true // auto — backstop, default 3d, configurable otherwise → false
settledOverride: "settled" | "active" | null + settledAt, server-side next to archivedAt — it must sync across desktop/remote, and worktree cleanup needs the server to know. Mirrors the thread.archived event pattern exactly.lastVisitedAt); settling asserts “this work is concluded.” Conflating them would make auto-unsettle fight the user.Whole-component swap, one seam. Today's Sidebar.tsx (~3,750 lines) takes zero props and mounts at exactly one place. Everything that must behave identically in both sidebars already lives outside the component in shared hooks and stores — and what lives inside (project grouping, drag-and-drop, show-more) is precisely what v2 deletes.
sidebarV2Enabled (default false) + sidebarAutoSettleAfterDays (default 3, nullable = never) in ClientSettingsSchema.contracts/settings.ts — add sidebarV2Enabled, sidebarAutoSettleAfterDays.contracts/orchestration.ts — settledOverride + settledAt on the thread (decoding defaults → old data decodes unchanged); thread.settled / thread.unsettled events; thread.settle / thread.unsettle commands.server/orchestration/decider.ts — handle both commands (settle is idempotent for bulk); activity paths (user message, session start, approval request) emit thread.unsettled when an override is set.client-runtime/threadReducer.ts — reduce both events (archive cases at :87–101 are the template).effectiveSettled(shell, {now, autoSettleAfterDays}) in client-runtime; unit-test the full truth table (override × PR state × inactivity).Verify: typecheck · reducer + predicate tests · zero visible change.
AppSidebarLayout.tsx.SidebarV2.tsx: flat virtualized list, sort = needs-approval (by wait) → recency; single SidebarV2Row with card / slim variants from the same shell data. Rows lead with the existing ProjectFavicon component (environmentId + cwd are already on the shell).Sidebar.logic.ts to four visual states: Needs approval, Working (incl. connecting), Ready (merges today's Awaiting Input / Plan Ready / Completed-unseen — same user action, so one unlabeled state), and new Failed (session.lastError, non-running) — kept shared so v1 can adopt Failed later.uiStateStore.threadLastVisitedAtById.useThreadActions: settleThread / unsettleThread mirroring archiveThread, minus navigation — settling never navigates away. Affordances: row hover button, context menu, bulk multi-select. No keyboard shortcut yet.Verify: toggle on → v2; toggle off → v1 pixel-identical to before · settle round-trips across a desktop+remote pair · killed session → Failed card.
getOrphanedWorktreePathForThread): non-blocking “Worktree kept · Remove?” prompt → vcsEnvironment.removeWorktree. Never automatic, never blocks the settle.| Question | Current thinking |
|---|---|
| Server-side activity observation | If decider activity paths are too scattered for unsettle-on-activity, fall back to client-side: compute with activity timestamps, override only stores manual state (activity newer than settledAt wins). Decide in P1. |
| Shell projection | settledOverride/settledAt must be in the thread shell stream — the sidebar never loads details. Verify first thing in P1. |
| Diff stats on cards | Requires checkpoint data in the shell; if absent, defer diff stats rather than loading details per row. |
| Project grouping in v2 | Not planned; v1 stays available for users who want groups. Revisit only if beta feedback demands it. |
.plans/21-sidebar-v2-beta.md (implementation detail) + this page (design + rationale). Mock data mirrors the original concepts page (from live state.sqlite, Jul 13).