DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

yuu1111 /

yuu1111/dsh-ui-font

Verified

DeepSeek Harness Web GUI plugin: change the UI font family through theme token overrides

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@6cbbfbcc

dsh-ui-font

A DeepSeek Harness Web GUI plugin that sets the UI and code font families from Settings → General.

日本語

Why this exists

The DSH Web GUI exposes the colour scheme and the conversation font size in Settings → General, but not the font family. dsh-client-ui-theme ships base.css with two hardcoded stacks:

Token Used by Stock value
--dsw-font-family body text and UI chrome -apple-system, BlinkMacSystemFont, "Segoe UI", ...
--dsw-font-mono, --ds-font-family-code code blocks and monospace UI "SF Mono", "JetBrains Mono", "Fira Code", Consolas, ...

This plugin adds two rows to that settings page, stores the chosen stacks in $DSH_HOME/settings.yaml, and applies them without touching any file inside the DSH installation (an npm update cannot revert them).

Install

dsh plugin --profile web add dsh-ui-font
dsh web

dsh plugin installs the package into the profile and appends dependency packages that declare dsh.bundle to dsh.profile.bundles automatically, so no manual patch editing is required.

A running dsh web composes dsh.profile.bundles once at startup and only watches the user patch layer (cordis.patch.yml), so restart it after installing — a browser refresh alone will not load the plugin.

Uninstall with dsh plugin --profile web remove dsh-ui-font.

Configure

Open Settings → General. Each row lists the saved families as ordered chips, shows a sample line set in that stack, and carries an Add font button:

Row Setting Applies to
Body font sans body text and UI chrome
Code font mono code blocks and monospaced text

Add font opens a searchable list of the families installed on this machine, read with the browser's queryLocalFonts(), and each entry is rendered in its own face. Chrome asks for font access the first time; entries already in the stack carry a check mark, and picking one appends it to the end. The panel stays open so several families can be added in a row. When the browser cannot list local fonts, or you decline the prompt, the list falls back to a short set of common families and says so. A search term that matches nothing in the list offers Add "<name>", so any family the browser can resolve stays reachable.

Each chip carries three buttons: move earlier, move later, and remove. The order is the fallback order, so keep a CJK-capable family after the Latin one. Every change saves immediately, and an unchanged stack writes nothing.

The stored value is still a plain CSS font-family list, so a hand edit works too. Braces, semicolons, and angle brackets are sanitised away, because the value is interpolated into a stylesheet. Only the fields you change are written; the rest keep the shipped defaults:

# $DSH_HOME/settings.yaml
ui-font:
  sans: '"JetBrains Mono", "BIZ UDPGothic", "Noto Sans JP", sans-serif'

The running server watches that file, so a hand edit applies on the next page reload. The shipped defaults have no ui-font section at all:

Setting Shipped default
sans "JetBrains Mono", "BIZ UDPGothic", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif
mono "JetBrains Mono", "SF Mono", "Fira Code", Consolas, "Liberation Mono", monospace

JetBrains Mono carries no CJK glyphs, so the shipped sans falls back to BIZ UDPGothic and Noto Sans JP for Japanese text. Keep at least one CJK-capable family after the Latin font, or Japanese falls back to whatever the browser picks.

A profile can also set the plugin's Loader config:, which becomes the base layer under the saved values — see the commented example in cordis.patch.yml.

Repository layout

Path Role
src/index.ts Host half: registers the ui-font settings section and injects the saved stacks into the served index
src/client.tsx Browser half: applies the stylesheet and registers the two settings rows, including the picker
src/shared.ts Values shared by both halves: namespace, defaults, sanitising, stack parsing and formatting, and the stylesheet builder
build.ts Bun build: host half as ESM, browser half as CJS wrapped in the module-loader format
cordis.patch.yml Inserts the ui-font row into the profile tree
lib/ Generated by bun run build and by prepack; not tracked, so it exists only locally and inside the published tarball
tests/host.test.ts, tests/client.test.ts, tests/shared.test.ts Contract tests that run against the built lib/

How it works

  • The host half calls settings.installSection(ctx, "ui-font", Config, config, hooks) inside ctx.inject(["settings"], ...). The plugin's Loader config: becomes the base layer, the saved settings.yaml section overrides it, and the schema defaults cover a config that omits a field.
  • The host half also pushes one { kind: "style" } row on webserver/index-inject. Rows are collected on every index render and placed directly after <head>, so the saved stacks are in effect for the first paint and a hand edit to settings.yaml shows up on the next reload without a restart.
  • The browser half declares inject: ["slots", "locale", "settingsScope"], binds the ui-font namespace with settingsScope.bind(), and keeps one style[data-plugin-css="dsh-ui-font/font-family.css"] tag in sync with the snapshot. Only one authority writes the tokens: both halves emit the same :root,body{... !important} declarations, and the tag the browser half appends later wins on equal specificity while the host row covers the window before the client loads.
  • The rows register into the settings.general.item slot with id: "dsh-ui-font-sans" / "dsh-ui-font-mono" and order: 70 / 71, so they sit after the built-in appearance and font-size rows. The browser half ships en, zh, and ja dictionaries; other locales fall back to English.
  • A row parses the stored stack into families, shows them as chips, and re-formats them with formatFontStack() before every save, so what the settings file holds stays a round-trippable CSS list. The picker reads installed families once per row mount with globalThis.queryLocalFonts(); a missing API, an empty result, or a rejected permission request falls back to a bundled list rather than leaving the row unusable.
  • !important is required because the ui-layout presenter writes the resolved tokens onto document.body as inline custom properties, which outrank a plain :root declaration for every descendant.

--dsw-font-mono is not declared by the stock sheets; some components read it through var(--dsw-font-mono, ui-monospace, monospace), so the override claims it too.

Development

bun install
bun run check    # tsc --noEmit through @yuu1111/tsconfig/bun.json
bun run lint     # biome check .
bun run format   # biome check --write --unsafe .
bun run test     # builds lib/, then runs the suite against it
bun run build    # regenerates lib/

The browser half may only require() modules the shell already seeds — react, react/jsx-runtime, @deepseek-ai/dsh-client-store, and @deepseek-ai/dsh-client-ui-primitives. build.ts fails the build when any other specifier shows up, and pins the production JSX runtime because the shell does not seed react/jsx-dev-runtime.

To try an edit in a live profile without publishing, install this checkout by path and rerun bun run build after each change:

dsh plugin --profile web add "link:$((Resolve-Path .\dsh-ui-font).Path)"

Release

Bump version, commit, then publish a GitHub Release for the matching tag (v1.1.0 ↔ 1.1.0). .github/workflows/release.yml builds the tarball in a contents: read job, verifies the tag against package.json, and publishes it from a separate job with npm trusted publishing (--provenance), so no long-lived npm token is stored.

Verified with

  • DSH 0.1.5-rc.2 (@deepseek-ai/dsh-client-ui-settings 0.1.5-rc.2), Windows 11 / Chromium
  • bun test: module id, exported plugin face, settings section registration, index style row, live source switch, scope binding, stylesheet updates, row registration, stack parsing and formatting, the picker paths (installed list, fallback, refused access, free-text add), chip removal and reordering
  • Real browser check: the served index carries the <style> row with the configured stacks, Settings → General shows both rows with the stored families as chips, the picker lists the installed fonts of this machine (174 families) and filters them by search, adding a family appends it to the stack and updates the computed body font without a reload, removing it restores the previous stack, reordering moves the family, and each change lands in $DSH_HOME/settings.yaml with no console errors

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 6cbbfbccf03f

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout