| author | |
| committer | |
| log | 80fc23ac32cd8fe5011a94420cff1f469367f1fc |
| tree | 0c057e6e61124858bd78320b800488e01e77db76 |
| parent | cb1a3f34670b06148d88806a9534772879c30ecd |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | 35 | import ThreadSidebar from "./Sidebar"; |
| 36 | 36 | import { SettingsSidebarNav } from "./settings/SettingsSidebarNav"; |
| 37 | 37 | import { SidebarChromeHeader } from "./sidebar/SidebarChrome"; |
| 38 | import { | |
| 39 | resolveSidebarStageFocusRingOffsetClass, | |
| 40 | useSidebarStageBackdropVariant, | |
| 41 | } from "./SidebarStageBackdrop"; | |
| 38 | import { useSidebarStageBackdropVariant } from "./SidebarStageBackdrop"; | |
| 42 | 39 | import { useProjects } from "../state/entities"; |
| 43 | 40 | import { |
| 44 | 41 | resolveInitialThreadSidebarWidth, |
| ... | ... | @@ -138,9 +135,6 @@ function SidebarControl() { |
| 138 | 135 | className={cn( |
| 139 | 136 | "pointer-events-auto", |
| 140 | 137 | isSidebarVisible && stageBackdropVariant && "relative top-auto translate-y-0", |
| 141 | isSidebarVisible && | |
| 142 | stageBackdropVariant && | |
| 143 | resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant), | |
| 144 | 138 | )} |
| 145 | 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 | 639 | ); |
| 640 | 640 | |
| 641 | 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 | 643 | expect(html).toContain('data-artifact-kind="document"'); |
| 644 | 644 | expect(html).toContain('data-markdown-copy="Hello World (Document template)\n\n"'); |
| 645 | 645 | expect(html).toContain('data-skill-name="artifact-template-hello-world"'); |
| ... | ... | @@ -654,7 +654,7 @@ describe("ChatMarkdown artifact-template cards", () => { |
| 654 | 654 | <ChatMarkdown cwd="/tmp/project" text={ARTIFACT_TEMPLATE_DIRECTIVE} />, |
| 655 | 655 | ); |
| 656 | 656 | |
| 657 | expect(html).toContain("chat-markdown-artifact-template"); | |
| 657 | expect(html).toContain("data-chat-markdown-artifact-template"); | |
| 658 | 658 | expect(html).not.toContain("Use template"); |
| 659 | 659 | }); |
| 660 | 660 | |
| ... | ... | @@ -666,7 +666,7 @@ describe("ChatMarkdown artifact-template cards", () => { |
| 666 | 666 | for (const text of [malformed, unfinished]) { |
| 667 | 667 | const html = renderToStaticMarkup(<ChatMarkdown cwd="/tmp/project" text={text} />); |
| 668 | 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 | }); |
| 672 | 672 | |
| ... | ... | @@ -678,7 +678,7 @@ describe("ChatMarkdown artifact-template cards", () => { |
| 678 | 678 | const html = renderToStaticMarkup(<ChatMarkdown cwd="/tmp/project" text={text} />); |
| 679 | 679 | |
| 680 | 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 | }); |
| 684 | 684 | |
| ... | ... | @@ -691,7 +691,7 @@ describe("ChatMarkdown artifact-template cards", () => { |
| 691 | 691 | ); |
| 692 | 692 | |
| 693 | 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 | }); |
| 697 | 697 |
apps/web/src/components/ChatMarkdown.tsx+2-1| ... | ... | @@ -299,7 +299,8 @@ function CodexArtifactTemplateCard(props: { |
| 299 | 299 | <div |
| 300 | 300 | role="group" |
| 301 | 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 | 304 | data-artifact-kind={props.template.artifactKind} |
| 304 | 305 | data-markdown-copy={`${props.template.displayName} (${presentationLabel})\n\n`} |
| 305 | 306 | data-skill-name={props.template.skillName} |
apps/web/src/components/CommandPalette.tsx-1| ... | ... | @@ -2985,7 +2985,6 @@ function OpenCommandPaletteDialog(props: { |
| 2985 | 2985 | setHighlightedItemValue(typeof value === "string" ? value : null); |
| 2986 | 2986 | }} |
| 2987 | 2987 | onValueChange={handleQueryChange} |
| 2988 | panelClassName="max-h-[min(28rem,70vh)]" | |
| 2989 | 2988 | showBackHint={isSubmenu} |
| 2990 | 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 | 11 | readonly footerTrailing?: ReactNode; |
| 12 | 12 | readonly inputAccessory?: ReactNode; |
| 13 | 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 | 19 | readonly showBackHint?: boolean; |
| 16 | 20 | readonly testId?: string; |
| 17 | 21 | }; |
| ... | ... | @@ -28,7 +32,7 @@ export function CommandPaletteContent({ |
| 28 | 32 | footerTrailing, |
| 29 | 33 | inputAccessory, |
| 30 | 34 | inputProps, |
| 31 | panelClassName, | |
| 35 | panelSize = "list", | |
| 32 | 36 | showBackHint, |
| 33 | 37 | testId, |
| 34 | 38 | ...commandProps |
| ... | ... | @@ -49,7 +53,17 @@ export function CommandPaletteContent({ |
| 49 | 53 | <CommandInput {...inputProps} ref={inputRef} /> |
| 50 | 54 | {inputAccessory} |
| 51 | 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 | 67 | <CommandFooter className="max-sm:flex-col max-sm:items-start"> |
| 54 | 68 | <div className="flex items-center gap-3"> |
| 55 | 69 | <KbdGroup> |
apps/web/src/components/DiffPanelShell.tsx+28-10| ... | ... | @@ -46,14 +46,27 @@ export function DiffPanelShell(props: { |
| 46 | 46 | ); |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: string }) { | |
| 49 | export function DiffFileHeaderSkeleton({ | |
| 50 | titleWidth, | |
| 51 | }: { | |
| 52 | titleWidth: "short" | "medium" | "long"; | |
| 53 | }) { | |
| 50 | 54 | return ( |
| 51 | 55 | <div className="flex h-8 items-center gap-2 px-2 pr-3"> |
| 52 | 56 | <div className="flex size-5 shrink-0 items-center justify-center"> |
| 53 | 57 | <Skeleton className="size-2.5" /> |
| 54 | 58 | </div> |
| 55 | 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 | 70 | <div className="ml-auto flex shrink-0 items-center gap-2"> |
| 58 | 71 | <Skeleton shape="pill" className="h-3 w-5" /> |
| 59 | 72 | <Skeleton shape="pill" className="h-3 w-5" /> |
| ... | ... | @@ -62,11 +75,16 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str |
| 62 | 75 | ); |
| 63 | 76 | } |
| 64 | 77 | |
| 65 | function DiffCodeLineSkeleton({ contentClassName }: { contentClassName: string }) { | |
| 78 | function DiffCodeLineSkeleton({ width }: { width: "short" | "medium" | "long" }) { | |
| 66 | 79 | return ( |
| 67 | 80 | <div className="flex items-center gap-3"> |
| 68 | 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 | 88 | </div> |
| 71 | 89 | ); |
| 72 | 90 | } |
| ... | ... | @@ -79,19 +97,19 @@ export function DiffPanelLoadingState(props: { label: string }) { |
| 79 | 97 | aria-live="polite" |
| 80 | 98 | aria-label={props.label} |
| 81 | 99 | > |
| 82 | <DiffFileHeaderSkeleton titleClassName="w-1/2 max-w-64" /> | |
| 100 | <DiffFileHeaderSkeleton titleWidth="medium" /> | |
| 83 | 101 | <div className="flex h-6 items-center gap-2 px-2 pr-3"> |
| 84 | 102 | <div className="h-px flex-1 bg-border/40" /> |
| 85 | 103 | <Skeleton shape="pill" className="h-2.5 w-24" /> |
| 86 | 104 | <div className="h-px flex-1 bg-border/40" /> |
| 87 | 105 | </div> |
| 88 | 106 | <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" /> | |
| 92 | 110 | </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" /> | |
| 95 | 113 | <span className="sr-only">{props.label}</span> |
| 96 | 114 | </div> |
| 97 | 115 | ); |
apps/web/src/components/LegacySidebar.tsx+20-11| ... | ... | @@ -26,6 +26,7 @@ import { ProjectFavicon } from "./ProjectFavicon"; |
| 26 | 26 | import { useAtomValue } from "@effect/atom-react"; |
| 27 | 27 | import { autoAnimate } from "@formkit/auto-animate"; |
| 28 | 28 | import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react"; |
| 29 | import { cn } from "~/lib/utils"; | |
| 29 | 30 | import { useShallow } from "zustand/react/shallow"; |
| 30 | 31 | import { |
| 31 | 32 | DndContext, |
| ... | ... | @@ -184,7 +185,6 @@ import { |
| 184 | 185 | isSidebarNestedLinkClick, |
| 185 | 186 | isTrailingDoubleClick, |
| 186 | 187 | resolveProjectStatusIndicator, |
| 187 | resolveThreadRowClassName, | |
| 188 | 188 | resolveThreadStatusPill, |
| 189 | 189 | orderItemsByPreferredIds, |
| 190 | 190 | shouldClearThreadSelectionOnMouseDown, |
| ... | ... | @@ -696,7 +696,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP |
| 696 | 696 | }, |
| 697 | 697 | [attemptArchiveThread, threadRef], |
| 698 | 698 | ); |
| 699 | const rowButtonRender = useMemo(() => <div role="button" tabIndex={0} />, []); | |
| 700 | 699 | |
| 701 | 700 | return ( |
| 702 | 701 | <SidebarMenuSubItem |
| ... | ... | @@ -707,15 +706,25 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP |
| 707 | 706 | onMouseLeave={handleMouseLeave} |
| 708 | 707 | onBlurCapture={handleBlurCapture} |
| 709 | 708 | > |
| 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" | |
| 714 | 718 | 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 | )} | |
| 719 | 728 | onClick={handleRowClick} |
| 720 | 729 | onDoubleClick={handleRowDoubleClick} |
| 721 | 730 | onKeyDown={handleRowKeyDown} |
| ... | ... | @@ -934,7 +943,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP |
| 934 | 943 | </span> |
| 935 | 944 | </div> |
| 936 | 945 | </div> |
| 937 | </SidebarMenuSubButton> | |
| 946 | </div> | |
| 938 | 947 | </SidebarMenuSubItem> |
| 939 | 948 | ); |
| 940 | 949 | }); |
apps/web/src/components/RightPanelSheet.tsx+1-2| ... | ... | @@ -1,6 +1,5 @@ |
| 1 | 1 | import { type ReactNode } from "react"; |
| 2 | 2 | |
| 3 | import { RIGHT_PANEL_SHEET_CLASS_NAME } from "../rightPanelLayout"; | |
| 4 | 3 | import { Sheet, SheetPopup } from "./ui/sheet"; |
| 5 | 4 | |
| 6 | 5 | export function RightPanelSheet(props: { |
| ... | ... | @@ -23,7 +22,7 @@ export function RightPanelSheet(props: { |
| 23 | 22 | side="right" |
| 24 | 23 | showCloseButton={false} |
| 25 | 24 | 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 | 27 | {props.children} |
| 29 | 28 | </SheetPopup> |
apps/web/src/components/Sidebar.logic.test.ts-23| ... | ... | @@ -23,7 +23,6 @@ import { |
| 23 | 23 | isTrailingDoubleClick, |
| 24 | 24 | orderItemsByPreferredIds, |
| 25 | 25 | resolveProjectStatusIndicator, |
| 26 | resolveThreadRowClassName, | |
| 27 | 26 | resolveSidebarThreadStatus, |
| 28 | 27 | resolveThreadStatusPill, |
| 29 | 28 | resolveWorkingStartedAt, |
| ... | ... | @@ -2059,28 +2058,6 @@ describe("resolveThreadStatusPill", () => { |
| 2059 | 2058 | }); |
| 2060 | 2059 | }); |
| 2061 | 2060 | |
| 2062 | describe("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 | ||
| 2084 | 2061 | describe("resolveProjectStatusIndicator", () => { |
| 2085 | 2062 | it("returns null when no threads have a notable status", () => { |
| 2086 | 2063 | expect(resolveProjectStatusIndicator([null, null])).toBeNull(); |
apps/web/src/components/Sidebar.logic.ts-34| ... | ... | @@ -768,40 +768,6 @@ export function isContextMenuPointerDown(input: { |
| 768 | 768 | return input.isMac && input.button === 0 && input.ctrlKey; |
| 769 | 769 | } |
| 770 | 770 | |
| 771 | export 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 | 771 | // ── Sidebar thread status model ───────────────────────────────────── |
| 806 | 772 | // Five visual states, three colors: color is reserved for "act now" |
| 807 | 773 | // (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 | 23 | return null; |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | export 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 | ||
| 34 | 26 | export function resolveEnvironmentIdentificationPillLabel( |
| 35 | 27 | stageLabel: string, |
| 36 | 28 | ): EnvironmentIdentificationPillLabel | null { |
| ... | ... | @@ -116,7 +108,8 @@ function NightlySkyArt({ compact = false }: { compact?: boolean }) { |
| 116 | 108 | |
| 117 | 109 | return ( |
| 118 | 110 | <svg |
| 119 | className="stage-art stage-nightly h-full w-full" | |
| 111 | data-stage-art="nightly" | |
| 112 | className="h-full w-full" | |
| 120 | 113 | fill="none" |
| 121 | 114 | preserveAspectRatio="xMinYMin slice" |
| 122 | 115 | viewBox={compact ? "96 0 8192 96" : STAGE_BACKDROP_VIEW_BOX} |
| ... | ... | @@ -230,7 +223,8 @@ function DevBlueprintArt({ compact = false }: { compact?: boolean }) { |
| 230 | 223 | |
| 231 | 224 | return ( |
| 232 | 225 | <svg |
| 233 | className="stage-art stage-blueprint h-full w-full" | |
| 226 | data-stage-art="blueprint" | |
| 227 | className="h-full w-full" | |
| 234 | 228 | fill="none" |
| 235 | 229 | preserveAspectRatio="xMinYMin slice" |
| 236 | 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 | 175 | |
| 176 | 176 | export function terminalThemeFromApp(mountElement?: HTMLElement | null): GhosttyTheme { |
| 177 | 177 | 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]") ?? | |
| 180 | 180 | document.body; |
| 181 | 181 | const drawerStyles = getComputedStyle(drawerSurface); |
| 182 | 182 | const themeStyles = mountElement ? getComputedStyle(mountElement) : drawerStyles; |
| ... | ... | @@ -1392,9 +1392,10 @@ export default function ThreadTerminalDrawer({ |
| 1392 | 1392 | if (normalizedTerminalIds.length === 0) { |
| 1393 | 1393 | return ( |
| 1394 | 1394 | <aside |
| 1395 | data-thread-terminal-drawer | |
| 1395 | 1396 | data-terminal-owner={isPanel ? "right-panel" : "drawer"} |
| 1396 | 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 | 1399 | isPanel ? "h-full flex-1" : "shrink-0 border-t border-border/80", |
| 1399 | 1400 | )} |
| 1400 | 1401 | style={isPanel ? undefined : { height: `${drawerHeight}px` }} |
| ... | ... | @@ -1422,9 +1423,10 @@ export default function ThreadTerminalDrawer({ |
| 1422 | 1423 | |
| 1423 | 1424 | return ( |
| 1424 | 1425 | <aside |
| 1426 | data-thread-terminal-drawer | |
| 1425 | 1427 | data-terminal-owner={isPanel ? "right-panel" : "drawer"} |
| 1426 | 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 | 1430 | isPanel ? "h-full flex-1" : "shrink-0 border-t border-border/80", |
| 1429 | 1431 | )} |
| 1430 | 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 | 19 | <div ref={ref} role="status" aria-label="Loading diff…"> |
| 20 | 20 | {Array.from({ length: Math.min(count, 4) }, (_, index) => ( |
| 21 | 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 | 23 | </div> |
| 24 | 24 | ))} |
| 25 | 25 | </div> |
apps/web/src/components/files/ProjectFilePicker.tsx+1-1| ... | ... | @@ -130,7 +130,7 @@ function OpenProjectFilePicker(props: ProjectFilePickerProps & { target: ActiveP |
| 130 | 130 | setHighlightedItemValue(null); |
| 131 | 131 | setQuery(value); |
| 132 | 132 | }} |
| 133 | panelClassName="max-h-[min(34rem,76vh)]" | |
| 133 | panelSize="tall-list" | |
| 134 | 134 | testId="project-file-picker" |
| 135 | 135 | value={query} |
| 136 | 136 | > |
apps/web/src/components/onboarding/WelcomeWizard.tsx+4-1| ... | ... | @@ -887,7 +887,10 @@ function AgentInstallTerminal({ |
| 887 | 887 | ]); |
| 888 | 888 | |
| 889 | 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 | 894 | <div className="flex items-center justify-between border-b border-border/60 bg-background/60 px-3 py-1.5"> |
| 892 | 895 | <span className="text-[11px] font-medium text-muted-foreground"> |
| 893 | 896 | {setupState === "writeFailed" ? ( |
apps/web/src/components/preview/PreviewPanelShell.tsx-3| ... | ... | @@ -7,7 +7,6 @@ import { |
| 7 | 7 | useState, |
| 8 | 8 | } from "react"; |
| 9 | 9 | |
| 10 | import { isElectron } from "~/env"; | |
| 11 | 10 | import { useResizableWidth } from "~/hooks/useResizableWidth"; |
| 12 | 11 | import { cn } from "~/lib/utils"; |
| 13 | 12 | |
| ... | ... | @@ -64,7 +63,6 @@ export function PreviewPanelShell(props: { |
| 64 | 63 | defaultWidth?: number; |
| 65 | 64 | children: ReactNode; |
| 66 | 65 | }) { |
| 67 | const useDragRegion = isElectron && props.mode !== "sheet" && props.mode !== "embedded"; | |
| 68 | 66 | const isInline = props.mode === "inline"; |
| 69 | 67 | const collapsible = isInline && props.open !== undefined; |
| 70 | 68 | const open = props.open ?? true; |
| ... | ... | @@ -149,7 +147,6 @@ export function PreviewPanelShell(props: { |
| 149 | 147 | className="flex h-full min-h-0 min-w-0 flex-col" |
| 150 | 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 | 150 | {props.children} |
| 154 | 151 | </div> |
| 155 | 152 | </div> |
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+5-3| ... | ... | @@ -1980,9 +1980,11 @@ export function PullRequestDetailPanel({ |
| 1980 | 1980 | </Tooltip> |
| 1981 | 1981 | ) : (primaryAction === "merged" || primaryAction === "closed") && |
| 1982 | 1982 | 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> | |
| 1986 | 1988 | </Badge> |
| 1987 | 1989 | ) : null} |
| 1988 | 1990 | <Menu> |
apps/web/src/components/pullRequest/PullRequestGhosts.tsx+1-2| ... | ... | @@ -169,10 +169,9 @@ export function PullRequestDetailGhost({ |
| 169 | 169 | <span className="min-w-0 truncate font-medium">{seed.repository}</span> |
| 170 | 170 | <InlineButton |
| 171 | 171 | onClick={() => void readLocalApi()?.shell.openExternal(seed.url)} |
| 172 | className={statePresentation?.toneClassName} | |
| 173 | 172 | aria-label={`Open pull request #${seed.number} on host`} |
| 174 | 173 | > |
| 175 | #{seed.number} | |
| 174 | <span className={statePresentation?.toneClassName}>#{seed.number}</span> | |
| 176 | 175 | <ExternalLinkIcon aria-hidden className="size-2.5" /> |
| 177 | 176 | </InlineButton> |
| 178 | 177 | </> |
apps/web/src/components/pullRequest/PullRequestRow.tsx+15-4| ... | ... | @@ -27,10 +27,11 @@ import { |
| 27 | 27 | * narrow row shows one label and a "+N" while a wide one spreads out up to three. The "+N" |
| 28 | 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. | |
| 30 | 31 | const 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: "" }, | |
| 34 | 35 | ] as const; |
| 35 | 36 | |
| 36 | 37 | function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry["labels"] }) { |
| ... | ... | @@ -42,7 +43,17 @@ function PullRequestRowLabels({ labels }: { labels: EnvironmentPullRequestEntry[ |
| 42 | 43 | if (!label) return null; |
| 43 | 44 | const remaining = labels.length - index - 1; |
| 44 | 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 | 57 | {remaining > 0 ? ( |
| 47 | 58 | <span className={cn("shrink-0", slot.overflow)}>+{remaining}</span> |
| 48 | 59 | ) : null} |
apps/web/src/components/search/ProjectContentSearchDialog.tsx+3-3| ... | ... | @@ -88,11 +88,11 @@ function EmptyContentSearchDialog() { |
| 88 | 88 | footerActionLabel="Open file" |
| 89 | 89 | inputProps={{ disabled: true, placeholder: "Search project contents…" }} |
| 90 | 90 | mode="none" |
| 91 | panelClassName="flex min-h-0 flex-1 items-center justify-center" | |
| 91 | panelSize="fill" | |
| 92 | 92 | testId="project-content-search" |
| 93 | 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 | 96 | Open a project to search its files. |
| 97 | 97 | </p> |
| 98 | 98 | </CommandPaletteContent> |
| ... | ... | @@ -219,7 +219,7 @@ function OpenContentSearchDialog(props: { |
| 219 | 219 | }} |
| 220 | 220 | mode="none" |
| 221 | 221 | onValueChange={setQuery} |
| 222 | panelClassName="flex min-h-0 flex-1 flex-col" | |
| 222 | panelSize="fill" | |
| 223 | 223 | testId="project-content-search" |
| 224 | 224 | value={query} |
| 225 | 225 | > |
apps/web/src/components/settings/ProjectFaviconPickerDialog.tsx+1-1| ... | ... | @@ -115,7 +115,7 @@ export function ProjectFaviconPickerDialog(props: { |
| 115 | 115 | setHighlightedItemValue(null); |
| 116 | 116 | setQuery(value); |
| 117 | 117 | }} |
| 118 | panelClassName="max-h-[min(34rem,76vh)]" | |
| 118 | panelSize="tall-list" | |
| 119 | 119 | testId="project-favicon-picker" |
| 120 | 120 | value={query} |
| 121 | 121 | > |
apps/web/src/components/sidebar/SidebarChrome.tsx+1-5| ... | ... | @@ -10,7 +10,6 @@ import { T3Wordmark } from "../T3Wordmark"; |
| 10 | 10 | import { |
| 11 | 11 | resolveEnvironmentIdentificationPillLabel, |
| 12 | 12 | resolveSidebarStageBackdropVariant, |
| 13 | resolveSidebarStageFocusRingOffsetClass, | |
| 14 | 13 | SidebarStageBackdrop, |
| 15 | 14 | useEnvironmentStageLabel, |
| 16 | 15 | } from "../SidebarStageBackdrop"; |
| ... | ... | @@ -58,10 +57,7 @@ export const SidebarChromeHeader = memo(function SidebarChromeHeader({ |
| 58 | 57 | <SidebarTrigger |
| 59 | 58 | // Over the stage artwork: the media viewer's control-on-imagery treatment. |
| 60 | 59 | 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" | |
| 65 | 61 | /> |
| 66 | 62 | <SidebarBrand onBackdrop={backdropVariant !== null} /> |
| 67 | 63 | {pillLabel ? ( |
apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx+6-5| ... | ... | @@ -14,13 +14,13 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 14 | 14 | |
| 15 | 15 | const PROVIDER_UPDATE_PILL_STYLES = { |
| 16 | 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 | 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 | 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 | 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 | 24 | } as const; |
| 25 | 25 | |
| 26 | 26 | const PROVIDER_UPDATE_PILL_PROGRESS_STYLES = { |
| ... | ... | @@ -169,7 +169,8 @@ export function SidebarProviderUpdatePill() { |
| 169 | 169 | <button |
| 170 | 170 | type="button" |
| 171 | 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 | 174 | onClick={openProviderSettings} |
| 174 | 175 | > |
| 175 | 176 | {displayedView.tone === "loading" ? ( |
apps/web/src/components/ui/alert-dialog.tsx+1-1| ... | ... | @@ -101,7 +101,7 @@ function AlertDialogFooter({ |
| 101 | 101 | function AlertDialogTitle({ className, ...props }: AlertDialogPrimitive.Title.Props) { |
| 102 | 102 | return ( |
| 103 | 103 | <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 | 105 | data-slot="alert-dialog-title" |
| 106 | 106 | {...props} |
| 107 | 107 | /> |
apps/web/src/components/ui/button.tsx+1-1| ... | ... | @@ -57,7 +57,7 @@ const buttonVariants = cva( |
| 57 | 57 | "media-close": |
| 58 | 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 | 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 | 61 | outline: |
| 62 | 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 | 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 | 143 | function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { |
| 144 | 144 | return ( |
| 145 | 145 | <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 | 147 | data-slot="dialog-title" |
| 148 | 148 | {...props} |
| 149 | 149 | /> |
apps/web/src/components/ui/empty.tsx+1-5| ... | ... | @@ -91,11 +91,7 @@ function EmptyMedia({ |
| 91 | 91 | |
| 92 | 92 | function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { |
| 93 | 93 | 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} /> | |
| 99 | 95 | ); |
| 100 | 96 | } |
| 101 | 97 |
apps/web/src/components/ui/scroll-area.tsx+1-1| ... | ... | @@ -53,7 +53,7 @@ function ScrollArea({ |
| 53 | 53 | > |
| 54 | 54 | <ScrollAreaPrimitive.Viewport |
| 55 | 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 | 57 | chainVerticalScroll && "overscroll-y-auto", |
| 58 | 58 | scrollFade && |
| 59 | 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 | 138 | function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) { |
| 139 | 139 | return ( |
| 140 | 140 | <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 | 142 | data-slot="sheet-title" |
| 143 | 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 | 192 | --color-popover: var(--popover); |
| 193 | 193 | --color-card-foreground: var(--contrast-card-foreground); |
| 194 | 194 | --color-card: var(--card); |
| 195 | --color-code: var(--code-background); | |
| 196 | --color-code-foreground: var(--code-foreground); | |
| 195 | 197 | --color-foreground: var(--contrast-foreground); |
| 196 | 198 | --color-background: var(--background); |
| 197 | 199 | --color-surface-raised: var(--surface-raised); |
apps/web/src/rightPanelLayout.ts-2| ... | ... | @@ -1,3 +1 @@ |
| 1 | 1 | export const RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY = "(max-width: 980px)"; |
| 2 | export 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 | 197 | files: ["apps/mobile/src/**"], |
| 198 | 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 | 207 | // components/ui exports own their look. App code picks a variant or size instead |
| 202 | 208 | // of restyling with className; layout classes (width, flex, margin, position) stay |
| ... | ... | @@ -205,6 +211,8 @@ export default defineConfig({ |
| 205 | 211 | files: ["apps/web/src/**"], |
| 206 | 212 | excludeFiles: ["apps/web/src/components/ui/**"], |
| 207 | 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 | 216 | "shadcn/no-restyle": [ |
| 209 | 217 | "error", |
| 210 | 218 | { |