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";
3535import ThreadSidebar from "./Sidebar";
3636import { SettingsSidebarNav } from "./settings/SettingsSidebarNav";
3737import { SidebarChromeHeader } from "./sidebar/SidebarChrome";
38import {
39 resolveSidebarStageFocusRingOffsetClass,
40 useSidebarStageBackdropVariant,
41} from "./SidebarStageBackdrop";
38import { useSidebarStageBackdropVariant } from "./SidebarStageBackdrop";
4239import { useProjects } from "../state/entities";
4340import {
4441 resolveInitialThreadSidebarWidth,
......@@ -138,9 +135,6 @@ function SidebarControl() {
138135 className={cn(
139136 "pointer-events-auto",
140137 isSidebarVisible && stageBackdropVariant && "relative top-auto translate-y-0",
141 isSidebarVisible &&
142 stageBackdropVariant &&
143 resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant),
144138 )}
145139 aria-label="Toggle main sidebar"
146140 />
apps/web/src/components/ChatMarkdown.test.tsx+5-5
......@@ -639,7 +639,7 @@ describe("ChatMarkdown artifact-template cards", () => {
639639 );
640640
641641 expect(html).not.toContain("::artifact-template");
642 expect(html).toContain("chat-markdown-artifact-template");
642 expect(html).toContain("data-chat-markdown-artifact-template");
643643 expect(html).toContain('data-artifact-kind="document"');
644644 expect(html).toContain('data-markdown-copy="Hello World (Document template)\n\n"');
645645 expect(html).toContain('data-skill-name="artifact-template-hello-world"');
......@@ -654,7 +654,7 @@ describe("ChatMarkdown artifact-template cards", () => {
654654 <ChatMarkdown cwd="/tmp/project" text={ARTIFACT_TEMPLATE_DIRECTIVE} />,
655655 );
656656
657 expect(html).toContain("chat-markdown-artifact-template");
657 expect(html).toContain("data-chat-markdown-artifact-template");
658658 expect(html).not.toContain("Use template");
659659 });
660660
......@@ -666,7 +666,7 @@ describe("ChatMarkdown artifact-template cards", () => {
666666 for (const text of [malformed, unfinished]) {
667667 const html = renderToStaticMarkup(<ChatMarkdown cwd="/tmp/project" text={text} />);
668668 expect(html).toContain("::artifact-template");
669 expect(html).not.toContain("chat-markdown-artifact-template");
669 expect(html).not.toContain("data-chat-markdown-artifact-template");
670670 }
671671 });
672672
......@@ -678,7 +678,7 @@ describe("ChatMarkdown artifact-template cards", () => {
678678 const html = renderToStaticMarkup(<ChatMarkdown cwd="/tmp/project" text={text} />);
679679
680680 expect(html).toContain("::artifact-template");
681 expect(html).not.toContain("chat-markdown-artifact-template");
681 expect(html).not.toContain("data-chat-markdown-artifact-template");
682682 }
683683 });
684684
......@@ -691,7 +691,7 @@ describe("ChatMarkdown artifact-template cards", () => {
691691 );
692692
693693 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");
695695 });
696696});
697697
apps/web/src/components/ChatMarkdown.tsx+2-1
......@@ -299,7 +299,8 @@ function CodexArtifactTemplateCard(props: {
299299 <div
300300 role="group"
301301 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"
303304 data-artifact-kind={props.template.artifactKind}
304305 data-markdown-copy={`${props.template.displayName} (${presentationLabel})\n\n`}
305306 data-skill-name={props.template.skillName}
apps/web/src/components/CommandPalette.tsx-1
......@@ -2985,7 +2985,6 @@ function OpenCommandPaletteDialog(props: {
29852985 setHighlightedItemValue(typeof value === "string" ? value : null);
29862986 }}
29872987 onValueChange={handleQueryChange}
2988 panelClassName="max-h-[min(28rem,70vh)]"
29892988 showBackHint={isSubmenu}
29902989 value={query}
29912990 >
apps/web/src/components/CommandPaletteContent.tsx+17-3
......@@ -11,7 +11,11 @@ type CommandPaletteContentProps = Omit<ComponentProps<typeof Command>, "children
1111 readonly footerTrailing?: ReactNode;
1212 readonly inputAccessory?: ReactNode;
1313 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";
1519 readonly showBackHint?: boolean;
1620 readonly testId?: string;
1721};
......@@ -28,7 +32,7 @@ export function CommandPaletteContent({
2832 footerTrailing,
2933 inputAccessory,
3034 inputProps,
31 panelClassName,
35 panelSize = "list",
3236 showBackHint,
3337 testId,
3438 ...commandProps
......@@ -49,7 +53,17 @@ export function CommandPaletteContent({
4953 <CommandInput {...inputProps} ref={inputRef} />
5054 {inputAccessory}
5155 </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>
5367 <CommandFooter className="max-sm:flex-col max-sm:items-start">
5468 <div className="flex items-center gap-3">
5569 <KbdGroup>
apps/web/src/components/DiffPanelShell.tsx+28-10
......@@ -46,14 +46,27 @@ export function DiffPanelShell(props: {
4646 );
4747}
4848
49export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: string }) {
49export function DiffFileHeaderSkeleton({
50 titleWidth,
51}: {
52 titleWidth: "short" | "medium" | "long";
53}) {
5054 return (
5155 <div className="flex h-8 items-center gap-2 px-2 pr-3">
5256 <div className="flex size-5 shrink-0 items-center justify-center">
5357 <Skeleton className="size-2.5" />
5458 </div>
5559 <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 />
5770 <div className="ml-auto flex shrink-0 items-center gap-2">
5871 <Skeleton shape="pill" className="h-3 w-5" />
5972 <Skeleton shape="pill" className="h-3 w-5" />
......@@ -62,11 +75,16 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str
6275 );
6376}
6477
65function DiffCodeLineSkeleton({ contentClassName }: { contentClassName: string }) {
78function DiffCodeLineSkeleton({ width }: { width: "short" | "medium" | "long" }) {
6679 return (
6780 <div className="flex items-center gap-3">
6881 <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 />
7088 </div>
7189 );
7290}
......@@ -79,19 +97,19 @@ export function DiffPanelLoadingState(props: { label: string }) {
7997 aria-live="polite"
8098 aria-label={props.label}
8199 >
82 <DiffFileHeaderSkeleton titleClassName="w-1/2 max-w-64" />
100 <DiffFileHeaderSkeleton titleWidth="medium" />
83101 <div className="flex h-6 items-center gap-2 px-2 pr-3">
84102 <div className="h-px flex-1 bg-border/40" />
85103 <Skeleton shape="pill" className="h-2.5 w-24" />
86104 <div className="h-px flex-1 bg-border/40" />
87105 </div>
88106 <div className="space-y-2 px-3 py-2">
89 <DiffCodeLineSkeleton contentClassName="w-2/3" />
90 <DiffCodeLineSkeleton contentClassName="w-4/5" />
91 <DiffCodeLineSkeleton contentClassName="w-3/5" />
107 <DiffCodeLineSkeleton width="medium" />
108 <DiffCodeLineSkeleton width="long" />
109 <DiffCodeLineSkeleton width="short" />
92110 </div>
93 <DiffFileHeaderSkeleton titleClassName="w-2/5 max-w-52" />
94 <DiffFileHeaderSkeleton titleClassName="w-3/5 max-w-72" />
111 <DiffFileHeaderSkeleton titleWidth="short" />
112 <DiffFileHeaderSkeleton titleWidth="long" />
95113 <span className="sr-only">{props.label}</span>
96114 </div>
97115 );
apps/web/src/components/LegacySidebar.tsx+20-11
......@@ -26,6 +26,7 @@ import { ProjectFavicon } from "./ProjectFavicon";
2626import { useAtomValue } from "@effect/atom-react";
2727import { autoAnimate } from "@formkit/auto-animate";
2828import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react";
29import { cn } from "~/lib/utils";
2930import { useShallow } from "zustand/react/shallow";
3031import {
3132 DndContext,
......@@ -184,7 +185,6 @@ import {
184185 isSidebarNestedLinkClick,
185186 isTrailingDoubleClick,
186187 resolveProjectStatusIndicator,
187 resolveThreadRowClassName,
188188 resolveThreadStatusPill,
189189 orderItemsByPreferredIds,
190190 shouldClearThreadSelectionOnMouseDown,
......@@ -696,7 +696,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
696696 },
697697 [attemptArchiveThread, threadRef],
698698 );
699 const rowButtonRender = useMemo(() => <div role="button" tabIndex={0} />, []);
700699
701700 return (
702701 <SidebarMenuSubItem
......@@ -707,15 +706,25 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
707706 onMouseLeave={handleMouseLeave}
708707 onBlurCapture={handleBlurCapture}
709708 >
710 <SidebarMenuSubButton
711 render={rowButtonRender}
712 size="sm"
713 isActive={isActive}
709 {/* A thread row is the legacy sidebar's own control (a focusable div that hosts nested
710 links and buttons), not a SidebarMenuSubButton, so it owns its look here. */}
711 <div
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"
714718 data-testid={`thread-row-${thread.id}`}
715 className={`${resolveThreadRowClassName({
716 isActive,
717 isSelected,
718 })} relative isolate${isFileDragOver ? " ring-1 ring-inset ring-primary/70" : ""}`}
719 className={cn(
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",
721 isActive
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 )}
719728 onClick={handleRowClick}
720729 onDoubleClick={handleRowDoubleClick}
721730 onKeyDown={handleRowKeyDown}
......@@ -934,7 +943,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
934943 </span>
935944 </div>
936945 </div>
937 </SidebarMenuSubButton>
946 </div>
938947 </SidebarMenuSubItem>
939948 );
940949});
apps/web/src/components/RightPanelSheet.tsx+1-2
......@@ -1,6 +1,5 @@
11import { type ReactNode } from "react";
22
3import { RIGHT_PANEL_SHEET_CLASS_NAME } from "../rightPanelLayout";
43import { Sheet, SheetPopup } from "./ui/sheet";
54
65export function RightPanelSheet(props: {
......@@ -23,7 +22,7 @@ export function RightPanelSheet(props: {
2322 side="right"
2423 showCloseButton={false}
2524 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))]"
2726 >
2827 {props.children}
2928 </SheetPopup>
apps/web/src/components/Sidebar.logic.test.ts-23
......@@ -23,7 +23,6 @@ import {
2323 isTrailingDoubleClick,
2424 orderItemsByPreferredIds,
2525 resolveProjectStatusIndicator,
26 resolveThreadRowClassName,
2726 resolveSidebarThreadStatus,
2827 resolveThreadStatusPill,
2928 resolveWorkingStartedAt,
......@@ -2059,28 +2058,6 @@ describe("resolveThreadStatusPill", () => {
20592058 });
20602059});
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
20842061describe("resolveProjectStatusIndicator", () => {
20852062 it("returns null when no threads have a notable status", () => {
20862063 expect(resolveProjectStatusIndicator([null, null])).toBeNull();
apps/web/src/components/Sidebar.logic.ts-34
......@@ -768,40 +768,6 @@ export function isContextMenuPointerDown(input: {
768768 return input.isMac && input.button === 0 && input.ctrlKey;
769769}
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
805771// ── Sidebar thread status model ─────────────────────────────────────
806772// Five visual states, three colors: color is reserved for "act now"
807773// (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(
2323 return null;
2424}
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
3426export function resolveEnvironmentIdentificationPillLabel(
3527 stageLabel: string,
3628): EnvironmentIdentificationPillLabel | null {
......@@ -116,7 +108,8 @@ function NightlySkyArt({ compact = false }: { compact?: boolean }) {
116108
117109 return (
118110 <svg
119 className="stage-art stage-nightly h-full w-full"
111 data-stage-art="nightly"
112 className="h-full w-full"
120113 fill="none"
121114 preserveAspectRatio="xMinYMin slice"
122115 viewBox={compact ? "96 0 8192 96" : STAGE_BACKDROP_VIEW_BOX}
......@@ -230,7 +223,8 @@ function DevBlueprintArt({ compact = false }: { compact?: boolean }) {
230223
231224 return (
232225 <svg
233 className="stage-art stage-blueprint h-full w-full"
226 data-stage-art="blueprint"
227 className="h-full w-full"
234228 fill="none"
235229 preserveAspectRatio="xMinYMin slice"
236230 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
175175
176176export function terminalThemeFromApp(mountElement?: HTMLElement | null): GhosttyTheme {
177177 const drawerSurface =
178 mountElement?.closest(".thread-terminal-drawer") ??
179 document.querySelector(".thread-terminal-drawer") ??
178 mountElement?.closest("[data-thread-terminal-drawer]") ??
179 document.querySelector("[data-thread-terminal-drawer]") ??
180180 document.body;
181181 const drawerStyles = getComputedStyle(drawerSurface);
182182 const themeStyles = mountElement ? getComputedStyle(mountElement) : drawerStyles;
......@@ -1392,9 +1392,10 @@ export default function ThreadTerminalDrawer({
13921392 if (normalizedTerminalIds.length === 0) {
13931393 return (
13941394 <aside
1395 data-thread-terminal-drawer
13951396 data-terminal-owner={isPanel ? "right-panel" : "drawer"}
13961397 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",
13981399 isPanel ? "h-full flex-1" : "shrink-0 border-t border-border/80",
13991400 )}
14001401 style={isPanel ? undefined : { height: `${drawerHeight}px` }}
......@@ -1422,9 +1423,10 @@ export default function ThreadTerminalDrawer({
14221423
14231424 return (
14241425 <aside
1426 data-thread-terminal-drawer
14251427 data-terminal-owner={isPanel ? "right-panel" : "drawer"}
14261428 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",
14281430 isPanel ? "h-full flex-1" : "shrink-0 border-t border-border/80",
14291431 )}
14301432 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
1919 <div ref={ref} role="status" aria-label="Loading diff…">
2020 {Array.from({ length: Math.min(count, 4) }, (_, index) => (
2121 <div key={index} aria-hidden className="border-b border-border/40">
22 <DiffFileHeaderSkeleton titleClassName="w-1/2 max-w-64" />
22 <DiffFileHeaderSkeleton titleWidth="medium" />
2323 </div>
2424 ))}
2525 </div>
apps/web/src/components/files/ProjectFilePicker.tsx+1-1
......@@ -130,7 +130,7 @@ function OpenProjectFilePicker(props: ProjectFilePickerProps & { target: ActiveP
130130 setHighlightedItemValue(null);
131131 setQuery(value);
132132 }}
133 panelClassName="max-h-[min(34rem,76vh)]"
133 panelSize="tall-list"
134134 testId="project-file-picker"
135135 value={query}
136136 >
apps/web/src/components/onboarding/WelcomeWizard.tsx+4-1
......@@ -887,7 +887,10 @@ function AgentInstallTerminal({
887887 ]);
888888
889889 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 >
891894 <div className="flex items-center justify-between border-b border-border/60 bg-background/60 px-3 py-1.5">
892895 <span className="text-[11px] font-medium text-muted-foreground">
893896 {setupState === "writeFailed" ? (
apps/web/src/components/preview/PreviewPanelShell.tsx-3
......@@ -7,7 +7,6 @@ import {
77 useState,
88} from "react";
99
10import { isElectron } from "~/env";
1110import { useResizableWidth } from "~/hooks/useResizableWidth";
1211import { cn } from "~/lib/utils";
1312
......@@ -64,7 +63,6 @@ export function PreviewPanelShell(props: {
6463 defaultWidth?: number;
6564 children: ReactNode;
6665}) {
67 const useDragRegion = isElectron && props.mode !== "sheet" && props.mode !== "embedded";
6866 const isInline = props.mode === "inline";
6967 const collapsible = isInline && props.open !== undefined;
7068 const open = props.open ?? true;
......@@ -149,7 +147,6 @@ export function PreviewPanelShell(props: {
149147 className="flex h-full min-h-0 min-w-0 flex-col"
150148 style={collapsible && !maximized ? { width: `calc(${width}px - 1px)` } : undefined}
151149 >
152 {useDragRegion ? <div className="electron-drag-region h-0 w-full" aria-hidden /> : null}
153150 {props.children}
154151 </div>
155152 </div>
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+5-3
......@@ -1980,9 +1980,11 @@ export function PullRequestDetailPanel({
19801980 </Tooltip>
19811981 ) : (primaryAction === "merged" || primaryAction === "closed") &&
19821982 statePresentation !== null ? (
1983 <Badge size="control" variant="outline" className={statePresentation.toneClassName}>
1984 <statePresentation.Icon className="size-3.5" />
1985 {statePresentation.label}
1983 <Badge size="control" variant="outline">
1984 <span className={cn("flex items-center gap-1", statePresentation.toneClassName)}>
1985 <statePresentation.Icon className="size-3.5" />
1986 {statePresentation.label}
1987 </span>
19861988 </Badge>
19871989 ) : null}
19881990 <Menu>
apps/web/src/components/pullRequest/PullRequestGhosts.tsx+1-2
......@@ -169,10 +169,9 @@ export function PullRequestDetailGhost({
169169 <span className="min-w-0 truncate font-medium">{seed.repository}</span>
170170 <InlineButton
171171 onClick={() => void readLocalApi()?.shell.openExternal(seed.url)}
172 className={statePresentation?.toneClassName}
173172 aria-label={`Open pull request #${seed.number} on host`}
174173 >
175 #{seed.number}
174 <span className={statePresentation?.toneClassName}>#{seed.number}</span>
176175 <ExternalLinkIcon aria-hidden className="size-2.5" />
177176 </InlineButton>
178177 </>
apps/web/src/components/pullRequest/PullRequestRow.tsx+15-4
......@@ -27,10 +27,11 @@ import {
2727 * narrow row shows one label and a "+N" while a wide one spreads out up to three. The "+N"
2828 * rides on whichever pill is the last visible one, and is hidden as soon as the next slot shows.
2929 */
30// Each slot's pill appears at a wider row, and its overflow count yields to the next slot.
3031const LABEL_SLOTS = [
31 { pill: "", overflow: "@xl/pr-row-meta:hidden" },
32 { pill: "hidden @xl/pr-row-meta:inline-flex", overflow: "@3xl/pr-row-meta:hidden" },
33 { pill: "hidden @3xl/pr-row-meta:inline-flex", overflow: "" },
32 { overflow: "@xl/pr-row-meta:hidden" },
33 { overflow: "@3xl/pr-row-meta:hidden" },
34 { overflow: "" },
3435] as const;
3536
3637function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry["labels"] }) {
......@@ -42,7 +43,17 @@ function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry[
4243 if (!label) return null;
4344 const remaining = labels.length - index - 1;
4445 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 >
4657 {remaining > 0 ? (
4758 <span className={cn("shrink-0", slot.overflow)}>+{remaining}</span>
4859 ) : null}
apps/web/src/components/search/ProjectContentSearchDialog.tsx+3-3
......@@ -88,11 +88,11 @@ function EmptyContentSearchDialog() {
8888 footerActionLabel="Open file"
8989 inputProps={{ disabled: true, placeholder: "Search project contents…" }}
9090 mode="none"
91 panelClassName="flex min-h-0 flex-1 items-center justify-center"
91 panelSize="fill"
9292 testId="project-content-search"
9393 value=""
9494 >
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">
9696 Open a project to search its files.
9797 </p>
9898 </CommandPaletteContent>
......@@ -219,7 +219,7 @@ function OpenContentSearchDialog(props: {
219219 }}
220220 mode="none"
221221 onValueChange={setQuery}
222 panelClassName="flex min-h-0 flex-1 flex-col"
222 panelSize="fill"
223223 testId="project-content-search"
224224 value={query}
225225 >
apps/web/src/components/settings/ProjectFaviconPickerDialog.tsx+1-1
......@@ -115,7 +115,7 @@ export function ProjectFaviconPickerDialog(props: {
115115 setHighlightedItemValue(null);
116116 setQuery(value);
117117 }}
118 panelClassName="max-h-[min(34rem,76vh)]"
118 panelSize="tall-list"
119119 testId="project-favicon-picker"
120120 value={query}
121121 >
apps/web/src/components/sidebar/SidebarChrome.tsx+1-5
......@@ -10,7 +10,6 @@ import { T3Wordmark } from "../T3Wordmark";
1010import {
1111 resolveEnvironmentIdentificationPillLabel,
1212 resolveSidebarStageBackdropVariant,
13 resolveSidebarStageFocusRingOffsetClass,
1413 SidebarStageBackdrop,
1514 useEnvironmentStageLabel,
1615} from "../SidebarStageBackdrop";
......@@ -58,10 +57,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({
5857 <SidebarTrigger
5958 // Over the stage artwork: the media viewer's control-on-imagery treatment.
6059 variant={backdropVariant ? "media-navigation" : "ghost"}
61 className={cn(
62 "relative top-auto z-10 translate-y-0 md:hidden",
63 backdropVariant && resolveSidebarStageFocusRingOffsetClass(backdropVariant),
64 )}
60 className="relative top-auto z-10 translate-y-0 md:hidden"
6561 />
6662 <SidebarBrand onBackdrop={backdropVariant !== null} />
6763 {pillLabel ? (
apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx+6-5
......@@ -14,13 +14,13 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
1414
1515const PROVIDER_UPDATE_PILL_STYLES = {
1616 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",
1818 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",
2020 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",
2222 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",
2424} as const;
2525
2626const PROVIDER_UPDATE_PILL_PROGRESS_STYLES = {
......@@ -169,7 +169,8 @@ export function SidebarProviderUpdatePill() {
169169 <button
170170 type="button"
171171 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"
173174 onClick={openProviderSettings}
174175 >
175176 {displayedView.tone === "loading" ? (
apps/web/src/components/ui/alert-dialog.tsx+1-1
......@@ -101,7 +101,7 @@ function AlertDialogFooter({
101101function AlertDialogTitle({ className, ...props }: AlertDialogPrimitive.Title.Props) {
102102 return (
103103 <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)}
105105 data-slot="alert-dialog-title"
106106 {...props}
107107 />
apps/web/src/components/ui/button.tsx+1-1
......@@ -57,7 +57,7 @@ const buttonVariants = cva(
5757 "media-close":
5858 "[--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",
5959 "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",
6161 outline:
6262 "[--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",
6363 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({
143143function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
144144 return (
145145 <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)}
147147 data-slot="dialog-title"
148148 {...props}
149149 />
apps/web/src/components/ui/empty.tsx+1-5
......@@ -91,11 +91,7 @@ function EmptyMedia({
9191
9292function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
9393 return (
94 <div
95 className={cn("font-heading font-semibold text-xl", className)}
96 data-slot="empty-title"
97 {...props}
98 />
94 <div className={cn("font-semibold text-xl", className)} data-slot="empty-title" {...props} />
9995 );
10096}
10197
apps/web/src/components/ui/scroll-area.tsx+1-1
......@@ -53,7 +53,7 @@ function ScrollArea({
5353 >
5454 <ScrollAreaPrimitive.Viewport
5555 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",
5757 chainVerticalScroll && "overscroll-y-auto",
5858 scrollFade &&
5959 "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">) {
138138function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
139139 return (
140140 <SheetPrimitive.Title
141 className={cn("font-heading font-semibold text-xl leading-none", className)}
141 className={cn("font-semibold text-xl leading-none", className)}
142142 data-slot="sheet-title"
143143 {...props}
144144 />
apps/web/src/index.css+2
......@@ -192,6 +192,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
192192 --color-popover: var(--popover);
193193 --color-card-foreground: var(--contrast-card-foreground);
194194 --color-card: var(--card);
195 --color-code: var(--code-background);
196 --color-code-foreground: var(--code-foreground);
195197 --color-foreground: var(--contrast-foreground);
196198 --color-background: var(--background);
197199 --color-surface-raised: var(--surface-raised);
apps/web/src/rightPanelLayout.ts-2
......@@ -1,3 +1 @@
11export 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({
197197 files: ["apps/mobile/src/**"],
198198 rules: { "t3code/no-mobile-uniwind-theme-escape-hatches": "error" },
199199 },
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 },
200206 {
201207 // components/ui exports own their look. App code picks a variant or size instead
202208 // of restyling with className; layout classes (width, flex, margin, position) stay
......@@ -205,6 +211,8 @@ export default defineConfig({
205211 files: ["apps/web/src/**"],
206212 excludeFiles: ["apps/web/src/components/ui/**"],
207213 rules: {
214 // A className built at runtime on a ui component is one no-restyle cannot read.
215 "shadcn/require-static-classes": "error",
208216 "shadcn/no-restyle": [
209217 "error",
210218 {