Physicolor/dsh-ui-harmonizer ↗★ 11
dsh-ui-harmonizer
UI harmonizer for DeepSeek Harness: normalizes unfinished or self-contradictory official UI, reconciles style conflicts between installed plugins, and unifies the visual language 鈥?with live-customizable chat width, markdown font size, workspace scale, UI font stack and a rounded center-column card from Settings 鈫?General, all following official design tokens. Client-only dsh bundle, zero model cost, no host side, removable without residue. 适合追求视觉统一,需要自定义界面宽度、字体和卡片样式的用户。
설치
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:Physicolor/dsh-ui-harmonizerEnglish · 简体中文
DSH UI Harmonizer
UI harmonizer for DeepSeek Harness.
Normalizes the official UI · reconciles every plugin · settings auto-normalizer · UI customization (incl. rounded card)
TL;DR: You installed a bunch of DSH plugins but the UI looks inconsistent? DSH UI Harmonizer uses CSS overrides + runtime DOM coordination to bring them back to the official design language — non-destructive, fully reversible, zero model cost.
DSH UI Harmonizer is a client-only DSH bundle plugin. It adds no model tools and modifies no session logs — it adjusts the UI purely through official slots (settings.section / settings.general.item) and the --dsw-* semantic token system.
Features
🎨 Official UI Normalization
| Capability | Detail |
|---|---|
| Single-line header | Moves the conversation / trajectory selector into the title row; the header collapses to one line |
| Button capsule family | Session log, widgets, and toggle buttons unified into 32px capsules |
| Right-rail flush rounded rect | better-sidebar panel overlay layout; the header stays put |
| Unified settings header | Title 18/600 + description 13px + hairline divider |
| Native-title tooltips | Raw title attributes render as the official dark tooltip bubble instead of the OS-native popup |
♻️ Plugin Visual Reconciliation
| Target | Approach |
|---|---|
dsh-better-sidebar | Capsule-ize toggle buttons, unify panel backgrounds, coordinate layout, smooth transitions |
dsh-widgets | Matching stat capsule family, header utilities alignment |
@omdsh-dev/dsh-genui | render_ui panels & tool cards: width follows the conversation content width (--enhancer-content-width, e.g. 840px) instead of inflating across the whole seat; fold bar long-title shrink fix with unified 11px/16px padding; 16px side-padding standard for full-width blocks (banner/steps, no box expansion); width guardrails for svg/pre/canvas/img/mermaid |
| Third-party settings pages | Auto-fill headings, drop redundant icons, normalize spacing |
🧹 Settings Auto-Normalizer ⭐
When any third-party plugin adds a page to settings.section that doesn't follow the official spec, this plugin auto-corrects it:
| Auto-check | Fix |
|---|---|
| Missing page title | Injects an 18/600 title (from the nav label or a known mapping) |
| Redundant icon beside the title | Removes the title-row logo, keeps plain text |
| Title/description too tight | Unifies to 4px spacing + hairline divider |
| Inconsistent type/size | Title 18/600, description 13/20 + border-bottom |
🎛️ UI Customization
The "UI Customization" block under Settings → General: chat width, markdown font size, workspace scale, UI font stack, and rounded card all adjust live. The "rounded card" renders the conversation area as a card with a rounded top-left corner and a drop shadow, auto-resizing with the sidebar width / details column.
Architecture
- Zero model cost: the host (node) half is a no-op; all changes happen in the browser half;
- Official design tokens: all styles use the
--dsw-*semantic tokens and follow light/dark themes automatically; - Two injection channels: static rules (CSS Modules) + dynamic `` tags;
- Reversible cleanup: fiber-effect disposers manage every side effect — uninstalling restores everything;
- Slot integration:
settings.general.item/settings.section/shell.overlay(rounded-card overlay).
Installation
# via npm (plugin market)
dsh plugin --profile web add dsh-ui-harmonizer
# local development (link)
dsh plugin --profile web add link:D:/dsh-home/plugins/harness-ui-enhancer
After installing, hard-refresh the browser (Ctrl+Shift+R); the "UI Customization" block appears under Settings → General.
Development
pnpm install
pnpm run build # tsdown builds lib/
pnpm run check # typecheck + build
peerDependencies:@deepseek-ai/dsh-client-ui-slots,dsh-client-runtime(provided by the DSH web profile);- A pure client plugin:
cordis.patch.ymlinserts theui-enhancerrow; the browser half is declared bydsh.client; - Must sync after changes:
npx tsdownrebuild → sync intoprofiles/web/node_modules/dsh-ui-harmonizer/lib/→ hard-refresh the browser.
Compatibility
- DeepSeek Harness
0.1.0-rc.6and compatible later0.1.x; - Integrates via official slots, coexisting with better-sidebar, dsh-widgets, dshmarket, etc. by slot order;
- Known reconciliation targets:
dsh-better-sidebar,dsh-widgets,dsh-notification,dshmarket; - The page fully restores to defaults after uninstall/disable — no residue.
Roadmap
- Phase 1 · Official UI Normalization (in progress): keep fixing unfinished parts of the official UI;
- Phase 2 · Plugin compatibility coordinator (in progress): detect and fix layout/style conflicts between plugins;
- Phase 3 · Unified visual style (in progress): an optional style layer — the "rounded card" is live (wrapped-header model), plus title-tooltip unification; next: spacing density, more radius/animation unification;
- Liquid Glass exploration: normalization's end goal is lowering cognitive cost — unified headings and tooltips remove the micro-fatigue of switching between styles; a material layer goes further, using one consistent physical metaphor to signal elevation and interactivity so the whole page reads as a single mental model. Scope: pure CSS/token experiments on top of semantic aliases — an opt-in switch, at most a couple of large backdrop surfaces (GPU budget), honoring reduced-transparency/reduced-motion, falling back to today's solid fills where unsupported, never touching plugin sources, and only shipping if readability measurably survives it;
- Phase 4 · Ecosystem: crystallize into an extensible rule-registration mechanism.
Changelog
v0.8.3 (unreleased, pending acceptance)
Perf — the sidebar squeeze keeps its smooth progressive glide at full frame rate (companion to dsh-widgets v1.2.3):
- Root cause of the panel-toggle jank: the three squeezed surfaces (conversation
viewArea,composerSeat, header) animatemargin-right: var(--dsh-sidebar-width)over 0.3s — a per-frame reflow of the WHOLE conversation DOM — which on long sessions (thousands of nodes) dropped to 20–31% dropped frames and visibly desynced from the compositor-driven widget rail and panel slide. - Fix: keep the progressive margin animation exactly as-is (left edge pinned, right edge gliding, text reflowing progressively — no "jump to final width, then slide" compromise) and make each per-frame reflow cheap instead: every conversation turn/step (
*_flowItem) now getscontent-visibility: auto+contain-intrinsic-size: auto 120px, so off-screen items skip layout entirely and each animation frame reflows only the handful of visible items.autolets the browser remember each item's last rendered height, so scrollbar height stays stable; browsers without support simply ignore the rule. - Measured (playwright + local Edge, widget rail open, heavy sessions, panel open/close window): dropped frames 20–31% → 11.5% (rail fix) → 0%; viewArea LEFT edge drift during the animation: 0 px (always aligned); rail↔conversation right-edge offset constant (std 0.01 px — perfect lockstep); scrollHeight after a jump-to-bottom: 0% shift (intrinsic sizes converge); once warm, the largest single-frame step is ~89 px — a mid-curve frame under headless software rendering, smaller on real GPUs. Known one-off: the FIRST panel open after a page load still has one large step (better-sidebar's first panel render long-task, unrelated to this change).
- Self-contained verification:
scripts/verify-glide.cjs(npm i -D playwright-core && node scripts/verify-glide.cjs [session]).
v0.8.2 — released
Feature (i18n — Chinese/English locale adaptation):
- All hardcoded Chinese UI strings in Settings → General now adapt to the browser locale: page header title/description, five setting row titles/descriptions, and font preset labels all render in English when the locale is not
zh-*. - New
src/client/i18n.tsmodule centralizes every user-facing string; language detection is responsive — re-evaluated on every render call, so switching Settings → Language takes effect immediately without a page reload. - Detection priority:
localStorage('dsh-language')(written by the official Settings panel) → `` attribute →navigator.languagefallback. - The
KNOWN_TITLESfallback table (used by the settings-section title-inject logic for third-party pages without a heading) is now locale-aware, matching both Chinese and English intro prefixes, and re-evaluated on each call so language switches take effect for injected titles too. - No visual or behavioral change in Chinese locales; English locales now see fully translated labels instead of a mix of Chinese and English.
v0.8.1 — released (2026-08-27)
Fix (cross-plugin width hygiene — @omdsh-dev/dsh-genui render_ui panels & tool cards):
- Root cause — the fold bar's
.panelToggletitle span (long nowrap text such as "opencode-go Multi-key Mini Pool — Final Architecture") is a flex child missingmin-width:0; flex defaultmin-width:autorefuses to shrink, so the title's max-content width inflates the fold bar and the whole panel beyond the conversation column. Harmonizer adds a hash-agnosticflex: 1 1 0%; min-width: 0shrink baseline (so ellipsis engages) and caps the panel/fold bar width; the same family is covered for.toolFallbackMeta(tool fallback long meta) and.tlTime(timeline long timestamps). Pure CSS overrides, zero modification of dsh-genui source; the rules survive upstream CSS-module rebuilds (class-substring matching). - Final baseline (user acceptance criteria, settled) — panel/tool width follows the conversation content width, i.e. the harmonizer chat column max-width slider's
--enhancer-content-width(currently 840px), matching the officialMd3f7G_columnconversation-content column — not the input box and not the outer composer seat. Final rule:[data-genui-panel]{ display:block; width:100% !important; max-width: var(--enhancer-content-width, 748px) !important; margin:10px auto 2px !important; contain:inline-size; box-sizing:border-box }— adapts live to the chat-width slider and centers horizontally in the conversation column. The earlier "measured input-box width via--enhc-message-maxw" approach was dropped (baseline drift). Lesson:width:auto+margin:autotriggers shrink-to-fit on a flex cross axis and collapses the panel into a vertical sliver (regression); always pair an explicitwidth:100%with a max-width instead. - Horizontal spacing for full-width blocks —
bannerreuses the fold bar's width format: content width with 16px side insets (text starts at the same x as the panelToggle title), never a negative-margin box expansion (which pushed padding past the card edge), in every container (panel body / inline / tool card);stepsgets 16px side padding inside padding-less inline/tool-card containers; svg/pre/canvas/img/mermaid are all width-guarded; block components (callout/card/list) keep their own shape untouched.
v0.8.0 — released
Feature (native-title tooltip harmonizer):
- Any element that only carries the raw HTML
titleattribute (the model selector trigger, assorted product controls) used to pop the OS-native tooltip and break the visual language kept by every surface routed through the official Tooltip primitive. Hover/focus is now intercepted: the title is lifted for the interaction and re-rendered as the official bubble —--dsw-alias-tooltip-bgchip, padding 3px 7px, radius 8px, 13px/20px type, 50vw width cap, 500ms hover delay, immediate on keyboard focus, placed 8px below the anchor (flips above when clipped), clamped to a 12px viewport margin, z-index 100 popup band. - Rollback safety: an ancestor carrying
data-enhc-no-tooltipopts a subtree out; the lifted attribute is restored verbatim on leave/blur/plugin stop (if the app rewrote the title mid-hover, its newer value wins); the fade-in respects reduced motion.
Fix (toggle cluster seat):
- The floating better-sidebar toggle cluster got an opaque seat in
bg-base. Default (panel closed): a whole-height block spanning the session header band (top 0 → 56px), so buttons/seat/header read as one flush right edge and widgets-rail cards can no longer show through. While a better-sidebar right panel is open (html.enhc-panel-open, kept in sync by the client half) the seat collapses back to a compact floating chip, because the panel's top edge deliberately sits below the page top and a tall seat would jut into it.
Fix (rounded card wraps the session header):
- The AppFrame's shell.overlay outlet is itself a z-20 stacking context, so the card chrome painted inside it can never out-draw the z-21 session header. Zero-pixel split-paint model (no white slab anywhere): the HEADER draws the card's top edge as an INSET box-shadow hairline (a real border-top grew the header by 1px and misaligned it against out-of-flow controls) plus the 18px top-left radius, while the overlay box demotes to a pure shadow caster spanning header + content with a custom left/top-emphasized recipe — official lv3 offsets down-right and its tight contact halo painted a stray edge line along the window. Routes without a session header fall back to the classic self-drawn card. Hover/active fills ride above the seat; the seat carries a continuation of the header's top line so the edge reads unbroken across the full card width.
v0.7.1 (folded into v0.8.0)
Fix (better-sidebar Files tab strip):
- 📏 Tabs now FILL the 44px tab bar: the previous fixed
height: 36pxbroke better-sidebar's nativealign-items: stretchchain, leaving ~8px of dead space at the strip's bottom. Reverted toheight: auto+ explicitalign-self: stretch— the 14px label and icons stay vertically centered inside the taller strip. - ↔️ The open right panel's tab strip now reserves 90px on its right end (was 72px): the toggle cluster got bigger (two 32px capsules + 6px gap at
right: 12px= 82px total), so the old seat let the rightmost tab / + button slide under the capsules. 90px = 82px cluster + 8px breathing room. The bottom panel's 40px seat is untouched. - 🧭 The session header's shared right margin bumped 82px → 90px to match the wider cluster (covers both the collapsed corner seat and the open-panel
max()path).
Fix (dark-mode active-state glyphs):
- ⚪ The active "Chat" tab now renders WHITE glyphs on the DeepSeek brand-blue fill in dark mode. It had used
--dsw-alias-label-primary-inverted, which resolves to a near-black bluish-800 on dark themes — black text on the blue fill. - ⚪ The activated "Components" capsule keeps its own shipped pair (
state-business-primary+#fff): an earlier override in this plugin had replaced that white with the same near-black token (dark-mode only). That override is dropped, so both buttons read as blue fill +