{"_id":"@dennisrongo/dsh-theme","name":"@dennisrongo/dsh-theme","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@dennisrongo/dsh-theme","version":"0.1.0","description":"Themes, accents and font pairings for DeepSeek Harness (dsh) — ten curated palettes as ctx.theme override layers, with live whole-app preview in Settings","type":"module","license":"MIT","author":{"name":"Dennis Rongo"},"main":"lib/index.js","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./client":{"default":"./lib/client.js"},"./package.json":"./package.json"},"scripts":{"build":"node build/build.mjs","typecheck":"tsc --noEmit","test":"node build/build.mjs && node test/themes.mjs && node test/smoke.mjs"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"platform":"web","inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-ui-theme","@deepseek-ai/dsh-client-ui-settings"],"immediately":true}},"devDependencies":{"@fontsource/geist-mono":"5.3.0","@fontsource/jetbrains-mono":"5.3.0","@types/react":"^18.3.0","esbuild":"^0.24.0","react":"^18.3.0","react-dom":"^18.3.0","typescript":"^5.6.0"},"repository":{"type":"git","url":"git+https://github.com/dennisrongo/dsh-plugins.git","directory":"plugins/dsh-theme"},"homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-theme#readme","bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"_id":"@dennisrongo/dsh-theme@0.1.0","gitHead":"95502da7aac802c5e846caefdd9bb9c2e5961a56","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-5I29EhDX+OJsciTk8IWK8sSB5AqFH3WyWzDGmT82qDiOSOWoPVfWjp+c7wFH/duImNAdlqZ+n8cOxpTyRdqZ1w==","shasum":"8c592bf0b1ef99af9c99fe54019662927e8575b1","tarball":"https://registry.npmjs.org/@dennisrongo/dsh-theme/-/dsh-theme-0.1.0.tgz","fileCount":30,"unpackedSize":295714,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dennisrongo%2fdsh-theme@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBC/opnR0TAFcvpSoLEudPGh+bF/CKzWEwczZ0PYXhHUAiABUqC7+fXSmZwStb3Ahm2r0qmG6trsRRS2Us6cV4U4jA=="}]},"_npmUser":{"name":"dennisrongo","email":"dennis@menacestudio.com"},"directories":{},"maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-theme_0.1.0_1787979880456_0.027178691520137965"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-29T05:04:40.289Z","0.1.0":"2026-08-29T05:04:40.607Z","modified":"2026-08-29T05:04:41.009Z"},"maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"description":"Themes, accents and font pairings for DeepSeek Harness (dsh) — ten curated palettes as ctx.theme override layers, with live whole-app preview in Settings","homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-theme#readme","repository":{"type":"git","url":"git+https://github.com/dennisrongo/dsh-plugins.git","directory":"plugins/dsh-theme"},"author":{"name":"Dennis Rongo"},"bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"license":"MIT","readme":"# @dennisrongo/dsh-theme\n\nThemes, accents and fonts for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) web UI.\n\nTwelve curated palettes — Bumble Bee, Catppuccin, Citron, Claude, Everforest, Gruvbox, Nord,\nOne, Rosé Pine, Sakura, Solarized, Tokyo Night — plus eight accent colours, a contrast slider,\na UI scale slider and three fonts (two of them bundled), picked from a **Themes** page in\nSettings with the whole app previewing live before you commit.\n\nEvery theme ships **both** a light and a dark palette, so it layers over your existing\nLight/Dark/System choice instead of replacing it. Switch appearance and the theme follows;\nleave it on System and it follows the OS.\n\nHovering a theme — or arrowing onto it — swaps its one-line description for its full authored\npalette, sixteen swatches from background through to syntax colours, each with its role and\nhex on the tooltip. It replaces the blurb rather than adding a row, so the grid never\nreflows, and the theme's name stays visible while you read the colours. Nothing is applied on\nhover; that is a local reveal, not a preview.\n\n## How it works\n\nThe harness ships a theme runtime (`ctx.theme`) whose `overrideTokens(source, tokens)` keeps\none token layer per source string and replaces it wholesale when the same source overrides\nagain. This plugin uses four independent layers:\n\n| Layer | Tokens | What it sets |\n| --- | --- | --- |\n| `…dsh-theme:palette` | 110 | Surfaces, text, borders, states, markdown, scrollbars, syntax |\n| `…dsh-theme:accent` | 17 | Primary fill and hover, model labels and chips, selection row, bubble tint, hover wash |\n| `…dsh-theme:font` | 2 | `--dsw-font-family` and `--ds-font-family-code` |\n| `…dsh-theme:scale` | 1 | `--dshth-ui-scale`, read by one injected `zoom` rule |\n\nLayers compose per token, so theme × accent × font × scale costs the SUM of those lists\nrather than their product — and each layer recolours or restyles whatever is underneath\nwithout knowing about the others.\n\nThe font layer is two tokens because one choice drives both faces: every one of the harness's\n~200 composed typography tokens is declared as `… var(--dsw-font-family)`, so overriding that\none name restyles the whole UI, and `--ds-font-family-code` does the same for code.\n\nA small first-paint bootstrap is inlined into the page by the host half, so a themed harness\ndoes not flash the stock palette on load.\n\n### Where the selection is kept\n\nIn a **cookie** (`dsh-theme=<theme>.<accent>.<font>.-.<contrast>.<scale>`; field 4 is a\nreserved placeholder from the retired code-font axis, kept so the later fields never shift), with\n`localStorage` written and read as a fallback. Fields fall back individually, so a cookie\nwritten before an axis existed still parses — it just takes the default for the field it\nlacks. Renamed theme ids resolve through `THEME_ALIASES`, so `high-contrast` still finds\nBumble Bee rather than silently resetting anyone to stock.\n\nThat is not arbitrary. **DSH Desktop serves the UI from a new ephemeral port on every\nlaunch**, and `localStorage` is origin-scoped — the port is part of the origin — so a\nlocalStorage-only plugin forgets your theme every time you restart the Desktop. Cookies are\n*not* isolated by port (RFC 6265 §8.5), so one set on `127.0.0.1` is read back whatever port\nthe next launch picks. It also sidesteps whether the Desktop loads `127.0.0.1` or\n`localhost`, since the cookie belongs to whichever host the page is on.\n\nVerified by restarting the harness on a different port: `localStorage` came back empty at the\nnew origin and the theme was still applied, from the cookie, including the pre-paint\nbootstrap.\n\nThe harness settings document was considered and rejected: `settingsScope` writes silently\nno-op on a non-loopback connection, so the selection would persist on the developer's machine\nand vanish for anyone on a remote browser.\n\n## Installing on Windows and macOS\n\nThe published install is the same command on both, and needs nothing else:\n\n```bash\ndsh plugin add @dennisrongo/dsh-theme\n```\n\nNothing about it is platform-specific, by construction:\n\n- **no runtime dependencies and no peer dependencies** — `npm pack` ships\n  `lib/`, `cordis.patch.yml` and this README, nothing more;\n- **no install scripts** — no `postinstall`, no native module, nothing to compile;\n- **the fonts are inside the bundle**, so no OS-level font installation happens\n  on either platform;\n- line endings are pinned to LF by the repo's `.gitattributes`, so `lib/client.js`\n  is byte-identical wherever it is built (verified: 0 CRLF pairs).\n\n### DSH Desktop\n\nThe Desktop keeps its own `DSH_HOME`, separate from the CLI's, so install once\nper profile per surface:\n\n| | Desktop harness home |\n| --- | --- |\n| Windows | `%APPDATA%\\dsh-desktop\\harness` |\n| macOS | `~/Library/Application Support/dsh-desktop/harness` |\n\n```bash\ndsh plugin --profile web add @dennisrongo/dsh-theme\n```\n\nRestart the Desktop afterwards; the page appears in Settings → Themes exactly as\nit does in the browser. Both surfaces here run the same harness version\n(`0.1.1-rc.2`), and the Desktop bundle provides `ctx.theme`, `overrideTokens`\nand the `settings.section` slot, so nothing is CLI-specific.\n\n### From a clone\n\n`pnpm install` at the repo root, then `node scripts/anchor.mjs` — that script is\nportable Node and resolves the harness through `npm root -g` first, with\nplatform fallbacks for both. Add the `file:` dependency and the\n`dsh.profile.bundles` row to the profile's `package.json`, and the plugin\nmounts.\n\nOnly the live-reload convenience is Windows-only: `scripts/dev-link.ps1`\nreplaces the profile's materialised copies with junctions. On macOS, either\nre-run `pnpm install` in the profile after a build, or symlink the package\ndirectory into the profile's `node_modules/@dennisrongo/` by hand — the plugin\nitself does not care which.\n\n> **Tested on Windows only.** The code has no platform-specific paths and the\n> resolver handles macOS, but the macOS Desktop layout above is from its\n> documented convention rather than a machine this was run on.\n\n## Adding a theme\n\nA theme is data. Write `src/themes/<id>.ts`:\n\n```ts\nimport type { ThemeSpec } from '../types.ts'\n\nexport const midnight: ThemeSpec = {\n  id: 'midnight',\n  label: 'Midnight',\n  blurb: 'One line, shown under the name in the picker.',\n  variants: {\n    dark: {\n      bg: '#0b1020', surface: '#141a2e', overlay: '#1d2540',\n      fg: '#e6ebff', muted: '#b9c2e0', faint: '#8792b8',\n      border: '#2a3354',\n      accent: '#7aa2f7', error: '#f7768e', success: '#9ece6a', warn: '#e0af68',\n      code: {\n        bg: '#080c18', comment: '#6b7394', keyword: '#bb9af7', string: '#9ece6a',\n        constant: '#ff9e64', function: '#7aa2f7', parameter: '#e0af68',\n        punctuation: '#b9c2e0', link: '#7dcfff',\n      },\n    },\n    light: { /* the same shape, authored against the light base palette */ },\n  },\n}\n```\n\nAdd it to `THEMES` in `src/themes/index.ts`, then `pnpm test`. Nothing else needs to know it\nexists — the picker, the preview, the persisted selection and the first-paint bootstrap all\niterate that array.\n\nRoughly fifteen colours per variant is the whole job; the builder in `src/tokens.ts` derives\nthe 110 custom properties the harness actually reads, including the alpha ladders for\nborders and hovers, the inverted tooltip chrome, elevation shadows, and the `--shiki-*`\nsyntax set. `sidebar`, `accentFg`, `bubble` and `info` are optional and derived when omitted.\n\n### Where `accent` shows up\n\nOn the send button, model labels and chips, mission-control's tags and its pomodoro pulse,\nthe selected session row, hover washes, primary buttons and the chat-bubble tint.\n\nThat list is long deliberately, and it was measured rather than guessed. The obvious home for\nan accent is `--dsw-alias-brand-primary` — but a colour scan of every element on a themed page\nfound **zero** carrying it, because the current dsh UI paints almost nothing with that token.\nThe send button reads `--dsw-alias-button-info-fill`; model labels, chips and (through\n`--mc-accent`) mission-control's tags and pomodoro pulse read\n`--dsw-alias-state-business-primary`; the transcript reads `--dsw-static-blue-*` directly.\nRouting the accent to those, plus an accent-tinted selection row, is what put it on the\nsurfaces you actually look at.\n\nTwo of those placements needed a solver rather than a constant. The send button hardcodes a\nwhite icon in its own CSS, which no token can override, so the accent passes through\n`legibleFill`, which walks outward from the\nauthored colour to the nearest shade where **both** the white icon and the button's own edge\nagainst the page clear 3:1 — the non-text contrast bar, which is the right one because the\ncontent is a 16×16 SVG rather than text. An accent that already clears both is left exactly\nas authored (Solarized's `#268bd2` is untouched); Bumble Bee's `#ffd400` becomes `#ad9000`,\na gold that is unmistakably the theme's colour and still legible. The suite asserts both bars\nfor every theme, every variant, and every accent.\n\nThe selected session row is the mirror case: it is a BACKGROUND under body text, so\n`legibleTint` applies the strongest accent tint that keeps `label-primary` above 4.5:1 on that\nrow, per theme. A flat tint failed five light variants — and exposed that the old neutral grey\nlift was already under AA on two of them, because lifting a surface toward the text spends the\nvery headroom the tint needs.\n\n`pnpm test` enforces contrast floors on every variant — 4.5:1 for primary and secondary text\nagainst both `bg` and `surface`, 3:1 for tertiary text, accents, errors and every syntax\ncolour against the code background. All failures are reported together, so a new palette can\nbe corrected in one pass.\n\n### Adding an accent or a font\n\nAn accent is one entry in `ACCENTS` (`src/accents.ts`), light and dark values, and the suite\nchecks both clear 3:1 on the base palette they target.\n\nA font is one entry in `FONTS` (`src/fonts.ts`) **plus** its faces in `src/font-faces.ts` —\nsee [Fonts are bundled](#fonts-are-bundled) for the licence constraint that governs which\nfaces may ship. Every stack still ends in a generic family (the suite checks), which only\nmatters if a bundled face somehow fails to decode.\n\n## Contrast and scale\n\nTwo sliders, both independent of which palette is selected.\n\n**Contrast** pushes surfaces and text apart — backgrounds toward the nearer extreme, text\ntoward the farther one — and deliberately leaves accents, state colours and syntax colours\nexactly where the theme author put them. That separation is the point: previously the only\nway to get a highly legible UI was to pick the one theme authored that way and take its\ncolours with it. Now Sakura at Maximum keeps its `#f2a0bd` pink while its background drops\nfrom `#1e181c` to `#090708`. The suite asserts that raising the level never lowers a measured\nratio, for every theme in both modes, and that the identity colours never move.\n\n**UI scale** is `zoom` on `#root`, driven by `--dshth-ui-scale`. It is honestly a UI scale and\nnot a font-size control, because a font-size control is not achievable from a plugin here: the\nharness sets `font-size` literally in **305** places and through a `--dsw-font-*` token in only\n**44**, so scaling the typography tokens would move about a seventh of the UI and leave the\nrest — strictly worse than doing nothing. Reaching the other 305 means overriding dsh's rules\nby their hashed CSS-module class names, which change on every harness build. `zoom` scales the\nrendered result instead, so hardcoded px, our plugins and dsh's own chrome all move together,\nand it targets `#root` rather than a class name. The trade-off, stated plainly: padding,\ncontrol heights and the sidebar's width scale too, exactly like browser zoom.\n\n## Fonts are bundled\n\nOne axis, not two: a font choice sets the interface face **and** the code face, because \"use\nthis face for everything\" is what people actually want from the setting.\n\n| Entry | Ships with the plugin |\n| --- | --- |\n| Default | no — your OS sans, and the best mono you have |\n| Geist Mono | yes |\n| JetBrains Mono | yes |\n\nThe two named faces travel **inside `client.js` as data URLs**, so they render identically on\nevery machine with nothing to install. That is the whole reason the list is short and the\nreason it is trustworthy: a named face you have not installed falls through silently and looks\nexactly like a setting that did nothing, which is a failure mode bundling removes rather than\nlabels. Verified on a machine with neither font installed — both register as `@font-face`,\nload on demand, and measurably resolve.\n\nCost: 6 faces (400/500/700 × 2 families, Latin subsets) ≈ **124 kB base64**, taking the bundle\nfrom 70 kB to ~194 kB. Latin only, because the other subsets would roughly triple that for\nglyphs this UI does not reach for.\n\n**Bundling is redistribution, so only OFL-1.1 faces qualify.** Berkeley Mono is a paid licence\nand is deliberately not shipped — it is named in the Default stack instead, so it resolves for\nanyone who owns it. Adding a face means adding it to `font-faces.ts` and checking its licence\npermits redistribution; the suite asserts the byte budget and that Berkeley never appears as a\nbundled face.\n\n## Single-mode themes\n\nA theme with no honest counterpart palette can set `pinScheme: 'light' | 'dark'`. Selecting it\ncalls `ctx.theme.setTheme()` with that built-in preference, which flips\n`body[data-ds-dark-theme]` — so the base palette's own alpha borders, the shiki base rules and\nany third-party CSS keyed on that attribute all land in the right mode.\n\nThat write is durable, so merely *previewing* such a theme would otherwise leave your\nLight/Dark setting changed after backing out. The page remembers the preference it opened\nwith and puts it back on Revert or on close, unless the theme you land on pins the scheme\nitself.\n\nPrefer authoring a real second variant; every theme shipped here does.\n\n## Development\n\n```bash\npnpm install          # at the repo root\npnpm run build        # esbuild dual build → lib/index.js + lib/client.js\npnpm test             # rebuilds first, then catalogue + bundle suites\npnpm run typecheck\n```\n\n`pnpm test` builds before asserting, because both suites read the **built** output — the\ncatalogue suite imports `lib/index.js` and the bundle suite reads `lib/client.js` as text.\n\nClient-half edits deploy on a browser refresh once `scripts/dev-link.ps1` has junctioned the\npackage into a profile; the host half (the first-paint bootstrap) needs a profile restart.\n\n## License\n\nMIT © Dennis Rongo\n","readmeFilename":"README.md","_rev":"1-8a45e6039981b0b12bf7e391ef05ca80"}