authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-24 10:49:04-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-24 10:49:04-07:00
log80fc23ac32cd8fe5011a94420cff1f469367f1fc
tree0c057e6e61124858bd78320b800488e01e77db76
parentcb1a3f34670b06148d88806a9534772879c30ecd
signature Signed by PGP key B5690EEEBB952194

lint/unknown and static (#13366)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

32 files changed, 138 insertions(+), 152 deletions(-)

apps/web/src/components/AppSidebarLayout.tsx+1-7
...@@ -35,10 +35,7 @@ import LegacyThreadSidebar from "./LegacySidebar";...@@ -35,10 +35,7 @@ import LegacyThreadSidebar from "./LegacySidebar";
35import ThreadSidebar from "./Sidebar";35import ThreadSidebar from "./Sidebar";
36import { SettingsSidebarNav } from "./settings/SettingsSidebarNav";36import { SettingsSidebarNav } from "./settings/SettingsSidebarNav";
37import { SidebarChromeHeader } from "./sidebar/SidebarChrome";37import { SidebarChromeHeader } from "./sidebar/SidebarChrome";
38import {38import { useSidebarStageBackdropVariant } from "./SidebarStageBackdrop";
39 resolveSidebarStageFocusRingOffsetClass,
40 useSidebarStageBackdropVariant,
41} from "./SidebarStageBackdrop";
42import { useProjects } from "../state/entities";39import { useProjects } from "../state/entities";
43import {40import {
44 resolveInitialThreadSidebarWidth,41 resolveInitialThreadSidebarWidth,
...@@ -138,9 +135,6 @@ function SidebarControl() {...@@ -138,9 +135,6 @@ function SidebarControl() {
138 className={cn(135 className={cn(
139 "pointer-events-auto",136 "pointer-events-auto",
140 isSidebarVisible && stageBackdropVariant && "relative top-auto translate-y-0",137 isSidebarVisible && stageBackdropVariant && "relative top-auto translate-y-0",
141 isSidebarVisible &&
142 stageBackdropVariant &&
143 resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant),
144 )}138 )}
145 aria-label="Toggle main sidebar"139 aria-label="Toggle main sidebar"
146 />140 />
apps/web/src/components/ChatMarkdown.test.tsx+5-5
...@@ -639,7 +639,7 @@ describe("ChatMarkdown artifact-template cards", () => {...@@ -639,7 +639,7 @@ describe("ChatMarkdown artifact-template cards", () => {
639 );639 );
640640
641 expect(html).not.toContain("::artifact-template");641 expect(html).not.toContain("::artifact-template");
642 expect(html).toContain("chat-markdown-artifact-template");642 expect(html).toContain("data-chat-markdown-artifact-template");
643 expect(html).toContain('data-artifact-kind="document"');643 expect(html).toContain('data-artifact-kind="document"');
644 expect(html).toContain('data-markdown-copy="Hello World (Document template)\n\n"');644 expect(html).toContain('data-markdown-copy="Hello World (Document template)\n\n"');
645 expect(html).toContain('data-skill-name="artifact-template-hello-world"');645 expect(html).toContain('data-skill-name="artifact-template-hello-world"');
...@@ -654,7 +654,7 @@ describe("ChatMarkdown artifact-template cards", () => {...@@ -654,7 +654,7 @@ describe("ChatMarkdown artifact-template cards", () => {
654 <ChatMarkdown cwd="/tmp/project" text={ARTIFACT_TEMPLATE_DIRECTIVE} />,654 <ChatMarkdown cwd="/tmp/project" text={ARTIFACT_TEMPLATE_DIRECTIVE} />,
655 );655 );
656656
657 expect(html).toContain("chat-markdown-artifact-template");657 expect(html).toContain("data-chat-markdown-artifact-template");
658 expect(html).not.toContain("Use template");658 expect(html).not.toContain("Use template");
659 });659 });
660660
...@@ -666,7 +666,7 @@ describe("ChatMarkdown artifact-template cards", () => {...@@ -666,7 +666,7 @@ describe("ChatMarkdown artifact-template cards", () => {
666 for (const text of [malformed, unfinished]) {666 for (const text of [malformed, unfinished]) {
667 const html = renderToStaticMarkup(<ChatMarkdown cwd="/tmp/project" text={text} />);667 const html = renderToStaticMarkup(<ChatMarkdown cwd="/tmp/project" text={text} />);
668 expect(html).toContain("::artifact-template");668 expect(html).toContain("::artifact-template");
669 expect(html).not.toContain("chat-markdown-artifact-template");669 expect(html).not.toContain("data-chat-markdown-artifact-template");
670 }670 }
671 });671 });
672672
...@@ -678,7 +678,7 @@ describe("ChatMarkdown artifact-template cards", () => {...@@ -678,7 +678,7 @@ describe("ChatMarkdown artifact-template cards", () => {
678 const html = renderToStaticMarkup(<ChatMarkdown cwd="/tmp/project" text={text} />);678 const html = renderToStaticMarkup(<ChatMarkdown cwd="/tmp/project" text={text} />);
679679
680 expect(html).toContain("::artifact-template");680 expect(html).toContain("::artifact-template");
681 expect(html).not.toContain("chat-markdown-artifact-template");681 expect(html).not.toContain("data-chat-markdown-artifact-template");
682 }682 }
683 });683 });
684684
...@@ -691,7 +691,7 @@ describe("ChatMarkdown artifact-template cards", () => {...@@ -691,7 +691,7 @@ describe("ChatMarkdown artifact-template cards", () => {
691 );691 );
692692
693 expect(html.match(/::artifact-template/g)).toHaveLength(2);693 expect(html.match(/::artifact-template/g)).toHaveLength(2);
694 expect(html).not.toContain("chat-markdown-artifact-template");694 expect(html).not.toContain("data-chat-markdown-artifact-template");
695 });695 });
696});696});
697697
apps/web/src/components/ChatMarkdown.tsx+2-1
...@@ -299,7 +299,8 @@ function CodexArtifactTemplateCard(props: {...@@ -299,7 +299,8 @@ function CodexArtifactTemplateCard(props: {
299 <div299 <div
300 role="group"300 role="group"
301 aria-label={`${props.template.displayName} template`}301 aria-label={`${props.template.displayName} template`}
302 className="chat-markdown-artifact-template my-[0.65rem] flex w-full min-w-0 items-center gap-3 rounded-xl border border-border/70 bg-card/60 px-3 py-2.5 text-foreground shadow-xs"302 data-chat-markdown-artifact-template
303 className="my-[0.65rem] flex w-full min-w-0 items-center gap-3 rounded-xl border border-border/70 bg-card/60 px-3 py-2.5 text-foreground shadow-xs"
303 data-artifact-kind={props.template.artifactKind}304 data-artifact-kind={props.template.artifactKind}
304 data-markdown-copy={`${props.template.displayName} (${presentationLabel})\n\n`}305 data-markdown-copy={`${props.template.displayName} (${presentationLabel})\n\n`}
305 data-skill-name={props.template.skillName}306 data-skill-name={props.template.skillName}
apps/web/src/components/CommandPalette.tsx-1
...@@ -2985,7 +2985,6 @@ function OpenCommandPaletteDialog(props: {...@@ -2985,7 +2985,6 @@ function OpenCommandPaletteDialog(props: {
2985 setHighlightedItemValue(typeof value === "string" ? value : null);2985 setHighlightedItemValue(typeof value === "string" ? value : null);
2986 }}2986 }}
2987 onValueChange={handleQueryChange}2987 onValueChange={handleQueryChange}
2988 panelClassName="max-h-[min(28rem,70vh)]"
2989 showBackHint={isSubmenu}2988 showBackHint={isSubmenu}
2990 value={query}2989 value={query}
2991 >2990 >
apps/web/src/components/CommandPaletteContent.tsx+17-3
...@@ -11,7 +11,11 @@ type CommandPaletteContentProps = Omit<ComponentProps<typeof Command>, "children...@@ -11,7 +11,11 @@ type CommandPaletteContentProps = Omit<ComponentProps<typeof Command>, "children
11 readonly footerTrailing?: ReactNode;11 readonly footerTrailing?: ReactNode;
12 readonly inputAccessory?: ReactNode;12 readonly inputAccessory?: ReactNode;
13 readonly inputProps: ComponentProps<typeof CommandInput>;13 readonly inputProps: ComponentProps<typeof CommandInput>;
14 readonly panelClassName?: string;14 /**
15 * How tall the results panel may grow: the palette's list, a taller file list, or the whole
16 * dialog body (for modes that lay out their own status and empty states).
17 */
18 readonly panelSize?: "list" | "tall-list" | "fill";
15 readonly showBackHint?: boolean;19 readonly showBackHint?: boolean;
16 readonly testId?: string;20 readonly testId?: string;
17};21};
...@@ -28,7 +32,7 @@ export function CommandPaletteContent({...@@ -28,7 +32,7 @@ export function CommandPaletteContent({
28 footerTrailing,32 footerTrailing,
29 inputAccessory,33 inputAccessory,
30 inputProps,34 inputProps,
31 panelClassName,35 panelSize = "list",
32 showBackHint,36 showBackHint,
33 testId,37 testId,
34 ...commandProps38 ...commandProps
...@@ -49,7 +53,17 @@ export function CommandPaletteContent({...@@ -49,7 +53,17 @@ export function CommandPaletteContent({
49 <CommandInput {...inputProps} ref={inputRef} />53 <CommandInput {...inputProps} ref={inputRef} />
50 {inputAccessory}54 {inputAccessory}
51 </div>55 </div>
52 <CommandPanel className={panelClassName}>{children}</CommandPanel>56 <CommandPanel
57 className={
58 panelSize === "fill"
59 ? "flex min-h-0 flex-1 flex-col"
60 : panelSize === "tall-list"
61 ? "max-h-[min(34rem,76vh)]"
62 : "max-h-[min(28rem,70vh)]"
63 }
64 >
65 {children}
66 </CommandPanel>
53 <CommandFooter className="max-sm:flex-col max-sm:items-start">67 <CommandFooter className="max-sm:flex-col max-sm:items-start">
54 <div className="flex items-center gap-3">68 <div className="flex items-center gap-3">
55 <KbdGroup>69 <KbdGroup>
apps/web/src/components/DiffPanelShell.tsx+28-10
...@@ -46,14 +46,27 @@ export function DiffPanelShell(props: {...@@ -46,14 +46,27 @@ export function DiffPanelShell(props: {
46 );46 );
47}47}
4848
49export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: string }) {49export function DiffFileHeaderSkeleton({
50 titleWidth,
51}: {
52 titleWidth: "short" | "medium" | "long";
53}) {
50 return (54 return (
51 <div className="flex h-8 items-center gap-2 px-2 pr-3">55 <div className="flex h-8 items-center gap-2 px-2 pr-3">
52 <div className="flex size-5 shrink-0 items-center justify-center">56 <div className="flex size-5 shrink-0 items-center justify-center">
53 <Skeleton className="size-2.5" />57 <Skeleton className="size-2.5" />
54 </div>58 </div>
55 <Skeleton className="size-5 shrink-0" />59 <Skeleton className="size-5 shrink-0" />
56 <Skeleton shape="pill" className={cn("h-3", titleClassName)} />60 <Skeleton
61 shape="pill"
62 className={
63 titleWidth === "short"
64 ? "h-3 w-2/5 max-w-52"
65 : titleWidth === "medium"
66 ? "h-3 w-1/2 max-w-64"
67 : "h-3 w-3/5 max-w-72"
68 }
69 />
57 <div className="ml-auto flex shrink-0 items-center gap-2">70 <div className="ml-auto flex shrink-0 items-center gap-2">
58 <Skeleton shape="pill" className="h-3 w-5" />71 <Skeleton shape="pill" className="h-3 w-5" />
59 <Skeleton shape="pill" className="h-3 w-5" />72 <Skeleton shape="pill" className="h-3 w-5" />
...@@ -62,11 +75,16 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str...@@ -62,11 +75,16 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str
62 );75 );
63}76}
6477
65function DiffCodeLineSkeleton({ contentClassName }: { contentClassName: string }) {78function DiffCodeLineSkeleton({ width }: { width: "short" | "medium" | "long" }) {
66 return (79 return (
67 <div className="flex items-center gap-3">80 <div className="flex items-center gap-3">
68 <Skeleton shape="pill" className="h-2.5 w-5 shrink-0" />81 <Skeleton shape="pill" className="h-2.5 w-5 shrink-0" />
69 <Skeleton shape="pill" className={cn("h-2.5", contentClassName)} />82 <Skeleton
83 shape="pill"
84 className={
85 width === "short" ? "h-2.5 w-3/5" : width === "medium" ? "h-2.5 w-2/3" : "h-2.5 w-4/5"
86 }
87 />
70 </div>88 </div>
71 );89 );
72}90}
...@@ -79,19 +97,19 @@ export function DiffPanelLoadingState(props: { label: string }) {...@@ -79,19 +97,19 @@ export function DiffPanelLoadingState(props: { label: string }) {
79 aria-live="polite"97 aria-live="polite"
80 aria-label={props.label}98 aria-label={props.label}
81 >99 >
82 <DiffFileHeaderSkeleton titleClassName="w-1/2 max-w-64" />100 <DiffFileHeaderSkeleton titleWidth="medium" />
83 <div className="flex h-6 items-center gap-2 px-2 pr-3">101 <div className="flex h-6 items-center gap-2 px-2 pr-3">
84 <div className="h-px flex-1 bg-border/40" />102 <div className="h-px flex-1 bg-border/40" />
85 <Skeleton shape="pill" className="h-2.5 w-24" />103 <Skeleton shape="pill" className="h-2.5 w-24" />
86 <div className="h-px flex-1 bg-border/40" />104 <div className="h-px flex-1 bg-border/40" />
87 </div>105 </div>
88 <div className="space-y-2 px-3 py-2">106 <div className="space-y-2 px-3 py-2">
89 <DiffCodeLineSkeleton contentClassName="w-2/3" />107 <DiffCodeLineSkeleton width="medium" />
90 <DiffCodeLineSkeleton contentClassName="w-4/5" />108 <DiffCodeLineSkeleton width="long" />
91 <DiffCodeLineSkeleton contentClassName="w-3/5" />109 <DiffCodeLineSkeleton width="short" />
92 </div>110 </div>
93 <DiffFileHeaderSkeleton titleClassName="w-2/5 max-w-52" />111 <DiffFileHeaderSkeleton titleWidth="short" />
94 <DiffFileHeaderSkeleton titleClassName="w-3/5 max-w-72" />112 <DiffFileHeaderSkeleton titleWidth="long" />
95 <span className="sr-only">{props.label}</span>113 <span className="sr-only">{props.label}</span>
96 </div>114 </div>
97 );115 );
apps/web/src/components/LegacySidebar.tsx+20-11
...@@ -26,6 +26,7 @@ import { ProjectFavicon } from "./ProjectFavicon";...@@ -26,6 +26,7 @@ import { ProjectFavicon } from "./ProjectFavicon";
26import { useAtomValue } from "@effect/atom-react";26import { useAtomValue } from "@effect/atom-react";
27import { autoAnimate } from "@formkit/auto-animate";27import { autoAnimate } from "@formkit/auto-animate";
28import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react";28import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react";
29import { cn } from "~/lib/utils";
29import { useShallow } from "zustand/react/shallow";30import { useShallow } from "zustand/react/shallow";
30import {31import {
31 DndContext,32 DndContext,
...@@ -184,7 +185,6 @@ import {...@@ -184,7 +185,6 @@ import {
184 isSidebarNestedLinkClick,185 isSidebarNestedLinkClick,
185 isTrailingDoubleClick,186 isTrailingDoubleClick,
186 resolveProjectStatusIndicator,187 resolveProjectStatusIndicator,
187 resolveThreadRowClassName,
188 resolveThreadStatusPill,188 resolveThreadStatusPill,
189 orderItemsByPreferredIds,189 orderItemsByPreferredIds,
190 shouldClearThreadSelectionOnMouseDown,190 shouldClearThreadSelectionOnMouseDown,
...@@ -696,7 +696,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -696,7 +696,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
696 },696 },
697 [attemptArchiveThread, threadRef],697 [attemptArchiveThread, threadRef],
698 );698 );
699 const rowButtonRender = useMemo(() => <div role="button" tabIndex={0} />, []);
700699
701 return (700 return (
702 <SidebarMenuSubItem701 <SidebarMenuSubItem
...@@ -707,15 +706,25 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -707,15 +706,25 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
707 onMouseLeave={handleMouseLeave}706 onMouseLeave={handleMouseLeave}
708 onBlurCapture={handleBlurCapture}707 onBlurCapture={handleBlurCapture}
709 >708 >
710 <SidebarMenuSubButton709 {/* A thread row is the legacy sidebar's own control (a focusable div that hosts nested
711 render={rowButtonRender}710 links and buttons), not a SidebarMenuSubButton, so it owns its look here. */}
712 size="sm"711 <div
713 isActive={isActive}712 role="button"
713 tabIndex={0}
714 data-active={isActive}
715 data-slot="sidebar-menu-sub-button"
716 data-sidebar="menu-sub-button"
717 data-size="sm"
714 data-testid={`thread-row-${thread.id}`}718 data-testid={`thread-row-${thread.id}`}
715 className={`${resolveThreadRowClassName({719 className={cn(
716 isActive,720 "relative isolate flex h-8 w-full min-w-0 cursor-pointer select-none items-center gap-2 overflow-hidden rounded-md px-2 text-left text-xs outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-muted-foreground",
717 isSelected,721 isActive
718 })} relative isolate${isFileDragOver ? " ring-1 ring-inset ring-primary/70" : ""}`}722 ? "bg-sidebar-row-active font-medium text-sidebar-foreground hover:bg-sidebar-row-active"
723 : isSelected
724 ? "bg-sidebar-row-selected text-sidebar-foreground hover:bg-sidebar-row-active"
725 : "text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground",
726 isFileDragOver && "ring-1 ring-inset ring-primary/70",
727 )}
719 onClick={handleRowClick}728 onClick={handleRowClick}
720 onDoubleClick={handleRowDoubleClick}729 onDoubleClick={handleRowDoubleClick}
721 onKeyDown={handleRowKeyDown}730 onKeyDown={handleRowKeyDown}
...@@ -934,7 +943,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -934,7 +943,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
934 </span>943 </span>
935 </div>944 </div>
936 </div>945 </div>
937 </SidebarMenuSubButton>946 </div>
938 </SidebarMenuSubItem>947 </SidebarMenuSubItem>
939 );948 );
940});949});
apps/web/src/components/RightPanelSheet.tsx+1-2
...@@ -1,6 +1,5 @@...@@ -1,6 +1,5 @@
1import { type ReactNode } from "react";1import { type ReactNode } from "react";
22
3import { RIGHT_PANEL_SHEET_CLASS_NAME } from "../rightPanelLayout";
4import { Sheet, SheetPopup } from "./ui/sheet";3import { Sheet, SheetPopup } from "./ui/sheet";
54
6export function RightPanelSheet(props: {5export function RightPanelSheet(props: {
...@@ -23,7 +22,7 @@ export function RightPanelSheet(props: {...@@ -23,7 +22,7 @@ export function RightPanelSheet(props: {
23 side="right"22 side="right"
24 showCloseButton={false}23 showCloseButton={false}
25 keepMounted24 keepMounted
26 className={RIGHT_PANEL_SHEET_CLASS_NAME}25 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))]"
27 >26 >
28 {props.children}27 {props.children}
29 </SheetPopup>28 </SheetPopup>
apps/web/src/components/Sidebar.logic.test.ts-23
...@@ -23,7 +23,6 @@ import {...@@ -23,7 +23,6 @@ import {
23 isTrailingDoubleClick,23 isTrailingDoubleClick,
24 orderItemsByPreferredIds,24 orderItemsByPreferredIds,
25 resolveProjectStatusIndicator,25 resolveProjectStatusIndicator,
26 resolveThreadRowClassName,
27 resolveSidebarThreadStatus,26 resolveSidebarThreadStatus,
28 resolveThreadStatusPill,27 resolveThreadStatusPill,
29 resolveWorkingStartedAt,28 resolveWorkingStartedAt,
...@@ -2059,28 +2058,6 @@ describe("resolveThreadStatusPill", () => {...@@ -2059,28 +2058,6 @@ describe("resolveThreadStatusPill", () => {
2059 });2058 });
2060});2059});
20612060
2062describe("resolveThreadRowClassName", () => {
2063 it("uses the active sidebar surface when a thread is both selected and active", () => {
2064 const className = resolveThreadRowClassName({ isActive: true, isSelected: true });
2065 expect(className).toContain("bg-sidebar-row-active");
2066 expect(className).toContain("text-sidebar-foreground");
2067 expect(className).not.toContain("bg-primary");
2068 });
2069
2070 it("uses selected hover colors for selected threads", () => {
2071 const className = resolveThreadRowClassName({ isActive: false, isSelected: true });
2072 expect(className).toContain("bg-sidebar-row-selected");
2073 expect(className).toContain("hover:bg-sidebar-row-active");
2074 expect(className).not.toContain("bg-primary");
2075 });
2076
2077 it("uses the active sidebar surface for active-only threads", () => {
2078 const className = resolveThreadRowClassName({ isActive: true, isSelected: false });
2079 expect(className).toContain("bg-sidebar-row-active");
2080 expect(className).toContain("hover:bg-sidebar-row-active");
2081 });
2082});
2083
2084describe("resolveProjectStatusIndicator", () => {2061describe("resolveProjectStatusIndicator", () => {
2085 it("returns null when no threads have a notable status", () => {2062 it("returns null when no threads have a notable status", () => {
2086 expect(resolveProjectStatusIndicator([null, null])).toBeNull();2063 expect(resolveProjectStatusIndicator([null, null])).toBeNull();
apps/web/src/components/Sidebar.logic.ts-34
...@@ -768,40 +768,6 @@ export function isContextMenuPointerDown(input: {...@@ -768,40 +768,6 @@ export function isContextMenuPointerDown(input: {
768 return input.isMac && input.button === 0 && input.ctrlKey;768 return input.isMac && input.button === 0 && input.ctrlKey;
769}769}
770770
771export function resolveThreadRowClassName(input: {
772 isActive: boolean;
773 isSelected: boolean;
774}): string {
775 const baseClassName =
776 "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";
777
778 if (input.isSelected && input.isActive) {
779 return cn(
780 baseClassName,
781 "bg-sidebar-row-active text-sidebar-foreground font-medium hover:bg-sidebar-row-active hover:text-sidebar-foreground",
782 );
783 }
784
785 if (input.isSelected) {
786 return cn(
787 baseClassName,
788 "bg-sidebar-row-selected text-sidebar-foreground hover:bg-sidebar-row-active hover:text-sidebar-foreground",
789 );
790 }
791
792 if (input.isActive) {
793 return cn(
794 baseClassName,
795 "bg-sidebar-row-active text-sidebar-foreground font-medium hover:bg-sidebar-row-active hover:text-sidebar-foreground",
796 );
797 }
798
799 return cn(
800 baseClassName,
801 "text-sidebar-muted-foreground/80 hover:bg-sidebar-row-hover hover:text-sidebar-foreground",
802 );
803}
804
805// ── Sidebar thread status model ─────────────────────────────────────771// ── Sidebar thread status model ─────────────────────────────────────
806// Five visual states, three colors: color is reserved for "act now"772// Five visual states, three colors: color is reserved for "act now"
807// (approval), "in motion" (working), and "broken" (failed). Ready is the773// (approval), "in motion" (working), and "broken" (failed). Ready is the
apps/web/src/components/SidebarStageBackdrop.tsx+4-10
...@@ -23,14 +23,6 @@ export function resolveSidebarStageBackdropVariant(...@@ -23,14 +23,6 @@ export function resolveSidebarStageBackdropVariant(
23 return null;23 return null;
24}24}
2525
26export function resolveSidebarStageFocusRingOffsetClass(
27 variant: SidebarStageBackdropVariant,
28): string {
29 return variant === "nightly"
30 ? "focus-visible:ring-offset-(--stage-night-bottom)"
31 : "focus-visible:ring-offset-(--stage-art-bottom)";
32}
33
34export function resolveEnvironmentIdentificationPillLabel(26export function resolveEnvironmentIdentificationPillLabel(
35 stageLabel: string,27 stageLabel: string,
36): EnvironmentIdentificationPillLabel | null {28): EnvironmentIdentificationPillLabel | null {
...@@ -116,7 +108,8 @@ function NightlySkyArt({ compact = false }: { compact?: boolean }) {...@@ -116,7 +108,8 @@ function NightlySkyArt({ compact = false }: { compact?: boolean }) {
116108
117 return (109 return (
118 <svg110 <svg
119 className="stage-art stage-nightly h-full w-full"111 data-stage-art="nightly"
112 className="h-full w-full"
120 fill="none"113 fill="none"
121 preserveAspectRatio="xMinYMin slice"114 preserveAspectRatio="xMinYMin slice"
122 viewBox={compact ? "96 0 8192 96" : STAGE_BACKDROP_VIEW_BOX}115 viewBox={compact ? "96 0 8192 96" : STAGE_BACKDROP_VIEW_BOX}
...@@ -230,7 +223,8 @@ function DevBlueprintArt({ compact = false }: { compact?: boolean }) {...@@ -230,7 +223,8 @@ function DevBlueprintArt({ compact = false }: { compact?: boolean }) {
230223
231 return (224 return (
232 <svg225 <svg
233 className="stage-art stage-blueprint h-full w-full"226 data-stage-art="blueprint"
227 className="h-full w-full"
234 fill="none"228 fill="none"
235 preserveAspectRatio="xMinYMin slice"229 preserveAspectRatio="xMinYMin slice"
236 viewBox={compact ? "64 0 8192 96" : STAGE_BACKDROP_VIEW_BOX}230 viewBox={compact ? "64 0 8192 96" : STAGE_BACKDROP_VIEW_BOX}
apps/web/src/components/ThreadTerminalDrawer.tsx+6-4
...@@ -175,8 +175,8 @@ function terminalFontOptions(family: string, size: number): { family?: string; s...@@ -175,8 +175,8 @@ function terminalFontOptions(family: string, size: number): { family?: string; s
175175
176export function terminalThemeFromApp(mountElement?: HTMLElement | null): GhosttyTheme {176export function terminalThemeFromApp(mountElement?: HTMLElement | null): GhosttyTheme {
177 const drawerSurface =177 const drawerSurface =
178 mountElement?.closest(".thread-terminal-drawer") ??178 mountElement?.closest("[data-thread-terminal-drawer]") ??
179 document.querySelector(".thread-terminal-drawer") ??179 document.querySelector("[data-thread-terminal-drawer]") ??
180 document.body;180 document.body;
181 const drawerStyles = getComputedStyle(drawerSurface);181 const drawerStyles = getComputedStyle(drawerSurface);
182 const themeStyles = mountElement ? getComputedStyle(mountElement) : drawerStyles;182 const themeStyles = mountElement ? getComputedStyle(mountElement) : drawerStyles;
...@@ -1392,9 +1392,10 @@ export default function ThreadTerminalDrawer({...@@ -1392,9 +1392,10 @@ export default function ThreadTerminalDrawer({
1392 if (normalizedTerminalIds.length === 0) {1392 if (normalizedTerminalIds.length === 0) {
1393 return (1393 return (
1394 <aside1394 <aside
1395 data-thread-terminal-drawer
1395 data-terminal-owner={isPanel ? "right-panel" : "drawer"}1396 data-terminal-owner={isPanel ? "right-panel" : "drawer"}
1396 className={cn(1397 className={cn(
1397 "thread-terminal-drawer relative flex min-w-0 flex-col overflow-hidden bg-background",1398 "relative flex min-w-0 flex-col overflow-hidden bg-background",
1398 isPanel ? "h-full flex-1" : "shrink-0 border-t border-border/80",1399 isPanel ? "h-full flex-1" : "shrink-0 border-t border-border/80",
1399 )}1400 )}
1400 style={isPanel ? undefined : { height: `${drawerHeight}px` }}1401 style={isPanel ? undefined : { height: `${drawerHeight}px` }}
...@@ -1422,9 +1423,10 @@ export default function ThreadTerminalDrawer({...@@ -1422,9 +1423,10 @@ export default function ThreadTerminalDrawer({
14221423
1423 return (1424 return (
1424 <aside1425 <aside
1426 data-thread-terminal-drawer
1425 data-terminal-owner={isPanel ? "right-panel" : "drawer"}1427 data-terminal-owner={isPanel ? "right-panel" : "drawer"}
1426 className={cn(1428 className={cn(
1427 "thread-terminal-drawer relative flex min-w-0 flex-col overflow-hidden bg-background",1429 "relative flex min-w-0 flex-col overflow-hidden bg-background",
1428 isPanel ? "h-full flex-1" : "shrink-0 border-t border-border/80",1430 isPanel ? "h-full flex-1" : "shrink-0 border-t border-border/80",
1429 )}1431 )}
1430 style={isPanel ? undefined : { height: `${drawerHeight}px` }}1432 style={isPanel ? undefined : { height: `${drawerHeight}px` }}
apps/web/src/components/diffs/DiffFileLoadingBoundary.tsx+1-1
...@@ -19,7 +19,7 @@ export function DiffFileLoadingBoundary({ load, count }: { load: () => void; cou...@@ -19,7 +19,7 @@ export function DiffFileLoadingBoundary({ load, count }: { load: () => void; cou
19 <div ref={ref} role="status" aria-label="Loading diff…">19 <div ref={ref} role="status" aria-label="Loading diff…">
20 {Array.from({ length: Math.min(count, 4) }, (_, index) => (20 {Array.from({ length: Math.min(count, 4) }, (_, index) => (
21 <div key={index} aria-hidden className="border-b border-border/40">21 <div key={index} aria-hidden className="border-b border-border/40">
22 <DiffFileHeaderSkeleton titleClassName="w-1/2 max-w-64" />22 <DiffFileHeaderSkeleton titleWidth="medium" />
23 </div>23 </div>
24 ))}24 ))}
25 </div>25 </div>
apps/web/src/components/files/ProjectFilePicker.tsx+1-1
...@@ -130,7 +130,7 @@ function OpenProjectFilePicker(props: ProjectFilePickerProps & { target: ActiveP...@@ -130,7 +130,7 @@ function OpenProjectFilePicker(props: ProjectFilePickerProps & { target: ActiveP
130 setHighlightedItemValue(null);130 setHighlightedItemValue(null);
131 setQuery(value);131 setQuery(value);
132 }}132 }}
133 panelClassName="max-h-[min(34rem,76vh)]"133 panelSize="tall-list"
134 testId="project-file-picker"134 testId="project-file-picker"
135 value={query}135 value={query}
136 >136 >
apps/web/src/components/onboarding/WelcomeWizard.tsx+4-1
...@@ -887,7 +887,10 @@ function AgentInstallTerminal({...@@ -887,7 +887,10 @@ function AgentInstallTerminal({
887 ]);887 ]);
888888
889 return (889 return (
890 <div className="thread-terminal-drawer mt-4 overflow-hidden rounded-lg border border-border/70 bg-background text-foreground">890 <div
891 data-thread-terminal-drawer
892 className="mt-4 overflow-hidden rounded-lg border border-border/70 bg-background text-foreground"
893 >
891 <div className="flex items-center justify-between border-b border-border/60 bg-background/60 px-3 py-1.5">894 <div className="flex items-center justify-between border-b border-border/60 bg-background/60 px-3 py-1.5">
892 <span className="text-[11px] font-medium text-muted-foreground">895 <span className="text-[11px] font-medium text-muted-foreground">
893 {setupState === "writeFailed" ? (896 {setupState === "writeFailed" ? (
apps/web/src/components/preview/PreviewPanelShell.tsx-3
...@@ -7,7 +7,6 @@ import {...@@ -7,7 +7,6 @@ import {
7 useState,7 useState,
8} from "react";8} from "react";
99
10import { isElectron } from "~/env";
11import { useResizableWidth } from "~/hooks/useResizableWidth";10import { useResizableWidth } from "~/hooks/useResizableWidth";
12import { cn } from "~/lib/utils";11import { cn } from "~/lib/utils";
1312
...@@ -64,7 +63,6 @@ export function PreviewPanelShell(props: {...@@ -64,7 +63,6 @@ export function PreviewPanelShell(props: {
64 defaultWidth?: number;63 defaultWidth?: number;
65 children: ReactNode;64 children: ReactNode;
66}) {65}) {
67 const useDragRegion = isElectron && props.mode !== "sheet" && props.mode !== "embedded";
68 const isInline = props.mode === "inline";66 const isInline = props.mode === "inline";
69 const collapsible = isInline && props.open !== undefined;67 const collapsible = isInline && props.open !== undefined;
70 const open = props.open ?? true;68 const open = props.open ?? true;
...@@ -149,7 +147,6 @@ export function PreviewPanelShell(props: {...@@ -149,7 +147,6 @@ export function PreviewPanelShell(props: {
149 className="flex h-full min-h-0 min-w-0 flex-col"147 className="flex h-full min-h-0 min-w-0 flex-col"
150 style={collapsible && !maximized ? { width: `calc(${width}px - 1px)` } : undefined}148 style={collapsible && !maximized ? { width: `calc(${width}px - 1px)` } : undefined}
151 >149 >
152 {useDragRegion ? <div className="electron-drag-region h-0 w-full" aria-hidden /> : null}
153 {props.children}150 {props.children}
154 </div>151 </div>
155 </div>152 </div>
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+5-3
...@@ -1980,9 +1980,11 @@ export function PullRequestDetailPanel({...@@ -1980,9 +1980,11 @@ export function PullRequestDetailPanel({
1980 </Tooltip>1980 </Tooltip>
1981 ) : (primaryAction === "merged" || primaryAction === "closed") &&1981 ) : (primaryAction === "merged" || primaryAction === "closed") &&
1982 statePresentation !== null ? (1982 statePresentation !== null ? (
1983 <Badge size="control" variant="outline" className={statePresentation.toneClassName}>1983 <Badge size="control" variant="outline">
1984 <statePresentation.Icon className="size-3.5" />1984 <span className={cn("flex items-center gap-1", statePresentation.toneClassName)}>
1985 {statePresentation.label}1985 <statePresentation.Icon className="size-3.5" />
1986 {statePresentation.label}
1987 </span>
1986 </Badge>1988 </Badge>
1987 ) : null}1989 ) : null}
1988 <Menu>1990 <Menu>
apps/web/src/components/pullRequest/PullRequestGhosts.tsx+1-2
...@@ -169,10 +169,9 @@ export function PullRequestDetailGhost({...@@ -169,10 +169,9 @@ export function PullRequestDetailGhost({
169 <span className="min-w-0 truncate font-medium">{seed.repository}</span>169 <span className="min-w-0 truncate font-medium">{seed.repository}</span>
170 <InlineButton170 <InlineButton
171 onClick={() => void readLocalApi()?.shell.openExternal(seed.url)}171 onClick={() => void readLocalApi()?.shell.openExternal(seed.url)}
172 className={statePresentation?.toneClassName}
173 aria-label={`Open pull request #${seed.number} on host`}172 aria-label={`Open pull request #${seed.number} on host`}
174 >173 >
175 #{seed.number}174 <span className={statePresentation?.toneClassName}>#{seed.number}</span>
176 <ExternalLinkIcon aria-hidden className="size-2.5" />175 <ExternalLinkIcon aria-hidden className="size-2.5" />
177 </InlineButton>176 </InlineButton>
178 </>177 </>
apps/web/src/components/pullRequest/PullRequestRow.tsx+15-4
...@@ -27,10 +27,11 @@ import {...@@ -27,10 +27,11 @@ import {
27 * narrow row shows one label and a "+N" while a wide one spreads out up to three. The "+N"27 * narrow row shows one label and a "+N" while a wide one spreads out up to three. The "+N"
28 * rides on whichever pill is the last visible one, and is hidden as soon as the next slot shows.28 * rides on whichever pill is the last visible one, and is hidden as soon as the next slot shows.
29 */29 */
30// Each slot's pill appears at a wider row, and its overflow count yields to the next slot.
30const LABEL_SLOTS = [31const LABEL_SLOTS = [
31 { pill: "", overflow: "@xl/pr-row-meta:hidden" },32 { overflow: "@xl/pr-row-meta:hidden" },
32 { pill: "hidden @xl/pr-row-meta:inline-flex", overflow: "@3xl/pr-row-meta:hidden" },33 { overflow: "@3xl/pr-row-meta:hidden" },
33 { pill: "hidden @3xl/pr-row-meta:inline-flex", overflow: "" },34 { overflow: "" },
34] as const;35] as const;
3536
36function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry["labels"] }) {37function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry["labels"] }) {
...@@ -42,7 +43,17 @@ function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry[...@@ -42,7 +43,17 @@ function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry[
42 if (!label) return null;43 if (!label) return null;
43 const remaining = labels.length - index - 1;44 const remaining = labels.length - index - 1;
44 return (45 return (
45 <PullRequestLabelChip key={label.name} label={label} className={slot.pill}>46 <PullRequestLabelChip
47 key={label.name}
48 label={label}
49 className={
50 index === 0
51 ? ""
52 : index === 1
53 ? "hidden @xl/pr-row-meta:inline-flex"
54 : "hidden @3xl/pr-row-meta:inline-flex"
55 }
56 >
46 {remaining > 0 ? (57 {remaining > 0 ? (
47 <span className={cn("shrink-0", slot.overflow)}>+{remaining}</span>58 <span className={cn("shrink-0", slot.overflow)}>+{remaining}</span>
48 ) : null}59 ) : null}
apps/web/src/components/search/ProjectContentSearchDialog.tsx+3-3
...@@ -88,11 +88,11 @@ function EmptyContentSearchDialog() {...@@ -88,11 +88,11 @@ function EmptyContentSearchDialog() {
88 footerActionLabel="Open file"88 footerActionLabel="Open file"
89 inputProps={{ disabled: true, placeholder: "Search project contents…" }}89 inputProps={{ disabled: true, placeholder: "Search project contents…" }}
90 mode="none"90 mode="none"
91 panelClassName="flex min-h-0 flex-1 items-center justify-center"91 panelSize="fill"
92 testId="project-content-search"92 testId="project-content-search"
93 value=""93 value=""
94 >94 >
95 <p className="px-6 text-center text-muted-foreground text-sm">95 <p className="m-auto px-6 text-center text-muted-foreground text-sm">
96 Open a project to search its files.96 Open a project to search its files.
97 </p>97 </p>
98 </CommandPaletteContent>98 </CommandPaletteContent>
...@@ -219,7 +219,7 @@ function OpenContentSearchDialog(props: {...@@ -219,7 +219,7 @@ function OpenContentSearchDialog(props: {
219 }}219 }}
220 mode="none"220 mode="none"
221 onValueChange={setQuery}221 onValueChange={setQuery}
222 panelClassName="flex min-h-0 flex-1 flex-col"222 panelSize="fill"
223 testId="project-content-search"223 testId="project-content-search"
224 value={query}224 value={query}
225 >225 >
apps/web/src/components/settings/ProjectFaviconPickerDialog.tsx+1-1
...@@ -115,7 +115,7 @@ export function ProjectFaviconPickerDialog(props: {...@@ -115,7 +115,7 @@ export function ProjectFaviconPickerDialog(props: {
115 setHighlightedItemValue(null);115 setHighlightedItemValue(null);
116 setQuery(value);116 setQuery(value);
117 }}117 }}
118 panelClassName="max-h-[min(34rem,76vh)]"118 panelSize="tall-list"
119 testId="project-favicon-picker"119 testId="project-favicon-picker"
120 value={query}120 value={query}
121 >121 >
apps/web/src/components/sidebar/SidebarChrome.tsx+1-5
...@@ -10,7 +10,6 @@ import { T3Wordmark } from "../T3Wordmark";...@@ -10,7 +10,6 @@ import { T3Wordmark } from "../T3Wordmark";
10import {10import {
11 resolveEnvironmentIdentificationPillLabel,11 resolveEnvironmentIdentificationPillLabel,
12 resolveSidebarStageBackdropVariant,12 resolveSidebarStageBackdropVariant,
13 resolveSidebarStageFocusRingOffsetClass,
14 SidebarStageBackdrop,13 SidebarStageBackdrop,
15 useEnvironmentStageLabel,14 useEnvironmentStageLabel,
16} from "../SidebarStageBackdrop";15} from "../SidebarStageBackdrop";
...@@ -58,10 +57,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({...@@ -58,10 +57,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({
58 <SidebarTrigger57 <SidebarTrigger
59 // Over the stage artwork: the media viewer's control-on-imagery treatment.58 // Over the stage artwork: the media viewer's control-on-imagery treatment.
60 variant={backdropVariant ? "media-navigation" : "ghost"}59 variant={backdropVariant ? "media-navigation" : "ghost"}
61 className={cn(60 className="relative top-auto z-10 translate-y-0 md:hidden"
62 "relative top-auto z-10 translate-y-0 md:hidden",
63 backdropVariant && resolveSidebarStageFocusRingOffsetClass(backdropVariant),
64 )}
65 />61 />
66 <SidebarBrand onBackdrop={backdropVariant !== null} />62 <SidebarBrand onBackdrop={backdropVariant !== null} />
67 {pillLabel ? (63 {pillLabel ? (
apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx+6-5
...@@ -14,13 +14,13 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";...@@ -14,13 +14,13 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
1414
15const PROVIDER_UPDATE_PILL_STYLES = {15const PROVIDER_UPDATE_PILL_STYLES = {
16 loading:16 loading:
17 "bg-sidebar-control-surface text-sidebar-foreground group-has-[button.provider-update-main:hover]/provider-update:bg-sidebar-row-hover",17 "bg-sidebar-control-surface text-sidebar-foreground group-has-[button[data-provider-update-main]:hover]/provider-update:bg-sidebar-row-hover",
18 success:18 success:
19 "bg-sidebar-control-surface text-sidebar-foreground group-has-[button.provider-update-main:hover]/provider-update:bg-sidebar-row-hover",19 "bg-sidebar-control-surface text-sidebar-foreground group-has-[button[data-provider-update-main]:hover]/provider-update:bg-sidebar-row-hover",
20 warning:20 warning:
21 "bg-warning/12 text-warning group-has-[button.provider-update-main:hover]/provider-update:bg-warning/18",21 "bg-warning/12 text-warning group-has-[button[data-provider-update-main]:hover]/provider-update:bg-warning/18",
22 error:22 error:
23 "bg-destructive/12 text-destructive group-has-[button.provider-update-main:hover]/provider-update:bg-destructive/18",23 "bg-destructive/12 text-destructive group-has-[button[data-provider-update-main]:hover]/provider-update:bg-destructive/18",
24} as const;24} as const;
2525
26const PROVIDER_UPDATE_PILL_PROGRESS_STYLES = {26const PROVIDER_UPDATE_PILL_PROGRESS_STYLES = {
...@@ -169,7 +169,8 @@ export function SidebarProviderUpdatePill() {...@@ -169,7 +169,8 @@ export function SidebarProviderUpdatePill() {
169 <button169 <button
170 type="button"170 type="button"
171 aria-label={displayedView.description}171 aria-label={displayedView.description}
172 className="provider-update-main relative z-[1] flex min-w-0 flex-1 items-center gap-2 px-2 py-1.5 text-left"172 data-provider-update-main
173 className="relative z-[1] flex min-w-0 flex-1 items-center gap-2 px-2 py-1.5 text-left"
173 onClick={openProviderSettings}174 onClick={openProviderSettings}
174 >175 >
175 {displayedView.tone === "loading" ? (176 {displayedView.tone === "loading" ? (
apps/web/src/components/ui/alert-dialog.tsx+1-1
...@@ -101,7 +101,7 @@ function AlertDialogFooter({...@@ -101,7 +101,7 @@ function AlertDialogFooter({
101function AlertDialogTitle({ className, ...props }: AlertDialogPrimitive.Title.Props) {101function AlertDialogTitle({ className, ...props }: AlertDialogPrimitive.Title.Props) {
102 return (102 return (
103 <AlertDialogPrimitive.Title103 <AlertDialogPrimitive.Title
104 className={cn("wrap-anywhere font-heading font-semibold text-xl leading-none", className)}104 className={cn("wrap-anywhere font-semibold text-xl leading-none", className)}
105 data-slot="alert-dialog-title"105 data-slot="alert-dialog-title"
106 {...props}106 {...props}
107 />107 />
apps/web/src/components/ui/button.tsx+1-1
...@@ -57,7 +57,7 @@ const buttonVariants = cva(...@@ -57,7 +57,7 @@ const buttonVariants = cva(
57 "media-close":57 "media-close":
58 "[--control-icon-color:currentColor] border-transparent bg-black/65 text-white shadow-sm ring-1 ring-white/20 [:hover,[data-pressed]]:bg-black/80 focus-visible:ring-white",58 "[--control-icon-color:currentColor] border-transparent bg-black/65 text-white shadow-sm ring-1 ring-white/20 [:hover,[data-pressed]]:bg-black/80 focus-visible:ring-white",
59 "media-navigation":59 "media-navigation":
60 "[--control-icon-color:currentColor] absolute top-1/2 z-20 -translate-y-1/2 border-transparent text-white/90 [:hover,[data-pressed]]:bg-white/10 [:hover,[data-pressed]]:text-white focus-visible:ring-white",60 "[--control-icon-color:currentColor] absolute top-1/2 z-20 -translate-y-1/2 border-transparent text-white/90 [:hover,[data-pressed]]:bg-white/10 [:hover,[data-pressed]]:text-white focus-visible:ring-white focus-visible:ring-offset-transparent",
61 outline:61 outline:
62 "[--control-icon-color:var(--contrast-muted-foreground)] border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64",62 "[--control-icon-color:var(--contrast-muted-foreground)] border-input bg-popover not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64",
63 overlay: "border-transparent bg-black/70 text-white/65 [:hover,[data-pressed]]:bg-black/90",63 overlay: "border-transparent bg-black/70 text-white/65 [:hover,[data-pressed]]:bg-black/90",
apps/web/src/components/ui/dialog.tsx+1-1
...@@ -143,7 +143,7 @@ function DialogFooter({...@@ -143,7 +143,7 @@ function DialogFooter({
143function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {143function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
144 return (144 return (
145 <DialogPrimitive.Title145 <DialogPrimitive.Title
146 className={cn("wrap-anywhere font-heading font-semibold text-xl leading-none", className)}146 className={cn("wrap-anywhere font-semibold text-xl leading-none", className)}
147 data-slot="dialog-title"147 data-slot="dialog-title"
148 {...props}148 {...props}
149 />149 />
apps/web/src/components/ui/empty.tsx+1-5
...@@ -91,11 +91,7 @@ function EmptyMedia({...@@ -91,11 +91,7 @@ function EmptyMedia({
9191
92function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {92function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
93 return (93 return (
94 <div94 <div className={cn("font-semibold text-xl", className)} data-slot="empty-title" {...props} />
95 className={cn("font-heading font-semibold text-xl", className)}
96 data-slot="empty-title"
97 {...props}
98 />
99 );95 );
100}96}
10197
apps/web/src/components/ui/scroll-area.tsx+1-1
...@@ -53,7 +53,7 @@ function ScrollArea({...@@ -53,7 +53,7 @@ function ScrollArea({
53 >53 >
54 <ScrollAreaPrimitive.Viewport54 <ScrollAreaPrimitive.Viewport
55 className={cn(55 className={cn(
56 "h-full max-h-[inherit] overflow-auto overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",56 "h-full max-h-[inherit] overflow-auto overscroll-contain rounded-[inherit] outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
57 chainVerticalScroll && "overscroll-y-auto",57 chainVerticalScroll && "overscroll-y-auto",
58 scrollFade &&58 scrollFade &&
59 "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",59 "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
apps/web/src/components/ui/sheet.tsx+1-1
...@@ -138,7 +138,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {...@@ -138,7 +138,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
138function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {138function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
139 return (139 return (
140 <SheetPrimitive.Title140 <SheetPrimitive.Title
141 className={cn("font-heading font-semibold text-xl leading-none", className)}141 className={cn("font-semibold text-xl leading-none", className)}
142 data-slot="sheet-title"142 data-slot="sheet-title"
143 {...props}143 {...props}
144 />144 />
apps/web/src/index.css+2
...@@ -192,6 +192,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil...@@ -192,6 +192,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
192 --color-popover: var(--popover);192 --color-popover: var(--popover);
193 --color-card-foreground: var(--contrast-card-foreground);193 --color-card-foreground: var(--contrast-card-foreground);
194 --color-card: var(--card);194 --color-card: var(--card);
195 --color-code: var(--code-background);
196 --color-code-foreground: var(--code-foreground);
195 --color-foreground: var(--contrast-foreground);197 --color-foreground: var(--contrast-foreground);
196 --color-background: var(--background);198 --color-background: var(--background);
197 --color-surface-raised: var(--surface-raised);199 --color-surface-raised: var(--surface-raised);
apps/web/src/rightPanelLayout.ts-2
...@@ -1,3 +1 @@...@@ -1,3 +1 @@
1export const RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 980px)";1export const RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 980px)";
2export const RIGHT_PANEL_SHEET_CLASS_NAME =
3 "w-[min(42vw,28rem)] min-w-80 max-w-[28rem] p-0 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))]";
vite.config.ts+8
...@@ -197,6 +197,12 @@ export default defineConfig({...@@ -197,6 +197,12 @@ export default defineConfig({
197 files: ["apps/mobile/src/**"],197 files: ["apps/mobile/src/**"],
198 rules: { "t3code/no-mobile-uniwind-theme-escape-hatches": "error" },198 rules: { "t3code/no-mobile-uniwind-theme-escape-hatches": "error" },
199 },199 },
200 {
201 // Every class in web code must be one Tailwind generates: a typo or a class nothing
202 // declares ships silently unstyled. JS hooks use data attributes, not class names.
203 files: ["apps/web/src/**"],
204 rules: { "shadcn/no-unknown-classes": "error" },
205 },
200 {206 {
201 // components/ui exports own their look. App code picks a variant or size instead207 // components/ui exports own their look. App code picks a variant or size instead
202 // of restyling with className; layout classes (width, flex, margin, position) stay208 // of restyling with className; layout classes (width, flex, margin, position) stay
...@@ -205,6 +211,8 @@ export default defineConfig({...@@ -205,6 +211,8 @@ export default defineConfig({
205 files: ["apps/web/src/**"],211 files: ["apps/web/src/**"],
206 excludeFiles: ["apps/web/src/components/ui/**"],212 excludeFiles: ["apps/web/src/components/ui/**"],
207 rules: {213 rules: {
214 // A className built at runtime on a ui component is one no-restyle cannot read.
215 "shadcn/require-static-classes": "error",
208 "shadcn/no-restyle": [216 "shadcn/no-restyle": [
209 "error",217 "error",
210 {218 {