Download

name: adom-theme-system description: THE Adom application theme system, the canonical spec every Adom desktop app implements (Hydrogen Desktop is the reference; Adom Desktop is the next implementer). Five colour schemes with exact tokens, slugs vs product labels, per-scheme font pairings, the VS Code theme contract, storage migration rules, and the guard scripts that keep implementations honest. Trigger words - adom theme, adom themes, color scheme, colour scheme, theme tokens, adom design tokens, implement adom themes, adom studio theme, kickstand, adom slate, bright inverse, scheme slugs, adom fonts, satoshi, jetbrains mono, familjen grotesk, vs code adom theme.

The Adom Theme System

One theme system for every Adom desktop surface. Hydrogen Desktop (HD) is the REFERENCE IMPLEMENTATION; this skill is the contract for implementing the same system elsewhere, starting with Adom Desktop (AD). The design was measured into place on 2026-07-25 (contrast maths, live pixel sampling, in-workbench font probes), so treat the numbers here as decisions, not suggestions.

Source of truth files (HD repo, hydrogen-desktop - PRIVATE):

  • src/routes/styles.css - the token set and all five scheme blocks
  • tools/gen-vscode-themes.mjs - generates the VS Code theme pack FROM the tokens (pack v7.2.0)
  • scripts/check-scheme-parity.sh + scripts/check-installed-themes.sh - the guards
  • scripts/install-brand-fonts.sh - webfont delivery into code-server
  • src/lib/stores/settings.ts - FONTS_FOR_SCHEME, slugs, labels, migrations

Because that repo is private, the wiki page carries usable copies (pinned @ a4c764be, pack v7.2.0) at https://wiki.adom.inc/adom/adom-theme-system :

  • tokens.css - all five scheme blocks as drop-in CSS custom properties
  • vscode/ - the complete generated theme pack; copy into ~/.local/share/code-server/extensions/adom.adom-themes-<version>/ (the page's install.sh does this automatically when code-server is present)
  • fonts/ - the REDISTRIBUTABLE faces only: JetBrains Mono + Familjen Grotesk (both OFL 1.1, license texts beside the files). Satoshi binaries are NOT there and must never be uploaded to any public server (Fontshare EULA); fonts/satoshi/README.md documents the official Fontshare endpoints and the page's install.sh fetches the faces from them automatically. Full rules: fonts/LICENSES.md on the page.
  • reference/ - byte-exact copies of the generator, template, font installer, and guards

1. The five schemes

slug (storage key) product label character
studio Adom Studio THE DEFAULT (John 2026-07-27; this design briefly staged as 'Adom Studio v2', then took over the name — zero-migration rename). VS Code's Dark 2026 architecture re-grounded on the brand: body #161b22, base text #c9d1d9, every gray on the brand slate hue at 2026's exact contrast steps, product blue re-issued as Adom teal, syntax untouched. Generated by reference/gen-adom-studio-v2.mjs from reference/reference-2026-dark.json. The old template-projected Studio is abandoned.
studio-bright Adom Studio Dark (Brighter Text) Everything on the deep ground, brighter interface text.
studio-dark Adom Studio Dark The original screen-tuned dark set, quieter text. This is the TOKEN BASE.
kickstand Adom Kickstand The design firm's identity palette: #191919 ground, muted navy chrome, headline face in prose.
slate Adom Slate Calm cool blue-grey; uses the code font Windows already ships.

Invariant history: the old "Adom Studio == Adom Studio Dark (Brighter Text) with --bg/--surface traded" parity guard applied to the ABANDONED template-projected Studio and is retired with it. The new invariant for Adom Studio (since 2026-07-27) is: it is a pure function of reference/reference-2026-dark.json + the two brand anchors, regenerated by reference/gen-adom-studio-v2.mjs — never hand-edited. check-scheme-parity.sh still guards the APP schemes (studio vs studio-bright tokens in styles.css); it no longer constrains the VS Code Studio theme.

2. The tokens, per scheme (generated from styles.css, do not hand-edit)

token Adom Studio Adom Studio Dark (Brighter Text) Adom Studio Dark Adom Kickstand Adom Slate
--bg #161b22 #0d1117 #0d1117 #191919 #17212e
--surface #0d1117 #161b22 #161b22 #202127 #202a37
--elevated #1c2128 #1c2128 #1c2128 #282a30 #28323f
--overlay #21262d #21262d #21262d #0e1c31 #313b48
--border #30363d #30363d #30363d #3d3f46 #3d4754
--text #e6edf3 #e6edf3 #e6edf3 #f5f7f9 #f0f6fc
--text-body #c9d1d9 #c9d1d9 #c9d1d9 #d7dde3 #d3e0ef
--text-2 #c9d1d9 #c9d1d9 #8b949e #97a0aa #93a6bd
--text-3 #6e7681 #6e7681 #484f58 #5a626b #4a5a70
--accent #00b8b1 #00b8b1 #00b8b1 #00b8b1 #00b8b1
--accent-hover #00d4cb #00d4cb #00d4cb #00d4cb #00d4cb
--accent-bright #00e6dc #00e6dc #00e6dc #8fd3d4 #5fe6de
--on-accent #05221f #05221f #05221f #003c3f #04211f
--blue #64abff #64abff #64abff #64abff #64abff
--blue-fill #0061ef #0061ef #0061ef #0061ef #0061ef
--purple #8c6bf7 #8c6bf7 #8c6bf7 #8c6bf7 #8c6bf7
--green #3fb950 #3fb950 #3fb950 #3fb950 #3fb950
--red #f85149 #f85149 #f85149 #f85149 #f85149
--yellow #d29922 #d29922 #d29922 #d29922 #d29922

Rationale you must not undo:

  • Body text lives in the 9-13:1 contrast band. Below strains; above ~14:1 dark-mode text haloes (that is why Adom Studio's primary text is 13.0:1, not 15.8:1).
  • The teal accent must stay >= 6:1 on every ground. That constraint is why there is no teal or purple GROUND: they were built and measured into failure.
  • Five distinct dark grounds are impossible inside those constraints (closest achievable pair measured 24 apart, i.e. invisible). The family therefore differentiates on AXES: hue (Slate), elevation (Adom Studio), legibility (Bright), accents (Kickstand).
  • --blue-fill (#0061ef) is 3.6:1 on dark grounds: FILLS AND ACCENTS ONLY, never text. Links and informational text use --blue (#64abff).
  • --green/--red/--yellow are semantic (success/destructive/warning) and nothing else. A settings confirmation is not "success"; use the accent.
  • Kickstand's chrome navy is a DERIVATION, not a new colour: mix(#00204f 45%, #191919 55%) = #0e1c31. Full-strength navy as a dropdown fill read as "that weird blue" and was demoted to accents only.
  • Kickstand's derived chrome greys are COOL-SHIFTED (correction, 2026-07-26): the firm's PDF specifies only the #191919 Black ground, which stays byte-exact. The greys we derived from it (--surface #202127, --elevated #282a30, --border #3d3f46) carry a few points of blue because pure neutral grey beside the blue-tinted sibling schemes and the wiki is perceived as warm brown (simultaneous contrast; the shipped pixels measured exactly neutral, and still read "brownish"). Implementers copying Kickstand must copy these corrected values, not re-derive neutral ones from the ground.

3. Token roles (what drives what)

token role
--bg the app's deepest ground / the editor sheet
--surface panels, toolbars, dialogs
--elevated hover surfaces, raised cards, and THE ACTIVE TAB (always the raised sheet; mapping tabs to --surface inverts into a black tab in Adom Studio)
--overlay menus, dropdowns, tooltips, popovers
--text emphasis only (headings, active items)
--text-body running text
--text-2 / --text-3 secondary / disabled-borders-only (text-3 fails AA as text)

4. Slugs vs labels, and the migration law

Slugs are storage keys (persisted preferences, data attributes, CSS selectors). Labels are product names, and they are EXACTLY the VS Code theme names, so one look has one name on every surface.

History that bites: on 2026-07-25 the slug studio CHANGED MEANING (it was the dark base, it is now the flagship). Any store that ever held old values must translate them ONCE under old semantics, guarded by a version marker, because the string alone cannot say which era wrote it. HD's pattern: a NEW localStorage key (hd-color-scheme2) whose legacy-key read translates {studio->studio-dark, studio-bright-inverse->studio, inverse->studio, blueprint->slate, contrast->studio-bright, graphite->slate} then deletes the legacy key; plus desktop.scheme_slug_version = 2 on the saved-preference layer. If AD stores scheme state, implement the same translation once, and NEVER reuse a slug again without bumping the version and extending the map.

Unknown/retired slug at load time: fall back to studio explicitly. Note the base tokens ARE studio-dark's look, so "no attribute set" silently paints Dark, not the default: always set the scheme attribute explicitly.

5. Fonts (part of the theme, three faces only)

Brand faces: Familjen Grotesk (headline), Satoshi (body/prose), JetBrains Mono (code, SIL OFL 1.1 - ship the licence with the files). Do not add faces; variety for its own sake is how a design system becomes a font menu.

Per-scheme pairing (FONTS_FOR_SCHEME):

scheme code stack prose stack
studio, studio-bright, studio-dark 'JetBrains Mono', 'Cascadia Code', Consolas, ui-monospace, monospace Satoshi, -apple-system, 'Segoe UI', sans-serif
kickstand same code stack 'Familjen Grotesk', Satoshi, -apple-system, 'Segoe UI', sans-serif
slate 'Cascadia Code', Consolas, 'JetBrains Mono', ui-monospace, monospace Satoshi stack

Delivery, learned the hard way:

  • Webfonts must be served from YOUR OWN origin with ABSOLUTE paths. HD's relative ./adom-fonts/... URLs 404'd for weeks while a Windows-installed copy silently covered for them; nothing anywhere admitted it.
  • Isolated webviews (the Claude chat panel) do NOT inherit your page's webfonts. Their brand faces resolve only from WINDOWS-INSTALLED fonts, so the installer must keep shipping Satoshi (and Familjen) as Windows fonts even when the main app uses webfonts.
  • A fallback chain never fails, it silently falls through. Any font UI must display the RESOLVED family, measured in the document that renders it, not the configured first name.
  • VS Code key trap: chat.fontFamily is the chat PROSE; chat.editor.fontFamily is the chat's CODE BLOCKS. They read backwards and were once wired backwards.

6. The VS Code theme contract

The editor themes are GENERATED from the tokens (HD tools/gen-vscode-themes.mjs), one theme per scheme, labels identical to the picker labels. Rules:

  • An unknown workbench.colorTheme name silently falls back to VS Code's DEFAULT LIGHT theme. Renames must therefore update the installed pack AND the live setting in one step, and a machine-side guard (check-installed-themes.sh) should verify the installed pack, because repo-vs-repo checks cannot see a stale machine.
  • The generator wipes its output dir before writing so a renamed theme cannot leave a stale file behind.
  • Current pack: adom.adom-theme (Adom Studio / Adom Studio Dark (Brighter Text) / Adom Studio Dark / Adom Kickstand / Adom Slate), version-locked to this package: the extension version and the package version are THE SAME NUMBER, bumped together in the same commit, every time.
  • The extension is NEVER in any marketplace/gallery, so gallery update checks return null for it by design; the only update channel is adom-wiki pkg update. Currency check = installed adom.adom-theme version vs this package version.
  • Legacy: before 2026-08-02 this was adom-theme-system + adom.adom-themes (7.x line); install.sh purges the old extension id on every install.

Delivering the pack: install it, never copy it

Copying the pack folder into ~/.local/share/code-server/extensions/ does not install it. The folder is absent from that directory's extensions.json, and code-server PRUNES unregistered folders on its next launch. The pack disappears before the user ever sees it, the picker lists zero "Adom ..." themes, and a workbench.colorTheme of "Adom Studio" then hits the unknown-name rule above and falls back to stock Light. This shipped in a golden image and rendered the whole editor off-brand; install.sh now packages a .vsix and installs it properly.

Three traps worth knowing before you write your own installer:

  1. code-server --install-extension takes a .vsix, not a directory. Build one at install time: it is a zip of extension/<the pack> plus extension.vsixmanifest and [Content_Types].xml. No vsce, no network, python's zipfile is enough.
  2. The code-server on PATH inside an editor terminal is the remote-cli shim and cannot install anything. Resolve the real launcher (/usr/lib/code-server/bin/ code-server and friends) and reject any path containing remote-cli.
  3. The CLI dies with error not spawned with IPC when it inherits CODE_SERVER_PARENT_PID (and VSCODE_IPC_HOOK_CLI) from the editor terminal that is running the installer. env -u both before invoking it.

The litmus is registration, not files on disk: assert the adom.adom-themes id appears in extensions.json after installing. A directory check passes for a pack that is about to be deleted, which is exactly how this got past a build gate. Uninstall through the CLI too, so the registration goes with the folder.

7. Implementing in Adom Desktop, the checklist

  1. Define the tokens as CSS custom properties on :root (the studio-dark values) plus one [data-scheme='<slug>'] block per scheme copied from the table above. Adom Studio's block should contain ONLY Bright's text ramp plus the bg/surface swap.
  2. Route every colour through the tokens. No hardcoded hex: HD carried 4,641 hardcoded values before the sweep and that is why its panels never matched each other.
  3. Picker UI: show the LABELS, store the SLUGS, default studio, apply via the data attribute before first paint (read the cache directly, do not wait for stores).
  4. Fonts follow the scheme's pairing; if you expose font pickers, overrides beat the pairing, "Match theme" means follow it, and the UI must name what that currently means.
  5. Copy the guards: scheme parity, brand-teal component matching (one colour has at least five CSS spellings; match rgb components, not strings), installed-theme verification if you install editor themes.
  6. Do not silently restyle approval/security surfaces with scheme colours that weaken semantics: amber stays the heavy-action colour, red stays destructive.

Version

Spec v1.0.0, extracted from hydrogen-desktop @ 2026-07-26 (pack v7.1.0). When HD's tokens change, regenerate section 2 from styles.css rather than editing it by hand.