T3 Code · Sidebar v2 · UX Plan

Snooze: inbox-zero for threads

Hide a thread from the inbox until a chosen time (later: until an event — review posted, PR merged, CI green), then bring it back loudly. This plan pressure-tests the idea against how Sidebar v2 actually works today, proposes a state model, and offers 6 mock options for how snooze could be presented.

Verdict Pushback State model The 6 mocks Wake behavior Recommendation Implementation Open questions

Verdict

The idea is good and fits the product unusually well — better than it fits email, honestly, because coding-agent threads have machine-checkable wake conditions (PR merged, review posted, CI state) that email never had. But two things in the current design need to be confronted before building:

  1. Sidebar v2 already has a snooze-adjacent verb: Settle. A third lifecycle verb next to Settle and Archive is real cognitive load. Snooze must be crisply "not now, come back to me" vs Settle's "done, tuck below." The mocks treat this distinction as the core design problem, and Option D explores collapsing them.
  2. "Hide until a time passes" is only half a feature — the wake is the feature. Because the active list sorts by creation order and never reorders on activity (by design), a woken thread silently slots back into its old position, possibly below the fold. If wake isn't loud, snooze reads as "my thread disappeared and quietly came back somewhere." Wake behavior gets its own section below.

Also: a fixed 1-day duration is the wrong default for this product. Agent-thread rhythms are hours (CI run, teammate review, next work session), not days. Presets should skew short. And event-based snooze shouldn't be treated as distant future — the auto-settle machinery already listens for PR merge/close events, so "until PR merges" is close to free.

Pushback & refinements

01 Truly hiding threads breaks trust

If a snoozed thread is simply gone, the first time someone forgets what they snoozed, the feature reads as data loss. Every credible snooze implementation (Gmail, Superhuman, Linear triage) keeps a visible, low-pressure home for snoozed items. We need a "where did it go" answer that's discoverable without being noisy — a collapsed section (Option C) or a badge in the settled area (Option D). Recommendation: never hide with zero trace.

02 Snooze vs Settle vs Archive needs one-sentence answers

Users already juggle Settle ("done for now, slim row below") and Archive ("remove from sidebar"). The pitch must be:

If we can't keep those crisp in the UI (distinct icons, distinct locations, distinct return semantics), we should consider making snooze a mode of settling ("settle until…") rather than a fourth verb — that's Option D.

03 The static sort makes silent wake a real failure mode

sortThreadsForSidebarV2 is intentionally static — creation order, newest on top, activity never reorders. That's a good calmness property, but it means a thread snoozed for 2 days wakes up buried at its original position. Wake needs at least: mark-unread + a distinct "Woke" pill. Optionally a stronger exception (float to top on wake) — but that trades away the calm-sort guarantee. I lean pill + unread first, measure, then decide.

04 Threads aren't email — they can raise their hand while snoozed

A snoozed email stays inert. A snoozed agent thread can finish a run, fail, or hit a pending approval where the agent is blocked on you. Sleeping through "agent needs approval" is the feature working against the product. Mirror the existing rule in effectiveSettled (blocked work always stays active): pending approval, pending input, and failures wake a thread early. A completed run… probably wakes it too (that's exactly the "something happened" future you described — we get a taste of it in v1 for free).

05 Snooze state must live server-side, not in the client UI store

Read/unread currently lives in the client-local uiStateStore. Snooze can't — it has to survive across devices (mobile sidebar) and the wake timer has to fire even when no client is open. It belongs next to archivedAt/settledAt on projection_threads, flowing through the same atom-command path as settle, capability-gated the same way.

06 Event-based snooze is the killer version — don't strand it

"Snooze until PR merges" is more on-brand for a coding agent than any time preset, and the plumbing half-exists: auto-settle already reacts to changeRequestState (PR merged/closed). The v1 schema should be snoozedUntil plus a nullable snoozeCondition so time-based is just one kind of condition. Mocks below show "Until PR merges" as a visible-but-disabled item labeled soon — it advertises the roadmap and teaches the mental model on day one.

Proposed state model

Snooze is an overlay on the active state, not a fourth destination. A snoozed thread is still "active" in the data model — it's just suppressed from the top of the inbox until its wake condition fires. This keeps Settle/Archive semantics untouched and makes un-snoozing trivial (clear the field).

Active card rows, top of inbox Snoozed hidden from top · overlay state Settled slim rows below divider Archived out of sidebar snooze until… wake: timer, event, or thread raises hand settle archive

// projection_threads — new columns (alongside archived_at, settled_at)
snoozed_until   TEXT  // IsoDateTime | null — time-based wake
snooze_condition TEXT  // JSON | null — { kind: "pr-merged" | "pr-closed" | "review-posted" | … } (v1.5+)
snoozed_at      TEXT  // for "snoozed 2d ago" affordances + analytics

Effective visibility becomes: snoozed = snoozed_until > now && !threadRaisedHand, where raised hand = pending approvals, pending input, or session error — computed in shared logic next to effectiveSettled so web and mobile agree.

The 6 mocks

All mocks use the real Sidebar v2 visual language: true-black .app-sidebar, 256px width, card rows for active threads, slim 34px rows below the Settled divider, status pills in the existing colors. Hover the rows — hover actions are live in these mocks, like the real "Settle" button. Options A–C and E–F are entry-point/placement choices and are largely combinable; D is a divergent philosophy.

A

One-click hover snooze

clock icon next to Settle · instant default, no menu

A clock button appears on hover beside the existing Settle button. One click snoozes to the smart default (tomorrow 9:00). No menu, no decisions — undo lives in a toast. Power users get exactly what Settle trained them to do: hover, click, gone.

All
t3code
uploadthing
t3code Approval
Refactor thread projection to support snooze columns
t3/snooze-cols+214 −36
Settle
t3code Working
Fix sticky PR fallback when remote URL unresolved
t3/pr-fallback#4289
Settle
uploadthing 3h
Investigate flaky presigned-URL test on CI
ut/flaky-presign
Settle
Settled · 12
Migrate mobile timer into timeline2d
Bump effect to 3.144d
Hover a row → clock appears left of Settle. Click = snoozed until tomorrow 9:00, toast with Undo.
Snoozed until tomorrow, 9:00Undo
The only feedback — no menu ever opens.

Pros

  • Zero-decision; matches the Settle hover pattern exactly — instantly learnable
  • Fastest possible inbox-zero sweep (click, click, click)
  • Smallest UI surface to build and maintain

Cons

  • One duration fits nobody; "tomorrow 9:00" is wrong for a 2-hour CI wait
  • No path to event-based snooze from this entry point
  • Two adjacent hover icons invite mis-clicks between Snooze and Settle
B

Preset popover (Gmail-style)

hover clock opens a small menu of times + future events

Same hover clock, but clicking opens a compact popover: short-cycle presets first (this product runs in hours, not days), a date picker escape hatch, and the event-based options visible but marked soon — teaching the roadmap's mental model from day one.

All
t3code
uploadthing
t3code
Fix sticky PR fallback when remote URL unresolved
t3/pr-fallback#4289
Settle
Snooze until
In 1 hour15:42
This evening18:00
TomorrowThu 9:00
Next weekMon 9:00
Until PR mergesSOON
Until next reviewSOON
Pick date & time…
Click the clock → popover. Presets skew short because agent cycles are hours. Event rows advertise v1.5.

Pros

  • Right granularity for a coding tool; "in 1 hour" covers the CI-wait case
  • Natural, already-designed home for event-based options later — no rework
  • Familiar from Gmail/Superhuman/Slack reminders; zero explanation needed

Cons

  • Two clicks instead of one for the sweep workflow
  • A popover inside a 256px sidebar is tight (must overflow the rail gracefully)
  • Preset list needs real thought — bad presets are worse than none
C

Collapsed "Snoozed" shelf

where threads go — a section between Active and Settled

Not an entry point but the destination: a collapsed one-line divider between the active cards and the Settled list. Collapsed by default (just a count), expandable to slim rows showing each thread's wake time. This is the direct answer to concern 01 — snoozed threads are out of the way but never gone.

All
t3code
uploadthing
t3code Working
Fix sticky PR fallback when remote URL unresolved
t3/pr-fallback#4289
Settle
Settled · 12
Migrate mobile timer into timeline2d
Bump effect to 3.144d
Collapsed (default): one quiet line. The count is the whole footprint.
All
t3code
uploadthing
t3code Working
Fix sticky PR fallback when remote URL unresolved
t3/pr-fallback#4289
Settle
Snoozed · 3
Refactor thread projection for snooze 18h
Flaky presigned-URL test on CI 2h
Design settings for notification rules PR
Settled · 12
Migrate mobile timer into timeline2d
Expanded: slim rows with wake-time pills. Hover a row to un-snooze (wake now). The "PR" pill previews an event-based snooze.

Pros

  • Solves the trust problem completely — snoozed items always findable in one click
  • Wake times visible at a glance ("what's coming back today?")
  • Slots cleanly into the existing Active / divider / Settled structure

Cons

  • A third section in a rail that just simplified to two — visual budget cost
  • Needs an entry point anyway (pair with A, B, E, or F)
  • Empty-state handling: the shelf should vanish entirely at count 0
D

Snooze as "Settle until…" — no new verb

divergent philosophy: extend Settle instead of adding Snooze

The contrarian option: don't add a fourth lifecycle verb. Snoozing is settling with a return ticket. The Settle button grows a chevron; "Settle until…" drops the thread into the existing Settled list with a small clock badge, and at wake time it un-settles automatically (existing machinery, reason:"timer") and comes back unread with a Woke pill.

uploadthing
Investigate flaky presigned-URL test on CI
ut/flaky-presign
Settle
Settle
Settle until
Tomorrow9:00
Next weekMon
PR mergesSOON
Settled · 13
Flaky presigned-URL test on CI 18h
Migrate mobile timer into timeline2d
One verb, one destination. The clock badge in the Settled list is the only trace of the timer.

Pros

  • Zero new concepts — no fourth state, no new section, no Settle-vs-Snooze confusion
  • Reuses settle/un-settle machinery and the settled section wholesale; cheapest build
  • Auto-settle already blurs this line (inactivity settles threads); this just adds intent

Cons

  • Semantically muddy: "settle" means done; "settle until" means not done — one word, opposite intents
  • Snoozed threads mix into a 12+-item settled list; wake times don't stand out
  • Boxes in the event-based future: "settle until next review" reads strangely
E

Command palette + keyboard first

Z on hover/selection · ⌘K "Snooze until…" with natural input

Lean into the palette-first direction the sidebar is already taking (new-thread picker just moved into the palette). Pressing Z with a thread hovered/selected applies the default snooze; ⌘K → "snooze" opens a duration picker that accepts natural input ("2h", "monday", "until pr"). Multi-select works for free: select 4 threads, Z, done.

Snooze · Fix sticky PR fallback… 2h
Until
In 2 hourstoday 16:42
TomorrowThu 9:00
Next weekMon 9:00
⌘K flow: type "snooze", pick a thread (or act on the current one), then free-text the duration — "2h", "fri", "next week" all parse.
Z 3 threads snoozed until tomorrowUndo
Keyboard path: hover or multi-select rows, press Z. Superhuman-style sweep.

Pros

  • Fastest workflow for heavy users; multi-select snooze falls out naturally
  • Consistent with the sidebar's palette-first trajectory
  • Natural-language durations beat any preset list for flexibility

Cons

  • Invisible — without a pointer entry point, most users never discover it
  • Single-key shortcuts in a list UI need careful focus handling (rename input, search)
  • NL date parsing is a real (if bounded) engineering cost
F

Context menu only (quiet v1)

a submenu in the existing right-click menu · no new chrome

The minimal-footprint probe: add "Snooze" with a submenu to the existing thread context menu, right under Settle. No hover buttons, no new sections (snoozed threads get a clock badge wherever they'd otherwise appear via search/palette). Ship it, watch usage, then invest in A–C chrome only if people actually snooze.

t3code Done
Preserve draft highlighting during promotion
t3/draft-highlight#4283
Settle thread
Snooze
Rename thread
Mark unread
Delete
In 1 hour
Tomorrow9:00
Next week
Until PR mergesSOON
Right-click → Snooze › submenu. Sits directly under "Settle thread" so the verbs read as siblings. Multi-select gets "Snooze (N)".

Pros

  • Cheapest validation of the whole hypothesis; nearly zero visual budget spent
  • Context menu already handles capability gating and multi-select patterns
  • Easy to promote to A/B later without throwing anything away

Cons

  • Low discoverability → risks a false-negative on the whole idea
  • Still needs a "where did it go" answer (at minimum, palette/search visibility)
  • Right-click is weak on touch/mobile

Wake behavior — the half that makes or breaks it

Whatever entry point wins, the return trip has fixed requirements. Given the static sort (concern 03), a woken thread reappears at its original creation-order position — so the wake signal has to carry the weight:

t3code Working
Fix sticky PR fallback when remote URL unresolved
t3/pr-fallback#4289
uploadthing Woke
Investigate flaky presigned-URL test on CI
ut/flaky-presign
t3code 5d
Design settings for notification rules
t3/notif-rules
A woken thread: unread weight + amber alarm-clock "Woke" pill, in its original sort position. Pill clears on visit, like "Done".

Wake rules (v1)

  • Timer fires → clear snoozedUntil, mark unread, show amber Woke pill until visited.
  • Early wake — thread raises its hand: pending approval, pending input, or session error immediately overrides the snooze. The agent blocking on you always outranks your snooze. (Mirrors the blocked-work rule in effectiveSettled.)
  • Run completes while snoozed: wake early too. This is the v1 taste of event-based snooze — "something happened" already exists as latestTurn.completedAt.
  • No float-to-top in v1. Keep the calm static sort; the pill + unread does the signaling. If woken threads get lost in practice, revisit with a scoped exception rather than pre-breaking the sort invariant.
  • Snoozing does not pause the agent. A working thread keeps working; snooze only affects visibility. (Worth stating in the UI the first time someone snoozes a Working thread.)

Recommendation

Combine B + C, with F's context-menu item for parity — and keep the verb "Snooze."

Why not A or F alone: A's single fixed duration recreates the "probably a day for now" arbitrariness at the UI level, and F alone under-exposes the feature enough that we'd learn nothing from shipping it.

OptionDiscoverabilitySweep speedEvent-readyBuild costCall
A · One-click hover●●●○●●●●●○○○lowToo rigid alone
B · Preset popover●●●○●●●○●●●●medShip (entry)
C · Snoozed shelf●●●●●●●●medShip (destination)
D · Settle until…●●○○●●●○●●○○lowReject (verb muddle)
E · Palette + Z●○○○●●●●●●●●med-highPhase 2
F · Context menu●●○○●●○○●●●○lowShip (parity)

Implementation sketch

P1

Server & contracts. Add snoozedUntil / snoozedAt (+ nullable snoozeCondition, unused in P1) to the thread shell in packages/contracts/src/orchestration.ts; migration on projection_threads; decider/projector commands snooze / unsnooze alongside settle; wake sweep on a server timer + on the existing PR/turn event paths. Capability-gate as threadSnooze like threadSettlement.

P2

Shared logic. effectiveSnoozed next to effectiveSettled in packages/client-runtime/src/state/threadSettled.ts-land: snoozed = future snoozedUntil and no raised hand (approvals / input / error / fresh completion). One implementation for web + mobile.

P3

Sidebar UI. Hover clock + popover on SidebarV2Row; Snoozed shelf between the active block and the Settled divider in SidebarV2.tsx (reusing the slim-row component + divider pattern, auto-animate handles the moves); context-menu items incl. multi-select "Snooze (N)"; Woke pill in the top-status pill priority list (below Approval/Input, above Done); undo toast.

P4

v1.5 — first event condition. Enable "Until PR merges/closes" by writing snoozeCondition and waking from the existing changeRequestState handler that auto-settle already uses. Then evaluate review-posted / CI-state conditions.

Open questions for you

  1. Default preset: I'd make the top (Enter-key) preset Tomorrow 9:00 but list In 1 hour first for the CI-wait case. Agree, or should the default be shorter?
  2. Does a completed run wake a snoozed thread? I say yes (argued above), but it does mean "snooze until tomorrow" sometimes returns in 20 minutes — is that delight or betrayal?
  3. Should snoozing a Working thread be allowed silently? Or show a one-time hint that the agent keeps running?
  4. Mobile: ship simultaneously (shelf + long-press menu) or web-first behind the existing Sidebar v2 beta flag?
  5. Snooze count badge: should the collapsed shelf show "next wake in 2h" instead of just a count?