feat(sidebar-v2): thread snooze with wake-loud return#4311
Conversation
Adds an inbox-zero snooze lifecycle to Sidebar v2: hide a thread until a chosen time, then bring it back loudly. Contracts + server: - thread.snooze / thread.unsnooze commands and thread.snoozed / thread.unsnoozed events; snoozedUntil/snoozedAt on thread + shell (optional, so pre-snooze payloads still decode) - decider invariants: wake time must be in the future; blocked-on-you work (open approval/user-input) cannot be snoozed; duplicates re-emit idempotently; a user message on a snoozed thread spends the return ticket (thread.unsnoozed reason "activity") - migration 034 adds snoozed_until/snoozed_at to projection_threads; projector, projection pipeline, and all snapshot/shell queries thread the fields through - threadSnooze capability flag, version-skew gated like threadSettlement Shared logic (client-runtime): - effectiveSnoozed: hidden while the wake time is ahead AND the thread hasn't raised its hand; timer wakes are derived (no server event) - threadRaisedHandWhileSnoozed: pending approval/input, a failure newer than the snooze, or a run completed after the snooze wake early — classification only, without clearing the server-side snooze - threadWokeAt drives the woke indicator until the user visits Sidebar v2 UI: - hover clock button beside Settle opening a preset popover (In 1 hour / This evening / Tomorrow / Next week; event-based options teased as SOON) - collapsed "Snoozed · N" shelf between Active and Settled — never hides threads without a trace; expanded rows show wake countdowns and a wake-now button; shelf disappears at count 0 - amber "Woke" pill + unread-weight title when a snooze ends; the static sort is untouched, the pill carries the signal - context-menu Snooze submenu (single + multi-select), undo toast Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
ApprovabilityVerdict: Needs human review 3 blocking correctness issues found. New feature adding thread snooze functionality across server, client, and UI layers with new database schema, commands, events, and business logic. Multiple unresolved medium-severity comments identify potential bugs in the snooze invariant checks, wake timestamp handling, and DST date arithmetic. You can customize Macroscope's approvability policy. Learn more. |
Merge resolves the SidebarV2 restyle (#4268) by re-applying snooze onto the new surface model (sidebar tokens, thread tooltips, restyled action buttons), and folds in serverSelfUpdate capability alongside threadSnooze. Review fixes: - Snooze classification now uses a precise clock instead of the minute-quantized partition time, plus a timeout armed at the earliest upcoming wake — the shelf empties the moment a snooze expires instead of up to a minute late (Cursor) - thread.snooze now rejects queued turn starts exactly like thread.settle, server-side (shared threadHasQueuedTurnStart helper) and client-side (canSnooze gains the hasQueuedTurnStart check) (Cursor) - threadWokeAt keeps an early hand-raise wake authoritative after the scheduled wake time passes, so a visited-and-cleared Woke pill cannot resurface when snoozedUntil elapses (Macroscope) - resolveSnoozePresets advances calendar days with setDate instead of DAY_MS offsets, fixing tomorrow/next-week presets across DST transitions (Macroscope) - Documented why hasOpenBlockingRequest's scan of the 500-capped activities array is sound for the snooze invariant (Macroscope) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Updated the branch (merged main, incl. the #4268 sidebar restyle — snooze UI re-applied onto the new surface tokens) and addressed all four review findings:
All suites green: server 1597, web 1450, client-runtime 469, contracts 187; repo-wide typecheck + lint clean. 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c56b487. Configure here.
| // shows the pill; visiting clears it. | ||
| const isWoke = | ||
| props.wokeAt !== null && | ||
| (lastVisitedAt === undefined || Date.parse(lastVisitedAt) < Date.parse(props.wokeAt)); |
There was a problem hiding this comment.
Invalid visit time hides Woke
Medium Severity
The new isWoke check and snooze label helpers (snoozeWakeLabel, snoozeWakeDescription) bypass the project's parseTimestampDate utility. This leads to inconsistent handling of unparseable ISO timestamps, causing the "Woke" pill to not display and potentially misclassifying thread status.
Additional Locations (1)
Triggered by learned rule: Use parseTimestampDate for ISO string parsing, return null for invalid
Reviewed by Cursor Bugbot for commit c56b487. Configure here.


Summary
Adds an inbox-zero snooze lifecycle to Sidebar v2: hide a thread from the inbox until a chosen time, then bring it back loudly. Design follows the approved plan (option B popover entry + option C collapsed shelf + option F context-menu parity, "Woke" pill for returns): https://2udjpqoxpjoz.postplan.dev
Snooze is an overlay on the active state, not a fourth destination — crisply distinct from Settle ("done, tuck below") and Archive ("out of the sidebar"): snooze is "not now, guaranteed to come back."
Contracts + server
thread.snooze/thread.unsnoozecommands andthread.snoozed/thread.unsnoozedevents;snoozedUntil/snoozedAtonOrchestrationThread+ shell (optional fields, so payloads from pre-snooze servers still decode)thread.unsnoozed, reasonactivity)snoozed_until/snoozed_attoprojection_threads; projector, projection pipeline, and every snapshot/shell query thread the fields throughthreadSnoozecapability flag, version-skew gated exactly likethreadSettlement— clients never send the commands to older serversShared logic (client-runtime)
effectiveSnoozed: hidden while the wake time is ahead and the thread hasn't raised its hand. Timer wakes are derived — no server event fires; a passedsnoozedUntilsimply stops classifying as snoozedthreadRaisedHandWhileSnoozed: pending approval/input, a failure newer than the snooze, or a run completed after the snooze wake the thread early (classification only — the snooze fields stay put, mirroring howeffectiveSettledtreats blocked work). Snoozing a thread that already failed stays snoozed: that snooze was "I saw it, not now"threadWokeAtfeeds the woke indicator until the user visitsSidebar v2 UI
2h,18h) and a hover wake-now button; the shelf disappears entirely at count 0Tests
decider.snoozed.test.ts— invariants, idempotent re-emission, activity wake on turn.startthreadSnoozed.test.ts— effectiveSnoozed / raised-hand / canSnooze / threadWokeAt edge cases (incl. pre-snooze failures staying snoozed)Sidebar.snooze.test.ts— preset boundaries (evening suppression, Monday next-week), wake labelspnpm typecheck,pnpm lint(no new warnings), and full test suites for server (1571 passed), web (1444), client-runtime (467), contracts (187) all green. Codex review ran on the diff; its one finding (pre-existing failures instantly un-hiding a fresh snooze) is fixed and covered by a test.🤖 Generated with Claude Code
Note
Medium Risk
Touches orchestration decider, projections, and sidebar partitioning/navigation; invariants mirror settlement but new lifecycle paths (activity unsnooze, client-derived timer wake) need careful rollout behind capability flags.
Overview
Adds thread snooze end-to-end: hide inbox threads until a chosen time, then surface them again with a visible Woke signal—distinct from settle/archive and gated by a new
threadSnoozecapability like settlement.Stack: New
thread.snooze/thread.unsnoozecommands andthread.snoozed/thread.unsnoozedevents; optionalsnoozedUntil/snoozedAton thread shells; migration 034 onprojection_threads; projector, pipeline, and snapshot queries updated. The decider enforces future wake times, blocks snooze when approvals/user-input or queued turn starts are pending, idempotent re-snooze to the same time, and auto-unsnoozes on user turn start (activity). Queued-turn detection is extracted to sharedthreadHasQueuedTurnStartfor settle and snooze.Client-runtime:
effectiveSnoozed,canSnooze,threadRaisedHandWhileSnoozed, andthreadWokeAtdrive visibility (timer wake needs no server event; pending work / fresh failures / post-snooze completions can wake early without clearing snooze fields).Sidebar v2: Preset popover (hour / evening / tomorrow / next week), collapsible Snoozed · N shelf with countdowns and wake-now, context-menu and bulk snooze, success toast with undo; settle navigation skips snoozed rows.
Tests: Decider, client-runtime snooze logic, and preset/label helpers; fixtures extended for new columns.
Reviewed by Cursor Bugbot for commit c56b487. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add thread snooze and wake functionality to SidebarV2 with server-side enforcement
useThreadActionsexposessnoozeThread/unsnoozeThreadwith structured errors (ThreadSnoozeUnsupportedError,ThreadSnoozeBlockedError) when capability or thread state blocks the action.Macroscope summarized c56b487.