extract the visual layer into its own workspace
The application no longer knows what it looks like. It imports one stylesheet and
mounts one provider; every colour, surface and radius lives in `src/workspaces/theme`.
Proven both directions, because either alone would be worthless:
Duck renders IDENTICALLY to before — checked against screenshots taken before
anything was touched, not asserted.
Plain renders as white with black ink, no component edits — panels turn white, the
teal avatar turns black, the grid fills vanish.
── What moved ──
`globals.css` held three unrelated things: Tailwind wiring, the token contract, and
the Duck brand's hex values. Split along those seams. `base.css` is structure and
names no colour; a theme file is values and nothing else, scoped to
`[data-theme='<id>']`. Adding a theme is a CSS file plus one registry line.
Light and dark are separate themes rather than a mode — the owner's call, and it
removes a concept from the app. Each is written out in full: a theme that only makes
sense as a diff against another cannot be authored by a stranger or sold to a
customer. `.dark` survives only as a per-theme flag, because 396 `dark:` utilities in
33 components resolve against it; retiring those retires the flag.
The `duck-*` utility names are kept for now and pointed at neutral variables, so a
non-Duck theme can define them sensibly. Renaming 1,470 occurrences in the same commit
as the extraction would make any visual difference impossible to attribute. Worth
recording that `duck-dark` was never a brand colour — `#14532d` in light, near-white
in dark, used by 946 components to mean "text". It is `--ink` now.
Surfaces are complete CSS values rather than HSL triplets: `--panel-bg` has to be able
to be a gradient or an image, or the glass the app already has would be unthemeable.
The old provider is a re-export, so its six call sites are untouched and the
extraction stays one variable.
── Bug found by the Plain theme ──
`useThemeSync` stamped the server's `appearance.colorMode` over the local theme on
every load, so choosing Plain and reloading silently reverted it. Diagnosed by asking
the page what it was wearing rather than reasoning: `stored` said `duck-dark` when the
script had written `plain`. The server value is now a starting point for a browser
that has never chosen, not a continuous instruction.
── Tools ──
Two, both of which lied to me before they worked. The snapshot script photographed
light twice and called it a clean run (one browser context, accumulated init scripts);
it now warns when a light/dark pair is identical. The preview server returned the
shell for missing assets, so the logo and wallpaper vanished and read as regressions
in the comparison — it falls through to upstream now.
Dead Tailwind v3 residue deleted: `components/ui/tailwind.{config.ts,css}`, referenced
by nothing, with content globs pointing at a directory that does not exist.
── Not done, deliberately ──
`THEMES-NOTES.md` has the full list. The headlines: the reference repo has not been
read, so the switching mechanism is deliberately the dumbest thing that works; the
wallpaper, panel radius and logo are still component-owned and Plain proves it; and
~985 raw Tailwind palette usages remain, worst in Soulseek.
tsgo clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
App furniture: the rules that are neither Tailwind nor a component, expressed in theme variables.
|
||||
|
||||
These came out of `globals.css` with hardcoded greys baked in — `#444` scrollbars, `rgb(128 128 128 / .3)`
|
||||
thumbs. Small, but exactly the kind of thing that makes a light theme look broken in the code editor and
|
||||
that a theme author has no way to reach. Now every value here is a variable a theme owns.
|
||||
*/
|
||||
|
||||
/* Sonner renders at the document root, above everything the app draws. */
|
||||
[data-sonner-toaster] {
|
||||
z-index: 700 !important;
|
||||
}
|
||||
|
||||
/* Code editor — thin scrollbars. Dark by default under Duck, because the editor surface is dark in both
|
||||
Duck themes; a theme with a light editor sets these to suit. */
|
||||
.code-editor-scrollable {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb) transparent;
|
||||
}
|
||||
|
||||
.code-editor-scrollable::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.code-editor-scrollable::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.code-editor-scrollable::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.code-editor-scrollable::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
/* Chat — the background-task strip. A default horizontal scrollbar is ~15px tall and is laid out INSIDE
|
||||
the row, so it stole most of the chips' breathing room and made the tray read as squeezed. */
|
||||
.task-strip {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thin-thumb) transparent;
|
||||
}
|
||||
|
||||
.task-strip::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.task-strip::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.task-strip::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thin-thumb);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.task-strip:hover::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thin-thumb-hover);
|
||||
}
|
||||
|
||||
/* File browser — while a context menu is open, the rest of the grid stops responding. Behaviour, not
|
||||
appearance, but it lives with the other furniture rather than in a component's stylesheet. */
|
||||
.file-grid:has([data-state='open']) [data-file-item]:not([data-state='open']) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.file-grid:has([data-state='open']) [data-file-item]:not([data-state='open']) .file-item-ellipsis {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
.file-grid [data-file-item][data-state='open'] .file-item-ellipsis {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
Reference in New Issue
Block a user