)
diff --git a/src/client/ui/empty.tsx b/src/client/ui/empty.tsx
index 3a13339..2fdffde 100644
--- a/src/client/ui/empty.tsx
+++ b/src/client/ui/empty.tsx
@@ -6,7 +6,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
) {
return (
)
@@ -72,7 +72,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
+ "text-[11px]/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
diff --git a/src/client/ui/input.tsx b/src/client/ui/input.tsx
index 9bbcc5c..a9c9b91 100644
--- a/src/client/ui/input.tsx
+++ b/src/client/ui/input.tsx
@@ -7,7 +7,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
- "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm ",
+ "h-7 w-full min-w-0 rounded-md border border-input bg-transparent px-2 py-1 text-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-5 file:border-0 file:bg-transparent file:text-[11px] file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 ",
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 ",
className
diff --git a/src/client/ui/separator.tsx b/src/client/ui/separator.tsx
index 94803b8..d8772bc 100644
--- a/src/client/ui/separator.tsx
+++ b/src/client/ui/separator.tsx
@@ -14,7 +14,7 @@ function Separator({
decorative={decorative}
orientation={orientation}
className={cn(
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
+ "shrink-0 bg-border-subtle data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
diff --git a/src/client/ui/theme.css b/src/client/ui/theme.css
index 7c90a87..d46063f 100644
--- a/src/client/ui/theme.css
+++ b/src/client/ui/theme.css
@@ -44,6 +44,40 @@
* host skin that re-paints a token is picked up live.
*/
@theme inline {
+ /*
+ * TYPE SCALE = THE PLUGIN'S OWN, not Tailwind's default (14/12px). Every
+ * other surface of this plugin (files, changes, side chat, settings) is set
+ * at 12px body / 11px meta / 10px micro with 16-20px line heights, so the
+ * vendored components inherit that scale through the ordinary `text-sm` /
+ * `text-xs` classes instead of every page re-overriding them.
+ */
+ --text-xs: 0.6875rem;
+ --text-xs--line-height: 1rem;
+ --text-sm: 0.75rem;
+ --text-sm--line-height: 1.125rem;
+ --text-base: 0.8125rem;
+ --text-base--line-height: 1.25rem;
+ --text-lg: 0.875rem;
+ --text-lg--line-height: 1.375rem;
+
+ /*
+ * ELEVATION = TOKENS, AND ONLY WHERE THE PLUGIN USES IT. Static panels in
+ * this plugin are flat with a hairline border and a hover fill; only floating
+ * layers take the host's own elevation tokens (`--dsw-shadow-lv2/3`, the same
+ * ones the plugin's settings modal and popovers already wear).
+ */
+ --shadow-2xs: none;
+ --shadow-xs: none;
+ --shadow-sm: none;
+ --shadow-md: var(--dsw-shadow-lv2);
+ --shadow-lg: var(--dsw-shadow-lv3);
+ --shadow-xl: var(--dsw-shadow-lv3);
+
+ --color-border-subtle: var(--border-subtle);
+ --color-state-business-tertiary: var(--dsw-alias-state-business-tertiary);
+ --color-state-success-tertiary: var(--dsw-alias-state-success-tertiary);
+ --color-state-warn-tertiary: var(--dsw-alias-state-warn-tertiary);
+
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@@ -110,8 +144,11 @@
--accent-foreground: var(--dsw-alias-label-primary);
--destructive: var(--dsw-alias-state-error-primary);
--destructive-foreground: var(--dsw-alias-label-primary-foreground);
- --border: var(--dsw-alias-border-l4);
- --input: var(--dsw-alias-border-l4);
+ /* The plugin's own vocabulary: 1px borders are `border-l2` (10%), hairlines
+ * `border-l1` (4%). `l4` (16%) is noticeably darker than every other tab. */
+ --border: var(--dsw-alias-border-l2);
+ --border-subtle: var(--dsw-alias-border-l1);
+ --input: var(--dsw-alias-border-l2);
--ring: var(--dsw-alias-state-business-primary);
--success: var(--dsw-alias-state-success-primary);
--warning: var(--dsw-alias-state-warn-primary);
diff --git a/tests/ui-foundation.spec.ts b/tests/ui-foundation.spec.ts
index b7bb9d4..d2d08b8 100644
--- a/tests/ui-foundation.spec.ts
+++ b/tests/ui-foundation.spec.ts
@@ -47,8 +47,10 @@ const TOKEN_BRIDGE: Array<[shadcn: string, dsw: string]> = [
['--accent-foreground', '--dsw-alias-label-primary'],
['--destructive', '--dsw-alias-state-error-primary'],
['--destructive-foreground', '--dsw-alias-label-primary-foreground'],
- ['--border', '--dsw-alias-border-l4'],
- ['--input', '--dsw-alias-border-l4'],
+ // The plugin's own vocabulary: 1px borders are border-l2 (10%), hairlines
+ // border-l1 (4%). l4 is noticeably darker than every other tab's chrome.
+ ['--border', '--dsw-alias-border-l2'],
+ ['--input', '--dsw-alias-border-l2'],
['--ring', '--dsw-alias-state-business-primary'],
['--success', '--dsw-alias-state-success-primary'],
['--warning', '--dsw-alias-state-warn-primary'],
@@ -154,6 +156,9 @@ describe('tailwind entry: theme + utilities only', () => {
// scale — never a paint value of its own.
const definedElsewhere = new Set([
...TOKEN_BRIDGE.map(([name]) => name.slice(2)),
+ // Non-bridged locals the bridge forwards to (e.g. `--border-subtle`),
+ // read straight out of the file so they cannot drift out of the set.
+ ...[...withoutComments.matchAll(/^\s*--([a-z0-9-]+)\s*:/gm)].map(m => m[1]),
'radius', 'radius-sm', 'radius-md', 'radius-lg', 'radius-xl',
])
for (const declaration of withoutComments.matchAll(/^\s*(--[a-z0-9-]+):\s*([^;]+);/gm)) {
@@ -163,6 +168,9 @@ describe('tailwind entry: theme + utilities only', () => {
const refName = reference?.[1]
const ok = (refName !== undefined && (refName.startsWith('dsw-') || definedElsewhere.has(refName)))
|| /^[0-9.]+(?:px|rem|em|%)$/.test(value)
+ // `none` is how the plugin says "flat": static panels take no elevation
+ // (the floating layers map to the host's --dsw-shadow-lv* tokens).
+ || value === 'none'
expect(ok, `${name}: ${value}`).toBe(true)
}
})