Skip to content

Sidebar v2 beta: flat thread list with a server-backed settled lifecycle#4026

Merged
t3dotgg merged 46 commits into
mainfrom
t3code/new-sidebar-client-only
Jul 22, 2026
Merged

Sidebar v2 beta: flat thread list with a server-backed settled lifecycle#4026
t3dotgg merged 46 commits into
mainfrom
t3code/new-sidebar-client-only

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Jul 16, 2026

Copy link
Copy Markdown
Member

What

Opt-in Sidebar v2 (web) and Thread List v2 (mobile): one flat thread list ordered by creation time, replacing the project-grouped sidebar. Active threads render as status cards; finished work "settles" into a slim, paged tail at the bottom. Threads settle manually (hover ✓ / context menu / swipe), or automatically when their PR merges/closes or after N days of inactivity — and un-settle automatically on real activity (a user message, a session coming alive, a new approval/user-input request).

Settled is a first-class server-side lifecycle (this PR absorbed #4243): thread.settle / thread.unsettle commands, thread.settled / thread.unsettled events, and settled_override + settled_at columns on projection_threads (migration 033). Settled ≠ archived — settled threads stay in the live shell stream, so their history stays readable without un-settling, and archive keeps its original "hidden from lists" meaning.

BEFORE:
image

AFTER:
image

MOBILE:
IMG_2680

Safety / gating

  • All v2 UI is behind beta flags, default off: sidebarV2Enabled (web, Settings → Beta) and threadListV2Enabled (mobile device preference). Flags off, v1 renders unchanged.
  • Migration 033 is additive and idempotent (two nullable columns, PRAGMA-guarded); no backfill, no data rewritten. Threads archived before this lands stay archived.
  • Version skew is negotiated: servers advertise a threadSettlement capability. Clients never send the new commands to a pre-settlement server (absent capability = unsupported), and threads on such environments never classify as settled — no stranded tails with dead affordances.
  • The settle target is guarded three ways against hiding live work: client canSettle (running/starting sessions, pending approvals/user input, queued turn starts), the same blockers in effectiveSettled (auto-settle can't misclassify either), and a server-side decider invariant for raced or stale clients.

What was touched / review focus

Server (apps/server):

  • orchestration/decider.ts — settle/unsettle command handling, the live-session + queued-turn invariants, and the three activity-un-settle injection points (message send, session set, activity append). Real activity clears ANY override: settled threads wake, keep-active pins reset to neutral. Re-emissions are projected no-ops (idempotency without updatedAt churn). Best place to start.
  • orchestration/projector.ts + Layers/ProjectionPipeline.ts — event application: reason: "user" → the keep-active pin, reason: "activity" → neutral.
  • persistence/Migrations/033_ProjectionThreadsSettled.ts + row plumbing.

Shared:

  • packages/client-runtime/src/state/threadSettled.tseffectiveSettled / canSettle / hasQueuedTurnStart, heavily tested (172 tests incl. a truth table over all override states).
  • packages/contracts — commands/events (client-dispatchable unsettle reason is "user" only; "activity" is server-owned), threadSettlement capability, two client settings.

Web (apps/web):

  • components/SidebarV2.tsx — the new sidebar. Review focus: post-settle navigation (next card / project draft, validated against the current route), per-thread in-flight guards, capability-aware partition, bulk actions over exactly the rendered selection.
  • hooks/useThreadActions.ts — settle/unsettle dispatch + typed errors.
  • Shared chrome extracted to sidebar/SidebarChrome.tsx (v1 and v2 render identical header/footer); new /settings/beta page; minor chat-header/composer tweaks.

Mobile (apps/mobile): features/threads/threadListV2.ts + thread-list-v2-items.tsx + HomeScreen.tsx — the same model with native list anatomy (swipe actions, long-press menus, settled tail paging, ticking auto-settle clock).

Known follow-ups (beta-period, not blockers)

Mobile split-view v2 wiring, mobile PR auto-settle without virtualized row mounts, a11y polish (composite row labels, ARIA patterns), v2 search parity with project titles, and the auto-settle policy knob on mobile.

🤖 Generated with Claude Code

Note

Add flat thread list Sidebar v2 with server-backed thread settle/unsettle lifecycle

  • Introduces a new SidebarV2 on web and a thread list v2 on mobile, both rendering threads sorted by creation time with active threads as cards and settled threads as a paginated tail; enabled via a new Beta settings page (/settings/beta) guarded by a sidebarV2Enabled client setting.
  • Adds thread.settle and thread.unsettle commands and thread.settled/thread.unsettled events to the contracts, with full decider enforcement (no live session, no open blocking requests, no queued turn start within a grace window) and idempotent re-emit on already-settled threads.
  • Propagates settlement state (settledOverride, settledAt) through the server projection pipeline, persistence layer (migration 033 adds settled_override/settled_at columns to projection_threads), in-memory projector, client-runtime reducers, and shell/detail merge.
  • Exposes settleThread/unsettleThread in useThreadActions (web) and useThreadListActions (mobile), gated by a new threadSettlement environment capability flag; mobile also adds swipe-to-settle gesture support.
  • Auto-unsettle fires when a user message is sent, a session starts/runs, or an approval/user-input request is appended, emitting thread.unsettled with reason activity before the triggering event.
  • Risk: the migration is additive (nullable columns), but servers without migration 033 will not expose the threadSettlement capability, so clients silently suppress settle/unsettle actions on those environments.

Macroscope summarized cb57915.


Note

Medium Risk
Touches orchestration command invariants and projection schema with new user-visible lifecycle behavior, but it is gated behind beta flags default-off and capability checks with extensive test coverage.

Overview
Introduces thread settlement as a server lifecycle distinct from archive: thread.settle / thread.unsettle commands, matching events, and nullable settled_override / settled_at on projection_threads (migration 033). Settled threads remain in the live shell stream; the decider blocks settling during active sessions, open approval/user-input requests, and queued turn starts, and prepends thread.unsettled on real activity (messages, live session updates, blocking activities). Servers advertise threadSettlement so older environments never get settle commands or settled classification.

Web adds beta Sidebar v2 (sidebarV2Enabled, Settings → Beta): creation-order flat list, active cards vs paged settled slim rows, auto-settle via inactivity days and merged/closed PR signals, with shared chrome extracted from v1. Mobile mirrors the model behind threadListV2Enabled in device preferences—FlatList v2 path, horizontal project scope chips, settle/unsettle in useThreadListActions, and swipe full-swipe commits the primary lifecycle action (settle/un-settle) instead of delete.

Shared effectiveSettled / canSettle in client-runtime drive partitioning; thread shells and tests across apps pick up settledOverride / settledAt fields.

Reviewed by Cursor Bugbot for commit cb57915. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features
    • Added Thread List v2 for mobile and web, with project scoping, search, status indicators, settled-thread history, and paging.
    • Added settle/unsettle thread actions, including swipe, menu, and bulk actions where supported.
    • Added beta settings for Sidebar v2 and automatic settling of inactive threads.
    • Added project icons and improved new-thread project selection.
  • Bug Fixes
    • Prevented unrelated branch or worktree context from carrying into threads created in another project.
  • Style
    • Refined sidebar surfaces, animations, stage backdrops, and composer focus states.

t3dotgg and others added 9 commits July 15, 2026 02:30
…ifecycle

A new thread sidebar behind a Settings → Beta toggle (sidebarV2Enabled,
default off). One flat recency-sorted list across projects: active threads
render as two-line cards (favicon, title, time, status word · branch ·
provider glyph + model · machine), settled threads collapse to slim
one-liners. Approval-blocked threads pin to the top with wait time; failed
sessions (session.lastError) finally get a visible state.

Settled is an event-sourced lifecycle state mirroring the archived pattern:
- settledOverride ("settled" | "active" | null) + settledAt on the thread,
  thread.settle/thread.unsettle commands, thread.settled/thread.unsettled
  events, projection columns via migration 033.
- effectiveSettled() computes the visible state: manual override beats
  auto rules (PR merged/closed, N days of inactivity — client setting,
  default 3, nullable). Idempotency is by event re-emission since the
  engine rejects zero-event commands.
- Activity auto-unsettles server-side: turn start, live session start,
  approval/user-input requests. Session stop/error status writes do not.

Manually settling a thread whose worktree is orphaned offers a one-click
worktree removal (re-validated at click time, never forced).

The v2 component is a sibling swapped at the single AppSidebarLayout mount
point; shared chrome moved to sidebar/SidebarChrome.tsx. The data model is
flag-independent and ships dark for v1 users, so toggling is a pure view
swap.

Plan: .plans/21-sidebar-v2-beta.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…reation picker

Design iteration after testing the beta over Tailscale:

- Settled is the only collapse rule. Reverted the "seen Ready threads render
  slim" heuristic — it silently turned settled back into a derived state.
  Every unsettled thread is a full card; density is earned by settling.
- Cards are bordered islands (rounded-xl, state-colored border: amber
  approval / sky working / red failed), status dot replaces the favicon on
  stateful cards, live elapsed timer on working. Slim rows grew to 34px with
  13px titles; a hairline divider separates cards from the settled tail.
- Harness identity is the provider glyph only (model name in tooltip). The
  full-row harness tint experiment is removed.
- Hover actions (Settle/Un-settle) are absolutely-positioned overlays that
  fade over the timestamp — no layout shift.
- Project scope chips above the list (All / per-project / + add): filters
  the flat list and informs the new-thread default. Session-local.
- New thread opens a project picker dialog: contextual default preselected
  and focused (Enter confirms), all projects with workspace paths, Add
  project row. Single-project setups skip the dialog. chat.new (mod+shift+o)
  routes through the picker via newThreadPickerBus since the shortcut is
  handled in the _chat route layout. Arrow/Home/End navigation in the list.
- chat.newLocal still bypasses the picker as the explicit escape hatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI: add settledOverride/settledAt + new client settings to desktop and
mobile test fixtures and the mobile threadDetailToShell mapping.

Review fixes (cursor bugbot + macroscope):
- failed status now requires session.status === "error" instead of any
  lingering lastError on a stopped/ready session
- effectiveSettled: pending approvals or user input always win — blocked
  work can never collapse into the settled tail, even manually settled
- decider unsettle-on-activity guards check settledOverride === "settled"
  so activity no longer erases a user's explicit keep-active override
- repeat thread.settle re-emits with updatedAt = original settledAt, so
  double-settles no longer bump recency ordering
- sidebarAutoSettleAfterDays bounded 1..90 in schema and patch
- approval wait sort/label use shell.updatedAt (bumped by the approval
  activity in the projection pipeline) instead of latestUserMessageAt /
  turn start, with the approximation documented
- v2 rows: Enter/Space keyboard activation; double-click inline rename
  ported from v1 plus a context-menu Rename entry
- new-thread picker: cross-project creation no longer inherits the active
  thread's branch/worktree; preselection consults the active draft via
  resolveThreadActionProjectRef; stale project scope resets to All
- settle worktree prompt: vcs status checked before offering (and again
  at click time) — dirty or ahead worktrees are never offered

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…led tail

- Cards are square hard-edged blocks on a darker shell; active row reads
  by a near-white border instead of a blue-adjacent wash
- Status moves from tinted border + dot to a solid 3px left edge strip
  (duty-cycled pulse while working), so the project favicon always shows
- Card titles clamp to two lines instead of truncating one
- Settled rows grey out (desaturated favicon, dimmed title) and restore
  on hover; section gets an explicit SETTLED label
- Project chips are square mono tabs; selected All chip inverts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Each card is a tiny window: a thin title bar carries the project
  favicon + mono uppercase name with the timestamp, separated from the
  thread content by a hairline — project identity reads as chrome
- Active card brightens its title bar on top of the near-white border
- Thread list is statically ordered by creation time, newest first;
  activity never reorders rows — only settling moves them
- Project identity mirrored on the open thread: favicon+name chip leads
  the chat header and the composer's run-context strip (drafts included)
- Settled rows keep dimmed favicons; chips/picker favicons restored
- Composer squared to match: 12px radius, higher-contrast border,
  neutral focus ring

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the web sidebar v2 design to React Native behind a device-local
Settings → Beta toggle (threadListV2Enabled — mobile has no
client-settings sync, so the flag lives in mobile preferences):

- threadListV2.ts: v2 status model (approval/working/failed/ready),
  static creation-order sort, and the effectiveSettled partition into
  an active card block + settled recency tail (shared
  @t3tools/client-runtime/state/thread-settled, web-default 3-day
  auto-settle)
- thread-list-v2-items.tsx: square title-bar cards — project favicon +
  mono uppercase name as chrome, status edge strip, two-line titles,
  branch/status meta — and dimmed slim rows under a SETTLED divider
- Settle (cards) / un-settle (slim) as the primary swipe action and in
  the long-press menu; useThreadListActions grows settle/unsettle via
  the shared threadEnvironment atoms
- HomeScreen renders the flat v2 list (no grouping, rows never reorder
  on activity) when the toggle is on; v1 list untouched otherwise

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first mobile pass copied the web card anatomy (outlined translucent
box, thin header strip) — which is exactly iOS push-banner anatomy, so
rows read as information rather than buttons. Rebuild the card around
native list-app touch signals:

- Solid raised bg-card surface, no border, 18pt continuous corners
- Leading 38pt favicon tile as the touch anchor; mono project name
  becomes an eyebrow line above the title
- Trailing chevron affordance; timestamp joins the eyebrow row
- Spring press feedback (scale to 97%) via reanimated
- Status edge strip survives as a full-height bar on the left edge

Settled rows, swipe actions, and long-press menus unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hanges

Makes the new sidebar/thread-list testable against existing unupgraded
servers by removing every backend dependency of the settled feature:

- apps/server, contracts orchestration schemas, and client-runtime
  command/reducer plumbing revert to main baseline (no thread.settle
  commands, no settled events/fields, no migration 033)
- effectiveSettled() is now archive-based: archivedAt is the explicit
  settled signal; PR merged/closed and inactivity auto-settle keep
  working client-side. Blocked work (pending approval/input, live
  session) still never settles
- settle/unsettle actions ride threadEnvironment.archive/unarchive on
  web and mobile — UI and UX unchanged
- Archived threads stay visible as the settled tail: the live shell
  stream drops archived threads (thread.archived → thread-removed), so
  both clients merge them back from the archived shell snapshot and
  refresh it after settle/unsettle/archive actions
- Client settings (sidebarV2Enabled, sidebarAutoSettleAfterDays) are
  kept — they persist client-side only

Trade-offs vs the event-sourced model (still on t3code/sidebar-v2-mobile):
no activity-driven auto-un-settle for archived threads, and no "keep
active" override distinct from unarchiving.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: a beta sidebar v2 with server-backed thread settlement.
Description check ✅ Passed The description is detailed and covers what changed, why, UI screenshots, and safety notes, even though it doesn't match the template headings exactly.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/new-sidebar-client-only

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 16, 2026
Comment thread apps/web/src/components/SidebarV2.tsx
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
Comment thread apps/mobile/src/features/threads/threadListV2.ts Outdated
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
Comment thread apps/mobile/src/features/threads/thread-list-v2-items.tsx
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
Comment thread apps/mobile/src/features/home/HomeScreen.tsx
Comment thread apps/web/src/hooks/useThreadActions.ts
Comment thread apps/web/src/hooks/useThreadActions.ts
Comment thread apps/mobile/src/features/threads/thread-list-v2-items.tsx
@macroscopeapp

macroscopeapp Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

Comment thread apps/web/src/hooks/useThreadActions.ts Outdated
Comment thread apps/web/src/hooks/useThreadActions.ts
Comment thread apps/web/src/components/AppSidebarLayout.tsx
- settleThread inherits archive's running-turn guard on web and mobile:
  settling can never interrupt an active turn
- unsettleThread no-ops on auto-settled (unarchived) threads instead of
  sending an unarchive the server would reject
- Mobile v2 partition now receives per-row PR state (rows report up via
  onChangeRequestState, mirroring web) so merged/closed PRs auto-settle
- Mobile empty state accounts for archived-only workspaces when v2 is on
- Optimistic settled hold on web + mobile: the shell being settled is kept
  visible (marked archived) between the live stream dropping it and the
  archived snapshot returning it — no flicker, and holds are created only
  at settle time so deletes are never resurrected
- Hover-only settle buttons in web SidebarV2 get focus-visible:opacity-100
- v2 full swipe commits the advertised primary action (Settle/Un-settle),
  with the stretch animation following; delete remains a tap target only.
  v1 keeps full-swipe delete via the new fullSwipeAction prop default
- Android long-press on v2 cards works: PressableScaleCard is the menu's
  direct child and forwards the injected onLongPress to its Pressable

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread apps/mobile/src/features/home/HomeScreen.tsx
Comment thread apps/mobile/src/features/home/HomeScreen.tsx
Comment thread apps/mobile/src/features/home/HomeScreen.tsx
Comment thread apps/mobile/src/features/home/HomeScreen.tsx
Comment thread apps/mobile/src/features/home/useThreadListActions.ts
Comment thread apps/web/src/hooks/useThreadActions.ts
- Worktree-removal offer works after settle: archived threads leave the
  live shell store, so the orphan check re-includes the settled shell and
  click-time revalidation treats absence-from-live as still-settled
- Un-settle is only offered when there is an archive to undo: auto-settled
  rows (inactivity / merged PR, archivedAt null) get Settle instead, on
  web (hover button + context menu) and mobile (swipe + long-press menu)
- Mobile optimistic settled hold rolls back when the settle is blocked or
  fails (settleThread now reports success); web and mobile both drop holds
  on delete and un-settle so ghosts can't linger
- Archived snapshot refreshes after delete too, clearing stale settled
  rows sourced from the snapshot
- v2 mobile list renders queued offline tasks above the cards (they were
  dropped entirely, leaving a blank screen for pending-task-only projects)

On the chat.new/settings finding: not fixed here — the shortcut routes to
the v2 picker only inside chat routes by design; on /settings it falls
back to contextual create, matching v1's behavior on those routes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread apps/mobile/src/features/threads/thread-list-v2-items.tsx Outdated
Comment thread apps/web/src/hooks/useThreadActions.ts Outdated
// Archived threads leave the live store entirely, so absence
// IS the settled state; a live shell means it woke back up
// (unarchive puts it back in the stream).
const stillSettled = currentShell === null || currentShell.archivedAt != null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted thread stillSettled check

High Severity

The worktree-removal toast's re-validation for stillSettled incorrectly treats a null currentShell (e.g., when the thread is deleted) as a settled state. This allows the 'Remove worktree' action to proceed for a thread that no longer exists.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5354496. Configure here.

next.delete(threadKey);
return next;
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In-flight settle clears hold

Medium Severity

The optimistic settledHolds rollback in handleSettleThread incorrectly removes a thread's hold when onSettleThread resolves false for an in-flight duplicate action. This causes the thread to temporarily disappear from the UI until the archived snapshot loads.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5354496. Configure here.

t3dotgg and others added 3 commits July 16, 2026 00:17
Settling is a high-frequency lifecycle action; announcing it with a
notification (plus a disk-cleanup offer) was noise. Settle is silent now.
Worktree cleanup still exists on the delete path, which asks explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Settling the active thread on web navigates to the next remaining card
  (wrapping, never a settled row), or to the new-thread screen when it
  was the last active one. Background settles never navigate.
- The settled tail renders 10 rows, then an explicit Show more reveals
  25 at a time (web + mobile). Expansion resets when scope, environment,
  or search changes. Mobile's builder counts hidden rows instead of
  building them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
Comment thread apps/web/src/components/SidebarV2.tsx Outdated
Conflict resolutions:
- AppSidebarLayout: keep sidebar v2 toggle alongside main's resizable
  sidebar width + macOS fullscreen handling
- Sidebar.tsx: main's new themed-header chrome (SidebarChromeHeader/
  Footer with stage backdrop art) moved into sidebar/SidebarChrome.tsx,
  which this branch had already extracted so SidebarV2 shares it
- ChatComposer: combine v2 styling with main's projectSelectionRequired
  and transition-[background-color]
- HomeScreen (mobile): union of imports

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread apps/mobile/src/features/threads/threadListV2.ts
Comment thread apps/web/src/components/Sidebar.logic.ts
t3dotgg and others added 2 commits July 21, 2026 23:29
… marker

Maria's design pass lands as-is: scoped graphite sidebar palette,
rounded composer shell, simplified project chrome, lighter card and
filter styling. Her removal of the composer project chip supersedes
ours (same outcome).

One addition on top: the settled boundary gets its quiet label back.
Her pass replaced the SETTLED mono-caps divider with a bare 8px gap —
but an unnamed gap doesn't explain why rows below it look different,
and the name anchors what un-settle/settle act on. Restored minimally
in her visual language: a 10px muted 'Settled' word with a soft
hairline, no mono, no caps, no tracking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread apps/web/src/components/SidebarV2.tsx
Comment thread apps/web/src/index.css
Comment thread apps/web/src/components/SidebarV2.tsx
t3dotgg and others added 2 commits July 21, 2026 23:55
- Dark .app-sidebar sets --sidebar-stage-fade to its own surface: the
  nightly/dev header art faded to the global chrome background and
  showed a seam against the black panel
- Failed cards render session.lastError again (one truncated line
  under the meta row, full text on hover) — the card refactor had
  reduced failures to a bare 'Failed' word
- Settled slim rows: the PR badge moved out of the hover-fading slot,
  so it stays visible and clickable while the settle affordance shows;
  only the time/jump label yields on hover
- New-thread tooltip resolves chat.newLocal ?? chat.new with no
  Electron gating, matching v1 — web users saw no shortcut at all

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex's final-gate review found the last hole in the settle guard: a
raced or stale client could settle a thread with an open approval or
user-input request. The client refuses (canSettle) and effectiveSettled
renders blocked threads active, but the override was still written —
so the row would drop into the settled tail the moment the request
resolved, hiding the transition the user cares about.

The decider now derives open blocking requests from the thread's
retained activities (requested minus resolved per requestId — the same
accounting ProjectionPipeline uses for pending counts) and rejects
thread.settle while any remain. Covered by decider tests for open,
resolved, and user-input variants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread apps/server/src/orchestration/decider.ts
Comment thread apps/server/src/orchestration/decider.ts Outdated
Comment thread apps/server/src/orchestration/decider.ts
t3dotgg and others added 3 commits July 22, 2026 00:18
v2 showed the ⌘1..9 overlay on a naive metaKey||ctrlKey check, so a
⌘⇧4 screenshot chord kept the hints on screen. v1 already solved this:
useShortcutModifierState + shouldShowThreadJumpHintsForModifiers show
hints only while the held modifiers EXACTLY match a thread-jump
binding — Shift or Alt breaks the match. v2 now uses the same
machinery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Threads read as discrete objects: every active card gets a quiet
rounded fill (white 3.5%, 2.5% for receded rows) instead of resting
flush on the panel. Active/selected/hover remain brighter tones of
the same treatment, so the whole interaction scale is one shape.
Settled slim rows are intentionally untouched — the contrast between
filled cards and bare history rows adds a second cue to the
active/settled boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 25b7d3f. Configure here.

Comment thread apps/mobile/src/features/home/HomeScreen.tsx
- Bulk delete: grow deletedThreadKeys as deletions land instead of
  seeding the whole batch, so orphaned-worktree detection never counts
  still-alive batch mates as deleted (premature shared-worktree removal).
- Decider settle invariant: clear open approval/user-input requests on
  provider.*.respond.failed activities whose detail marks the request
  stale/unknown, matching the projection's pending accounting.
- Queued-turn grace: bound the age check on both sides (client + server)
  so a clock ahead of the evaluator reads as skew, not eternal queued work.
- effectiveSettled: let a server-accepted settle (settledAt >= message
  time) overrule the clock-derived queued blocker so minute-floored list
  clocks don't pin a just-settled row active for up to 60s.
- Mobile: card accessibilityHint follows the real swipe action (archive
  fallback); v2 empty state is self-contained — search outranks project
  scope and never leans on v1's projectGroups signal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@maria-rcks

Copy link
Copy Markdown
Collaborator

theo go to sleep it isnt worth it

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/components/settings/BetaSettingsPanel.tsx`:
- Around line 35-42: Update the value parsing in the auto-settle change handler
to use Number() and require Number.isInteger(parsed) before applying the
existing AUTO_SETTLE_MIN_DAYS/AUTO_SETTLE_MAX_DAYS bounds and calling onCommit,
ensuring decimal and exponent inputs are not truncated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 40fa2463-85ff-4c99-ac3c-e3ade72158f1

📥 Commits

Reviewing files that changed from the base of the PR and between 23c18fd and 0f135b2.

📒 Files selected for processing (79)
  • apps/desktop/src/settings/DesktopClientSettings.test.ts
  • apps/mobile/src/features/archive/archivedThreadList.test.ts
  • apps/mobile/src/features/home/HomeRouteScreen.tsx
  • apps/mobile/src/features/home/HomeScreen.tsx
  • apps/mobile/src/features/home/homeListItems.test.ts
  • apps/mobile/src/features/home/homeThreadList.test.ts
  • apps/mobile/src/features/home/thread-swipe-actions.tsx
  • apps/mobile/src/features/home/useThreadListActions.ts
  • apps/mobile/src/features/settings/SettingsRouteScreen.tsx
  • apps/mobile/src/features/threads/thread-list-v2-items.tsx
  • apps/mobile/src/features/threads/threadListV2.test.ts
  • apps/mobile/src/features/threads/threadListV2.ts
  • apps/mobile/src/lib/repositoryGroups.test.ts
  • apps/mobile/src/lib/threadActivity.test.ts
  • apps/mobile/src/persistence/mobile-preferences.ts
  • apps/mobile/src/state/use-thread-selection.ts
  • apps/server/src/environment/ServerEnvironment.ts
  • apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts
  • apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts
  • apps/server/src/orchestration/Layers/ProjectionPipeline.ts
  • apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts
  • apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts
  • apps/server/src/orchestration/Schemas.ts
  • apps/server/src/orchestration/commandInvariants.test.ts
  • apps/server/src/orchestration/decider.settled.test.ts
  • apps/server/src/orchestration/decider.ts
  • apps/server/src/orchestration/projector.settled.test.ts
  • apps/server/src/orchestration/projector.test.ts
  • apps/server/src/orchestration/projector.ts
  • apps/server/src/persistence/Layers/ProjectionRepositories.test.ts
  • apps/server/src/persistence/Layers/ProjectionThreads.ts
  • apps/server/src/persistence/Migrations.ts
  • apps/server/src/persistence/Migrations/033_ProjectionThreadsSettled.ts
  • apps/server/src/persistence/Services/ProjectionThreads.ts
  • apps/server/src/provider/Layers/ProviderSessionReaper.test.ts
  • apps/server/src/relay/AgentAwarenessRelay.test.ts
  • apps/server/src/server.test.ts
  • apps/web/src/components/AppSidebarLayout.tsx
  • apps/web/src/components/ChatView.logic.test.ts
  • apps/web/src/components/ChatView.logic.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/CommandPalette.logic.test.ts
  • apps/web/src/components/Sidebar.logic.test.ts
  • apps/web/src/components/Sidebar.logic.ts
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/SidebarV2.tsx
  • apps/web/src/components/chat/ChatComposer.tsx
  • apps/web/src/components/chat/ChatHeader.tsx
  • apps/web/src/components/settings/BetaSettingsPanel.tsx
  • apps/web/src/components/settings/SettingsSidebarNav.tsx
  • apps/web/src/components/sidebar/SidebarChrome.tsx
  • apps/web/src/hooks/useThreadActions.ts
  • apps/web/src/index.css
  • apps/web/src/lib/chatThreadActions.test.ts
  • apps/web/src/lib/chatThreadActions.ts
  • apps/web/src/lib/threadSort.test.ts
  • apps/web/src/newThreadPickerBus.ts
  • apps/web/src/routeTree.gen.ts
  • apps/web/src/routes/_chat.tsx
  • apps/web/src/routes/settings.beta.tsx
  • apps/web/src/state/entities.ts
  • apps/web/src/worktreeCleanup.test.ts
  • packages/client-runtime/package.json
  • packages/client-runtime/src/operations/commands.test.ts
  • packages/client-runtime/src/operations/commands.ts
  • packages/client-runtime/src/state/entities.test.ts
  • packages/client-runtime/src/state/shellReducer.test.ts
  • packages/client-runtime/src/state/threadCommands.ts
  • packages/client-runtime/src/state/threadDetail.ts
  • packages/client-runtime/src/state/threadReducer.test.ts
  • packages/client-runtime/src/state/threadReducer.ts
  • packages/client-runtime/src/state/threadSettled.test.ts
  • packages/client-runtime/src/state/threadSettled.ts
  • packages/client-runtime/src/state/threads-sync.test.ts
  • packages/contracts/src/environment.ts
  • packages/contracts/src/orchestration.test.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/settings.test.ts
  • packages/contracts/src/settings.ts

Comment on lines +35 to +42
const parsed = Number.parseInt(event.target.value, 10);
if (
Number.isFinite(parsed) &&
parsed >= AUTO_SETTLE_MIN_DAYS &&
parsed <= AUTO_SETTLE_MAX_DAYS
) {
onCommit(parsed);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject non-integer input instead of truncating it.

parseInt("3.5", 10) commits 3, and parseInt("1e2", 10) commits 1. Use Number() plus Number.isInteger() so the persisted value matches the displayed value.

Proposed fix
-        const parsed = Number.parseInt(event.target.value, 10);
+        const parsed = Number(event.target.value);
         if (
-          Number.isFinite(parsed) &&
+          Number.isInteger(parsed) &&
           parsed >= AUTO_SETTLE_MIN_DAYS &&
           parsed <= AUTO_SETTLE_MAX_DAYS
         ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parsed = Number.parseInt(event.target.value, 10);
if (
Number.isFinite(parsed) &&
parsed >= AUTO_SETTLE_MIN_DAYS &&
parsed <= AUTO_SETTLE_MAX_DAYS
) {
onCommit(parsed);
}
const parsed = Number(event.target.value);
if (
Number.isInteger(parsed) &&
parsed >= AUTO_SETTLE_MIN_DAYS &&
parsed <= AUTO_SETTLE_MAX_DAYS
) {
onCommit(parsed);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/settings/BetaSettingsPanel.tsx` around lines 35 - 42,
Update the value parsing in the auto-settle change handler to use Number() and
require Number.isInteger(parsed) before applying the existing
AUTO_SETTLE_MIN_DAYS/AUTO_SETTLE_MAX_DAYS bounds and calling onCommit, ensuring
decimal and exponent inputs are not truncated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge juliusmarminge added the 🚀 Mobile Continuous Deployment Trigger Expo preview build label Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
  🤖 Android 🍎 iOS
Fingerprint 00e5b19bd3fc1481ea4ef627471c627133ebc3d7 d68cf195f2e8866a1a50f9fa454fec49043e3ab8
Build Details Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: 00e5b19bd3fc1481ea4ef627471c627133ebc3d7
App version: 0.1.0
Git commit: 79d3ac6933f7cf6d608486c746cabd43edff126b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: d68cf195f2e8866a1a50f9fa454fec49043e3ab8
App version: 0.1.0
Git commit: 79d3ac6933f7cf6d608486c746cabd43edff126b
Update Details Update Permalink
DetailsBranch: pr-4026
Runtime version: 00e5b19bd3fc1481ea4ef627471c627133ebc3d7
Git commit: f1d70110e73a07e9e3fbcb676e1b078c61f020f8
Update Permalink
DetailsBranch: pr-4026
Runtime version: d68cf195f2e8866a1a50f9fa454fec49043e3ab8
Git commit: f1d70110e73a07e9e3fbcb676e1b078c61f020f8
Update QR

@t3dotgg
t3dotgg merged commit 32c6012 into main Jul 22, 2026
17 checks passed
@t3dotgg
t3dotgg deleted the t3code/new-sidebar-client-only branch July 22, 2026 10:36
@jakeleventhal

jakeleventhal commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@t3dotgg @juliusmarminge this aesthetically looks much better but it doesn't solve a key problem which is that work happens at the level of the worktree, not an individual thread.

I outlined the problem here https://cap.so/s/dvaz19nmrwjh2ja

I'd really appreciate feedback here because some sort of worktree grouping, and having terminals and dev servers at the worktree level, is much more clear in my opinion.

@jakeleventhal

Copy link
Copy Markdown
Contributor

@t3dotgg @juliusmarminge this aesthetically looks much better but it doesn't solve a key problem which is that work happens at the level of the worktree, not an individual thread.

I outlined the problem here https://cap.so/s/dvaz19nmrwjh2ja

I'd really appreciate feedback here because some sorting of worktree grouping, and having terminals and dev servers at the worktree level, is much more clear in my opinion.

this problem is made most apparent when checking out a branch to review/test

t3dotgg added a commit that referenced this pull request Jul 22, 2026
… lifecycle

Rebased onto main's sidebar v2 (#4026): migration renumbered 033→034
(settled lifecycle took 033), and the pending-turn-start status resolution
in ingestion merged with the roster branch.

A thread waiting on background tasks is in motion, not done:
- resolveSidebarV2Status shows it as 'Working' instead of the receded
  ready state
- canSettle/effectiveSettled block settling (and override a stale settled
  pin), matching the running-session blockers
- the thread.settle decider rejects it server-side, mirroring the
  active-session invariant

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UNN-Devotek pushed a commit to unn-corp/t3code that referenced this pull request Jul 23, 2026
…cle (pingdotgg#4026)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: maria-rcks <maria@kuuro.net>
AnanduB13 pushed a commit to AnanduB13/t3code that referenced this pull request Jul 23, 2026
…cle (pingdotgg#4026)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: maria-rcks <maria@kuuro.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🚀 Mobile Continuous Deployment Trigger Expo preview build size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants