authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-10-09 20:54:14-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-09 20:54:14-03:00
log5dcb59ed6c191589e730fb964aabad005466eb6d
tree77babc90b134bf5c63322d7dbfd17643ff73e4e3
parent08e18b7833c9b5d33bfcd1ce8976dcb67c11dde5
signature Signed by PGP key B5690EEEBB952194

fix(ui): keep focus and selection borders visible across the app (#16675)

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

79 files changed, 359 insertions(+), 131 deletions(-)

apps/desktop/src/permissions/MacPermissionHelper.ts+1-1
...@@ -52,7 +52,7 @@ body { margin: 0; background: transparent; color: var(--text); font: 15px/22px -...@@ -52,7 +52,7 @@ body { margin: 0; background: transparent; color: var(--text); font: 15px/22px -
52#panel { position: relative; margin: 2px; padding: 20px; height: 136px; border: 1px solid var(--line); border-radius: 24px; background: var(--base); }52#panel { position: relative; margin: 2px; padding: 20px; height: 136px; border: 1px solid var(--line); border-radius: 24px; background: var(--base); }
53header { font-weight: 600; white-space: nowrap; }53header { font-weight: 600; white-space: nowrap; }
54button { font: inherit; color: inherit; }54button { font: inherit; color: inherit; }
55button:focus-visible { outline: 2px solid #007aff; outline-offset: 3px; }55button:focus-visible { outline: 2px solid #007aff; outline-offset: -2px; }
56#close { position: absolute; right: 8px; top: 6px; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%; background: var(--base); font-size: 18px; cursor: pointer; opacity: 0; }56#close { position: absolute; right: 8px; top: 6px; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%; background: var(--base); font-size: 18px; cursor: pointer; opacity: 0; }
57#panel:hover #close, #close:focus-visible { opacity: 1; }57#panel:hover #close, #close:focus-visible { opacity: 1; }
58#app { display: flex; align-items: center; gap: 12px; width: 100%; height: 52px; margin-top: 20px; padding: 8px 12px; border: 0; border-radius: 10px; background: var(--row); cursor: grab; text-align: left; font-size: 16px; font-weight: 600; }58#app { display: flex; align-items: center; gap: 12px; width: 100%; height: 52px; margin-top: 20px; padding: 8px 12px; border: 0; border-radius: 10px; background: var(--row); cursor: grab; text-align: left; font-size: 16px; font-weight: 600; }
apps/desktop/src/preview/Annotation.css+1-1
...@@ -64,5 +64,5 @@ input:focus-visible,...@@ -64,5 +64,5 @@ input:focus-visible,
64select:focus-visible,64select:focus-visible,
65textarea:focus-visible {65textarea:focus-visible {
66 outline: 2px solid color-mix(in srgb, var(--t3-ring) 72%, transparent);66 outline: 2px solid color-mix(in srgb, var(--t3-ring) 72%, transparent);
67 outline-offset: 1px;67 outline-offset: -2px;
68}68}
apps/desktop/src/preview/AnnotationStyles.generated.ts+1-1
...@@ -1,3 +1,3 @@...@@ -1,3 +1,3 @@
1// Generated by scripts/build-preview-annotation-css.mjs. Do not edit.1// Generated by scripts/build-preview-annotation-css.mjs. Do not edit.
2export const previewAnnotationStyles =2export const previewAnnotationStyles =
3 '/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n@layer properties;\n:root, :host {\n --spacing: 0.25rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --blur-xl: 24px;\n --default-font-family: var(--t3-font-sans);\n --default-mono-font-family: var(--t3-font-mono);\n}\n*, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n}\nhtml, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, \'Helvetica Neue\', \'Noto Sans\', Arial, sans-serif, \'Apple Color Emoji\', \'Segoe UI Emoji\', \'Segoe UI Symbol\', \'Noto Color Emoji\');\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n}\nhr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n}\nabbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n}\nh1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n}\na {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n}\nb, strong {\n font-weight: bolder;\n}\ncode, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \'Liberation Mono\', \'Courier New\', monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n}\nsmall {\n font-size: 80%;\n}\nsub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsub {\n bottom: -0.25em;\n}\nsup {\n top: -0.5em;\n}\ntable {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n}\n:-moz-focusring:where(:not(iframe)) {\n outline: auto;\n}\nprogress {\n vertical-align: baseline;\n}\nsummary {\n display: list-item;\n}\nol, ul, menu {\n list-style: none;\n}\nimg, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n}\nimg, video {\n max-width: 100%;\n height: auto;\n}\nbutton, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n}\n:where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n}\n:where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n}\n::file-selector-button {\n margin-inline-end: 4px;\n}\n::placeholder {\n opacity: 1;\n}\n@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n}\ntextarea {\n resize: vertical;\n}\n::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n}\n::-webkit-datetime-edit {\n display: inline-flex;\n}\n::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n}\n::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n}\n::-webkit-calendar-picker-indicator {\n line-height: 1;\n}\n:-moz-ui-invalid {\n box-shadow: none;\n}\nbutton, input:where([type=\'button\'], [type=\'reset\'], [type=\'submit\']), ::file-selector-button {\n appearance: button;\n}\n::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n}\n[hidden]:where(:not([hidden=\'until-found\'])) {\n display: none !important;\n}\n.pointer-events-auto {\n pointer-events: auto;\n}\n.pointer-events-none {\n pointer-events: none;\n}\n.absolute {\n position: absolute;\n}\n.fixed {\n position: fixed;\n}\n.inset-0 {\n inset: 0px;\n}\n.top-1\\/2 {\n top: calc(1 / 2 * 100%);\n}\n.top-2\\.5 {\n top: calc(var(--spacing) * 2.5);\n}\n.right-2 {\n right: calc(var(--spacing) * 2);\n}\n.left-1\\/2 {\n left: calc(1 / 2 * 100%);\n}\n.z-1 {\n z-index: 1;\n}\n.block {\n display: block;\n}\n.flex {\n display: flex;\n}\n.grid {\n display: grid;\n}\n.hidden {\n display: none;\n}\n.inline-flex {\n display: inline-flex;\n}\n.h-7 {\n height: calc(var(--spacing) * 7);\n}\n.h-8 {\n height: calc(var(--spacing) * 8);\n}\n.max-h-24 {\n max-height: calc(var(--spacing) * 24);\n}\n.max-h-\\[calc\\(100vh-16px\\)\\] {\n max-height: calc(100vh - 16px);\n}\n.max-h-\\[min\\(176px\\,calc\\(100vh-180px\\)\\)\\] {\n max-height: min(176px, calc(100vh - 180px));\n}\n.min-h-7 {\n min-height: calc(var(--spacing) * 7);\n}\n.min-h-8 {\n min-height: calc(var(--spacing) * 8);\n}\n.w-6 {\n width: calc(var(--spacing) * 6);\n}\n.w-8 {\n width: calc(var(--spacing) * 8);\n}\n.w-\\[min\\(360px\\,calc\\(100vw-16px\\)\\)\\] {\n width: min(360px, calc(100vw - 16px));\n}\n.w-full {\n width: 100%;\n}\n.max-w-70 {\n max-width: calc(var(--spacing) * 70);\n}\n.min-w-0 {\n min-width: 0px;\n}\n.flex-1 {\n flex: 1;\n}\n.shrink-0 {\n flex-shrink: 0;\n}\n.-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n}\n.-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n}\n.cursor-grab {\n cursor: grab;\n}\n.cursor-pointer {\n cursor: pointer;\n}\n.resize {\n resize: both;\n}\n.resize-none {\n resize: none;\n}\n.appearance-none {\n appearance: none;\n}\n.grid-cols-\\[22px_minmax\\(0\\,1fr\\)\\] {\n grid-template-columns: 22px minmax(0,1fr);\n}\n.grid-cols-\\[82px_minmax\\(0\\,1fr\\)\\] {\n grid-template-columns: 82px minmax(0,1fr);\n}\n.flex-col {\n flex-direction: column;\n}\n.items-center {\n align-items: center;\n}\n.items-start {\n align-items: flex-start;\n}\n.justify-center {\n justify-content: center;\n}\n.gap-0\\.5 {\n gap: calc(var(--spacing) * 0.5);\n}\n.gap-1 {\n gap: var(--spacing);\n}\n.gap-2 {\n gap: calc(var(--spacing) * 2);\n}\n.overflow-auto {\n overflow: auto;\n}\n.overflow-hidden {\n overflow: hidden;\n}\n.overflow-y-hidden {\n overflow-y: hidden;\n}\n.rounded-lg {\n border-radius: var(--t3-radius);\n}\n.rounded-md {\n border-radius: calc(var(--t3-radius) - 2px);\n}\n.rounded-xl {\n border-radius: calc(var(--t3-radius) + 4px);\n}\n.border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n}\n.border-0 {\n border-style: var(--tw-border-style);\n border-width: 0px;\n}\n.border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n}\n.border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n}\n.border-border {\n border-color: var(--t3-border);\n}\n.border-input {\n border-color: var(--t3-input);\n}\n.border-primary {\n border-color: var(--t3-primary);\n}\n.border-transparent {\n border-color: transparent;\n}\n.border-b-transparent {\n border-bottom-color: transparent;\n}\n.bg-background {\n background-color: var(--t3-background);\n}\n.bg-muted {\n background-color: var(--t3-muted);\n}\n.bg-muted\\/40 {\n background-color: var(--t3-muted);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-muted) 40%, transparent);\n }\n}\n.bg-popover\\/95 {\n background-color: var(--t3-popover);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-popover) 95%, transparent);\n }\n}\n.bg-popover\\/96 {\n background-color: var(--t3-popover);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-popover) 96%, transparent);\n }\n}\n.bg-primary {\n background-color: var(--t3-primary);\n}\n.bg-primary\\/10 {\n background-color: var(--t3-primary);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-primary) 10%, transparent);\n }\n}\n.bg-transparent {\n background-color: transparent;\n}\n.p-0 {\n padding: 0px;\n}\n.p-1 {\n padding: var(--spacing);\n}\n.p-2 {\n padding: calc(var(--spacing) * 2);\n}\n.px-0 {\n padding-inline: 0px;\n}\n.px-1 {\n padding-inline: var(--spacing);\n}\n.px-2 {\n padding-inline: calc(var(--spacing) * 2);\n}\n.px-2\\.5 {\n padding-inline: calc(var(--spacing) * 2.5);\n}\n.px-3 {\n padding-inline: calc(var(--spacing) * 3);\n}\n.py-1 {\n padding-block: var(--spacing);\n}\n.py-1\\.5 {\n padding-block: calc(var(--spacing) * 1.5);\n}\n.py-2 {\n padding-block: calc(var(--spacing) * 2);\n}\n.font-mono {\n font-family: var(--t3-font-mono);\n}\n.font-sans {\n font-family: var(--t3-font-sans);\n}\n.text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n}\n.text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n}\n.text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n}\n.leading-5 {\n --tw-leading: calc(var(--spacing) * 5);\n line-height: calc(var(--spacing) * 5);\n}\n.font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n}\n.font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n}\n.font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n}\n.text-foreground {\n color: var(--t3-foreground);\n}\n.text-muted-foreground {\n color: var(--t3-muted-foreground);\n}\n.text-popover-foreground {\n color: var(--t3-popover-foreground);\n}\n.text-primary {\n color: var(--t3-primary);\n}\n.text-primary-foreground {\n color: var(--t3-primary-foreground);\n}\n.shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-md {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-sm {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-xs {\n --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.ring-0 {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.blur {\n --tw-blur: blur(8px);\n filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n}\n.backdrop-blur-xl {\n --tw-backdrop-blur: blur(var(--blur-xl));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n}\n.outline-none {\n --tw-outline-style: none;\n outline-style: none;\n}\n.select-none {\n -webkit-user-select: none;\n user-select: none;\n}\n.placeholder\\:text-muted-foreground::placeholder {\n color: var(--t3-muted-foreground);\n}\n@media (hover: hover) {\n .hover\\:bg-accent:hover {\n background-color: var(--t3-accent);\n }\n .hover\\:bg-primary\\/90:hover {\n background-color: var(--t3-primary);\n }\n @supports (color: color-mix(in lab, red, red)) {\n .hover\\:bg-primary\\/90:hover {\n background-color: color-mix(in oklab, var(--t3-primary) 90%, transparent);\n }\n }\n .hover\\:text-accent-foreground:hover {\n color: var(--t3-accent-foreground);\n }\n}\n.focus\\:border-b-primary:focus {\n border-bottom-color: var(--t3-primary);\n}\n.focus\\:ring-0:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.focus\\:outline-none:focus {\n --tw-outline-style: none;\n outline-style: none;\n}\n.disabled\\:pointer-events-none:disabled {\n pointer-events: none;\n}\n.disabled\\:opacity-60:disabled {\n opacity: 60%;\n}\n:host {\n --t3-font-sans: "DM Sans Variable", "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,\n sans-serif;\n --t3-font-mono: "SF Mono", "SFMono-Regular", "JetBrains Mono", Consolas, "Liberation Mono", Menlo, monospace;\n --t3-radius: 0.625rem;\n --t3-background: white;\n --t3-foreground: oklch(0.269 0 0);\n --t3-popover: white;\n --t3-popover-foreground: oklch(0.269 0 0);\n --t3-primary: oklch(0.488 0.217 264);\n --t3-primary-foreground: white;\n --t3-muted: rgb(0 0 0 / 4%);\n --t3-muted-foreground: oklch(0.556 0 0);\n --t3-accent: rgb(0 0 0 / 4%);\n --t3-accent-foreground: oklch(0.269 0 0);\n --t3-border: rgb(0 0 0 / 8%);\n --t3-input: rgb(0 0 0 / 10%);\n --t3-ring: oklch(0.488 0.217 264);\n color: var(--t3-foreground);\n font-family: var(--t3-font-sans);\n}\n* {\n box-sizing: border-box;\n border-color: var(--t3-border);\n}\nbutton, input, select, textarea {\n font: inherit;\n}\nbutton:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {\n outline: 2px solid var(--t3-ring);\n @supports (color: color-mix(in lab, red, red)) {\n outline: 2px solid color-mix(in srgb, var(--t3-ring) 72%, transparent);\n }\n outline-offset: 1px;\n}\n@property --tw-translate-x {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-leading {\n syntax: "*";\n inherits: false;\n}\n@property --tw-font-weight {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: "<percentage>";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: "<percentage>";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: "<length>";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: "*";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-blur {\n syntax: "*";\n inherits: false;\n}\n@property --tw-brightness {\n syntax: "*";\n inherits: false;\n}\n@property --tw-contrast {\n syntax: "*";\n inherits: false;\n}\n@property --tw-grayscale {\n syntax: "*";\n inherits: false;\n}\n@property --tw-hue-rotate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-invert {\n syntax: "*";\n inherits: false;\n}\n@property --tw-opacity {\n syntax: "*";\n inherits: false;\n}\n@property --tw-saturate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-sepia {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow-alpha {\n syntax: "<percentage>";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-drop-shadow-size {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-blur {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: "*";\n inherits: false;\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-border-style: solid;\n --tw-leading: initial;\n --tw-font-weight: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-blur: initial;\n --tw-brightness: initial;\n --tw-contrast: initial;\n --tw-grayscale: initial;\n --tw-hue-rotate: initial;\n --tw-invert: initial;\n --tw-opacity: initial;\n --tw-saturate: initial;\n --tw-sepia: initial;\n --tw-drop-shadow: initial;\n --tw-drop-shadow-color: initial;\n --tw-drop-shadow-alpha: 100%;\n --tw-drop-shadow-size: initial;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n }\n }\n}\n';3 '/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n@layer properties;\n:root, :host {\n --spacing: 0.25rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --blur-xl: 24px;\n --default-font-family: var(--t3-font-sans);\n --default-mono-font-family: var(--t3-font-mono);\n}\n*, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n}\nhtml, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, \'Helvetica Neue\', \'Noto Sans\', Arial, sans-serif, \'Apple Color Emoji\', \'Segoe UI Emoji\', \'Segoe UI Symbol\', \'Noto Color Emoji\');\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n}\nhr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n}\nabbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n}\nh1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n}\na {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n}\nb, strong {\n font-weight: bolder;\n}\ncode, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \'Liberation Mono\', \'Courier New\', monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n}\nsmall {\n font-size: 80%;\n}\nsub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsub {\n bottom: -0.25em;\n}\nsup {\n top: -0.5em;\n}\ntable {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n}\n:-moz-focusring:where(:not(iframe)) {\n outline: auto;\n}\nprogress {\n vertical-align: baseline;\n}\nsummary {\n display: list-item;\n}\nol, ul, menu {\n list-style: none;\n}\nimg, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n}\nimg, video {\n max-width: 100%;\n height: auto;\n}\nbutton, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n}\n:where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n}\n:where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n}\n::file-selector-button {\n margin-inline-end: 4px;\n}\n::placeholder {\n opacity: 1;\n}\n@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n}\ntextarea {\n resize: vertical;\n}\n::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n}\n::-webkit-datetime-edit {\n display: inline-flex;\n}\n::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n}\n::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n}\n::-webkit-calendar-picker-indicator {\n line-height: 1;\n}\n:-moz-ui-invalid {\n box-shadow: none;\n}\nbutton, input:where([type=\'button\'], [type=\'reset\'], [type=\'submit\']), ::file-selector-button {\n appearance: button;\n}\n::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n}\n[hidden]:where(:not([hidden=\'until-found\'])) {\n display: none !important;\n}\n.pointer-events-auto {\n pointer-events: auto;\n}\n.pointer-events-none {\n pointer-events: none;\n}\n.visible {\n visibility: visible;\n}\n.absolute {\n position: absolute;\n}\n.fixed {\n position: fixed;\n}\n.static {\n position: static;\n}\n.inset-0 {\n inset: 0px;\n}\n.top-1\\/2 {\n top: calc(1 / 2 * 100%);\n}\n.top-2\\.5 {\n top: calc(var(--spacing) * 2.5);\n}\n.right-2 {\n right: calc(var(--spacing) * 2);\n}\n.left-1\\/2 {\n left: calc(1 / 2 * 100%);\n}\n.z-1 {\n z-index: 1;\n}\n.block {\n display: block;\n}\n.flex {\n display: flex;\n}\n.grid {\n display: grid;\n}\n.hidden {\n display: none;\n}\n.inline-flex {\n display: inline-flex;\n}\n.h-7 {\n height: calc(var(--spacing) * 7);\n}\n.h-8 {\n height: calc(var(--spacing) * 8);\n}\n.max-h-24 {\n max-height: calc(var(--spacing) * 24);\n}\n.max-h-\\[calc\\(100vh-16px\\)\\] {\n max-height: calc(100vh - 16px);\n}\n.max-h-\\[min\\(176px\\,calc\\(100vh-180px\\)\\)\\] {\n max-height: min(176px, calc(100vh - 180px));\n}\n.min-h-7 {\n min-height: calc(var(--spacing) * 7);\n}\n.min-h-8 {\n min-height: calc(var(--spacing) * 8);\n}\n.w-6 {\n width: calc(var(--spacing) * 6);\n}\n.w-8 {\n width: calc(var(--spacing) * 8);\n}\n.w-\\[min\\(360px\\,calc\\(100vw-16px\\)\\)\\] {\n width: min(360px, calc(100vw - 16px));\n}\n.w-full {\n width: 100%;\n}\n.max-w-70 {\n max-width: calc(var(--spacing) * 70);\n}\n.min-w-0 {\n min-width: 0px;\n}\n.flex-1 {\n flex: 1;\n}\n.shrink-0 {\n flex-shrink: 0;\n}\n.-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n}\n.-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n}\n.cursor-grab {\n cursor: grab;\n}\n.cursor-pointer {\n cursor: pointer;\n}\n.resize {\n resize: both;\n}\n.resize-none {\n resize: none;\n}\n.appearance-none {\n appearance: none;\n}\n.grid-cols-\\[22px_minmax\\(0\\,1fr\\)\\] {\n grid-template-columns: 22px minmax(0,1fr);\n}\n.grid-cols-\\[82px_minmax\\(0\\,1fr\\)\\] {\n grid-template-columns: 82px minmax(0,1fr);\n}\n.flex-col {\n flex-direction: column;\n}\n.items-center {\n align-items: center;\n}\n.items-start {\n align-items: flex-start;\n}\n.justify-center {\n justify-content: center;\n}\n.gap-0\\.5 {\n gap: calc(var(--spacing) * 0.5);\n}\n.gap-1 {\n gap: var(--spacing);\n}\n.gap-2 {\n gap: calc(var(--spacing) * 2);\n}\n.overflow-auto {\n overflow: auto;\n}\n.overflow-hidden {\n overflow: hidden;\n}\n.overflow-y-hidden {\n overflow-y: hidden;\n}\n.rounded-lg {\n border-radius: var(--t3-radius);\n}\n.rounded-md {\n border-radius: calc(var(--t3-radius) - 2px);\n}\n.rounded-xl {\n border-radius: calc(var(--t3-radius) + 4px);\n}\n.border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n}\n.border-0 {\n border-style: var(--tw-border-style);\n border-width: 0px;\n}\n.border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n}\n.border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n}\n.border-border {\n border-color: var(--t3-border);\n}\n.border-input {\n border-color: var(--t3-input);\n}\n.border-primary {\n border-color: var(--t3-primary);\n}\n.border-transparent {\n border-color: transparent;\n}\n.border-b-transparent {\n border-bottom-color: transparent;\n}\n.bg-background {\n background-color: var(--t3-background);\n}\n.bg-muted {\n background-color: var(--t3-muted);\n}\n.bg-muted\\/40 {\n background-color: var(--t3-muted);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-muted) 40%, transparent);\n }\n}\n.bg-popover\\/95 {\n background-color: var(--t3-popover);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-popover) 95%, transparent);\n }\n}\n.bg-popover\\/96 {\n background-color: var(--t3-popover);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-popover) 96%, transparent);\n }\n}\n.bg-primary {\n background-color: var(--t3-primary);\n}\n.bg-primary\\/10 {\n background-color: var(--t3-primary);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--t3-primary) 10%, transparent);\n }\n}\n.bg-transparent {\n background-color: transparent;\n}\n.p-0 {\n padding: 0px;\n}\n.p-1 {\n padding: var(--spacing);\n}\n.p-2 {\n padding: calc(var(--spacing) * 2);\n}\n.px-0 {\n padding-inline: 0px;\n}\n.px-1 {\n padding-inline: var(--spacing);\n}\n.px-2 {\n padding-inline: calc(var(--spacing) * 2);\n}\n.px-2\\.5 {\n padding-inline: calc(var(--spacing) * 2.5);\n}\n.px-3 {\n padding-inline: calc(var(--spacing) * 3);\n}\n.py-1 {\n padding-block: var(--spacing);\n}\n.py-1\\.5 {\n padding-block: calc(var(--spacing) * 1.5);\n}\n.py-2 {\n padding-block: calc(var(--spacing) * 2);\n}\n.font-mono {\n font-family: var(--t3-font-mono);\n}\n.font-sans {\n font-family: var(--t3-font-sans);\n}\n.text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n}\n.text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n}\n.text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n}\n.leading-5 {\n --tw-leading: calc(var(--spacing) * 5);\n line-height: calc(var(--spacing) * 5);\n}\n.font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n}\n.font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n}\n.font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n}\n.text-foreground {\n color: var(--t3-foreground);\n}\n.text-muted-foreground {\n color: var(--t3-muted-foreground);\n}\n.text-popover-foreground {\n color: var(--t3-popover-foreground);\n}\n.text-primary {\n color: var(--t3-primary);\n}\n.text-primary-foreground {\n color: var(--t3-primary-foreground);\n}\n.shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-md {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-sm {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.shadow-xs {\n --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.ring-0 {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.blur {\n --tw-blur: blur(8px);\n filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n}\n.backdrop-blur-xl {\n --tw-backdrop-blur: blur(var(--blur-xl));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n}\n.outline-none {\n --tw-outline-style: none;\n outline-style: none;\n}\n.select-none {\n -webkit-user-select: none;\n user-select: none;\n}\n.placeholder\\:text-muted-foreground::placeholder {\n color: var(--t3-muted-foreground);\n}\n@media (hover: hover) {\n .hover\\:bg-accent:hover {\n background-color: var(--t3-accent);\n }\n .hover\\:bg-primary\\/90:hover {\n background-color: var(--t3-primary);\n }\n @supports (color: color-mix(in lab, red, red)) {\n .hover\\:bg-primary\\/90:hover {\n background-color: color-mix(in oklab, var(--t3-primary) 90%, transparent);\n }\n }\n .hover\\:text-accent-foreground:hover {\n color: var(--t3-accent-foreground);\n }\n}\n.focus\\:border-b-primary:focus {\n border-bottom-color: var(--t3-primary);\n}\n.focus\\:ring-0:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n}\n.focus\\:outline-none:focus {\n --tw-outline-style: none;\n outline-style: none;\n}\n.disabled\\:pointer-events-none:disabled {\n pointer-events: none;\n}\n.disabled\\:opacity-60:disabled {\n opacity: 60%;\n}\n:host {\n --t3-font-sans: "DM Sans Variable", "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,\n sans-serif;\n --t3-font-mono: "SF Mono", "SFMono-Regular", "JetBrains Mono", Consolas, "Liberation Mono", Menlo, monospace;\n --t3-radius: 0.625rem;\n --t3-background: white;\n --t3-foreground: oklch(0.269 0 0);\n --t3-popover: white;\n --t3-popover-foreground: oklch(0.269 0 0);\n --t3-primary: oklch(0.488 0.217 264);\n --t3-primary-foreground: white;\n --t3-muted: rgb(0 0 0 / 4%);\n --t3-muted-foreground: oklch(0.556 0 0);\n --t3-accent: rgb(0 0 0 / 4%);\n --t3-accent-foreground: oklch(0.269 0 0);\n --t3-border: rgb(0 0 0 / 8%);\n --t3-input: rgb(0 0 0 / 10%);\n --t3-ring: oklch(0.488 0.217 264);\n color: var(--t3-foreground);\n font-family: var(--t3-font-sans);\n}\n* {\n box-sizing: border-box;\n border-color: var(--t3-border);\n}\nbutton, input, select, textarea {\n font: inherit;\n}\nbutton:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {\n outline: 2px solid var(--t3-ring);\n @supports (color: color-mix(in lab, red, red)) {\n outline: 2px solid color-mix(in srgb, var(--t3-ring) 72%, transparent);\n }\n outline-offset: -2px;\n}\n@property --tw-translate-x {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: "*";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: "*";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-leading {\n syntax: "*";\n inherits: false;\n}\n@property --tw-font-weight {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: "<percentage>";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: "<percentage>";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: "*";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: "<length>";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: "*";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: "*";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-blur {\n syntax: "*";\n inherits: false;\n}\n@property --tw-brightness {\n syntax: "*";\n inherits: false;\n}\n@property --tw-contrast {\n syntax: "*";\n inherits: false;\n}\n@property --tw-grayscale {\n syntax: "*";\n inherits: false;\n}\n@property --tw-hue-rotate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-invert {\n syntax: "*";\n inherits: false;\n}\n@property --tw-opacity {\n syntax: "*";\n inherits: false;\n}\n@property --tw-saturate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-sepia {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow-color {\n syntax: "*";\n inherits: false;\n}\n@property --tw-drop-shadow-alpha {\n syntax: "<percentage>";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-drop-shadow-size {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-blur {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: "*";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: "*";\n inherits: false;\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-border-style: solid;\n --tw-leading: initial;\n --tw-font-weight: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-blur: initial;\n --tw-brightness: initial;\n --tw-contrast: initial;\n --tw-grayscale: initial;\n --tw-hue-rotate: initial;\n --tw-invert: initial;\n --tw-opacity: initial;\n --tw-saturate: initial;\n --tw-sepia: initial;\n --tw-drop-shadow: initial;\n --tw-drop-shadow-color: initial;\n --tw-drop-shadow-alpha: 100%;\n --tw-drop-shadow-size: initial;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n }\n }\n}\n';
apps/desktop/src/preview/PickPreload.ts+46-6
...@@ -316,12 +316,43 @@ function createBox(color: string, fill: string): HTMLDivElement {...@@ -316,12 +316,43 @@ function createBox(color: string, fill: string): HTMLDivElement {
316}316}
317317
318function positionBox(node: HTMLElement, rect: PreviewAnnotationRect): void {318function positionBox(node: HTMLElement, rect: PreviewAnnotationRect): void {
319 if (rect.width <= 0 || rect.height <= 0) {
320 node.style.display = "none";
321 return;
322 }
319 node.style.display = "block";323 node.style.display = "block";
320 node.style.transform = `translate(${rect.x}px, ${rect.y}px)`;324 node.style.transform = `translate(${rect.x}px, ${rect.y}px)`;
321 node.style.width = `${rect.width}px`;325 node.style.width = `${rect.width}px`;
322 node.style.height = `${rect.height}px`;326 node.style.height = `${rect.height}px`;
323}327}
324328
329/** Clamps a box to the viewport and clipping ancestors, so its border is never painted off-screen. */
330function visibleElementRect(element: Element): PreviewAnnotationRect {
331 const rect = element.getBoundingClientRect();
332 let left = Math.max(0, rect.left);
333 let top = Math.max(0, rect.top);
334 let right = Math.min(document.documentElement.clientWidth, rect.right);
335 let bottom = Math.min(document.documentElement.clientHeight, rect.bottom);
336 if (getComputedStyle(element).position !== "fixed") {
337 for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
338 if (ancestor === document.body || ancestor === document.documentElement) break;
339 const style = getComputedStyle(ancestor);
340 const bounds = ancestor.getBoundingClientRect();
341 const clipLeft = bounds.left + ancestor.clientLeft;
342 const clipTop = bounds.top + ancestor.clientTop;
343 if (style.overflowX !== "visible") {
344 left = Math.max(left, clipLeft);
345 right = Math.min(right, clipLeft + ancestor.clientWidth);
346 }
347 if (style.overflowY !== "visible") {
348 top = Math.max(top, clipTop);
349 bottom = Math.min(bottom, clipTop + ancestor.clientHeight);
350 }
351 }
352 }
353 return { x: left, y: top, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
354}
355
325function createLabel(): HTMLDivElement {356function createLabel(): HTMLDivElement {
326 const label = document.createElement("div");357 const label = document.createElement("div");
327 label.setAttribute(OVERLAY_ATTRIBUTE, "");358 label.setAttribute(OVERLAY_ATTRIBUTE, "");
...@@ -343,11 +374,15 @@ function updateSelectedVisual(target: SelectedElement): void {...@@ -343,11 +374,15 @@ function updateSelectedVisual(target: SelectedElement): void {
343 target.label.style.display = "none";374 target.label.style.display = "none";
344 return;375 return;
345 }376 }
346 const rect = target.element.getBoundingClientRect();377 const rect = visibleElementRect(target.element);
347 positionBox(target.outline, rectFromDomRect(rect));378 positionBox(target.outline, rect);
379 if (rect.width === 0 || rect.height === 0) {
380 target.label.style.display = "none";
381 return;
382 }
348 target.label.textContent = describeRawElement(target.element);383 target.label.textContent = describeRawElement(target.element);
349 target.label.style.display = "block";384 target.label.style.display = "block";
350 target.label.style.transform = `translate(${Math.max(4, rect.left)}px, ${Math.max(4, rect.top - 22)}px)`;385 target.label.style.transform = `translate(${Math.max(4, rect.x)}px, ${Math.max(4, rect.y - 22)}px)`;
351}386}
352387
353function toStackFrame(frame: {388function toStackFrame(frame: {
...@@ -995,8 +1030,9 @@ function startAnnotation(sendEnabled: boolean): void {...@@ -995,8 +1030,9 @@ function startAnnotation(sendEnabled: boolean): void {
995 const getAnnotationBounds = (): PreviewAnnotationRect | null =>1030 const getAnnotationBounds = (): PreviewAnnotationRect | null =>
996 unionRects(1031 unionRects(
997 [1032 [
998 ...Array.from(selected.values(), (target) =>1033 // Follow the visible outline, so a clipped selection does not anchor the editor off-screen.
999 rectFromDomRect(target.element.getBoundingClientRect()),1034 ...Array.from(selected.values(), (target) => visibleElementRect(target.element)).filter(
1035 (rect) => rect.width > 0 && rect.height > 0,
1000 ),1036 ),
1001 ...regions.map((region) => region.rect),1037 ...regions.map((region) => region.rect),
1002 ...strokes.map((stroke) => stroke.bounds),1038 ...strokes.map((stroke) => stroke.bounds),
...@@ -1108,6 +1144,8 @@ function startAnnotation(sendEnabled: boolean): void {...@@ -1108,6 +1144,8 @@ function startAnnotation(sendEnabled: boolean): void {
1108 for (const target of selected.values()) updateSelectedVisual(target);1144 for (const target of selected.values()) updateSelectedVisual(target);
1109 queueEditorLayout();1145 queueEditorLayout();
1110 };1146 };
1147 // A scrollbar appearing narrows the viewport without a window resize.
1148 const rootResizeObserver = new ResizeObserver(repaint);
11111149
1112 const removeTargetAtPoint = (x: number, y: number): boolean => {1150 const removeTargetAtPoint = (x: number, y: number): boolean => {
1113 for (const target of Array.from(selected.values()).toReversed()) {1151 for (const target of Array.from(selected.values()).toReversed()) {
...@@ -1192,7 +1230,7 @@ function startAnnotation(sendEnabled: boolean): void {...@@ -1192,7 +1230,7 @@ function startAnnotation(sendEnabled: boolean): void {
1192 }1230 }
1193 if (tool === "select" && dragStart === null) {1231 if (tool === "select" && dragStart === null) {
1194 const target = pickFromPoint(event.clientX, event.clientY);1232 const target = pickFromPoint(event.clientX, event.clientY);
1195 if (target) positionBox(hoverOutline, rectFromDomRect(target.getBoundingClientRect()));1233 if (target) positionBox(hoverOutline, visibleElementRect(target));
1196 else clearHoverOutline();1234 else clearHoverOutline();
1197 return;1235 return;
1198 }1236 }
...@@ -1310,6 +1348,7 @@ function startAnnotation(sendEnabled: boolean): void {...@@ -1310,6 +1348,7 @@ function startAnnotation(sendEnabled: boolean): void {
1310 const teardown = (notifyMain: boolean): void => {1348 const teardown = (notifyMain: boolean): void => {
1311 if (finished) return;1349 if (finished) return;
1312 finished = true;1350 finished = true;
1351 rootResizeObserver.disconnect();
1313 restoreStyles();1352 restoreStyles();
1314 window.removeEventListener("pointermove", onPointerMove, true);1353 window.removeEventListener("pointermove", onPointerMove, true);
1315 window.removeEventListener("pointerdown", onPointerDown, true);1354 window.removeEventListener("pointerdown", onPointerDown, true);
...@@ -1445,6 +1484,7 @@ function startAnnotation(sendEnabled: boolean): void {...@@ -1445,6 +1484,7 @@ function startAnnotation(sendEnabled: boolean): void {
1445 ipcRenderer.on(CANCEL_PICK_CHANNEL, onCancel);1484 ipcRenderer.on(CANCEL_PICK_CHANNEL, onCancel);
1446 ipcRenderer.on(ANNOTATION_CAPTURED_CHANNEL, onCaptured);1485 ipcRenderer.on(ANNOTATION_CAPTURED_CHANNEL, onCaptured);
1447 document.documentElement.appendChild(host);1486 document.documentElement.appendChild(host);
1487 rootResizeObserver.observe(document.documentElement);
1448 refreshToolButtons();1488 refreshToolButtons();
1449 updateStatus();1489 updateStatus();
1450 activeSession = {1490 activeSession = {
apps/server/src/htmlRender/HtmlRender.test.ts+4-1
...@@ -244,10 +244,12 @@ describe("HtmlRender", () => {...@@ -244,10 +244,12 @@ describe("HtmlRender", () => {
244 const htmlRender = yield* HtmlRender.HtmlRender;244 const htmlRender = yield* HtmlRender.HtmlRender;
245 const preview = yield* htmlRender.preview({245 const preview = yield* htmlRender.preview({
246 html: [246 html: [
247 '<!doctype html><html><head></head><body><div style="height:300px;background:var(--accent)"></div>',247 '<!doctype html><html><head></head><body><div id="page" tabindex="0" style="height:300px;background:var(--accent)"></div>',
248 '<img src="/nonexistent/t3-missing.png" hidden>',248 '<img src="/nonexistent/t3-missing.png" hidden>',
249 "<script>",249 "<script>",
250 "const root = getComputedStyle(document.documentElement);",250 "const root = getComputedStyle(document.documentElement);",
251 'const page = document.getElementById("page"); page.focus();',
252 'const focus = getComputedStyle(page); console.log("focus", focus.outlineWidth, focus.outlineOffset, page.getBoundingClientRect().width, document.documentElement.scrollWidth);',
251 'console.log("ready", 3); console.info(root.getPropertyValue("--font-sans"));',253 'console.log("ready", 3); console.info(root.getPropertyValue("--font-sans"));',
252 'console.warn(root.getPropertyValue("--background")); console.error("boom");',254 'console.warn(root.getPropertyValue("--background")); console.error("boom");',
253 "</script>",255 "</script>",
...@@ -271,6 +273,7 @@ describe("HtmlRender", () => {...@@ -271,6 +273,7 @@ describe("HtmlRender", () => {
271 expect(preview.consoleMessages).toEqual(273 expect(preview.consoleMessages).toEqual(
272 expect.arrayContaining([274 expect.arrayContaining([
273 { level: "log", text: "ready 3" },275 { level: "log", text: "ready 3" },
276 { level: "log", text: "focus 2px -2px 400 400" },
274 { level: "info", text: HTML_RENDER_MEASURE_FONTS.sans },277 { level: "info", text: HTML_RENDER_MEASURE_FONTS.sans },
275 {278 {
276 level: "warning",279 level: "warning",
apps/web/src/components/ContextChip.tsx+2-2
...@@ -23,7 +23,7 @@ import { cn } from "~/lib/utils";...@@ -23,7 +23,7 @@ import { cn } from "~/lib/utils";
23 * span with tabIndex for a tooltip) gets the focus outline.23 * span with tabIndex for a tooltip) gets the focus outline.
24 */24 */
25const contextChipVariants = cva(25const contextChipVariants = cva(
26 "inline-flex h-[1.41em] max-w-full items-center gap-[0.33em] rounded-[0.5em] border px-[0.5em] align-middle font-medium text-[0.86em] leading-none [&_svg]:block [&_svg]:size-[1.17em] [&_svg]:shrink-0 [&_svg]:self-center [button&,a&,[data-popup-open]&]:cursor-pointer [button&,a&]:transition-colors [button&,a&]:motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground disabled:cursor-default",26 "inline-flex h-[1.41em] max-w-full items-center gap-[0.33em] rounded-[0.5em] border px-[0.5em] align-middle font-medium text-[0.86em] leading-none [&_svg]:block [&_svg]:size-[1.17em] [&_svg]:shrink-0 [&_svg]:self-center [button&,a&,[data-popup-open]&]:cursor-pointer [button&,a&]:transition-colors [button&,a&]:motion-reduce:transition-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-foreground disabled:cursor-default",
27 {27 {
28 defaultVariants: { kind: "neutral" },28 defaultVariants: { kind: "neutral" },
29 variants: {29 variants: {
...@@ -117,7 +117,7 @@ function ContextChipLabel({ className, ...props }: React.ComponentProps<"span">)...@@ -117,7 +117,7 @@ function ContextChipLabel({ className, ...props }: React.ComponentProps<"span">)
117function ContextChipAction({ className, render, ...props }: useRender.ComponentProps<"button">) {117function ContextChipAction({ className, render, ...props }: useRender.ComponentProps<"button">) {
118 const defaultProps = {118 const defaultProps = {
119 className: cn(119 className: cn(
120 "ml-[0.17em] inline-flex size-[1.17em] shrink-0 cursor-pointer items-center justify-center rounded-sm text-current transition-colors hover:bg-(--context-chip-accent,var(--color-foreground))/17 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring motion-reduce:transition-none [&_svg]:size-[0.85em]",120 "ml-[0.17em] inline-flex size-[1.17em] shrink-0 cursor-pointer items-center justify-center rounded-sm text-current transition-colors hover:bg-(--context-chip-accent,var(--color-foreground))/17 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring motion-reduce:transition-none [&_svg]:size-[0.85em]",
121 className,121 className,
122 ),122 ),
123 "data-slot": "context-chip-action",123 "data-slot": "context-chip-action",
apps/web/src/components/GitActionsControl.tsx+5-5
...@@ -793,9 +793,9 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {...@@ -793,9 +793,9 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
793 value={option.value}793 value={option.value}
794 className={cn(794 className={cn(
795 "relative flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-3 text-left outline-none transition-[background-color,border-color,box-shadow]",795 "relative flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-3 text-left outline-none transition-[background-color,border-color,box-shadow]",
796 "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",796 "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
797 isSelected797 isSelected
798 ? "border-primary bg-background shadow-sm ring-2 ring-primary/35 dark:border-transparent dark:bg-primary/10 dark:shadow-none dark:ring-1 dark:ring-primary/30"798 ? "border-primary bg-background shadow-sm ring-2 ring-inset ring-primary/35 dark:border-transparent dark:bg-primary/10 dark:shadow-none dark:ring-1 dark:ring-primary/30"
799 : "border-border bg-background hover:border-foreground/20 hover:bg-muted/50 dark:border-transparent dark:bg-white/[0.035] dark:hover:bg-accent",799 : "border-border bg-background hover:border-foreground/20 hover:bg-muted/50 dark:border-transparent dark:bg-white/[0.035] dark:hover:bg-accent",
800 )}800 )}
801 >801 >
...@@ -817,7 +817,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {...@@ -817,7 +817,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
817 >817 >
818 Repository818 Repository
819 </label>819 </label>
820 <div className="flex items-stretch overflow-hidden rounded-md border border-input bg-background focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-ring">820 <div className="flex items-stretch overflow-hidden rounded-md border border-input bg-background focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-ring">
821 <span className="flex shrink-0 items-center gap-1.5 border-r border-input bg-muted/50 px-2.5 font-mono text-xs text-muted-foreground">821 <span className="flex shrink-0 items-center gap-1.5 border-r border-input bg-muted/50 px-2.5 font-mono text-xs text-muted-foreground">
822 <currentPublishProvider.Icon className="size-3.5" />822 <currentPublishProvider.Icon className="size-3.5" />
823 {publishHost}/823 {publishHost}/
...@@ -879,9 +879,9 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {...@@ -879,9 +879,9 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
879 value={option.value}879 value={option.value}
880 className={cn(880 className={cn(
881 "relative flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2.5 text-left outline-none transition-[background-color,border-color,box-shadow]",881 "relative flex cursor-pointer items-center gap-3 rounded-lg border px-3 py-2.5 text-left outline-none transition-[background-color,border-color,box-shadow]",
882 "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",882 "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
883 isSelected883 isSelected
884 ? "border-primary bg-background shadow-sm ring-2 ring-primary/35 dark:border-transparent dark:bg-primary/10 dark:shadow-none dark:ring-1 dark:ring-primary/30"884 ? "border-primary bg-background shadow-sm ring-2 ring-inset ring-primary/35 dark:border-transparent dark:bg-primary/10 dark:shadow-none dark:ring-1 dark:ring-primary/30"
885 : "border-border bg-background hover:border-foreground/20 hover:bg-muted/50 dark:border-transparent dark:bg-white/[0.035] dark:hover:bg-accent",885 : "border-border bg-background hover:border-foreground/20 hover:bg-muted/50 dark:border-transparent dark:bg-white/[0.035] dark:hover:bg-accent",
886 )}886 )}
887 >887 >
apps/web/src/components/LegacySidebar.tsx+5-5
...@@ -246,7 +246,7 @@ const PROJECT_GROUPING_MODE_LABELS: Record<SidebarProjectGroupingMode, string> =...@@ -246,7 +246,7 @@ const PROJECT_GROUPING_MODE_LABELS: Record<SidebarProjectGroupingMode, string> =
246 separate: "Keep separate",246 separate: "Keep separate",
247};247};
248const SIDEBAR_ICON_ACTION_BUTTON_CLASS =248const SIDEBAR_ICON_ACTION_BUTTON_CLASS =
249 "inline-flex h-6 min-w-6 cursor-pointer items-center justify-center rounded-md px-0.75 text-icon-muted hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring";249 "inline-flex h-6 min-w-6 cursor-pointer items-center justify-center rounded-md px-0.75 text-icon-muted hover:text-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring";
250250
251function SidebarThreadDetailPrewarmer({ threadRef }: { readonly threadRef: ScopedThreadRef }) {251function SidebarThreadDetailPrewarmer({ threadRef }: { readonly threadRef: ScopedThreadRef }) {
252 useEnvironmentThread(threadRef.environmentId, threadRef.threadId);252 useEnvironmentThread(threadRef.environmentId, threadRef.threadId);
...@@ -769,7 +769,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -769,7 +769,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
769 target="_blank"769 target="_blank"
770 rel="noopener noreferrer"770 rel="noopener noreferrer"
771 aria-label={prStatus.tooltip}771 aria-label={prStatus.tooltip}
772 className={`inline-flex items-center justify-center ${prStatus.colorClass} cursor-pointer rounded-sm outline-hidden focus-visible:ring-1 focus-visible:ring-ring`}772 className={`inline-flex items-center justify-center ${prStatus.colorClass} cursor-pointer rounded-sm outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring`}
773 onPointerDown={(event) => event.stopPropagation()}773 onPointerDown={(event) => event.stopPropagation()}
774 onClick={handlePrClick}774 onClick={handlePrClick}
775 >775 >
...@@ -835,7 +835,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -835,7 +835,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
835 <button835 <button
836 type="button"836 type="button"
837 aria-label={`Open localhost:${discoveredPorts[0]?.port ?? ""}`}837 aria-label={`Open localhost:${discoveredPorts[0]?.port ?? ""}`}
838 className="inline-flex cursor-pointer items-center justify-center text-success-foreground outline-hidden focus-visible:ring-1 focus-visible:ring-ring"838 className="inline-flex cursor-pointer items-center justify-center text-success-foreground outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"
839 onClick={handleOpenDiscoveredPort}839 onClick={handleOpenDiscoveredPort}
840 />840 />
841 }841 }
...@@ -880,7 +880,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -880,7 +880,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
880 data-thread-selection-safe880 data-thread-selection-safe
881 data-testid={`thread-archive-confirm-${thread.id}`}881 data-testid={`thread-archive-confirm-${thread.id}`}
882 aria-label={`Confirm archive ${thread.title}`}882 aria-label={`Confirm archive ${thread.title}`}
883 className="absolute top-1/2 right-1 inline-flex h-5 -translate-y-1/2 cursor-pointer items-center rounded-md bg-destructive/12 px-2 text-3xs font-medium text-destructive transition-colors hover:bg-destructive/18 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-destructive/40"883 className="absolute top-1/2 right-1 inline-flex h-5 -translate-y-1/2 cursor-pointer items-center rounded-md bg-destructive/12 px-2 text-3xs font-medium text-destructive transition-colors hover:bg-destructive/18 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-destructive/40"
884 onPointerDown={stopPropagationOnPointerDown}884 onPointerDown={stopPropagationOnPointerDown}
885 onClick={handleConfirmArchiveClick}885 onClick={handleConfirmArchiveClick}
886 >886 >
...@@ -2955,7 +2955,7 @@ function SortableProjectItem({...@@ -2955,7 +2955,7 @@ function SortableProjectItem({
2955 }}2955 }}
2956 className={`group/menu-item relative rounded-md ${2956 className={`group/menu-item relative rounded-md ${
2957 isDragging ? "z-20 opacity-80" : ""2957 isDragging ? "z-20 opacity-80" : ""
2958 } ${isOver && !isDragging ? "ring-1 ring-primary/40" : ""}`}2958 } ${isOver && !isDragging ? "ring-1 ring-inset ring-primary/40" : ""}`}
2959 data-sidebar="menu-item"2959 data-sidebar="menu-item"
2960 data-slot="sidebar-menu-item"2960 data-slot="sidebar-menu-item"
2961 >2961 >
apps/web/src/components/RightPanelTabs.tsx+1-1
...@@ -1193,7 +1193,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) {...@@ -1193,7 +1193,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
1193 {renamingDevice === surface.id ? (1193 {renamingDevice === surface.id ? (
1194 <input1194 <input
1195 aria-label="Device tab name"1195 aria-label="Device tab name"
1196 className="w-24 min-w-0 rounded-sm bg-background px-1 outline-none ring-1 ring-ring"1196 className="w-24 min-w-0 rounded-sm bg-background px-1 outline-none ring-1 ring-inset ring-ring"
1197 defaultValue={title}1197 defaultValue={title}
1198 ref={(element) => {1198 ref={(element) => {
1199 element?.focus();1199 element?.focus();
apps/web/src/components/Sidebar.tsx+3-3
...@@ -1763,7 +1763,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -1763,7 +1763,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
1763 type="button"1763 type="button"
1764 aria-label="Unpin thread"1764 aria-label="Unpin thread"
1765 onClick={handleUnpinClick}1765 onClick={handleUnpinClick}
1766 className="group/unpin inline-flex cursor-pointer items-center rounded-sm text-muted-foreground/65 outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"1766 className="group/unpin inline-flex cursor-pointer items-center rounded-sm text-muted-foreground/65 outline-none transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
1767 />1767 />
1768 }1768 }
1769 >1769 >
...@@ -1876,7 +1876,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -1876,7 +1876,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
1876 type="button"1876 type="button"
1877 aria-label="Dismiss Woke notification"1877 aria-label="Dismiss Woke notification"
1878 onClick={handleAcknowledgeWokeClick}1878 onClick={handleAcknowledgeWokeClick}
1879 className="inline-flex cursor-pointer items-center gap-1 rounded-sm text-xs font-medium text-warning-foreground outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring"1879 className="inline-flex cursor-pointer items-center gap-1 rounded-sm text-xs font-medium text-warning-foreground outline-none hover:underline focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
1880 >1880 >
1881 <AlarmClockIcon aria-hidden className="size-3" />1881 <AlarmClockIcon aria-hidden className="size-3" />
1882 <span role="status">Woke</span>1882 <span role="status">Woke</span>
...@@ -2040,7 +2040,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {...@@ -2040,7 +2040,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
2040 aria-label="Dismiss Woke notification"2040 aria-label="Dismiss Woke notification"
2041 onClick={handleAcknowledgeWokeClick}2041 onClick={handleAcknowledgeWokeClick}
2042 className={cn(2042 className={cn(
2043 "inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring",2043 "inline-flex cursor-pointer items-center gap-1 rounded-sm font-medium outline-none hover:underline focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
2044 topStatus.className,2044 topStatus.className,
2045 )}2045 )}
2046 >2046 >
apps/web/src/components/auth/ConnectAgentSurface.tsx+2-2
...@@ -311,9 +311,9 @@ function AccessOption({...@@ -311,9 +311,9 @@ function AccessOption({
311 value={access}311 value={access}
312 className={cn(312 className={cn(
313 "flex cursor-pointer items-start gap-3 rounded-lg border px-3 py-2.5 text-left outline-none transition-[background-color,border-color,box-shadow]",313 "flex cursor-pointer items-start gap-3 rounded-lg border px-3 py-2.5 text-left outline-none transition-[background-color,border-color,box-shadow]",
314 "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",314 "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
315 selected315 selected
316 ? "border-primary bg-background ring-2 ring-primary/25 dark:border-transparent dark:bg-primary/10 dark:ring-1 dark:ring-primary/30"316 ? "border-primary bg-background ring-2 ring-inset ring-primary/25 dark:border-transparent dark:bg-primary/10 dark:ring-1 dark:ring-primary/30"
317 : "border-border bg-background hover:bg-muted/50 dark:border-transparent dark:bg-white/[0.035] dark:hover:bg-accent",317 : "border-border bg-background hover:bg-muted/50 dark:border-transparent dark:bg-white/[0.035] dark:hover:bg-accent",
318 )}318 )}
319 >319 >
apps/web/src/components/chat/ChangedFilesTree.tsx+2-2
...@@ -178,7 +178,7 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {...@@ -178,7 +178,7 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
178 type="button"178 type="button"
179 data-scroll-anchor-ignore179 data-scroll-anchor-ignore
180 aria-expanded={isExpanded}180 aria-expanded={isExpanded}
181 className="group flex w-full items-center gap-2 rounded-md py-1.5 pr-2 text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"181 className="group flex w-full items-center gap-2 rounded-md py-1.5 pr-2 text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
182 style={{ paddingLeft: `${leftPadding}px` }}182 style={{ paddingLeft: `${leftPadding}px` }}
183 onClick={() => toggleDirectory(node.path)}183 onClick={() => toggleDirectory(node.path)}
184 >184 >
...@@ -213,7 +213,7 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {...@@ -213,7 +213,7 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
213 <button213 <button
214 key={`file:${node.path}`}214 key={`file:${node.path}`}
215 type="button"215 type="button"
216 className="group flex w-full items-center gap-2 rounded-md py-1.5 pr-2 text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"216 className="group flex w-full items-center gap-2 rounded-md py-1.5 pr-2 text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
217 style={{ paddingLeft: `${leftPadding}px` }}217 style={{ paddingLeft: `${leftPadding}px` }}
218 onClick={() => onOpenTurnDiff(runId, node.path)}218 onClick={() => onOpenTurnDiff(runId, node.path)}
219 onContextMenu={219 onContextMenu={
apps/web/src/components/chat/ChatComposer.tsx+3-3
...@@ -5314,7 +5314,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -5314,7 +5314,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
5314 {restingImagePreviewCounts.overflowCount > 0 ? (5314 {restingImagePreviewCounts.overflowCount > 0 ? (
5315 <button5315 <button
5316 type="button"5316 type="button"
5317 className="flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-md border border-border/70 bg-muted/60 font-medium text-secondary-label text-xs tabular-nums outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"5317 className="flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-md border border-border/70 bg-muted/60 font-medium text-secondary-label text-xs tabular-nums outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
5318 aria-label={`Show ${String(restingImagePreviewCounts.overflowCount)} more image attachments`}5318 aria-label={`Show ${String(restingImagePreviewCounts.overflowCount)} more image attachments`}
5319 onPointerDown={(event) => event.preventDefault()}5319 onPointerDown={(event) => event.preventDefault()}
5320 onClick={() => {5320 onClick={() => {
...@@ -6947,8 +6947,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -6947,8 +6947,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
6947 data-chat-composer-mobile-collapsed={isComposerCollapsedMobile ? "true" : "false"}6947 data-chat-composer-mobile-collapsed={isComposerCollapsedMobile ? "true" : "false"}
6948 className={cn(6948 className={cn(
6949 "rounded-3xl transition-[background-color] duration-200",6949 "rounded-3xl transition-[background-color] duration-200",
6950 "in-data-[thread-context-over]:bg-accent/45 in-data-[thread-context-over]:ring-1 in-data-[thread-context-over]:ring-primary/70",6950 "in-data-[thread-context-over]:bg-accent/45 in-data-[thread-context-over]:ring-1 in-data-[thread-context-over]:ring-inset in-data-[thread-context-over]:ring-primary/70",
6951 isDragOverComposer ? "bg-accent/45 ring-1 ring-primary/70" : null,6951 isDragOverComposer ? "bg-accent/45 ring-1 ring-inset ring-primary/70" : null,
6952 projectSelectionRequired ? "opacity-75" : null,6952 projectSelectionRequired ? "opacity-75" : null,
6953 composerProviderState.composerSurfaceClassName,6953 composerProviderState.composerSurfaceClassName,
6954 )}6954 )}
apps/web/src/components/chat/ChatHeader.tsx+4-4
...@@ -323,7 +323,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -323,7 +323,7 @@ export const ChatHeader = memo(function ChatHeader({
323 type="button"323 type="button"
324 aria-label={`New thread in ${activeProjectName}`}324 aria-label={`New thread in ${activeProjectName}`}
325 onClick={onNewThreadInProject}325 onClick={onNewThreadInProject}
326 className="inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"326 className="inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
327 />327 />
328 }328 }
329 >329 >
...@@ -350,7 +350,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -350,7 +350,7 @@ export const ChatHeader = memo(function ChatHeader({
350 type="button"350 type="button"
351 aria-label={`Open parent thread: ${parentThreadLink.title}`}351 aria-label={`Open parent thread: ${parentThreadLink.title}`}
352 onClick={() => onOpenThread(parentThreadLink.threadId)}352 onClick={() => onOpenThread(parentThreadLink.threadId)}
353 className="inline-flex min-w-0 max-w-full cursor-pointer items-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"353 className="inline-flex min-w-0 max-w-full cursor-pointer items-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
354 />354 />
355 }355 }
356 >356 >
...@@ -379,7 +379,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -379,7 +379,7 @@ export const ChatHeader = memo(function ChatHeader({
379 <input379 <input
380 autoFocus380 autoFocus
381 aria-label="Thread title"381 aria-label="Thread title"
382 className="min-w-0 flex-1 rounded-sm bg-transparent text-sm font-medium text-foreground outline-none ring-1 ring-ring/50 focus:ring-ring"382 className="min-w-0 flex-1 rounded-sm bg-transparent text-sm font-medium text-foreground outline-none ring-1 ring-inset ring-ring/50 focus:ring-ring"
383 defaultValue={renamingTitle}383 defaultValue={renamingTitle}
384 onBlur={(event) => {384 onBlur={(event) => {
385 if (renameCommittedRef.current) return;385 if (renameCommittedRef.current) return;
...@@ -409,7 +409,7 @@ export const ChatHeader = memo(function ChatHeader({...@@ -409,7 +409,7 @@ export const ChatHeader = memo(function ChatHeader({
409 onClick={openMenuFromTitle}409 onClick={openMenuFromTitle}
410 onDoubleClick={canOperateThread ? handleTitleDoubleClick : undefined}410 onDoubleClick={canOperateThread ? handleTitleDoubleClick : undefined}
411 onBlur={cancelPendingTitleMenu}411 onBlur={cancelPendingTitleMenu}
412 className="group/thread-title inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1 rounded-sm text-left focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"412 className="group/thread-title inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1 rounded-sm text-left focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
413 />413 />
414 }414 }
415 >415 >
apps/web/src/components/chat/ComposerControl.tsx+1-1
...@@ -16,7 +16,7 @@ export type ComposerControlSize = "sm" | "xs";...@@ -16,7 +16,7 @@ export type ComposerControlSize = "sm" | "xs";
16 */16 */
17function composerControlClassName(size: ComposerControlSize, className?: string) {17function composerControlClassName(size: ComposerControlSize, className?: string) {
18 return cn(18 return cn(
19 "relative inline-flex shrink-0 cursor-pointer items-center justify-center whitespace-nowrap rounded-(--control-radius) border border-transparent text-base outline-none hover:bg-accent data-pressed:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-disabled:pointer-events-none data-disabled:opacity-64 pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&:active:not([aria-haspopup])]:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:-mx-0.5 [&_svg[data-composer-control-icon]]:mx-0",19 "relative inline-flex shrink-0 cursor-pointer items-center justify-center whitespace-nowrap rounded-(--control-radius) border border-transparent text-base outline-none hover:bg-accent data-pressed:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-disabled:pointer-events-none data-disabled:opacity-64 pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&:active:not([aria-haspopup])]:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:-mx-0.5 [&_svg[data-composer-control-icon]]:mx-0",
20 size === "xs"20 size === "xs"
21 ? "h-7 gap-1 px-1.75 font-normal text-muted-foreground/70 text-sm hover:text-foreground/80 sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5 [&_svg[data-composer-control-chevron]]:ms-0 [&_svg[data-composer-control-chevron]]:-me-1"21 ? "h-7 gap-1 px-1.75 font-normal text-muted-foreground/70 text-sm hover:text-foreground/80 sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5 [&_svg[data-composer-control-chevron]]:ms-0 [&_svg[data-composer-control-chevron]]:-me-1"
22 : "h-7 gap-1.5 px-2.5 font-medium text-secondary-label [&_svg:not([class*='text-'])]:text-muted-foreground hover:text-foreground sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4",22 : "h-7 gap-1.5 px-2.5 font-medium text-secondary-label [&_svg:not([class*='text-'])]:text-muted-foreground hover:text-foreground sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4",
apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx+1-1
...@@ -261,7 +261,7 @@ const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard(...@@ -261,7 +261,7 @@ const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard(
261 (!customAnswerActive && progress.selectedOptionValues.includes(optionValue));261 (!customAnswerActive && progress.selectedOptionValues.includes(optionValue));
262 const shortcutKey = index < 9 ? index + 1 : null;262 const shortcutKey = index < 9 ? index + 1 : null;
263 const className = cn(263 const className = cn(
264 "group flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left outline-none transition-colors duration-150 focus-visible:ring-1 focus-visible:ring-primary/25",264 "group flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left outline-none transition-colors duration-150 focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary/25",
265 isSelected265 isSelected
266 ? "bg-muted/55 text-foreground"266 ? "bg-muted/55 text-foreground"
267 : "bg-transparent text-foreground/85 hover:bg-muted/30",267 : "bg-transparent text-foreground/85 hover:bg-muted/30",
apps/web/src/components/chat/ComposerPrimaryActions.tsx+2-2
...@@ -79,7 +79,7 @@ const formatPendingPrimaryActionLabel = (input: {...@@ -79,7 +79,7 @@ const formatPendingPrimaryActionLabel = (input: {
79// The composer's labeled primary actions (Submit, Refine, Implement) share the send button's79// The composer's labeled primary actions (Submit, Refine, Implement) share the send button's
80// message-action pill, so they are composer-owned buttons rather than restyled Buttons.80// message-action pill, so they are composer-owned buttons rather than restyled Buttons.
81const messageActionPillClassName =81const messageActionPillClassName =
82 "inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-full bg-message-action font-medium text-base text-message-action-foreground shadow-xs shadow-message-action/24 outline-none hover:bg-message-action-hover focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 disabled:shadow-none sm:text-sm";82 "inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-full bg-message-action font-medium text-base text-message-action-foreground shadow-xs shadow-message-action/24 outline-none hover:bg-message-action-hover focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 disabled:shadow-none sm:text-sm";
8383
84const preventPointerFocus: PointerEventHandler<HTMLElement> = (event) => {84const preventPointerFocus: PointerEventHandler<HTMLElement> = (event) => {
85 event.preventDefault();85 event.preventDefault();
...@@ -370,7 +370,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({...@@ -370,7 +370,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({
370 <button370 <button
371 type="button"371 type="button"
372 className={cn(372 className={cn(
373 "flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-full border px-2.5 text-xs tabular-nums outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-64 [&_svg]:pointer-events-none [&_svg]:size-3.5",373 "flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-full border px-2.5 text-xs tabular-nums outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-64 [&_svg]:pointer-events-none [&_svg]:size-3.5",
374 keepFullHistory374 keepFullHistory
375 ? "border-border text-muted-foreground hover:text-foreground"375 ? "border-border text-muted-foreground hover:text-foreground"
376 : "border-warning/40 text-warning hover:bg-warning/8",376 : "border-warning/40 text-warning hover:bg-warning/8",
apps/web/src/components/chat/ComposerServerUpdateStatus.tsx+1-1
...@@ -47,7 +47,7 @@ export function ComposerServerUpdateStatus({...@@ -47,7 +47,7 @@ export function ComposerServerUpdateStatus({
47 <button47 <button
48 type="button"48 type="button"
49 aria-label={`${title}: ${detail}`}49 aria-label={`${title}: ${detail}`}
50 className="block max-w-full cursor-help truncate rounded-sm text-left outline-none focus-visible:ring-2 focus-visible:ring-ring"50 className="block max-w-full cursor-help truncate rounded-sm text-left outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
51 onClick={() => setDetailsOpen(true)}51 onClick={() => setDetailsOpen(true)}
52 >52 >
53 {title}53 {title}
apps/web/src/components/chat/ComposerStashMenu.tsx+1-1
...@@ -164,7 +164,7 @@ export const ComposerStashMenu = memo(function ComposerStashMenu(props: {...@@ -164,7 +164,7 @@ export const ComposerStashMenu = memo(function ComposerStashMenu(props: {
164 <ComposerBanner.Content>164 <ComposerBanner.Content>
165 <button165 <button
166 type="button"166 type="button"
167 className="min-w-0 flex-1 cursor-pointer truncate text-left text-foreground/80 outline-none before:absolute before:inset-0 before:rounded-sm focus-visible:before:ring-2 focus-visible:before:ring-ring"167 className="min-w-0 flex-1 cursor-pointer truncate text-left text-foreground/80 outline-none before:absolute before:inset-0 before:rounded-sm focus-visible:before:ring-2 focus-visible:before:ring-inset focus-visible:before:ring-ring"
168 data-stash-restore={entry.id}168 data-stash-restore={entry.id}
169 aria-label={`Restore stashed prompt: ${stashEntrySnippet(entry)}`}169 aria-label={`Restore stashed prompt: ${stashEntrySnippet(entry)}`}
170 onPointerDown={(event) => event.preventDefault()}170 onPointerDown={(event) => event.preventDefault()}
apps/web/src/components/chat/DraftHeroHeadline.tsx+1-1
...@@ -312,7 +312,7 @@ export function DraftHeroHeadline({...@@ -312,7 +312,7 @@ export function DraftHeroHeadline({
312 <button312 <button
313 type="button"313 type="button"
314 onClick={openAddProject}314 onClick={openAddProject}
315 className="pointer-events-auto inline cursor-pointer border-muted-foreground/35 border-b border-dotted text-muted-foreground/60 transition-colors hover:border-muted-foreground/60 hover:text-muted-foreground/80 focus-visible:rounded-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"315 className="pointer-events-auto inline cursor-pointer border-muted-foreground/35 border-b border-dotted text-muted-foreground/60 transition-colors hover:border-muted-foreground/60 hover:text-muted-foreground/80 focus-visible:rounded-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
316 >316 >
317 {activeProjectTitle ?? "Add a project"}317 {activeProjectTitle ?? "Add a project"}
318 </button>318 </button>
apps/web/src/components/chat/MessagesTimeline.tsx+3-3
...@@ -1817,7 +1817,7 @@ function TimelineMinimap({...@@ -1817,7 +1817,7 @@ function TimelineMinimap({
1817 />1817 />
1818 <button1818 <button
1819 aria-label={`Jump to message: ${activeItem?.userText ?? "User message"}`}1819 aria-label={`Jump to message: ${activeItem?.userText ?? "User message"}`}
1820 className="absolute inset-y-0 left-0 w-full cursor-pointer bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/70"1820 className="absolute inset-y-0 left-0 w-full cursor-pointer bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"
1821 onBlur={() => setActiveIndex(null)}1821 onBlur={() => setActiveIndex(null)}
1822 onClick={(event) => {1822 onClick={(event) => {
1823 if (timelineMinimapEventTargetsPreview(event.target)) {1823 if (timelineMinimapEventTargetsPreview(event.target)) {
...@@ -2457,7 +2457,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message"...@@ -2457,7 +2457,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message"
2457 type="button"2457 type="button"
2458 aria-label={`Preview ${file.name}`}2458 aria-label={`Preview ${file.name}`}
2459 onClick={() => ctx.onFileOpen(file)}2459 onClick={() => ctx.onFileOpen(file)}
2460 className="focus-visible:ring-ring/70 flex min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-md py-1 text-left text-sm hover:underline focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset"2460 className="focus-visible:ring-ring/70 flex min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-md py-1 text-left text-sm hover:underline focus-visible:ring-2 focus-visible:ring-inset focus-visible:outline-none"
2461 >2461 >
2462 {fileIdentity}2462 {fileIdentity}
2463 <EyeIcon className="size-4 shrink-0" />2463 <EyeIcon className="size-4 shrink-0" />
...@@ -5674,7 +5674,7 @@ function WorkEntryLogRow(props: WorkEntryRowProps) {...@@ -5674,7 +5674,7 @@ function WorkEntryLogRow(props: WorkEntryRowProps) {
5674 {createdThread ? (5674 {createdThread ? (
5675 <button5675 <button
5676 type="button"5676 type="button"
5677 className="shrink-0 rounded-sm text-sm text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"5677 className="shrink-0 rounded-sm text-sm text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
5678 aria-label={`Open ${createdThread.title ?? "created thread"}`}5678 aria-label={`Open ${createdThread.title ?? "created thread"}`}
5679 onClick={(event) => {5679 onClick={(event) => {
5680 event.stopPropagation();5680 event.stopPropagation();
apps/web/src/components/chat/ThreadDetailsControl.tsx+1-1
...@@ -49,7 +49,7 @@ export function ThreadDetailsControl({...@@ -49,7 +49,7 @@ export function ThreadDetailsControl({
49 {49 {
50 type: render ? undefined : "button",50 type: render ? undefined : "button",
51 className: cn(51 className: cn(
52 "relative inline-flex shrink-0 cursor-pointer items-center whitespace-nowrap border outline-none transition-[box-shadow,scale] [&:active:not([aria-haspopup])]:scale-[0.97] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 disabled:pointer-events-none disabled:opacity-64 aria-disabled:cursor-not-allowed aria-disabled:opacity-64 [&_svg]:shrink-0",52 "relative inline-flex shrink-0 cursor-pointer items-center whitespace-nowrap border outline-none transition-[box-shadow,scale] [&:active:not([aria-haspopup])]:scale-[0.97] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 disabled:pointer-events-none disabled:opacity-64 aria-disabled:cursor-not-allowed aria-disabled:opacity-64 [&_svg]:shrink-0",
53 parts[part],53 parts[part],
54 multiline && "h-auto min-h-8 py-0.75 disabled:opacity-100 sm:h-auto",54 multiline && "h-auto min-h-8 py-0.75 disabled:opacity-100 sm:h-auto",
55 tone === "muted" && "text-muted-foreground/70 hover:text-foreground/80",55 tone === "muted" && "text-muted-foreground/70 hover:text-foreground/80",
apps/web/src/components/chat/V2LifecycleRow.tsx+1-1
...@@ -596,7 +596,7 @@ function HandoffEndpoint(props: {...@@ -596,7 +596,7 @@ function HandoffEndpoint(props: {
596 render={596 render={
597 <span597 <span
598 tabIndex={0}598 tabIndex={0}
599 className="inline-flex min-w-0 items-center gap-1 rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"599 className="inline-flex min-w-0 items-center gap-1 rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
600 >600 >
601 <ProviderInstanceIcon601 <ProviderInstanceIcon
602 driverKind={entry?.driverKind ?? ProviderDriverKind.make(props.instanceId)}602 driverKind={entry?.driverKind ?? ProviderDriverKind.make(props.instanceId)}
apps/web/src/components/chat/ZoomableImage.tsx+1-1
...@@ -150,7 +150,7 @@ export function ZoomableImage({...@@ -150,7 +150,7 @@ export function ZoomableImage({
150 aria-label={`${name}, zoomable image`}150 aria-label={`${name}, zoomable image`}
151 aria-description="Click to zoom in or return to fit. Scroll to zoom, drag to pan. Use Enter to toggle zoom, plus or minus to zoom, and 0 to fit."151 aria-description="Click to zoom in or return to fit. Scroll to zoom, drag to pan. Use Enter to toggle zoom, plus or minus to zoom, and 0 to fit."
152 tabIndex={0}152 tabIndex={0}
153 className="max-w-[var(--media-width)] overflow-auto overscroll-contain rounded-lg bg-background shadow-2xl ring-1 ring-border/70 outline-none focus-visible:ring-2 focus-visible:ring-ring"153 className="max-w-[var(--media-width)] overflow-auto overscroll-contain rounded-lg bg-background shadow-2xl ring-1 ring-border/70 outline-none focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-ring"
154 style={{154 style={{
155 width: width || undefined,155 width: width || undefined,
156 height: height || undefined,156 height: height || undefined,
apps/web/src/components/files/FileBreadcrumbs.tsx+1-1
...@@ -237,7 +237,7 @@ function DirectoryBreadcrumb(props: FileBreadcrumbsProps & { readonly crumb: Fil...@@ -237,7 +237,7 @@ function DirectoryBreadcrumb(props: FileBreadcrumbsProps & { readonly crumb: Fil
237 <button237 <button
238 type="button"238 type="button"
239 aria-label={`Browse ${props.crumb.label}`}239 aria-label={`Browse ${props.crumb.label}`}
240 className="relative block max-w-40 cursor-pointer rounded-sm px-0.5 text-left text-muted-foreground outline-none pointer-coarse:after:-inset-y-3 pointer-coarse:after:absolute pointer-coarse:after:inset-x-0 hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring data-popup-open:bg-accent data-popup-open:text-foreground"240 className="relative block max-w-40 cursor-pointer rounded-sm px-0.5 text-left text-muted-foreground outline-none pointer-coarse:after:-inset-y-3 pointer-coarse:after:absolute pointer-coarse:after:inset-x-0 hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring data-popup-open:bg-accent data-popup-open:text-foreground"
241 />241 />
242 }242 }
243 />243 />
apps/web/src/components/files/fileSurfaceChrome.tsx+1-1
...@@ -153,7 +153,7 @@ export function FileSurfaceFailure(props: {...@@ -153,7 +153,7 @@ export function FileSurfaceFailure(props: {
153 <button153 <button
154 type="button"154 type="button"
155 onClick={props.onRetry}155 onClick={props.onRetry}
156 className="rounded-md border border-input px-2.5 py-1 text-xs text-foreground hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring"156 className="rounded-md border border-input px-2.5 py-1 text-xs text-foreground hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
157 >157 >
158 Try again158 Try again
159 </button>159 </button>
apps/web/src/components/media/MediaVideoPlayer.tsx+1-1
...@@ -203,7 +203,7 @@ export function MediaVideoPlayer({...@@ -203,7 +203,7 @@ export function MediaVideoPlayer({
203 type="button"203 type="button"
204 aria-label={label ? `Play ${label}` : "Play video"}204 aria-label={label ? `Play ${label}` : "Play video"}
205 onClick={onOpen}205 onClick={onOpen}
206 className="absolute inset-0 flex cursor-pointer items-center justify-center rounded-lg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"206 className="absolute inset-0 flex cursor-pointer items-center justify-center rounded-lg focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring"
207 >207 >
208 <span className="flex size-8 items-center justify-center rounded-full bg-black/50 text-white">208 <span className="flex size-8 items-center justify-center rounded-full bg-black/50 text-white">
209 <PlayIcon aria-hidden className="size-4 fill-current" />209 <PlayIcon aria-hidden className="size-4 fill-current" />
apps/web/src/components/preview/PreviewRecentUrlCard.tsx+1-1
...@@ -44,7 +44,7 @@ export function PreviewRecentUrlCard({ threadRef, entry, onOpen, onRemove }: Pro...@@ -44,7 +44,7 @@ export function PreviewRecentUrlCard({ threadRef, entry, onOpen, onRemove }: Pro
44 type="button"44 type="button"
45 aria-label={`Remove ${label} from history`}45 aria-label={`Remove ${label} from history`}
46 onClick={onRemove}46 onClick={onRemove}
47 className="absolute right-3 rounded p-1 text-muted-foreground opacity-0 hover:bg-accent hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100"47 className="absolute right-3 rounded p-1 text-muted-foreground opacity-0 hover:bg-accent hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring group-hover:opacity-100"
48 >48 >
49 <X className="size-3.5" />49 <X className="size-3.5" />
50 </button>50 </button>
apps/web/src/components/projectScriptEditor.tsx+1-1
...@@ -369,7 +369,7 @@ export function ProjectScriptEditorDialog({...@@ -369,7 +369,7 @@ export function ProjectScriptEditorDialog({
369 type="button"369 type="button"
370 className={`relative flex flex-col items-center gap-2 rounded-md border px-2 py-2 text-xs dark:border-transparent ${370 className={`relative flex flex-col items-center gap-2 rounded-md border px-2 py-2 text-xs dark:border-transparent ${
371 isSelected371 isSelected
372 ? "border-primary/70 bg-primary/10 dark:ring-1 dark:ring-primary/30"372 ? "border-primary/70 bg-primary/10 ring-inset dark:ring-1 dark:ring-primary/30"
373 : "border-border/70 hover:bg-accent/60 dark:bg-white/[0.035]"373 : "border-border/70 hover:bg-accent/60 dark:bg-white/[0.035]"
374 }`}374 }`}
375 onClick={() => {375 onClick={() => {
apps/web/src/components/pullRequest/PullRequestCopyableCode.tsx+1-1
...@@ -32,7 +32,7 @@ export function PullRequestCopyableCode({...@@ -32,7 +32,7 @@ export function PullRequestCopyableCode({
32 <button32 <button
33 type="button"33 type="button"
34 className={cn(34 className={cn(
35 "relative grid w-fit min-w-0 max-w-full shrink cursor-pointer rounded px-1 py-0.5 text-left outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent/45 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",35 "relative grid w-fit min-w-0 max-w-full shrink cursor-pointer rounded px-1 py-0.5 text-left outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent/45 hover:text-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
36 className,36 className,
37 )}37 )}
38 aria-label={isCopied ? copiedLabel : copyLabel}38 aria-label={isCopied ? copiedLabel : copyLabel}
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+1-1
...@@ -380,7 +380,7 @@ function PullRequestBaseFreshnessWarning({...@@ -380,7 +380,7 @@ function PullRequestBaseFreshnessWarning({
380 type="button"380 type="button"
381 aria-label={summary}381 aria-label={summary}
382 className={cn(382 className={cn(
383 "inline-flex min-w-0 shrink-0 cursor-help items-center gap-1 rounded-sm text-warning-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring",383 "inline-flex min-w-0 shrink-0 cursor-help items-center gap-1 rounded-sm text-warning-foreground outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
384 className,384 className,
385 )}385 )}
386 />386 />
apps/web/src/components/pullRequest/PullRequestReactions.tsx+2-2
...@@ -23,7 +23,7 @@ import {...@@ -23,7 +23,7 @@ import {
23} from "./pullRequestReactions.logic";23} from "./pullRequestReactions.logic";
2424
25const PILL_CLASS =25const PILL_CLASS =
26 "inline-flex h-6 shrink-0 items-center gap-1 rounded-full border px-2 text-xs outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring";26 "inline-flex h-6 shrink-0 items-center gap-1 rounded-full border px-2 text-xs outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring";
2727
28const EMPTY_PENDING: ReadonlyMap<PullRequestReactionContent, boolean> = new Map();28const EMPTY_PENDING: ReadonlyMap<PullRequestReactionContent, boolean> = new Map();
2929
...@@ -148,7 +148,7 @@ export function PullRequestReactionBar({...@@ -148,7 +148,7 @@ export function PullRequestReactionBar({
148 aria-pressed={reacted}148 aria-pressed={reacted}
149 aria-label={pullRequestReactionName(content)}149 aria-label={pullRequestReactionName(content)}
150 className={cn(150 className={cn(
151 "flex size-7 items-center justify-center rounded-md text-base outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring",151 "flex size-7 items-center justify-center rounded-md text-base outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
152 reacted && "bg-primary/10",152 reacted && "bg-primary/10",
153 )}153 )}
154 onClick={() => {154 onClick={() => {
apps/web/src/components/pullRequest/PullRequestRow.tsx+1-1
...@@ -136,7 +136,7 @@ function PullRequestRowImpl({...@@ -136,7 +136,7 @@ function PullRequestRowImpl({
136 className={cn(136 className={cn(
137 PULL_REQUEST_ROW_CLASS,137 PULL_REQUEST_ROW_CLASS,
138 PAGE_ROW_CLASS,138 PAGE_ROW_CLASS,
139 "min-w-0 flex-1 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",139 "min-w-0 flex-1 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
140 )}140 )}
141 >141 >
142 <PullRequestRowGlyph142 <PullRequestRowGlyph
apps/web/src/components/pullRequest/PullRequestTimelineTab.tsx+1-1
...@@ -376,7 +376,7 @@ function CommitEvent({...@@ -376,7 +376,7 @@ function CommitEvent({
376 return (376 return (
377 <button377 <button
378 type="button"378 type="button"
379 className="group relative mb-5 block w-full cursor-pointer rounded-sm pl-12 text-left outline-none [contain-intrinsic-block-size:48px] [content-visibility:auto] focus-visible:ring-2 focus-visible:ring-ring"379 className="group relative mb-5 block w-full cursor-pointer rounded-sm pl-12 text-left outline-none [contain-intrinsic-block-size:48px] [content-visibility:auto] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
380 aria-label={`View commit ${event.id}`}380 aria-label={`View commit ${event.id}`}
381 onClick={() => onOpen(event.id)}381 onClick={() => onOpen(event.id)}
382 >382 >
apps/web/src/components/settings/AddProviderInstanceDialog.tsx+1-1
...@@ -435,7 +435,7 @@ export function AddProviderInstanceDialog({...@@ -435,7 +435,7 @@ export function AddProviderInstanceDialog({
435 <RadioPrimitive.Root435 <RadioPrimitive.Root
436 key={option.driverKind}436 key={option.driverKind}
437 value={option.driverKind}437 value={option.driverKind}
438 className="relative flex cursor-pointer items-center gap-3 rounded-lg bg-card px-3 py-3 text-left text-muted-foreground outline-none ring-1 ring-black/5 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring data-checked:bg-primary/8 data-checked:text-foreground data-checked:ring-2 data-checked:ring-primary data-checked:hover:bg-primary/8 dark:bg-white/3 dark:ring-white/5 dark:hover:bg-white/5 dark:data-checked:bg-primary/15 dark:data-checked:ring-primary dark:data-checked:hover:bg-primary/15"438 className="relative flex cursor-pointer items-center gap-3 rounded-lg bg-card px-3 py-3 text-left text-muted-foreground outline-none ring-1 ring-black/5 hover:bg-muted focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring data-checked:bg-primary/8 data-checked:text-foreground data-checked:ring-2 data-checked:ring-inset data-checked:ring-primary data-checked:hover:bg-primary/8 dark:bg-white/3 dark:ring-white/5 dark:hover:bg-white/5 dark:data-checked:bg-primary/15 dark:data-checked:ring-primary dark:data-checked:hover:bg-primary/15"
439 >439 >
440 <ProviderInstanceIcon440 <ProviderInstanceIcon
441 driverKind={option.driverKind}441 driverKind={option.driverKind}
apps/web/src/components/settings/BrowserImportWizard.tsx+1-1
...@@ -457,7 +457,7 @@ function SelectableTile({...@@ -457,7 +457,7 @@ function SelectableTile({
457 aria-pressed={selected}457 aria-pressed={selected}
458 onClick={onSelect}458 onClick={onSelect}
459 className={cn(459 className={cn(
460 "flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-2 text-left outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",460 "flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-2 text-left outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
461 selected461 selected
462 ? "border-primary bg-primary/8"462 ? "border-primary bg-primary/8"
463 : "border-border/60 hover:border-border hover:bg-muted/40",463 : "border-border/60 hover:border-border hover:bg-muted/40",
apps/web/src/components/settings/CodexSetupSection.tsx+1-1
...@@ -1098,7 +1098,7 @@ function CodexSignInDescription({...@@ -1098,7 +1098,7 @@ function CodexSignInDescription({
1098 return (1098 return (
1099 <button1099 <button
1100 type="button"1100 type="button"
1101 className="inline-flex items-center gap-1.5 rounded-sm text-left leading-relaxed outline-none focus-visible:ring-2 focus-visible:ring-ring"1101 className="inline-flex items-center gap-1.5 rounded-sm text-left leading-relaxed outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
1102 aria-label="Having trouble signing in?"1102 aria-label="Having trouble signing in?"
1103 aria-expanded={expanded}1103 aria-expanded={expanded}
1104 aria-controls={controls}1104 aria-controls={controls}
apps/web/src/components/settings/ConnectionsSettings.tsx+4-4
...@@ -870,7 +870,7 @@ const PairingLinkListRow = memo(function PairingLinkListRow({...@@ -870,7 +870,7 @@ const PairingLinkListRow = memo(function PairingLinkListRow({
870 role="radio"870 role="radio"
871 aria-checked={isSelected}871 aria-checked={isSelected}
872 className={cn(872 className={cn(
873 "flex w-full items-baseline gap-2 rounded-lg border px-2.5 py-1.5 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring",873 "flex w-full items-baseline gap-2 rounded-lg border px-2.5 py-1.5 text-left outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
874 isSelected874 isSelected
875 ? "border-foreground/60 bg-muted/30"875 ? "border-foreground/60 bg-muted/30"
876 : "border-border/50 hover:bg-muted/20",876 : "border-border/50 hover:bg-muted/20",
...@@ -1651,7 +1651,7 @@ function SavedBackendListRow({...@@ -1651,7 +1651,7 @@ function SavedBackendListRow({
1651 type="button"1651 type="button"
1652 aria-expanded={routesOpen}1652 aria-expanded={routesOpen}
1653 onClick={() => setRoutesOpen((open) => !open)}1653 onClick={() => setRoutesOpen((open) => !open)}
1654 className="inline-flex shrink-0 items-center gap-0.5 rounded-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring"1654 className="inline-flex shrink-0 items-center gap-0.5 rounded-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"
1655 >1655 >
1656 {routeCount === 1 ? "Routes" : `${routeCount} routes`}1656 {routeCount === 1 ? "Routes" : `${routeCount} routes`}
1657 <ChevronRightIcon1657 <ChevronRightIcon
...@@ -1670,7 +1670,7 @@ function SavedBackendListRow({...@@ -1670,7 +1670,7 @@ function SavedBackendListRow({
1670 aria-expanded={permissionsOpen}1670 aria-expanded={permissionsOpen}
1671 aria-controls={`remote-permissions-${environmentId}`}1671 aria-controls={`remote-permissions-${environmentId}`}
1672 onClick={() => setPermissionsOpen((open) => !open)}1672 onClick={() => setPermissionsOpen((open) => !open)}
1673 className="inline-flex shrink-0 items-center gap-0.5 rounded-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring"1673 className="inline-flex shrink-0 items-center gap-0.5 rounded-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"
1674 >1674 >
1675 Permissions1675 Permissions
1676 <ChevronRightIcon1676 <ChevronRightIcon
...@@ -3718,7 +3718,7 @@ export function ConnectionsSettings() {...@@ -3718,7 +3718,7 @@ export function ConnectionsSettings() {
3718 ) : null}3718 ) : null}
3719 {primaryEnvironment ? (3719 {primaryEnvironment ? (
3720 <details className="group px-3 sm:px-4">3720 <details className="group px-3 sm:px-4">
3721 <summary className="flex min-h-11 cursor-pointer list-none items-center gap-2 rounded-md text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-details-marker]:hidden">3721 <summary className="flex min-h-11 cursor-pointer list-none items-center gap-2 rounded-md text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring [&::-webkit-details-marker]:hidden">
3722 Permissions3722 Permissions
3723 <ChevronRightIcon3723 <ChevronRightIcon
3724 aria-hidden3724 aria-hidden
apps/web/src/components/settings/EnvironmentRoutesList.tsx+1-1
...@@ -195,7 +195,7 @@ function SortableRouteRow({...@@ -195,7 +195,7 @@ function SortableRouteRow({
195 {...attributes}195 {...attributes}
196 {...listeners}196 {...listeners}
197 aria-label={`Reorder ${label}, position ${position}`}197 aria-label={`Reorder ${label}, position ${position}`}
198 className="flex size-6 shrink-0 cursor-grab touch-none items-center justify-center rounded text-muted-foreground/70 outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring active:cursor-grabbing"198 className="flex size-6 shrink-0 cursor-grab touch-none items-center justify-center rounded text-muted-foreground/70 outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring active:cursor-grabbing"
199 >199 >
200 <GripVerticalIcon className="size-3.5" />200 <GripVerticalIcon className="size-3.5" />
201 </button>201 </button>
apps/web/src/components/settings/FoldedSettingsSection.tsx+2-2
...@@ -46,7 +46,7 @@ export function FoldedSettingsSection({...@@ -46,7 +46,7 @@ export function FoldedSettingsSection({
46 className="flex min-h-7 items-start justify-between gap-4 px-3 sm:px-4"46 className="flex min-h-7 items-start justify-between gap-4 px-3 sm:px-4"
47 >47 >
48 <h2>48 <h2>
49 <CollapsibleTrigger className="flex min-h-7 items-center gap-2 rounded-md text-sm font-normal text-foreground/70 outline-none focus-visible:ring-2 focus-visible:ring-ring">49 <CollapsibleTrigger className="flex min-h-7 items-center gap-2 rounded-md text-sm font-normal text-foreground/70 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
50 {title}50 {title}
51 <ChevronRightIcon51 <ChevronRightIcon
52 aria-hidden52 aria-hidden
...@@ -72,7 +72,7 @@ export function FoldedSettingsSection({...@@ -72,7 +72,7 @@ export function FoldedSettingsSection({
72 <section id={id} ref={targetRef} tabIndex={-1} className="outline-none">72 <section id={id} ref={targetRef} tabIndex={-1} className="outline-none">
73 <Collapsible open={open} onOpenChange={setOpen} render={<SettingsGroup divided={false} />}>73 <Collapsible open={open} onOpenChange={setOpen} render={<SettingsGroup divided={false} />}>
74 <div className="flex items-center gap-4 px-3 sm:px-4">74 <div className="flex items-center gap-4 px-3 sm:px-4">
75 <CollapsibleTrigger className="flex min-h-11 min-w-0 flex-1 items-center gap-2 py-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-md">75 <CollapsibleTrigger className="flex min-h-11 min-w-0 flex-1 items-center gap-2 py-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring rounded-md">
76 <ChevronRightIcon76 <ChevronRightIcon
77 aria-hidden77 aria-hidden
78 className={cn(78 className={cn(
apps/web/src/components/settings/KeybindingsSettings.tsx+2-2
...@@ -230,7 +230,7 @@ function WarningTooltipIcon({...@@ -230,7 +230,7 @@ function WarningTooltipIcon({
230 tabIndex={focusable ? 0 : undefined}230 tabIndex={focusable ? 0 : undefined}
231 aria-label={label}231 aria-label={label}
232 className={cn(232 className={cn(
233 "inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-warning outline-none transition-colors hover:bg-warning/10 focus-visible:ring-3 focus-visible:ring-warning/25",233 "inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-warning outline-none transition-colors hover:bg-warning/10 focus-visible:ring-3 focus-visible:ring-inset focus-visible:ring-warning/25",
234 className,234 className,
235 )}235 )}
236 />236 />
...@@ -818,7 +818,7 @@ function KeybindingKeyControl({...@@ -818,7 +818,7 @@ function KeybindingKeyControl({
818 onClick={() => setDraft({ isRecording: true })}818 onClick={() => setDraft({ isRecording: true })}
819 aria-label={`Edit shortcut for ${commandLabel(row.command)}: ${formatShortcutLabel(row.binding.shortcut)}`}819 aria-label={`Edit shortcut for ${commandLabel(row.command)}: ${formatShortcutLabel(row.binding.shortcut)}`}
820 className={cn(820 className={cn(
821 "inline-flex h-8 cursor-pointer items-center rounded-md border border-transparent px-1.5 sm:h-7 outline-none transition-colors hover:border-border/70 hover:bg-accent focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/24",821 "inline-flex h-8 cursor-pointer items-center rounded-md border border-transparent px-1.5 sm:h-7 outline-none transition-colors hover:border-border/70 hover:bg-accent focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-inset focus-visible:ring-ring/24",
822 pillClassName,822 pillClassName,
823 )}823 )}
824 >824 >
apps/web/src/components/settings/PanelAnimationsPreview.tsx+1-1
...@@ -11,7 +11,7 @@ export function PanelAnimationsPreview({ durationMs }: { durationMs: number }) {...@@ -11,7 +11,7 @@ export function PanelAnimationsPreview({ durationMs }: { durationMs: number }) {
11 <button11 <button
12 type="button"12 type="button"
13 aria-label="Replay panel animation preview"13 aria-label="Replay panel animation preview"
14 className="flex h-10 w-full cursor-pointer overflow-hidden rounded-lg border border-border bg-background p-1 shadow-xs/5 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"14 className="flex h-10 w-full cursor-pointer overflow-hidden rounded-lg border border-border bg-background p-1 shadow-xs/5 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
15 onClick={() => setPanelsOpen((open) => !open)}15 onClick={() => setPanelsOpen((open) => !open)}
16 style={{ "--preview-duration": `${durationMs}ms` } as CSSProperties}16 style={{ "--preview-duration": `${durationMs}ms` } as CSSProperties}
17 >17 >
apps/web/src/components/settings/ProjectIconPickerDialog.tsx+3-3
...@@ -131,7 +131,7 @@ export function ProjectIconPickerDialog({...@@ -131,7 +131,7 @@ export function ProjectIconPickerDialog({
131 aria-label={option.label}131 aria-label={option.label}
132 aria-pressed={color === option.value}132 aria-pressed={color === option.value}
133 className={cn(133 className={cn(
134 "flex size-6 items-center justify-center rounded-full border border-transparent outline-none focus-visible:ring-2 focus-visible:ring-ring",134 "flex size-6 items-center justify-center rounded-full border border-transparent outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
135 color === option.value && "border-foreground/64",135 color === option.value && "border-foreground/64",
136 )}136 )}
137 onClick={() => setColor(option.value)}137 onClick={() => setColor(option.value)}
...@@ -161,7 +161,7 @@ export function ProjectIconPickerDialog({...@@ -161,7 +161,7 @@ export function ProjectIconPickerDialog({
161 aria-label={iconLabel(name)}161 aria-label={iconLabel(name)}
162 aria-pressed={iconName === name}162 aria-pressed={iconName === name}
163 className={cn(163 className={cn(
164 "flex aspect-square items-center justify-center rounded-md border border-transparent outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring",164 "flex aspect-square items-center justify-center rounded-md border border-transparent outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
165 iconName === name && "border-border bg-accent",165 iconName === name && "border-border bg-accent",
166 selectedColorClassName,166 selectedColorClassName,
167 )}167 )}
...@@ -211,7 +211,7 @@ export function ProjectIconPickerDialog({...@@ -211,7 +211,7 @@ export function ProjectIconPickerDialog({
211 aria-label={option.label}211 aria-label={option.label}
212 aria-pressed={emoji === option.emoji}212 aria-pressed={emoji === option.emoji}
213 className={cn(213 className={cn(
214 "flex aspect-square items-center justify-center rounded-md border border-transparent text-xl outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring",214 "flex aspect-square items-center justify-center rounded-md border border-transparent text-xl outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
215 emoji === option.emoji && "border-border bg-accent",215 emoji === option.emoji && "border-border bg-accent",
216 )}216 )}
217 onClick={() => setEmoji(option.emoji)}217 onClick={() => setEmoji(option.emoji)}
apps/web/src/components/settings/ProviderInstanceCard.tsx+1-1
...@@ -923,7 +923,7 @@ export function ProviderInstanceCard({...@@ -923,7 +923,7 @@ export function ProviderInstanceCard({
923 >923 >
924 <button924 <button
925 type="button"925 type="button"
926 className="pointer-events-auto absolute inset-0 cursor-pointer rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring"926 className="pointer-events-auto absolute inset-0 cursor-pointer rounded-md outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
927 onClick={onSelect}927 onClick={onSelect}
928 aria-label={`Select ${displayName}`}928 aria-label={`Select ${displayName}`}
929 aria-pressed={selected}929 aria-pressed={selected}
apps/web/src/components/settings/ThemeColorPicker.tsx+2-2
...@@ -266,7 +266,7 @@ function ThemeColorPicker({...@@ -266,7 +266,7 @@ function ThemeColorPicker({
266 render={266 render={
267 <button267 <button
268 aria-label={`Choose ${label} color`}268 aria-label={`Choose ${label} color`}
269 className="relative flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border border-foreground/30 transition-transform hover:scale-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"269 className="relative flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border border-foreground/30 transition-transform hover:scale-105 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-ring"
270 onFocus={onInteract}270 onFocus={onInteract}
271 onPointerDown={onInteract}271 onPointerDown={onInteract}
272 type="button"272 type="button"
...@@ -333,7 +333,7 @@ export const ThemeColorField = memo(function ThemeColorField({...@@ -333,7 +333,7 @@ export const ThemeColorField = memo(function ThemeColorField({
333 <button333 <button
334 aria-label={`${selected ? "Hide" : "Show"} ${label} usage`}334 aria-label={`${selected ? "Hide" : "Show"} ${label} usage`}
335 aria-pressed={selected}335 aria-pressed={selected}
336 className="flex min-w-0 flex-1 cursor-pointer items-center rounded-md text-left text-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"336 className="flex min-w-0 flex-1 cursor-pointer items-center rounded-md text-left text-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
337 onClick={() => onToggleSelected?.(role)}337 onClick={() => onToggleSelected?.(role)}
338 type="button"338 type="button"
339 >339 >
apps/web/src/components/settings/ThemeImportDialog.tsx+1-1
...@@ -111,7 +111,7 @@ function ThemeJsonEditor({...@@ -111,7 +111,7 @@ function ThemeJsonEditor({
111 }, []);111 }, []);
112112
113 return (113 return (
114 <div className="relative overflow-hidden rounded-xl border border-input bg-background shadow-xs/5 focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/24">114 <div className="relative overflow-hidden rounded-xl border border-input bg-background shadow-xs/5 focus-within:border-ring focus-within:ring-3 focus-within:ring-inset focus-within:ring-ring/24">
115 {isPlainText ? null : (115 {isPlainText ? null : (
116 <pre116 <pre
117 ref={highlightRef}117 ref={highlightRef}
apps/web/src/components/settings/ThemePreviewCircles.tsx+1-1
...@@ -187,7 +187,7 @@ export function ThemePreviewCircles({...@@ -187,7 +187,7 @@ export function ThemePreviewCircles({
187 aria-label={`Use ${label} ${mode} mode`}187 aria-label={`Use ${label} ${mode} mode`}
188 aria-pressed={isPicked}188 aria-pressed={isPicked}
189 className={cn(189 className={cn(
190 "relative flex size-[68px] shrink-0 transform-gpu cursor-pointer items-center justify-center rounded-full p-1 outline-none transition-transform hover:scale-105 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card",190 "relative flex size-[68px] shrink-0 transform-gpu cursor-pointer items-center justify-center rounded-full p-1 outline-none transition-transform hover:scale-105 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card",
191 isPicked && "hover:scale-100",191 isPicked && "hover:scale-100",
192 )}192 )}
193 onClick={(event) => {193 onClick={(event) => {
apps/web/src/components/settings/ThemeSettings.tsx+9-7
...@@ -197,7 +197,7 @@ function ThemeLibraryCard({...@@ -197,7 +197,7 @@ function ThemeLibraryCard({
197 : `Use ${mode} variant, currently ${selected.option.label}`197 : `Use ${mode} variant, currently ${selected.option.label}`
198 }198 }
199 aria-pressed={isActive}199 aria-pressed={isActive}
200 className="absolute left-1/2 top-2 z-20 flex size-14 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring"200 className="absolute left-1/2 top-2 z-20 flex size-14 items-center justify-center rounded-full outline-none focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-ring"
201 style={{201 style={{
202 transform: `translateX(calc(-50% + ${rootOffsetX}px))`,202 transform: `translateX(calc(-50% + ${rootOffsetX}px))`,
203 }}203 }}
...@@ -216,7 +216,7 @@ function ThemeLibraryCard({...@@ -216,7 +216,7 @@ function ThemeLibraryCard({
216 {isActive ? (216 {isActive ? (
217 <span217 <span
218 aria-hidden218 aria-hidden
219 className="pointer-events-none absolute inset-0 rounded-full ring-2 ring-ring"219 className="pointer-events-none absolute inset-0 rounded-full ring-2 ring-inset ring-ring"
220 />220 />
221 ) : null}221 ) : null}
222 {isActive ? (222 {isActive ? (
...@@ -256,8 +256,10 @@ function ThemeLibraryCard({...@@ -256,8 +256,10 @@ function ThemeLibraryCard({
256 aria-label={`Use ${option.label} for ${mode} mode${optionIsActive ? ", currently active" : ""}`}256 aria-label={`Use ${option.label} for ${mode} mode${optionIsActive ? ", currently active" : ""}`}
257 aria-pressed={optionIsActive}257 aria-pressed={optionIsActive}
258 className={cn(258 className={cn(
259 "absolute left-1/2 top-1 z-30 flex size-7 items-center justify-center rounded-full bg-background shadow-sm outline-none transition-[opacity,transform] duration-200 ease-out motion-reduce:transition-none focus-visible:ring-2 focus-visible:ring-ring",259 "absolute left-1/2 top-1 z-30 flex size-7 items-center justify-center rounded-full bg-background shadow-sm outline-none transition-[opacity,transform] duration-200 ease-out motion-reduce:transition-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
260 optionIsActive ? "ring-2 ring-ring" : "ring-1 ring-border/70",260 optionIsActive
261 ? "ring-2 ring-inset ring-ring"
262 : "ring-1 ring-border/70",
261 )}263 )}
262 style={{264 style={{
263 opacity: isOpen ? 1 : 0,265 opacity: isOpen ? 1 : 0,
...@@ -303,7 +305,7 @@ function ThemeLibraryCard({...@@ -303,7 +305,7 @@ function ThemeLibraryCard({
303 <button305 <button
304 aria-label={`Use ${variantNavigation ? `${variantNavigation.collectionLabel}, ${theme.label} variant` : `${theme.label} theme`}${isActive ? ", currently active" : ""}`}306 aria-label={`Use ${variantNavigation ? `${variantNavigation.collectionLabel}, ${theme.label} variant` : `${theme.label} theme`}${isActive ? ", currently active" : ""}`}
305 aria-pressed={isActive}307 aria-pressed={isActive}
306 className="min-w-0 cursor-pointer truncate rounded-sm text-left text-sm font-medium text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card"308 className="min-w-0 cursor-pointer truncate rounded-sm text-left text-sm font-medium text-foreground outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card"
307 type="button"309 type="button"
308 onClick={(event) => {310 onClick={(event) => {
309 event.stopPropagation();311 event.stopPropagation();
...@@ -726,7 +728,7 @@ export function ThemeLibrary({...@@ -726,7 +728,7 @@ export function ThemeLibrary({
726 aria-label={mode === "system" ? "Follow the system appearance" : `Use ${mode} mode`}728 aria-label={mode === "system" ? "Follow the system appearance" : `Use ${mode} mode`}
727 aria-pressed={isActive}729 aria-pressed={isActive}
728 className={cn(730 className={cn(
729 "flex cursor-pointer flex-col items-stretch gap-1.5 rounded-xl border p-2 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring",731 "flex cursor-pointer flex-col items-stretch gap-1.5 rounded-xl border p-2 outline-none transition-colors focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-ring",
730 isActive732 isActive
731 ? "border-transparent bg-accent/30"733 ? "border-transparent bg-accent/30"
732 : "border-border/70 bg-card/60 hover:bg-accent/10",734 : "border-border/70 bg-card/60 hover:bg-accent/10",
...@@ -989,7 +991,7 @@ export function ThemeLibrary({...@@ -989,7 +991,7 @@ export function ThemeLibrary({
989 <span className="absolute right-2 top-2 inline-grid size-5 grid-cols-1 sm:size-4">991 <span className="absolute right-2 top-2 inline-grid size-5 grid-cols-1 sm:size-4">
990 <input992 <input
991 checked={checked}993 checked={checked}
992 className="col-start-1 row-start-1 size-full appearance-none rounded-sm border border-input bg-background outline-none checked:border-primary checked:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring dark:not-checked:bg-input/32 forced-colors:appearance-auto"994 className="col-start-1 row-start-1 size-full appearance-none rounded-sm border border-input bg-background outline-none checked:border-primary checked:bg-primary focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring checked:focus-visible:inset-ring-3 checked:focus-visible:inset-ring-background dark:not-checked:bg-input/32 forced-colors:appearance-auto"
993 id={checkboxId}995 id={checkboxId}
994 name="themes-to-remove"996 name="themes-to-remove"
995 type="checkbox"997 type="checkbox"
apps/web/src/components/settings/settingsLayout.tsx+1-1
...@@ -375,7 +375,7 @@ export function SettingsRow({...@@ -375,7 +375,7 @@ export function SettingsRow({
375 // Focusable so keyboard users can still reach the explanation.375 // Focusable so keyboard users can still reach the explanation.
376 <span376 <span
377 tabIndex={0}377 tabIndex={0}
378 className="flex w-full items-center rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring @min-[32rem]/settings-row:w-auto"378 className="flex w-full items-center rounded-md outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring @min-[32rem]/settings-row:w-auto"
379 />379 />
380 }380 }
381 >381 >
apps/web/src/components/sidebar/SidebarChrome.tsx+1-1
...@@ -112,7 +112,7 @@ function SidebarBrand({ onBackdrop }: { onBackdrop: boolean }) {...@@ -112,7 +112,7 @@ function SidebarBrand({ onBackdrop }: { onBackdrop: boolean }) {
112 <Link112 <Link
113 aria-label="Go to threads"113 aria-label="Go to threads"
114 className={cn(114 className={cn(
115 "relative z-10 ml-[var(--workspace-titlebar-content-left)] hidden h-7 w-fit min-w-0 shrink-0 items-center overflow-hidden rounded-md outline-hidden ring-ring focus-visible:ring-2 md:flex",115 "relative z-10 ml-[var(--workspace-titlebar-content-left)] hidden h-7 w-fit min-w-0 shrink-0 items-center overflow-hidden rounded-md outline-hidden ring-ring focus-visible:ring-2 focus-visible:ring-inset md:flex",
116 onBackdrop ? "text-white" : "text-foreground",116 onBackdrop ? "text-white" : "text-foreground",
117 )}117 )}
118 to="/"118 to="/"
apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx+1-1
...@@ -195,7 +195,7 @@ export function SidebarProviderUpdatePill() {...@@ -195,7 +195,7 @@ export function SidebarProviderUpdatePill() {
195 <button195 <button
196 type="button"196 type="button"
197 aria-label="Dismiss provider update notice"197 aria-label="Dismiss provider update notice"
198 className="relative z-[1] mr-1 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-md opacity-70 outline-none hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring"198 className="relative z-[1] mr-1 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-md opacity-70 outline-none hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
199 onClick={() => startExit(displayedView.key, null, displayedView.key)}199 onClick={() => startExit(displayedView.key, null, displayedView.key)}
200 >200 >
201 <XIcon className="size-3.5 shrink-0" />201 <XIcon className="size-3.5 shrink-0" />
apps/web/src/components/sidebar/SidebarUpdatePill.tsx+1-1
...@@ -303,7 +303,7 @@ function SidebarUpdateControl() {...@@ -303,7 +303,7 @@ function SidebarUpdateControl() {
303 aria-label={tooltip}303 aria-label={tooltip}
304 aria-disabled={isInteractionDisabled || undefined}304 aria-disabled={isInteractionDisabled || undefined}
305 className={cn(305 className={cn(
306 "inline-flex size-8 items-center justify-center rounded-full outline-hidden ring-ring transition-colors focus-visible:ring-2",306 "inline-flex size-8 items-center justify-center rounded-full outline-hidden ring-ring transition-colors focus-visible:ring-2 focus-visible:ring-inset",
307 isInteractionDisabled ? "cursor-not-allowed" : "cursor-pointer",307 isInteractionDisabled ? "cursor-not-allowed" : "cursor-pointer",
308 showUpdateIconState308 showUpdateIconState
309 ? cn(309 ? cn(
apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx+1-1
...@@ -30,7 +30,7 @@ function ReleaseLink({...@@ -30,7 +30,7 @@ function ReleaseLink({
30}) {30}) {
31 return (31 return (
32 <a32 <a
33 className="mt-2 inline-flex items-center gap-1 rounded-sm text-xs leading-5 text-muted-foreground underline decoration-dotted underline-offset-4 outline-none transition-colors hover:text-foreground focus-visible:text-foreground focus-visible:ring-2 focus-visible:ring-ring"33 className="mt-2 inline-flex items-center gap-1 rounded-sm text-xs leading-5 text-muted-foreground underline decoration-dotted underline-offset-4 outline-none transition-colors hover:text-foreground focus-visible:text-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
34 href={releaseUrl}34 href={releaseUrl}
35 onClick={(event) => {35 onClick={(event) => {
36 event.preventDefault();36 event.preventDefault();
apps/web/src/components/ui/badge.tsx+1-1
...@@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority";...@@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority";
7import { cn } from "~/lib/utils";7import { cn } from "~/lib/utils";
88
9const badgeVariants = cva(9const badgeVariants = cva(
10 "relative inline-flex shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-sm border border-transparent font-medium outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [button&,a&]:cursor-pointer [button&,a&]:pointer-coarse:after:absolute [button&,a&]:pointer-coarse:after:size-full [button&,a&]:pointer-coarse:after:min-h-11 [button&,a&]:pointer-coarse:after:min-w-11",10 "relative inline-flex shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-sm border border-transparent font-medium outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [button&,a&]:cursor-pointer [button&,a&]:pointer-coarse:after:absolute [button&,a&]:pointer-coarse:after:size-full [button&,a&]:pointer-coarse:after:min-h-11 [button&,a&]:pointer-coarse:after:min-w-11",
11 {11 {
12 defaultVariants: {12 defaultVariants: {
13 size: "default",13 size: "default",
apps/web/src/components/ui/button.tsx+1-1
...@@ -8,7 +8,7 @@ import type * as React from "react";...@@ -8,7 +8,7 @@ import type * as React from "react";
8import { cn } from "~/lib/utils";8import { cn } from "~/lib/utils";
99
10const buttonVariants = cva(10const buttonVariants = cva(
11 "[--control-icon-color:currentColor] [&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-[var(--control-radius)] border font-medium text-base outline-none transition-[box-shadow,scale] [&:active:not([aria-haspopup])]:scale-[0.97] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--control-radius)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 aria-disabled:cursor-not-allowed aria-disabled:opacity-64 sm:text-sm [&_svg:not([class*='text-'])]:text-[var(--control-icon-color)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",11 "[--control-icon-color:currentColor] [&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-[var(--control-radius)] border font-medium text-base outline-none transition-[box-shadow,scale] [&:active:not([aria-haspopup])]:scale-[0.97] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--control-radius)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 aria-disabled:cursor-not-allowed aria-disabled:opacity-64 sm:text-sm [&_svg:not([class*='text-'])]:text-[var(--control-icon-color)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
12 {12 {
13 defaultVariants: {13 defaultVariants: {
14 size: "default",14 size: "default",
apps/web/src/components/ui/calendar.tsx+2-2
...@@ -56,11 +56,11 @@ export function Calendar({...@@ -56,11 +56,11 @@ export function Calendar({
56 day: "size-(--cell-size) text-sm py-px",56 day: "size-(--cell-size) text-sm py-px",
57 day_button: cn(57 day_button: cn(
58 buttonClassNames,58 buttonClassNames,
59 "in-data-disabled:pointer-events-none in-[.range-middle]:rounded-none in-[.range-end:not(.range-start)]:rounded-s-none in-[.range-start:not(.range-end)]:rounded-e-none in-[.range-middle]:in-data-selected:bg-accent in-data-selected:bg-primary in-[.range-middle]:in-data-selected:text-foreground in-data-disabled:text-muted-foreground/72 in-data-outside:text-muted-foreground/72 in-data-selected:in-data-outside:text-primary-foreground in-data-selected:text-primary-foreground in-data-disabled:line-through outline-none in-[[data-selected]:not(.range-middle)]:transition-[border-radius,box-shadow] focus-visible:z-1 focus-visible:ring-[3px] focus-visible:ring-ring/50",59 "in-data-disabled:pointer-events-none in-[.range-middle]:rounded-none in-[.range-end:not(.range-start)]:rounded-s-none in-[.range-start:not(.range-end)]:rounded-e-none in-[.range-middle]:in-data-selected:bg-accent in-data-selected:bg-primary in-[.range-middle]:in-data-selected:text-foreground in-data-disabled:text-muted-foreground/72 in-data-outside:text-muted-foreground/72 in-data-selected:in-data-outside:text-primary-foreground in-data-selected:text-primary-foreground in-data-disabled:line-through outline-none in-[[data-selected]:not(.range-middle)]:transition-[border-radius,box-shadow] focus-visible:z-1 focus-visible:ring-[3px] focus-visible:ring-inset focus-visible:ring-ring/50",
60 ),60 ),
61 dropdown: "absolute bg-popover inset-0 opacity-0",61 dropdown: "absolute bg-popover inset-0 opacity-0",
62 dropdown_root:62 dropdown_root:
63 "relative has-focus:border-ring has-focus:ring-ring/50 has-focus:ring-[3px] border border-input shadow-xs/5 rounded-lg px-[calc(--spacing(3)-1px)] h-9 sm:h-8 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-me-1",63 "relative has-focus:border-ring has-focus:ring-ring/50 has-focus:ring-[3px] has-focus:ring-inset border border-input shadow-xs/5 rounded-lg px-[calc(--spacing(3)-1px)] h-9 sm:h-8 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-me-1",
64 dropdowns:64 dropdowns:
65 "w-full flex items-center text-base sm:text-sm justify-center h-(--cell-size) gap-1.5 *:[span]:font-medium",65 "w-full flex items-center text-base sm:text-sm justify-center h-(--cell-size) gap-1.5 *:[span]:font-medium",
66 hidden: "invisible",66 hidden: "invisible",
apps/web/src/components/ui/checkbox.tsx+3-2
...@@ -8,14 +8,15 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {...@@ -8,14 +8,15 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
8 return (8 return (
9 <CheckboxPrimitive.Root9 <CheckboxPrimitive.Root
10 className={cn(10 className={cn(
11 "relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-[.25rem] border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none ring-ring transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[3px] not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/48 data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",11 "relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-[.25rem] border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[3px] not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-ring aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:outline-destructive/48 data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 dark:focus-visible:aria-invalid:outline-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",
12 className,12 className,
13 )}13 )}
14 data-slot="checkbox"14 data-slot="checkbox"
15 {...props}15 {...props}
16 >16 >
17 {/* Leave a contrasting inner gap inside the 2px focus outline. */}
17 <CheckboxPrimitive.Indicator18 <CheckboxPrimitive.Indicator
18 className="-inset-px absolute flex items-center justify-center rounded-[.25rem] text-primary-foreground data-unchecked:hidden data-checked:bg-primary data-indeterminate:text-foreground"19 className="-inset-px absolute flex items-center justify-center rounded-[.25rem] text-primary-foreground data-unchecked:hidden data-checked:bg-primary data-indeterminate:text-foreground in-[[data-slot=checkbox]:focus-visible]:inset-shadow-[0_0_0_3px_var(--background)] in-[[data-slot=checkbox]:focus-visible]:outline-2 in-[[data-slot=checkbox]:focus-visible]:outline-solid in-[[data-slot=checkbox]:focus-visible]:-outline-offset-2 in-[[data-slot=checkbox]:focus-visible]:outline-inherit"
19 data-slot="checkbox-indicator"20 data-slot="checkbox-indicator"
20 render={(props, state) => (21 render={(props, state) => (
21 <span {...props}>22 <span {...props}>
apps/web/src/components/ui/color-picker.tsx+2-2
...@@ -116,7 +116,7 @@ export function ColorSaturationValuePlane({...@@ -116,7 +116,7 @@ export function ColorSaturationValuePlane({
116 aria-label={`${label} saturation and brightness`}116 aria-label={`${label} saturation and brightness`}
117 role="group"117 role="group"
118 className={cn(118 className={cn(
119 "relative cursor-crosshair touch-none overflow-hidden bg-[linear-gradient(to_top,#000,transparent),linear-gradient(to_right,#fff,transparent)] has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-2 has-[:focus-visible]:ring-offset-popover",119 "relative cursor-crosshair touch-none overflow-hidden bg-[linear-gradient(to_top,#000,transparent),linear-gradient(to_right,#fff,transparent)] has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-inset has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-2 has-[:focus-visible]:ring-offset-popover",
120 variant === "edge" ? "h-36 rounded-none" : "h-32 rounded-lg",120 variant === "edge" ? "h-36 rounded-none" : "h-32 rounded-lg",
121 className,121 className,
122 )}122 )}
...@@ -200,7 +200,7 @@ export function ColorHueSlider({...@@ -200,7 +200,7 @@ export function ColorHueSlider({
200 aria-valuemin={0}200 aria-valuemin={0}
201 aria-valuenow={Math.round(value)}201 aria-valuenow={Math.round(value)}
202 className={cn(202 className={cn(
203 "relative flex h-6 cursor-pointer touch-none items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-popover",203 "relative flex h-6 cursor-pointer touch-none items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-popover",
204 className,204 className,
205 )}205 )}
206 role="slider"206 role="slider"
apps/web/src/components/ui/input-group.tsx+1-1
...@@ -7,7 +7,7 @@ import { cn } from "~/lib/utils";...@@ -7,7 +7,7 @@ import { cn } from "~/lib/utils";
7import { Input, type InputProps } from "~/components/ui/input";7import { Input, type InputProps } from "~/components/ui/input";
88
9const inputGroupVariants = cva(9const inputGroupVariants = cva(
10 "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)]",10 "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] has-[input:focus-visible,textarea:focus-visible]:ring-inset 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)]",
11 {11 {
12 defaultVariants: {12 defaultVariants: {
13 variant: "default",13 variant: "default",
apps/web/src/components/ui/input.tsx+1-1
...@@ -65,7 +65,7 @@ function Input({...@@ -65,7 +65,7 @@ function Input({
65 className={65 className={
66 cn(66 cn(
67 !unstyled &&67 !unstyled &&
68 "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%)]",68 "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] has-focus-visible:ring-inset 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%)]",
69 !unstyled &&69 !unstyled &&
70 size === "compact" &&70 size === "compact" &&
71 "rounded-md before:rounded-[calc(var(--radius-md)-1px)]",71 "rounded-md before:rounded-[calc(var(--radius-md)-1px)]",
apps/web/src/components/ui/menu.tsx+1-1
...@@ -145,7 +145,7 @@ function MenuCheckboxItem({...@@ -145,7 +145,7 @@ function MenuCheckboxItem({
145 <>145 <>
146 <span className="col-start-1">{children}</span>146 <span className="col-start-1">{children}</span>
147 <MenuPrimitive.CheckboxItemIndicator147 <MenuPrimitive.CheckboxItemIndicator
148 className="inset-shadow-[0_1px_--theme(--color-black/4%)] inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(4)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(3)]"148 className="inset-shadow-[0_1px_--theme(--color-black/4%)] inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 [--thumb-size:--spacing(4)] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-64 sm:[--thumb-size:--spacing(3)]"
149 keepMounted149 keepMounted
150 >150 >
151 <span className="pointer-events-none block aspect-square h-full in-[[data-slot=menu-checkbox-item][data-checked]]:origin-[var(--thumb-size)_50%] origin-left in-[[data-slot=menu-checkbox-item][data-checked]]:translate-x-[calc(var(--thumb-size)-4px)] in-[[data-slot=menu-checkbox-item]:active]:not-data-disabled:scale-x-110 in-[[data-slot=menu-checkbox-item]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.10)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s]" />151 <span className="pointer-events-none block aspect-square h-full in-[[data-slot=menu-checkbox-item][data-checked]]:origin-[var(--thumb-size)_50%] origin-left in-[[data-slot=menu-checkbox-item][data-checked]]:translate-x-[calc(var(--thumb-size)-4px)] in-[[data-slot=menu-checkbox-item]:active]:not-data-disabled:scale-x-110 in-[[data-slot=menu-checkbox-item]:active]:rounded-[var(--thumb-size)/calc(var(--thumb-size)*1.10)] rounded-(--thumb-size) bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s]" />
apps/web/src/components/ui/number-field.tsx+1-1
...@@ -33,7 +33,7 @@ export function NumberFieldGroup({...@@ -33,7 +33,7 @@ export function NumberFieldGroup({
33 return (33 return (
34 <NumberFieldPrimitive.Group34 <NumberFieldPrimitive.Group
35 className={cn(35 className={cn(
36 "relative flex w-full justify-between 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-data-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-within:border-ring focus-within:ring-[3px] has-aria-invalid:border-destructive/36 has-autofill:bg-foreground/4 focus-within:has-aria-invalid:border-destructive/64 focus-within:has-aria-invalid:ring-destructive/48 data-disabled:pointer-events-none data-disabled:opacity-64 sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [[data-disabled],:focus-within,[aria-invalid]]:shadow-none",36 "relative flex w-full justify-between 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-data-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-inset has-aria-invalid:border-destructive/36 has-autofill:bg-foreground/4 focus-within:has-aria-invalid:border-destructive/64 focus-within:has-aria-invalid:ring-destructive/48 data-disabled:pointer-events-none data-disabled:opacity-64 sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [[data-disabled],:focus-within,[aria-invalid]]:shadow-none",
37 className,37 className,
38 )}38 )}
39 data-slot="number-field-group"39 data-slot="number-field-group"
apps/web/src/components/ui/radio-group.tsx+3-2
...@@ -19,14 +19,15 @@ function Radio({ className, ...props }: RadioPrimitive.Root.Props) {...@@ -19,14 +19,15 @@ function Radio({ className, ...props }: RadioPrimitive.Root.Props) {
19 return (19 return (
20 <RadioPrimitive.Root20 <RadioPrimitive.Root
21 className={cn(21 className={cn(
22 "relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-full border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-full not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/48 data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",22 "relative inline-flex size-4.5 shrink-0 items-center justify-center rounded-full border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-full not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-ring aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:outline-destructive/48 data-disabled:opacity-64 sm:size-4 dark:not-data-checked:bg-input/32 dark:focus-visible:aria-invalid:outline-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",
23 className,23 className,
24 )}24 )}
25 data-slot="radio"25 data-slot="radio"
26 {...props}26 {...props}
27 >27 >
28 {/* Leave a contrasting inner gap inside the 2px focus outline. */}
28 <RadioPrimitive.Indicator29 <RadioPrimitive.Indicator
29 className="-inset-px absolute flex size-4.5 items-center justify-center rounded-full before:size-2 before:rounded-full before:bg-primary-foreground data-unchecked:hidden data-checked:bg-primary sm:size-4 sm:before:size-1.5"30 className="-inset-px absolute flex size-4.5 items-center justify-center rounded-full before:size-2 before:rounded-full before:bg-primary-foreground data-unchecked:hidden data-checked:bg-primary sm:size-4 sm:before:size-1.5 in-[[data-slot=radio]:focus-visible]:inset-shadow-[0_0_0_3px_var(--background)] in-[[data-slot=radio]:focus-visible]:outline-2 in-[[data-slot=radio]:focus-visible]:outline-solid in-[[data-slot=radio]:focus-visible]:-outline-offset-2 in-[[data-slot=radio]:focus-visible]:outline-inherit"
30 data-slot="radio-indicator"31 data-slot="radio-indicator"
31 />32 />
32 </RadioPrimitive.Root>33 </RadioPrimitive.Root>
apps/web/src/components/ui/scroll-area.tsx+3-2
...@@ -57,11 +57,12 @@ function ScrollArea({...@@ -57,11 +57,12 @@ function ScrollArea({
57 <ScrollAreaPrimitive.Viewport57 <ScrollAreaPrimitive.Viewport
58 {...(viewportTabIndex === undefined ? {} : { tabIndex: viewportTabIndex })}58 {...(viewportTabIndex === undefined ? {} : { tabIndex: viewportTabIndex })}
59 className={cn(59 className={cn(
60 "h-full max-h-[inherit] overflow-auto overscroll-contain rounded-[inherit] outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",60 "h-full max-h-[inherit] overflow-auto overscroll-contain rounded-[inherit] outline-none transition-shadow focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
61 chainVerticalScroll && "overscroll-y-auto",61 chainVerticalScroll && "overscroll-y-auto",
62 scrollFade &&62 scrollFade &&
63 "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",63 "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] focus-visible:mask-none [--fade-size:1.5rem]",
64 scrollFade && scrollFadePadding && "scroll-p-[var(--fade-size)]",64 scrollFade && scrollFadePadding && "scroll-p-[var(--fade-size)]",
65 scrollFade && !scrollFadePadding && "has-focus-visible:mask-none",
65 scrollbarGutter && "scrollbar-gutter-stable",66 scrollbarGutter && "scrollbar-gutter-stable",
66 hideScrollbars &&67 hideScrollbars &&
67 "[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",68 "[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
apps/web/src/components/ui/select.tsx+2-2
...@@ -20,9 +20,9 @@ const selectTriggerVariants = cva(...@@ -20,9 +20,9 @@ const selectTriggerVariants = cva(
20 variants: {20 variants: {
21 variant: {21 variant: {
22 default:22 default:
23 "w-full min-w-36 border-input bg-background not-dark:bg-clip-padding text-foreground shadow-xs/5 ring-ring/24 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 focus-visible:border-ring focus-visible:ring-[3px] aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 dark:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='text-'])]:text-icon-muted [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none",23 "w-full min-w-36 border-input bg-background not-dark:bg-clip-padding text-foreground shadow-xs/5 ring-ring/24 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-inset aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/16 dark:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='text-'])]:text-icon-muted [[data-disabled],:focus-visible,[aria-invalid],[data-pressed]]:shadow-none",
24 ghost:24 ghost:
25 "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",25 "border-transparent text-secondary-label focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent [:hover,[data-pressed]]:text-foreground",
26 },26 },
27 size: {27 size: {
28 compact:28 compact:
apps/web/src/components/ui/sidebar.tsx+2-2
...@@ -660,7 +660,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {...@@ -660,7 +660,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
660}660}
661661
662const sidebarMenuButtonVariants = cva(662const sidebarMenuButtonVariants = cva(
663 "peer/menu-button flex w-full cursor-pointer items-center gap-[var(--sidebar-control-gap)] overflow-hidden text-left outline-hidden ring-ring transition-[width,height,padding] hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-row-active active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 data-[active=true]:bg-sidebar-row-selected data-[active=true]:font-medium data-[active=true]:text-sidebar-foreground data-[state=open]:hover:bg-sidebar-row-hover data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-[var(--sidebar-content-inset)]! [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-[var(--sidebar-icon-color)] hover:[&>svg]:text-sidebar-foreground active:[&>svg]:text-sidebar-foreground data-[active=true]:[&>svg]:text-sidebar-foreground",663 "peer/menu-button flex w-full cursor-pointer items-center gap-[var(--sidebar-control-gap)] overflow-hidden text-left outline-hidden ring-ring transition-[width,height,padding] hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-inset active:bg-sidebar-row-active active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 data-[active=true]:bg-sidebar-row-selected data-[active=true]:font-medium data-[active=true]:text-sidebar-foreground data-[state=open]:hover:bg-sidebar-row-hover data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-[var(--sidebar-content-inset)]! [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-[var(--sidebar-icon-color)] hover:[&>svg]:text-sidebar-foreground active:[&>svg]:text-sidebar-foreground data-[active=true]:[&>svg]:text-sidebar-foreground",
664 {664 {
665 defaultVariants: {665 defaultVariants: {
666 size: "default",666 size: "default",
...@@ -773,7 +773,7 @@ function SidebarMenuSubButton({...@@ -773,7 +773,7 @@ function SidebarMenuSubButton({
773}) {773}) {
774 const defaultProps = {774 const defaultProps = {
775 className: cn(775 className: cn(
776 "-translate-x-px flex h-7 min-w-0 cursor-pointer items-center gap-2 overflow-hidden rounded-lg px-2 text-sidebar-foreground outline-hidden ring-ring hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-row-active active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-muted-foreground",776 "-translate-x-px flex h-7 min-w-0 cursor-pointer items-center gap-2 overflow-hidden rounded-lg px-2 text-sidebar-foreground outline-hidden ring-ring hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-inset active:bg-sidebar-row-active active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-muted-foreground",
777 "data-[active=true]:bg-sidebar-row-selected data-[active=true]:text-sidebar-foreground",777 "data-[active=true]:bg-sidebar-row-selected data-[active=true]:text-sidebar-foreground",
778 size === "sm" && "text-xs",778 size === "sm" && "text-xs",
779 size === "md" && "text-sm",779 size === "md" && "text-sm",
apps/web/src/components/ui/switch.tsx+1-1
...@@ -18,7 +18,7 @@ function Switch({...@@ -18,7 +18,7 @@ function Switch({
18 return (18 return (
19 <SwitchPrimitive.Root19 <SwitchPrimitive.Root
20 className={cn(20 className={cn(
21 "inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 cursor-pointer items-center rounded-full p-[2px] outline-none transition-[background-color,box-shadow] duration-200 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:cursor-not-allowed data-disabled:opacity-64 data-[mixed]:bg-input",21 "inline-flex h-[calc(var(--thumb-size)+2px)] w-[calc(var(--thumb-size)*2-2px)] shrink-0 cursor-pointer items-center rounded-full p-[2px] outline-none transition-[background-color,box-shadow] duration-200 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:cursor-not-allowed data-disabled:opacity-64 data-[mixed]:bg-input",
22 size === "sm"22 size === "sm"
23 ? "[--thumb-size:--spacing(4)] sm:[--thumb-size:--spacing(3.5)]"23 ? "[--thumb-size:--spacing(4)] sm:[--thumb-size:--spacing(3.5)]"
24 : "[--thumb-size:--spacing(5)] sm:[--thumb-size:--spacing(4)]",24 : "[--thumb-size:--spacing(5)] sm:[--thumb-size:--spacing(4)]",
apps/web/src/components/ui/textarea.tsx+1-1
...@@ -17,7 +17,7 @@ function Textarea({ className, size = "default", unstyled = false, ...props }: T...@@ -17,7 +17,7 @@ function Textarea({ className, size = "default", unstyled = false, ...props }: T
17 className={17 className={
18 cn(18 cn(
19 !unstyled &&19 !unstyled &&
20 "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)] 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-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] sm:text-sm dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",20 "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)] 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-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] has-focus-visible:ring-inset not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] sm:text-sm dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:has-not-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
21 className,21 className,
22 ) || undefined22 ) || undefined
23 }23 }
apps/web/src/components/ui/toast.tsx+2-2
...@@ -98,7 +98,7 @@ const toastCornerDismissClass = "absolute z-20 -top-1.5 -right-1.5";...@@ -98,7 +98,7 @@ const toastCornerDismissClass = "absolute z-20 -top-1.5 -right-1.5";
98const toastCornerOrbClass = cn(98const toastCornerOrbClass = cn(
99 "inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border border-border/60 bg-popover/92 text-muted-foreground shadow-sm outline-none backdrop-blur-sm",99 "inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border border-border/60 bg-popover/92 text-muted-foreground shadow-sm outline-none backdrop-blur-sm",
100 "transition-[color,background-color,box-shadow] hover:bg-popover hover:text-foreground",100 "transition-[color,background-color,box-shadow] hover:bg-popover hover:text-foreground",
101 "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",101 "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
102);102);
103103
104function handleToastDismissClick(104function handleToastDismissClick(
...@@ -225,7 +225,7 @@ function ToastDescriptionAndExpandable({...@@ -225,7 +225,7 @@ function ToastDescriptionAndExpandable({
225 className={cn(225 className={cn(
226 "group flex min-w-0 w-full cursor-pointer select-none items-start gap-1.5 rounded-sm text-left outline-none ring-offset-background",226 "group flex min-w-0 w-full cursor-pointer select-none items-start gap-1.5 rounded-sm text-left outline-none ring-offset-background",
227 "transition-colors hover:bg-muted/40",227 "transition-colors hover:bg-muted/40",
228 "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",228 "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
229 )}229 )}
230 onClick={toggle}230 onClick={toggle}
231 onKeyDown={onKeyDown}231 onKeyDown={onKeyDown}
apps/web/src/components/ui/toggle.tsx+1-1
...@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";...@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";
6import { cn } from "~/lib/utils";6import { cn } from "~/lib/utils";
77
8const toggleVariants = cva(8const toggleVariants = cva(
9 "[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base text-foreground outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 aria-disabled:opacity-64 data-pressed:bg-input/64 data-pressed:text-accent-foreground sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",9 "[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base text-foreground outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 aria-disabled:opacity-64 data-pressed:bg-input/64 data-pressed:text-accent-foreground sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
10 {10 {
11 defaultVariants: {11 defaultVariants: {
12 size: "default",12 size: "default",
apps/web/src/components/ui/wizard.tsx+2-2
...@@ -96,11 +96,11 @@ export function WizardSteps({...@@ -96,11 +96,11 @@ export function WizardSteps({
96 ? { type: "button" as const, disabled: isStepDisabled?.(index) }96 ? { type: "button" as const, disabled: isStepDisabled?.(index) }
97 : {})}97 : {})}
98 className={cn(98 className={cn(
99 "flex w-full min-w-0 items-center gap-2 rounded-lg px-2.5 py-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring max-sm:justify-center max-sm:px-2",99 "flex w-full min-w-0 items-center gap-2 rounded-lg px-2.5 py-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring max-sm:justify-center max-sm:px-2",
100 onStepChange &&100 onStepChange &&
101 "cursor-pointer hover:bg-card disabled:cursor-default disabled:hover:bg-transparent",101 "cursor-pointer hover:bg-card disabled:cursor-default disabled:hover:bg-transparent",
102 index === currentStep &&102 index === currentStep &&
103 "bg-card text-foreground shadow-xs ring-1 ring-black/5 hover:bg-card dark:shadow-none dark:ring-white/5",103 "bg-card text-foreground shadow-xs ring-1 ring-inset ring-black/5 hover:bg-card dark:shadow-none dark:ring-white/5",
104 )}104 )}
105 aria-current={index === currentStep ? "step" : undefined}105 aria-current={index === currentStep ? "step" : undefined}
106 aria-label={`${step}, step ${index + 1}${index < currentStep && summaries?.[index] ? `, ${summaries?.[index]}` : ""}`}106 aria-label={`${step}, step ${index + 1}${index < currentStep && summaries?.[index] ? `, ${summaries?.[index]}` : ""}`}
apps/web/src/components/usage/UsageLimits.tsx+1-1
...@@ -110,7 +110,7 @@ function WindowBar({...@@ -110,7 +110,7 @@ function WindowBar({
110 role="img"110 role="img"
111 aria-label={summary}111 aria-label={summary}
112 tabIndex={0}112 tabIndex={0}
113 className="relative h-6 cursor-default rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"113 className="relative h-6 cursor-default rounded-full outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
114 />114 />
115 }115 }
116 >116 >
apps/web/src/components/usage/UsageLimitsPooled.tsx+1-1
...@@ -255,7 +255,7 @@ function PoolSegment({...@@ -255,7 +255,7 @@ function PoolSegment({
255 type="button"255 type="button"
256 style={{ gridColumn: index, gridRow: 1 }}256 style={{ gridColumn: index, gridRow: 1 }}
257 aria-label={`${account.displayName ?? (account.email ? accountInitials(account.email) : account.driver)}: ${remaining}% left${resetsIn ? `, ${resetsIn}` : ""}${credits ? `, ${credits} reset ${credits === 1 ? "credit" : "credits"} banked` : ""}`}257 aria-label={`${account.displayName ?? (account.email ? accountInitials(account.email) : account.driver)}: ${remaining}% left${resetsIn ? `, ${resetsIn}` : ""}${credits ? `, ${credits} reset ${credits === 1 ? "credit" : "credits"} banked` : ""}`}
258 className="relative h-5 min-w-0 cursor-pointer overflow-hidden rounded-md bg-muted text-start outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[popup-open]:ring-1 data-[popup-open]:ring-border @2xl/pool:h-8"258 className="relative h-5 min-w-0 cursor-pointer overflow-hidden rounded-md bg-muted text-start outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[popup-open]:ring-1 data-[popup-open]:ring-inset data-[popup-open]:ring-border @2xl/pool:h-8"
259 />259 />
260 }260 }
261 >261 >
apps/web/src/index.css+13-7
...@@ -684,6 +684,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil...@@ -684,6 +684,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
684 * {684 * {
685 @apply border-border outline-ring/50;685 @apply border-border outline-ring/50;
686 }686 }
687 /* The app's 2px outlines stay inside controls at clipped and scrollport edges. */
688 *,
689 ::before,
690 ::after {
691 outline-offset: -2px;
692 }
687 :where([data-slot="menu-popup"], [data-slot="select-popup"], [data-slot="popover-popup"]):focus,693 :where([data-slot="menu-popup"], [data-slot="select-popup"], [data-slot="popover-popup"]):focus,
688 :where(694 :where(
689 [data-slot="menu-popup"],695 [data-slot="menu-popup"],
...@@ -887,10 +893,10 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil...@@ -887,10 +893,10 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
887 @keyframes settings-search-target-pulse {893 @keyframes settings-search-target-pulse {
888 0%,894 0%,
889 100% {895 100% {
890 box-shadow: 0 0 0 0 transparent;896 box-shadow: inset 0 0 0 0 transparent;
891 }897 }
892 50% {898 50% {
893 box-shadow: 0 0 0 2px color-mix(in oklab, var(--color-primary) 70%, transparent);899 box-shadow: inset 0 0 0 2px color-mix(in oklab, var(--color-primary) 70%, transparent);
894 }900 }
895 }901 }
896902
...@@ -1015,14 +1021,14 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil...@@ -1015,14 +1021,14 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil
10151021
1016 .settings-slider:focus-visible::-webkit-slider-thumb {1022 .settings-slider:focus-visible::-webkit-slider-thumb {
1017 box-shadow:1023 box-shadow:
1018 0 0 0 3px var(--background),1024 inset 0 0 0 1px var(--background),
1019 0 0 0 5px var(--ring);1025 inset 0 0 0 3px var(--ring);
1020 }1026 }
10211027
1022 .settings-slider:focus-visible::-moz-range-thumb {1028 .settings-slider:focus-visible::-moz-range-thumb {
1023 box-shadow:1029 box-shadow:
1024 0 0 0 3px var(--background),1030 inset 0 0 0 1px var(--background),
1025 0 0 0 5px var(--ring);1031 inset 0 0 0 3px var(--ring);
1026 }1032 }
10271033
1028 @media (forced-colors: active) {1034 @media (forced-colors: active) {
...@@ -2255,7 +2261,7 @@ code {...@@ -2255,7 +2261,7 @@ code {
2255/* Selected chips (node selection from arrowing into or clicking a chip). */2261/* Selected chips (node selection from arrowing into or clicking a chip). */
2256.composer-tiptap .ProseMirror-selectednode {2262.composer-tiptap .ProseMirror-selectednode {
2257 outline: 2px solid var(--ring);2263 outline: 2px solid var(--ring);
2258 outline-offset: 1px;2264 outline-offset: -2px;
2259 border-radius: 6px;2265 border-radius: 6px;
2260}2266}
22612267
oxlint-plugin-t3code/index.ts+2
...@@ -7,6 +7,7 @@ import noInlineSchemaCompile from "./rules/no-inline-schema-compile.ts";...@@ -7,6 +7,7 @@ import noInlineSchemaCompile from "./rules/no-inline-schema-compile.ts";
7import noManualEffectRuntimeInTests from "./rules/no-manual-effect-runtime-in-tests.ts";7import noManualEffectRuntimeInTests from "./rules/no-manual-effect-runtime-in-tests.ts";
8import noMobileUniwindThemeEscapeHatches from "./rules/no-mobile-uniwind-theme-escape-hatches.ts";8import noMobileUniwindThemeEscapeHatches from "./rules/no-mobile-uniwind-theme-escape-hatches.ts";
9import noNativeTitleTooltip from "./rules/no-native-title-tooltip.ts";9import noNativeTitleTooltip from "./rules/no-native-title-tooltip.ts";
10import noOutsetStateIndicators from "./rules/no-outset-state-indicators.ts";
10import noRawMcpRegistration from "./rules/no-raw-mcp-registration.ts";11import noRawMcpRegistration from "./rules/no-raw-mcp-registration.ts";
11import noTestInLoop from "./rules/no-test-in-loop.ts";12import noTestInLoop from "./rules/no-test-in-loop.ts";
12import noRpcPermissionBypass from "./rules/no-rpc-permission-bypass.ts";13import noRpcPermissionBypass from "./rules/no-rpc-permission-bypass.ts";
...@@ -27,6 +28,7 @@ export default definePlugin({...@@ -27,6 +28,7 @@ export default definePlugin({
27 "no-manual-effect-runtime-in-tests": noManualEffectRuntimeInTests,28 "no-manual-effect-runtime-in-tests": noManualEffectRuntimeInTests,
28 "no-mobile-uniwind-theme-escape-hatches": noMobileUniwindThemeEscapeHatches,29 "no-mobile-uniwind-theme-escape-hatches": noMobileUniwindThemeEscapeHatches,
29 "no-native-title-tooltip": noNativeTitleTooltip,30 "no-native-title-tooltip": noNativeTitleTooltip,
31 "no-outset-state-indicators": noOutsetStateIndicators,
30 "no-raw-mcp-registration": noRawMcpRegistration,32 "no-raw-mcp-registration": noRawMcpRegistration,
31 "no-test-in-loop": noTestInLoop,33 "no-test-in-loop": noTestInLoop,
32 "no-rpc-permission-bypass": noRpcPermissionBypass,34 "no-rpc-permission-bypass": noRpcPermissionBypass,
oxlint-plugin-t3code/rules/no-outset-state-indicators.ts created+126
...@@ -0,0 +1,126 @@
1import { defineRule, type ESTree } from "@oxlint/plugins";
2
3const CLASS_COMPOSERS = new Set(["cn", "clsx", "classNames", "cva", "twMerge"]);
4const STATE_VARIANT =
5 /(?:^|[^\w])(?:focus(?:-visible|-within)?|selected|checked|pressed)(?=$|[^\w])/u;
6const RING_WIDTH = /^ring(?:-(?:[1-9]\d*|\[[^\]]+\]|\(length:[^)]+\)))?$/u;
7// Pixel widths only; arbitrary values like `outline-[#243c5a]` are colors.
8const OUTLINE_WIDTH = /^outline-(?:(\d+)|\[(?:length:)?(\d+(?:\.\d+)?)px\])$/u;
9const INWARD_OFFSET = /^-outline-offset-(\d+)$/u;
10
11/** Splits `dark:[&_a]:ring-2!` into its variants and utility, ignoring colons inside brackets. */
12function parseClass(token: string) {
13 const variants: string[] = [];
14 let depth = 0;
15 let start = 0;
16 for (let index = 0; index < token.length; index++) {
17 const character = token[index];
18 if (character === "[" || character === "(") depth++;
19 else if (character === "]" || character === ")") depth--;
20 else if (character === ":" && depth === 0) {
21 variants.push(token.slice(start, index));
22 start = index + 1;
23 }
24 }
25 return { variants, utility: token.slice(start).replace(/^!|!$/gu, "") };
26}
27
28type ParsedClass = ReturnType<typeof parseClass>;
29
30/** A companion applies when its variants are a subset of the indicator's variants. */
31const covers = (companion: ParsedClass, indicator: ParsedClass) =>
32 companion.variants.every((variant) => indicator.variants.includes(variant));
33
34const isClassComposer = (node: ESTree.Node) =>
35 node.type === "CallExpression" &&
36 node.callee.type === "Identifier" &&
37 CLASS_COMPOSERS.has(node.callee.name);
38
39/** Strings a composition always includes; branches, class maps, and other calls are not guaranteed. */
40function collectStrings(node: unknown, out: string[]) {
41 if (Array.isArray(node)) {
42 for (const child of node) collectStrings(child, out);
43 return;
44 }
45 if (typeof node !== "object" || node === null || !("type" in node)) return;
46 const current = node as ESTree.Node;
47 if (/^(?:Conditional|Logical|Object)Expression$/u.test(current.type)) return;
48 if (current.type === "CallExpression" && !isClassComposer(current)) return;
49 if (current.type === "Literal" && typeof current.value === "string") out.push(current.value);
50 if (current.type === "TemplateElement") out.push(current.value.cooked ?? current.value.raw);
51 for (const [key, value] of Object.entries(current)) {
52 if (key !== "parent") collectStrings(value, out);
53 }
54}
55
56/** Classes guaranteed alongside a node by each enclosing `cn(...)`, so sibling companions count. */
57function scopeClasses(node: ESTree.Node, own: ParsedClass[]) {
58 const strings: string[] = [];
59 for (let current = node.parent; current; current = current.parent) {
60 if (/Statement$|Declaration$|^JSX(?:Attribute|Element)$/u.test(current.type)) break;
61 if (isClassComposer(current)) collectStrings(current, strings);
62 }
63 return [...own, ...strings.join(" ").split(/\s+/u).map(parseClass)];
64}
65
66function offenders(text: string, node: ESTree.Node) {
67 const own = text.split(/\s+/u).filter(Boolean).map(parseClass);
68 const found: string[] = [];
69 let scope: ParsedClass[] | undefined;
70 for (const token of own) {
71 const { utility, variants } = token;
72 const label = [...variants, utility].join(":");
73 // Positive offsets push the outline out of the element; the app default is inward.
74 if (/^outline-offset-|^\[outline-offset:/u.test(utility)) {
75 found.push(label);
76 continue;
77 }
78 if (!STATE_VARIANT.test(variants.join(":"))) continue;
79 if (RING_WIDTH.test(utility)) {
80 scope ??= scopeClasses(node, own);
81 const inset = scope.some((other) => other.utility === "ring-inset" && covers(other, token));
82 if (!inset) found.push(label);
83 continue;
84 }
85 const outline = OUTLINE_WIDTH.exec(utility);
86 if (outline) {
87 const width = Number(outline[1] ?? outline[2]);
88 if (width <= 2) continue;
89 scope ??= scopeClasses(node, own);
90 const inward = scope.some((other) => {
91 const offset = INWARD_OFFSET.exec(other.utility);
92 return offset !== null && Number(offset[1]) >= width && covers(other, token);
93 });
94 if (!inward) found.push(label);
95 }
96 }
97 return found;
98}
99
100export default defineRule({
101 meta: {
102 type: "problem",
103 docs: {
104 description:
105 "Keep focus and selection indicators inside their element, where an ancestor cannot clip them.",
106 },
107 },
108 create(context) {
109 const check = (node: ESTree.Node, text: string) => {
110 for (const utility of offenders(text, node)) {
111 context.report({
112 node,
113 message: `${utility} paints a focus or selection indicator outside its element, where an ancestor can clip it. Add ring-inset under the same variant, drop the positive outline offset, or use a negative offset at least as wide as the outline.`,
114 });
115 }
116 };
117 return {
118 Literal(node) {
119 if (typeof node.value === "string") check(node, node.value);
120 },
121 TemplateLiteral(node) {
122 check(node, node.quasis.map((part) => part.value.cooked ?? part.value.raw).join(" "));
123 },
124 };
125 },
126});
oxlint-plugin-t3code/rules/no-unscoped-has.test.ts+39
...@@ -122,3 +122,42 @@ describe("t3code/no-unscoped-has", () => {...@@ -122,3 +122,42 @@ describe("t3code/no-unscoped-has", () => {
122 `const className = "[body:has([data-dialog-open])_&]:overflow-hidden";`,122 `const className = "[body:has([data-dialog-open])_&]:overflow-hidden";`,
123 );123 );
124});124});
125
126const indicators = createOxlintRuleHarness("t3code/no-outset-state-indicators", {
127 filename: "fixture.tsx",
128});
129
130describe("t3code/no-outset-state-indicators", () => {
131 describe.each(
132 Object.entries({
133 "inset state rings": `const el = <button className="focus-visible:ring-2 focus-visible:ring-inset ring-1 ring-border" />;`,
134 "an unconditional inset": `const el = <button className="ring-inset data-selected:ring-2" />;`,
135 "an inset in a sibling composer argument": `const value = cn(active && "dark:focus-visible:ring-2", "focus-visible:ring-inset");`,
136 "an inset in a nested conditional composer": `const value = cn(enabled && cn("focus-visible:ring-2", "focus-visible:ring-inset"));`,
137 "outlines covered by the inward default": `const el = <button className="focus-visible:outline-2 focus-visible:outline-ring" />;`,
138 "wider outlines with a matching inward offset": `const el = <button className="focus-visible:outline-3 focus-visible:-outline-offset-3" />;`,
139 "arbitrary pixel outlines with a matching inward offset": `const el = <button className="focus-visible:outline-[3px] focus-visible:-outline-offset-3" />;`,
140 "arbitrary outline colors": `const el = <button className="focus-visible:outline-[#243c5a]" />;`,
141 "decorative rings without a state": `const el = <img className="ring-2 ring-background" />;`,
142 prose: `const note = "the focus ring stays visible";`,
143 }),
144 )("valid: %s", (name, source) => {
145 indicators.valid(name, source);
146 });
147
148 describe.each(
149 Object.entries({
150 "state rings without an inset": `const el = <button className="focus-visible:ring-2 focus-visible:ring-ring" />;`,
151 "an inset from another state": `const el = <button className="focus:ring-inset focus-visible:ring-2" />;`,
152 "an inset from another conditional branch": `const value = cn(active ? "focus:ring-2" : "ring-inset");`,
153 "an inset passed to another function": `const value = cn("focus-visible:ring-2", label("ring-inset"));`,
154 "arbitrary state ring widths": `const value = cn("has-[:focus-visible]:ring-[3px]");`,
155 "positive outline offsets": `const el = <button className="focus-visible:outline-2 focus-visible:outline-offset-2" />;`,
156 "arbitrary outline offsets": `const el = <button className="focus-visible:[outline-offset:2px]" />;`,
157 "wide outlines without an inward offset":
158 "const className = `focus-visible:outline-4 ${layout}`;",
159 }),
160 )("invalid: %s", (name, source) => {
161 indicators.invalid(name, source);
162 });
163});
packages/shared/src/htmlRender.ts+3-1
...@@ -325,7 +325,9 @@ export function htmlRenderThemeMessage(theme: HtmlRenderTheme) {...@@ -325,7 +325,9 @@ export function htmlRenderThemeMessage(theme: HtmlRenderTheme) {
325// reply reads as a box within the thread, so it stays hidden.325// reply reads as a box within the thread, so it stays hidden.
326const BASE_CSS =326const BASE_CSS =
327 "html{background:var(--background);color:var(--foreground);font-family:var(--font-sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;scrollbar-width:none}" +327 "html{background:var(--background);color:var(--foreground);font-family:var(--font-sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;scrollbar-width:none}" +
328 "html::-webkit-scrollbar{display:none}body{margin:0}code,kbd,pre,samp{font-family:var(--font-mono)}";328 "html::-webkit-scrollbar{display:none}body{margin:0}code,kbd,pre,samp{font-family:var(--font-mono)}" +
329 // Focus outlines paint inside the element, so the frame edge never clips them.
330 ":where(:focus-visible){outline:2px solid var(--ring)}:focus-visible{outline-width:2px!important;outline-offset:-2px!important}";
329331
330function rootRule(theme: HtmlRenderTheme): string {332function rootRule(theme: HtmlRenderTheme): string {
331 const declarations = Object.entries(theme.variables)333 const declarations = Object.entries(theme.variables)
vite.config.ts+5
...@@ -256,6 +256,11 @@ export default defineConfig({...@@ -256,6 +256,11 @@ export default defineConfig({
256 files: ["apps/web/src/**"],256 files: ["apps/web/src/**"],
257 rules: { "shadcn/no-unknown-classes": "error" },257 rules: { "shadcn/no-unknown-classes": "error" },
258 },258 },
259 {
260 // State indicators paint inward; decorative rings keep their own geometry.
261 files: ["apps/web/src/**"],
262 rules: { "t3code/no-outset-state-indicators": "error" },
263 },
259 {264 {
260 // Colors come from theme tokens so status tones follow custom themes. components/ui265 // Colors come from theme tokens so status tones follow custom themes. components/ui
261 // has no findings and stays covered too.266 // has no findings and stays covered too.