deepseek-harness
starsstreaming/beautiCode为 DSH Web 与 Desktop 设置本地图片与 MP4 视频背景(也支持 Codex、Cursor、豆包、WorkBuddy):侧栏「背景」面板、可保存命名主题、声音开关、恢复上次背景、只留背景的摸鱼模式,以及 /bg、/bg-theme、/bg-clear 命令。
为 DeepSeek Harness 打造的 QQ NT 皮肤:浅色与深色共用一套 QQ NT 语言——浅色干净克制(品牌蓝 #12B7F5、浅蓝气泡),深色为沉稳蓝灰(#101822)——以可逆的 token 与布局两层实现,不改变你的浅色/深色偏好。
A QQ NT messenger skin for DeepSeek Harness (dsh). Light and dark share one QQ NT language — light mode is clean and restrained (near-white surfaces, neutral borders/hover, brand blue #12B7F5 and light-blue bubbles #A8E3FF as recognition accents), dark mode is a calm blue-gray (base #101822, desaturated, blue reserved for accents), instantly recognizable. Ships with five palette variants (Classic Blue / Vivid Purple / Clean White / Green Bubble / Deep Black), configurable toggles and sizing, plus a settings-panel visual switcher with hot reload.
How it works. The skin is two reversible layers; it never registers a new theme id and never touches the user's Light/Dark/System preference.
- Token layer — stacks one--dsw-*semantic-token override viactx.theme.overrideTokens('dsh-qq-skin', ...). Every token carries a{ light, dark }pair, so the skin follows the active base palette instead of fighting it. Light mode runs blue through surfaces, borders, interactions, and scrollbars; the dark palette is a#101822-based calm blue-gray scale where blue only accents (brand, bubbles, primary buttons). The set is generated per palette (buildTokenOverrides(palette)): the five palettes share the neutral blue-gray skeleton and differ only in the personality tokens.
- Layout layer — injects one global `tag (qq-layout.ts) for geometry and structure: the chat flow is centered and narrowed, user bubbles are light blue with a "tail", assistant rows get an avatar disc + white card (matched viadata-chat-flow-kind='assistant-step'), the input area becomes a capsule, plus a slim conversation-header divider, QQ-styled timestamps and scrollbar, and sidebar session-row hover/selected states. The brand area stays product-native — the QQ feel comes entirely from the blue token layer. Colors always come from--dsw-*tokens, and dark variants are gated bybody[data-ds-dark-theme]. The CSS is generated from the config (buildQQLayoutCss`).
On unload both layers are fully removed through effect cleanup and the product look returns. Settings and config changes hot-reapply both layers via QQSkinRuntime.update without restarting the plugin.The chat view in the Classic Blue palette: centered, narrowed chat flow, light-blue user bubbles with a "tail", and the penguin avatar disc on assistant rows:
The QQ Skin settings row in the App settings General section, with the palette / bubble tail / assistant avatar / chat width toggles:
The skin registers a QQ Skin settings row in the App settings General section, with visual toggles for palette / bubble tail / assistant avatar / chat width. Changes apply immediately (hot reload remounts both layers) and are persisted to the user-settings document. The settings panel overrides the static config below.
The plugin receives config the cordis function-plugin way (apply(ctx, config)); unset fields fall back to defaults:
Option · Default · Meaning palette · 'classic' · Palette variant: classic Classic Blue / vivid Vivid Purple / clean Clean White / green Green Bubble / black Deep Black bubbleTail · true · Small triangular "tail" on the right of user bubbles assistantAvatar · true · Official QQ-penguin avatar disc left of assistant messages chatMaxWidth · 880 · Max width of the chat flow (px)
Example (set in the profile's plugin config):
{ "palette": "vivid", "bubbleTail": false, "chatMaxWidth": 720 }Area · Mechanism Brand & primary actions (QQ blue) · --dsw-alias-brand-primary, --dsw-alias-button-primary-*, --dsw-alias-state-business-* Canvas & surfaces (clean light) · --dsw-alias-bg-base (#F7F9FB), --dsw-alias-bg-layer-1..3, --dsw-alias-bg-overlay Conversation bubbles (light blue #A8E3FF) · --dsw-specific-bubble, --dsw-specific-bubble-highlight + layout-layer radius/shadow Assistant messages · layout-layer avatar disc (token colors) + white card with border (--dsw-alias-bg-layer-1 + border-l1) Chat flow · layout-layer centering (via data-chat-flow, width configurable) Input bar · layout-layer capsule (data-composer-card) + toolbar button radius + --dsw-specific-input-major Conversation header · layout-layer slim divider (header[class$='_header'], never hits side panels) Message timestamps · layout-layer QQ-style chips (data-time-hover-root + _timeStart/_timeEnd) Sidebar session rows · layout-layer hover/selected fill + status-dot radius (_sessionRow/_projectRow/_dot) Scrollbar · layout-layer widening + radius (data-conversation-scroll) + --dsw-alias-scrollbar-* Sidebar (clean light / blue-gray dark) · --dsw-specific-sidebar-fill (#F2F5F8), --dsw-specific-sidebar-nav-item-* + layout-layer divider Text & borders · --dsw-alias-label-*, --dsw-alias-border-l1..l4 (blue-tinted strokes) Status colors (QQ green/red/amber) · --dsw-alias-state-success/error/warn-* Static palette (component-direct, unified sky blue) · --dsw-static-deepseek-*, --dsw-static-blue-* (remapped to the #12B7F5 sky-blue scale) Markdown, scrollbar (QQ blue), menus, tooltips · --dsw-alias-markdown-*, --dsw-alias-scrollbar-*, --dsw-specific-menu, --dsw-alias-tooltip-bg
Tokens that are not overridden keep the product default, so a QQ-skin run stays readable and consistent everywhere else.
Built and run against deepseek-harness (`dsh`) 0.2.0 or later. dsh 0.2 changed two settings-system contracts (2026-10):
ctx.settings.register(ns, schema) is gone: the plugin's settings schema is now the Config export of its host entry, and every field must be `.volatile()` — the settings panel projects volatile fields only, so a non-volatile field appears in no descriptor and accepts no write. The namespace comes from the id of the entry in cordis.patch.yml (qq-skin), not from a string the plugin picks.ctx.settingsScope service is gone: the client uses ctx.configForms.get('qq-skin'), whose snapshot value is the fully resolved host config (schema defaults → profile config → user edits) and whose set(field, value) persists an edit. A 0.2 client entry no longer receives a config argument at all, so the built-in defaults only cover the first frame.settings.configure({ auto: false }) to suppress the auto-generated page.Earlier dsh versions (0.1.x) are not compatible: they have no settings.configure, no ctx.configForms, and no volatile config projection.
One command installs the plugin into the web profile:
dsh plugin --profile web add dsh-qq-skinThat is the whole flow: dsh plugin initializes the profile on first use, runs pnpm add dsh-qq-skin inside the profile directory, then reconciles the profile's bundle layer against the installed state — because dsh-qq-skin declares dsh.bundle.patch (→ cordis.patch.yml), it joins the bundle stack automatically. The next dsh web boot composes it, the client bundle loads, and the skin stacks as soon as ui-theme is active.
Installing from a local checkout (path specs are anchored to your invoking directory; link: keeps the live checkout linked):
dsh plugin --profile web add ../dsh-qq-skin
dsh plugin --profile web add link:../dsh-qq-skinRemoving is equally one command:
dsh plugin --profile web remove dsh-qq-skinThe plugin declares dsh.client with platform: web and injects the theme, slots, locale, remote, and configForms services, so the Web boot loads its client bundle and applies the layer as soon as ui-theme is active.
pnpm install
pnpm run typecheck # tsc --noEmit
pnpm test # vitest (token shape + layout injection + runtime hot reload + settings wiring)
pnpm run build # tsc + tsdown → lib/index.js (host) + lib/client.js (browser closure-factory)The client bundle is produced by the same two-face layout as the harness clientBundle preset: lib/index.js is the plain ESM host row, lib/client.js is the window.__ModuleLoader__ closure-factory the Web boot consumes. No build-time CSS pipeline is needed: the layout styles ship as an inline string inside the client bundle (QQ_LAYOUT_CSS in qq-layout.ts) and are injected at runtime via document.createElement('style') — the same shape as ui-theme's installThemeStyles — then removed on effect dispose. Layout selectors rely on the client CSS Modules [hash]_[local] naming ([class$="_localName"] suffix matches) and the components' own data-* hooks, never on build-time hash values.
The settings row (settings-row.tsx) uses inline token styles reading --dsw-* directly, so it adds no CSS Modules pipeline; react and the collaboration services stay external to the client bundle and resolve through the platform module table.
MIT