8d39586de5a12d8a44602c9765ebb09fb91187b2
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9f243f569f |
native controls follow the theme
Nothing set `color-scheme`, so the browser drew every control it renders itself as if the page were light. On a dark theme a <select>'s dropdown opened as a white sheet with the page's own pale text on it — unreadable, and unreachable by any utility class, because that list is painted by the browser and is not in the document. `color-scheme` is the only thing that reaches it. It also fixes, for free, the other controls with no styleable interior: date and colour pickers, the spinner on a number input, and default scrollbars where a theme has not replaced them. The `dark` class decides it, because that is the signal that exists — 203 of the 231 themes set applyDarkClass, and the provider puts it on <html> beside data-theme, so the two always agree about the same element. Both are specificity 0-1-0 and themes import after this file, so a theme that disagrees can say so. This is a default, not a decision taken away. Explicit colours on select/option/optgroup underneath, because color-scheme gives the browser's generic dark rather than the theme's. Where a browser honours them — Firefox, Chrome on Linux and Windows — the list becomes the theme's own popover surface. Where it does not, Safari and Chrome on macOS use a genuinely native menu and the scheme carries it, which is a great deal better than white on white. Base-layer rules, so a utility still wins and `bg-transparent` on a select behaves as it did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8fa3f7da3e |
hex everywhere, because the palettes were half broken
Reported as "the ellipsis doesn't show in the streaming bubble". It was the visible corner of something much larger: every Tailwind semantic utility was broken under all eleven ported palettes. `@theme` mapped `--color-muted-foreground: hsl(var(--muted-foreground))`, which needs an unwrapped triplet. A ported palette publishes `#6e6a86`, so the mapping produced `hsl(#6e6a86)` — invalid — and the utility fell back to white or transparent: duck-dark text-muted-foreground=rgb(148,163,184) bg-muted=rgb(30,41,59) rose-pine text-muted-foreground=rgb(255,255,255) bg-muted=rgba(0,0,0,0) The themes looked convincing because panels, the page and the header read their variables DIRECTLY rather than through a utility class. Everything that went through Tailwind was quietly wrong, and the thinking dots — `bg-muted-foreground/60` — were simply invisible. So: 123 HSL triplets converted to hex across the three hand-written themes, and 37 `hsl(var(--x))` mappings unwrapped. One format everywhere, which is also the format every published palette ships in. The triplets only ever existed so `bg-card/50` could get opacity. That was tested earlier tonight and turned out unnecessary — Tailwind v4 emits `color-mix(in oklab, var(--card) 50%, transparent)` for any colour format. Having proved it, I then left the mappings wrapped, which is what broke the palettes. Verified across six themes: `bg-muted` and `bg-muted-foreground/60` resolve in every one of them, and Duck's values are unchanged through the conversion — `bg-muted` is `rgb(30,41,59)` before and after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
94a55fe679 |
panel chrome stops being black
Reported against a dark theme: the panel header's title, zoom readout and controls were hardcoded `text-black`, `text-black/60`, `hover:bg-black/10` and `border-black/10`. Legible on Duck's pale glass, invisible on every palette theme — which is the whole class of bug the theme work exists to remove, sitting in the one component I moved between packages and did not re-read. Three derived tokens, so no theme has to think about them: --panel-header-fg-dim the header's ink at 60% (the zoom readout) --panel-header-border the header's ink at 15% (the rule under it) --panel-control-hover the header's ink at 12% (control hover) The swap, drop-target and app-picker chrome used `duck-teal` and `text-white` directly. Those now use `--panel-accent` and a new `--panel-accent-fg`, so a chip on the accent stays readable whatever the accent is. Verified across five themes rather than by reading: duck-dark ink=rgb(248,250,252) duck-light ink=rgb(2,8,23) rose-pine ink=rgb(224,222,244) gruvbox ink=rgb(235,219,178) catppuccin-latte ink=rgb(76,79,105) Two comments corrected while there: both asserted the chrome "is black throughout", which was true when written and is now the opposite of true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b106f67288 |
twenty tokens is the contract; eleven palettes prove it
Officer now ships fourteen themes. Eleven of them were written by other people, ported unchanged, and none of them knows that panels, docks, glass, grids or scrollbars exist. ── What changed ── `base.css` derives every surface from the standard tokens: a panel is `card` at 92%, the header is `card` at 85%, grid lines are `border` faded, scrollbars are `muted-foreground`. Those defaults sit on `:root`; a theme sits on `[data-theme='x']` and both land on <html>, so a theme's own declaration simply outranks them. No machinery. State tones are defaulted too — shadcn's vocabulary has `destructive` and nothing else, and green-means-good is a convention rather than a palette decision. That is what drops the required contract from 29 to 20: exactly the tokens every published shadcn palette already ships. So there are now three tiers, and an author picks one: palette 20 tokens Tokyo Night, Gruvbox, Nord — surfaces derived textured + a few surfaces glass, blur, radius, a wallpaper of its own white-label + assets its own logo and background Duck is tier three and is untouched — verified by probe: it still reports `panel=#ffffff1f`, `blur=24px` and its own grid, because its declarations outrank the new defaults. ── Verified before committing, not assumed ── The whole HSL-triplet design existed to make `bg-card/50` work. Tested it: Tailwind v4 emits `color-mix(in oklab, var(--card) 50%, transparent)` for a HEX token, with a `@supports` fallback. The unwrapped triplets were never necessary, and the ported palettes are plain hex. Six themes probed in the browser: each derives panels from its own card colour, including the light one (Catppuccin Latte). ── Credit ── The eleven palettes and the idea of a small standard contract come from the owner's `vimazing` repository, which solved this more simply than we had. What Officer adds is the surface layer underneath, so a palette gets a textured app for free. ── One open decision ── `--logo-src` defaults to empty, so a palette theme shows no mark at all. Correct for a third-party theme; probably wrong for Officer, where the alternative is defaulting to the officer.dev logo and letting a white-label theme replace it. One line either way. tsgo clean, suite unchanged, builds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dfb8a6d6cb |
a sixth tone, and no raw palette left in the core apps
The five core apps — FileBrowser, FileViewer, SystemMonitor, Chat, Plugins — now contain ZERO raw Tailwind palette classes. Every colour in them names a state or a surface, so all of it follows the theme. ── Busy ── The 38 ambers were the blocker, and the answer was not to pick between two wrong mappings. They mean RUNNING — Chat's own comment says "the amber a RUNNING task chip wears" — and the vocabulary had no word for it. `warning` would have made a working job look like a failing one, and `--status-in-progress` is blue. So `tone.ts` has six tones instead of five. It was always written as a closed set that could grow when a genuine sixth state appeared; this is that. `--busy-ink` comes with it, and generalises: a fill bright enough to carry black text is far too light to BE text. The other five tones are dark enough to do both jobs, which is why the problem had not surfaced. `text-amber-950` — ink ON the fill — became `text-busy-foreground`, which is what it always was. ── On fidelity, which stopped being the constraint ── Duck's exact colours were preserved up to this point because an extraction has to be provably invisible. The owner has since said the palette is not precious: the wallpaper, the graph-paper grid and the mark are what matter, and everything else can move. So shades collapse — one busy, one red, one green — rather than being approximated to keep old pixels. The verification built for the old constraint keeps its value: it proves the token layer resolves, which is a different claim from "the colours did not change". Verified: the utilities compile (`text-busy-ink`, `bg-busy`, `border-busy`, `text-busy-foreground`) and resolve in the browser. 950 pass / 7 fail unchanged. tsgo clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
105766a4eb |
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>
|