| author | |
| committer | |
| log | 9885a845c97325b1099b095011da8385485616f5 |
| tree | 31e6c8fca18195f58e1584a223323bca4ed2c734 |
| parent | 196c8ea0d642acd1db66bd57f5d98abe81d8da6e |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>69 files changed, 851 insertions(+), 1123 deletions(-)
.macroscope/check-run-agents/ui-consistency.md created+82| ... | @@ -0,0 +1,82 @@ | ||
| 1 | --- | ||
| 2 | title: UI Consistency | ||
| 3 | model: claude-opus-5 | ||
| 4 | effort: high | ||
| 5 | input: full_diff | ||
| 6 | tools: | ||
| 7 | - browse_code | ||
| 8 | - git_tools | ||
| 9 | - github_api_read_only | ||
| 10 | - modify_pr | ||
| 11 | include: | ||
| 12 | - "apps/web/src/**/*.ts" | ||
| 13 | - "apps/web/src/**/*.tsx" | ||
| 14 | - "apps/web/src/**/*.css" | ||
| 15 | conclusion: failure | ||
| 16 | showToolCalls: true | ||
| 17 | --- | ||
| 18 | |||
| 19 | # UI consistency review | ||
| 20 | |||
| 21 | Review changed web UI code and directly affected call sites for consistency with the shared component system, Tailwind ownership, and the behavioral constraints below. Apply these rules when a pull request creates, moves, or modifies controls or styling. Do not demand unrelated repository-wide cleanup. | ||
| 22 | |||
| 23 | The goal is not to minimize CSS or class counts at any cost. The goal is to put each behavior in the smallest correct owner while preserving interaction, theming, accessibility, layout, and browser behavior. | ||
| 24 | |||
| 25 | ## Shared controls and variants | ||
| 26 | |||
| 27 | - Prefer the core UI primitives in `apps/web/src/components/ui` over native controls or locally reconstructed primitives. In ordinary product UI, a raw `<button>` that recreates `Button`, a raw input that recreates `Input` or `InputGroup`, or a local trigger that recreates `Select`, `Toggle`, or `Menu` is a concrete finding. | ||
| 28 | - Do not flag raw elements that intentionally implement a semantic row, tab, resize handle, swatch, image target, editor surface, or another interaction whose behavior or geometry differs from the core primitive. | ||
| 29 | - When multiple call sites repeat the same durable geometry or treatment, prefer a named primitive size or variant. Examples include compact controls, micro icon actions, muted ghost actions, and glass actions. Keep contextual layout, width, and color at the call site. | ||
| 30 | - Flag large call-site class strings that override a primitive's core height, radius, padding, focus ring, cursor, hit target, or base state colors. Prefer extending the primitive contract when the same pattern is genuinely shared. | ||
| 31 | - Preserve accessibility and interaction semantics during migrations: focus-visible rings, disabled behavior, loading state, keyboard behavior, pointer cursor, `aria-*`, Base UI or Radix render/close props, and coarse-pointer hit targets. | ||
| 32 | - Do not require tests for a tiny visual-only class migration. Require focused tests when primitive composition changes behavior, prop forwarding, state transitions, keyboard handling, or width/defaulting logic. | ||
| 33 | |||
| 34 | ## CSS and Tailwind ownership | ||
| 35 | |||
| 36 | - Ordinary one-owner presentation belongs in the owning TS or TSX module as static Tailwind classes or owner-level CSS variables. Examples include local geometry, spacing, typography, backgrounds, borders, simple vendor pseudo-elements, and component-only positioning. | ||
| 37 | - Keep global CSS when it is genuinely reusable or behaviorally complex: generated markdown or imperative DOM, custom elements and shadow roots, masks, shared or complex pseudo-elements, animations, glass composition, runtime theme variables, safe-area calculations, scrollbar-lane preservation, Electron drag regions, and browser/vendor integration. Simple owner-local pseudo-elements may still belong in the owning module. | ||
| 38 | - Before calling a selector dead, trace literal, dynamic, generated, imperative, test, custom-element, and shadow-root consumers. Search both the emitted class string and any class-valued field names through their final DOM sink. A helper returning a class is not proof that it is rendered, and a missed downstream property read can make a deletion unsafe. | ||
| 39 | - Flag duplicate declarations only after comparing cascade layer, selector specificity, inheritance, runtime theme scope, media/variant scope, and the final owning element. Textually identical declarations are not necessarily behaviorally redundant. | ||
| 40 | - When moving CSS into Tailwind, preserve selector scope and cascade ownership. A utility at the owner is preferable to a fragile global override that depends on stylesheet order. | ||
| 41 | - Do not request moving complex global behavior into arbitrary Tailwind merely to reduce `index.css`. Do not preserve ordinary one-owner CSS merely because it already exists globally. | ||
| 42 | |||
| 43 | ## Themes and generated CSS | ||
| 44 | |||
| 45 | - Use the project variants for theme-only declarations: | ||
| 46 | - dark-only declarations use `@variant dark`; | ||
| 47 | - light-only declarations use `@variant light`; | ||
| 48 | - raw `.dark` should remain only in the `dark` and `light` custom-variant definitions. | ||
| 49 | - Preserve custom themes and runtime token bridges. Removing a variable or selector is safe only when all runtime, inspector, generated, and theme-palette consumers are accounted for. | ||
| 50 | - Inspect emitted production CSS after unusual variants, arbitrary selectors, nested pseudo-elements, or attribute matching. Source syntax that looks valid is insufficient. | ||
| 51 | - Flag malformed or empty emitted selectors such as empty `:is()` or `:not(:is())`, selector branches that can never match their own class attribute, and transformations that silently drop the intended rule. | ||
| 52 | - Prefer source-level logic over clever selectors when behavior depends on consumer-provided class strings. Preserve `MenuPopup`'s current defaulting contract: a string `className` containing a `w-*`, `min-w-*`, or `max-w-*` utility after variant prefixes are stripped suppresses `min-w-32`; a string without one and a functional/non-string `className` keep the default. Arbitrary width values count as width utilities, and the consumer class must be merged last so it retains control. Do not replace this with a raw class-attribute substring selector. | ||
| 53 | - Do not fail solely because a valid emitted selector is verbose or because a source rule uses an intentional custom property. | ||
| 54 | |||
| 55 | ## Scroll and virtualized lists | ||
| 56 | |||
| 57 | - `ScrollArea` owns and masks its Base UI viewport. A virtualized list usually owns a native scrolling element and cannot automatically reuse viewport-specific `ScrollArea` behavior. | ||
| 58 | - Repeated native or virtualized overflow fades should use the shared virtualized-scroll-fade contract rather than component-named mask selectors. | ||
| 59 | - Preserve runtime top and bottom overflow state. Do not replace dynamic fades with an always-on static mask. | ||
| 60 | - Preserve fade geometry and keep the native scrollbar lane opaque so the track and thumb stay visible and usable. A visually similar mask that fades the scrollbar is a regression. | ||
| 61 | - Verify actual scroll behavior when changing virtualizers, masks, overflow ownership, or scrollbar selectors. Source-level class comparison is not enough. | ||
| 62 | |||
| 63 | ## Visual and layout preservation | ||
| 64 | |||
| 65 | - Preserve responsive geometry, titlebar insets, panel and inline-preview modes, desktop Electron layout, light and dark contrast, clipping, radius, and composable shadows. | ||
| 66 | - For meaningful visual changes, prefer available real-app evidence using the actual component and state. A mock recreation does not validate the real component. Light and dark evidence is useful when theme-sensitive styles change, but missing or inaccessible evidence alone is not a finding; report only a concrete regression supported by the diff, code, or available artifacts. | ||
| 67 | - Do not treat a screenshot as proof of keyboard, overflow, scrollbar, responsive, or runtime-theme behavior. Pair visual evidence with source, computed-style, emitted-CSS, or interaction checks as appropriate. | ||
| 68 | - Be alert to shared primitive color indirection. When a primitive routes icon color through a CSS variable, ensure migrated contextual icons retain their intended tone, including pressed and disabled states. | ||
| 69 | |||
| 70 | ## Change discipline | ||
| 71 | |||
| 72 | - Review the pull request's changed scope and directly affected consumers. Do not turn a focused PR into a demand for unrelated legacy cleanup. | ||
| 73 | - Prefer the smallest durable contract over a component-specific workaround or a broad abstraction with one consumer. | ||
| 74 | - Preserve intentional exceptions and comments that explain browser, virtualizer, theme, or Electron constraints. | ||
| 75 | - If a proposed cleanup cannot prove ownership or semantic equivalence, ask for evidence or leave it unchanged rather than guessing. | ||
| 76 | - Select verification gates according to the changed behavior: typecheck or focused tests for typing and interaction contracts, production build and emitted-CSS inspection for Tailwind or selector transformations, and real-app evidence for meaningful visual behavior when available. These gates are complementary when applicable, but do not require every gate for tiny visual-only migrations or fail solely because an artifact the configured tools cannot produce is absent. | ||
| 77 | |||
| 78 | ## Reporting | ||
| 79 | |||
| 80 | Report only concrete violations introduced by changed lines or behavior, plus pre-existing behavior that the patch directly makes relevant or worsens. Touching a large file does not make unrelated retained issues reportable. Prefer precise inline comments on the smallest relevant line range. Explain the broken behavior or ownership rule, not merely the preferred syntax, and state the smallest expected fix. A clear consistency or regression risk may fail the check. Do not fail for optional aesthetic preferences, harmless class ordering, or unrelated legacy code. | ||
| 81 | |||
| 82 | This check defaults to failure. When there are no findings, stop immediately and make the entire final response exactly `All clear` on one line. Do not add a title, explanation, punctuation, Markdown, JSON, or trailing analysis, and do not continue reasoning after deciding the review is clean. | ||
apps/web/src/components/AgentsPanel.tsx+10-8| ... | @@ -28,6 +28,7 @@ import { useEffect, useRef, useState } from "react"; | ... | @@ -28,6 +28,7 @@ import { useEffect, useRef, useState } from "react"; |
| 28 | import { cn } from "~/lib/utils"; | 28 | import { cn } from "~/lib/utils"; |
| 29 | import { orchestrationEnvironment } from "~/state/orchestration"; | 29 | import { orchestrationEnvironment } from "~/state/orchestration"; |
| 30 | import { ScrollArea } from "~/components/ui/scroll-area"; | 30 | import { ScrollArea } from "~/components/ui/scroll-area"; |
| 31 | import { Button } from "~/components/ui/button"; | ||
| 31 | 32 | ||
| 32 | /** | 33 | /** |
| 33 | * In-flight states all present as Working (one steady state, per the | 34 | * In-flight states all present as Working (one steady state, per the |
| ... | @@ -282,14 +283,15 @@ function WorkflowScriptView({ | ... | @@ -282,14 +283,15 @@ function WorkflowScriptView({ |
| 282 | <span className="truncate font-mono text-[.65rem] text-muted-foreground"> | 283 | <span className="truncate font-mono text-[.65rem] text-muted-foreground"> |
| 283 | {scriptPath.split("/").at(-1)} | 284 | {scriptPath.split("/").at(-1)} |
| 284 | </span> | 285 | </span> |
| 285 | <button | 286 | <Button |
| 286 | type="button" | 287 | size="icon-micro" |
| 288 | variant="ghost-muted" | ||
| 287 | onClick={onClose} | 289 | onClick={onClose} |
| 288 | aria-label="Close script" | 290 | aria-label="Close script" |
| 289 | className="ml-auto text-muted-foreground hover:text-foreground" | 291 | className="ml-auto" |
| 290 | > | 292 | > |
| 291 | <X aria-hidden className="size-3" /> | 293 | <X aria-hidden className="size-3" /> |
| 292 | </button> | 294 | </Button> |
| 293 | </div> | 295 | </div> |
| 294 | <div className="max-h-72 overflow-auto p-2"> | 296 | <div className="max-h-72 overflow-auto p-2"> |
| 295 | {result._tag === "Success" ? ( | 297 | {result._tag === "Success" ? ( |
| ... | @@ -417,14 +419,14 @@ function ExpandedWorkflowSection({ | ... | @@ -417,14 +419,14 @@ function ExpandedWorkflowSection({ |
| 417 | <span className="ml-auto font-mono normal-case text-muted-foreground/80"> | 419 | <span className="ml-auto font-mono normal-case text-muted-foreground/80"> |
| 418 | {settled}/{members.length} settled | 420 | {settled}/{members.length} settled |
| 419 | </span> | 421 | </span> |
| 420 | <button | 422 | <Button |
| 421 | type="button" | 423 | size="icon-micro" |
| 424 | variant="ghost-muted" | ||
| 422 | onClick={onCollapse} | 425 | onClick={onCollapse} |
| 423 | aria-label="Collapse workflow" | 426 | aria-label="Collapse workflow" |
| 424 | className="text-muted-foreground hover:text-foreground" | ||
| 425 | > | 427 | > |
| 426 | <ChevronDown aria-hidden className="size-3" /> | 428 | <ChevronDown aria-hidden className="size-3" /> |
| 427 | </button> | 429 | </Button> |
| 428 | </div> | 430 | </div> |
| 429 | <PhaseRail group={group} /> | 431 | <PhaseRail group={group} /> |
| 430 | {scriptOpen && canShowScript ? ( | 432 | {scriptOpen && canShowScript ? ( |
apps/web/src/components/BranchToolbarBranchSelector.tsx+6-3| ... | @@ -51,6 +51,7 @@ import { | ... | @@ -51,6 +51,7 @@ import { |
| 51 | } from "./ThreadStatusIndicators"; | 51 | } from "./ThreadStatusIndicators"; |
| 52 | import { Button } from "./ui/button"; | 52 | import { Button } from "./ui/button"; |
| 53 | import { Switch } from "./ui/switch"; | 53 | import { Switch } from "./ui/switch"; |
| 54 | import { getVirtualizedScrollFadeClassName } from "./ui/scroll-area"; | ||
| 54 | import { | 55 | import { |
| 55 | Combobox, | 56 | Combobox, |
| 56 | ComboboxEmpty, | 57 | ComboboxEmpty, |
| ... | @@ -814,9 +815,11 @@ export function BranchToolbarBranchSelector({ | ... | @@ -814,9 +815,11 @@ export function BranchToolbarBranchSelector({ |
| 814 | maybeFetchNextBranchPage(); | 815 | maybeFetchNextBranchPage(); |
| 815 | }} | 816 | }} |
| 816 | className={cn( | 817 | className={cn( |
| 817 | "scrollbar-gutter-stable overflow-x-hidden overscroll-y-contain ps-1 pe-0 pt-2 pb-1 [--fade-size:1.5rem]", | 818 | "scrollbar-gutter-stable overflow-x-hidden overscroll-y-contain ps-1 pe-0 pt-2 pb-1", |
| 818 | showTopBranchScrollFade && "mask-t-from-[calc(100%-var(--fade-size))]", | 819 | getVirtualizedScrollFadeClassName({ |
| 819 | showBottomBranchScrollFade && "mask-b-from-[calc(100%-var(--fade-size))]", | 820 | top: showTopBranchScrollFade, |
| 821 | bottom: showBottomBranchScrollFade, | ||
| 822 | }), | ||
| 820 | )} | 823 | )} |
| 821 | style={{ maxHeight: "14rem" }} | 824 | style={{ maxHeight: "14rem" }} |
| 822 | /> | 825 | /> |
apps/web/src/components/ChatMarkdown.tsx+13-10| ... | @@ -449,7 +449,7 @@ function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) { | ... | @@ -449,7 +449,7 @@ function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) { |
| 449 | {children} | 449 | {children} |
| 450 | </table> | 450 | </table> |
| 451 | </ScrollArea> | 451 | </ScrollArea> |
| 452 | <div className="chat-markdown-table-footer select-none"> | 452 | <div className="mt-0.5 flex items-center justify-between select-none"> |
| 453 | <Tooltip> | 453 | <Tooltip> |
| 454 | <TooltipTrigger | 454 | <TooltipTrigger |
| 455 | render={ | 455 | render={ |
| ... | @@ -642,12 +642,12 @@ function MarkdownCodeBlock({ | ... | @@ -642,12 +642,12 @@ function MarkdownCodeBlock({ |
| 642 | 642 | ||
| 643 | return ( | 643 | return ( |
| 644 | <div | 644 | <div |
| 645 | className="chat-markdown-codeblock border border-border/70 bg-secondary leading-snug dark:border-transparent dark:bg-input/32" | 645 | className="chat-markdown-codeblock my-[0.65rem] overflow-hidden rounded-[var(--radius)] border border-border/70 bg-secondary leading-snug dark:border-transparent dark:bg-input/32" |
| 646 | data-language={language} | 646 | data-language={language} |
| 647 | data-wrap={wrapped ? "true" : "false"} | 647 | data-wrap={wrapped ? "true" : "false"} |
| 648 | > | 648 | > |
| 649 | <div className="chat-markdown-codeblock-header select-none"> | 649 | <div className="chat-markdown-codeblock-header flex items-center justify-between gap-2 pt-1.5 pr-1.5 pb-0 pl-3 select-none"> |
| 650 | <span className="chat-markdown-codeblock-title"> | 650 | <span className="inline-flex min-w-0 items-center gap-[0.4rem] [font-family:var(--font-mono,ui-monospace,SFMono-Regular,monospace)] [font-size:0.6875rem]"> |
| 651 | <MarkdownCodeBlockTitleContent | 651 | <MarkdownCodeBlockTitleContent |
| 652 | fenceTitle={fenceTitle} | 652 | fenceTitle={fenceTitle} |
| 653 | language={language} | 653 | language={language} |
| ... | @@ -897,7 +897,10 @@ const failedFaviconHosts = new Set<string>(); | ... | @@ -897,7 +897,10 @@ const failedFaviconHosts = new Set<string>(); |
| 897 | const MarkdownLinkFavicon = memo(function MarkdownLinkFavicon({ host }: { host: string }) { | 897 | const MarkdownLinkFavicon = memo(function MarkdownLinkFavicon({ host }: { host: string }) { |
| 898 | const [failedHost, setFailedHost] = useState<string | null>(null); | 898 | const [failedHost, setFailedHost] = useState<string | null>(null); |
| 899 | return ( | 899 | return ( |
| 900 | <span className="chat-markdown-link-favicon" aria-hidden> | 900 | <span |
| 901 | className="ms-[0.25em] me-[0.2em] inline-flex size-[14px] [vertical-align:-0.125em]" | ||
| 902 | aria-hidden | ||
| 903 | > | ||
| 901 | {failedHost === host || failedFaviconHosts.has(host) ? ( | 904 | {failedHost === host || failedFaviconHosts.has(host) ? ( |
| 902 | <GlobeIcon className={MARKDOWN_LINK_FAVICON_CLASS_NAME} /> | 905 | <GlobeIcon className={MARKDOWN_LINK_FAVICON_CLASS_NAME} /> |
| 903 | ) : ( | 906 | ) : ( |
| ... | @@ -1044,7 +1047,7 @@ function MarkdownExternalLinkContent({ | ... | @@ -1044,7 +1047,7 @@ function MarkdownExternalLinkContent({ |
| 1044 | const leadingLength = leadingExternalLinkTextLength(plainText); | 1047 | const leadingLength = leadingExternalLinkTextLength(plainText); |
| 1045 | return ( | 1048 | return ( |
| 1046 | <> | 1049 | <> |
| 1047 | <span className="chat-markdown-link-leading"> | 1050 | <span className="whitespace-nowrap"> |
| 1048 | <MarkdownLinkFavicon host={host} /> | 1051 | <MarkdownLinkFavicon host={host} /> |
| 1049 | {plainText.slice(0, leadingLength)} | 1052 | {plainText.slice(0, leadingLength)} |
| 1050 | </span> | 1053 | </span> |
| ... | @@ -1060,7 +1063,7 @@ function MarkdownExternalLinkContent({ | ... | @@ -1060,7 +1063,7 @@ function MarkdownExternalLinkContent({ |
| 1060 | const leadingLength = leadingExternalLinkTextLength(firstChild); | 1063 | const leadingLength = leadingExternalLinkTextLength(firstChild); |
| 1061 | return ( | 1064 | return ( |
| 1062 | <> | 1065 | <> |
| 1063 | <span className="chat-markdown-link-leading"> | 1066 | <span className="whitespace-nowrap"> |
| 1064 | <MarkdownLinkFavicon host={host} /> | 1067 | <MarkdownLinkFavicon host={host} /> |
| 1065 | {firstChild.slice(0, leadingLength)} | 1068 | {firstChild.slice(0, leadingLength)} |
| 1066 | </span> | 1069 | </span> |
| ... | @@ -1072,7 +1075,7 @@ function MarkdownExternalLinkContent({ | ... | @@ -1072,7 +1075,7 @@ function MarkdownExternalLinkContent({ |
| 1072 | 1075 | ||
| 1073 | return ( | 1076 | return ( |
| 1074 | <> | 1077 | <> |
| 1075 | <span className="chat-markdown-link-leading"> | 1078 | <span className="whitespace-nowrap"> |
| 1076 | <MarkdownLinkFavicon host={host} /> | 1079 | <MarkdownLinkFavicon host={host} /> |
| 1077 | {firstChild} | 1080 | {firstChild} |
| 1078 | </span> | 1081 | </span> |
| ... | @@ -1289,7 +1292,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({ | ... | @@ -1289,7 +1292,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({ |
| 1289 | side="top" | 1292 | side="top" |
| 1290 | className="max-w-[min(40rem,calc(100vw-2rem))] font-mono text-[11px] leading-tight" | 1293 | className="max-w-[min(40rem,calc(100vw-2rem))] font-mono text-[11px] leading-tight" |
| 1291 | > | 1294 | > |
| 1292 | <div className="markdown-file-link-tooltip-scroll overflow-x-auto whitespace-nowrap"> | 1295 | <div className="overflow-x-auto whitespace-nowrap [scrollbar-color:color-mix(in_srgb,var(--border)_78%,transparent)_transparent] [scrollbar-width:thin] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-[color-mix(in_srgb,var(--border)_78%,transparent)] [&::-webkit-scrollbar-track]:bg-transparent"> |
| 1293 | {displayPath} | 1296 | {displayPath} |
| 1294 | </div> | 1297 | </div> |
| 1295 | </TooltipPopup> | 1298 | </TooltipPopup> |
| ... | @@ -1699,7 +1702,7 @@ function ChatMarkdown({ | ... | @@ -1699,7 +1702,7 @@ function ChatMarkdown({ |
| 1699 | return ( | 1702 | return ( |
| 1700 | <div | 1703 | <div |
| 1701 | className={cn( | 1704 | className={cn( |
| 1702 | "chat-markdown w-full min-w-0 text-sm leading-relaxed text-foreground/80", | 1705 | "chat-markdown w-full min-w-0 text-sm leading-relaxed text-foreground/80 [overflow-wrap:anywhere] [word-break:break-word]", |
| 1703 | className, | 1706 | className, |
| 1704 | )} | 1707 | )} |
| 1705 | onCopy={handleCopy} | 1708 | onCopy={handleCopy} |
apps/web/src/components/ChatView.tsx+10-8| ... | @@ -6023,8 +6023,9 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -6023,8 +6023,9 @@ function ChatViewContent(props: ChatViewProps) { |
| 6023 | // One inset in both states: the controls move between containers when | 6023 | // One inset in both states: the controls move between containers when |
| 6024 | // the right panel opens, and a different right offset made them jump | 6024 | // the right panel opens, and a different right offset made them jump |
| 6025 | // sideways on every toggle. | 6025 | // sideways on every toggle. |
| 6026 | "workspace-titlebar-controls z-50 mr-px gap-1 [-webkit-app-region:no-drag]", | 6026 | "absolute top-[var(--workspace-controls-top)] right-[var(--workspace-controls-right)] z-50 mr-px flex h-[var(--workspace-topbar-height)] items-center gap-1 [-webkit-app-region:no-drag]", |
| 6027 | )} | 6027 | )} |
| 6028 | data-workspace-titlebar-controls | ||
| 6028 | > | 6029 | > |
| 6029 | {rightPanelOpen && !shouldUseRightPanelSheet ? ( | 6030 | {rightPanelOpen && !shouldUseRightPanelSheet ? ( |
| 6030 | <RightPanelMaximizeControl | 6031 | <RightPanelMaximizeControl |
| ... | @@ -6165,12 +6166,12 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -6165,12 +6166,12 @@ function ChatViewContent(props: ChatViewProps) { |
| 6165 | "bg-background transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none", | 6166 | "bg-background transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none", |
| 6166 | isElectron | 6167 | isElectron |
| 6167 | ? cn( | 6168 | ? cn( |
| 6168 | "workspace-topbar drag-region relative px-3 sm:px-5", | 6169 | "drag-region relative flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 sm:px-5", |
| 6169 | reserveTitleBarControlInset && | 6170 | reserveTitleBarControlInset && |
| 6170 | !inlineRightPanelOwnsTitleBar && | 6171 | !inlineRightPanelOwnsTitleBar && |
| 6171 | "wco:pr-[var(--workspace-native-controls-inset)]", | 6172 | "wco:pr-[var(--workspace-native-controls-inset)]", |
| 6172 | ) | 6173 | ) |
| 6173 | : "workspace-topbar pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)]", | 6174 | : "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center pl-[calc(env(safe-area-inset-left)+0.75rem)] pr-[calc(env(safe-area-inset-right)+0.75rem)] sm:pl-[calc(env(safe-area-inset-left)+1.25rem)] sm:pr-[calc(env(safe-area-inset-right)+1.25rem)]", |
| 6174 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | 6175 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 6175 | )} | 6176 | )} |
| 6176 | > | 6177 | > |
| ... | @@ -6273,16 +6274,17 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -6273,16 +6274,17 @@ function ChatViewContent(props: ChatViewProps) { |
| 6273 | className="pointer-events-none absolute left-1/2 z-30 flex -translate-x-1/2 justify-center py-1.5" | 6274 | className="pointer-events-none absolute left-1/2 z-30 flex -translate-x-1/2 justify-center py-1.5" |
| 6274 | style={{ bottom: composerOverlayHeight + 4 }} | 6275 | style={{ bottom: composerOverlayHeight + 4 }} |
| 6275 | > | 6276 | > |
| 6276 | <button | 6277 | <Button |
| 6277 | type="button" | ||
| 6278 | aria-label="Scroll to end" | 6278 | aria-label="Scroll to end" |
| 6279 | title="Scroll to end" | 6279 | title="Scroll to end" |
| 6280 | onClick={() => scrollToEnd(true)} | 6280 | onClick={() => scrollToEnd(true)} |
| 6281 | className="chat-composer-glass pointer-events-auto flex items-center gap-1.5 rounded-full border border-border/60 px-3 py-1 text-muted-foreground text-xs shadow-sm transition-colors hover:border-border hover:text-foreground hover:cursor-pointer" | 6281 | className="pointer-events-auto gap-1.5 rounded-full px-3 text-muted-foreground hover:text-foreground" |
| 6282 | size="xs" | ||
| 6283 | variant="glass" | ||
| 6282 | > | 6284 | > |
| 6283 | <ChevronDownIcon className="size-3.5" /> | 6285 | <ChevronDownIcon className="size-3.5" /> |
| 6284 | Scroll to end | 6286 | Scroll to end |
| 6285 | </button> | 6287 | </Button> |
| 6286 | </div> | 6288 | </div> |
| 6287 | )} | 6289 | )} |
| 6288 | </div> | 6290 | </div> |
| ... | @@ -6299,7 +6301,7 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -6299,7 +6301,7 @@ function ChatViewContent(props: ChatViewProps) { |
| 6299 | > | 6301 | > |
| 6300 | <div | 6302 | <div |
| 6301 | ref={attachDraftHeroTransitionGroupRef} | 6303 | ref={attachDraftHeroTransitionGroupRef} |
| 6302 | className="chat-composer-horizontal-inset w-full" | 6304 | className="w-full ps-[calc(env(safe-area-inset-left)+0.75rem)] pe-[calc(env(safe-area-inset-right)+0.75rem)] sm:ps-[calc(env(safe-area-inset-left)+1.25rem)] sm:pe-[calc(env(safe-area-inset-right)+1.25rem)]" |
| 6303 | > | 6305 | > |
| 6304 | <div className="pointer-events-auto relative z-10"> | 6306 | <div className="pointer-events-auto relative z-10"> |
| 6305 | {isDraftHeroState ? ( | 6307 | {isDraftHeroState ? ( |
apps/web/src/components/ComposerPromptEditor.tsx+6-6| ... | @@ -71,6 +71,7 @@ import { | ... | @@ -71,6 +71,7 @@ import { |
| 71 | import { cn, isMacPlatform } from "~/lib/utils"; | 71 | import { cn, isMacPlatform } from "~/lib/utils"; |
| 72 | import { basenameOfPath } from "~/pierre-icons"; | 72 | import { basenameOfPath } from "~/pierre-icons"; |
| 73 | import { | 73 | import { |
| 74 | COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME, | ||
| 74 | COMPOSER_INLINE_CHIP_ICON_CLASS_NAME, | 75 | COMPOSER_INLINE_CHIP_ICON_CLASS_NAME, |
| 75 | COMPOSER_INLINE_SKILL_CHIP_CLASS_NAME, | 76 | COMPOSER_INLINE_SKILL_CHIP_CLASS_NAME, |
| 76 | COMPOSER_INLINE_SKILL_CHIP_LABEL_CLASS_NAME, | 77 | COMPOSER_INLINE_SKILL_CHIP_LABEL_CLASS_NAME, |
| ... | @@ -188,7 +189,7 @@ class ComposerMentionNode extends DecoratorNode<React.ReactElement> { | ... | @@ -188,7 +189,7 @@ class ComposerMentionNode extends DecoratorNode<React.ReactElement> { |
| 188 | 189 | ||
| 189 | override createDOM(): HTMLElement { | 190 | override createDOM(): HTMLElement { |
| 190 | const dom = document.createElement("span"); | 191 | const dom = document.createElement("span"); |
| 191 | dom.className = "composer-inline-chip relative inline-flex align-[-0.125em] leading-none"; | 192 | dom.className = COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME; |
| 192 | return dom; | 193 | return dom; |
| 193 | } | 194 | } |
| 194 | 195 | ||
| ... | @@ -326,7 +327,7 @@ class ComposerSkillNode extends DecoratorNode<React.ReactElement> { | ... | @@ -326,7 +327,7 @@ class ComposerSkillNode extends DecoratorNode<React.ReactElement> { |
| 326 | 327 | ||
| 327 | override createDOM(): HTMLElement { | 328 | override createDOM(): HTMLElement { |
| 328 | const dom = document.createElement("span"); | 329 | const dom = document.createElement("span"); |
| 329 | dom.className = "composer-inline-chip relative inline-flex align-[-0.125em] leading-none"; | 330 | dom.className = COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME; |
| 330 | return dom; | 331 | return dom; |
| 331 | } | 332 | } |
| 332 | 333 | ||
| ... | @@ -397,7 +398,7 @@ class ComposerTerminalContextNode extends DecoratorNode<React.ReactElement> { | ... | @@ -397,7 +398,7 @@ class ComposerTerminalContextNode extends DecoratorNode<React.ReactElement> { |
| 397 | 398 | ||
| 398 | override createDOM(): HTMLElement { | 399 | override createDOM(): HTMLElement { |
| 399 | const dom = document.createElement("span"); | 400 | const dom = document.createElement("span"); |
| 400 | dom.className = "composer-inline-chip relative inline-flex align-[-0.125em] leading-none"; | 401 | dom.className = COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME; |
| 401 | return dom; | 402 | return dom; |
| 402 | } | 403 | } |
| 403 | 404 | ||
| ... | @@ -1747,13 +1748,12 @@ function ComposerPromptEditorInner({ | ... | @@ -1747,13 +1748,12 @@ function ComposerPromptEditorInner({ |
| 1747 | 1748 | ||
| 1748 | return ( | 1749 | return ( |
| 1749 | <ComposerTerminalContextActionsContext value={terminalContextActions}> | 1750 | <ComposerTerminalContextActionsContext value={terminalContextActions}> |
| 1750 | <div className="composer-editor-surface relative"> | 1751 | <div className="relative [font-family:var(--font-composer,var(--font-sans))] [font-size:var(--font-size-prompt,0.875rem)] [@media(max-width:39.999rem)_and_(pointer:coarse)]:[font-size:max(var(--font-size-prompt,1rem),16px)]"> |
| 1751 | <PlainTextPlugin | 1752 | <PlainTextPlugin |
| 1752 | contentEditable={ | 1753 | contentEditable={ |
| 1753 | <ContentEditable | 1754 | <ContentEditable |
| 1754 | className={cn( | 1755 | className={cn( |
| 1755 | // The size comes from .composer-editor-surface so Settings -> Appearance | 1756 | // The wrapper owns the appearance preference; keep everything else here. |
| 1756 | // can drive it; keep everything else here. | ||
| 1757 | "block max-h-50 min-h-17.5 w-full overflow-y-auto whitespace-pre-wrap wrap-break-word bg-transparent leading-relaxed text-foreground focus:outline-none", | 1757 | "block max-h-50 min-h-17.5 w-full overflow-y-auto whitespace-pre-wrap wrap-break-word bg-transparent leading-relaxed text-foreground focus:outline-none", |
| 1758 | className, | 1758 | className, |
| 1759 | )} | 1759 | )} |
apps/web/src/components/DiffPanel.tsx+5-4| ... | @@ -825,7 +825,7 @@ export default function DiffPanel({ | ... | @@ -825,7 +825,7 @@ export default function DiffPanel({ |
| 825 | </div> | 825 | </div> |
| 826 | ) : ( | 826 | ) : ( |
| 827 | <> | 827 | <> |
| 828 | <div className="diff-panel-viewport flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden"> | 828 | <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-background"> |
| 829 | {isSelectedPatchTruncated && ( | 829 | {isSelectedPatchTruncated && ( |
| 830 | <p className="shrink-0 border-b border-border/70 bg-muted/40 px-3 py-1.5 text-[11px] text-muted-foreground"> | 830 | <p className="shrink-0 border-b border-border/70 bg-muted/40 px-3 py-1.5 text-[11px] text-muted-foreground"> |
| 831 | This diff was truncated because it exceeded the preview limit. The changes shown are | 831 | This diff was truncated because it exceeded the preview limit. The changes shown are |
| ... | @@ -907,10 +907,11 @@ export default function DiffPanel({ | ... | @@ -907,10 +907,11 @@ export default function DiffPanel({ |
| 907 | <Tooltip> | 907 | <Tooltip> |
| 908 | <TooltipTrigger | 908 | <TooltipTrigger |
| 909 | render={ | 909 | render={ |
| 910 | <button | 910 | <Button |
| 911 | type="button" | 911 | size="icon-micro" |
| 912 | variant="ghost" | ||
| 912 | className={cn( | 913 | className={cn( |
| 913 | "-ms-0.5 inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors hover:bg-foreground/10 focus-visible:outline-hidden", | 914 | "-ms-0.5 [--control-icon-color:currentColor] bg-transparent hover:bg-foreground/10", |
| 914 | getDiffCollapseIconClassName(fileDiff), | 915 | getDiffCollapseIconClassName(fileDiff), |
| 915 | )} | 916 | )} |
| 916 | aria-label={collapsed ? `Expand ${filePath}` : `Collapse ${filePath}`} | 917 | aria-label={collapsed ? `Expand ${filePath}` : `Collapse ${filePath}`} |
apps/web/src/components/DiffPanelShell.tsx+1-1| ... | @@ -14,7 +14,7 @@ function getDiffPanelHeaderRowClassName(mode: DiffPanelMode) { | ... | @@ -14,7 +14,7 @@ function getDiffPanelHeaderRowClassName(mode: DiffPanelMode) { |
| 14 | mode === "embedded" ? "px-2" : "px-4", | 14 | mode === "embedded" ? "px-2" : "px-4", |
| 15 | shouldUseDragRegion | 15 | shouldUseDragRegion |
| 16 | ? "drag-region h-[52px] border-b border-border wco:h-[env(titlebar-area-height)] wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]" | 16 | ? "drag-region h-[52px] border-b border-border wco:h-[env(titlebar-area-height)] wco:pr-[calc(100vw-env(titlebar-area-width)-env(titlebar-area-x)+1em)]" |
| 17 | : "surface-subheader", | 17 | : "flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background in-data-[preview-panel-mode=inline]:mb-3 in-data-[preview-panel-mode=inline]:h-7 in-data-[preview-panel-mode=inline]:min-h-7 in-data-[preview-panel-mode=inline]:border-b-transparent", |
| 18 | ); | 18 | ); |
| 19 | } | 19 | } |
| 20 | 20 |
apps/web/src/components/LegacySidebar.tsx+4-3| ... | @@ -2925,11 +2925,12 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2925,11 +2925,12 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2925 | <Tooltip> | 2925 | <Tooltip> |
| 2926 | <TooltipTrigger | 2926 | <TooltipTrigger |
| 2927 | render={ | 2927 | render={ |
| 2928 | <button | 2928 | <Button |
| 2929 | type="button" | 2929 | size="icon-xs" |
| 2930 | variant="ghost-muted" | ||
| 2930 | aria-label="Add project" | 2931 | aria-label="Add project" |
| 2931 | data-testid="sidebar-add-project-trigger" | 2932 | data-testid="sidebar-add-project-trigger" |
| 2932 | className="inline-flex h-6 min-w-6 cursor-pointer items-center justify-center rounded-md px-[calc(--spacing(1)-1px)] text-icon-muted transition-colors hover:bg-accent hover:text-foreground" | 2933 | className="size-6 [--control-icon-color:currentColor] text-icon-muted" |
| 2933 | onClick={openAddProject} | 2934 | onClick={openAddProject} |
| 2934 | /> | 2935 | /> |
| 2935 | } | 2936 | } |
apps/web/src/components/NoActiveThreadState.tsx+3-1| ... | @@ -11,7 +11,9 @@ export function NoActiveThreadState() { | ... | @@ -11,7 +11,9 @@ export function NoActiveThreadState() { |
| 11 | <header | 11 | <header |
| 12 | className={cn( | 12 | className={cn( |
| 13 | "border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | 13 | "border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", |
| 14 | isElectron ? "workspace-topbar drag-region" : "workspace-topbar", | 14 | isElectron |
| 15 | ? "drag-region flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center" | ||
| 16 | : "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center", | ||
| 15 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | 17 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 16 | )} | 18 | )} |
| 17 | > | 19 | > |
apps/web/src/components/RightPanelTabs.tsx+10-3| ... | @@ -26,6 +26,7 @@ import type { DesktopPreviewOverlay } from "~/previewStateStore"; | ... | @@ -26,6 +26,7 @@ import type { DesktopPreviewOverlay } from "~/previewStateStore"; |
| 26 | import type { RightPanelSurface } from "~/rightPanelStore"; | 26 | import type { RightPanelSurface } from "~/rightPanelStore"; |
| 27 | import { cn } from "~/lib/utils"; | 27 | import { cn } from "~/lib/utils"; |
| 28 | import { readLocalApi } from "~/localApi"; | 28 | import { readLocalApi } from "~/localApi"; |
| 29 | import { Button } from "~/components/ui/button"; | ||
| 29 | import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; | 30 | import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; |
| 30 | import { Kbd } from "~/components/ui/kbd"; | 31 | import { Kbd } from "~/components/ui/kbd"; |
| 31 | import { Menu, MenuItem, MenuPopup, MenuTrigger } from "~/components/ui/menu"; | 32 | import { Menu, MenuItem, MenuPopup, MenuTrigger } from "~/components/ui/menu"; |
| ... | @@ -603,7 +604,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { | ... | @@ -603,7 +604,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) { |
| 603 | > | 604 | > |
| 604 | <div | 605 | <div |
| 605 | className={cn( | 606 | className={cn( |
| 606 | "workspace-topbar gap-1 pl-2", | 607 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center gap-1 pl-2", |
| 607 | // The sheet overlays from the viewport top, so its tab bar keeps | 608 | // The sheet overlays from the viewport top, so its tab bar keeps |
| 608 | // the titlebar's height: a compact row re-centers the layout | 609 | // the titlebar's height: a compact row re-centers the layout |
| 609 | // controls a few pixels higher and the cluster jumps on open. | 610 | // controls a few pixels higher and the cluster jumps on open. |
| ... | @@ -682,8 +683,14 @@ export function RightPanelTabs(props: RightPanelTabsProps) { | ... | @@ -682,8 +683,14 @@ export function RightPanelTabs(props: RightPanelTabsProps) { |
| 682 | {props.surfaces.length > 0 ? ( | 683 | {props.surfaces.length > 0 ? ( |
| 683 | <Menu> | 684 | <Menu> |
| 684 | <MenuTrigger | 685 | <MenuTrigger |
| 685 | className="cursor-pointer relative inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground" | 686 | render={ |
| 686 | aria-label="Add panel surface" | 687 | <Button |
| 688 | aria-label="Add panel surface" | ||
| 689 | className="size-6 shrink-0 text-muted-foreground hover:text-foreground" | ||
| 690 | size="icon-xs" | ||
| 691 | variant="ghost" | ||
| 692 | /> | ||
| 693 | } | ||
| 687 | > | 694 | > |
| 688 | <Plus className="size-3.5" /> | 695 | <Plus className="size-3.5" /> |
| 689 | </MenuTrigger> | 696 | </MenuTrigger> |
apps/web/src/components/Sidebar.tsx+7-6| ... | @@ -3298,9 +3298,9 @@ export default function Sidebar() { | ... | @@ -3298,9 +3298,9 @@ export default function Sidebar() { |
| 3298 | {isSearchingThreads ? ( | 3298 | {isSearchingThreads ? ( |
| 3299 | <Button | 3299 | <Button |
| 3300 | type="button" | 3300 | type="button" |
| 3301 | size="icon-xs" | 3301 | size="icon-micro" |
| 3302 | variant="ghost" | 3302 | variant="ghost" |
| 3303 | className="size-5 shrink-0 rounded-sm text-sidebar-muted-foreground hover:bg-sidebar-control-surface hover:text-sidebar-foreground" | 3303 | className="shrink-0 text-sidebar-muted-foreground hover:bg-sidebar-control-surface hover:text-sidebar-foreground" |
| 3304 | aria-label="Clear thread search" | 3304 | aria-label="Clear thread search" |
| 3305 | onClick={() => { | 3305 | onClick={() => { |
| 3306 | clearThreadSearch(); | 3306 | clearThreadSearch(); |
| ... | @@ -3412,18 +3412,19 @@ export default function Sidebar() { | ... | @@ -3412,18 +3412,19 @@ export default function Sidebar() { |
| 3412 | className="size-4 shrink-0" | 3412 | className="size-4 shrink-0" |
| 3413 | /> | 3413 | /> |
| 3414 | <span className="min-w-0 truncate text-sm">{project.displayName}</span> | 3414 | <span className="min-w-0 truncate text-sm">{project.displayName}</span> |
| 3415 | <button | 3415 | <Button |
| 3416 | type="button" | 3416 | size="icon-xs" |
| 3417 | variant="ghost-muted" | ||
| 3417 | aria-label={`Project settings for ${project.displayName}`} | 3418 | aria-label={`Project settings for ${project.displayName}`} |
| 3418 | title={`Project settings for ${project.displayName}`} | 3419 | title={`Project settings for ${project.displayName}`} |
| 3419 | className="ml-auto inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-icon-muted outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:bg-accent focus-visible:text-foreground focus-visible:ring-2 focus-visible:ring-ring" | 3420 | className="ml-auto size-6 [--control-icon-color:currentColor] text-icon-muted focus-visible:bg-accent focus-visible:text-foreground" |
| 3420 | onPointerDown={(event) => event.stopPropagation()} | 3421 | onPointerDown={(event) => event.stopPropagation()} |
| 3421 | onClick={(event) => { | 3422 | onClick={(event) => { |
| 3422 | void handleProjectSettings(event, project); | 3423 | void handleProjectSettings(event, project); |
| 3423 | }} | 3424 | }} |
| 3424 | > | 3425 | > |
| 3425 | <SettingsIcon className="size-3.5" /> | 3426 | <SettingsIcon className="size-3.5" /> |
| 3426 | </button> | 3427 | </Button> |
| 3427 | </MenuRadioItem> | 3428 | </MenuRadioItem> |
| 3428 | ); | 3429 | ); |
| 3429 | })} | 3430 | })} |
apps/web/src/components/ThreadTerminalDrawer.tsx+3-6| ... | @@ -31,6 +31,7 @@ import { | ... | @@ -31,6 +31,7 @@ import { |
| 31 | useState, | 31 | useState, |
| 32 | } from "react"; | 32 | } from "react"; |
| 33 | import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; | 33 | import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; |
| 34 | import { Button } from "~/components/ui/button"; | ||
| 34 | import { writeTextToClipboard } from "~/hooks/useCopyToClipboard"; | 35 | import { writeTextToClipboard } from "~/hooks/useCopyToClipboard"; |
| 35 | import { cn } from "~/lib/utils"; | 36 | import { cn } from "~/lib/utils"; |
| 36 | import { type TerminalContextSelection } from "~/lib/terminalContext"; | 37 | import { type TerminalContextSelection } from "~/lib/terminalContext"; |
| ... | @@ -1273,13 +1274,9 @@ export default function ThreadTerminalDrawer({ | ... | @@ -1273,13 +1274,9 @@ export default function ThreadTerminalDrawer({ |
| 1273 | ) : null} | 1274 | ) : null} |
| 1274 | <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-4 py-6 text-center text-sm text-muted-foreground"> | 1275 | <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-4 py-6 text-center text-sm text-muted-foreground"> |
| 1275 | <p>No terminal sessions for this thread yet.</p> | 1276 | <p>No terminal sessions for this thread yet.</p> |
| 1276 | <button | 1277 | <Button size="xs" variant="outline" onClick={onNewTerminalAction}> |
| 1277 | type="button" | ||
| 1278 | className="rounded-md border border-border/80 bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent" | ||
| 1279 | onClick={onNewTerminalAction} | ||
| 1280 | > | ||
| 1281 | {newTerminalActionLabel} | 1278 | {newTerminalActionLabel} |
| 1282 | </button> | 1279 | </Button> |
| 1283 | </div> | 1280 | </div> |
| 1284 | </aside> | 1281 | </aside> |
| 1285 | ); | 1282 | ); |
apps/web/src/components/chat/ComposerCommandMenu.tsx+1-1| ... | @@ -141,7 +141,7 @@ export const ComposerCommandMenu = memo(function ComposerCommandMenu(props: { | ... | @@ -141,7 +141,7 @@ export const ComposerCommandMenu = memo(function ComposerCommandMenu(props: { |
| 141 | > | 141 | > |
| 142 | <div | 142 | <div |
| 143 | ref={listRef} | 143 | ref={listRef} |
| 144 | className="dropdown-glass relative w-full overflow-hidden rounded-[20px] **:data-[slot=scroll-area-scrollbar]:data-[orientation=vertical]:my-4" | 144 | className="dropdown-glass relative w-full overflow-hidden rounded-[20px] shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] **:data-[slot=scroll-area-scrollbar]:data-[orientation=vertical]:my-4 dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]" |
| 145 | > | 145 | > |
| 146 | {props.items.length > 0 ? ( | 146 | {props.items.length > 0 ? ( |
| 147 | <CommandList className="max-h-72"> | 147 | <CommandList className="max-h-72"> |
apps/web/src/components/chat/ComposerPreviewAnnotationCards.test.tsx+14| ... | @@ -43,4 +43,18 @@ describe("ComposerPreviewAnnotationCards", () => { | ... | @@ -43,4 +43,18 @@ describe("ComposerPreviewAnnotationCards", () => { |
| 43 | expect(markup).not.toContain("localhost:3000"); | 43 | expect(markup).not.toContain("localhost:3000"); |
| 44 | expect(markup).not.toContain("Preview annotation"); | 44 | expect(markup).not.toContain("Preview annotation"); |
| 45 | }); | 45 | }); |
| 46 | |||
| 47 | it("uses the shared button contract for removal", () => { | ||
| 48 | const markup = renderToStaticMarkup( | ||
| 49 | <ComposerPreviewAnnotationCards | ||
| 50 | annotations={[annotation]} | ||
| 51 | images={[]} | ||
| 52 | onRemove={vi.fn()} | ||
| 53 | onExpandImage={vi.fn()} | ||
| 54 | />, | ||
| 55 | ); | ||
| 56 | |||
| 57 | expect(markup).toContain('aria-label="Remove preview annotation"'); | ||
| 58 | expect(markup).toContain('data-slot="button"'); | ||
| 59 | }); | ||
| 46 | }); | 60 | }); |
apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx+6-4| ... | @@ -5,6 +5,7 @@ import type { ReactNode } from "react"; | ... | @@ -5,6 +5,7 @@ import type { ReactNode } from "react"; |
| 5 | import type { ComposerImageAttachment } from "~/composerDraftStore"; | 5 | import type { ComposerImageAttachment } from "~/composerDraftStore"; |
| 6 | import { formatElementContextLabel, normalizeElementContextSelection } from "~/lib/elementContext"; | 6 | import { formatElementContextLabel, normalizeElementContextSelection } from "~/lib/elementContext"; |
| 7 | import { cn } from "~/lib/utils"; | 7 | import { cn } from "~/lib/utils"; |
| 8 | import { Button } from "../ui/button"; | ||
| 8 | 9 | ||
| 9 | interface ComposerPreviewAnnotationCardsProps { | 10 | interface ComposerPreviewAnnotationCardsProps { |
| 10 | annotations: ReadonlyArray<PreviewAnnotationPayload>; | 11 | annotations: ReadonlyArray<PreviewAnnotationPayload>; |
| ... | @@ -128,14 +129,15 @@ export function ComposerPreviewAnnotationCards({ | ... | @@ -128,14 +129,15 @@ export function ComposerPreviewAnnotationCards({ |
| 128 | </div> | 129 | </div> |
| 129 | </div> | 130 | </div> |
| 130 | </div> | 131 | </div> |
| 131 | <button | 132 | <Button |
| 132 | type="button" | 133 | size="icon-micro" |
| 134 | variant="ghost-muted" | ||
| 133 | aria-label="Remove preview annotation" | 135 | aria-label="Remove preview annotation" |
| 134 | className="absolute right-1.5 top-1.5 grid size-5 place-items-center rounded text-icon-muted transition hover:bg-muted hover:text-foreground" | 136 | className="absolute right-1.5 top-1.5 [--control-icon-color:currentColor] rounded text-icon-muted hover:bg-muted" |
| 135 | onClick={() => onRemove(annotation.id)} | 137 | onClick={() => onRemove(annotation.id)} |
| 136 | > | 138 | > |
| 137 | <X className="size-3" /> | 139 | <X className="size-3" /> |
| 138 | </button> | 140 | </Button> |
| 139 | </section> | 141 | </section> |
| 140 | ); | 142 | ); |
| 141 | })} | 143 | })} |
apps/web/src/components/chat/ComposerStashBadge.tsx+1-1| ... | @@ -46,7 +46,7 @@ export const ComposerStashBadge = memo(function ComposerStashBadge(props: { | ... | @@ -46,7 +46,7 @@ export const ComposerStashBadge = memo(function ComposerStashBadge(props: { |
| 46 | className={cn( | 46 | className={cn( |
| 47 | "rounded-full px-1.5 text-[10px] font-medium tabular-nums", | 47 | "rounded-full px-1.5 text-[10px] font-medium tabular-nums", |
| 48 | props.pulsing | 48 | props.pulsing |
| 49 | ? "prompt-stash-count-enter bg-primary text-primary-foreground" | 49 | ? "animate-[prompt-stash-count-enter_180ms_ease-out_both] bg-primary text-primary-foreground motion-reduce:animate-none" |
| 50 | : "bg-muted text-muted-foreground", | 50 | : "bg-muted text-muted-foreground", |
| 51 | )} | 51 | )} |
| 52 | > | 52 | > |
apps/web/src/components/chat/ComposerStashMenu.tsx+1-1| ... | @@ -91,7 +91,7 @@ export const ComposerStashMenu = memo(function ComposerStashMenu(props: { | ... | @@ -91,7 +91,7 @@ export const ComposerStashMenu = memo(function ComposerStashMenu(props: { |
| 91 | 91 | ||
| 92 | return ( | 92 | return ( |
| 93 | <Command autoHighlight={false} mode="none"> | 93 | <Command autoHighlight={false} mode="none"> |
| 94 | <div className="dropdown-glass relative w-full overflow-hidden rounded-[20px]"> | 94 | <div className="dropdown-glass relative w-full overflow-hidden rounded-[20px] shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]"> |
| 95 | <CommandList className="max-h-72"> | 95 | <CommandList className="max-h-72"> |
| 96 | <CommandGroup> | 96 | <CommandGroup> |
| 97 | <CommandGroupLabel className="flex items-center gap-1.5 px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-[0.08em] text-secondary-label"> | 97 | <CommandGroupLabel className="flex items-center gap-1.5 px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-[0.08em] text-secondary-label"> |
apps/web/src/components/chat/ContextWindowMeter.tsx+6-9| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { cn } from "~/lib/utils"; | 1 | import { Button } from "../ui/button"; |
| 2 | import { type ContextWindowSnapshot, formatContextWindowTokens } from "~/lib/contextWindow"; | 2 | import { type ContextWindowSnapshot, formatContextWindowTokens } from "~/lib/contextWindow"; |
| 3 | import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; | 3 | import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; |
| 4 | 4 | ||
| ... | @@ -36,13 +36,10 @@ export function ContextWindowMeter(props: { | ... | @@ -36,13 +36,10 @@ export function ContextWindowMeter(props: { |
| 36 | delay={150} | 36 | delay={150} |
| 37 | closeDelay={0} | 37 | closeDelay={0} |
| 38 | render={ | 38 | render={ |
| 39 | <button | 39 | <Button |
| 40 | type="button" | 40 | size="icon-sm" |
| 41 | className={cn( | 41 | variant="ghost-muted" |
| 42 | "inline-flex size-7 cursor-pointer items-center justify-center rounded-full border border-transparent text-muted-foreground outline-none transition-colors", | 42 | className="size-7 rounded-full hover:text-muted-foreground data-pressed:text-muted-foreground" |
| 43 | "hover:bg-accent data-[pressed]:bg-accent", | ||
| 44 | "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background", | ||
| 45 | )} | ||
| 46 | aria-label={ | 43 | aria-label={ |
| 47 | usage.maxTokens !== null && usedPercentage | 44 | usage.maxTokens !== null && usedPercentage |
| 48 | ? `Context window ${usedPercentage} used` | 45 | ? `Context window ${usedPercentage} used` |
| ... | @@ -77,7 +74,7 @@ export function ContextWindowMeter(props: { | ... | @@ -77,7 +74,7 @@ export function ContextWindowMeter(props: { |
| 77 | /> | 74 | /> |
| 78 | </svg> | 75 | </svg> |
| 79 | </span> | 76 | </span> |
| 80 | </button> | 77 | </Button> |
| 81 | } | 78 | } |
| 82 | /> | 79 | /> |
| 83 | <PopoverPopup | 80 | <PopoverPopup |
apps/web/src/components/chat/MessagesTimeline.test.tsx+2-2| ... | @@ -237,9 +237,9 @@ describe("MessagesTimeline", () => { | ... | @@ -237,9 +237,9 @@ describe("MessagesTimeline", () => { |
| 237 | ); | 237 | ); |
| 238 | 238 | ||
| 239 | expect(compactMarkup).toContain('class="h-3 sm:h-4"'); | 239 | expect(compactMarkup).toContain('class="h-3 sm:h-4"'); |
| 240 | expect(compactMarkup).not.toContain("chat-timeline-scroll-fade"); | 240 | expect(compactMarkup).not.toContain("topbar-scroll-fade"); |
| 241 | expect(fadedMarkup).toContain('class="h-10 sm:h-12"'); | 241 | expect(fadedMarkup).toContain('class="h-10 sm:h-12"'); |
| 242 | expect(fadedMarkup).toContain("chat-timeline-scroll-fade"); | 242 | expect(fadedMarkup).toContain("topbar-scroll-fade"); |
| 243 | }); | 243 | }); |
| 244 | 244 | ||
| 245 | it("keeps assistant changed-files headers sticky below the thread header", () => { | 245 | it("keeps assistant changed-files headers sticky below the thread header", () => { |
apps/web/src/components/chat/MessagesTimeline.tsx+1-1| ... | @@ -591,7 +591,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ | ... | @@ -591,7 +591,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ |
| 591 | onScroll={handleScroll} | 591 | onScroll={handleScroll} |
| 592 | className={cn( | 592 | className={cn( |
| 593 | "scrollbar-gutter-both h-full min-h-0 overflow-x-hidden overscroll-y-contain px-3 [overflow-anchor:none] sm:px-5", | 593 | "scrollbar-gutter-both h-full min-h-0 overflow-x-hidden overscroll-y-contain px-3 [overflow-anchor:none] sm:px-5", |
| 594 | topFadeEnabled && "chat-timeline-scroll-fade", | 594 | topFadeEnabled && "topbar-scroll-fade", |
| 595 | )} | 595 | )} |
| 596 | ListHeaderComponent={ | 596 | ListHeaderComponent={ |
| 597 | loadEarlier !== null ? ( | 597 | loadEarlier !== null ? ( |
apps/web/src/components/chat/ModelPickerContent.tsx+7-4| ... | @@ -33,6 +33,7 @@ import { | ... | @@ -33,6 +33,7 @@ import { |
| 33 | } from "../../keybindings"; | 33 | } from "../../keybindings"; |
| 34 | import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; | 34 | import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; |
| 35 | import { cn } from "~/lib/utils"; | 35 | import { cn } from "~/lib/utils"; |
| 36 | import { getVirtualizedScrollFadeClassName } from "../ui/scroll-area"; | ||
| 36 | import { TooltipProvider } from "../ui/tooltip"; | 37 | import { TooltipProvider } from "../ui/tooltip"; |
| 37 | import { | 38 | import { |
| 38 | isProviderInstancePickerReady, | 39 | isProviderInstancePickerReady, |
| ... | @@ -598,7 +599,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -598,7 +599,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 598 | return ( | 599 | return ( |
| 599 | <TooltipProvider delay={0}> | 600 | <TooltipProvider delay={0}> |
| 600 | <div | 601 | <div |
| 601 | className="dropdown-glass model-picker-surface relative flex h-screen max-h-86.5 w-screen max-w-90 flex-row overflow-hidden rounded-lg text-popover-foreground [clip-path:inset(0_round_var(--radius-lg))]" | 602 | className="dropdown-glass relative flex h-screen max-h-86.5 w-screen max-w-90 flex-row overflow-hidden rounded-lg text-popover-foreground shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] [clip-path:inset(0_round_var(--radius-lg))] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]" |
| 602 | data-model-picker-content="true" | 603 | data-model-picker-content="true" |
| 603 | > | 604 | > |
| 604 | {/* Sidebar */} | 605 | {/* Sidebar */} |
| ... | @@ -781,9 +782,11 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { | ... | @@ -781,9 +782,11 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 781 | onLayout={updateModelListScrollFades} | 782 | onLayout={updateModelListScrollFades} |
| 782 | onScroll={updateModelListScrollFades} | 783 | onScroll={updateModelListScrollFades} |
| 783 | className={cn( | 784 | className={cn( |
| 784 | "model-picker-list scrollbar-gutter-stable h-full overflow-x-hidden overscroll-y-contain py-1.5 [--fade-size:1.5rem]", | 785 | "scrollbar-gutter-stable h-full overflow-x-hidden overscroll-y-contain py-1.5 [&::-webkit-scrollbar-track]:my-2", |
| 785 | showTopScrollFade && "model-picker-list-scroll-fade-top", | 786 | getVirtualizedScrollFadeClassName({ |
| 786 | showBottomScrollFade && "model-picker-list-scroll-fade-bottom", | 787 | top: showTopScrollFade, |
| 788 | bottom: showBottomScrollFade, | ||
| 789 | }), | ||
| 787 | )} | 790 | )} |
| 788 | /> | 791 | /> |
| 789 | </ComboboxListVirtualized> | 792 | </ComboboxListVirtualized> |
apps/web/src/components/chat/ProviderStatusBanner.tsx+6-4| ... | @@ -2,6 +2,7 @@ import { type ServerProvider } from "@t3tools/contracts"; | ... | @@ -2,6 +2,7 @@ import { type ServerProvider } from "@t3tools/contracts"; |
| 2 | import { memo } from "react"; | 2 | import { memo } from "react"; |
| 3 | import { InfoIcon, XIcon } from "lucide-react"; | 3 | import { InfoIcon, XIcon } from "lucide-react"; |
| 4 | import { cn } from "~/lib/utils"; | 4 | import { cn } from "~/lib/utils"; |
| 5 | import { Button } from "../ui/button"; | ||
| 5 | import { formatProviderDriverKindLabel } from "../../providerModels"; | 6 | import { formatProviderDriverKindLabel } from "../../providerModels"; |
| 6 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | 7 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 7 | 8 | ||
| ... | @@ -66,14 +67,15 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({ | ... | @@ -66,14 +67,15 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({ |
| 66 | </TooltipPopup> | 67 | </TooltipPopup> |
| 67 | </Tooltip> | 68 | </Tooltip> |
| 68 | </div> | 69 | </div> |
| 69 | <button | 70 | <Button |
| 70 | type="button" | ||
| 71 | aria-label={`Dismiss ${providerName} provider ${status.status}`} | 71 | aria-label={`Dismiss ${providerName} provider ${status.status}`} |
| 72 | className="absolute top-2 right-2 inline-flex size-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-foreground/8 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring" | 72 | className="absolute top-2 right-2 size-6 text-muted-foreground hover:text-foreground" |
| 73 | onClick={onDismiss} | 73 | onClick={onDismiss} |
| 74 | size="icon-xs" | ||
| 75 | variant="ghost" | ||
| 74 | > | 76 | > |
| 75 | <XIcon aria-hidden className="size-3.5" /> | 77 | <XIcon aria-hidden className="size-3.5" /> |
| 76 | </button> | 78 | </Button> |
| 77 | </div> | 79 | </div> |
| 78 | </div> | 80 | </div> |
| 79 | ); | 81 | ); |
apps/web/src/components/composerInlineChip.ts+3| ... | @@ -8,6 +8,9 @@ export const CHAT_INLINE_CHIP_CLASS_NAME = `${INLINE_CHIP_CLASS_NAME} text-[12px | ... | @@ -8,6 +8,9 @@ export const CHAT_INLINE_CHIP_CLASS_NAME = `${INLINE_CHIP_CLASS_NAME} text-[12px |
| 8 | 8 | ||
| 9 | export const COMPOSER_INLINE_CHIP_CLASS_NAME = `${INLINE_CHIP_CLASS_NAME} text-[0.86em] select-none`; | 9 | export const COMPOSER_INLINE_CHIP_CLASS_NAME = `${INLINE_CHIP_CLASS_NAME} text-[0.86em] select-none`; |
| 10 | 10 | ||
| 11 | export const COMPOSER_INLINE_CHIP_DECORATOR_CLASS_NAME = | ||
| 12 | "relative inline-flex align-[-0.125em] leading-none data-[composer-chip-selected]:after:pointer-events-none data-[composer-chip-selected]:after:absolute data-[composer-chip-selected]:after:inset-0 data-[composer-chip-selected]:after:rounded-[6px] data-[composer-chip-selected]:after:bg-[Highlight] data-[composer-chip-selected]:after:opacity-30 data-[composer-chip-selected]:after:content-['']"; | ||
| 13 | |||
| 11 | export const COMPOSER_INLINE_CHIP_ICON_CLASS_NAME = "size-[1.17em] shrink-0 opacity-85"; | 14 | export const COMPOSER_INLINE_CHIP_ICON_CLASS_NAME = "size-[1.17em] shrink-0 opacity-85"; |
| 12 | 15 | ||
| 13 | export const CHAT_INLINE_CHIP_LABEL_CLASS_NAME = "truncate leading-tight"; | 16 | export const CHAT_INLINE_CHIP_LABEL_CLASS_NAME = "truncate leading-tight"; |
apps/web/src/components/diffs/StyledDiffCodeView.test.tsx+3-1| ... | @@ -35,7 +35,9 @@ describe("StyledDiffCodeView", () => { | ... | @@ -35,7 +35,9 @@ describe("StyledDiffCodeView", () => { |
| 35 | />, | 35 | />, |
| 36 | ); | 36 | ); |
| 37 | 37 | ||
| 38 | expect(testState.codeViewClassName).toBe("diff-render-surface outline-none min-h-0"); | 38 | expect(testState.codeViewClassName).toBe( |
| 39 | "diff-render-surface [--code-background:var(--background)] outline-none min-h-0", | ||
| 40 | ); | ||
| 39 | expect(testState.codeViewOptions).toMatchObject({ | 41 | expect(testState.codeViewOptions).toMatchObject({ |
| 40 | theme: "pierre-dark", | 42 | theme: "pierre-dark", |
| 41 | stickyHeaders: true, | 43 | stickyHeaders: true, |
apps/web/src/components/diffs/StyledDiffCodeView.tsx+2-2| ... | @@ -292,8 +292,8 @@ export function StyledDiffCodeView<LAnnotation = undefined>({ | ... | @@ -292,8 +292,8 @@ export function StyledDiffCodeView<LAnnotation = undefined>({ |
| 292 | // outside the panel clipping boundary; actual controls inside retain their own indicators. | 292 | // outside the panel clipping boundary; actual controls inside retain their own indicators. |
| 293 | className={ | 293 | className={ |
| 294 | className | 294 | className |
| 295 | ? `diff-render-surface outline-none ${className}` | 295 | ? `diff-render-surface [--code-background:var(--background)] outline-none ${className}` |
| 296 | : "diff-render-surface outline-none" | 296 | : "diff-render-surface [--code-background:var(--background)] outline-none" |
| 297 | } | 297 | } |
| 298 | options={{ | 298 | options={{ |
| 299 | ...options, | 299 | ...options, |
apps/web/src/components/files/FileBrowserPanel.tsx+5-2| ... | @@ -78,7 +78,7 @@ function FileSearchField(props: { | ... | @@ -78,7 +78,7 @@ function FileSearchField(props: { |
| 78 | value: string; | 78 | value: string; |
| 79 | }) { | 79 | }) { |
| 80 | return ( | 80 | return ( |
| 81 | <InputGroup variant="ghost" className="h-7 min-w-0 flex-1 rounded-md"> | 81 | <InputGroup variant="ghost" className="h-7 min-w-0 flex-1"> |
| 82 | <InputGroupInput | 82 | <InputGroupInput |
| 83 | type="search" | 83 | type="search" |
| 84 | name={props.name} | 84 | name={props.name} |
| ... | @@ -350,7 +350,10 @@ export default function FileBrowserPanel({ | ... | @@ -350,7 +350,10 @@ export default function FileBrowserPanel({ |
| 350 | className="flex min-h-0 flex-1 flex-col bg-background" | 350 | className="flex min-h-0 flex-1 flex-col bg-background" |
| 351 | data-file-browser-panel={`${environmentId}:${cwd}`} | 351 | data-file-browser-panel={`${environmentId}:${cwd}`} |
| 352 | > | 352 | > |
| 353 | <div className="surface-subheader gap-1 px-2" data-surface-subheader> | 353 | <div |
| 354 | className="flex h-10 min-h-10 shrink-0 items-center gap-1 border-b border-border/60 bg-background px-2 in-data-[preview-panel-mode=inline]:mb-3 in-data-[preview-panel-mode=inline]:h-7 in-data-[preview-panel-mode=inline]:min-h-7 in-data-[preview-panel-mode=inline]:border-b-transparent" | ||
| 355 | data-surface-subheader | ||
| 356 | > | ||
| 354 | <RefreshFilesButton isPending={entriesQuery.isPending} onRefresh={entriesQuery.refresh} /> | 357 | <RefreshFilesButton isPending={entriesQuery.isPending} onRefresh={entriesQuery.refresh} /> |
| 355 | <FileSearchField | 358 | <FileSearchField |
| 356 | name="project-files-search" | 359 | name="project-files-search" |
apps/web/src/components/files/FilePreviewPanel.tsx+4-1| ... | @@ -857,7 +857,10 @@ export default function FilePreviewPanel({ | ... | @@ -857,7 +857,10 @@ export default function FilePreviewPanel({ |
| 857 | return ( | 857 | return ( |
| 858 | <div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-background"> | 858 | <div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-background"> |
| 859 | {relativePath ? ( | 859 | {relativePath ? ( |
| 860 | <div className="surface-subheader gap-2 px-3" data-surface-subheader> | 860 | <div |
| 861 | className="flex h-10 min-h-10 shrink-0 items-center gap-2 border-b border-border/60 bg-background px-3 in-data-[preview-panel-mode=inline]:mb-3 in-data-[preview-panel-mode=inline]:h-7 in-data-[preview-panel-mode=inline]:min-h-7 in-data-[preview-panel-mode=inline]:border-b-transparent" | ||
| 862 | data-surface-subheader | ||
| 863 | > | ||
| 861 | <ScrollArea | 864 | <ScrollArea |
| 862 | ref={breadcrumbRef} | 865 | ref={breadcrumbRef} |
| 863 | hideScrollbars | 866 | hideScrollbars |
apps/web/src/components/preview/PreviewChromeRow.tsx+6-2| ... | @@ -109,7 +109,11 @@ export function PreviewChromeRow({ | ... | @@ -109,7 +109,11 @@ export function PreviewChromeRow({ |
| 109 | 109 | ||
| 110 | return ( | 110 | return ( |
| 111 | <div className="relative"> | 111 | <div className="relative"> |
| 112 | <form onSubmit={submit} className="surface-subheader gap-1 px-2" data-surface-subheader> | 112 | <form |
| 113 | onSubmit={submit} | ||
| 114 | className="flex h-10 min-h-10 shrink-0 items-center gap-1 border-b border-border/60 bg-background px-2 in-data-[preview-panel-mode=inline]:mb-3 in-data-[preview-panel-mode=inline]:h-7 in-data-[preview-panel-mode=inline]:min-h-7 in-data-[preview-panel-mode=inline]:border-b-transparent" | ||
| 115 | data-surface-subheader | ||
| 116 | > | ||
| 113 | <div className="flex items-center gap-0.5" role="group" aria-label="Navigation"> | 117 | <div className="flex items-center gap-0.5" role="group" aria-label="Navigation"> |
| 114 | <Tooltip> | 118 | <Tooltip> |
| 115 | <TooltipTrigger | 119 | <TooltipTrigger |
| ... | @@ -164,7 +168,7 @@ export function PreviewChromeRow({ | ... | @@ -164,7 +168,7 @@ export function PreviewChromeRow({ |
| 164 | </Tooltip> | 168 | </Tooltip> |
| 165 | </div> | 169 | </div> |
| 166 | 170 | ||
| 167 | <InputGroup variant="ghost" className="group/address h-7 flex-1 rounded-md"> | 171 | <InputGroup variant="ghost" className="group/address h-7 flex-1"> |
| 168 | <Tooltip> | 172 | <Tooltip> |
| 169 | <TooltipTrigger | 173 | <TooltipTrigger |
| 170 | render={ | 174 | render={ |
apps/web/src/components/pullRequest/PullRequestCodeTab.tsx+12-12| ... | @@ -668,13 +668,12 @@ export function PullRequestCodeTab({ | ... | @@ -668,13 +668,12 @@ export function PullRequestCodeTab({ |
| 668 | // chevron follows it rather than recomputing the default here. | 668 | // chevron follows it rather than recomputing the default here. |
| 669 | const collapsed = item.collapsed === true; | 669 | const collapsed = item.collapsed === true; |
| 670 | return ( | 670 | return ( |
| 671 | <button | 671 | <Button |
| 672 | type="button" | 672 | size="icon-micro" |
| 673 | variant="ghost-muted" | ||
| 673 | aria-expanded={!collapsed} | 674 | aria-expanded={!collapsed} |
| 674 | aria-label={collapsed ? "Expand diff" : "Collapse diff"} | 675 | aria-label={collapsed ? "Expand diff" : "Collapse diff"} |
| 675 | className={cn( | 676 | className="mr-1 rounded hover:bg-transparent" |
| 676 | "mr-1 inline-flex size-5 items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground", | ||
| 677 | )} | ||
| 678 | onClick={(event) => { | 677 | onClick={(event) => { |
| 679 | event.stopPropagation(); | 678 | event.stopPropagation(); |
| 680 | toggleFile(item.id); | 679 | toggleFile(item.id); |
| ... | @@ -685,7 +684,7 @@ export function PullRequestCodeTab({ | ... | @@ -685,7 +684,7 @@ export function PullRequestCodeTab({ |
| 685 | ) : ( | 684 | ) : ( |
| 686 | <ChevronDownIcon className="size-4" /> | 685 | <ChevronDownIcon className="size-4" /> |
| 687 | )} | 686 | )} |
| 688 | </button> | 687 | </Button> |
| 689 | ); | 688 | ); |
| 690 | }, | 689 | }, |
| 691 | [toggleFile], | 690 | [toggleFile], |
| ... | @@ -899,7 +898,7 @@ export function PullRequestCodeTab({ | ... | @@ -899,7 +898,7 @@ export function PullRequestCodeTab({ |
| 899 | review.verdicts.length === 0 ? null : ( | 898 | review.verdicts.length === 0 ? null : ( |
| 900 | <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10"> | 899 | <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10"> |
| 901 | {reviewOpen ? ( | 900 | {reviewOpen ? ( |
| 902 | <div className="chat-composer-glass pointer-events-auto absolute inset-x-3 bottom-3 rounded-xl border border-border/60 shadow-lg"> | 901 | <div className="surface-glass pointer-events-auto absolute inset-x-3 bottom-3 rounded-xl border border-border/60 shadow-lg"> |
| 903 | <Button | 902 | <Button |
| 904 | type="button" | 903 | type="button" |
| 905 | size="icon-sm" | 904 | size="icon-sm" |
| ... | @@ -923,10 +922,11 @@ export function PullRequestCodeTab({ | ... | @@ -923,10 +922,11 @@ export function PullRequestCodeTab({ |
| 923 | ) : ( | 922 | ) : ( |
| 924 | // Bottom-right, clear of the vertical scrollbar the diff view keeps to its own right | 923 | // Bottom-right, clear of the vertical scrollbar the diff view keeps to its own right |
| 925 | // edge. | 924 | // edge. |
| 926 | <button | 925 | <Button |
| 927 | type="button" | 926 | className="pointer-events-auto absolute right-4 bottom-3 rounded-full shadow-lg" |
| 928 | className="chat-composer-glass pointer-events-auto absolute bottom-3 right-4 flex items-center gap-1.5 rounded-full border border-border/60 px-3 py-1.5 text-xs font-medium shadow-lg" | ||
| 929 | onClick={() => setReviewOpen(true)} | 927 | onClick={() => setReviewOpen(true)} |
| 928 | size="compact" | ||
| 929 | variant="glass" | ||
| 930 | > | 930 | > |
| 931 | <MessageSquareIcon className="size-3.5" /> | 931 | <MessageSquareIcon className="size-3.5" /> |
| 932 | Review | 932 | Review |
| ... | @@ -935,7 +935,7 @@ export function PullRequestCodeTab({ | ... | @@ -935,7 +935,7 @@ export function PullRequestCodeTab({ |
| 935 | {pendingComments.length} | 935 | {pendingComments.length} |
| 936 | </span> | 936 | </span> |
| 937 | ) : null} | 937 | ) : null} |
| 938 | </button> | 938 | </Button> |
| 939 | )} | 939 | )} |
| 940 | </div> | 940 | </div> |
| 941 | ); | 941 | ); |
| ... | @@ -959,7 +959,7 @@ export function PullRequestCodeTab({ | ... | @@ -959,7 +959,7 @@ export function PullRequestCodeTab({ |
| 959 | * diff API offers it. | 959 | * diff API offers it. |
| 960 | */ | 960 | */ |
| 961 | const toolbar = ( | 961 | const toolbar = ( |
| 962 | <div className="surface-subheader justify-between gap-2 px-4 text-xs text-muted-foreground"> | 962 | <div className="flex h-10 min-h-10 shrink-0 items-center justify-between gap-2 border-b border-border/60 bg-background px-4 text-xs text-muted-foreground"> |
| 963 | <div className="flex min-w-0 flex-1 items-center gap-3"> | 963 | <div className="flex min-w-0 flex-1 items-center gap-3"> |
| 964 | {/* A host that reports no commits has nothing to scope by, and a dropdown whose only | 964 | {/* A host that reports no commits has nothing to scope by, and a dropdown whose only |
| 965 | entry is the scope already showing is a control that does nothing. */} | 965 | entry is the scope already showing is a control that does nothing. */} |
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+8-2| ... | @@ -1137,8 +1137,14 @@ export function PullRequestDetailPanel({ | ... | @@ -1137,8 +1137,14 @@ export function PullRequestDetailPanel({ |
| 1137 | <> | 1137 | <> |
| 1138 | <Menu> | 1138 | <Menu> |
| 1139 | <MenuTrigger | 1139 | <MenuTrigger |
| 1140 | className="inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground" | 1140 | render={ |
| 1141 | aria-label="More pull request actions" | 1141 | <Button |
| 1142 | aria-label="More pull request actions" | ||
| 1143 | className="size-6" | ||
| 1144 | size="icon-xs" | ||
| 1145 | variant="ghost-muted" | ||
| 1146 | /> | ||
| 1147 | } | ||
| 1142 | > | 1148 | > |
| 1143 | <MoreHorizontalIcon className="size-4" /> | 1149 | <MoreHorizontalIcon className="size-4" /> |
| 1144 | </MenuTrigger> | 1150 | </MenuTrigger> |
apps/web/src/components/pullRequest/PullRequestListFilters.tsx+8-18| ... | @@ -24,6 +24,7 @@ import type { ElementType } from "react"; | ... | @@ -24,6 +24,7 @@ import type { ElementType } from "react"; |
| 24 | import { cn } from "~/lib/utils"; | 24 | import { cn } from "~/lib/utils"; |
| 25 | import { getSourceControlPresentationForKind } from "~/sourceControlPresentation"; | 25 | import { getSourceControlPresentationForKind } from "~/sourceControlPresentation"; |
| 26 | import { ProjectFavicon } from "../ProjectFavicon"; | 26 | import { ProjectFavicon } from "../ProjectFavicon"; |
| 27 | import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group"; | ||
| 27 | 28 | ||
| 28 | import { | 29 | import { |
| 29 | Menu, | 30 | Menu, |
| ... | @@ -79,29 +80,18 @@ export function PullRequestSearchInput({ | ... | @@ -79,29 +80,18 @@ export function PullRequestSearchInput({ |
| 79 | onChange: (value: string) => void; | 80 | onChange: (value: string) => void; |
| 80 | }) { | 81 | }) { |
| 81 | return ( | 82 | return ( |
| 82 | <div className="relative min-w-0 flex-1"> | 83 | <InputGroup className="min-w-0 flex-1 **:[input]:h-9 sm:**:[input]:h-8"> |
| 83 | {busy ? ( | 84 | <InputGroupAddon> |
| 84 | <LoaderIcon | 85 | {busy ? <LoaderIcon aria-hidden className="animate-spin" /> : <SearchIcon aria-hidden />} |
| 85 | aria-hidden | 86 | </InputGroupAddon> |
| 86 | className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 animate-spin text-muted-foreground" | 87 | <InputGroupInput |
| 87 | /> | 88 | type="search" |
| 88 | ) : ( | ||
| 89 | <SearchIcon | ||
| 90 | aria-hidden | ||
| 91 | className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" | ||
| 92 | /> | ||
| 93 | )} | ||
| 94 | <input | ||
| 95 | type="text" | ||
| 96 | value={value} | 89 | value={value} |
| 97 | onChange={(event) => onChange(event.currentTarget.value)} | 90 | onChange={(event) => onChange(event.currentTarget.value)} |
| 98 | placeholder="Search pull requests, or label:bug" | 91 | placeholder="Search pull requests, or label:bug" |
| 99 | aria-label="Search pull requests" | 92 | aria-label="Search pull requests" |
| 100 | // Tracks the shared input's height at both widths, so it stays level with the icon | ||
| 101 | // button beside it rather than towering over it on wide screens. | ||
| 102 | className="h-9 w-full rounded-lg border border-input bg-background pr-3 pl-9 text-sm outline-none placeholder:text-muted-foreground/72 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/24 sm:h-8" | ||
| 103 | /> | 93 | /> |
| 104 | </div> | 94 | </InputGroup> |
| 105 | ); | 95 | ); |
| 106 | } | 96 | } |
| 107 | 97 |
apps/web/src/components/pullRequest/PullRequestReviewerPicker.tsx+3-2| ... | @@ -19,6 +19,7 @@ import { useAtomCommand } from "~/state/use-atom-command"; | ... | @@ -19,6 +19,7 @@ import { useAtomCommand } from "~/state/use-atom-command"; |
| 19 | import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; | 19 | import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; |
| 20 | 20 | ||
| 21 | import { Button } from "../ui/button"; | 21 | import { Button } from "../ui/button"; |
| 22 | import { Input } from "../ui/input"; | ||
| 22 | import { Menu, MenuPopup, MenuTrigger } from "../ui/menu"; | 23 | import { Menu, MenuPopup, MenuTrigger } from "../ui/menu"; |
| 23 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | 24 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 24 | import { toastManager } from "../ui/toast"; | 25 | import { toastManager } from "../ui/toast"; |
| ... | @@ -131,13 +132,13 @@ export function PullRequestReviewerPicker({ | ... | @@ -131,13 +132,13 @@ export function PullRequestReviewerPicker({ |
| 131 | /> | 132 | /> |
| 132 | <MenuPopup align="start" side="bottom" className="w-72 p-0"> | 133 | <MenuPopup align="start" side="bottom" className="w-72 p-0"> |
| 133 | <div className="border-b border-border/60 p-2"> | 134 | <div className="border-b border-border/60 p-2"> |
| 134 | <input | 135 | <Input |
| 135 | autoFocus | 136 | autoFocus |
| 136 | value={query} | 137 | value={query} |
| 137 | onChange={(event) => setQuery(event.currentTarget.value)} | 138 | onChange={(event) => setQuery(event.currentTarget.value)} |
| 138 | placeholder="Search people with access" | 139 | placeholder="Search people with access" |
| 139 | aria-label="Search people with access" | 140 | aria-label="Search people with access" |
| 140 | className="h-7 w-full rounded-md border border-input bg-background px-2 text-xs outline-none placeholder:text-muted-foreground/72 focus-visible:border-ring" | 141 | size="compact" |
| 141 | /> | 142 | /> |
| 142 | </div> | 143 | </div> |
| 143 | <div className="max-h-72 overflow-y-auto p-1"> | 144 | <div className="max-h-72 overflow-y-auto p-1"> |
apps/web/src/components/search/ProjectContentSearchDialog.tsx+7-8| ... | @@ -11,6 +11,7 @@ import { useProjectContentSearch } from "~/state/queries"; | ... | @@ -11,6 +11,7 @@ import { useProjectContentSearch } from "~/state/queries"; |
| 11 | import { PierreEntryIcon } from "../chat/PierreEntryIcon"; | 11 | import { PierreEntryIcon } from "../chat/PierreEntryIcon"; |
| 12 | import { CommandPaletteContent } from "../CommandPaletteContent"; | 12 | import { CommandPaletteContent } from "../CommandPaletteContent"; |
| 13 | import { ScrollArea } from "../ui/scroll-area"; | 13 | import { ScrollArea } from "../ui/scroll-area"; |
| 14 | import { Toggle } from "../ui/toggle"; | ||
| 14 | import { HighlightedSearchLine } from "./HighlightedSearchLine"; | 15 | import { HighlightedSearchLine } from "./HighlightedSearchLine"; |
| 15 | 16 | ||
| 16 | interface ProjectContentSearchDialogProps { | 17 | interface ProjectContentSearchDialogProps { |
| ... | @@ -58,19 +59,17 @@ function SearchOptionButton(props: { | ... | @@ -58,19 +59,17 @@ function SearchOptionButton(props: { |
| 58 | readonly children: ReactNode; | 59 | readonly children: ReactNode; |
| 59 | }) { | 60 | }) { |
| 60 | return ( | 61 | return ( |
| 61 | <button | 62 | <Toggle |
| 62 | type="button" | ||
| 63 | aria-label={props.label} | 63 | aria-label={props.label} |
| 64 | aria-pressed={props.active} | 64 | pressed={props.active} |
| 65 | title={props.label} | 65 | title={props.label} |
| 66 | className={cn( | 66 | className="size-8 rounded-[5px] font-mono text-muted-foreground data-pressed:text-foreground sm:size-7" |
| 67 | "flex size-8 shrink-0 items-center justify-center rounded-[5px] font-mono text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground", | 67 | size="compact" |
| 68 | props.active && "bg-accent text-foreground shadow-sm", | 68 | variant="ghost" |
| 69 | )} | ||
| 70 | onClick={props.onClick} | 69 | onClick={props.onClick} |
| 71 | > | 70 | > |
| 72 | {props.children} | 71 | {props.children} |
| 73 | </button> | 72 | </Toggle> |
| 74 | ); | 73 | ); |
| 75 | } | 74 | } |
| 76 | 75 |
apps/web/src/components/settings/DiagnosticsSettings.tsx+12-14| ... | @@ -273,14 +273,14 @@ function TraceIdCell({ traceId }: { traceId: string }) { | ... | @@ -273,14 +273,14 @@ function TraceIdCell({ traceId }: { traceId: string }) { |
| 273 | <Tooltip> | 273 | <Tooltip> |
| 274 | <TooltipTrigger | 274 | <TooltipTrigger |
| 275 | render={ | 275 | render={ |
| 276 | <button | 276 | <Button |
| 277 | type="button" | 277 | size="icon-micro" |
| 278 | className="cursor-pointer inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground" | 278 | variant="ghost-muted" |
| 279 | aria-label={copied ? "Copied trace ID" : "Copy trace ID"} | 279 | aria-label={copied ? "Copied trace ID" : "Copy trace ID"} |
| 280 | onClick={() => copyToClipboard(traceId)} | 280 | onClick={() => copyToClipboard(traceId)} |
| 281 | > | 281 | > |
| 282 | <CopyIcon className="size-3" /> | 282 | <CopyIcon className="size-3" /> |
| 283 | </button> | 283 | </Button> |
| 284 | } | 284 | } |
| 285 | /> | 285 | /> |
| 286 | <TooltipPopup side="top">{copied ? "Copied" : "Copy full trace ID"}</TooltipPopup> | 286 | <TooltipPopup side="top">{copied ? "Copied" : "Copy full trace ID"}</TooltipPopup> |
| ... | @@ -322,14 +322,14 @@ function ProcessNameCell({ | ... | @@ -322,14 +322,14 @@ function ProcessNameCell({ |
| 322 | style={{ paddingLeft: `${Math.min(process.depth, 6) * 10}px` }} | 322 | style={{ paddingLeft: `${Math.min(process.depth, 6) * 10}px` }} |
| 323 | > | 323 | > |
| 324 | {hasChildren ? ( | 324 | {hasChildren ? ( |
| 325 | <button | 325 | <Button |
| 326 | type="button" | 326 | size="icon-micro" |
| 327 | className="cursor-pointer inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground" | 327 | variant="ghost-muted" |
| 328 | aria-label={isExpanded ? `Collapse ${name}` : `Expand ${name}`} | 328 | aria-label={isExpanded ? `Collapse ${name}` : `Expand ${name}`} |
| 329 | onClick={() => onToggle(process.pid)} | 329 | onClick={() => onToggle(process.pid)} |
| 330 | > | 330 | > |
| 331 | <ChevronIcon className="size-3.5" /> | 331 | <ChevronIcon className="size-3.5" /> |
| 332 | </button> | 332 | </Button> |
| 333 | ) : ( | 333 | ) : ( |
| 334 | <span className="size-5 shrink-0" aria-hidden="true" /> | 334 | <span className="size-5 shrink-0" aria-hidden="true" /> |
| 335 | )} | 335 | )} |
| ... | @@ -793,9 +793,8 @@ function DiagnosticsRefreshButton({ | ... | @@ -793,9 +793,8 @@ function DiagnosticsRefreshButton({ |
| 793 | <TooltipTrigger | 793 | <TooltipTrigger |
| 794 | render={ | 794 | render={ |
| 795 | <Button | 795 | <Button |
| 796 | size="icon-xs" | 796 | size="icon-micro" |
| 797 | variant="ghost" | 797 | variant="ghost-muted" |
| 798 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 799 | disabled={isPending} | 798 | disabled={isPending} |
| 800 | onClick={onClick} | 799 | onClick={onClick} |
| 801 | aria-label={label} | 800 | aria-label={label} |
| ... | @@ -1130,9 +1129,8 @@ export function DiagnosticsSettingsPanel() { | ... | @@ -1130,9 +1129,8 @@ export function DiagnosticsSettingsPanel() { |
| 1130 | <TooltipTrigger | 1129 | <TooltipTrigger |
| 1131 | render={ | 1130 | render={ |
| 1132 | <Button | 1131 | <Button |
| 1133 | size="icon-xs" | 1132 | size="icon-micro" |
| 1134 | variant="ghost" | 1133 | variant="ghost-muted" |
| 1135 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 1136 | disabled={!observability?.logsDirectoryPath || isOpeningLogsDirectory} | 1134 | disabled={!observability?.logsDirectoryPath || isOpeningLogsDirectory} |
| 1137 | onClick={openLogsDirectory} | 1135 | onClick={openLogsDirectory} |
| 1138 | aria-label="Open logs folder" | 1136 | aria-label="Open logs folder" |
apps/web/src/components/settings/KeybindingsSettings.tsx+31-67| ... | @@ -125,9 +125,8 @@ function ExpandableHeaderSearch({ | ... | @@ -125,9 +125,8 @@ function ExpandableHeaderSearch({ |
| 125 | render={ | 125 | render={ |
| 126 | <Button | 126 | <Button |
| 127 | type="button" | 127 | type="button" |
| 128 | size="icon-xs" | 128 | size="icon-micro" |
| 129 | variant="ghost" | 129 | variant="ghost-muted" |
| 130 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 131 | onClick={() => onOpenChange(true)} | 130 | onClick={() => onOpenChange(true)} |
| 132 | aria-label="Search keybindings" | 131 | aria-label="Search keybindings" |
| 133 | > | 132 | > |
| ... | @@ -144,10 +143,10 @@ function ExpandableHeaderSearch({ | ... | @@ -144,10 +143,10 @@ function ExpandableHeaderSearch({ |
| 144 | return ( | 143 | return ( |
| 145 | <div className="relative"> | 144 | <div className="relative"> |
| 146 | <SearchIcon className="pointer-events-none absolute top-1/2 left-2 size-3 -translate-y-1/2 text-muted-foreground" /> | 145 | <SearchIcon className="pointer-events-none absolute top-1/2 left-2 size-3 -translate-y-1/2 text-muted-foreground" /> |
| 147 | <input | 146 | <Input |
| 148 | ref={inputRef} | 147 | ref={inputRef} |
| 149 | autoFocus | 148 | autoFocus |
| 150 | type="text" | 149 | type="search" |
| 151 | value={query} | 150 | value={query} |
| 152 | onChange={(event) => onChange(event.currentTarget.value)} | 151 | onChange={(event) => onChange(event.currentTarget.value)} |
| 153 | onBlur={() => { | 152 | onBlur={() => { |
| ... | @@ -162,7 +161,8 @@ function ExpandableHeaderSearch({ | ... | @@ -162,7 +161,8 @@ function ExpandableHeaderSearch({ |
| 162 | }} | 161 | }} |
| 163 | placeholder="Search keybindings" | 162 | placeholder="Search keybindings" |
| 164 | aria-label="Search keybindings" | 163 | aria-label="Search keybindings" |
| 165 | className="h-6 w-44 rounded-md border border-input bg-background pl-7 pr-2 text-[11px] text-foreground outline-none placeholder:text-muted-foreground/72 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/24" | 164 | className="w-44 [&_[data-slot=input]]:pl-7" |
| 165 | size="compact" | ||
| 166 | /> | 166 | /> |
| 167 | </div> | 167 | </div> |
| 168 | ); | 168 | ); |
| ... | @@ -309,10 +309,7 @@ function WhenVariableSelect({ | ... | @@ -309,10 +309,7 @@ function WhenVariableSelect({ |
| 309 | 309 | ||
| 310 | return ( | 310 | return ( |
| 311 | <Select value={value} onValueChange={(nextValue) => nextValue && onChange(nextValue)}> | 311 | <Select value={value} onValueChange={(nextValue) => nextValue && onChange(nextValue)}> |
| 312 | <SelectTrigger | 312 | <SelectTrigger size="compact" className="min-w-0 flex-1 font-mono"> |
| 313 | size="xs" | ||
| 314 | className="h-7 min-h-7 min-w-0 flex-1 rounded-md font-mono text-xs sm:h-7" | ||
| 315 | > | ||
| 316 | <SelectValue placeholder="Condition" className="leading-7" /> | 313 | <SelectValue placeholder="Condition" className="leading-7" /> |
| 317 | {unknownIdentifiers && unknownIdentifiers.length > 0 ? ( | 314 | {unknownIdentifiers && unknownIdentifiers.length > 0 ? ( |
| 318 | <UnknownWhenVariableWarning identifiers={unknownIdentifiers} focusable={false} /> | 315 | <UnknownWhenVariableWarning identifiers={unknownIdentifiers} focusable={false} /> |
| ... | @@ -365,8 +362,8 @@ function WhenExpressionNodeEditor({ | ... | @@ -365,8 +362,8 @@ function WhenExpressionNodeEditor({ |
| 365 | onPressedChange={(pressed) => onChange(setConditionNegated(node, pressed))} | 362 | onPressedChange={(pressed) => onChange(setConditionNegated(node, pressed))} |
| 366 | aria-label={`Negate ${condition.identifier}`} | 363 | aria-label={`Negate ${condition.identifier}`} |
| 367 | variant="outline" | 364 | variant="outline" |
| 368 | size="xs" | 365 | size="compact" |
| 369 | className="h-7 min-w-10 px-2 text-[11px] sm:h-7" | 366 | className="min-w-10" |
| 370 | > | 367 | > |
| 371 | Not | 368 | Not |
| 372 | </Toggle> | 369 | </Toggle> |
| ... | @@ -381,7 +378,7 @@ function WhenExpressionNodeEditor({ | ... | @@ -381,7 +378,7 @@ function WhenExpressionNodeEditor({ |
| 381 | type="button" | 378 | type="button" |
| 382 | variant="ghost" | 379 | variant="ghost" |
| 383 | size="icon-sm" | 380 | size="icon-sm" |
| 384 | className="size-7 sm:size-7" | 381 | className="size-7" |
| 385 | aria-label="Remove condition" | 382 | aria-label="Remove condition" |
| 386 | onClick={onRemove} | 383 | onClick={onRemove} |
| 387 | > | 384 | > |
| ... | @@ -406,8 +403,8 @@ function WhenExpressionNodeEditor({ | ... | @@ -406,8 +403,8 @@ function WhenExpressionNodeEditor({ |
| 406 | onPressedChange={(pressed) => onChange(pressed ? node : node.node)} | 403 | onPressedChange={(pressed) => onChange(pressed ? node : node.node)} |
| 407 | aria-label="Negate group" | 404 | aria-label="Negate group" |
| 408 | variant="outline" | 405 | variant="outline" |
| 409 | size="xs" | 406 | size="compact" |
| 410 | className="h-7 min-w-10 px-2 text-[11px] sm:h-7" | 407 | className="min-w-10" |
| 411 | > | 408 | > |
| 412 | Not | 409 | Not |
| 413 | </Toggle> | 410 | </Toggle> |
| ... | @@ -416,7 +413,7 @@ function WhenExpressionNodeEditor({ | ... | @@ -416,7 +413,7 @@ function WhenExpressionNodeEditor({ |
| 416 | type="button" | 413 | type="button" |
| 417 | variant="ghost" | 414 | variant="ghost" |
| 418 | size="icon-sm" | 415 | size="icon-sm" |
| 419 | className="ml-auto size-7 sm:size-7" | 416 | className="ml-auto size-7" |
| 420 | aria-label="Remove negated group" | 417 | aria-label="Remove negated group" |
| 421 | onClick={onRemove} | 418 | onClick={onRemove} |
| 422 | > | 419 | > |
| ... | @@ -509,7 +506,7 @@ function WhenExpressionNodeEditor({ | ... | @@ -509,7 +506,7 @@ function WhenExpressionNodeEditor({ |
| 509 | > | 506 | > |
| 510 | <div className="flex flex-wrap items-center gap-2"> | 507 | <div className="flex flex-wrap items-center gap-2"> |
| 511 | <Select value={operator} onValueChange={(value) => setOperator(value as BooleanOperator)}> | 508 | <Select value={operator} onValueChange={(value) => setOperator(value as BooleanOperator)}> |
| 512 | <SelectTrigger size="xs" className="h-7 min-h-7 w-24 rounded-md text-xs sm:h-7"> | 509 | <SelectTrigger size="compact" className="w-24"> |
| 513 | <SelectValue /> | 510 | <SelectValue /> |
| 514 | </SelectTrigger> | 511 | </SelectTrigger> |
| 515 | <SelectContent | 512 | <SelectContent |
| ... | @@ -526,17 +523,11 @@ function WhenExpressionNodeEditor({ | ... | @@ -526,17 +523,11 @@ function WhenExpressionNodeEditor({ |
| 526 | </SelectItem> | 523 | </SelectItem> |
| 527 | </SelectContent> | 524 | </SelectContent> |
| 528 | </Select> | 525 | </Select> |
| 529 | <Button | 526 | <Button type="button" variant="outline" size="compact" onClick={addCondition}> |
| 530 | type="button" | ||
| 531 | variant="outline" | ||
| 532 | size="xs" | ||
| 533 | className="h-7 sm:h-7" | ||
| 534 | onClick={addCondition} | ||
| 535 | > | ||
| 536 | <PlusIcon className="size-3.5" /> | 527 | <PlusIcon className="size-3.5" /> |
| 537 | Condition | 528 | Condition |
| 538 | </Button> | 529 | </Button> |
| 539 | <Button type="button" variant="outline" size="xs" className="h-7 sm:h-7" onClick={addGroup}> | 530 | <Button type="button" variant="outline" size="compact" onClick={addGroup}> |
| 540 | <PlusIcon className="size-3.5" /> | 531 | <PlusIcon className="size-3.5" /> |
| 541 | Group | 532 | Group |
| 542 | </Button> | 533 | </Button> |
| ... | @@ -545,7 +536,7 @@ function WhenExpressionNodeEditor({ | ... | @@ -545,7 +536,7 @@ function WhenExpressionNodeEditor({ |
| 545 | type="button" | 536 | type="button" |
| 546 | variant="ghost" | 537 | variant="ghost" |
| 547 | size="icon-sm" | 538 | size="icon-sm" |
| 548 | className="ml-auto size-7 sm:size-7" | 539 | className="ml-auto size-7" |
| 549 | aria-label="Remove group" | 540 | aria-label="Remove group" |
| 550 | onClick={onRemove} | 541 | onClick={onRemove} |
| 551 | > | 542 | > |
| ... | @@ -640,23 +631,11 @@ function WhenExpressionBuilder({ | ... | @@ -640,23 +631,11 @@ function WhenExpressionBuilder({ |
| 640 | <div className="text-sm font-medium text-foreground">When</div> | 631 | <div className="text-sm font-medium text-foreground">When</div> |
| 641 | </div> | 632 | </div> |
| 642 | <div className="flex shrink-0 items-center gap-2"> | 633 | <div className="flex shrink-0 items-center gap-2"> |
| 643 | <Button | 634 | <Button type="button" variant="outline" size="compact" onClick={addRootCondition}> |
| 644 | type="button" | ||
| 645 | variant="outline" | ||
| 646 | size="xs" | ||
| 647 | className="h-7 sm:h-7" | ||
| 648 | onClick={addRootCondition} | ||
| 649 | > | ||
| 650 | <PlusIcon className="size-3.5" /> | 635 | <PlusIcon className="size-3.5" /> |
| 651 | Condition | 636 | Condition |
| 652 | </Button> | 637 | </Button> |
| 653 | <Button | 638 | <Button type="button" variant="outline" size="compact" onClick={addRootGroup}> |
| 654 | type="button" | ||
| 655 | variant="outline" | ||
| 656 | size="xs" | ||
| 657 | className="h-7 sm:h-7" | ||
| 658 | onClick={addRootGroup} | ||
| 659 | > | ||
| 660 | <PlusIcon className="size-3.5" /> | 639 | <PlusIcon className="size-3.5" /> |
| 661 | Group | 640 | Group |
| 662 | </Button> | 641 | </Button> |
| ... | @@ -702,17 +681,11 @@ function WhenExpressionBuilder({ | ... | @@ -702,17 +681,11 @@ function WhenExpressionBuilder({ |
| 702 | ) : ( | 681 | ) : ( |
| 703 | <div className="rounded-md border border-dashed border-border/80 bg-muted/15 p-3"> | 682 | <div className="rounded-md border border-dashed border-border/80 bg-muted/15 p-3"> |
| 704 | <div className="flex flex-wrap gap-2"> | 683 | <div className="flex flex-wrap gap-2"> |
| 705 | <Button type="button" size="xs" className="h-7 sm:h-7" onClick={addRootCondition}> | 684 | <Button type="button" size="compact" onClick={addRootCondition}> |
| 706 | <PlusIcon className="size-3.5" /> | 685 | <PlusIcon className="size-3.5" /> |
| 707 | Condition | 686 | Condition |
| 708 | </Button> | 687 | </Button> |
| 709 | <Button | 688 | <Button type="button" variant="outline" size="compact" onClick={addRootGroup}> |
| 710 | type="button" | ||
| 711 | variant="outline" | ||
| 712 | size="xs" | ||
| 713 | className="h-7 sm:h-7" | ||
| 714 | onClick={addRootGroup} | ||
| 715 | > | ||
| 716 | <PlusIcon className="size-3.5" /> | 689 | <PlusIcon className="size-3.5" /> |
| 717 | Group | 690 | Group |
| 718 | </Button> | 691 | </Button> |
| ... | @@ -864,8 +837,7 @@ function KeybindingTableRow({ | ... | @@ -864,8 +837,7 @@ function KeybindingTableRow({ |
| 864 | )} | 837 | )} |
| 865 | {isDirty ? ( | 838 | {isDirty ? ( |
| 866 | <Button | 839 | <Button |
| 867 | size="xs" | 840 | size="compact" |
| 868 | className="h-7 sm:h-7" | ||
| 869 | disabled={isSaving || keyDraft.trim().length === 0 || !isWhenDraftValid} | 841 | disabled={isSaving || keyDraft.trim().length === 0 || !isWhenDraftValid} |
| 870 | onClick={save} | 842 | onClick={save} |
| 871 | > | 843 | > |
| ... | @@ -992,10 +964,7 @@ function NewKeybindingTableRow({ | ... | @@ -992,10 +964,7 @@ function NewKeybindingTableRow({ |
| 992 | value={commandDraft} | 964 | value={commandDraft} |
| 993 | onValueChange={(value) => setCommandDraft(value as KeybindingCommand)} | 965 | onValueChange={(value) => setCommandDraft(value as KeybindingCommand)} |
| 994 | > | 966 | > |
| 995 | <SelectTrigger | 967 | <SelectTrigger size="compact" className="w-full max-w-60"> |
| 996 | size="xs" | ||
| 997 | className="h-7 min-h-7 w-full max-w-60 rounded-md text-xs sm:h-7" | ||
| 998 | > | ||
| 999 | <SelectValue placeholder="Command" /> | 968 | <SelectValue placeholder="Command" /> |
| 1000 | </SelectTrigger> | 969 | </SelectTrigger> |
| 1001 | <SelectContent | 970 | <SelectContent |
| ... | @@ -1017,18 +986,15 @@ function NewKeybindingTableRow({ | ... | @@ -1017,18 +986,15 @@ function NewKeybindingTableRow({ |
| 1017 | aria-label={`Keybinding for ${commandLabelText}`} | 986 | aria-label={`Keybinding for ${commandLabelText}`} |
| 1018 | value={isRecording ? "" : keyDraft} | 987 | value={isRecording ? "" : keyDraft} |
| 1019 | placeholder={isRecording ? "Press shortcut" : "Unassigned"} | 988 | placeholder={isRecording ? "Press shortcut" : "Unassigned"} |
| 1020 | className={cn( | 989 | size="compact" |
| 1021 | "h-7 w-44 rounded-md font-mono text-[12px] sm:h-7", | 990 | className={cn("w-44 font-mono", isRecording && "border-primary/70 bg-primary/5")} |
| 1022 | isRecording && "border-primary/70 bg-primary/5", | ||
| 1023 | )} | ||
| 1024 | onFocus={() => setDraft({ isRecording: true })} | 991 | onFocus={() => setDraft({ isRecording: true })} |
| 1025 | onBlur={() => setDraft({ isRecording: false })} | 992 | onBlur={() => setDraft({ isRecording: false })} |
| 1026 | onChange={(event) => setDraft({ keyDraft: event.currentTarget.value })} | 993 | onChange={(event) => setDraft({ keyDraft: event.currentTarget.value })} |
| 1027 | onKeyDown={captureKeybinding} | 994 | onKeyDown={captureKeybinding} |
| 1028 | /> | 995 | /> |
| 1029 | <Button | 996 | <Button |
| 1030 | size="xs" | 997 | size="compact" |
| 1031 | className="h-7 sm:h-7" | ||
| 1032 | disabled={isSaving || !commandDraft || keyDraft.trim().length === 0 || !isWhenDraftValid} | 998 | disabled={isSaving || !commandDraft || keyDraft.trim().length === 0 || !isWhenDraftValid} |
| 1033 | onClick={save} | 999 | onClick={save} |
| 1034 | > | 1000 | > |
| ... | @@ -1066,7 +1032,7 @@ function NewKeybindingTableRow({ | ... | @@ -1066,7 +1032,7 @@ function NewKeybindingTableRow({ |
| 1066 | type="button" | 1032 | type="button" |
| 1067 | variant="ghost" | 1033 | variant="ghost" |
| 1068 | size="icon-sm" | 1034 | size="icon-sm" |
| 1069 | className="size-7 text-muted-foreground hover:text-foreground sm:size-7" | 1035 | className="size-7 text-muted-foreground hover:text-foreground" |
| 1070 | disabled={isSaving} | 1036 | disabled={isSaving} |
| 1071 | aria-label="Cancel new keybinding" | 1037 | aria-label="Cancel new keybinding" |
| 1072 | onClick={onCancel} | 1038 | onClick={onCancel} |
| ... | @@ -1247,9 +1213,8 @@ export function KeybindingsSettingsPanel() { | ... | @@ -1247,9 +1213,8 @@ export function KeybindingsSettingsPanel() { |
| 1247 | render={ | 1213 | render={ |
| 1248 | <Button | 1214 | <Button |
| 1249 | type="button" | 1215 | type="button" |
| 1250 | size="icon-xs" | 1216 | size="icon-micro" |
| 1251 | variant="ghost" | 1217 | variant="ghost-muted" |
| 1252 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 1253 | onClick={() => setIsAddingBinding(true)} | 1218 | onClick={() => setIsAddingBinding(true)} |
| 1254 | aria-label="Add keybinding" | 1219 | aria-label="Add keybinding" |
| 1255 | > | 1220 | > |
| ... | @@ -1264,9 +1229,8 @@ export function KeybindingsSettingsPanel() { | ... | @@ -1264,9 +1229,8 @@ export function KeybindingsSettingsPanel() { |
| 1264 | render={ | 1229 | render={ |
| 1265 | <Button | 1230 | <Button |
| 1266 | type="button" | 1231 | type="button" |
| 1267 | size="icon-xs" | 1232 | size="icon-micro" |
| 1268 | variant="ghost" | 1233 | variant="ghost-muted" |
| 1269 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 1270 | disabled={!keybindingsConfigPath} | 1234 | disabled={!keybindingsConfigPath} |
| 1271 | onClick={openKeybindingsFile} | 1235 | onClick={openKeybindingsFile} |
| 1272 | aria-label="Open keybindings.json" | 1236 | aria-label="Open keybindings.json" |
apps/web/src/components/settings/ProjectSettingsPanel.tsx+1-1| ... | @@ -177,7 +177,7 @@ export function ProjectSettingsPage({ projectKey }: { projectKey: string }) { | ... | @@ -177,7 +177,7 @@ export function ProjectSettingsPage({ projectKey }: { projectKey: string }) { |
| 177 | {!isElectron && ( | 177 | {!isElectron && ( |
| 178 | <header | 178 | <header |
| 179 | className={cn( | 179 | className={cn( |
| 180 | "workspace-topbar px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | 180 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", |
| 181 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | 181 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 182 | )} | 182 | )} |
| 183 | > | 183 | > |
apps/web/src/components/settings/ProviderInstanceCard.tsx+4-5| ... | @@ -560,9 +560,9 @@ export function ProviderInstanceCard({ | ... | @@ -560,9 +560,9 @@ export function ProviderInstanceCard({ |
| 560 | <TooltipTrigger | 560 | <TooltipTrigger |
| 561 | render={ | 561 | render={ |
| 562 | <Button | 562 | <Button |
| 563 | size="icon-xs" | 563 | size="icon-micro" |
| 564 | variant="ghost" | 564 | variant="ghost" |
| 565 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-destructive" | 565 | className="text-muted-foreground hover:text-destructive" |
| 566 | onClick={onDelete} | 566 | onClick={onDelete} |
| 567 | aria-label={`Delete provider instance ${instanceId}`} | 567 | aria-label={`Delete provider instance ${instanceId}`} |
| 568 | > | 568 | > |
| ... | @@ -708,9 +708,8 @@ export function ProviderInstanceCard({ | ... | @@ -708,9 +708,8 @@ export function ProviderInstanceCard({ |
| 708 | </div> | 708 | </div> |
| 709 | <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end"> | 709 | <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end"> |
| 710 | <Button | 710 | <Button |
| 711 | size="sm" | 711 | size="compact" |
| 712 | variant="ghost" | 712 | variant="ghost-muted" |
| 713 | className="h-7 px-2 text-xs text-muted-foreground hover:text-foreground" | ||
| 714 | onClick={() => onExpandedChange(!isExpanded)} | 713 | onClick={() => onExpandedChange(!isExpanded)} |
| 715 | aria-label={`Toggle ${displayName} details`} | 714 | aria-label={`Toggle ${displayName} details`} |
| 716 | > | 715 | > |
apps/web/src/components/settings/ProviderModelsSection.tsx+13-20| ... | @@ -245,9 +245,9 @@ export function ProviderModelsSection({ | ... | @@ -245,9 +245,9 @@ export function ProviderModelsSection({ |
| 245 | <TooltipTrigger | 245 | <TooltipTrigger |
| 246 | render={ | 246 | render={ |
| 247 | <Button | 247 | <Button |
| 248 | size="icon-xs" | 248 | size="icon-micro" |
| 249 | variant="ghost" | 249 | variant="ghost" |
| 250 | className="size-5 rounded-sm p-0 text-muted-foreground/60 hover:text-muted-foreground" | 250 | className="text-muted-foreground/60 hover:text-muted-foreground" |
| 251 | aria-label={`Details for ${model.name}`} | 251 | aria-label={`Details for ${model.name}`} |
| 252 | /> | 252 | /> |
| 253 | } | 253 | } |
| ... | @@ -282,12 +282,9 @@ export function ProviderModelsSection({ | ... | @@ -282,12 +282,9 @@ export function ProviderModelsSection({ |
| 282 | <TooltipTrigger | 282 | <TooltipTrigger |
| 283 | render={ | 283 | render={ |
| 284 | <Button | 284 | <Button |
| 285 | size="icon-xs" | 285 | size="icon-micro" |
| 286 | variant="ghost" | 286 | variant="ghost-muted" |
| 287 | className={cn( | 287 | className={cn(isFavorite && "text-yellow-500 hover:text-yellow-600")} |
| 288 | "size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground", | ||
| 289 | isFavorite && "text-yellow-500 hover:text-yellow-600", | ||
| 290 | )} | ||
| 291 | onClick={() => handleToggleFavorite(model.slug)} | 288 | onClick={() => handleToggleFavorite(model.slug)} |
| 292 | aria-label={`${isFavorite ? "Remove" : "Add"} ${model.name} ${ | 289 | aria-label={`${isFavorite ? "Remove" : "Add"} ${model.name} ${ |
| 293 | isFavorite ? "from" : "to" | 290 | isFavorite ? "from" : "to" |
| ... | @@ -305,9 +302,8 @@ export function ProviderModelsSection({ | ... | @@ -305,9 +302,8 @@ export function ProviderModelsSection({ |
| 305 | <TooltipTrigger | 302 | <TooltipTrigger |
| 306 | render={ | 303 | render={ |
| 307 | <Button | 304 | <Button |
| 308 | size="icon-xs" | 305 | size="icon-micro" |
| 309 | variant="ghost" | 306 | variant="ghost-muted" |
| 310 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 311 | disabled={!canMoveUp} | 307 | disabled={!canMoveUp} |
| 312 | onClick={() => handleMove(model.slug, -1)} | 308 | onClick={() => handleMove(model.slug, -1)} |
| 313 | aria-label={`Move ${model.name} up`} | 309 | aria-label={`Move ${model.name} up`} |
| ... | @@ -322,9 +318,8 @@ export function ProviderModelsSection({ | ... | @@ -322,9 +318,8 @@ export function ProviderModelsSection({ |
| 322 | <TooltipTrigger | 318 | <TooltipTrigger |
| 323 | render={ | 319 | render={ |
| 324 | <Button | 320 | <Button |
| 325 | size="icon-xs" | 321 | size="icon-micro" |
| 326 | variant="ghost" | 322 | variant="ghost-muted" |
| 327 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 328 | disabled={!canMoveDown} | 323 | disabled={!canMoveDown} |
| 329 | onClick={() => handleMove(model.slug, 1)} | 324 | onClick={() => handleMove(model.slug, 1)} |
| 330 | aria-label={`Move ${model.name} down`} | 325 | aria-label={`Move ${model.name} down`} |
| ... | @@ -340,9 +335,8 @@ export function ProviderModelsSection({ | ... | @@ -340,9 +335,8 @@ export function ProviderModelsSection({ |
| 340 | <TooltipTrigger | 335 | <TooltipTrigger |
| 341 | render={ | 336 | render={ |
| 342 | <Button | 337 | <Button |
| 343 | size="icon-xs" | 338 | size="icon-micro" |
| 344 | variant="ghost" | 339 | variant="ghost-muted" |
| 345 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 346 | onClick={() => handleToggleHidden(model.slug)} | 340 | onClick={() => handleToggleHidden(model.slug)} |
| 347 | aria-label={`${isHidden ? "Show" : "Hide"} ${model.name}`} | 341 | aria-label={`${isHidden ? "Show" : "Hide"} ${model.name}`} |
| 348 | /> | 342 | /> |
| ... | @@ -364,9 +358,8 @@ export function ProviderModelsSection({ | ... | @@ -364,9 +358,8 @@ export function ProviderModelsSection({ |
| 364 | <TooltipTrigger | 358 | <TooltipTrigger |
| 365 | render={ | 359 | render={ |
| 366 | <Button | 360 | <Button |
| 367 | size="icon-xs" | 361 | size="icon-micro" |
| 368 | variant="ghost" | 362 | variant="ghost-muted" |
| 369 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 370 | aria-label={`Remove ${model.slug}`} | 363 | aria-label={`Remove ${model.slug}`} |
| 371 | onClick={() => handleRemove(model.slug)} | 364 | onClick={() => handleRemove(model.slug)} |
| 372 | /> | 365 | /> |
apps/web/src/components/settings/ProviderSettingsPanel.tsx+4-6| ... | @@ -670,9 +670,8 @@ export function EnvironmentProviderSettings({ | ... | @@ -670,9 +670,8 @@ export function EnvironmentProviderSettings({ |
| 670 | <TooltipTrigger | 670 | <TooltipTrigger |
| 671 | render={ | 671 | render={ |
| 672 | <Button | 672 | <Button |
| 673 | size="icon-xs" | 673 | size="icon-micro" |
| 674 | variant="ghost" | 674 | variant="ghost-muted" |
| 675 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 676 | onClick={() => setIsAddInstanceDialogOpen(true)} | 675 | onClick={() => setIsAddInstanceDialogOpen(true)} |
| 677 | aria-label="Add provider instance" | 676 | aria-label="Add provider instance" |
| 678 | > | 677 | > |
| ... | @@ -686,9 +685,8 @@ export function EnvironmentProviderSettings({ | ... | @@ -686,9 +685,8 @@ export function EnvironmentProviderSettings({ |
| 686 | <TooltipTrigger | 685 | <TooltipTrigger |
| 687 | render={ | 686 | render={ |
| 688 | <Button | 687 | <Button |
| 689 | size="icon-xs" | 688 | size="icon-micro" |
| 690 | variant="ghost" | 689 | variant="ghost-muted" |
| 691 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 692 | disabled={isRefreshingProviders} | 690 | disabled={isRefreshingProviders} |
| 693 | onClick={() => void refreshProviders()} | 691 | onClick={() => void refreshProviders()} |
| 694 | aria-label="Refresh provider status" | 692 | aria-label="Refresh provider status" |
apps/web/src/components/settings/ResourceTelemetryDiagnostics.tsx+6-8| ... | @@ -486,14 +486,14 @@ function ProcessTreeName({ | ... | @@ -486,14 +486,14 @@ function ProcessTreeName({ |
| 486 | style={{ paddingLeft: `${Math.min(process.depth, 7) * 10}px` }} | 486 | style={{ paddingLeft: `${Math.min(process.depth, 7) * 10}px` }} |
| 487 | > | 487 | > |
| 488 | {hasChildren ? ( | 488 | {hasChildren ? ( |
| 489 | <button | 489 | <Button |
| 490 | type="button" | 490 | size="icon-micro" |
| 491 | className="cursor-pointer inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground" | 491 | variant="ghost-muted" |
| 492 | onClick={() => onToggle(process)} | 492 | onClick={() => onToggle(process)} |
| 493 | aria-label={collapsed ? `Expand ${name}` : `Collapse ${name}`} | 493 | aria-label={collapsed ? `Expand ${name}` : `Collapse ${name}`} |
| 494 | > | 494 | > |
| 495 | <ChevronIcon className="size-3.5" /> | 495 | <ChevronIcon className="size-3.5" /> |
| 496 | </button> | 496 | </Button> |
| 497 | ) : ( | 497 | ) : ( |
| 498 | <span className="size-5" aria-hidden /> | 498 | <span className="size-5" aria-hidden /> |
| 499 | )} | 499 | )} |
| ... | @@ -975,9 +975,8 @@ export function ResourceTelemetryDiagnostics() { | ... | @@ -975,9 +975,8 @@ export function ResourceTelemetryDiagnostics() { |
| 975 | <TooltipTrigger | 975 | <TooltipTrigger |
| 976 | render={ | 976 | render={ |
| 977 | <Button | 977 | <Button |
| 978 | size="icon-xs" | 978 | size="icon-micro" |
| 979 | variant="ghost" | 979 | variant="ghost" |
| 980 | className="size-5 rounded-sm p-0" | ||
| 981 | disabled={telemetry.isPending} | 980 | disabled={telemetry.isPending} |
| 982 | onClick={telemetry.refresh} | 981 | onClick={telemetry.refresh} |
| 983 | aria-label="Refresh resource telemetry" | 982 | aria-label="Refresh resource telemetry" |
| ... | @@ -1227,9 +1226,8 @@ export function ResourceTelemetryDiagnostics() { | ... | @@ -1227,9 +1226,8 @@ export function ResourceTelemetryDiagnostics() { |
| 1227 | <div className="flex items-center gap-2"> | 1226 | <div className="flex items-center gap-2"> |
| 1228 | <HistoryWindowSelector selectedWindowMs={windowMs} onSelect={setWindowMs} /> | 1227 | <HistoryWindowSelector selectedWindowMs={windowMs} onSelect={setWindowMs} /> |
| 1229 | <Button | 1228 | <Button |
| 1230 | size="icon-xs" | 1229 | size="icon-micro" |
| 1231 | variant="ghost" | 1230 | variant="ghost" |
| 1232 | className="size-5 rounded-sm p-0" | ||
| 1233 | disabled={history.isPending} | 1231 | disabled={history.isPending} |
| 1234 | onClick={history.refresh} | 1232 | onClick={history.refresh} |
| 1235 | aria-label="Refresh resource history" | 1233 | aria-label="Refresh resource history" |
apps/web/src/components/settings/SettingsSidebarNav.tsx+2-2| ... | @@ -220,9 +220,9 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { | ... | @@ -220,9 +220,9 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { |
| 220 | {isSearching ? ( | 220 | {isSearching ? ( |
| 221 | <Button | 221 | <Button |
| 222 | type="button" | 222 | type="button" |
| 223 | size="icon-xs" | 223 | size="icon-micro" |
| 224 | variant="ghost" | 224 | variant="ghost" |
| 225 | className="size-5 shrink-0 rounded-sm text-sidebar-muted-foreground hover:bg-sidebar-control-surface hover:text-sidebar-foreground" | 225 | className="shrink-0 text-sidebar-muted-foreground hover:bg-sidebar-control-surface hover:text-sidebar-foreground" |
| 226 | aria-label="Clear settings search" | 226 | aria-label="Clear settings search" |
| 227 | onClick={() => { | 227 | onClick={() => { |
| 228 | clearSearch(); | 228 | clearSearch(); |
apps/web/src/components/settings/SourceControlSettings.tsx+14-31| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { ChevronDownIcon, GitPullRequestIcon, InfoIcon, RefreshCwIcon } from "lucide-react"; | 1 | import { ChevronDownIcon, GitPullRequestIcon, RefreshCwIcon } from "lucide-react"; |
| 2 | import * as Duration from "effect/Duration"; | 2 | import * as Duration from "effect/Duration"; |
| 3 | import * as Option from "effect/Option"; | 3 | import * as Option from "effect/Option"; |
| 4 | import { useState, type ReactNode } from "react"; | 4 | import { useState, type ReactNode } from "react"; |
| ... | @@ -43,6 +43,7 @@ import { | ... | @@ -43,6 +43,7 @@ import { |
| 43 | } from "../ui/number-field"; | 43 | } from "../ui/number-field"; |
| 44 | import { Switch } from "../ui/switch"; | 44 | import { Switch } from "../ui/switch"; |
| 45 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | 45 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 46 | |||
| 46 | import { | 47 | import { |
| 47 | AzureDevOpsIcon, | 48 | AzureDevOpsIcon, |
| 48 | BitbucketIcon, | 49 | BitbucketIcon, |
| ... | @@ -54,7 +55,12 @@ import { | ... | @@ -54,7 +55,12 @@ import { |
| 54 | } from "../Icons"; | 55 | } from "../Icons"; |
| 55 | import { RedactedSensitiveText } from "./RedactedSensitiveText"; | 56 | import { RedactedSensitiveText } from "./RedactedSensitiveText"; |
| 56 | import { SourceControlWritingSettingsSection } from "./SourceControlWritingSettings"; | 57 | import { SourceControlWritingSettingsSection } from "./SourceControlWritingSettings"; |
| 57 | import { SettingResetButton, SettingsPageContainer, SettingsSection } from "./settingsLayout"; | 58 | import { |
| 59 | PolicyTooltip, | ||
| 60 | SettingResetButton, | ||
| 61 | SettingsPageContainer, | ||
| 62 | SettingsSection, | ||
| 63 | } from "./settingsLayout"; | ||
| 58 | import { searchableSetting } from "./settingsSearch"; | 64 | import { searchableSetting } from "./settingsSearch"; |
| 59 | 65 | ||
| 60 | const EMPTY_DISCOVERY_RESULT: SourceControlDiscoveryResult = { | 66 | const EMPTY_DISCOVERY_RESULT: SourceControlDiscoveryResult = { |
| ... | @@ -116,27 +122,6 @@ function backgroundActivityOverrideSettings( | ... | @@ -116,27 +122,6 @@ function backgroundActivityOverrideSettings( |
| 116 | }; | 122 | }; |
| 117 | } | 123 | } |
| 118 | 124 | ||
| 119 | function BackgroundPolicyTooltip({ children }: { readonly children: string }) { | ||
| 120 | return ( | ||
| 121 | <Tooltip> | ||
| 122 | <TooltipTrigger | ||
| 123 | render={ | ||
| 124 | <button | ||
| 125 | type="button" | ||
| 126 | className="inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground" | ||
| 127 | aria-label="Background policy details" | ||
| 128 | > | ||
| 129 | <InfoIcon className="size-3.5" /> | ||
| 130 | </button> | ||
| 131 | } | ||
| 132 | /> | ||
| 133 | <TooltipPopup side="top" className="max-w-72"> | ||
| 134 | {children} | ||
| 135 | </TooltipPopup> | ||
| 136 | </Tooltip> | ||
| 137 | ); | ||
| 138 | } | ||
| 139 | |||
| 140 | function optionLabel(value: Option.Option<string>): string | null { | 125 | function optionLabel(value: Option.Option<string>): string | null { |
| 141 | return Option.getOrNull(value); | 126 | return Option.getOrNull(value); |
| 142 | } | 127 | } |
| ... | @@ -316,9 +301,8 @@ function DiscoveryItemRow({ | ... | @@ -316,9 +301,8 @@ function DiscoveryItemRow({ |
| 316 | <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end"> | 301 | <div className="flex w-full shrink-0 items-center gap-2 sm:w-auto sm:justify-end"> |
| 317 | {hasDetails ? ( | 302 | {hasDetails ? ( |
| 318 | <Button | 303 | <Button |
| 319 | size="sm" | 304 | size="compact" |
| 320 | variant="ghost" | 305 | variant="ghost-muted" |
| 321 | className="h-7 px-2 text-xs text-muted-foreground hover:text-foreground" | ||
| 322 | onClick={() => setIsExpanded((open) => !open)} | 306 | onClick={() => setIsExpanded((open) => !open)} |
| 323 | aria-expanded={isExpanded} | 307 | aria-expanded={isExpanded} |
| 324 | aria-label={`Toggle ${item.label} details`} | 308 | aria-label={`Toggle ${item.label} details`} |
| ... | @@ -367,11 +351,11 @@ function GitFetchIntervalSettings() { | ... | @@ -367,11 +351,11 @@ function GitFetchIntervalSettings() { |
| 367 | <div className="min-w-0 space-y-1"> | 351 | <div className="min-w-0 space-y-1"> |
| 368 | <div className="flex min-w-0 items-center gap-1"> | 352 | <div className="flex min-w-0 items-center gap-1"> |
| 369 | <span className="text-xs font-medium text-foreground">Fetch interval</span> | 353 | <span className="text-xs font-medium text-foreground">Fetch interval</span> |
| 370 | <BackgroundPolicyTooltip> | 354 | <PolicyTooltip> |
| 371 | This interval is configured for Git only. The shared Background activity policy still | 355 | This interval is configured for Git only. The shared Background activity policy still |
| 372 | decides whether Git refreshes may run when the timer fires. Custom intervals appear as | 356 | decides whether Git refreshes may run when the timer fires. Custom intervals appear as |
| 373 | Advanced in General settings. | 357 | Advanced in General settings. |
| 374 | </BackgroundPolicyTooltip> | 358 | </PolicyTooltip> |
| 375 | <span | 359 | <span |
| 376 | className={cn( | 360 | className={cn( |
| 377 | "inline-flex size-5 shrink-0 items-center justify-center transition-opacity", | 361 | "inline-flex size-5 shrink-0 items-center justify-center transition-opacity", |
| ... | @@ -537,9 +521,8 @@ export function SourceControlSettingsPanel() { | ... | @@ -537,9 +521,8 @@ export function SourceControlSettingsPanel() { |
| 537 | <TooltipTrigger | 521 | <TooltipTrigger |
| 538 | render={ | 522 | render={ |
| 539 | <Button | 523 | <Button |
| 540 | size="icon-xs" | 524 | size="icon-micro" |
| 541 | variant="ghost" | 525 | variant="ghost-muted" |
| 542 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 543 | onClick={handleScan} | 526 | onClick={handleScan} |
| 544 | disabled={discovery.isPending} | 527 | disabled={discovery.isPending} |
| 545 | aria-label="Rescan server environment" | 528 | aria-label="Rescan server environment" |
apps/web/src/components/settings/ThemeImportDialog.tsx+4-4| ... | @@ -79,13 +79,13 @@ function highlightJson(value: string): string { | ... | @@ -79,13 +79,13 @@ function highlightJson(value: string): string { |
| 79 | const index = match.index ?? 0; | 79 | const index = match.index ?? 0; |
| 80 | highlighted += escapeJsonHtml(value.slice(cursor, index)); | 80 | highlighted += escapeJsonHtml(value.slice(cursor, index)); |
| 81 | 81 | ||
| 82 | let tokenClass = "theme-json-number"; | 82 | let tokenClass = "text-[var(--app-theme-secondary-foreground,var(--color-amber-600))]"; |
| 83 | if (token.startsWith('"')) { | 83 | if (token.startsWith('"')) { |
| 84 | tokenClass = /^\s*:/.test(value.slice(index + token.length)) | 84 | tokenClass = /^\s*:/.test(value.slice(index + token.length)) |
| 85 | ? "theme-json-key" | 85 | ? "text-[var(--app-theme-accent,var(--color-blue-600))]" |
| 86 | : "theme-json-string"; | 86 | : "text-[var(--app-theme-message-action,var(--color-emerald-600))]"; |
| 87 | } else if (token === "true" || token === "false" || token === "null") { | 87 | } else if (token === "true" || token === "false" || token === "null") { |
| 88 | tokenClass = "theme-json-constant"; | 88 | tokenClass = "text-[var(--app-theme-accent-surface-foreground,var(--color-violet-600))]"; |
| 89 | } | 89 | } |
| 90 | highlighted += `<span class="${tokenClass}">${escapeJsonHtml(token)}</span>`; | 90 | highlighted += `<span class="${tokenClass}">${escapeJsonHtml(token)}</span>`; |
| 91 | cursor = index + token.length; | 91 | cursor = index + token.length; |
apps/web/src/components/settings/ThemeSearchSection.tsx+17-13| ... | @@ -30,7 +30,7 @@ import { | ... | @@ -30,7 +30,7 @@ import { |
| 30 | AlertDialogTitle, | 30 | AlertDialogTitle, |
| 31 | } from "../ui/alert-dialog"; | 31 | } from "../ui/alert-dialog"; |
| 32 | import { Button } from "../ui/button"; | 32 | import { Button } from "../ui/button"; |
| 33 | import { Input } from "../ui/input"; | 33 | import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group"; |
| 34 | import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; | 34 | import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; |
| 35 | import { Spinner } from "../ui/spinner"; | 35 | import { Spinner } from "../ui/spinner"; |
| 36 | 36 | ||
| ... | @@ -202,20 +202,24 @@ export function ThemeSearchSection({ | ... | @@ -202,20 +202,24 @@ export function ThemeSearchSection({ |
| 202 | Find open-source themes from Open VSX. | 202 | Find open-source themes from Open VSX. |
| 203 | </p> | 203 | </p> |
| 204 | </div> | 204 | </div> |
| 205 | <form className="relative flex gap-2" onSubmit={handleSearch}> | 205 | <form className="flex gap-2" onSubmit={handleSearch}> |
| 206 | <SearchIcon className="pointer-events-none absolute left-3 top-1/2 z-10 size-4 -translate-y-1/2 text-muted-foreground" /> | 206 | <InputGroup> |
| 207 | <Input | 207 | <InputGroupAddon> |
| 208 | aria-label="Search Open VSX themes" | 208 | <SearchIcon /> |
| 209 | className="rounded-xl [&_input]:pl-9" | 209 | </InputGroupAddon> |
| 210 | autoFocus | 210 | <InputGroupInput |
| 211 | onChange={(event) => setQuery(event.currentTarget.value)} | 211 | aria-label="Search Open VSX themes" |
| 212 | placeholder="try dracula, nord, catppuccin..." | 212 | autoFocus |
| 213 | size="lg" | 213 | onChange={(event) => setQuery(event.currentTarget.value)} |
| 214 | type="search" | 214 | placeholder="try dracula, nord, catppuccin..." |
| 215 | value={query} | 215 | size="lg" |
| 216 | /> | 216 | type="search" |
| 217 | value={query} | ||
| 218 | /> | ||
| 219 | </InputGroup> | ||
| 217 | <Button | 220 | <Button |
| 218 | disabled={!query.trim() || isSearching || installingId !== null} | 221 | disabled={!query.trim() || isSearching || installingId !== null} |
| 222 | size="lg" | ||
| 219 | type="submit" | 223 | type="submit" |
| 220 | variant="outline" | 224 | variant="outline" |
| 221 | > | 225 | > |
apps/web/src/components/settings/settingsLayout.tsx+8-10| ... | @@ -90,13 +90,9 @@ export function PolicyTooltip({ children }: { readonly children: string }) { | ... | @@ -90,13 +90,9 @@ export function PolicyTooltip({ children }: { readonly children: string }) { |
| 90 | <TooltipTrigger | 90 | <TooltipTrigger |
| 91 | delay={200} | 91 | delay={200} |
| 92 | render={ | 92 | render={ |
| 93 | <button | 93 | <Button size="icon-micro" variant="ghost-muted" aria-label="Background policy details"> |
| 94 | type="button" | ||
| 95 | className="inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground" | ||
| 96 | aria-label="Background policy details" | ||
| 97 | > | ||
| 98 | <InfoIcon className="size-3.5" /> | 94 | <InfoIcon className="size-3.5" /> |
| 99 | </button> | 95 | </Button> |
| 100 | } | 96 | } |
| 101 | /> | 97 | /> |
| 102 | <TooltipPopup side="top" className="max-w-72"> | 98 | <TooltipPopup side="top" className="max-w-72"> |
| ... | @@ -216,11 +212,10 @@ export function SettingResetButton({ | ... | @@ -216,11 +212,10 @@ export function SettingResetButton({ |
| 216 | <TooltipTrigger | 212 | <TooltipTrigger |
| 217 | render={ | 213 | render={ |
| 218 | <Button | 214 | <Button |
| 219 | size="icon-xs" | 215 | size="icon-micro" |
| 220 | variant="ghost" | 216 | variant="ghost-muted" |
| 221 | aria-label={`Reset ${label} to default`} | 217 | aria-label={`Reset ${label} to default`} |
| 222 | disabled={disabled} | 218 | disabled={disabled} |
| 223 | className="size-5 rounded-sm p-0 text-muted-foreground hover:text-foreground" | ||
| 224 | onClick={(event) => { | 219 | onClick={(event) => { |
| 225 | event.stopPropagation(); | 220 | event.stopPropagation(); |
| 226 | onClick(); | 221 | onClick(); |
| ... | @@ -251,7 +246,10 @@ export function SettingsPageContainer({ | ... | @@ -251,7 +246,10 @@ export function SettingsPageContainer({ |
| 251 | 246 | ||
| 252 | return ( | 247 | return ( |
| 253 | <SettingsSearchTargetProvider targetId={targetId} onTargetHandled={clearTargetHash}> | 248 | <SettingsSearchTargetProvider targetId={targetId} onTargetHandled={clearTargetHash}> |
| 254 | <div className="settings-page-scroll-fade scrollbar-gutter-both flex-1 overflow-y-auto px-4 pt-10 pb-7 sm:px-8 sm:pt-12 sm:pb-10"> | 249 | <div |
| 250 | className="topbar-scroll-fade scrollbar-gutter-both flex-1 overflow-y-auto px-4 pt-10 pb-7 sm:px-8 sm:pt-12 sm:pb-10" | ||
| 251 | data-settings-page-scroll | ||
| 252 | > | ||
| 255 | <div className={cn("mx-auto flex w-full max-w-4xl flex-col gap-12", className)}> | 253 | <div className={cn("mx-auto flex w-full max-w-4xl flex-col gap-12", className)}> |
| 256 | {children} | 254 | {children} |
| 257 | </div> | 255 | </div> |
apps/web/src/components/sidebar/SidebarChrome.tsx+1-1| ... | @@ -83,7 +83,7 @@ function SidebarBrand({ onBackdrop }: { onBackdrop: boolean }) { | ... | @@ -83,7 +83,7 @@ function SidebarBrand({ onBackdrop }: { onBackdrop: boolean }) { |
| 83 | <Link | 83 | <Link |
| 84 | aria-label="Go to threads" | 84 | aria-label="Go to threads" |
| 85 | className={cn( | 85 | className={cn( |
| 86 | "sidebar-brand relative z-10 ml-[var(--workspace-titlebar-content-left)] h-7 w-fit min-w-0 shrink-0 items-center gap-1 overflow-hidden rounded-md outline-hidden ring-ring focus-visible:ring-2", | 86 | "relative z-10 ml-[var(--workspace-titlebar-content-left)] hidden h-7 w-fit min-w-0 shrink-0 items-center gap-1 overflow-hidden rounded-md outline-hidden ring-ring focus-visible:ring-2 md:flex", |
| 87 | onBackdrop ? "text-white" : "text-foreground", | 87 | onBackdrop ? "text-white" : "text-foreground", |
| 88 | )} | 88 | )} |
| 89 | to="/" | 89 | to="/" |
apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx+7-5| ... | @@ -10,6 +10,7 @@ import { | ... | @@ -10,6 +10,7 @@ import { |
| 10 | type ProviderUpdateSidebarPillView, | 10 | type ProviderUpdateSidebarPillView, |
| 11 | } from "../ProviderUpdateLaunchNotification.logic"; | 11 | } from "../ProviderUpdateLaunchNotification.logic"; |
| 12 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | 12 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 13 | import { Button } from "../ui/button"; | ||
| 13 | 14 | ||
| 14 | const PROVIDER_UPDATE_PILL_STYLES = { | 15 | const PROVIDER_UPDATE_PILL_STYLES = { |
| 15 | loading: | 16 | loading: |
| ... | @@ -151,7 +152,7 @@ export function SidebarProviderUpdatePill() { | ... | @@ -151,7 +152,7 @@ export function SidebarProviderUpdatePill() { |
| 151 | <div | 152 | <div |
| 152 | key={displayedView.key} | 153 | key={displayedView.key} |
| 153 | aria-hidden="true" | 154 | aria-hidden="true" |
| 154 | className={`provider-update-pill-progress pointer-events-none absolute inset-y-0 left-0 w-full origin-left border-r border-current/15 shadow-[inset_0_1px_0_rgb(255_255_255_/_0.08)] ${ | 155 | className={`pointer-events-none absolute inset-y-0 left-0 w-full origin-left animate-[provider-update-pill-countdown_var(--provider-update-pill-dismiss-ms)_linear_forwards] border-r border-current/15 shadow-[inset_0_1px_0_rgb(255_255_255_/_0.08)] ${ |
| 155 | PROVIDER_UPDATE_PILL_PROGRESS_STYLES[displayedView.tone] | 156 | PROVIDER_UPDATE_PILL_PROGRESS_STYLES[displayedView.tone] |
| 156 | }`} | 157 | }`} |
| 157 | style={ | 158 | style={ |
| ... | @@ -190,14 +191,15 @@ export function SidebarProviderUpdatePill() { | ... | @@ -190,14 +191,15 @@ export function SidebarProviderUpdatePill() { |
| 190 | <Tooltip> | 191 | <Tooltip> |
| 191 | <TooltipTrigger | 192 | <TooltipTrigger |
| 192 | render={ | 193 | render={ |
| 193 | <button | 194 | <Button |
| 194 | type="button" | 195 | size="icon-micro" |
| 196 | variant="ghost" | ||
| 195 | aria-label="Dismiss provider update notice" | 197 | aria-label="Dismiss provider update notice" |
| 196 | className="relative z-[1] mr-1 inline-flex size-5 items-center justify-center rounded-md opacity-70 transition-opacity hover:opacity-100" | 198 | className="relative z-[1] mr-1 [--control-icon-color:currentColor] rounded-md text-inherit opacity-70 hover:bg-transparent hover:opacity-100" |
| 197 | onClick={() => startExit(displayedView.key, null, displayedView.key)} | 199 | onClick={() => startExit(displayedView.key, null, displayedView.key)} |
| 198 | > | 200 | > |
| 199 | <XIcon className="size-3.5" /> | 201 | <XIcon className="size-3.5" /> |
| 200 | </button> | 202 | </Button> |
| 201 | } | 203 | } |
| 202 | /> | 204 | /> |
| 203 | <TooltipPopup side="top">Dismiss until provider status changes</TooltipPopup> | 205 | <TooltipPopup side="top">Dismiss until provider status changes</TooltipPopup> |
apps/web/src/components/threadSidebarWidth.test.ts+6-13| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off - Regression coverage compares shipped CSS with the sidebar width contract. | 1 | // @effect-diagnostics nodeBuiltinImport:off - Regression coverage compares the sidebar component with its width contract. |
| 2 | import * as NodeFS from "node:fs"; | 2 | import * as NodeFS from "node:fs"; |
| 3 | 3 | ||
| 4 | import { describe, expect, it } from "vite-plus/test"; | 4 | import { describe, expect, it } from "vite-plus/test"; |
| ... | @@ -36,20 +36,13 @@ describe("thread sidebar width", () => { | ... | @@ -36,20 +36,13 @@ describe("thread sidebar width", () => { |
| 36 | }); | 36 | }); |
| 37 | 37 | ||
| 38 | it("shows the desktop wordmark across the sidebar's full legal width range", () => { | 38 | it("shows the desktop wordmark across the sidebar's full legal width range", () => { |
| 39 | const sidebarStyles = NodeFS.readFileSync(new URL("../index.css", import.meta.url), "utf8"); | 39 | const sidebarSource = NodeFS.readFileSync( |
| 40 | const desktopHeaderStyles = sidebarStyles.slice( | 40 | new URL("./sidebar/SidebarChrome.tsx", import.meta.url), |
| 41 | sidebarStyles.indexOf("@media (min-width: 48rem)"), | 41 | "utf8", |
| 42 | sidebarStyles.indexOf("/* Stage-channel sidebar art"), | ||
| 43 | ); | 42 | ); |
| 44 | const stageLabelThreshold = desktopHeaderStyles.match( | ||
| 45 | /@container sidebar-header \(min-width: ([\d.]+)rem\) \{\s*\.sidebar-brand-stage \{\s*display: inline-flex;/, | ||
| 46 | )?.[1]; | ||
| 47 | 43 | ||
| 48 | expect(sidebarStyles).toMatch(/\.sidebar-brand \{\s*display: none;/); | 44 | expect(sidebarSource).toContain("hidden h-7 w-fit min-w-0 shrink-0 items-center gap-1"); |
| 49 | expect(desktopHeaderStyles).toMatch( | 45 | expect(sidebarSource).toContain("md:flex"); |
| 50 | /@media \(min-width: 48rem\) \{\s*\.sidebar-brand \{\s*display: flex;/, | ||
| 51 | ); | ||
| 52 | expect(THREAD_SIDEBAR_MIN_WIDTH).toBe(13 * 16); | 46 | expect(THREAD_SIDEBAR_MIN_WIDTH).toBe(13 * 16); |
| 53 | expect(Number(stageLabelThreshold) * 16).toBeGreaterThan(THREAD_SIDEBAR_MIN_WIDTH); | ||
| 54 | }); | 47 | }); |
| 55 | }); | 48 | }); |
apps/web/src/components/ui/button.test.tsx+15| ... | @@ -28,4 +28,19 @@ describe("button geometry tokens", () => { | ... | @@ -28,4 +28,19 @@ describe("button geometry tokens", () => { |
| 28 | expect(html).toContain("size-7"); | 28 | expect(html).toContain("size-7"); |
| 29 | expect(html).toContain("sm:size-6"); | 29 | expect(html).toContain("sm:size-6"); |
| 30 | }); | 30 | }); |
| 31 | |||
| 32 | it("owns shared compact and micro control geometry", () => { | ||
| 33 | const compact = renderToStaticMarkup(<Button size="compact">Condition</Button>); | ||
| 34 | const micro = renderToStaticMarkup( | ||
| 35 | <Button size="icon-micro" variant="ghost-muted" aria-label="Add action"> | ||
| 36 | <span>+</span> | ||
| 37 | </Button>, | ||
| 38 | ); | ||
| 39 | |||
| 40 | expect(compact).toContain("h-7"); | ||
| 41 | expect(compact).toContain("rounded-md"); | ||
| 42 | expect(micro).toContain("size-5"); | ||
| 43 | expect(micro).toContain("rounded-sm"); | ||
| 44 | expect(micro).toContain("text-muted-foreground"); | ||
| 45 | }); | ||
| 31 | }); | 46 | }); |
apps/web/src/components/ui/button.tsx+8| ... | @@ -16,9 +16,13 @@ const buttonVariants = cva( | ... | @@ -16,9 +16,13 @@ const buttonVariants = cva( |
| 16 | }, | 16 | }, |
| 17 | variants: { | 17 | variants: { |
| 18 | size: { | 18 | size: { |
| 19 | compact: | ||
| 20 | "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3.5", | ||
| 19 | default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8", | 21 | default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8", |
| 20 | icon: "size-9 sm:size-8", | 22 | icon: "size-9 sm:size-8", |
| 21 | "icon-lg": "size-10 sm:size-9", | 23 | "icon-lg": "size-10 sm:size-9", |
| 24 | "icon-micro": | ||
| 25 | "size-5 rounded-sm p-0 before:rounded-[calc(var(--radius-sm)-1px)] [&_svg:not([class*='size-'])]:size-3", | ||
| 22 | "icon-sm": "size-8 sm:size-7", | 26 | "icon-sm": "size-8 sm:size-7", |
| 23 | "icon-xl": | 27 | "icon-xl": |
| 24 | "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5", | 28 | "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5", |
| ... | @@ -38,6 +42,10 @@ const buttonVariants = cva( | ... | @@ -38,6 +42,10 @@ const buttonVariants = cva( |
| 38 | "border-input bg-popover not-dark:bg-clip-padding text-destructive-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]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4", | 42 | "border-input bg-popover not-dark:bg-clip-padding text-destructive-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]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4", |
| 39 | ghost: | 43 | ghost: |
| 40 | "[--control-icon-color:var(--muted-foreground)] border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent", | 44 | "[--control-icon-color:var(--muted-foreground)] border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent", |
| 45 | "ghost-muted": | ||
| 46 | "[--control-icon-color:var(--muted-foreground)] border-transparent text-muted-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [:hover,[data-pressed]]:text-foreground", | ||
| 47 | glass: | ||
| 48 | "surface-glass [--control-icon-color:var(--muted-foreground)] border-border/60 text-foreground shadow-sm [:hover,[data-pressed]]:border-border", | ||
| 41 | link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline", | 49 | link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline", |
| 42 | outline: | 50 | outline: |
| 43 | "[--control-icon-color:var(--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", | 51 | "[--control-icon-color:var(--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", |
apps/web/src/components/ui/combobox.tsx+1-1| ... | @@ -170,7 +170,7 @@ function ComboboxPopup({ | ... | @@ -170,7 +170,7 @@ function ComboboxPopup({ |
| 170 | > | 170 | > |
| 171 | <span | 171 | <span |
| 172 | className={cn( | 172 | className={cn( |
| 173 | "dropdown-glass relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg transition-[scale,opacity]", | 173 | "dropdown-glass relative flex max-h-full min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) rounded-lg shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] transition-[scale,opacity] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]", |
| 174 | className, | 174 | className, |
| 175 | )} | 175 | )} |
| 176 | > | 176 | > |
apps/web/src/components/ui/input-group.tsx+2-2| ... | @@ -8,7 +8,7 @@ import { Input, type InputProps } from "~/components/ui/input"; | ... | @@ -8,7 +8,7 @@ import { Input, type InputProps } from "~/components/ui/input"; |
| 8 | import { Textarea, type TextareaProps } from "~/components/ui/textarea"; | 8 | import { Textarea, type TextareaProps } from "~/components/ui/textarea"; |
| 9 | 9 | ||
| 10 | const inputGroupVariants = cva( | 10 | const inputGroupVariants = cva( |
| 11 | "relative inline-flex w-full min-w-0 items-center rounded-lg border text-base text-foreground ring-ring/24 transition-shadow has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-autofill:bg-foreground/4 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:has-autofill:bg-foreground/8 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--radius-md)-1px)]", | 11 | "relative inline-flex w-full min-w-0 items-center rounded-[var(--control-radius)] border text-base text-foreground ring-ring/24 transition-shadow has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-autofill:bg-foreground/4 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:has-autofill:bg-foreground/8 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--control-radius)-1px)]", |
| 12 | { | 12 | { |
| 13 | defaultVariants: { | 13 | defaultVariants: { |
| 14 | variant: "default", | 14 | variant: "default", |
| ... | @@ -16,7 +16,7 @@ const inputGroupVariants = cva( | ... | @@ -16,7 +16,7 @@ const inputGroupVariants = cva( |
| 16 | variants: { | 16 | variants: { |
| 17 | variant: { | 17 | variant: { |
| 18 | default: | 18 | default: |
| 19 | "border-input bg-background not-dark:bg-clip-padding shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_-1px_--theme(--color-white/6%)]", | 19 | "border-input bg-background not-dark:bg-clip-padding shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--control-radius)-1px)] not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_-1px_--theme(--color-white/6%)]", |
| 20 | ghost: | 20 | ghost: |
| 21 | "border-transparent bg-transparent shadow-none hover:bg-muted/40 has-[input:focus-visible,textarea:focus-visible]:bg-background", | 21 | "border-transparent bg-transparent shadow-none hover:bg-muted/40 has-[input:focus-visible,textarea:focus-visible]:bg-background", |
| 22 | }, | 22 | }, |
apps/web/src/components/ui/input.tsx+5-1| ... | @@ -6,7 +6,7 @@ import type * as React from "react"; | ... | @@ -6,7 +6,7 @@ import type * as React from "react"; |
| 6 | import { cn } from "~/lib/utils"; | 6 | import { cn } from "~/lib/utils"; |
| 7 | 7 | ||
| 8 | type InputProps = Omit<InputPrimitive.Props & React.RefAttributes<HTMLInputElement>, "size"> & { | 8 | type InputProps = Omit<InputPrimitive.Props & React.RefAttributes<HTMLInputElement>, "size"> & { |
| 9 | size?: "sm" | "default" | "lg" | number; | 9 | size?: "sm" | "compact" | "default" | "lg" | number; |
| 10 | unstyled?: boolean; | 10 | unstyled?: boolean; |
| 11 | nativeInput?: boolean; | 11 | nativeInput?: boolean; |
| 12 | }; | 12 | }; |
| ... | @@ -20,6 +20,7 @@ function Input({ | ... | @@ -20,6 +20,7 @@ function Input({ |
| 20 | }: InputProps) { | 20 | }: InputProps) { |
| 21 | const inputClassName = cn( | 21 | const inputClassName = cn( |
| 22 | "h-8.5 w-full min-w-0 rounded-[inherit] px-[calc(--spacing(3)-1px)] leading-8.5 outline-none placeholder:text-placeholder sm:h-7.5 sm:leading-7.5 [transition:background-color_5000000s_ease-in-out_0s]", | 22 | "h-8.5 w-full min-w-0 rounded-[inherit] px-[calc(--spacing(3)-1px)] leading-8.5 outline-none placeholder:text-placeholder sm:h-7.5 sm:leading-7.5 [transition:background-color_5000000s_ease-in-out_0s]", |
| 23 | size === "compact" && "h-7 px-[calc(--spacing(2.5)-1px)] text-xs leading-7 sm:h-7 sm:leading-7", | ||
| 23 | size === "sm" && "h-7.5 px-[calc(--spacing(2.5)-1px)] leading-7.5 sm:h-6.5 sm:leading-6.5", | 24 | size === "sm" && "h-7.5 px-[calc(--spacing(2.5)-1px)] leading-7.5 sm:h-6.5 sm:leading-6.5", |
| 24 | size === "lg" && "h-9.5 leading-9.5 sm:h-8.5 sm:leading-8.5", | 25 | size === "lg" && "h-9.5 leading-9.5 sm:h-8.5 sm:leading-8.5", |
| 25 | props.type === "search" && | 26 | props.type === "search" && |
| ... | @@ -59,6 +60,9 @@ function Input({ | ... | @@ -59,6 +60,9 @@ function Input({ |
| 59 | cn( | 60 | cn( |
| 60 | !unstyled && | 61 | !unstyled && |
| 61 | "relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-autofill:bg-foreground/4 has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]", | 62 | "relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-autofill:bg-foreground/4 has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]", |
| 63 | !unstyled && | ||
| 64 | size === "compact" && | ||
| 65 | "rounded-md before:rounded-[calc(var(--radius-md)-1px)]", | ||
| 62 | className, | 66 | className, |
| 63 | ) || undefined | 67 | ) || undefined |
| 64 | } | 68 | } |
apps/web/src/components/ui/menu.tsx+9-1| ... | @@ -36,6 +36,13 @@ function MenuPopup({ | ... | @@ -36,6 +36,13 @@ function MenuPopup({ |
| 36 | side?: MenuPrimitive.Positioner.Props["side"]; | 36 | side?: MenuPrimitive.Positioner.Props["side"]; |
| 37 | anchor?: MenuPrimitive.Positioner.Props["anchor"]; | 37 | anchor?: MenuPrimitive.Positioner.Props["anchor"]; |
| 38 | }) { | 38 | }) { |
| 39 | const hasExplicitWidthClass = | ||
| 40 | typeof className === "string" && | ||
| 41 | className.split(/\s+/).some((classToken) => { | ||
| 42 | const utility = classToken.split(":").at(-1) ?? classToken; | ||
| 43 | return /^(?:min-|max-)?w-/.test(utility); | ||
| 44 | }); | ||
| 45 | |||
| 39 | return ( | 46 | return ( |
| 40 | <MenuPrimitive.Portal> | 47 | <MenuPrimitive.Portal> |
| 41 | <MenuPrimitive.Positioner | 48 | <MenuPrimitive.Positioner |
| ... | @@ -49,7 +56,8 @@ function MenuPopup({ | ... | @@ -49,7 +56,8 @@ function MenuPopup({ |
| 49 | > | 56 | > |
| 50 | <MenuPrimitive.Popup | 57 | <MenuPrimitive.Popup |
| 51 | className={cn( | 58 | className={cn( |
| 52 | "dropdown-glass relative flex not-[class*='w-']:min-w-32 origin-(--transform-origin) rounded-lg outline-none focus:outline-none", | 59 | "dropdown-glass relative flex origin-(--transform-origin) rounded-lg shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] outline-none focus:outline-none dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]", |
| 60 | !hasExplicitWidthClass && "min-w-32", | ||
| 53 | className, | 61 | className, |
| 54 | )} | 62 | )} |
| 55 | data-slot="menu-popup" | 63 | data-slot="menu-popup" |
apps/web/src/components/ui/popover.tsx+2| ... | @@ -52,6 +52,8 @@ function PopoverPopup({ | ... | @@ -52,6 +52,8 @@ function PopoverPopup({ |
| 52 | "dropdown-glass relative flex h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-lg text-popover-foreground outline-none transition-[width,height,scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] has-data-[slot=calendar]:rounded-xl has-data-[slot=calendar]:before:rounded-[calc(var(--radius-xl)-1px)] data-starting-style:scale-98 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", | 52 | "dropdown-glass relative flex h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) rounded-lg text-popover-foreground outline-none transition-[width,height,scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] has-data-[slot=calendar]:rounded-xl has-data-[slot=calendar]:before:rounded-[calc(var(--radius-xl)-1px)] data-starting-style:scale-98 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", |
| 53 | tooltipStyle && | 53 | tooltipStyle && |
| 54 | "w-fit text-balance rounded-md text-xs shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]", | 54 | "w-fit text-balance rounded-md text-xs shadow-md/5 before:rounded-[calc(var(--radius-md)-1px)]", |
| 55 | !tooltipStyle && | ||
| 56 | "shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]", | ||
| 55 | className, | 57 | className, |
| 56 | )} | 58 | )} |
| 57 | data-slot="popover-popup" | 59 | data-slot="popover-popup" |
apps/web/src/components/ui/scroll-area.tsx+18-1| ... | @@ -4,6 +4,23 @@ import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"; | ... | @@ -4,6 +4,23 @@ import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"; |
| 4 | 4 | ||
| 5 | import { cn } from "~/lib/utils"; | 5 | import { cn } from "~/lib/utils"; |
| 6 | 6 | ||
| 7 | function getVirtualizedScrollFadeClassName({ top, bottom }: { top: boolean; bottom: boolean }) { | ||
| 8 | if (!top && !bottom) return undefined; | ||
| 9 | |||
| 10 | return cn( | ||
| 11 | "virtualized-scroll-fade [--fade-size:1.5rem]", | ||
| 12 | top && | ||
| 13 | bottom && | ||
| 14 | "[--virtualized-scroll-fade-mask:linear-gradient(to_bottom,transparent,black_var(--fade-size),black_calc(100%-var(--fade-size)),transparent)]", | ||
| 15 | top && | ||
| 16 | !bottom && | ||
| 17 | "[--virtualized-scroll-fade-mask:linear-gradient(to_bottom,transparent,black_var(--fade-size))]", | ||
| 18 | !top && | ||
| 19 | bottom && | ||
| 20 | "[--virtualized-scroll-fade-mask:linear-gradient(to_bottom,black_calc(100%-var(--fade-size)),transparent)]", | ||
| 21 | ); | ||
| 22 | } | ||
| 23 | |||
| 7 | function ScrollArea({ | 24 | function ScrollArea({ |
| 8 | className, | 25 | className, |
| 9 | children, | 26 | children, |
| ... | @@ -71,4 +88,4 @@ function ScrollBar({ | ... | @@ -71,4 +88,4 @@ function ScrollBar({ |
| 71 | ); | 88 | ); |
| 72 | } | 89 | } |
| 73 | 90 | ||
| 74 | export { ScrollArea, ScrollBar }; | 91 | export { getVirtualizedScrollFadeClassName, ScrollArea, ScrollBar }; |
apps/web/src/components/ui/select.tsx+3-1| ... | @@ -26,6 +26,8 @@ const selectTriggerVariants = cva( | ... | @@ -26,6 +26,8 @@ const selectTriggerVariants = cva( |
| 26 | "border-transparent text-secondary-label focus-visible:ring-2 focus-visible:ring-ring data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [:hover,[data-pressed]]:text-foreground", | 26 | "border-transparent text-secondary-label focus-visible:ring-2 focus-visible:ring-ring data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [:hover,[data-pressed]]:text-foreground", |
| 27 | }, | 27 | }, |
| 28 | size: { | 28 | size: { |
| 29 | compact: | ||
| 30 | "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3.5", | ||
| 29 | default: "min-h-9 px-[calc(--spacing(3)-1px)] sm:min-h-8", | 31 | default: "min-h-9 px-[calc(--spacing(3)-1px)] sm:min-h-8", |
| 30 | lg: "min-h-10 px-[calc(--spacing(3)-1px)] sm:min-h-9", | 32 | lg: "min-h-10 px-[calc(--spacing(3)-1px)] sm:min-h-9", |
| 31 | sm: "min-h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:min-h-7", | 33 | sm: "min-h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:min-h-7", |
| ... | @@ -150,7 +152,7 @@ function SelectPopup({ | ... | @@ -150,7 +152,7 @@ function SelectPopup({ |
| 150 | </SelectPrimitive.ScrollUpArrow> | 152 | </SelectPrimitive.ScrollUpArrow> |
| 151 | <div | 153 | <div |
| 152 | className={cn( | 154 | className={cn( |
| 153 | "dropdown-glass relative h-full rounded-lg", | 155 | "dropdown-glass relative h-full rounded-lg shadow-[0_16px_40px_-18px_rgb(0_0_0/55%)] dark:shadow-[0_18px_44px_-18px_rgb(0_0_0/80%)]", |
| 154 | matchTriggerWidth && "min-w-(--anchor-width)", | 156 | matchTriggerWidth && "min-w-(--anchor-width)", |
| 155 | popupClassName, | 157 | popupClassName, |
| 156 | )} | 158 | )} |
apps/web/src/components/ui/sidebar.tsx+2| ... | @@ -166,6 +166,8 @@ function SidebarProvider({ | ... | @@ -166,6 +166,8 @@ function SidebarProvider({ |
| 166 | { | 166 | { |
| 167 | "--sidebar-width": SIDEBAR_WIDTH, | 167 | "--sidebar-width": SIDEBAR_WIDTH, |
| 168 | "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, | 168 | "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, |
| 169 | "--workspace-titlebar-content-left": | ||
| 170 | "calc(var(--workspace-controls-left) + var(--workspace-titlebar-control-size) + var(--workspace-titlebar-control-gap))", | ||
| 169 | ...style, | 171 | ...style, |
| 170 | } as React.CSSProperties | 172 | } as React.CSSProperties |
| 171 | } | 173 | } |
apps/web/src/components/ui/skeleton.tsx+1-1| ... | @@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) { | ... | @@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) { |
| 4 | return ( | 4 | return ( |
| 5 | <div | 5 | <div |
| 6 | className={cn( | 6 | className={cn( |
| 7 | "relative overflow-hidden rounded-sm bg-muted [--skeleton-highlight:--alpha(var(--color-white)/64%)] after:absolute after:inset-0 after:animate-skeleton after:bg-[linear-gradient(120deg,transparent_40%,var(--skeleton-highlight),transparent_60%)] dark:[--skeleton-highlight:--alpha(var(--color-white)/4%)]", | 7 | "relative overflow-hidden rounded-sm bg-muted [--skeleton-highlight:--alpha(var(--color-white)/64%)] after:absolute after:inset-0 after:animate-skeleton after:bg-[linear-gradient(120deg,transparent_40%,var(--skeleton-highlight),transparent_60%)] motion-reduce:after:content-none dark:[--skeleton-highlight:--alpha(var(--color-white)/4%)]", |
| 8 | className, | 8 | className, |
| 9 | )} | 9 | )} |
| 10 | data-slot="skeleton" | 10 | data-slot="skeleton" |
apps/web/src/components/ui/toast.tsx+13-14| ... | @@ -26,7 +26,7 @@ import { | ... | @@ -26,7 +26,7 @@ import { |
| 26 | } from "lucide-react"; | 26 | } from "lucide-react"; |
| 27 | 27 | ||
| 28 | import { cn } from "~/lib/utils"; | 28 | import { cn } from "~/lib/utils"; |
| 29 | import { buttonVariants } from "~/components/ui/button"; | 29 | import { Button, buttonVariants } from "~/components/ui/button"; |
| 30 | import { useComposerDraftStore } from "~/composerDraftStore"; | 30 | import { useComposerDraftStore } from "~/composerDraftStore"; |
| 31 | import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; | 31 | import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; |
| 32 | import { resolveThreadRouteTarget } from "~/threadRoutes"; | 32 | import { resolveThreadRouteTarget } from "~/threadRoutes"; |
| ... | @@ -124,11 +124,12 @@ function CopyErrorButton({ text }: { text: string }) { | ... | @@ -124,11 +124,12 @@ function CopyErrorButton({ text }: { text: string }) { |
| 124 | <Tooltip> | 124 | <Tooltip> |
| 125 | <TooltipTrigger | 125 | <TooltipTrigger |
| 126 | render={ | 126 | render={ |
| 127 | <button | 127 | <Button |
| 128 | size="icon-micro" | ||
| 129 | variant="ghost-muted" | ||
| 128 | aria-label={label} | 130 | aria-label={label} |
| 129 | className="inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-md p-0 text-muted-foreground/80 transition-colors hover:text-muted-foreground" | 131 | className="[--control-icon-color:currentColor] rounded-md text-muted-foreground/80 hover:bg-transparent hover:text-muted-foreground" |
| 130 | onClick={() => copyToClipboard(text)} | 132 | onClick={() => copyToClipboard(text)} |
| 131 | type="button" | ||
| 132 | /> | 133 | /> |
| 133 | } | 134 | } |
| 134 | > | 135 | > |
| ... | @@ -381,24 +382,22 @@ function ToastBodyContent({ | ... | @@ -381,24 +382,22 @@ function ToastBodyContent({ |
| 381 | > | 382 | > |
| 382 | {copyErrorText !== null ? <CopyErrorButton text={copyErrorText} /> : null} | 383 | {copyErrorText !== null ? <CopyErrorButton text={copyErrorText} /> : null} |
| 383 | {additionalActions.map(({ id, props: { className, ...props } }) => ( | 384 | {additionalActions.map(({ id, props: { className, ...props } }) => ( |
| 384 | <button | 385 | <Button |
| 385 | {...props} | 386 | {...props} |
| 386 | className={cn( | 387 | className={className} |
| 387 | buttonVariants({ size: "xs", variant: secondaryActionVariant }), | ||
| 388 | className, | ||
| 389 | )} | ||
| 390 | key={id} | 388 | key={id} |
| 389 | size="xs" | ||
| 391 | type="button" | 390 | type="button" |
| 391 | variant={secondaryActionVariant} | ||
| 392 | /> | 392 | /> |
| 393 | ))} | 393 | ))} |
| 394 | {secondaryActionProps ? ( | 394 | {secondaryActionProps ? ( |
| 395 | <button | 395 | <Button |
| 396 | {...secondaryActionRest} | 396 | {...secondaryActionRest} |
| 397 | className={cn( | 397 | className={secondaryActionClassName} |
| 398 | buttonVariants({ size: "xs", variant: secondaryActionVariant }), | 398 | size="xs" |
| 399 | secondaryActionClassName, | ||
| 400 | )} | ||
| 401 | type="button" | 399 | type="button" |
| 400 | variant={secondaryActionVariant} | ||
| 402 | /> | 401 | /> |
| 403 | ) : null} | 402 | ) : null} |
| 404 | {actionProps ? ( | 403 | {actionProps ? ( |
apps/web/src/components/ui/toggle.tsx+2| ... | @@ -14,6 +14,8 @@ const toggleVariants = cva( | ... | @@ -14,6 +14,8 @@ const toggleVariants = cva( |
| 14 | }, | 14 | }, |
| 15 | variants: { | 15 | variants: { |
| 16 | size: { | 16 | size: { |
| 17 | compact: | ||
| 18 | "h-7 min-w-7 rounded-md px-[calc(--spacing(1)-1px)] text-xs before:rounded-[calc(var(--radius-md)-1px)] [&_svg:not([class*='size-'])]:size-3.5", | ||
| 17 | default: "h-9 min-w-9 px-[calc(--spacing(2)-1px)] sm:h-8 sm:min-w-8", | 19 | default: "h-9 min-w-9 px-[calc(--spacing(2)-1px)] sm:h-8 sm:min-w-8", |
| 18 | lg: "h-10 min-w-10 px-[calc(--spacing(2.5)-1px)] sm:h-9 sm:min-w-9", | 20 | lg: "h-10 min-w-10 px-[calc(--spacing(2.5)-1px)] sm:h-9 sm:min-w-9", |
| 19 | sm: "h-8 min-w-8 px-[calc(--spacing(1.5)-1px)] sm:h-7 sm:min-w-7", | 21 | sm: "h-8 min-w-8 px-[calc(--spacing(1.5)-1px)] sm:h-7 sm:min-w-7", |
apps/web/src/components/usage/UsagePage.tsx+6-5| ... | @@ -20,6 +20,7 @@ import { | ... | @@ -20,6 +20,7 @@ import { |
| 20 | makeWindow, | 20 | makeWindow, |
| 21 | } from "@t3tools/shared/usageFormat"; | 21 | } from "@t3tools/shared/usageFormat"; |
| 22 | import { ScrollArea } from "../ui/scroll-area"; | 22 | import { ScrollArea } from "../ui/scroll-area"; |
| 23 | import { Button } from "../ui/button"; | ||
| 23 | import { SidebarInset } from "../ui/sidebar"; | 24 | import { SidebarInset } from "../ui/sidebar"; |
| 24 | import { WorkspaceBreadcrumb, WorkspaceBreadcrumbItem } from "../WorkspaceBreadcrumb"; | 25 | import { WorkspaceBreadcrumb, WorkspaceBreadcrumbItem } from "../WorkspaceBreadcrumb"; |
| 25 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "../../workspaceTitlebar"; | 26 | import { COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS } from "../../workspaceTitlebar"; |
| ... | @@ -106,7 +107,7 @@ export function UsagePage() { | ... | @@ -106,7 +107,7 @@ export function UsagePage() { |
| 106 | {!isElectron && ( | 107 | {!isElectron && ( |
| 107 | <header | 108 | <header |
| 108 | className={cn( | 109 | className={cn( |
| 109 | "workspace-topbar px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | 110 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", |
| 110 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | 111 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 111 | )} | 112 | )} |
| 112 | > | 113 | > |
| ... | @@ -156,14 +157,14 @@ export function UsagePage() { | ... | @@ -156,14 +157,14 @@ export function UsagePage() { |
| 156 | </button> | 157 | </button> |
| 157 | ))} | 158 | ))} |
| 158 | </div> | 159 | </div> |
| 159 | <button | 160 | <Button |
| 160 | type="button" | 161 | size="icon" |
| 162 | variant="outline" | ||
| 161 | onClick={refreshWindow} | 163 | onClick={refreshWindow} |
| 162 | aria-label="Refresh usage" | 164 | aria-label="Refresh usage" |
| 163 | className="cursor-pointer rounded-md border border-border p-2 text-muted-foreground hover:text-foreground" | ||
| 164 | > | 165 | > |
| 165 | <RefreshCwIcon className="size-3.5" /> | 166 | <RefreshCwIcon className="size-3.5" /> |
| 166 | </button> | 167 | </Button> |
| 167 | </div> | 168 | </div> |
| 168 | </div> | 169 | </div> |
| 169 | 170 |
apps/web/src/index.css+352-749| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | @import "tailwindcss"; | 1 | @import "tailwindcss"; |
| 2 | 2 | ||
| 3 | @custom-variant dark (&:is(.dark, .dark *)); | 3 | @custom-variant dark (&:is(.dark, .dark *)); |
| 4 | @custom-variant light (&:not(.dark, .dark *)); | ||
| 4 | /* Window Controls Overlay: active when Electron exposes native titlebar control geometry. */ | 5 | /* Window Controls Overlay: active when Electron exposes native titlebar control geometry. */ |
| 5 | @custom-variant wco (&:is(.wco, .wco *)); | 6 | @custom-variant wco (&:is(.wco, .wco *)); |
| 6 | 7 | ||
| ... | @@ -102,20 +103,13 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -102,20 +103,13 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 102 | --workspace-native-controls-inset: 0px; | 103 | --workspace-native-controls-inset: 0px; |
| 103 | --workspace-titlebar-control-size: 1.75rem; | 104 | --workspace-titlebar-control-size: 1.75rem; |
| 104 | --workspace-titlebar-control-gap: 0.75rem; | 105 | --workspace-titlebar-control-gap: 0.75rem; |
| 105 | } | ||
| 106 | |||
| 107 | .dark { | ||
| 108 | --app-scrollbar-thumb: rgb(255 255 255 / 8%); | ||
| 109 | --app-scrollbar-thumb-hover: rgb(255 255 255 / 12%); | ||
| 110 | --glass-blur: 16px; | ||
| 111 | --glass-saturation: 1.08; | ||
| 112 | } | ||
| 113 | 106 | ||
| 114 | [data-slot="sidebar-wrapper"] { | 107 | @variant dark { |
| 115 | --workspace-titlebar-content-left: calc( | 108 | --app-scrollbar-thumb: rgb(255 255 255 / 8%); |
| 116 | var(--workspace-controls-left) + var(--workspace-titlebar-control-size) + | 109 | --app-scrollbar-thumb-hover: rgb(255 255 255 / 12%); |
| 117 | var(--workspace-titlebar-control-gap) | 110 | --glass-blur: 16px; |
| 118 | ); | 111 | --glass-saturation: 1.08; |
| 112 | } | ||
| 119 | } | 113 | } |
| 120 | 114 | ||
| 121 | .wco { | 115 | .wco { |
| ... | @@ -264,6 +258,179 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -264,6 +258,179 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 264 | } | 258 | } |
| 265 | } | 259 | } |
| 266 | 260 | ||
| 261 | @utility surface-glass { | ||
| 262 | background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); | ||
| 263 | -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 264 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 265 | |||
| 266 | @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { | ||
| 267 | background: var(--background) !important; | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | @utility alert-glass { | ||
| 272 | --alert-glass-tint: transparent; | ||
| 273 | background: | ||
| 274 | linear-gradient( | ||
| 275 | color-mix(in srgb, var(--alert-glass-tint) 4%, transparent), | ||
| 276 | color-mix(in srgb, var(--alert-glass-tint) 4%, transparent) | ||
| 277 | ), | ||
| 278 | color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important; | ||
| 279 | -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 280 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 281 | |||
| 282 | &[data-variant="error"] { | ||
| 283 | --alert-glass-tint: var(--destructive); | ||
| 284 | } | ||
| 285 | |||
| 286 | &[data-variant="info"] { | ||
| 287 | --alert-glass-tint: var(--info); | ||
| 288 | } | ||
| 289 | |||
| 290 | &[data-variant="success"] { | ||
| 291 | --alert-glass-tint: var(--success); | ||
| 292 | } | ||
| 293 | |||
| 294 | &[data-variant="warning"] { | ||
| 295 | --alert-glass-tint: var(--warning); | ||
| 296 | } | ||
| 297 | |||
| 298 | @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { | ||
| 299 | background: var(--background) !important; | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | @utility dialog-glass { | ||
| 304 | background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); | ||
| 305 | -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 306 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 307 | border-color: color-mix(in srgb, var(--foreground) 10%, transparent); | ||
| 308 | box-shadow: 0 24px 64px -24px rgb(0 0 0 / 65%); | ||
| 309 | |||
| 310 | @variant dark { | ||
| 311 | border-color: color-mix(in srgb, var(--color-white) 8%, transparent); | ||
| 312 | box-shadow: | ||
| 313 | inset 0 1px rgb(255 255 255 / 4%), | ||
| 314 | 0 24px 72px -20px rgb(0 0 0 / 90%); | ||
| 315 | } | ||
| 316 | |||
| 317 | @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { | ||
| 318 | background: var(--popover) !important; | ||
| 319 | } | ||
| 320 | } | ||
| 321 | |||
| 322 | @utility dialog-backdrop { | ||
| 323 | background: color-mix(in srgb, var(--background) 60%, transparent); | ||
| 324 | -webkit-backdrop-filter: blur(4px); | ||
| 325 | backdrop-filter: blur(4px); | ||
| 326 | |||
| 327 | @variant dark { | ||
| 328 | background: color-mix(in srgb, var(--background) 64%, transparent); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | @utility dropdown-glass { | ||
| 333 | background: color-mix( | ||
| 334 | in srgb, | ||
| 335 | var(--popover) 18%, | ||
| 336 | color-mix(in srgb, var(--popover) var(--glass-opacity), transparent) | ||
| 337 | ); | ||
| 338 | -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 339 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 340 | border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); | ||
| 341 | |||
| 342 | @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { | ||
| 343 | background: var(--popover) !important; | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | @utility topbar-scroll-fade { | ||
| 348 | --topbar-scroll-fade-height: 2.5rem; | ||
| 349 | -webkit-mask-image: | ||
| 350 | linear-gradient( | ||
| 351 | to bottom, | ||
| 352 | transparent 0%, | ||
| 353 | rgb(0 0 0 / 10%) 10%, | ||
| 354 | rgb(0 0 0 / 30%) 24%, | ||
| 355 | rgb(0 0 0 / 58%) 42%, | ||
| 356 | rgb(0 0 0 / 82%) 62%, | ||
| 357 | rgb(0 0 0 / 96%) 82%, | ||
| 358 | black 100% | ||
| 359 | ), | ||
| 360 | linear-gradient(black, black), linear-gradient(black, black); | ||
| 361 | -webkit-mask-position: top, bottom, right; | ||
| 362 | -webkit-mask-repeat: no-repeat; | ||
| 363 | -webkit-mask-size: | ||
| 364 | 100% var(--topbar-scroll-fade-height), | ||
| 365 | 100% calc(100% - var(--topbar-scroll-fade-height)), | ||
| 366 | var(--app-scrollbar-width) 100%; | ||
| 367 | mask-image: | ||
| 368 | linear-gradient( | ||
| 369 | to bottom, | ||
| 370 | transparent 0%, | ||
| 371 | rgb(0 0 0 / 10%) 10%, | ||
| 372 | rgb(0 0 0 / 30%) 24%, | ||
| 373 | rgb(0 0 0 / 58%) 42%, | ||
| 374 | rgb(0 0 0 / 82%) 62%, | ||
| 375 | rgb(0 0 0 / 96%) 82%, | ||
| 376 | black 100% | ||
| 377 | ), | ||
| 378 | linear-gradient(black, black), linear-gradient(black, black); | ||
| 379 | mask-position: top, bottom, right; | ||
| 380 | mask-repeat: no-repeat; | ||
| 381 | mask-size: | ||
| 382 | 100% var(--topbar-scroll-fade-height), | ||
| 383 | 100% calc(100% - var(--topbar-scroll-fade-height)), | ||
| 384 | var(--app-scrollbar-width) 100%; | ||
| 385 | |||
| 386 | @variant sm { | ||
| 387 | --topbar-scroll-fade-height: 3rem; | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | /* Virtualizers own their native scroll element, so they cannot use ScrollArea's | ||
| 392 | viewport fade. Keep the scrollbar lane opaque while sharing the same fade | ||
| 393 | contract across those lists. */ | ||
| 394 | @utility virtualized-scroll-fade { | ||
| 395 | -webkit-mask-image: var(--virtualized-scroll-fade-mask), linear-gradient(black, black); | ||
| 396 | mask-image: var(--virtualized-scroll-fade-mask), linear-gradient(black, black); | ||
| 397 | -webkit-mask-position: left, right; | ||
| 398 | mask-position: left, right; | ||
| 399 | -webkit-mask-repeat: no-repeat; | ||
| 400 | mask-repeat: no-repeat; | ||
| 401 | -webkit-mask-size: | ||
| 402 | calc(100% - var(--app-scrollbar-width)) 100%, | ||
| 403 | var(--app-scrollbar-width) 100%; | ||
| 404 | mask-size: | ||
| 405 | calc(100% - var(--app-scrollbar-width)) 100%, | ||
| 406 | var(--app-scrollbar-width) 100%; | ||
| 407 | } | ||
| 408 | |||
| 409 | /* Stage-channel art needs a mask and pseudo-element gradient, so keep the | ||
| 410 | behavior composable without tying it to the global components layer. */ | ||
| 411 | @utility sidebar-stage-backdrop { | ||
| 412 | --stage-fade: var(--sidebar-stage-fade, var(--app-chrome-background)); | ||
| 413 | mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%); | ||
| 414 | -webkit-mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%); | ||
| 415 | |||
| 416 | &::after { | ||
| 417 | content: ""; | ||
| 418 | position: absolute; | ||
| 419 | inset: 0; | ||
| 420 | background: linear-gradient( | ||
| 421 | to bottom, | ||
| 422 | transparent 0%, | ||
| 423 | transparent 28%, | ||
| 424 | color-mix(in srgb, var(--stage-fade) 10%, transparent) 40%, | ||
| 425 | color-mix(in srgb, var(--stage-fade) 30%, transparent) 52%, | ||
| 426 | color-mix(in srgb, var(--stage-fade) 58%, transparent) 64%, | ||
| 427 | color-mix(in srgb, var(--stage-fade) 82%, transparent) 75%, | ||
| 428 | color-mix(in srgb, var(--stage-fade) 96%, transparent) 85%, | ||
| 429 | var(--stage-fade) 93% | ||
| 430 | ); | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 267 | @layer base { | 434 | @layer base { |
| 268 | :root { | 435 | :root { |
| 269 | /* Keep the original T3 Code artwork palettes as the defaults. Built-in | 436 | /* Keep the original T3 Code artwork palettes as the defaults. Built-in |
| ... | @@ -292,12 +459,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -292,12 +459,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 292 | --stage-night-glow-highlight: oklch(0.553749 0.176543 271.958); | 459 | --stage-night-glow-highlight: oklch(0.553749 0.176543 271.958); |
| 293 | --stage-night-glow-secondary: oklch(0.345571 0.117466 273.568); | 460 | --stage-night-glow-secondary: oklch(0.345571 0.117466 273.568); |
| 294 | --stage-night-sparkle: oklch(0.880867 0.057747 269.011); | 461 | --stage-night-sparkle: oklch(0.880867 0.057747 269.011); |
| 295 | } | ||
| 296 | 462 | ||
| 297 | .dark { | 463 | @variant dark { |
| 298 | --stage-art-top: oklch(0.581473 0.149124 256.9); | 464 | --stage-art-top: oklch(0.581473 0.149124 256.9); |
| 299 | --stage-art-mid: oklch(0.456509 0.159377 261.945); | 465 | --stage-art-mid: oklch(0.456509 0.159377 261.945); |
| 300 | --stage-art-bottom: oklch(0.291327 0.136578 267.649); | 466 | --stage-art-bottom: oklch(0.291327 0.136578 267.649); |
| 467 | } | ||
| 301 | } | 468 | } |
| 302 | 469 | ||
| 303 | * { | 470 | * { |
| ... | @@ -311,63 +478,16 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -311,63 +478,16 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 311 | ):focus-visible { | 478 | ):focus-visible { |
| 312 | @apply outline-none ring-0; | 479 | @apply outline-none ring-0; |
| 313 | } | 480 | } |
| 314 | html { | 481 | html, |
| 482 | body { | ||
| 315 | background-color: var(--app-chrome-background); | 483 | background-color: var(--app-chrome-background); |
| 316 | } | 484 | } |
| 317 | body { | 485 | body { |
| 318 | @apply text-foreground relative; | 486 | @apply text-foreground relative; |
| 319 | background-color: var(--app-chrome-background); | ||
| 320 | } | 487 | } |
| 321 | } | 488 | } |
| 322 | 489 | ||
| 323 | @layer components { | 490 | @layer components { |
| 324 | .sidebar-brand { | ||
| 325 | display: none; | ||
| 326 | } | ||
| 327 | |||
| 328 | .sidebar-brand-stage { | ||
| 329 | display: none; | ||
| 330 | } | ||
| 331 | |||
| 332 | @media (min-width: 48rem) { | ||
| 333 | .sidebar-brand { | ||
| 334 | display: flex; | ||
| 335 | } | ||
| 336 | |||
| 337 | @container sidebar-header (min-width: 15.75rem) { | ||
| 338 | .sidebar-brand-stage { | ||
| 339 | display: inline-flex; | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | /* Stage-channel sidebar art; ::after ramps to the sidebar bg color and the | ||
| 345 | mask lets the surface grain show through at the boundary. Panels whose | ||
| 346 | background differs from the app chrome (e.g. sidebar v2) override | ||
| 347 | --sidebar-stage-fade so the art fades into their own surface color. */ | ||
| 348 | .sidebar-stage-backdrop { | ||
| 349 | --stage-fade: var(--sidebar-stage-fade, var(--app-chrome-background)); | ||
| 350 | mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%); | ||
| 351 | -webkit-mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%); | ||
| 352 | } | ||
| 353 | |||
| 354 | .sidebar-stage-backdrop::after { | ||
| 355 | content: ""; | ||
| 356 | position: absolute; | ||
| 357 | inset: 0; | ||
| 358 | background: linear-gradient( | ||
| 359 | to bottom, | ||
| 360 | transparent 0%, | ||
| 361 | transparent 28%, | ||
| 362 | color-mix(in srgb, var(--stage-fade) 10%, transparent) 40%, | ||
| 363 | color-mix(in srgb, var(--stage-fade) 30%, transparent) 52%, | ||
| 364 | color-mix(in srgb, var(--stage-fade) 58%, transparent) 64%, | ||
| 365 | color-mix(in srgb, var(--stage-fade) 82%, transparent) 75%, | ||
| 366 | color-mix(in srgb, var(--stage-fade) 96%, transparent) 85%, | ||
| 367 | var(--stage-fade) 93% | ||
| 368 | ); | ||
| 369 | } | ||
| 370 | |||
| 371 | /* Each maintainer palette gives the same line art its own material: rose | 491 | /* Each maintainer palette gives the same line art its own material: rose |
| 372 | vellum, forest drafting paper, marine cyanotype, copper, and violet ink. | 492 | vellum, forest drafting paper, marine cyanotype, copper, and violet ink. |
| 373 | These colors stay deliberately deep at the top edge so the white stage | 493 | These colors stay deliberately deep at the top edge so the white stage |
| ... | @@ -380,16 +500,16 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -380,16 +500,16 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 380 | --stage-art-secondary: oklch(0.763402 0.163836 352.525); | 500 | --stage-art-secondary: oklch(0.763402 0.163836 352.525); |
| 381 | --stage-art-tertiary: oklch(0.70819 0.180285 311.949); | 501 | --stage-art-tertiary: oklch(0.70819 0.180285 311.949); |
| 382 | --stage-art-line: oklch(0.952158 0.034194 336.179); | 502 | --stage-art-line: oklch(0.952158 0.034194 336.179); |
| 383 | } | ||
| 384 | 503 | ||
| 385 | html.dark[data-theme-id="t3-chat"] { | 504 | @variant dark { |
| 386 | --stage-art-top: oklch(0.540689 0.143665 347.587); | 505 | --stage-art-top: oklch(0.540689 0.143665 347.587); |
| 387 | --stage-art-mid: oklch(0.396586 0.126592 347.6); | 506 | --stage-art-mid: oklch(0.396586 0.126592 347.6); |
| 388 | --stage-art-bottom: oklch(0.249959 0.079694 340.523); | 507 | --stage-art-bottom: oklch(0.249959 0.079694 340.523); |
| 389 | --stage-art-highlight: oklch(0.921297 0.051708 343.229); | 508 | --stage-art-highlight: oklch(0.921297 0.051708 343.229); |
| 390 | --stage-art-secondary: oklch(0.667398 0.165674 352.549); | 509 | --stage-art-secondary: oklch(0.667398 0.165674 352.549); |
| 391 | --stage-art-tertiary: oklch(0.609315 0.163722 306.315); | 510 | --stage-art-tertiary: oklch(0.609315 0.163722 306.315); |
| 392 | --stage-art-line: oklch(0.945349 0.036045 341.433); | 511 | --stage-art-line: oklch(0.945349 0.036045 341.433); |
| 512 | } | ||
| 393 | } | 513 | } |
| 394 | 514 | ||
| 395 | html[data-theme-id="grove"] { | 515 | html[data-theme-id="grove"] { |
| ... | @@ -407,23 +527,23 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -407,23 +527,23 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 407 | --stage-night-secondary: oklch(0.665652 0.109731 156.599); | 527 | --stage-night-secondary: oklch(0.665652 0.109731 156.599); |
| 408 | --stage-night-tertiary: oklch(0.698651 0.103024 89.828); | 528 | --stage-night-tertiary: oklch(0.698651 0.103024 89.828); |
| 409 | --stage-night-line: oklch(0.945336 0.041923 157.222); | 529 | --stage-night-line: oklch(0.945336 0.041923 157.222); |
| 410 | } | ||
| 411 | 530 | ||
| 412 | html.dark[data-theme-id="grove"] { | 531 | @variant dark { |
| 413 | --stage-art-top: oklch(0.58719 0.09869 157.426); | 532 | --stage-art-top: oklch(0.58719 0.09869 157.426); |
| 414 | --stage-art-mid: oklch(0.454979 0.079031 159.756); | 533 | --stage-art-mid: oklch(0.454979 0.079031 159.756); |
| 415 | --stage-art-bottom: oklch(0.297856 0.050355 161.167); | 534 | --stage-art-bottom: oklch(0.297856 0.050355 161.167); |
| 416 | --stage-art-highlight: oklch(0.952407 0.053872 158.44); | 535 | --stage-art-highlight: oklch(0.952407 0.053872 158.44); |
| 417 | --stage-art-secondary: oklch(0.732591 0.120606 155.853); | 536 | --stage-art-secondary: oklch(0.732591 0.120606 155.853); |
| 418 | --stage-art-tertiary: oklch(0.716282 0.116547 80.563); | 537 | --stage-art-tertiary: oklch(0.716282 0.116547 80.563); |
| 419 | --stage-art-line: oklch(0.961577 0.035285 157.03); | 538 | --stage-art-line: oklch(0.961577 0.035285 157.03); |
| 420 | --stage-night-top: oklch(0.398632 0.065534 158.601); | 539 | --stage-night-top: oklch(0.398632 0.065534 158.601); |
| 421 | --stage-night-mid: oklch(0.290561 0.049694 160.456); | 540 | --stage-night-mid: oklch(0.290561 0.049694 160.456); |
| 422 | --stage-night-bottom: oklch(0.210147 0.03173 169.818); | 541 | --stage-night-bottom: oklch(0.210147 0.03173 169.818); |
| 423 | --stage-night-highlight: oklch(0.866303 0.057526 156.796); | 542 | --stage-night-highlight: oklch(0.866303 0.057526 156.796); |
| 424 | --stage-night-secondary: oklch(0.586553 0.093722 157.365); | 543 | --stage-night-secondary: oklch(0.586553 0.093722 157.365); |
| 425 | --stage-night-tertiary: oklch(0.6364 0.101769 82.985); | 544 | --stage-night-tertiary: oklch(0.6364 0.101769 82.985); |
| 426 | --stage-night-line: oklch(0.913292 0.035718 156.976); | 545 | --stage-night-line: oklch(0.913292 0.035718 156.976); |
| 546 | } | ||
| 427 | } | 547 | } |
| 428 | 548 | ||
| 429 | html[data-theme-id="ocean"] { | 549 | html[data-theme-id="ocean"] { |
| ... | @@ -434,16 +554,16 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -434,16 +554,16 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 434 | --stage-art-secondary: oklch(0.788391 0.090856 215.684); | 554 | --stage-art-secondary: oklch(0.788391 0.090856 215.684); |
| 435 | --stage-art-tertiary: oklch(0.76441 0.099607 187.893); | 555 | --stage-art-tertiary: oklch(0.76441 0.099607 187.893); |
| 436 | --stage-art-line: oklch(0.976025 0.019647 212.543); | 556 | --stage-art-line: oklch(0.976025 0.019647 212.543); |
| 437 | } | ||
| 438 | 557 | ||
| 439 | html.dark[data-theme-id="ocean"] { | 558 | @variant dark { |
| 440 | --stage-art-top: oklch(0.59663 0.089167 233.427); | 559 | --stage-art-top: oklch(0.59663 0.089167 233.427); |
| 441 | --stage-art-mid: oklch(0.461094 0.084904 243.478); | 560 | --stage-art-mid: oklch(0.461094 0.084904 243.478); |
| 442 | --stage-art-bottom: oklch(0.294818 0.05947 250.526); | 561 | --stage-art-bottom: oklch(0.294818 0.05947 250.526); |
| 443 | --stage-art-highlight: oklch(0.952907 0.032224 221.27); | 562 | --stage-art-highlight: oklch(0.952907 0.032224 221.27); |
| 444 | --stage-art-secondary: oklch(0.732079 0.09296 224.414); | 563 | --stage-art-secondary: oklch(0.732079 0.09296 224.414); |
| 445 | --stage-art-tertiary: oklch(0.720885 0.095495 190.903); | 564 | --stage-art-tertiary: oklch(0.720885 0.095495 190.903); |
| 446 | --stage-art-line: oklch(0.961039 0.027355 219.756); | 565 | --stage-art-line: oklch(0.961039 0.027355 219.756); |
| 566 | } | ||
| 447 | } | 567 | } |
| 448 | 568 | ||
| 449 | html[data-theme-id="ember"] { | 569 | html[data-theme-id="ember"] { |
| ... | @@ -461,23 +581,23 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -461,23 +581,23 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 461 | --stage-night-secondary: oklch(0.641705 0.126508 44.376); | 581 | --stage-night-secondary: oklch(0.641705 0.126508 44.376); |
| 462 | --stage-night-tertiary: oklch(0.538694 0.129931 25.865); | 582 | --stage-night-tertiary: oklch(0.538694 0.129931 25.865); |
| 463 | --stage-night-line: oklch(0.926348 0.046029 58.73); | 583 | --stage-night-line: oklch(0.926348 0.046029 58.73); |
| 464 | } | ||
| 465 | 584 | ||
| 466 | html.dark[data-theme-id="ember"] { | 585 | @variant dark { |
| 467 | --stage-art-top: oklch(0.597533 0.120694 43.455); | 586 | --stage-art-top: oklch(0.597533 0.120694 43.455); |
| 468 | --stage-art-mid: oklch(0.437763 0.101287 34.86); | 587 | --stage-art-mid: oklch(0.437763 0.101287 34.86); |
| 469 | --stage-art-bottom: oklch(0.264269 0.055858 26.548); | 588 | --stage-art-bottom: oklch(0.264269 0.055858 26.548); |
| 470 | --stage-art-highlight: oklch(0.929214 0.042638 55.801); | 589 | --stage-art-highlight: oklch(0.929214 0.042638 55.801); |
| 471 | --stage-art-secondary: oklch(0.705592 0.137369 43.176); | 590 | --stage-art-secondary: oklch(0.705592 0.137369 43.176); |
| 472 | --stage-art-tertiary: oklch(0.629583 0.158322 24.088); | 591 | --stage-art-tertiary: oklch(0.629583 0.158322 24.088); |
| 473 | --stage-art-line: oklch(0.945058 0.033906 58.824); | 592 | --stage-art-line: oklch(0.945058 0.033906 58.824); |
| 474 | --stage-night-top: oklch(0.392352 0.081287 36.444); | 593 | --stage-night-top: oklch(0.392352 0.081287 36.444); |
| 475 | --stage-night-mid: oklch(0.271305 0.056352 31.135); | 594 | --stage-night-mid: oklch(0.271305 0.056352 31.135); |
| 476 | --stage-night-bottom: oklch(0.182126 0.028154 27.774); | 595 | --stage-night-bottom: oklch(0.182126 0.028154 27.774); |
| 477 | --stage-night-highlight: oklch(0.851007 0.061294 53.805); | 596 | --stage-night-highlight: oklch(0.851007 0.061294 53.805); |
| 478 | --stage-night-secondary: oklch(0.560789 0.10645 42.953); | 597 | --stage-night-secondary: oklch(0.560789 0.10645 42.953); |
| 479 | --stage-night-tertiary: oklch(0.476228 0.106656 24.165); | 598 | --stage-night-tertiary: oklch(0.476228 0.106656 24.165); |
| 480 | --stage-night-line: oklch(0.884931 0.046607 56.556); | 599 | --stage-night-line: oklch(0.884931 0.046607 56.556); |
| 600 | } | ||
| 481 | } | 601 | } |
| 482 | 602 | ||
| 483 | html[data-theme-id="iris"] { | 603 | html[data-theme-id="iris"] { |
| ... | @@ -488,16 +608,16 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -488,16 +608,16 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 488 | --stage-art-secondary: oklch(0.745085 0.125892 298.647); | 608 | --stage-art-secondary: oklch(0.745085 0.125892 298.647); |
| 489 | --stage-art-tertiary: oklch(0.73066 0.167815 340.964); | 609 | --stage-art-tertiary: oklch(0.73066 0.167815 340.964); |
| 490 | --stage-art-line: oklch(0.960278 0.024064 306.969); | 610 | --stage-art-line: oklch(0.960278 0.024064 306.969); |
| 491 | } | ||
| 492 | 611 | ||
| 493 | html.dark[data-theme-id="iris"] { | 612 | @variant dark { |
| 494 | --stage-art-top: oklch(0.57297 0.145973 295.185); | 613 | --stage-art-top: oklch(0.57297 0.145973 295.185); |
| 495 | --stage-art-mid: oklch(0.419499 0.13752 292.131); | 614 | --stage-art-mid: oklch(0.419499 0.13752 292.131); |
| 496 | --stage-art-bottom: oklch(0.274235 0.095798 286.608); | 615 | --stage-art-bottom: oklch(0.274235 0.095798 286.608); |
| 497 | --stage-art-highlight: oklch(0.916698 0.047206 300.224); | 616 | --stage-art-highlight: oklch(0.916698 0.047206 300.224); |
| 498 | --stage-art-secondary: oklch(0.670994 0.13095 296.689); | 617 | --stage-art-secondary: oklch(0.670994 0.13095 296.689); |
| 499 | --stage-art-tertiary: oklch(0.679357 0.165376 340.439); | 618 | --stage-art-tertiary: oklch(0.679357 0.165376 340.439); |
| 500 | --stage-art-line: oklch(0.940582 0.032921 299.076); | 619 | --stage-art-line: oklch(0.940582 0.032921 299.076); |
| 620 | } | ||
| 501 | } | 621 | } |
| 502 | 622 | ||
| 503 | :is(html[data-theme-id="t3-chat"], html[data-theme-id="ocean"], html[data-theme-id="iris"]) { | 623 | :is(html[data-theme-id="t3-chat"], html[data-theme-id="ocean"], html[data-theme-id="iris"]) { |
| ... | @@ -538,65 +658,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -538,65 +658,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 538 | --stage-night-sparkle: var(--stage-night-line); | 658 | --stage-night-sparkle: var(--stage-night-line); |
| 539 | } | 659 | } |
| 540 | 660 | ||
| 541 | .workspace-topbar { | ||
| 542 | display: flex; | ||
| 543 | height: var(--workspace-topbar-height); | ||
| 544 | min-height: var(--workspace-topbar-height); | ||
| 545 | flex-shrink: 0; | ||
| 546 | align-items: center; | ||
| 547 | } | ||
| 548 | |||
| 549 | /* Fade rows themselves as they pass beneath the top chrome. A mask remains | ||
| 550 | visible even when the header and timeline share the same background. */ | ||
| 551 | .chat-timeline-scroll-fade, | ||
| 552 | .settings-page-scroll-fade, | ||
| 553 | .pull-requests-scroll-fade { | ||
| 554 | --topbar-scroll-fade-height: 2.5rem; | ||
| 555 | -webkit-mask-image: | ||
| 556 | linear-gradient( | ||
| 557 | to bottom, | ||
| 558 | transparent 0%, | ||
| 559 | rgb(0 0 0 / 10%) 10%, | ||
| 560 | rgb(0 0 0 / 30%) 24%, | ||
| 561 | rgb(0 0 0 / 58%) 42%, | ||
| 562 | rgb(0 0 0 / 82%) 62%, | ||
| 563 | rgb(0 0 0 / 96%) 82%, | ||
| 564 | black 100% | ||
| 565 | ), | ||
| 566 | linear-gradient(black, black), linear-gradient(black, black); | ||
| 567 | -webkit-mask-position: top, bottom, right; | ||
| 568 | -webkit-mask-repeat: no-repeat; | ||
| 569 | -webkit-mask-size: | ||
| 570 | 100% var(--topbar-scroll-fade-height), | ||
| 571 | 100% calc(100% - var(--topbar-scroll-fade-height)), | ||
| 572 | var(--app-scrollbar-width) 100%; | ||
| 573 | mask-image: | ||
| 574 | linear-gradient( | ||
| 575 | to bottom, | ||
| 576 | transparent 0%, | ||
| 577 | rgb(0 0 0 / 10%) 10%, | ||
| 578 | rgb(0 0 0 / 30%) 24%, | ||
| 579 | rgb(0 0 0 / 58%) 42%, | ||
| 580 | rgb(0 0 0 / 82%) 62%, | ||
| 581 | rgb(0 0 0 / 96%) 82%, | ||
| 582 | black 100% | ||
| 583 | ), | ||
| 584 | linear-gradient(black, black), linear-gradient(black, black); | ||
| 585 | mask-position: top, bottom, right; | ||
| 586 | mask-repeat: no-repeat; | ||
| 587 | mask-size: | ||
| 588 | 100% var(--topbar-scroll-fade-height), | ||
| 589 | 100% calc(100% - var(--topbar-scroll-fade-height)), | ||
| 590 | var(--app-scrollbar-width) 100%; | ||
| 591 | } | ||
| 592 | |||
| 593 | /* The pull request list sits directly under its topbar, so the tall band the chat and | ||
| 594 | settings pages fade under would read as empty padding here. A shorter band keeps the | ||
| 595 | fade while letting the controls start near the chrome. */ | ||
| 596 | .pull-requests-scroll-fade { | ||
| 597 | --topbar-scroll-fade-height: 1.5rem; | ||
| 598 | } | ||
| 599 | |||
| 600 | @keyframes settings-search-target-pulse { | 661 | @keyframes settings-search-target-pulse { |
| 601 | 0%, | 662 | 0%, |
| 602 | 100% { | 663 | 100% { |
| ... | @@ -607,56 +668,29 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -607,56 +668,29 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 607 | } | 668 | } |
| 608 | } | 669 | } |
| 609 | 670 | ||
| 610 | .settings-page-scroll-fade div.settings-search-target-pulse, | 671 | [data-settings-page-scroll] div.settings-search-target-pulse, |
| 611 | .settings-page-scroll-fade section.settings-search-target-pulse > div:first-child { | 672 | [data-settings-page-scroll] section.settings-search-target-pulse > div:first-child { |
| 612 | animation: settings-search-target-pulse 650ms ease-in-out 2; | 673 | animation: settings-search-target-pulse 650ms ease-in-out 2; |
| 613 | border-radius: 0.75rem; | 674 | border-radius: 0.75rem; |
| 614 | } | 675 | } |
| 615 | 676 | ||
| 616 | /* The pulse is the destination indicator; without it (reduced motion), the | 677 | /* The pulse is the destination indicator; without it (reduced motion), the |
| 617 | focus outline takes over, so exactly one indicator shows at a time. */ | 678 | focus outline takes over, so exactly one indicator shows at a time. */ |
| 618 | .settings-page-scroll-fade .settings-search-target-pulse:focus { | 679 | [data-settings-page-scroll] .settings-search-target-pulse:focus { |
| 619 | outline: none; | 680 | outline: none; |
| 620 | } | 681 | } |
| 621 | 682 | ||
| 622 | .workspace-titlebar-controls { | ||
| 623 | position: absolute; | ||
| 624 | top: var(--workspace-controls-top); | ||
| 625 | right: var(--workspace-controls-right); | ||
| 626 | display: flex; | ||
| 627 | height: var(--workspace-topbar-height); | ||
| 628 | align-items: center; | ||
| 629 | -webkit-app-region: no-drag; | ||
| 630 | } | ||
| 631 | |||
| 632 | .surface-subheader { | ||
| 633 | @apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background; | ||
| 634 | } | ||
| 635 | |||
| 636 | [data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] { | ||
| 637 | height: calc(var(--spacing) * 7); | ||
| 638 | min-height: calc(var(--spacing) * 7); | ||
| 639 | margin-bottom: calc(var(--spacing) * 3); | ||
| 640 | border-bottom-color: transparent; | ||
| 641 | } | ||
| 642 | |||
| 643 | .chat-composer-horizontal-inset { | ||
| 644 | padding-inline-start: calc(env(safe-area-inset-left) + 0.75rem); | ||
| 645 | padding-inline-end: calc(env(safe-area-inset-right) + 0.75rem); | ||
| 646 | } | ||
| 647 | |||
| 648 | .chat-composer-glass { | ||
| 649 | background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); | ||
| 650 | -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 651 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 652 | } | ||
| 653 | |||
| 654 | .chat-composer-glass-shell { | 683 | .chat-composer-glass-shell { |
| 655 | --chat-composer-glass-surface: var(--card); | 684 | --chat-composer-glass-surface: var(--card); |
| 656 | --chat-composer-outline: rgb(0 0 0 / 8%); | 685 | --chat-composer-outline: rgb(0 0 0 / 8%); |
| 657 | 686 | ||
| 658 | position: relative; | ||
| 659 | isolation: isolate; | 687 | isolation: isolate; |
| 688 | |||
| 689 | @variant dark { | ||
| 690 | --chat-composer-glass-surface: color-mix(in srgb, var(--background) 96%, var(--color-white)); | ||
| 691 | --chat-composer-outline: color-mix(in srgb, var(--color-white) 5%, transparent); | ||
| 692 | --chat-composer-highlight: rgb(255 255 255 / 3%); | ||
| 693 | } | ||
| 660 | } | 694 | } |
| 661 | 695 | ||
| 662 | .chat-composer-glass-shell::before { | 696 | .chat-composer-glass-shell::before { |
| ... | @@ -716,8 +750,15 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -716,8 +750,15 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 716 | } | 750 | } |
| 717 | 751 | ||
| 718 | .chat-composer-glass-host { | 752 | .chat-composer-glass-host { |
| 719 | position: relative; | ||
| 720 | box-shadow: 0 12px 28px -18px rgb(0 0 0 / 40%); | 753 | box-shadow: 0 12px 28px -18px rgb(0 0 0 / 40%); |
| 754 | |||
| 755 | @variant dark { | ||
| 756 | box-shadow: none; | ||
| 757 | |||
| 758 | &::after { | ||
| 759 | box-shadow: inset 0 1px var(--chat-composer-highlight); | ||
| 760 | } | ||
| 761 | } | ||
| 721 | } | 762 | } |
| 722 | 763 | ||
| 723 | .chat-composer-glass-host::after { | 764 | .chat-composer-glass-host::after { |
| ... | @@ -747,6 +788,21 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -747,6 +788,21 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 747 | .chat-composer-context-strip { | 788 | .chat-composer-context-strip { |
| 748 | position: relative; | 789 | position: relative; |
| 749 | isolation: isolate; | 790 | isolation: isolate; |
| 791 | |||
| 792 | @variant dark { | ||
| 793 | &::before { | ||
| 794 | border-color: rgb(255 255 255 / 7%); | ||
| 795 | background: | ||
| 796 | linear-gradient( | ||
| 797 | to bottom, | ||
| 798 | transparent 0 1rem, | ||
| 799 | rgb(0 0 0 / 18%) 1rem, | ||
| 800 | transparent calc(1rem + 10px) | ||
| 801 | ), | ||
| 802 | rgb(255 255 255 / 2%); | ||
| 803 | box-shadow: 0 14px 32px -18px rgb(0 0 0 / 75%); | ||
| 804 | } | ||
| 805 | } | ||
| 750 | } | 806 | } |
| 751 | 807 | ||
| 752 | .chat-composer-context-strip::before { | 808 | .chat-composer-context-strip::before { |
| ... | @@ -762,33 +818,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -762,33 +818,6 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 762 | content: ""; | 818 | content: ""; |
| 763 | } | 819 | } |
| 764 | 820 | ||
| 765 | .dark .chat-composer-glass-shell { | ||
| 766 | --chat-composer-glass-surface: color-mix(in srgb, var(--background) 96%, var(--color-white)); | ||
| 767 | --chat-composer-outline: color-mix(in srgb, var(--color-white) 5%, transparent); | ||
| 768 | --chat-composer-highlight: rgb(255 255 255 / 3%); | ||
| 769 | } | ||
| 770 | |||
| 771 | .dark .chat-composer-glass-host { | ||
| 772 | box-shadow: none; | ||
| 773 | } | ||
| 774 | |||
| 775 | .dark .chat-composer-glass-host::after { | ||
| 776 | box-shadow: inset 0 1px var(--chat-composer-highlight); | ||
| 777 | } | ||
| 778 | |||
| 779 | .dark .chat-composer-context-strip::before { | ||
| 780 | border-color: rgb(255 255 255 / 7%); | ||
| 781 | background: | ||
| 782 | linear-gradient( | ||
| 783 | to bottom, | ||
| 784 | transparent 0 1rem, | ||
| 785 | rgb(0 0 0 / 18%) 1rem, | ||
| 786 | transparent calc(1rem + 10px) | ||
| 787 | ), | ||
| 788 | rgb(255 255 255 / 2%); | ||
| 789 | box-shadow: 0 14px 32px -18px rgb(0 0 0 / 75%); | ||
| 790 | } | ||
| 791 | |||
| 792 | @supports not (clip-path: shape(from 0 0, line to 1px 1px)) { | 821 | @supports not (clip-path: shape(from 0 0, line to 1px 1px)) { |
| 793 | .chat-composer-glass-shell-with-context::before { | 822 | .chat-composer-glass-shell-with-context::before { |
| 794 | inset-block-end: var(--chat-composer-context-extension); | 823 | inset-block-end: var(--chat-composer-context-extension); |
| ... | @@ -806,106 +835,23 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -806,106 +835,23 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 806 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | 835 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); |
| 807 | } | 836 | } |
| 808 | 837 | ||
| 809 | .dark .chat-composer-context-strip::before { | 838 | .chat-composer-context-strip { |
| 810 | background: | 839 | @variant dark { |
| 811 | linear-gradient( | 840 | &::before { |
| 812 | to bottom, | 841 | background: |
| 813 | transparent 0 1rem, | 842 | linear-gradient( |
| 814 | rgb(0 0 0 / 18%) 1rem, | 843 | to bottom, |
| 815 | transparent calc(1rem + 10px) | 844 | transparent 0 1rem, |
| 816 | ), | 845 | rgb(0 0 0 / 18%) 1rem, |
| 817 | linear-gradient(rgb(255 255 255 / 2%), rgb(255 255 255 / 2%)), | 846 | transparent calc(1rem + 10px) |
| 818 | color-mix(in srgb, var(--chat-composer-glass-surface) var(--glass-opacity), transparent); | 847 | ), |
| 848 | linear-gradient(rgb(255 255 255 / 2%), rgb(255 255 255 / 2%)), | ||
| 849 | color-mix(in srgb, var(--chat-composer-glass-surface) var(--glass-opacity), transparent); | ||
| 850 | } | ||
| 851 | } | ||
| 819 | } | 852 | } |
| 820 | } | 853 | } |
| 821 | 854 | ||
| 822 | .alert-glass { | ||
| 823 | --alert-glass-tint: transparent; | ||
| 824 | |||
| 825 | background: | ||
| 826 | linear-gradient( | ||
| 827 | color-mix(in srgb, var(--alert-glass-tint) 4%, transparent), | ||
| 828 | color-mix(in srgb, var(--alert-glass-tint) 4%, transparent) | ||
| 829 | ), | ||
| 830 | color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important; | ||
| 831 | -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 832 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 833 | } | ||
| 834 | |||
| 835 | .alert-glass[data-variant="error"] { | ||
| 836 | --alert-glass-tint: var(--destructive); | ||
| 837 | } | ||
| 838 | |||
| 839 | .alert-glass[data-variant="info"] { | ||
| 840 | --alert-glass-tint: var(--info); | ||
| 841 | } | ||
| 842 | |||
| 843 | .alert-glass[data-variant="success"] { | ||
| 844 | --alert-glass-tint: var(--success); | ||
| 845 | } | ||
| 846 | |||
| 847 | .alert-glass[data-variant="warning"] { | ||
| 848 | --alert-glass-tint: var(--warning); | ||
| 849 | } | ||
| 850 | |||
| 851 | .dialog-glass { | ||
| 852 | background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); | ||
| 853 | -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 854 | backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); | ||
| 855 | } | ||
| 856 | |||
| 857 | .dialog-backdrop { | ||
| 858 | background: color-mix(in srgb, var(--background) 60%, transparent); | ||
| 859 | -webkit-backdrop-filter: blur(4px); | ||
| 860 | backdrop-filter: blur(4px); | ||
| 861 | } | ||
| 862 | |||
| 863 | .dropdown-glass { | ||
| 864 | /* | ||
| 865 | * Elevated glass needs a denser tint than broad ambient surfaces. Nesting | ||
| 866 | * the user-controlled mix inside an 18% popover tint preserves the full | ||
| 867 | * opacity setting range (40% -> 51%, 80% -> 84%, 100% -> 100%) while | ||
| 868 | * keeping high-contrast page content from blooming through menus. | ||
| 869 | */ | ||
| 870 | background: color-mix( | ||
| 871 | in srgb, | ||
| 872 | var(--popover) 18%, | ||
| 873 | color-mix(in srgb, var(--popover) var(--glass-opacity), transparent) | ||
| 874 | ); | ||
| 875 | -webkit-backdrop-filter: blur(var(--glass-blur)); | ||
| 876 | backdrop-filter: blur(var(--glass-blur)); | ||
| 877 | border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); | ||
| 878 | box-shadow: 0 16px 40px -18px rgb(0 0 0 / 55%); | ||
| 879 | } | ||
| 880 | |||
| 881 | .dialog-glass { | ||
| 882 | border-color: color-mix(in srgb, var(--foreground) 10%, transparent); | ||
| 883 | box-shadow: 0 24px 64px -24px rgb(0 0 0 / 65%); | ||
| 884 | } | ||
| 885 | |||
| 886 | .dark .dropdown-glass { | ||
| 887 | box-shadow: 0 18px 44px -18px rgb(0 0 0 / 80%); | ||
| 888 | } | ||
| 889 | |||
| 890 | .dark .model-picker-surface.model-picker-surface { | ||
| 891 | background: color-mix( | ||
| 892 | in srgb, | ||
| 893 | var(--popover) 18%, | ||
| 894 | color-mix(in srgb, var(--popover) var(--glass-opacity), transparent) | ||
| 895 | ); | ||
| 896 | } | ||
| 897 | |||
| 898 | .dark .dialog-glass { | ||
| 899 | border-color: color-mix(in srgb, var(--color-white) 8%, transparent); | ||
| 900 | box-shadow: | ||
| 901 | inset 0 1px rgb(255 255 255 / 4%), | ||
| 902 | 0 24px 72px -20px rgb(0 0 0 / 90%); | ||
| 903 | } | ||
| 904 | |||
| 905 | .dark .dialog-backdrop { | ||
| 906 | background: color-mix(in srgb, var(--background) 64%, transparent); | ||
| 907 | } | ||
| 908 | |||
| 909 | .settings-slider { | 855 | .settings-slider { |
| 910 | --settings-slider-progress: 0%; | 856 | --settings-slider-progress: 0%; |
| 911 | --settings-slider-fill-offset: 0.5rem; | 857 | --settings-slider-fill-offset: 0.5rem; |
| ... | @@ -1021,32 +967,10 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -1021,32 +967,10 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 1021 | } | 967 | } |
| 1022 | } | 968 | } |
| 1023 | 969 | ||
| 1024 | @media (min-width: 40rem) { | ||
| 1025 | .chat-timeline-scroll-fade, | ||
| 1026 | .settings-page-scroll-fade { | ||
| 1027 | --topbar-scroll-fade-height: 3rem; | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | .chat-composer-horizontal-inset { | ||
| 1031 | padding-inline-start: calc(env(safe-area-inset-left) + 1.25rem); | ||
| 1032 | padding-inline-end: calc(env(safe-area-inset-right) + 1.25rem); | ||
| 1033 | } | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { | 970 | @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) { |
| 1037 | .chat-composer-glass, | ||
| 1038 | .alert-glass { | ||
| 1039 | background: var(--background) !important; | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | .chat-composer-glass-shell::before { | 971 | .chat-composer-glass-shell::before { |
| 1043 | background: var(--chat-composer-glass-surface); | 972 | background: var(--chat-composer-glass-surface); |
| 1044 | } | 973 | } |
| 1045 | |||
| 1046 | .dialog-glass, | ||
| 1047 | .dropdown-glass { | ||
| 1048 | background: var(--popover) !important; | ||
| 1049 | } | ||
| 1050 | } | 974 | } |
| 1051 | } | 975 | } |
| 1052 | 976 | ||
| ... | @@ -1152,15 +1076,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -1152,15 +1076,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 1152 | --terminal-foreground: var(--foreground); | 1076 | --terminal-foreground: var(--foreground); |
| 1153 | --terminal-cursor: rgb(38 56 78); | 1077 | --terminal-cursor: rgb(38 56 78); |
| 1154 | --terminal-selection-background: rgb(37 63 99 / 20%); | 1078 | --terminal-selection-background: rgb(37 63 99 / 20%); |
| 1155 | --terminal-scrollbar: rgb(0 0 0 / 15%); | ||
| 1156 | --terminal-scrollbar-hover: rgb(0 0 0 / 25%); | ||
| 1157 | 1079 | ||
| 1158 | @variant dark { | 1080 | @variant dark { |
| 1159 | color-scheme: dark; | 1081 | color-scheme: dark; |
| 1160 | /* Keep the workspace in the same neutral-black family as sidebar v2. | 1082 | /* Keep the workspace in the same neutral-black family as sidebar v2. |
| 1161 | Surfaces lift from this base instead of starting from a milky gray. */ | 1083 | Surfaces lift from this base instead of starting from a milky gray. */ |
| 1162 | --background: var(--color-neutral-950); | 1084 | --background: var(--color-neutral-950); |
| 1163 | --app-chrome-background: var(--background); | ||
| 1164 | --surface-raised: var(--secondary); | 1085 | --surface-raised: var(--secondary); |
| 1165 | --foreground: var(--color-neutral-100); | 1086 | --foreground: var(--color-neutral-100); |
| 1166 | --card: color-mix(in srgb, var(--background) 97%, var(--color-white)); | 1087 | --card: color-mix(in srgb, var(--background) 97%, var(--color-white)); |
| ... | @@ -1168,54 +1089,31 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -1168,54 +1089,31 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 1168 | --popover: color-mix(in srgb, var(--background) 94%, var(--color-white)); | 1089 | --popover: color-mix(in srgb, var(--background) 94%, var(--color-white)); |
| 1169 | --popover-foreground: var(--color-neutral-100); | 1090 | --popover-foreground: var(--color-neutral-100); |
| 1170 | --primary: oklch(0.571 0.21 264); | 1091 | --primary: oklch(0.571 0.21 264); |
| 1171 | --primary-foreground: var(--color-white); | ||
| 1172 | --secondary: --alpha(var(--color-white) / 4%); | 1092 | --secondary: --alpha(var(--color-white) / 4%); |
| 1173 | --secondary-foreground: var(--color-neutral-100); | 1093 | --secondary-foreground: var(--color-neutral-100); |
| 1174 | --muted: --alpha(var(--color-white) / 4%); | 1094 | --muted: --alpha(var(--color-white) / 4%); |
| 1175 | --muted-foreground: color-mix(in srgb, var(--color-neutral-500) 90%, var(--color-white)); | 1095 | --muted-foreground: color-mix(in srgb, var(--color-neutral-500) 90%, var(--color-white)); |
| 1176 | --placeholder: var(--muted-foreground); | ||
| 1177 | --secondary-label: var(--muted-foreground); | ||
| 1178 | --icon-muted: var(--muted-foreground); | ||
| 1179 | --message-surface: var(--accent); | ||
| 1180 | --message-foreground: var(--foreground); | ||
| 1181 | --message-action: var(--primary); | ||
| 1182 | --message-action-foreground: var(--primary-foreground); | ||
| 1183 | --message-action-hover: color-mix(in srgb, var(--primary) 90%, var(--background)); | ||
| 1184 | --accent: --alpha(var(--color-white) / 4%); | 1096 | --accent: --alpha(var(--color-white) / 4%); |
| 1185 | --accent-foreground: var(--color-neutral-100); | 1097 | --accent-foreground: var(--color-neutral-100); |
| 1186 | --error: color-mix(in srgb, var(--color-red-500) 90%, var(--color-white)); | 1098 | --error: color-mix(in srgb, var(--color-red-500) 90%, var(--color-white)); |
| 1187 | --error-foreground: var(--color-red-400); | 1099 | --error-foreground: var(--color-red-400); |
| 1188 | --error-surface: color-mix(in srgb, var(--error) 16%, transparent); | 1100 | --error-surface: color-mix(in srgb, var(--error) 16%, transparent); |
| 1189 | --destructive: var(--error); | ||
| 1190 | --border: --alpha(var(--color-white) / 6%); | 1101 | --border: --alpha(var(--color-white) / 6%); |
| 1191 | --input: --alpha(var(--color-white) / 8%); | 1102 | --input: --alpha(var(--color-white) / 8%); |
| 1192 | --ring: var(--primary); | ||
| 1193 | --destructive-foreground: var(--error-foreground); | ||
| 1194 | --info: var(--color-blue-500); | ||
| 1195 | --info-foreground: var(--color-blue-400); | 1103 | --info-foreground: var(--color-blue-400); |
| 1196 | --success: var(--color-emerald-500); | ||
| 1197 | --success-foreground: var(--color-emerald-400); | 1104 | --success-foreground: var(--color-emerald-400); |
| 1198 | --warning: var(--color-amber-500); | ||
| 1199 | --warning-foreground: var(--color-amber-400); | 1105 | --warning-foreground: var(--color-amber-400); |
| 1200 | --warning-surface: color-mix(in srgb, var(--warning) 16%, transparent); | 1106 | --warning-surface: color-mix(in srgb, var(--warning) 16%, transparent); |
| 1201 | --update: var(--primary); | ||
| 1202 | --update-foreground: var(--color-blue-400); | 1107 | --update-foreground: var(--color-blue-400); |
| 1203 | --update-surface: color-mix(in srgb, var(--update) 18%, transparent); | 1108 | --update-surface: color-mix(in srgb, var(--update) 18%, transparent); |
| 1204 | --sidebar: var(--card); | 1109 | --sidebar: var(--card); |
| 1205 | --sidebar-foreground: var(--foreground); | ||
| 1206 | --sidebar-muted-foreground: var(--muted-foreground); | ||
| 1207 | --sidebar-control-surface: var(--muted); | 1110 | --sidebar-control-surface: var(--muted); |
| 1208 | --sidebar-row-hover: var(--accent); | 1111 | --sidebar-row-hover: var(--accent); |
| 1209 | --sidebar-row-active: var(--accent); | 1112 | --sidebar-row-active: var(--accent); |
| 1210 | --sidebar-row-selected: var(--muted); | 1113 | --sidebar-row-selected: var(--muted); |
| 1211 | --sidebar-border: var(--border); | ||
| 1212 | --sidebar-stage-fade: var(--card); | 1114 | --sidebar-stage-fade: var(--card); |
| 1213 | --terminal-background: var(--background); | ||
| 1214 | --terminal-foreground: var(--foreground); | ||
| 1215 | --terminal-cursor: rgb(180 203 255); | 1115 | --terminal-cursor: rgb(180 203 255); |
| 1216 | --terminal-selection-background: rgb(180 203 255 / 25%); | 1116 | --terminal-selection-background: rgb(180 203 255 / 25%); |
| 1217 | --terminal-scrollbar: rgb(255 255 255 / 10%); | ||
| 1218 | --terminal-scrollbar-hover: rgb(255 255 255 / 18%); | ||
| 1219 | } | 1117 | } |
| 1220 | } | 1118 | } |
| 1221 | 1119 | ||
| ... | @@ -1242,32 +1140,28 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -1242,32 +1140,28 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 1242 | --sidebar-row-selected: var(--color-white); | 1140 | --sidebar-row-selected: var(--color-white); |
| 1243 | --sidebar-border: var(--color-zinc-200); | 1141 | --sidebar-border: var(--color-zinc-200); |
| 1244 | --sidebar-stage-fade: var(--sidebar); | 1142 | --sidebar-stage-fade: var(--sidebar); |
| 1245 | background-color: var(--sidebar); | 1143 | |
| 1246 | } | 1144 | @variant dark { |
| 1247 | 1145 | --background: #000; | |
| 1248 | .dark [data-app-sidebar] { | 1146 | --foreground: #f1f3f7; |
| 1249 | --background: #000; | 1147 | --card: #000; |
| 1250 | --foreground: #f1f3f7; | 1148 | --card-foreground: var(--foreground); |
| 1251 | --card: #000; | 1149 | --accent: #191a1d; |
| 1252 | --card-foreground: var(--foreground); | 1150 | --accent-foreground: #f7f9ff; |
| 1253 | --accent: #191a1d; | 1151 | --muted: #0a0a0a; |
| 1254 | --accent-foreground: #f7f9ff; | 1152 | --muted-foreground: #a3a3a3; |
| 1255 | --muted: #0a0a0a; | 1153 | --border: rgb(255 255 255 / 8%); |
| 1256 | --muted-foreground: #a3a3a3; | 1154 | --input: rgb(255 255 255 / 18%); |
| 1257 | --border: rgb(255 255 255 / 8%); | 1155 | --sidebar: var(--card); |
| 1258 | --input: rgb(255 255 255 / 18%); | 1156 | --sidebar-foreground: var(--foreground); |
| 1259 | --sidebar: var(--card); | 1157 | --sidebar-muted-foreground: var(--muted-foreground); |
| 1260 | --sidebar-foreground: var(--foreground); | 1158 | --sidebar-control-surface: var(--muted); |
| 1261 | --sidebar-muted-foreground: var(--muted-foreground); | 1159 | --sidebar-row-hover: color-mix(in srgb, var(--foreground) 8%, transparent); |
| 1262 | --sidebar-control-surface: var(--muted); | 1160 | --sidebar-row-active: color-mix(in srgb, var(--foreground) 11%, transparent); |
| 1263 | --sidebar-row-hover: color-mix(in srgb, var(--foreground) 8%, transparent); | 1161 | --sidebar-row-selected: color-mix(in srgb, var(--foreground) 7%, transparent); |
| 1264 | --sidebar-row-active: color-mix(in srgb, var(--foreground) 11%, transparent); | 1162 | --sidebar-border: var(--border); |
| 1265 | --sidebar-row-selected: color-mix(in srgb, var(--foreground) 7%, transparent); | 1163 | --sidebar-stage-fade: var(--card); |
| 1266 | --sidebar-border: var(--border); | 1164 | } |
| 1267 | /* The stage-channel header art must ramp to THIS panel's surface, not the | ||
| 1268 | global chrome background, or the fade shows a seam (same rule as the | ||
| 1269 | light palette above). */ | ||
| 1270 | --sidebar-stage-fade: var(--card); | ||
| 1271 | } | 1165 | } |
| 1272 | 1166 | ||
| 1273 | /* Theme files are expressed in app color roles and mapped to the existing | 1167 | /* Theme files are expressed in app color roles and mapped to the existing |
| ... | @@ -1275,8 +1169,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil | ... | @@ -1275,8 +1169,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 1275 | compatibility overrides so both navigation implementations receive the | 1169 | compatibility overrides so both navigation implementations receive the |
| 1276 | same palette. Success, info, provider, and channel identity colors remain | 1170 | same palette. Success, info, provider, and channel identity colors remain |
| 1277 | independent; error, warning, and update roles are themeable below. */ | 1171 | independent; error, warning, and update roles are themeable below. */ |
| 1278 | html[data-theme-id], | 1172 | html[data-theme-id] { |
| 1279 | html.dark[data-theme-id] { | ||
| 1280 | --background: var(--app-theme-canvas); | 1173 | --background: var(--app-theme-canvas); |
| 1281 | --app-chrome-background: var(--app-theme-chrome); | 1174 | --app-chrome-background: var(--app-theme-chrome); |
| 1282 | --toolbar-background: var(--app-theme-toolbar); | 1175 | --toolbar-background: var(--app-theme-toolbar); |
| ... | @@ -1340,8 +1233,6 @@ html.dark[data-theme-id] { | ... | @@ -1340,8 +1233,6 @@ html.dark[data-theme-id] { |
| 1340 | --terminal-foreground: var(--app-theme-terminal-foreground); | 1233 | --terminal-foreground: var(--app-theme-terminal-foreground); |
| 1341 | --terminal-cursor: var(--app-theme-terminal-cursor); | 1234 | --terminal-cursor: var(--app-theme-terminal-cursor); |
| 1342 | --terminal-selection-background: var(--app-theme-terminal-selection-background); | 1235 | --terminal-selection-background: var(--app-theme-terminal-selection-background); |
| 1343 | --terminal-scrollbar: var(--app-theme-terminal-scrollbar); | ||
| 1344 | --terminal-scrollbar-hover: var(--app-theme-terminal-scrollbar-hover); | ||
| 1345 | } | 1236 | } |
| 1346 | 1237 | ||
| 1347 | /* T3 Chat's composer is a translucent lift over --chat-background. Route its | 1238 | /* T3 Chat's composer is a translucent lift over --chat-background. Route its |
| ... | @@ -1349,35 +1240,21 @@ html.dark[data-theme-id] { | ... | @@ -1349,35 +1240,21 @@ html.dark[data-theme-id] { |
| 1349 | another tint from the canvas, which made the dark composer too red. */ | 1240 | another tint from the canvas, which made the dark composer too red. */ |
| 1350 | html[data-theme-id] .chat-composer-glass-shell { | 1241 | html[data-theme-id] .chat-composer-glass-shell { |
| 1351 | --chat-composer-glass-surface: var(--app-theme-surface-raised); | 1242 | --chat-composer-glass-surface: var(--app-theme-surface-raised); |
| 1352 | } | ||
| 1353 | |||
| 1354 | html[data-theme-id]:not(.dark) .chat-composer-glass-shell { | ||
| 1355 | --chat-composer-outline: var(--app-theme-toolbar-border); | 1243 | --chat-composer-outline: var(--app-theme-toolbar-border); |
| 1356 | } | ||
| 1357 | 1244 | ||
| 1358 | html.dark[data-theme-id] .chat-composer-glass-shell { | 1245 | @variant dark { |
| 1359 | --chat-composer-outline: color-mix(in srgb, var(--app-theme-input) 30%, var(--background)); | 1246 | --chat-composer-outline: color-mix(in srgb, var(--app-theme-input) 30%, var(--background)); |
| 1360 | --chat-composer-highlight: color-mix(in srgb, var(--app-theme-input) 12%, transparent); | 1247 | --chat-composer-highlight: color-mix(in srgb, var(--app-theme-input) 12%, transparent); |
| 1248 | } | ||
| 1361 | } | 1249 | } |
| 1362 | 1250 | ||
| 1363 | html.dark[data-theme-id="t3-chat"] .chat-composer-glass-shell { | 1251 | html[data-theme-id="t3-chat"] .chat-composer-glass-shell { |
| 1364 | /* T3 Chat's visible composer edge is a dark plum, not the stock translucent | 1252 | /* T3 Chat's visible composer edge is a dark plum, not the stock translucent |
| 1365 | white outline. Its highlight is derived from --chat-input-gradient. */ | 1253 | white outline. Its highlight is derived from --chat-input-gradient. */ |
| 1366 | --chat-composer-outline: #241e28; | 1254 | @variant dark { |
| 1367 | --chat-composer-highlight: color-mix(in srgb, #432d48 12%, transparent); | 1255 | --chat-composer-outline: #241e28; |
| 1368 | } | 1256 | --chat-composer-highlight: color-mix(in srgb, #432d48 12%, transparent); |
| 1369 | 1257 | } | |
| 1370 | html[data-theme-id]:not(.dark) { | ||
| 1371 | color-scheme: light; | ||
| 1372 | } | ||
| 1373 | |||
| 1374 | html.dark[data-theme-id] { | ||
| 1375 | color-scheme: dark; | ||
| 1376 | } | ||
| 1377 | |||
| 1378 | html[data-theme-id] body { | ||
| 1379 | background-color: var(--app-chrome-background); | ||
| 1380 | color: var(--foreground); | ||
| 1381 | } | 1258 | } |
| 1382 | 1259 | ||
| 1383 | /* Theme-token dependency probes are restored synchronously, before paint. Keep | 1260 | /* Theme-token dependency probes are restored synchronously, before paint. Keep |
| ... | @@ -1477,8 +1354,8 @@ html[data-theme-id] [data-chat-header] [data-toolbar-control] { | ... | @@ -1477,8 +1354,8 @@ html[data-theme-id] [data-chat-header] [data-toolbar-control] { |
| 1477 | toggle's when the trigger renders the toggle, so match both. */ | 1354 | toggle's when the trigger renders the toggle, so match both. */ |
| 1478 | html[data-theme-id] [data-panel-layout-controls] [data-slot="toggle"], | 1355 | html[data-theme-id] [data-panel-layout-controls] [data-slot="toggle"], |
| 1479 | html[data-theme-id] [data-panel-layout-controls] [data-slot="tooltip-trigger"], | 1356 | html[data-theme-id] [data-panel-layout-controls] [data-slot="tooltip-trigger"], |
| 1480 | html[data-theme-id] .workspace-titlebar-controls [data-slot="toggle"], | 1357 | html[data-theme-id] [data-workspace-titlebar-controls] [data-slot="toggle"], |
| 1481 | html[data-theme-id] .workspace-titlebar-controls [data-slot="tooltip-trigger"] { | 1358 | html[data-theme-id] [data-workspace-titlebar-controls] [data-slot="tooltip-trigger"] { |
| 1482 | --control-icon-color: var(--toolbar-foreground); | 1359 | --control-icon-color: var(--toolbar-foreground); |
| 1483 | color: var(--toolbar-foreground); | 1360 | color: var(--toolbar-foreground); |
| 1484 | } | 1361 | } |
| ... | @@ -1522,19 +1399,23 @@ html[data-theme-id] .chat-markdown .chat-markdown-chrome-action { | ... | @@ -1522,19 +1399,23 @@ html[data-theme-id] .chat-markdown .chat-markdown-chrome-action { |
| 1522 | /* T3 Chat renders inline code and compact chat artifacts with its translucent | 1399 | /* T3 Chat renders inline code and compact chat artifacts with its translucent |
| 1523 | secondary surface flattened over the light chat canvas. The raw muted and | 1400 | secondary surface flattened over the light chat canvas. The raw muted and |
| 1524 | secondary tokens are substantially darker than those visible pixels. */ | 1401 | secondary tokens are substantially darker than those visible pixels. */ |
| 1525 | html[data-theme-id="t3-chat"]:not(.dark) .chat-markdown :not(pre) > code, | 1402 | html[data-theme-id="t3-chat"] { |
| 1526 | html[data-theme-id="t3-chat"]:not(.dark) [data-changed-files-state], | 1403 | @variant light { |
| 1527 | html[data-theme-id="t3-chat"]:not(.dark) [data-changed-files-header] { | 1404 | & .chat-markdown :not(pre) > code, |
| 1528 | background-color: var(--message-surface); | 1405 | & [data-changed-files-state], |
| 1529 | } | 1406 | & [data-changed-files-header] { |
| 1407 | background-color: var(--message-surface); | ||
| 1408 | } | ||
| 1530 | 1409 | ||
| 1531 | html[data-theme-id="t3-chat"]:not(.dark) .chat-markdown :not(pre) > code, | 1410 | & .chat-markdown :not(pre) > code, |
| 1532 | html[data-theme-id="t3-chat"]:not(.dark) [data-changed-files-state] { | 1411 | & [data-changed-files-state] { |
| 1533 | border-color: transparent; | 1412 | border-color: transparent; |
| 1534 | } | 1413 | } |
| 1535 | 1414 | ||
| 1536 | html[data-theme-id="t3-chat"]:not(.dark) .chat-markdown :not(pre) > code { | 1415 | & .chat-markdown :not(pre) > code { |
| 1537 | color: var(--message-foreground); | 1416 | color: var(--message-foreground); |
| 1417 | } | ||
| 1418 | } | ||
| 1538 | } | 1419 | } |
| 1539 | 1420 | ||
| 1540 | html[data-theme-id] .chat-markdown .chat-markdown-chrome-action:hover, | 1421 | html[data-theme-id] .chat-markdown .chat-markdown-chrome-action:hover, |
| ... | @@ -1562,40 +1443,19 @@ html[data-theme-id] [data-app-sidebar] { | ... | @@ -1562,40 +1443,19 @@ html[data-theme-id] [data-app-sidebar] { |
| 1562 | --sidebar-row-selected: var(--app-theme-sidebar-row-selected); | 1443 | --sidebar-row-selected: var(--app-theme-sidebar-row-selected); |
| 1563 | --sidebar-border: var(--app-theme-sidebar-border); | 1444 | --sidebar-border: var(--app-theme-sidebar-border); |
| 1564 | --sidebar-stage-fade: var(--app-theme-sidebar); | 1445 | --sidebar-stage-fade: var(--app-theme-sidebar); |
| 1565 | background-color: var(--sidebar); | ||
| 1566 | } | ||
| 1567 | |||
| 1568 | /* Keep the navigation edge as quiet as the standard palettes. Theme files may | ||
| 1569 | still use sidebarBorder for controls and internal separators, but the outer | ||
| 1570 | divider should not become more prominent just because a palette is vivid. */ | ||
| 1571 | html[data-theme-id] [data-app-sidebar] { | ||
| 1572 | border-color: color-mix(in srgb, var(--sidebar-foreground) 10%, transparent); | 1446 | border-color: color-mix(in srgb, var(--sidebar-foreground) 10%, transparent); |
| 1573 | } | ||
| 1574 | 1447 | ||
| 1575 | html.dark[data-theme-id] [data-app-sidebar] { | 1448 | @variant dark { |
| 1576 | border-color: color-mix(in srgb, var(--sidebar-foreground) 8%, transparent); | 1449 | border-color: color-mix(in srgb, var(--sidebar-foreground) 8%, transparent); |
| 1450 | } | ||
| 1577 | } | 1451 | } |
| 1578 | 1452 | ||
| 1579 | /* T3 Chat's panel divider is deliberately pink, and its resize affordance | 1453 | /* T3 Chat's panel divider is deliberately pink, and its resize affordance |
| 1580 | keeps that color while hovered. Do not neutralize this branded edge. */ | 1454 | keeps that color while hovered. Do not neutralize this branded edge. */ |
| 1581 | html.dark[data-theme-id="t3-chat"] [data-app-sidebar] { | 1455 | html[data-theme-id="t3-chat"] [data-app-sidebar] { |
| 1582 | border-color: var(--sidebar-border); | 1456 | @variant dark { |
| 1583 | } | 1457 | border-color: var(--sidebar-border); |
| 1584 | 1458 | } | |
| 1585 | .theme-json-key { | ||
| 1586 | color: var(--app-theme-accent, var(--color-blue-600)); | ||
| 1587 | } | ||
| 1588 | |||
| 1589 | .theme-json-string { | ||
| 1590 | color: var(--app-theme-message-action, var(--color-emerald-600)); | ||
| 1591 | } | ||
| 1592 | |||
| 1593 | .theme-json-number { | ||
| 1594 | color: var(--app-theme-secondary-foreground, var(--color-amber-600)); | ||
| 1595 | } | ||
| 1596 | |||
| 1597 | .theme-json-constant { | ||
| 1598 | color: var(--app-theme-accent-surface-foreground, var(--color-violet-600)); | ||
| 1599 | } | 1459 | } |
| 1600 | 1460 | ||
| 1601 | body { | 1461 | body { |
| ... | @@ -1715,125 +1575,7 @@ code { | ... | @@ -1715,125 +1575,7 @@ code { |
| 1715 | background: var(--app-scrollbar-thumb-hover); | 1575 | background: var(--app-scrollbar-thumb-hover); |
| 1716 | } | 1576 | } |
| 1717 | 1577 | ||
| 1718 | /* Settings -> Appearance can point the composer at its own face (for example a | ||
| 1719 | mono font); default follows the sans stack. Applied on the surface wrapper so | ||
| 1720 | the editor and its placeholder inherit together. */ | ||
| 1721 | .composer-editor-surface { | ||
| 1722 | font-family: var(--font-composer, var(--font-sans)); | ||
| 1723 | font-size: var(--font-size-prompt, 0.875rem); | ||
| 1724 | } | ||
| 1725 | |||
| 1726 | /* Touch browsers zoom the page when a focused field is under 16px, so keep | ||
| 1727 | the floor there regardless of the preference. Gated on a coarse pointer: | ||
| 1728 | the zoom quirk does not exist on desktop, where a narrow window must not | ||
| 1729 | silently override a smaller chosen prompt size. */ | ||
| 1730 | @media (max-width: 39.999rem) and (pointer: coarse) { | ||
| 1731 | .composer-editor-surface { | ||
| 1732 | font-size: max(var(--font-size-prompt, 1rem), 16px); | ||
| 1733 | } | ||
| 1734 | } | ||
| 1735 | |||
| 1736 | .t3-ghostty-canvas { | ||
| 1737 | cursor: text; | ||
| 1738 | } | ||
| 1739 | |||
| 1740 | .t3-ghostty-scrollbar { | ||
| 1741 | position: absolute; | ||
| 1742 | z-index: 1; | ||
| 1743 | top: 4px; | ||
| 1744 | right: 1px; | ||
| 1745 | bottom: 4px; | ||
| 1746 | width: var(--app-scrollbar-width); | ||
| 1747 | cursor: default; | ||
| 1748 | touch-action: none; | ||
| 1749 | } | ||
| 1750 | |||
| 1751 | .t3-ghostty-scrollbar-thumb { | ||
| 1752 | position: absolute; | ||
| 1753 | top: 0; | ||
| 1754 | right: 1px; | ||
| 1755 | left: 1px; | ||
| 1756 | border-radius: 3px; | ||
| 1757 | background: var(--app-scrollbar-thumb); | ||
| 1758 | transition: background-color 120ms ease-out; | ||
| 1759 | } | ||
| 1760 | |||
| 1761 | .t3-ghostty-scrollbar:hover .t3-ghostty-scrollbar-thumb, | ||
| 1762 | .t3-ghostty-scrollbar:focus-visible .t3-ghostty-scrollbar-thumb { | ||
| 1763 | background: var(--app-scrollbar-thumb-hover); | ||
| 1764 | } | ||
| 1765 | |||
| 1766 | .model-picker-list::-webkit-scrollbar-track { | ||
| 1767 | margin-block: 0.5rem; | ||
| 1768 | } | ||
| 1769 | |||
| 1770 | .model-picker-list-scroll-fade-top, | ||
| 1771 | .model-picker-list-scroll-fade-bottom { | ||
| 1772 | -webkit-mask-image: var(--model-picker-list-scroll-mask), linear-gradient(black, black); | ||
| 1773 | -webkit-mask-position: left, right; | ||
| 1774 | -webkit-mask-repeat: no-repeat; | ||
| 1775 | -webkit-mask-size: | ||
| 1776 | calc(100% - var(--app-scrollbar-width)) 100%, | ||
| 1777 | var(--app-scrollbar-width) 100%; | ||
| 1778 | mask-image: var(--model-picker-list-scroll-mask), linear-gradient(black, black); | ||
| 1779 | mask-position: left, right; | ||
| 1780 | mask-repeat: no-repeat; | ||
| 1781 | mask-size: | ||
| 1782 | calc(100% - var(--app-scrollbar-width)) 100%, | ||
| 1783 | var(--app-scrollbar-width) 100%; | ||
| 1784 | } | ||
| 1785 | |||
| 1786 | .model-picker-list-scroll-fade-top { | ||
| 1787 | --model-picker-list-scroll-mask: linear-gradient(to bottom, transparent, black var(--fade-size)); | ||
| 1788 | } | ||
| 1789 | |||
| 1790 | .model-picker-list-scroll-fade-bottom { | ||
| 1791 | --model-picker-list-scroll-mask: linear-gradient( | ||
| 1792 | to bottom, | ||
| 1793 | black calc(100% - var(--fade-size)), | ||
| 1794 | transparent | ||
| 1795 | ); | ||
| 1796 | } | ||
| 1797 | |||
| 1798 | .model-picker-list-scroll-fade-top.model-picker-list-scroll-fade-bottom { | ||
| 1799 | --model-picker-list-scroll-mask: linear-gradient( | ||
| 1800 | to bottom, | ||
| 1801 | transparent, | ||
| 1802 | black var(--fade-size), | ||
| 1803 | black calc(100% - var(--fade-size)), | ||
| 1804 | transparent | ||
| 1805 | ); | ||
| 1806 | } | ||
| 1807 | |||
| 1808 | .turn-chip-strip { | ||
| 1809 | scrollbar-width: none; | ||
| 1810 | -ms-overflow-style: none; | ||
| 1811 | overscroll-behavior-x: contain; | ||
| 1812 | } | ||
| 1813 | |||
| 1814 | .turn-chip-strip::-webkit-scrollbar { | ||
| 1815 | display: none; | ||
| 1816 | } | ||
| 1817 | |||
| 1818 | /* Reasoning select -- clickable label surface */ | ||
| 1819 | label:has(> select#reasoning-effort) { | ||
| 1820 | position: relative; | ||
| 1821 | } | ||
| 1822 | label:has(> select#reasoning-effort) select { | ||
| 1823 | position: absolute; | ||
| 1824 | inset: 0; | ||
| 1825 | opacity: 0; | ||
| 1826 | cursor: pointer; | ||
| 1827 | width: 100%; | ||
| 1828 | height: 100%; | ||
| 1829 | } | ||
| 1830 | |||
| 1831 | /* Chat markdown rendering */ | 1578 | /* Chat markdown rendering */ |
| 1832 | .chat-markdown { | ||
| 1833 | min-width: 0; | ||
| 1834 | overflow-wrap: anywhere; | ||
| 1835 | word-break: break-word; | ||
| 1836 | } | ||
| 1837 | 1579 | ||
| 1838 | .chat-markdown > :first-child { | 1580 | .chat-markdown > :first-child { |
| 1839 | margin-top: 0; | 1581 | margin-top: 0; |
| ... | @@ -1945,18 +1687,6 @@ label:has(> select#reasoning-effort) select { | ... | @@ -1945,18 +1687,6 @@ label:has(> select#reasoning-effort) select { |
| 1945 | background-size: 4px 2px; | 1687 | background-size: 4px 2px; |
| 1946 | } | 1688 | } |
| 1947 | 1689 | ||
| 1948 | .chat-markdown .chat-markdown-link-favicon { | ||
| 1949 | @apply inline-flex; | ||
| 1950 | width: 14px; | ||
| 1951 | height: 14px; | ||
| 1952 | margin-inline: 0.25em 0.2em; | ||
| 1953 | vertical-align: -0.125em; | ||
| 1954 | } | ||
| 1955 | |||
| 1956 | .chat-markdown .chat-markdown-link-leading { | ||
| 1957 | white-space: nowrap; | ||
| 1958 | } | ||
| 1959 | |||
| 1960 | .chat-markdown blockquote { | 1690 | .chat-markdown blockquote { |
| 1961 | border-left: 2px solid var(--border); | 1691 | border-left: 2px solid var(--border); |
| 1962 | padding-left: 0.8rem; | 1692 | padding-left: 0.8rem; |
| ... | @@ -2001,11 +1731,7 @@ label:has(> select#reasoning-effort) select { | ... | @@ -2001,11 +1731,7 @@ label:has(> select#reasoning-effort) select { |
| 2001 | font-size: 0.75rem; | 1731 | font-size: 0.75rem; |
| 2002 | } | 1732 | } |
| 2003 | 1733 | ||
| 2004 | .chat-markdown a.chat-markdown-file-link { | 1734 | .chat-markdown a.chat-markdown-file-link, |
| 2005 | color: var(--foreground); | ||
| 2006 | text-decoration: none; | ||
| 2007 | } | ||
| 2008 | |||
| 2009 | .chat-markdown a.chat-markdown-file-link:hover { | 1735 | .chat-markdown a.chat-markdown-file-link:hover { |
| 2010 | color: var(--foreground); | 1736 | color: var(--foreground); |
| 2011 | text-decoration: none; | 1737 | text-decoration: none; |
| ... | @@ -2023,75 +1749,26 @@ label:has(> select#reasoning-effort) select { | ... | @@ -2023,75 +1749,26 @@ label:has(> select#reasoning-effort) select { |
| 2023 | border-radius: 0.75rem; | 1749 | border-radius: 0.75rem; |
| 2024 | background: var(--muted); | 1750 | background: var(--muted); |
| 2025 | padding: 0.8rem 0.9rem; | 1751 | padding: 0.8rem 0.9rem; |
| 1752 | scrollbar-width: thin; | ||
| 1753 | scrollbar-color: color-mix(in srgb, var(--border) 78%, transparent) transparent; | ||
| 2026 | } | 1754 | } |
| 2027 | 1755 | ||
| 2028 | .chat-markdown pre code { | 1756 | .chat-markdown pre code { |
| 2029 | border: none; | 1757 | border: none; |
| 2030 | background: transparent; | 1758 | background: transparent; |
| 2031 | padding: 0; | 1759 | padding: 0; |
| 2032 | font-size: 0.75rem; | ||
| 2033 | } | ||
| 2034 | |||
| 2035 | .chat-markdown pre { | ||
| 2036 | scrollbar-width: thin; | ||
| 2037 | scrollbar-color: color-mix(in srgb, var(--border) 78%, transparent) transparent; | ||
| 2038 | } | 1760 | } |
| 2039 | 1761 | ||
| 2040 | .chat-markdown pre::-webkit-scrollbar { | 1762 | .chat-markdown pre::-webkit-scrollbar { |
| 2041 | height: 7px; | 1763 | height: 7px; |
| 2042 | } | 1764 | } |
| 2043 | 1765 | ||
| 2044 | .chat-markdown pre::-webkit-scrollbar-track { | ||
| 2045 | background: transparent; | ||
| 2046 | } | ||
| 2047 | |||
| 2048 | .chat-markdown pre::-webkit-scrollbar-thumb { | 1766 | .chat-markdown pre::-webkit-scrollbar-thumb { |
| 2049 | border-radius: 999px; | 1767 | border-radius: 999px; |
| 2050 | background: color-mix(in srgb, var(--border) 78%, transparent); | 1768 | background: color-mix(in srgb, var(--border) 78%, transparent); |
| 2051 | } | 1769 | } |
| 2052 | 1770 | ||
| 2053 | .markdown-file-link-tooltip-scroll { | 1771 | .chat-markdown .chat-markdown-codeblock-header, |
| 2054 | scrollbar-width: thin; | ||
| 2055 | scrollbar-color: color-mix(in srgb, var(--border) 78%, transparent) transparent; | ||
| 2056 | } | ||
| 2057 | |||
| 2058 | .markdown-file-link-tooltip-scroll::-webkit-scrollbar { | ||
| 2059 | height: 6px; | ||
| 2060 | } | ||
| 2061 | |||
| 2062 | .markdown-file-link-tooltip-scroll::-webkit-scrollbar-track { | ||
| 2063 | background: transparent; | ||
| 2064 | } | ||
| 2065 | |||
| 2066 | .markdown-file-link-tooltip-scroll::-webkit-scrollbar-thumb { | ||
| 2067 | border-radius: 999px; | ||
| 2068 | background: color-mix(in srgb, var(--border) 78%, transparent); | ||
| 2069 | } | ||
| 2070 | |||
| 2071 | .chat-markdown .chat-markdown-codeblock { | ||
| 2072 | margin: 0.65rem 0; | ||
| 2073 | overflow: hidden; | ||
| 2074 | border-radius: var(--radius); | ||
| 2075 | } | ||
| 2076 | |||
| 2077 | .chat-markdown .chat-markdown-codeblock-header { | ||
| 2078 | display: flex; | ||
| 2079 | align-items: center; | ||
| 2080 | justify-content: space-between; | ||
| 2081 | gap: 0.5rem; | ||
| 2082 | padding: 0.375rem 0.375rem 0 0.75rem; | ||
| 2083 | color: color-mix(in srgb, var(--foreground) 72%, transparent); | ||
| 2084 | } | ||
| 2085 | |||
| 2086 | .chat-markdown .chat-markdown-codeblock-title { | ||
| 2087 | display: inline-flex; | ||
| 2088 | min-width: 0; | ||
| 2089 | align-items: center; | ||
| 2090 | gap: 0.4rem; | ||
| 2091 | font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace); | ||
| 2092 | font-size: 0.6875rem; | ||
| 2093 | } | ||
| 2094 | |||
| 2095 | .chat-markdown .chat-markdown-chrome-action { | 1772 | .chat-markdown .chat-markdown-chrome-action { |
| 2096 | color: color-mix(in srgb, var(--foreground) 72%, transparent); | 1773 | color: color-mix(in srgb, var(--foreground) 72%, transparent); |
| 2097 | } | 1774 | } |
| ... | @@ -2167,13 +1844,6 @@ label:has(> select#reasoning-effort) select { | ... | @@ -2167,13 +1844,6 @@ label:has(> select#reasoning-effort) select { |
| 2167 | overflow-wrap: anywhere; | 1844 | overflow-wrap: anywhere; |
| 2168 | } | 1845 | } |
| 2169 | 1846 | ||
| 2170 | .chat-markdown .chat-markdown-table-footer { | ||
| 2171 | display: flex; | ||
| 2172 | align-items: center; | ||
| 2173 | justify-content: space-between; | ||
| 2174 | margin-top: 0.125rem; | ||
| 2175 | } | ||
| 2176 | |||
| 2177 | /* Prompt-stash save acknowledgement: the new count fades up from just below | 1847 | /* Prompt-stash save acknowledgement: the new count fades up from just below |
| 2178 | its resting position, once, then stops. One-shot and event-driven (React | 1848 | its resting position, once, then stops. One-shot and event-driven (React |
| 2179 | remounts the element by key on each stash) — no continuous animation. */ | 1849 | remounts the element by key on each stash) — no continuous animation. */ |
| ... | @@ -2188,19 +1858,6 @@ label:has(> select#reasoning-effort) select { | ... | @@ -2188,19 +1858,6 @@ label:has(> select#reasoning-effort) select { |
| 2188 | } | 1858 | } |
| 2189 | } | 1859 | } |
| 2190 | 1860 | ||
| 2191 | .prompt-stash-count-enter { | ||
| 2192 | animation: prompt-stash-count-enter 180ms ease-out both; | ||
| 2193 | } | ||
| 2194 | |||
| 2195 | @media (prefers-reduced-motion: reduce) { | ||
| 2196 | .prompt-stash-count-enter { | ||
| 2197 | animation: none; | ||
| 2198 | } | ||
| 2199 | [data-slot="skeleton"]::after { | ||
| 2200 | content: none; | ||
| 2201 | } | ||
| 2202 | } | ||
| 2203 | |||
| 2204 | @keyframes provider-update-pill-countdown { | 1861 | @keyframes provider-update-pill-countdown { |
| 2205 | from { | 1862 | from { |
| 2206 | transform: scaleX(1); | 1863 | transform: scaleX(1); |
| ... | @@ -2210,23 +1867,6 @@ label:has(> select#reasoning-effort) select { | ... | @@ -2210,23 +1867,6 @@ label:has(> select#reasoning-effort) select { |
| 2210 | } | 1867 | } |
| 2211 | } | 1868 | } |
| 2212 | 1869 | ||
| 2213 | .provider-update-pill-progress { | ||
| 2214 | animation: provider-update-pill-countdown var(--provider-update-pill-dismiss-ms) linear forwards; | ||
| 2215 | } | ||
| 2216 | |||
| 2217 | /* Diffs theme bridge (match diff surfaces to app palette) */ | ||
| 2218 | .diff-panel-viewport { | ||
| 2219 | background: var(--background); | ||
| 2220 | } | ||
| 2221 | |||
| 2222 | /* Diffs live directly on the panel canvas. Normal chat code blocks may use a | ||
| 2223 | raised code surface, but carrying that fill into the diff creates a card-like | ||
| 2224 | rectangle that does not belong in the panel. */ | ||
| 2225 | .diff-render-surface { | ||
| 2226 | --code-background: var(--background); | ||
| 2227 | } | ||
| 2228 | |||
| 2229 | .diff-render-file, | ||
| 2230 | .diff-render-surface diffs-container { | 1870 | .diff-render-surface diffs-container { |
| 2231 | border: 0; | 1871 | border: 0; |
| 2232 | border-radius: 0; | 1872 | border-radius: 0; |
| ... | @@ -2307,40 +1947,3 @@ label:has(> select#reasoning-effort) select { | ... | @@ -2307,40 +1947,3 @@ label:has(> select#reasoning-effort) select { |
| 2307 | .ultrathink-chroma { | 1947 | .ultrathink-chroma { |
| 2308 | animation: ultrathink-chroma-shift 10s linear infinite; | 1948 | animation: ultrathink-chroma-shift 10s linear infinite; |
| 2309 | } | 1949 | } |
| 2310 | |||
| 2311 | .ultrathink-pill { | ||
| 2312 | background: | ||
| 2313 | linear-gradient(var(--card), var(--card)) padding-box, | ||
| 2314 | var(--ultrathink-spectrum) border-box; | ||
| 2315 | background-size: | ||
| 2316 | 100% 100%, | ||
| 2317 | 220% 220%; | ||
| 2318 | background-position: | ||
| 2319 | 0 0, | ||
| 2320 | 0% 50%; | ||
| 2321 | animation: ultrathink-rainbow 10s linear infinite; | ||
| 2322 | box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--card) 82%, transparent); | ||
| 2323 | } | ||
| 2324 | |||
| 2325 | .ultrathink-word { | ||
| 2326 | display: inline-block; | ||
| 2327 | color: transparent; | ||
| 2328 | background-image: var(--ultrathink-spectrum); | ||
| 2329 | background-size: 220% 220%; | ||
| 2330 | background-position: 0% 50%; | ||
| 2331 | background-clip: text; | ||
| 2332 | -webkit-background-clip: text; | ||
| 2333 | animation: ultrathink-rainbow 10s linear infinite; | ||
| 2334 | } | ||
| 2335 | |||
| 2336 | /* Composer chips are non-editable decorators, so the browser skips them when | ||
| 2337 | painting text selection; this overlay stands in for the native highlight. */ | ||
| 2338 | .composer-inline-chip[data-composer-chip-selected]::after { | ||
| 2339 | content: ""; | ||
| 2340 | position: absolute; | ||
| 2341 | inset: 0; | ||
| 2342 | border-radius: 6px; | ||
| 2343 | background-color: Highlight; | ||
| 2344 | opacity: 0.3; | ||
| 2345 | pointer-events: none; | ||
| 2346 | } |
apps/web/src/routes/-chatIndexTitlebar.test.ts+5-2| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off - Regression coverage compares the onboarding header with the shared titlebar contract. | 1 | // @effect-diagnostics nodeBuiltinImport:off |
| 2 | // Regression coverage compares the onboarding header with the shared titlebar contract. | ||
| 2 | import * as NodeFS from "node:fs"; | 3 | import * as NodeFS from "node:fs"; |
| 3 | 4 | ||
| 4 | import { describe, expect, it } from "vite-plus/test"; | 5 | import { describe, expect, it } from "vite-plus/test"; |
| ... | @@ -14,7 +15,9 @@ describe("hosted static onboarding header", () => { | ... | @@ -14,7 +15,9 @@ describe("hosted static onboarding header", () => { |
| 14 | 15 | ||
| 15 | const onboardingHeader = routeSource.slice(onboardingStart, onboardingEnd); | 16 | const onboardingHeader = routeSource.slice(onboardingStart, onboardingEnd); |
| 16 | 17 | ||
| 17 | expect(onboardingHeader).toContain("workspace-topbar"); | 18 | expect(onboardingHeader).toContain("h-[var(--workspace-topbar-height)]"); |
| 19 | expect(onboardingHeader).toContain("min-h-[var(--workspace-topbar-height)]"); | ||
| 20 | expect(onboardingHeader).toContain("COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS"); | ||
| 18 | expect(onboardingHeader).not.toMatch(/(?:^|\s)(?:[\w-]+:)*py-/); | 21 | expect(onboardingHeader).not.toMatch(/(?:^|\s)(?:[\w-]+:)*py-/); |
| 19 | }); | 22 | }); |
| 20 | }); | 23 | }); |
apps/web/src/routes/_chat.index.tsx+1-1| ... | @@ -145,7 +145,7 @@ function HostedStaticOnboardingState() { | ... | @@ -145,7 +145,7 @@ function HostedStaticOnboardingState() { |
| 145 | <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background"> | 145 | <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background"> |
| 146 | <header | 146 | <header |
| 147 | className={cn( | 147 | className={cn( |
| 148 | "workspace-topbar border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | 148 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center border-b border-border px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", |
| 149 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | 149 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 150 | )} | 150 | )} |
| 151 | > | 151 | > |
apps/web/src/routes/_chat.pull-requests.tsx+4-3| ... | @@ -1308,7 +1308,8 @@ function PullRequestsRouteView() { | ... | @@ -1308,7 +1308,8 @@ function PullRequestsRouteView() { |
| 1308 | // anchor the thread view's controls and the sidebar trigger use, so | 1308 | // anchor the thread view's controls and the sidebar trigger use, so |
| 1309 | // every titlebar cluster in the app sits one shared inset from its | 1309 | // every titlebar cluster in the app sits one shared inset from its |
| 1310 | // edge. | 1310 | // edge. |
| 1311 | className="workspace-titlebar-controls z-50 mr-px gap-1 [-webkit-app-region:no-drag]" | 1311 | className="absolute top-[var(--workspace-controls-top)] right-[var(--workspace-controls-right)] z-50 mr-px flex h-[var(--workspace-topbar-height)] items-center gap-1 [-webkit-app-region:no-drag]" |
| 1312 | data-workspace-titlebar-controls | ||
| 1312 | > | 1313 | > |
| 1313 | {panelToggleControls} | 1314 | {panelToggleControls} |
| 1314 | </div> | 1315 | </div> |
| ... | @@ -1828,7 +1829,7 @@ function PullRequestsColumn({ | ... | @@ -1828,7 +1829,7 @@ function PullRequestsColumn({ |
| 1828 | <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background"> | 1829 | <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-background"> |
| 1829 | <header | 1830 | <header |
| 1830 | className={cn( | 1831 | className={cn( |
| 1831 | "workspace-topbar drag-region gap-1.5 px-3 sm:px-5", | 1832 | "drag-region flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center gap-1.5 px-3 sm:px-5", |
| 1832 | // A closed right panel leaves this column full-width, so its header runs | 1833 | // A closed right panel leaves this column full-width, so its header runs |
| 1833 | // underneath the native window controls on Windows; reserve the inset the | 1834 | // underneath the native window controls on Windows; reserve the inset the |
| 1834 | // way Settings and the chat view do. While the panel is open the column | 1835 | // way Settings and the chat view do. While the panel is open the column |
| ... | @@ -1903,7 +1904,7 @@ function PullRequestsColumn({ | ... | @@ -1903,7 +1904,7 @@ function PullRequestsColumn({ |
| 1903 | 1904 | ||
| 1904 | <div | 1905 | <div |
| 1905 | ref={scrollRef} | 1906 | ref={scrollRef} |
| 1906 | className="pull-requests-scroll-fade scrollbar-gutter-both min-h-0 flex-1 overflow-y-auto" | 1907 | className="topbar-scroll-fade scrollbar-gutter-both min-h-0 flex-1 overflow-y-auto [--topbar-scroll-fade-height:1.5rem] sm:[--topbar-scroll-fade-height:1.5rem]" |
| 1907 | > | 1908 | > |
| 1908 | {/* The top padding is the fade band's own height (1.5rem here), the same pairing the | 1909 | {/* The top padding is the fade band's own height (1.5rem here), the same pairing the |
| 1909 | settings page makes: at rest the controls sit fully below the mask, and only | 1910 | settings page makes: at rest the controls sit fully below the mask, and only |
apps/web/src/routes/settings.tsx+1-1| ... | @@ -75,7 +75,7 @@ function SettingsContentLayout() { | ... | @@ -75,7 +75,7 @@ function SettingsContentLayout() { |
| 75 | {!isElectron && ( | 75 | {!isElectron && ( |
| 76 | <header | 76 | <header |
| 77 | className={cn( | 77 | className={cn( |
| 78 | "workspace-topbar px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", | 78 | "flex h-[var(--workspace-topbar-height)] min-h-[var(--workspace-topbar-height)] shrink-0 items-center px-3 transition-[padding-left] duration-200 ease-linear motion-reduce:transition-none sm:px-5", |
| 79 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, | 79 | COLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, |
| 80 | )} | 80 | )} |
| 81 | > | 81 | > |
apps/web/src/terminal/ghostty/surface.ts+5-4| ... | @@ -578,8 +578,7 @@ export class GhosttyTerminalSurface { | ... | @@ -578,8 +578,7 @@ export class GhosttyTerminalSurface { |
| 578 | options: GhosttyTerminalSurfaceOptions, | 578 | options: GhosttyTerminalSurfaceOptions, |
| 579 | ): Promise<GhosttyTerminalSurface> { | 579 | ): Promise<GhosttyTerminalSurface> { |
| 580 | const canvas = document.createElement("canvas"); | 580 | const canvas = document.createElement("canvas"); |
| 581 | canvas.className = "t3-ghostty-canvas"; | 581 | canvas.className = "block size-full cursor-text"; |
| 582 | canvas.style.cssText = "display:block;width:100%;height:100%;"; | ||
| 583 | canvas.setAttribute("aria-hidden", "true"); | 582 | canvas.setAttribute("aria-hidden", "true"); |
| 584 | 583 | ||
| 585 | const input = document.createElement("textarea"); | 584 | const input = document.createElement("textarea"); |
| ... | @@ -592,14 +591,16 @@ export class GhosttyTerminalSurface { | ... | @@ -592,14 +591,16 @@ export class GhosttyTerminalSurface { |
| 592 | "position:absolute;left:4px;top:4px;width:1px;height:1px;opacity:0;padding:0;border:0;resize:none;pointer-events:none;"; | 591 | "position:absolute;left:4px;top:4px;width:1px;height:1px;opacity:0;padding:0;border:0;resize:none;pointer-events:none;"; |
| 593 | 592 | ||
| 594 | const scrollbar = document.createElement("div"); | 593 | const scrollbar = document.createElement("div"); |
| 595 | scrollbar.className = "t3-ghostty-scrollbar"; | 594 | scrollbar.className = |
| 595 | "group absolute top-1 right-px bottom-1 z-1 w-[var(--app-scrollbar-width)] cursor-default touch-none"; | ||
| 596 | scrollbar.setAttribute("role", "scrollbar"); | 596 | scrollbar.setAttribute("role", "scrollbar"); |
| 597 | scrollbar.setAttribute("aria-label", "Terminal scrollback"); | 597 | scrollbar.setAttribute("aria-label", "Terminal scrollback"); |
| 598 | scrollbar.setAttribute("aria-orientation", "vertical"); | 598 | scrollbar.setAttribute("aria-orientation", "vertical"); |
| 599 | scrollbar.tabIndex = 0; | 599 | scrollbar.tabIndex = 0; |
| 600 | scrollbar.hidden = true; | 600 | scrollbar.hidden = true; |
| 601 | const scrollbarThumb = document.createElement("div"); | 601 | const scrollbarThumb = document.createElement("div"); |
| 602 | scrollbarThumb.className = "t3-ghostty-scrollbar-thumb"; | 602 | scrollbarThumb.className = |
| 603 | "absolute inset-x-px top-0 rounded-[3px] bg-[var(--app-scrollbar-thumb)] transition-[background-color] duration-[120ms] ease-[ease-out] group-hover:bg-[var(--app-scrollbar-thumb-hover)] group-focus-visible:bg-[var(--app-scrollbar-thumb-hover)]"; | ||
| 603 | scrollbar.append(scrollbarThumb); | 604 | scrollbar.append(scrollbarThumb); |
| 604 | mount.replaceChildren(canvas, input, scrollbar); | 605 | mount.replaceChildren(canvas, input, scrollbar); |
| 605 | 606 |