Sidebar: backend activity stamps, snooze, and a wrapping-up tier - #206
Conversation
The idle sweep measured against a client-side stamp invented the first time each build saw a workspace, so installing an update restarted every workspace's idle clock and months of real history were discarded. Workspaces now carry backend-owned `last_active_at` / `last_visited_at` (persisted, stamped on non-idle pane transitions, at create, and on activation), with a one-time boot backfill dating undated checkouts from their last git commit, then directory mtime, and never inventing `now`. `effectiveActivityAt` makes the backend stamp authoritative so installs that already wrote a synthetic baseline are corrected rather than skipped. Alongside that: - Snooze as a sibling lifecycle to settle: DST-safe presets resolved when the menu opens and labelled with the absolute wake time, its own collapsed shelf, a precise wake timer clamped against the signed 32-bit setTimeout overflow, and an immediate hand-raise wake on working/blocked. - A "wrapping up" tier: an open (non-draft) PR that is idle and already read drops below a non-collapsible divider, so nearly-done work stops holding prime space without being hidden. A follow-up message restores it to the top on its own. - Cards ordered newest-first and statically: position changes only at lifecycle transitions the user caused, never on status churn, so Alt+1..9 stays muscle memory. - Merged/closed PRs settle only after an hour of additional idle, so follow-up work stays visible while it is warm. - Unread markers, multi-select with bulk parking, forward navigation on park, shelf counts, and forced visibility of the open workspace. - Off-screen rows use CSS containment and the sidebar reserves a stable scrollbar gutter, so long lists stop costing layout and stop jogging sideways as workspaces park. The collapsed rail now hides snoozed workspaces as well as settled ones, switching away marks the outgoing workspace visited so watched work is not reported unread, and adopted workspaces leave their stamp unknown so the backfill can date them from real history.
Review — Opus 5 (high effort)This is the strongest reasoning of the eleven open PRs — the
One real bug: "Tomorrow" and "Next week" are the same option on Sundays
Two menu entries, identical This is the same class of problem Two notes, not defects
Only the thread-keyed writer is change-guarded. Overlap — this needs sequencing, not independent merging#207 implements the same static newest-first ordering, independently, in the same file: Full overlap set: Also: on the |
|
Review by GPT 5.6 Sol xhigh found three functional issues:
Focused validation passed: 142/142 sidebar frontend tests and 13/13 workspace-activity Rust tests. Coordination note: this head has pairwise textual conflicts with open PRs #207, #208, #212, and #213; #212 also needs a semantic resolution for this PR's new snoozed lifecycle. |
…tomorrow
On Sundays daysToNextMonday returns 1, so the next-week preset resolved
to the exact instant the tomorrow preset already offers — two menu
entries, one wake time ("Next week · Tomorrow 09:00"). Withhold it
unless it lands at least a calendar day past tomorrow, the same
conditional-offer pattern this-evening already uses, and pin the two
wake-label tests to a fixed Wednesday so they cannot flake on real
Sundays.
…k guardrails Bulk Settle settled every selected row unconditionally while the per-row action and bulk Snooze both refuse to park working or permission-blocked workspaces. Gate the menu item and the handler on the same all-selected predicate, and show a disabled explanatory line instead of an empty menu when the selection contains live work. Enter/Space on settled and snoozed shelf rows activated the workspace without collapsing the multi-selection the way plain clicks and active cards do, leaving an invisible selection behind for the next bulk action to act on. Collapse it first, matching the click path.
…tches activate_terminal_session and activate_pane assigned active_workspace_id directly, skipping the visit bookkeeping activate_workspace performs — so a jump-to-session navigation switched workspaces without closing the outgoing visit or stamping the incoming one, leaving the sidebar's unread state wrong after every such jump. Extract the switch bookkeeping (outgoing visit close, incoming visit stamp, notification read/clear, review-status cleanup) into a shared record_workspace_switch helper and call it from all three activation paths whenever the active workspace actually changes. Same-workspace focus moves still stamp nothing. Covered by three new tests in workspace_activity_tests.
…otes - last_visited_at is now written by record_workspace_switch, shared by all three activation paths on cross-workspace switches - the next-week preset is conditional (dropped on Sundays when it would duplicate tomorrow) - bulk Settle rides the same guardrail as bulk Snooze - note that backfill quality depends on the checkout being a git repo, since the mtime fallback is usually close to now
Why
Auto-settle measured idleness against a client-side stamp invented the first time each build saw a workspace. Installing an update therefore restarted every workspace's idle clock, and real history was thrown away — a machine full of month-old worktrees looked brand new and nothing swept for another full window.
The fix
WorkspaceSnapshotnow carries persistedlast_active_at/last_visited_at, stamped on non-idle pane transitions, at create, and on activation. A one-time boot backfill dates undated checkouts from their last git commit, falling back to directory mtime, and never inventsnow— an unknown stamp means "don't sweep", so a bad read fails toward staying visible.effectiveActivityAtmakes the backend stamp authoritative over the client map. That part matters: a first-seen-only backfill would have fixed new installs and done nothing for anyone who had already updated, because their client map was already populated with synthetic baselines. There is a regression test pinning exactly that, and it is the only test that fails if the line is reverted.Also in here
+86400000), resolved when the menu opens rather than off the coarse tick, and labelled with the absolute wake time. Own collapsed shelf showing time-until-wake, a precise wake timer clamped against the signed 32-bitsetTimeoutoverflow, and an immediate hand-raise wake when a snoozed agent goes working or blocked.Alt+1..9muscle memory.Alt+1is now the newest workspace.Defects fixed along the way
last_active_at = now, defeating the backfill.SnoozeEntry.atwas documented as guarding stale hand-raise wakes but nothing read it; traced every path, confirmed the wake is unreachable, and made the comment honest rather than implementing a no-op guard.Anti-oscillation
Four states (active / settled / snoozed / pinned-active) with disjoint status bands: auto-settle fires only at status null, auto-un-settle and the hand-raise only at working/blocked, the wake sweep only on an elapsed timer. The shelves are mutually exclusive from both sides. Documented as prose in
docs/features/sidebar.mdalongside a Load-Bearing Assumptions section for the things a future change could silently break.Verification
npm run checkclean · 3156 frontend tests green ·cargo checkclean · Rust suite green apart from pre-existing flakes that shell out togit/find/grepand pass in isolation.Ordering, guardrails, and the full snooze flow were also exercised against the running app, not just the test suite.
Note
no-scrollbaris used in several places but is not defined anywhere — no@utilityblock, no plugin. It is a dead class, so those surfaces have had real scrollbars all along. Out of scope here; worth a follow-up.