Skip to content
dsh.fish
Bundle

@guowenzhang/dsh-ui-beautify

UI settings for DeepSeek Harness fonts, composer motion, and uploaded branding images

Source
zhang-guo-wen
stars
1 stars
License
Apache-2.0
Updated
Updated 2 days ago

Readme

# @guowenzhang/dsh-ui-beautify

English | [中文](README.zh.md)

## Background: DeepSeek Harness

DeepSeek Harness (`dsh`) is the open-source agent harness from DeepSeek AI, where nearly every capability is a plugin on [Cordis](https://github.com/cordiverse/cordis). It is in **developer preview** and iterating fast, so expect compatibility-breaking changes ([docs](https://deepseek-harness.github.io/deepseek-harness/), `0.1.7-alpha.*`); this plugin is a standalone third-party package that resolves `@deepseek-ai/*` from the running host.

## The problem this plugin solves

This plugin adds its own **Settings → UI Beautify** page for body and code fonts, composer motion, and branding. Font files download on first use; uploaded brand images stay on the Host.

## Install

```sh
npx @deepseek-ai/dsh plugin --profile web add @guowenzhang/dsh-ui-beautify
```

From the npm registry: <https://www.npmjs.com/package/@guowenzhang/dsh-ui-beautify> — restart the host afterwards; local checkouts, git sources and troubleshooting are in [AGENTS.md](AGENTS.md).

## Usage

### Change the logo and top-left brand

Under **Settings → UI Beautify**, choose **Welcome logo** and **Top-left icon** from your computer. PNG, JPEG, WebP, and GIF files up to 2 MB are uploaded to the Host and previewed on the page. The logo appears on the blank conversation page; the icon appears in both sidebar views. Edit **Top-left name** beside the expanded sidebar icon, and **Welcome tagline** on the blank conversation page. **Restore default** clears an image choice; clearing a text field restores the built-in text. Uploaded files are stored under `$DSH_HOME/assets/ui-beautify`. Restart the Host once after upgrading, then refresh the page. The native first-run welcome window in the Electron installer is packaged separately and does not load this plugin.

### Pick a body font

Open **Settings → UI Beautify → Body font** and choose a face. The page restyles on selection with no confirmation step, and the choice is written to the current profile's settings file.

### Pick a code font

The **Code font** row is on the same page. It defaults to `system`, so a fresh install changes nothing about how code looks until you choose otherwise.

### Understand what `system` means

`system` removes the plugin's stylesheet links and token override, returning the tokens to `ui-theme`'s own declaration. It is not a frozen copy of today's defaults — upstream default-font changes are followed. Selecting it is also the only way to switch custom fonts off; uninstalling is not required.

### Read the cache status line

Each row shows a title, the selected face's description, and a status line such as `已缓存 103 KB · 1/101 片`. There is no download button: fonts are pulled on demand as the page needs them. The counter is a progress read-out of on-demand downloading, not a failure — it rises as you browse and new characters appear. The numbers are read when the row renders and about 1.5 seconds after a font switch, not in real time; the status line therefore keeps a standing hint that refreshing the page (F5) re-counts.

### Understand the download behavior

Downloads happen per shard, not as a whole package. A selected face's stylesheet is fetched first, then only the shards the page's characters actually need. Concurrent requests for the same file are coalesced, and later page loads read from local disk without touching the network.

| Situation | What you see |
|---|---|
| The host machine has no outbound network | Downloads fail; the GUI keeps rendering with its fallback fonts |
| The browser has network access but the host does not | Downloads still fail — the browser never contacts an external site |

## Quick replies

The strip directly below the message box carries tags — **Continue**, **OK**, **I don’t understand**, **What’s going on now** by default. One click sends that phrase as your message: the tag writes it into the composer and submits, which is the same path typing the same text and pressing Enter takes.

- The phrases follow the interface language, because the phrase on a tag *is* the message that goes out.
- A click inserts at the caret instead of replacing the draft, so a half-typed message is never thrown away by a stray click. Over an empty composer the two are the same thing.
- While a submission is in flight the composer has locked its editor, and the tags close with it rather than looking clickable.
- Sending queues: if the agent is still working, the message waits for its own turn, exactly like a normal send.

### The phrases are built in

This version has **no editor for them in Settings**: the row shows the four built-in phrases and follows the interface language. The plugin already carries the machinery for custom phrases — a `quickReplies` field the dock reads — but the presentation is still undecided, so the settings row is not mounted. To customize in the meantime, edit `quickReplies` on the `ui-beautify` row of the current profile's `cordis.patch.yml` and reload the page.

## The composer lane

The strip directly above the message box carries a cyclist. It moves while the model is writing and rolls to a halt when it isn't: a fast stream sends it across quickly, a slow one lets it crawl, and once the output stops it keeps the speed it had and bleeds it off over eight seconds before coming to rest. It is a report of output, not a looping decoration.

It is decoration, and it is deliberately quiet about it:

- It reads nothing you type and sends nothing anywhere; the only thing it measures is how much assistant output has arrived.
- Screen readers skip it (`aria-hidden`).

### Turning it on or off

**Settings → UI Beautify → Composer lane**, directly below the two font rows:

| Choice | What it does |
|---|---|
| **Follow the browser** (default) | Plays unless the browser asks for reduced motion, in which case the strip above the message box stays empty |
| **Always play** | Plays even when the browser asks for reduced motion |
| **Off** | Never appears |

The row's status line says *why* the strip is empty. If it reads that your browser reports `prefers-reduced-motion: reduce`, that is the whole explanation — and **Always play** is the way past it.

The lane is live in the settings document the moment you pick, but the **Host needs a restart** to know about the field at all: until then the row says the Host is running an older build of the plugin. That is the same one-time restart every new setting in this plugin needs.

## Notes and caveats

- **The host machine must reach the network for a font's first use.** The browser only talks to the DSH origin; the host does the fetching. This is the one place the plugin depends on host connectivity, and it is the only reason a first use can fail while the browser itself is online.
- **`system` is the code font's default**, so a fresh install is visually a no-op until you pick something.
- **`maple-mono-cn` is the only code font that covers Chinese.** The other four cover Latin only and fall back to the built-in stack for CJK; `maple-mono-cn` is also 9 MB and reaches the machine through jsDelivr because the npm mirror does not carry that package.
- **A missing font never breaks the interface.** Text renders first with the fallback font and swaps in when the shard arrives; if the download fails for good, the fallback simply stays.
- **Two places ignore the font tokens** because they hardcode their own font: the integrated terminal (an xterm constructor argument, not CSS) and a hardcoded `Inter` prefix in the queue panel's stylesheet.
- **The mirror list and cache directory are host-start configuration**, not live settings — changing them requires restarting the host.
- **The composer lane is decoration, not a read-out.** It has no number, and its speed is an approximation of output speed rather than the `tok/s` in the status bar: while a step is still streaming the provider has reported no token count, so the lane measures characters instead.
- **A new setting needs one Host restart.** The browser half picks up a new build on reload, but the Host loads its settings schema once per process — so a row for a newly added field is disabled until dsh restarts.
- **Fonts are not covered by this plugin's license.** Each font keeps its own; see [LICENSE](LICENSE) and [NOTICE](NOTICE).

## License

The plugin itself is Apache-2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).

**The plugin distributes no font file.** Fonts are downloaded on demand from npm mirrors into a local cache and keep their original licenses. Noto Sans SC, Noto Serif SC, ZCOOL XiaoWei, ZCOOL KuaiLe, ZCOOL QingKe HuangYou, Ma Shan Zheng, Zhi Mang Xing, Long Cang, Liu Jian Mao Cao, Inter, Geist, JetBrains Mono, Fira Code, Geist Mono, Noto Sans Mono and Maple Mono CN are SIL Open Font License 1.1, packaged by Fontsource or by the font's own publisher. LXGW WenKai, LXGW WenKai TC and LXGW WenKai Screen are SIL Open Font License 1.1; the npm packages carrying them — `lxgw-wenkai-webfont`, `lxgw-wenkai-tc-webfont` and `lxgw-wenkai-screen-webfont` — are MIT.

## Further reading

- [AGENTS.md](AGENTS.md) — the full font catalogue, the download pipeline, the cache layout, the composer lane's mechanics, developer commands, the add-a-font procedure, and troubleshooting.
- [dsh-web-design](https://github.com/zhang-guo-wen/dsh-web-design) — a sibling plugin that previews and edits HTML in the DSH Sidebar.
- [DeepSeek Harness documentation](https://deepseek-harness.github.io/deepseek-harness/).

Install

dsh plugin --profile web add github:zhang-guo-wen/dsh-ui-beautify#eed2c6834966cdc002ff5f6758648c51a128ac2a

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
Source