DeepSeek Harness Plugin

webkubor/dsh-bloom-theme

Stars ★ 1 Category Themes & Appearance Added 2026-08-19 npm @kubor/dsh-bloom-theme

Bloom Morandi theme: four variants (mist, cinnabar, petal, ripple) built on OKLCH so light and dark stay perceptually even, switchable from the top bar, with all eight foreground/background pairs measured at WCAG AA or better.

Install

# from npm (prebuilt)

dsh plugin --profile web add @kubor/dsh-bloom-theme

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:webkubor/dsh-bloom-theme

Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).

README

What Bloom is

Bloom started as a Typora theme. Its point was never "swap the colors" — it is a complete Morandi design language: low-saturation ambient gradients, cool-toned hairlines, long soft shadows, restrained radii and spacing.

This plugin ports that language to DSH — including the half of it that is easiest to miss:

The Morandi character does not live in --accent. It lives in --accent-rgb.

See the two-track palette.

Why Bloom

Feature Notes
Two-track palette One track guarantees contrast, the other carries the mood — never mixed
A real texture layer Ambient gradients, cool hairlines, paper-like shadows, Markdown detailing — not just recolored variables
OKLCH throughout Perceptually uniform, so light/dark switching doesn't jump in brightness
WCAG AA All 8 accent/background pairs measured at ≥ 4.5:1
Zero dependencies Pure client-side injection, nothing added to your runtime
Respects native controls The switcher mounts into the DSH top bar instead of floating over it

Palettes

Four palettes, each with a light and a dark version.

Switch from the top bar; your choice persists in localStorage:

Install

dsh plugin --profile web add @kubor/dsh-bloom-theme

Then add the package to bundles in ~/.dsh/profiles/web/package.json:

{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "@kubor/dsh-bloom-theme"
      ]
    }
  }
}

Restart DSH — the theme dropdown appears in the top bar.

The plugin ships its own cordis.patch.yml and declares it via dsh.bundle, so once listed in bundles it inserts itself into the boot graph — no need to hand-edit cordis.patch.yml.

If you'd rather not touch bundles, inserting it in ~/.dsh/profiles/web/cordis.patch.yml works too:

- insert:
    - id: bloom-theme
      name: '@kubor/dsh-bloom-theme'

Install from source: dsh plugin --profile web add github:webkubor/dsh-bloom-theme

Design: the two-track palette

Every Bloom palette carries two colors. The original root-mist.css says it outright:

/* --- Morandi Mist (Blue) - Deepened for better contrast --- */
--accent: oklch(50% 0.08 240);   /* readable track: deliberately darkened, to pass contrast */
--accent-rgb: 146, 168, 179;     /* mood track: the actual Morandi color — greyed, desaturated */

The two must not be mixed:

  • Readable track → text, button fills, borders. It is the darkened version; painting large areas with it looks loud and muddy.
  • Mood track → only for wide ambient gradients and glows at rgba(morandi, 0.05~0.2). All 14 gradients in the original use it, and never the readable track.

Port only --accent — the natural assumption — and petal turns from lotus pink #e8859b into fluorescent magenta #e63f9f. The hue is right; the Morandi character is gone.

Palette Mood track Readable track (light / dark) Light contrast
mist #92a8b3 oklch(50%) / oklch(72%) 5.28:1
cinnabar #d74b4b oklch(55%) / oklch(72%) 4.87:1
petal #e8859b oklch(58%) / oklch(75%) 4.55:1
ripple #5fa8b2 oklch(51%) / oklch(75%) 4.61:1

The light readable track was calibrated by solving backwards from WCAG AA — and darkening it actually brings it closer to Morandi, which is exactly what the original author did for mist.

The texture layer

Port the palette alone and you get "the same UI in different colors". In the original, root-*.css (palette) is 89 lines while base-light/dark.css (texture) is 2,968 — that gap is the whole point.

Technique Implementation
Ambient gradients Four stacked Morandi glows on body, background-attachment: fixed
Cool hairlines Sidebar rule, card outlines, tab underline, inset top highlight
Paper feel Three shadow tiers plus an inset 0 1px 0 highlight
Markdown Gradient heading rules, fading hr, accent-barred blockquotes, outlined code blocks
Sidebar Ambient wash, cool hover states, accent marker on the active item

Architecture

lib/index.js    node half (cordis plugin) — empty; this is a pure client-side theme
lib/client.js   browser half, all logic lives here
  ├─ PALETTE          4 palettes × two tracks
  ├─ bloomTokens()    → --bloom-* tokens (single source of truth for the texture layer)
  ├─ mistLight/Dark() → mist fully takes over the DSH alias + specific variable system
  ├─ variantBlock()   → the other 3 override only accent and background; greys inherit mist
  ├─ COMPONENT_CSS    → texture layer (written once, adapts across 4 palettes × light/dark)
  └─ SWITCHER_CSS     → top-bar dropdown switcher

Development

npm run dev      # watch lib/, deploy to the web profile, auto-reload the browser on save
npm run deploy   # deploy once

The skin is injected client-side and its CSS is generated at runtime by client.js, so there is no "hot-reload the CSS only" path — the script must re-run, which means the page must reload. npm run dev handles it (press a to toggle, or set DSH_BLOOM_NO_AUTORELOAD=1).

FAQ

Make sure the package is listed in dsh.profile.bundles in ~/.dsh/profiles/web/package.json, then restart the DSH service (reloading the page is not enough):

launchctl kickstart -k gui/$(id -u)/ai.deepseek.dsh   # macOS LaunchAgent

The boot graph is resolved at process start; a page reload won't re-read the profile.

The skin is injected client-side and its CSS is generated at runtime by client.js — there is no "hot-reload the CSS only" path. The script has to re-run, which means the page must reload (Cmd+R). npm run dev does it for you on save.

Two usual causes:

  1. The package was renamed but stale copies remain under ~/.dsh/profiles/web/. Clean up node_modules/<old-scope>/, cordis.patch.yml and node_modules/.package-map.json together, then restart the service.
  2. The client factory returned a bare {}. DSH expects a function or an object with an apply method. That error surfaces in the browser and is unrelated to the ESM export style of lib/index.js.

Yes. The switcher only sets body[data-bloom-variant] and persists it to localStorage. Set that attribute yourself in CSS, or trim VARIANTS down to a single entry.

Light/dark follows DSH's own appearance setting (Settings → Appearance). Each of the four palettes ships a light and a dark version and adapts automatically — no separate toggle.

Known limitations

  • <think> tags flash briefly while streaming. Mid-stream the tag and the content share a single text node; only after the response completes and markdown re-renders do they split into standalone paragraphs the rule can catch. The end state is correct. A real fix belongs in the LLM provider adapter — parsing the thinking into a reasoning field so DSH's native ReasoningRow renders it — which is outside a theme's job.
  • Relies on CSS Modules semantic class names. DSH class names look like wSkVaW_root (<hash>_<semantic>); the hash changes between DSH builds while the semantic part is stable, so this plugin matches on [class*="_semantic"]. If a DSH release breaks the match, the effect degrades to flat colors — nothing misaligns or breaks.
  • --dsw-alias-toast-bg / tooltip-bg are not overridden per palette, so all four inherit mist's blue-grey hue. In dark mode the value sits close to the background; this has not been verified against a real toast.
  • Web profile only. The tui / headless profiles don't render in a browser, so this has no effect.

Lessons learned

Full write-ups in DEV_NOTES.md — each with symptom → root cause → fix → takeaway. The ones worth reading first:

  • A client factory must return an object with an apply method. Returning a bare {} white-screens the whole of DSH. That error surfaces in the browser and has nothing to do with the ESM export style of lib/index.js.
  • Foreground tokens can't be used as backgrounds or shadows. In dark mode the foreground is near-white, so a color-mix "shadow" renders as a white haze, and giving markdown-inline-code a text color yields a 1.2:1 pale-on-white block.
  • DSH uses CSS Modules — class names look like wSkVaW_root. Match with [class*="_semanticName"], always scope to div (or you'll hit SVG elements), and always count the matches (a bare _card hits 30+ message cards).
  • Only outline the layer that has a solid background. Outlining an inner transparent element produces a box-in-a-box.

License

MIT

Author

@webkubor · See also: Bloom for Typora

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →