Pure black dark theme - #4068
Conversation
…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>
- 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>
- 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>
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>
|
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 2 blocking correctness issues found. Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
Dismissing prior approval to re-evaluate 70d022d
| : () => void router.navigate({ to: "/" }); | ||
| } | ||
| const result = await settleThread(threadRef); | ||
| if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { |
There was a problem hiding this comment.
🟡 Medium components/SidebarV2.tsx:991
attemptSettle treats an interrupted settleThread result as a success: when result._tag === "Failure" and isAtomCommandInterrupted(result) is true, the failure branch is skipped, so the optimistic settledHolds entry stays in place, refreshArchivedThreadsForEnvironment runs, and navigateAfterSettle fires — navigating the user away from a thread whose settle was interrupted, not completed. Every other stateful call site in this file returns before its success-side effects when the result is an interrupted failure. The attemptSettle callback should remove the hold and skip navigation for any failure, while still suppressing the toast for interruptions.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/SidebarV2.tsx around line 991:
`attemptSettle` treats an interrupted `settleThread` result as a success: when `result._tag === "Failure"` and `isAtomCommandInterrupted(result)` is true, the failure branch is skipped, so the optimistic `settledHolds` entry stays in place, `refreshArchivedThreadsForEnvironment` runs, and `navigateAfterSettle` fires — navigating the user away from a thread whose settle was interrupted, not completed. Every other stateful call site in this file returns before its success-side effects when the result is an interrupted failure. The `attemptSettle` callback should remove the hold and skip navigation for any failure, while still suppressing the toast for interruptions.
| const v2ListHeader = ( | ||
| <> | ||
| {listHeader} | ||
| {props.pendingTasks.map((pendingTask, index) => ( |
There was a problem hiding this comment.
🟡 Medium home/HomeScreen.tsx:655
The v2 list header renders every queued task in props.pendingTasks without filtering by selectedEnvironmentId or searchQuery, so when Thread List v2 is enabled, selecting a single environment still shows pending tasks from all environments, and pending tasks that don't match an active search stay visible. The grouped v1 list filters both ways via buildHomeThreadGroups, but the v2 header maps props.pendingTasks directly. Consider applying the same environmentId and search-query filters to props.pendingTasks before rendering the v2 header rows.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/src/features/home/HomeScreen.tsx around line 655:
The v2 list header renders every queued task in `props.pendingTasks` without filtering by `selectedEnvironmentId` or `searchQuery`, so when Thread List v2 is enabled, selecting a single environment still shows pending tasks from all environments, and pending tasks that don't match an active search stay visible. The grouped v1 list filters both ways via `buildHomeThreadGroups`, but the v2 header maps `props.pendingTasks` directly. Consider applying the same `environmentId` and search-query filters to `props.pendingTasks` before rendering the v2 header rows.


Dark canvas goes from
#161616to pure black with raised card surfaces, a light primary, and a rounder composer.Before
After
Note
Low Risk
Visual and CSS token changes only; no auth, data, or business logic affected.
Overview
Dark mode shifts from charcoal (
#161616) to a pure black canvas, with boot splash,theme-color, and CSS tokens updated so--backgroundis black and card/popover surfaces use raised oklch grays instead of neutral mixes.Primary actions in dark mode flip to a light primary on dark text (
--primarynear white), and focus rings move to a white alpha instead of the old blue primary.UI polish includes a rounder chat composer (20px radii), stronger composer glass (82% card mix), sidebar v2 using
dark:bg-cardon the black canvas, user message bubbles with translucent white fill and no border in dark, terminal fallback background black, and a split-button border tweak on Implement actions in dark.Reviewed by Cursor Bugbot for commit 70d022d. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add pure black dark theme and Sidebar v2 beta with thread settling
#000000) across index.html and index.css, updating CSS variables for contrast, opacity, and composer glass opacity.SidebarV2component (SidebarV2.tsx) behind asidebarV2Enabledclient setting, rendering animated card-based thread rows with visual status indicators ordered by creation time.effectiveSettledin threadSettled.ts marks threads as settled based on archive state, PR merge/close state, or configurable inactivity (sidebarAutoSettleAfterDays, default 3, range 1–90)./settings/betaroute with controls for toggling sidebar v2 and configuring the auto-settle inactivity period; mobile gets a matching Beta section in settings.HomeScreenrenders a merged live + archived thread list whenthreadListV2Enabledis set, with a settled tail behind a divider, optimistic hold for newly settled threads, and settle/unsettle swipe actions.startNewThreadInProjectFromContextno longer passes branch/worktree context when the target project differs from the current context.📊 Macroscope summarized 70d022d. 31 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.