From b2b43bef73447c483ceae486890cb79f01c369cb Mon Sep 17 00:00:00 2001 From: Tristan Knight Date: Thu, 24 Sep 2026 10:42:42 +0100 Subject: [PATCH 01/14] fix(server): preserve racy edits in review diff previews (#12613) Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 45 ++++++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 10 ++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 98dec86522de..4a5dff870fd2 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1449,6 +1449,51 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { }), ); + for (const [timestamp, splitIndex] of [ + [1_700_000_000, false], + [1_700_000_000.9999, false], + [1_700_000_000, true], + [1_700_000_000.9999, true], + ] as const) { + it.effect( + `preserves same-size edits with a racy review index (${timestamp}, split: ${splitIndex})`, + () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const filePath = path.join(cwd, "tracked.txt"); + const indexPath = path.join(cwd, ".git", "index"); + // Reproduce a same-timestamp edit without relying on filesystem clock resolution. + yield* git(cwd, ["config", "core.trustctime", "false"]); + yield* writeTextFile(cwd, "tracked.txt", "before\n"); + yield* fileSystem.utimes(filePath, timestamp, timestamp); + yield* git(cwd, ["add", "tracked.txt"]); + yield* git(cwd, ["commit", "-m", "record racy file"]); + if (splitIndex) yield* git(cwd, ["update-index", "--split-index"]); + yield* fileSystem.utimes(indexPath, timestamp, timestamp); + const originalIndex = yield* fileSystem.readFile(indexPath); + const originalIndexMtime = (yield* fileSystem.stat(indexPath)).mtime; + yield* writeTextFile(cwd, "tracked.txt", "after!\n"); + yield* fileSystem.utimes(filePath, timestamp, timestamp); + yield* writeTextFile(cwd, "untracked.txt", "new\n"); + + const preview = yield* driver.getReviewDiffPreview({ cwd }); + const dirty = preview.sources.find((source) => source.kind === "working-tree")!; + assert.deepStrictEqual(dirty.files, [ + { path: "tracked.txt", previousPath: null, additions: 1, deletions: 1 }, + { path: "untracked.txt", previousPath: null, additions: 1, deletions: 0 }, + ]); + assert.include(dirty.diff, "-before"); + assert.include(dirty.diff, "+after!"); + assert.deepStrictEqual(yield* fileSystem.readFile(indexPath), originalIndex); + assert.deepStrictEqual((yield* fileSystem.stat(indexPath)).mtime, originalIndexMtime); + }), + ); + } + it.effect("keeps complete stats for files beyond the combined patch limit", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 5fbae919c258..47fd3bd20725 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -2360,7 +2360,15 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* prefix: `t3code-review-index-${process.pid}-`, }); const indexExists = yield* fileSystem.exists(indexPath); - if (indexExists) yield* fileSystem.copyFile(indexPath, tempIndexPath); + if (indexExists) { + const { mtime } = yield* fileSystem.stat(indexPath); + yield* fileSystem.copyFile(indexPath, tempIndexPath); + // A newer copy timestamp hides racily clean edits. Round down before Git reads or rewrites it. + const indexTime = Option.isSome(mtime) + ? Math.max(0, Math.floor((mtime.value.getTime() - 1) / 1000)) + : 0; + yield* fileSystem.utimes(tempIndexPath, indexTime, indexTime); + } const env = { GIT_INDEX_FILE: tempIndexPath } satisfies NodeJS.ProcessEnv; const tempIndexConfig = [ "-c", From cb1a3f34670b06148d88806a9534772879c30ecd Mon Sep 17 00:00:00 2001 From: Bilal Bakr <62337003+Bil0000@users.noreply.github.com> Date: Thu, 24 Sep 2026 19:20:52 +0300 Subject: [PATCH 02/14] fix(web): show repository names on linked pull requests (#13061) --- .../pullRequest/ThreadPullRequestsPanel.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/pullRequest/ThreadPullRequestsPanel.tsx b/apps/web/src/components/pullRequest/ThreadPullRequestsPanel.tsx index 15221f26e63d..d9a8b60c36e2 100644 --- a/apps/web/src/components/pullRequest/ThreadPullRequestsPanel.tsx +++ b/apps/web/src/components/pullRequest/ThreadPullRequestsPanel.tsx @@ -16,6 +16,7 @@ import { useAtomCommand } from "~/state/use-atom-command"; import { formatRelativeTimeLabel } from "~/timestampFormat"; import { Button } from "../ui/button"; import { Menu, MenuItem, MenuPopup, MenuTrigger } from "../ui/menu"; +import { MiddleTruncate } from "../ui/middle-truncate"; import { ScrollArea } from "../ui/scroll-area"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { openLinkPullRequestDialog } from "./LinkPullRequestDialog"; @@ -161,7 +162,17 @@ function LinkRow({ /> ) : null} {snapshot !== null ? ( - + <> + {/* Cut in the middle: rows from one owner differ in the repository name at the + end, which a tail cut would hide. */} + + }> + + + {link.repository} + + + ) : ( {link.host}/{link.repository} From 80fc23ac32cd8fe5011a94420cff1f469367f1fc Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 24 Sep 2026 10:49:04 -0700 Subject: [PATCH 03/14] lint/unknown and static (#13366) Co-authored-by: Claude Opus 5.5 (1M context) --- apps/web/src/components/AppSidebarLayout.tsx | 8 +--- apps/web/src/components/ChatMarkdown.test.tsx | 10 ++--- apps/web/src/components/ChatMarkdown.tsx | 3 +- apps/web/src/components/CommandPalette.tsx | 1 - .../src/components/CommandPaletteContent.tsx | 20 ++++++++-- apps/web/src/components/DiffPanelShell.tsx | 38 ++++++++++++++----- apps/web/src/components/LegacySidebar.tsx | 31 +++++++++------ apps/web/src/components/RightPanelSheet.tsx | 3 +- apps/web/src/components/Sidebar.logic.test.ts | 23 ----------- apps/web/src/components/Sidebar.logic.ts | 34 ----------------- .../src/components/SidebarStageBackdrop.tsx | 14 ++----- .../src/components/ThreadTerminalDrawer.tsx | 10 +++-- .../diffs/DiffFileLoadingBoundary.tsx | 2 +- .../components/files/ProjectFilePicker.tsx | 2 +- .../components/onboarding/WelcomeWizard.tsx | 5 ++- .../components/preview/PreviewPanelShell.tsx | 3 -- .../pullRequest/PullRequestDetailPanel.tsx | 8 ++-- .../pullRequest/PullRequestGhosts.tsx | 3 +- .../components/pullRequest/PullRequestRow.tsx | 19 ++++++++-- .../search/ProjectContentSearchDialog.tsx | 6 +-- .../settings/ProjectFaviconPickerDialog.tsx | 2 +- .../src/components/sidebar/SidebarChrome.tsx | 6 +-- .../sidebar/SidebarProviderUpdatePill.tsx | 11 +++--- apps/web/src/components/ui/alert-dialog.tsx | 2 +- apps/web/src/components/ui/button.tsx | 2 +- apps/web/src/components/ui/dialog.tsx | 2 +- apps/web/src/components/ui/empty.tsx | 6 +-- apps/web/src/components/ui/scroll-area.tsx | 2 +- apps/web/src/components/ui/sheet.tsx | 2 +- apps/web/src/index.css | 2 + apps/web/src/rightPanelLayout.ts | 2 - vite.config.ts | 8 ++++ 32 files changed, 138 insertions(+), 152 deletions(-) diff --git a/apps/web/src/components/AppSidebarLayout.tsx b/apps/web/src/components/AppSidebarLayout.tsx index eccf3ef21355..97c0bd44eb96 100644 --- a/apps/web/src/components/AppSidebarLayout.tsx +++ b/apps/web/src/components/AppSidebarLayout.tsx @@ -35,10 +35,7 @@ import LegacyThreadSidebar from "./LegacySidebar"; import ThreadSidebar from "./Sidebar"; import { SettingsSidebarNav } from "./settings/SettingsSidebarNav"; import { SidebarChromeHeader } from "./sidebar/SidebarChrome"; -import { - resolveSidebarStageFocusRingOffsetClass, - useSidebarStageBackdropVariant, -} from "./SidebarStageBackdrop"; +import { useSidebarStageBackdropVariant } from "./SidebarStageBackdrop"; import { useProjects } from "../state/entities"; import { resolveInitialThreadSidebarWidth, @@ -138,9 +135,6 @@ function SidebarControl() { className={cn( "pointer-events-auto", isSidebarVisible && stageBackdropVariant && "relative top-auto translate-y-0", - isSidebarVisible && - stageBackdropVariant && - resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant), )} aria-label="Toggle main sidebar" /> diff --git a/apps/web/src/components/ChatMarkdown.test.tsx b/apps/web/src/components/ChatMarkdown.test.tsx index 3e1222e0ebb9..d8a5f651ff83 100644 --- a/apps/web/src/components/ChatMarkdown.test.tsx +++ b/apps/web/src/components/ChatMarkdown.test.tsx @@ -639,7 +639,7 @@ describe("ChatMarkdown artifact-template cards", () => { ); expect(html).not.toContain("::artifact-template"); - expect(html).toContain("chat-markdown-artifact-template"); + expect(html).toContain("data-chat-markdown-artifact-template"); expect(html).toContain('data-artifact-kind="document"'); expect(html).toContain('data-markdown-copy="Hello World (Document template)\n\n"'); expect(html).toContain('data-skill-name="artifact-template-hello-world"'); @@ -654,7 +654,7 @@ describe("ChatMarkdown artifact-template cards", () => { , ); - expect(html).toContain("chat-markdown-artifact-template"); + expect(html).toContain("data-chat-markdown-artifact-template"); expect(html).not.toContain("Use template"); }); @@ -666,7 +666,7 @@ describe("ChatMarkdown artifact-template cards", () => { for (const text of [malformed, unfinished]) { const html = renderToStaticMarkup(); expect(html).toContain("::artifact-template"); - expect(html).not.toContain("chat-markdown-artifact-template"); + expect(html).not.toContain("data-chat-markdown-artifact-template"); } }); @@ -678,7 +678,7 @@ describe("ChatMarkdown artifact-template cards", () => { const html = renderToStaticMarkup(); expect(html).toContain("::artifact-template"); - expect(html).not.toContain("chat-markdown-artifact-template"); + expect(html).not.toContain("data-chat-markdown-artifact-template"); } }); @@ -691,7 +691,7 @@ describe("ChatMarkdown artifact-template cards", () => { ); expect(html.match(/::artifact-template/g)).toHaveLength(2); - expect(html).not.toContain("chat-markdown-artifact-template"); + expect(html).not.toContain("data-chat-markdown-artifact-template"); }); }); diff --git a/apps/web/src/components/ChatMarkdown.tsx b/apps/web/src/components/ChatMarkdown.tsx index f48c573aa8bc..56d46c010891 100644 --- a/apps/web/src/components/ChatMarkdown.tsx +++ b/apps/web/src/components/ChatMarkdown.tsx @@ -299,7 +299,8 @@ function CodexArtifactTemplateCard(props: {
diff --git a/apps/web/src/components/CommandPaletteContent.tsx b/apps/web/src/components/CommandPaletteContent.tsx index e9e6149b6cd7..8732c6ef926f 100644 --- a/apps/web/src/components/CommandPaletteContent.tsx +++ b/apps/web/src/components/CommandPaletteContent.tsx @@ -11,7 +11,11 @@ type CommandPaletteContentProps = Omit, "children readonly footerTrailing?: ReactNode; readonly inputAccessory?: ReactNode; readonly inputProps: ComponentProps; - readonly panelClassName?: string; + /** + * How tall the results panel may grow: the palette's list, a taller file list, or the whole + * dialog body (for modes that lay out their own status and empty states). + */ + readonly panelSize?: "list" | "tall-list" | "fill"; readonly showBackHint?: boolean; readonly testId?: string; }; @@ -28,7 +32,7 @@ export function CommandPaletteContent({ footerTrailing, inputAccessory, inputProps, - panelClassName, + panelSize = "list", showBackHint, testId, ...commandProps @@ -49,7 +53,17 @@ export function CommandPaletteContent({ {inputAccessory}
- {children} + + {children} +
diff --git a/apps/web/src/components/DiffPanelShell.tsx b/apps/web/src/components/DiffPanelShell.tsx index 61d78518b00a..0b7da6ab635a 100644 --- a/apps/web/src/components/DiffPanelShell.tsx +++ b/apps/web/src/components/DiffPanelShell.tsx @@ -46,14 +46,27 @@ export function DiffPanelShell(props: { ); } -export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: string }) { +export function DiffFileHeaderSkeleton({ + titleWidth, +}: { + titleWidth: "short" | "medium" | "long"; +}) { return (
- +
@@ -62,11 +75,16 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str ); } -function DiffCodeLineSkeleton({ contentClassName }: { contentClassName: string }) { +function DiffCodeLineSkeleton({ width }: { width: "short" | "medium" | "long" }) { return (
- +
); } @@ -79,19 +97,19 @@ export function DiffPanelLoadingState(props: { label: string }) { aria-live="polite" aria-label={props.label} > - +
- - - + + +
- - + + {props.label}
); diff --git a/apps/web/src/components/LegacySidebar.tsx b/apps/web/src/components/LegacySidebar.tsx index 075ccea0a1c5..82c5af7de136 100644 --- a/apps/web/src/components/LegacySidebar.tsx +++ b/apps/web/src/components/LegacySidebar.tsx @@ -26,6 +26,7 @@ import { ProjectFavicon } from "./ProjectFavicon"; import { useAtomValue } from "@effect/atom-react"; import { autoAnimate } from "@formkit/auto-animate"; import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react"; +import { cn } from "~/lib/utils"; import { useShallow } from "zustand/react/shallow"; import { DndContext, @@ -184,7 +185,6 @@ import { isSidebarNestedLinkClick, isTrailingDoubleClick, resolveProjectStatusIndicator, - resolveThreadRowClassName, resolveThreadStatusPill, orderItemsByPreferredIds, shouldClearThreadSelectionOnMouseDown, @@ -696,7 +696,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP }, [attemptArchiveThread, threadRef], ); - const rowButtonRender = useMemo(() =>
, []); return ( - span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-muted-foreground", + isActive + ? "bg-sidebar-row-active font-medium text-sidebar-foreground hover:bg-sidebar-row-active" + : isSelected + ? "bg-sidebar-row-selected text-sidebar-foreground hover:bg-sidebar-row-active" + : "text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground", + isFileDragOver && "ring-1 ring-inset ring-primary/70", + )} onClick={handleRowClick} onDoubleClick={handleRowDoubleClick} onKeyDown={handleRowKeyDown} @@ -934,7 +943,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
- +
); }); diff --git a/apps/web/src/components/RightPanelSheet.tsx b/apps/web/src/components/RightPanelSheet.tsx index e3468034396b..044369ed0f76 100644 --- a/apps/web/src/components/RightPanelSheet.tsx +++ b/apps/web/src/components/RightPanelSheet.tsx @@ -1,6 +1,5 @@ import { type ReactNode } from "react"; -import { RIGHT_PANEL_SHEET_CLASS_NAME } from "../rightPanelLayout"; import { Sheet, SheetPopup } from "./ui/sheet"; export function RightPanelSheet(props: { @@ -23,7 +22,7 @@ export function RightPanelSheet(props: { side="right" showCloseButton={false} keepMounted - className={RIGHT_PANEL_SHEET_CLASS_NAME} + className="w-[min(42vw,28rem)] min-w-80 max-w-[28rem] max-[760px]:w-[min(88vw,24rem)] max-[760px]:min-w-0 wco:mt-[env(titlebar-area-height)] wco:h-[calc(100%-env(titlebar-area-height))] wco:max-h-[calc(100%-env(titlebar-area-height))]" > {props.children} diff --git a/apps/web/src/components/Sidebar.logic.test.ts b/apps/web/src/components/Sidebar.logic.test.ts index 76960f3f533b..30044e641137 100644 --- a/apps/web/src/components/Sidebar.logic.test.ts +++ b/apps/web/src/components/Sidebar.logic.test.ts @@ -23,7 +23,6 @@ import { isTrailingDoubleClick, orderItemsByPreferredIds, resolveProjectStatusIndicator, - resolveThreadRowClassName, resolveSidebarThreadStatus, resolveThreadStatusPill, resolveWorkingStartedAt, @@ -2059,28 +2058,6 @@ describe("resolveThreadStatusPill", () => { }); }); -describe("resolveThreadRowClassName", () => { - it("uses the active sidebar surface when a thread is both selected and active", () => { - const className = resolveThreadRowClassName({ isActive: true, isSelected: true }); - expect(className).toContain("bg-sidebar-row-active"); - expect(className).toContain("text-sidebar-foreground"); - expect(className).not.toContain("bg-primary"); - }); - - it("uses selected hover colors for selected threads", () => { - const className = resolveThreadRowClassName({ isActive: false, isSelected: true }); - expect(className).toContain("bg-sidebar-row-selected"); - expect(className).toContain("hover:bg-sidebar-row-active"); - expect(className).not.toContain("bg-primary"); - }); - - it("uses the active sidebar surface for active-only threads", () => { - const className = resolveThreadRowClassName({ isActive: true, isSelected: false }); - expect(className).toContain("bg-sidebar-row-active"); - expect(className).toContain("hover:bg-sidebar-row-active"); - }); -}); - describe("resolveProjectStatusIndicator", () => { it("returns null when no threads have a notable status", () => { expect(resolveProjectStatusIndicator([null, null])).toBeNull(); diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts index fcedf46ce507..f4b13a5000bc 100644 --- a/apps/web/src/components/Sidebar.logic.ts +++ b/apps/web/src/components/Sidebar.logic.ts @@ -768,40 +768,6 @@ export function isContextMenuPointerDown(input: { return input.isMac && input.button === 0 && input.ctrlKey; } -export function resolveThreadRowClassName(input: { - isActive: boolean; - isSelected: boolean; -}): string { - const baseClassName = - "h-8 w-full translate-x-0 cursor-pointer justify-start rounded-md px-2 text-left text-sm select-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"; - - if (input.isSelected && input.isActive) { - return cn( - baseClassName, - "bg-sidebar-row-active text-sidebar-foreground font-medium hover:bg-sidebar-row-active hover:text-sidebar-foreground", - ); - } - - if (input.isSelected) { - return cn( - baseClassName, - "bg-sidebar-row-selected text-sidebar-foreground hover:bg-sidebar-row-active hover:text-sidebar-foreground", - ); - } - - if (input.isActive) { - return cn( - baseClassName, - "bg-sidebar-row-active text-sidebar-foreground font-medium hover:bg-sidebar-row-active hover:text-sidebar-foreground", - ); - } - - return cn( - baseClassName, - "text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground", - ); -} - // ── Sidebar thread status model ───────────────────────────────────── // Five visual states, three colors: color is reserved for "act now" // (approval), "in motion" (working), and "broken" (failed). Ready is the diff --git a/apps/web/src/components/SidebarStageBackdrop.tsx b/apps/web/src/components/SidebarStageBackdrop.tsx index 9f77d7298c39..c1e020906492 100644 --- a/apps/web/src/components/SidebarStageBackdrop.tsx +++ b/apps/web/src/components/SidebarStageBackdrop.tsx @@ -23,14 +23,6 @@ export function resolveSidebarStageBackdropVariant( return null; } -export function resolveSidebarStageFocusRingOffsetClass( - variant: SidebarStageBackdropVariant, -): string { - return variant === "nightly" - ? "focus-visible:ring-offset-(--stage-night-bottom)" - : "focus-visible:ring-offset-(--stage-art-bottom)"; -} - export function resolveEnvironmentIdentificationPillLabel( stageLabel: string, ): EnvironmentIdentificationPillLabel | null { @@ -116,7 +108,8 @@ function NightlySkyArt({ compact = false }: { compact?: boolean }) { return ( void; cou
{Array.from({ length: Math.min(count, 4) }, (_, index) => (
- +
))}
diff --git a/apps/web/src/components/files/ProjectFilePicker.tsx b/apps/web/src/components/files/ProjectFilePicker.tsx index a59e8dd77e47..38d21f6c1005 100644 --- a/apps/web/src/components/files/ProjectFilePicker.tsx +++ b/apps/web/src/components/files/ProjectFilePicker.tsx @@ -130,7 +130,7 @@ function OpenProjectFilePicker(props: ProjectFilePickerProps & { target: ActiveP setHighlightedItemValue(null); setQuery(value); }} - panelClassName="max-h-[min(34rem,76vh)]" + panelSize="tall-list" testId="project-file-picker" value={query} > diff --git a/apps/web/src/components/onboarding/WelcomeWizard.tsx b/apps/web/src/components/onboarding/WelcomeWizard.tsx index 26ba0cea419d..169f1f8b0575 100644 --- a/apps/web/src/components/onboarding/WelcomeWizard.tsx +++ b/apps/web/src/components/onboarding/WelcomeWizard.tsx @@ -887,7 +887,10 @@ function AgentInstallTerminal({ ]); return ( -
+
{setupState === "writeFailed" ? ( diff --git a/apps/web/src/components/preview/PreviewPanelShell.tsx b/apps/web/src/components/preview/PreviewPanelShell.tsx index eddb549cf225..71dde5b3ad91 100644 --- a/apps/web/src/components/preview/PreviewPanelShell.tsx +++ b/apps/web/src/components/preview/PreviewPanelShell.tsx @@ -7,7 +7,6 @@ import { useState, } from "react"; -import { isElectron } from "~/env"; import { useResizableWidth } from "~/hooks/useResizableWidth"; import { cn } from "~/lib/utils"; @@ -64,7 +63,6 @@ export function PreviewPanelShell(props: { defaultWidth?: number; children: ReactNode; }) { - const useDragRegion = isElectron && props.mode !== "sheet" && props.mode !== "embedded"; const isInline = props.mode === "inline"; const collapsible = isInline && props.open !== undefined; const open = props.open ?? true; @@ -149,7 +147,6 @@ export function PreviewPanelShell(props: { className="flex h-full min-h-0 min-w-0 flex-col" style={collapsible && !maximized ? { width: `calc(${width}px - 1px)` } : undefined} > - {useDragRegion ?
: null} {props.children}
diff --git a/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx b/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx index 536bab75b438..de2ee18f9d99 100644 --- a/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx +++ b/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx @@ -1980,9 +1980,11 @@ export function PullRequestDetailPanel({ ) : (primaryAction === "merged" || primaryAction === "closed") && statePresentation !== null ? ( - - - {statePresentation.label} + + + + {statePresentation.label} + ) : null} diff --git a/apps/web/src/components/pullRequest/PullRequestGhosts.tsx b/apps/web/src/components/pullRequest/PullRequestGhosts.tsx index 78d93915e0dd..85c9802c2170 100644 --- a/apps/web/src/components/pullRequest/PullRequestGhosts.tsx +++ b/apps/web/src/components/pullRequest/PullRequestGhosts.tsx @@ -169,10 +169,9 @@ export function PullRequestDetailGhost({ {seed.repository} void readLocalApi()?.shell.openExternal(seed.url)} - className={statePresentation?.toneClassName} aria-label={`Open pull request #${seed.number} on host`} > - #{seed.number} + #{seed.number} diff --git a/apps/web/src/components/pullRequest/PullRequestRow.tsx b/apps/web/src/components/pullRequest/PullRequestRow.tsx index e32decde136c..898f58ecb220 100644 --- a/apps/web/src/components/pullRequest/PullRequestRow.tsx +++ b/apps/web/src/components/pullRequest/PullRequestRow.tsx @@ -27,10 +27,11 @@ import { * narrow row shows one label and a "+N" while a wide one spreads out up to three. The "+N" * rides on whichever pill is the last visible one, and is hidden as soon as the next slot shows. */ +// Each slot's pill appears at a wider row, and its overflow count yields to the next slot. const LABEL_SLOTS = [ - { pill: "", overflow: "@xl/pr-row-meta:hidden" }, - { pill: "hidden @xl/pr-row-meta:inline-flex", overflow: "@3xl/pr-row-meta:hidden" }, - { pill: "hidden @3xl/pr-row-meta:inline-flex", overflow: "" }, + { overflow: "@xl/pr-row-meta:hidden" }, + { overflow: "@3xl/pr-row-meta:hidden" }, + { overflow: "" }, ] as const; function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry["labels"] }) { @@ -42,7 +43,17 @@ function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry[ if (!label) return null; const remaining = labels.length - index - 1; return ( - + {remaining > 0 ? ( +{remaining} ) : null} diff --git a/apps/web/src/components/search/ProjectContentSearchDialog.tsx b/apps/web/src/components/search/ProjectContentSearchDialog.tsx index e037dd0ee9c8..2812b6004692 100644 --- a/apps/web/src/components/search/ProjectContentSearchDialog.tsx +++ b/apps/web/src/components/search/ProjectContentSearchDialog.tsx @@ -88,11 +88,11 @@ function EmptyContentSearchDialog() { footerActionLabel="Open file" inputProps={{ disabled: true, placeholder: "Search project contents…" }} mode="none" - panelClassName="flex min-h-0 flex-1 items-center justify-center" + panelSize="fill" testId="project-content-search" value="" > -

+

Open a project to search its files.

@@ -219,7 +219,7 @@ function OpenContentSearchDialog(props: { }} mode="none" onValueChange={setQuery} - panelClassName="flex min-h-0 flex-1 flex-col" + panelSize="fill" testId="project-content-search" value={query} > diff --git a/apps/web/src/components/settings/ProjectFaviconPickerDialog.tsx b/apps/web/src/components/settings/ProjectFaviconPickerDialog.tsx index a0971db3ac25..5ed077f31bcf 100644 --- a/apps/web/src/components/settings/ProjectFaviconPickerDialog.tsx +++ b/apps/web/src/components/settings/ProjectFaviconPickerDialog.tsx @@ -115,7 +115,7 @@ export function ProjectFaviconPickerDialog(props: { setHighlightedItemValue(null); setQuery(value); }} - panelClassName="max-h-[min(34rem,76vh)]" + panelSize="tall-list" testId="project-favicon-picker" value={query} > diff --git a/apps/web/src/components/sidebar/SidebarChrome.tsx b/apps/web/src/components/sidebar/SidebarChrome.tsx index c956ab0a63c8..a37aba27b8de 100644 --- a/apps/web/src/components/sidebar/SidebarChrome.tsx +++ b/apps/web/src/components/sidebar/SidebarChrome.tsx @@ -10,7 +10,6 @@ import { T3Wordmark } from "../T3Wordmark"; import { resolveEnvironmentIdentificationPillLabel, resolveSidebarStageBackdropVariant, - resolveSidebarStageFocusRingOffsetClass, SidebarStageBackdrop, useEnvironmentStageLabel, } from "../SidebarStageBackdrop"; @@ -58,10 +57,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({ {pillLabel ? ( diff --git a/apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx b/apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx index 2d1159bdb40c..b77848f96f24 100644 --- a/apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx +++ b/apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx @@ -14,13 +14,13 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; const PROVIDER_UPDATE_PILL_STYLES = { loading: - "bg-sidebar-control-surface text-sidebar-foreground group-has-[button.provider-update-main:hover]/provider-update:bg-sidebar-row-hover", + "bg-sidebar-control-surface text-sidebar-foreground group-has-[button[data-provider-update-main]:hover]/provider-update:bg-sidebar-row-hover", success: - "bg-sidebar-control-surface text-sidebar-foreground group-has-[button.provider-update-main:hover]/provider-update:bg-sidebar-row-hover", + "bg-sidebar-control-surface text-sidebar-foreground group-has-[button[data-provider-update-main]:hover]/provider-update:bg-sidebar-row-hover", warning: - "bg-warning/12 text-warning group-has-[button.provider-update-main:hover]/provider-update:bg-warning/18", + "bg-warning/12 text-warning group-has-[button[data-provider-update-main]:hover]/provider-update:bg-warning/18", error: - "bg-destructive/12 text-destructive group-has-[button.provider-update-main:hover]/provider-update:bg-destructive/18", + "bg-destructive/12 text-destructive group-has-[button[data-provider-update-main]:hover]/provider-update:bg-destructive/18", } as const; const PROVIDER_UPDATE_PILL_PROGRESS_STYLES = { @@ -169,7 +169,8 @@ export function SidebarProviderUpdatePill() {
) : null} {thread.session?.lastError ? ( -
+
Error occurred
@@ -529,8 +529,8 @@ function SortableThreadRow(props: { // Unsent work shares one look: the new-thread draft rows and thread rows // with unsent composer text both use this tint and pen so they read alike. -const draftSurfaceClassName = "bg-amber-400/[0.04] hover:bg-amber-400/[0.08]"; -const draftPenClassName = "size-3 shrink-0 text-amber-600 dark:text-amber-300/80"; +const draftSurfaceClassName = "bg-warning/4 hover:bg-warning/8"; +const draftPenClassName = "size-3 shrink-0 text-warning-foreground"; // Structural list items — the section headers and the // empty-section placeholders — take part in the sortable list so they shift @@ -647,7 +647,7 @@ function SidebarSectionHeader(props: { const snoozed = props.marker === "snoozed-header"; const className = cn( "flex h-full w-full items-center gap-2 px-2 text-left text-xs font-medium", - snoozed ? "text-blue-600 dark:text-blue-400" : "text-sidebar-muted-foreground/60", + snoozed ? "text-info-foreground" : "text-sidebar-muted-foreground/60", props.dragging && "text-sidebar-foreground/80", props.isDropTarget && "text-primary", ); @@ -658,7 +658,7 @@ function SidebarSectionHeader(props: { aria-hidden className={cn( "h-px min-w-2 flex-1", - snoozed ? "bg-blue-500/20 dark:bg-blue-400/15" : "bg-sidebar-border/60", + snoozed ? "bg-info/20" : "bg-sidebar-border/60", props.dragging && "bg-sidebar-foreground/25", props.isDropTarget && "bg-primary/50", )} @@ -1143,7 +1143,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { ? { label: "Approval", icon: "approval" as const, - className: "text-amber-700 dark:text-amber-300", + className: "text-warning-foreground", } : status === "input" ? { @@ -1161,7 +1161,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { ? { label: "Woke", icon: "woke" as const, - className: "text-amber-700 dark:text-amber-300", + className: "text-warning-foreground", } : isUnread ? { @@ -1629,7 +1629,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { {variantAction === "unsnooze" && props.snoozeWakeLabelText !== null ? ( // Snoozed rows show when they come BACK, not when they were // last touched — the return ticket is the row's whole story. - + {props.snoozeWakeLabelText} ) : isWoke ? ( @@ -1642,7 +1642,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { type="button" aria-label="Dismiss Woke notification" onClick={handleAcknowledgeWokeClick} - className="inline-flex cursor-pointer items-center gap-1 rounded-sm text-xs font-medium text-amber-700 outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring dark:text-amber-300" + className="inline-flex cursor-pointer items-center gap-1 rounded-sm text-xs font-medium text-warning-foreground outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring" > Woke diff --git a/apps/web/src/components/ThreadNotificationCoordinator.tsx b/apps/web/src/components/ThreadNotificationCoordinator.tsx index 1401965e90c7..c4494f866ccb 100644 --- a/apps/web/src/components/ThreadNotificationCoordinator.tsx +++ b/apps/web/src/components/ThreadNotificationCoordinator.tsx @@ -164,22 +164,13 @@ function EnvironmentNotifications({ hideCopyButton: true, leadingIcon: kind === "completion" ? ( - + ) : status === "approval" ? ( - + ) : status === "failed" ? ( - + ) : ( - + ), }, actionProps: { diff --git a/apps/web/src/components/ThreadSearchMatch.tsx b/apps/web/src/components/ThreadSearchMatch.tsx index db2e286ae28d..a79ab8543987 100644 --- a/apps/web/src/components/ThreadSearchMatch.tsx +++ b/apps/web/src/components/ThreadSearchMatch.tsx @@ -57,7 +57,7 @@ export function ThreadSearchMatchExcerpt(props: { const isUser = props.match.source === "user"; return ( - + {isUser ? "You:" : "Agent:"} {" "} diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 15440c577ef3..c5d1d01dad60 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -6559,7 +6559,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) diff --git a/apps/web/src/components/chat/ContextWindowMeter.tsx b/apps/web/src/components/chat/ContextWindowMeter.tsx index 8f6b09954d3a..43d5fb061d4e 100644 --- a/apps/web/src/components/chat/ContextWindowMeter.tsx +++ b/apps/web/src/components/chat/ContextWindowMeter.tsx @@ -63,7 +63,7 @@ export function ContextWindowMeter(props: { cy="12" r={radius} fill="none" - stroke="color-mix(in oklab, var(--color-muted-foreground) 24%, transparent)" + className="stroke-muted-foreground/24" strokeWidth="3" /> - - - - - - - - - - - - ); -} - function ToolActivityIconView(props: { icon: ToolActivityIcon | undefined; fallbackName: WorkEntryIconName; diff --git a/apps/web/src/components/chat/ModelListRow.tsx b/apps/web/src/components/chat/ModelListRow.tsx index 7206d8b79527..7e8bdeb24ce8 100644 --- a/apps/web/src/components/chat/ModelListRow.tsx +++ b/apps/web/src/components/chat/ModelListRow.tsx @@ -117,7 +117,7 @@ export const ModelListRow = memo(function ModelListRow(props: { diff --git a/apps/web/src/components/chat/TerminalContextInlineChip.tsx b/apps/web/src/components/chat/TerminalContextInlineChip.tsx index 8ec06221c649..faf5ae3dccbc 100644 --- a/apps/web/src/components/chat/TerminalContextInlineChip.tsx +++ b/apps/web/src/components/chat/TerminalContextInlineChip.tsx @@ -26,7 +26,7 @@ export function TerminalContextInlineChip(props: TerminalContextInlineChipProps) >
- + {terminalLabel} diff --git a/apps/web/src/components/cloud/ConnectCliAuthSurface.tsx b/apps/web/src/components/cloud/ConnectCliAuthSurface.tsx index afe0dbc31c5e..408d2bbe36e0 100644 --- a/apps/web/src/components/cloud/ConnectCliAuthSurface.tsx +++ b/apps/web/src/components/cloud/ConnectCliAuthSurface.tsx @@ -23,7 +23,7 @@ function ConnectCliAuthMessage({ return ( <> {eyebrow ? ( -

+

{eyebrow}

) : null} diff --git a/apps/web/src/components/device/DeviceStreamView.tsx b/apps/web/src/components/device/DeviceStreamView.tsx index 3e2cd54e3c26..0a4cfb97b1bf 100644 --- a/apps/web/src/components/device/DeviceStreamView.tsx +++ b/apps/web/src/components/device/DeviceStreamView.tsx @@ -395,7 +395,7 @@ export function DeviceStreamView(props: { {axElements.map((element) => (
{element.label ? ( - + {element.label} ) : null} diff --git a/apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx b/apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx index 3e42c39715ee..0538495da7c4 100644 --- a/apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx +++ b/apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx @@ -462,7 +462,9 @@ function MiniPlayerShell({
@@ -475,7 +477,7 @@ function MiniPlayerShell({ > {recording ? ( - + ) : null} diff --git a/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx b/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx index 997f2d46f836..1139b412bde5 100644 --- a/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx +++ b/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx @@ -852,7 +852,7 @@ function PullRequestCodeTab({ /> {stale ? ( - }> + }> Changed @@ -1157,7 +1157,7 @@ function PullRequestCodeTab({ }> @@ -1171,7 +1171,7 @@ function PullRequestCodeTab({ }> @@ -1187,7 +1187,7 @@ function PullRequestCodeTab({ }> diff --git a/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx b/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx index de2ee18f9d99..e8507ae3cfe6 100644 --- a/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx +++ b/apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx @@ -379,7 +379,7 @@ function PullRequestBaseFreshnessWarning({ type="button" aria-label={summary} className={cn( - "inline-flex min-w-0 shrink-0 cursor-help items-center gap-1 rounded-sm text-amber-600 outline-none focus-visible:ring-2 focus-visible:ring-ring dark:text-amber-400/90", + "inline-flex min-w-0 shrink-0 cursor-help items-center gap-1 rounded-sm text-warning-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring", className, )} /> diff --git a/apps/web/src/components/pullRequest/PullRequestReviewAnnotation.tsx b/apps/web/src/components/pullRequest/PullRequestReviewAnnotation.tsx index 11acd48a4c10..1ba8ccba3780 100644 --- a/apps/web/src/components/pullRequest/PullRequestReviewAnnotation.tsx +++ b/apps/web/src/components/pullRequest/PullRequestReviewAnnotation.tsx @@ -220,7 +220,7 @@ export function ReviewThreadCard({ >
{thread.isResolved ? ( - + ) : ( )} diff --git a/apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx b/apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx index 33b9a42c6ce0..100a0f5737b9 100644 --- a/apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx +++ b/apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx @@ -943,7 +943,7 @@ export function PullRequestSummaryTab({ ) : ( <> {detail.commentsTruncated ? ( -

+

This conversation is longer than this page reads in one go. The most recent{" "} {detail.comments.length} are here; open it on the host to read the rest.

diff --git a/apps/web/src/components/settings/AddProviderInstanceDialog.tsx b/apps/web/src/components/settings/AddProviderInstanceDialog.tsx index f912f5f68d65..806a887822fc 100644 --- a/apps/web/src/components/settings/AddProviderInstanceDialog.tsx +++ b/apps/web/src/components/settings/AddProviderInstanceDialog.tsx @@ -259,7 +259,7 @@ export function AddProviderInstanceDialog({ diff --git a/apps/web/src/components/settings/ConnectionsSettings.tsx b/apps/web/src/components/settings/ConnectionsSettings.tsx index defd65cbf6b9..6455831ccb04 100644 --- a/apps/web/src/components/settings/ConnectionsSettings.tsx +++ b/apps/web/src/components/settings/ConnectionsSettings.tsx @@ -756,7 +756,7 @@ const PairingLinkListRow = memo(function PairingLinkListRow({

{primaryLabel}

diff --git a/apps/web/src/components/settings/DiagnosticsSettings.tsx b/apps/web/src/components/settings/DiagnosticsSettings.tsx index 15ad85bbb1a7..17555540145a 100644 --- a/apps/web/src/components/settings/DiagnosticsSettings.tsx +++ b/apps/web/src/components/settings/DiagnosticsSettings.tsx @@ -116,7 +116,7 @@ function StatBlock({
@@ -287,7 +287,7 @@ function ProcessNameCell({ ) : (
@@ -1063,17 +1061,17 @@ export function ResourceTelemetryDiagnostics({
@@ -1186,7 +1184,7 @@ export function ResourceTelemetryDiagnostics({ value={String(snapshot.health.inaccessibleProcessCount)} valueClassName={ snapshot.health.inaccessibleProcessCount > 0 - ? "text-amber-600 dark:text-amber-300" + ? "text-warning-foreground" : undefined } /> diff --git a/apps/web/src/components/settings/SnapShotSetupDialog.tsx b/apps/web/src/components/settings/SnapShotSetupDialog.tsx index f0adbcc40cf1..c5ba97cedd97 100644 --- a/apps/web/src/components/settings/SnapShotSetupDialog.tsx +++ b/apps/web/src/components/settings/SnapShotSetupDialog.tsx @@ -5,7 +5,8 @@ import { type DesktopSnapShotSetupAction, type DesktopSnapShotState, } from "@t3tools/contracts"; -import { useId, useState, type ReactNode } from "react"; +import { useState, type ReactNode } from "react"; +import { MacAccessibilityIcon, MacScreenRecordingIcon } from "../Icons"; import { CaptureShortcutConfig } from "./CaptureShortcutConfig"; import { Button } from "../ui/button"; import { Dialog, DialogDescription } from "../ui/dialog"; @@ -61,72 +62,6 @@ const GNOME_ACCESS_COPY = { }, }; -function ScreenRecordingIcon() { - const gradientId = useId(); - return ( - - ); -} - -function AccessibilityPermissionIcon() { - const gradientId = useId(); - return ( - - ); -} - export function SnapShotSetupDialog({ state, initialStep, @@ -333,7 +268,9 @@ export function SnapShotSetupDialog({ permissions={[ { id: "screenRecording", - icon: , + icon: ( + + ), title: "Screen Recording", description: "Capture the window you're using.", granted: macPermissions.screenRecording, @@ -341,7 +278,9 @@ export function SnapShotSetupDialog({ }, { id: "accessibility", - icon: , + icon: ( + + ), title: "Accessibility", description: includeAccessibility ? "Include text and controls from the captured app." diff --git a/apps/web/src/components/sidebar/DesktopUpdateStatusIcon.tsx b/apps/web/src/components/sidebar/DesktopUpdateStatusIcon.tsx index 833559e1cc27..04ee884bbf6c 100644 --- a/apps/web/src/components/sidebar/DesktopUpdateStatusIcon.tsx +++ b/apps/web/src/components/sidebar/DesktopUpdateStatusIcon.tsx @@ -67,7 +67,7 @@ function DesktopUpdateDownloadingIcon({ percent }: { readonly percent: number | cy="16" r={DOWNLOAD_PROGRESS_RADIUS} fill="none" - stroke="color-mix(in srgb, currentColor 22%, transparent)" + className="stroke-current/22" strokeWidth="1.5" /> ) : showUsageStatus && hasIssue ? ( ) : ( diff --git a/apps/web/src/routes/_chat.pull-requests.tsx b/apps/web/src/routes/_chat.pull-requests.tsx index 7b3d4d4418dd..87439e8a4bc9 100644 --- a/apps/web/src/routes/_chat.pull-requests.tsx +++ b/apps/web/src/routes/_chat.pull-requests.tsx @@ -1819,7 +1819,7 @@ function PullRequestsRouteView() { )} {listQuery.error && shownCount > 0 ? ( -
+
{listQuery.error} Showing the last pull requests loaded. diff --git a/apps/web/src/components/WorkspaceBreadcrumb.tsx b/apps/web/src/components/WorkspaceBreadcrumb.tsx index 015e381aa251..2d5729cb7d15 100644 --- a/apps/web/src/components/WorkspaceBreadcrumb.tsx +++ b/apps/web/src/components/WorkspaceBreadcrumb.tsx @@ -33,7 +33,7 @@ export function WorkspaceBreadcrumbText({ children, className, ...props }: Compo // Center the capital letters with adjacent icons, not the font's leading. // Padding preserves accents and descenders when a label is truncated. className={cn( - "block min-w-0 truncate [text-box:trim-both_cap_alphabetic] supports-[text-box:trim-both_cap_alphabetic]:py-[0.5em]", + "block min-w-0 truncate [text-box:trim-both_cap_alphabetic] supports-[text-box:trim-both_cap_alphabetic]:py-1.5", className, )} {...props} diff --git a/apps/web/src/components/WorkspacePageHeader.tsx b/apps/web/src/components/WorkspacePageHeader.tsx index 5d98b80760ac..a484b4497539 100644 --- a/apps/web/src/components/WorkspacePageHeader.tsx +++ b/apps/web/src/components/WorkspacePageHeader.tsx @@ -16,9 +16,9 @@ export function WorkspacePageHeader({ return (
-

+

{APP_DISPLAY_NAME}

diff --git a/apps/web/src/components/chat/AssistantCitationChip.tsx b/apps/web/src/components/chat/AssistantCitationChip.tsx index d838c53d27d2..6baf14b18887 100644 --- a/apps/web/src/components/chat/AssistantCitationChip.tsx +++ b/apps/web/src/components/chat/AssistantCitationChip.tsx @@ -110,7 +110,7 @@ export function AssistantCitationChip({ const composerSourceLink = (