
Security News
Deno Goes the Way of the Dinosaur
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.
dsh-ui-harmonizer
Advanced tools
English · 简体中文
UI harmonizer for DeepSeek Harness.
Normalizes the official UI · reconciles every plugin · settings auto-normalizer · UI customization (width, fonts, panel glide)
Part of DSH Design System — this repository is the runtime half.
The spec it enforces lives at Physicolor/dsh-design-resources.
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.
| 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 |
| 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 |
dsh-context | Keep the 组件 rail alive under its 上下文 dashboard: the composer seat dsh-context hides is the seat the rail is mounted in, so the seat is restored clipped and zero-height while the rail is on. Its 上下文洞察 dashboard (shell.overlay) gets the frame back: the outlet's z-20 stacking context is raised above the z-21 header while the backdrop is up (the mask used to leave the header, and the widgets' body-portaled hover magnifier, painting over the modal), the polychrome ContextIcon resolves to currentColor in both chrome seats, and the active pills stop painting the dark theme's near-white primary fill inside the dashboard. The dashboard then wears the Settings window itself: --dsw-radius-panel corners, bg-layer-2 fill, --dsw-elevation-prominent shadow and no border, the dialog title row (22px top / 24px side inset, 16px/24px 500 title, 28px round close seat), the content column starting 54px down at a 24px inset, and the official card recipe (20px radius, 12px 14px padding, 10px card-list gap) on its tiles — see docs/dialog-window-style.md |
@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 |
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 |
The "UI Customization" block under Settings → General: chat width, markdown font size, workspace scale, UI font stack and smooth panel toggle all adjust live. "Smooth panel toggle" (default on) makes the right sidebar's open/close stop re-laying out the whole frame every frame: the center track snaps to its final value and the motion is re-added as covers on the transcript column and the composer capsule, which do not force layout. Turning it off restores the original eased track.
The plugin is organised by target, not by file type. It exists to harmonize two different things at once — (a) the DSH shell itself and (b) whichever community plugins happen to be installed next to it — so every source file belongs to exactly one of four layers:
| Layer | Answers | Examples |
|---|---|---|
core/ | infrastructure true regardless of DSH or any plugin | state model + persistence, the Harmony Contract, i18n, DOM/React helpers |
harness/ | normalizations and repairs aimed at the DSH shell | official control-recipe mirrors, the settings-header reconciler, the chat-width channel, the frame's column-track transition, menu width, the stylesheet keeper, native-title tooltips, the rounded center card |
plugins/<package>/ | adaptations written for one community plugin | commandcode-provider text normalisation, dsh-widgets rail squeeze + handle re-anchor, dsh-better-sidebar panels, dsh-genui width hygiene |
self/ | the plugin's own UI | Settings → General rows, the font selector, the Harmony Doctor page |
Stylesheets follow the same split: 29 .module.css fragments with a single entry
(src/client/styles/index.ts) whose import order is the cascade order.
The rest of the contract is unchanged:
--dsw-* semantic tokens and follow light/dark themes automatically;<style data-plugin> tags;ctx.effect returns a disposer, so stopping the plugin leaves no residue — including DOM nodes it relocated and third-party strings it rewrote;settings.general.item / settings.section / shell.overlay.Guards live in scripts/: tools/css-baseline.mjs (byte-invariant of the compiled
stylesheet), verify-frame-track.cjs (column-track animation + panel glide),
verify-rapid-toggle.cjs (the glide's re-entrancy: overlapping toggles must leave
no inline style, no running cover and no rail drift), verify/settings-page.cjs
(Settings → General signals), probes/harness/* (selector and network triage).
# 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.
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);cordis.patch.yml inserts the ui-enhancer row; the browser half is declared by dsh.client;npx tsdown rebuild → sync into profiles/web/node_modules/dsh-ui-harmonizer/lib/ → hard-refresh the browser.0.1.0-rc.6 and compatible later 0.1.x;dsh-better-sidebar, dsh-widgets, dsh-context, dsh-notification, dshmarket;Refactor — every source file now belongs to exactly one layer
core/ (infrastructure true regardless of DSH or any plugin), harness/ (normalizations aimed at the DSH shell), plugins/<package>/ (adaptations written for one community plugin), self/ (this plugin's own UI). src/client/ now holds nothing but index.ts and services.d.ts..module.css fragments (29 today — the plugin-reconciliation sheets for dsh-widgets / dsh-context, including its Settings-window shell, were appended after the split) behind ONE entry (src/client/styles/index.ts) whose import order is the cascade order; the split itself was proven byte-identical to the original, and scripts/tools/css-baseline.mjs now pins the compiled result.npx tsc --noEmit went from 52 errors to 0, and build runs tsc so lib/types/ really exists (it was advertised in files but never emitted).Fixed — dead and wrong selectors, found by an adversarial pass over the live DOM
[role='menu'] [class^='_list_'] is both a descendant and a prefix match, while the product puts role and the list class on the SAME element with the list class not first (_surface… _list_4ub78_7 …). Measured with a menu open — descendant 0, prefix 0, contains 1. Fixed to [role='menu'][class*='_list_']; list / item / icon / label rules now land (padding 4px, min-width 218px, item min-height 40px)._brand _wide) silently stopped matching [class$='_brand']; the anchors now read "the class list CONTAINS a _brand item".svg { width:182px } stretched the whale 7.6×.menu-anchor must not treat the shell's settings button (aria-haspopup="dialog", aria-expanded true for as long as the dialog is open) as a menu trigger — it was force-sizing the product's own popups to a sidebar row.deadInThisView was declared twice (a count and a row array), so its Markdown export printed [object Object]; a dead market-hash rule and a misattributed data-dsh-* coupling are gone.Fixed — the frame's column track actually animates on DSH 0.2
transition: grid-template-columns behind [data-animating], which React sets in a layout effect on the same commit that writes the new track — the before-change style therefore carries no transition, so none ever ran and the conversation column SNAPPED. Measured: 1640px → 776px in one frame, no transition events, data-animating cleared by the component's own 600ms fallback timer.[data-dragging] / [data-rightbar-instant] rules still win), and frame-track.ts owns the one exclusion the product implements in JS — a window resize.Fixed — lifecycle
setInterval is gone (ResizeObserver + resize + transition events already cover it); the settings-header fingerprint hashes TEXT instead of its length (equal-length rewrites used to freeze the title).Published as a patch at the owner's call: it carries new behaviour, which semver would normally file as a minor. It also removes an optional visual layer that 0.9.0 shipped, so the changelog entry for it is written as a removal rather than as a silent disappearance.
Removed — the center-column rounded card
shell.overlay cover, its stylesheet and its state field. Reason: it was a visual opinion rather than a normalization — it added a corner radius and a shadow the product does not have, and it owned a second surface inside the center column that every other co-tenant had to route around. What remains in that block is the part that removes a defect instead of adding a look: chat width, font stack and panel glide. The removal is residue-free by construction — the root class is no longer written, and disposeDynamicStyle still clears enhc-center-card-on so a page running an older build of the plugin during a hot update cannot keep a card that nothing paints. The persisted card key is dropped when state is read, so a profile that had it on does not carry a field nothing owns.New — the right sidebar's open/close stops re-laying out the whole frame ("Smooth Panel Toggle", default on)
grid-template-columns on _frame, and that is a layout property — the center track travels 1427px → 659px over 300ms, re-laying out the entire frame on every frame of it (the product's own ResizeObserver wakes 27 times, against 10 for a left-panel toggle) while the compositor and GPU threads idle. Per-frame capture named more than one offender: besides the track, _scrollBody's padding-right eases over 300ms and _widthHandle's left/right ease over 300ms too. The transcript column is centered by scrollBody width − padding-right, so when the track has arrived and the padding has not, the column transiently flies out — measured +261px of overshoot on close, and +54px still left when only the track is pinned.src/client/harness/chrome/instant-track.ts takes the right-panel button's click in the capture phase: it writes data-enhc-instant on <html>, pins _frame and _scrollBody to a 1ms transition, then finishes every transition the gate created from the mutation microtask of the product's own track write — so the track lands in one frame while transitionrun/transitionstart/transitionend all still fire. It is an attribute rather than a class because the theme observer watches <html>'s class and rebuilding the material costs ~7ms per write (measurement in the handoff); transition-duration: none is wrong for the same reason 0s is — a transition that never starts emits no events at all, and dsh-widgets' rail yield reads that beat.transform on the transcript column and left on the composer capsule. A cover must track the layout, never predict it: the keyframe's from is the displacement read in the same frame the cover is armed, and a ResizeObserver re-seats it after layout and before paint while it runs (a fixed keyframe overshoots by construction whenever its from comes from a transient — the close direction's first readable seed is +358px against a 304px corridor). Measured on four arms, no frame paints outside the [312, 616] corridor and neither endpoint is ever returned to, which is the "flash then translate" report.dsh-widgets keeps three position: fixed nodes (dsx-stats-drawer/-zoom/-rail) inside those ancestors, and a transform rewrites their containing block and drags the rail into the viewport (measured: rail 1247 → 412.4). left is a layout property that creates no containing block, so the rail's drift reads 0px in every sample.step bails as soon as it is stale. Before the fix, 9 clicks at 130ms left transform: translateX(-230px) and left: -230px behind permanently (the orphan cover adopted the newer click's inline style as the "original" and then "restored" it); after the fix three storms (9×130ms / 15×60ms / 5×400ms) pass every check with no residue.getAnimations() on the dock plus each cover's own busy()), capped at HOLD_MS + 400ms. Releasing on the click's clock cut the open slide at currentTime 254.7/300 with no animationend at all, snapping the dock from translateX(10.35px) to none — the residual difference between the release and the panel's own animationend is now ≈10ms.enhc-panel-glide on <html>, 9 toggles each): long-task blocking 285.2ms → 205.7ms (−28%), long tasks 58 → 43; the price is about +8ms of layout and +23 LayoutCount per toggle, with TaskDuration/ScriptDuration essentially unchanged. Treat these as directional only: the later 15-toggle pair on one build disagreed between its own two arms, so only the isolated mechanism measurements (a class write vs an attribute write on <html>; getComputedStyle inside the track's transitionrun) are used as causal evidence.apply.ts owns the root class, disposeDynamicStyle also drops data-enhc-instant).scripts/verify-frame-track.cjs (12 checks: snap on open and close, the three transition events, a cover on the transcript column, zero rail drift, and a viewport change that neither eases nor misses enhc-window-resizing) plus scripts/verify-rapid-toggle.cjs (28 checks: the re-entrancy contract above, plus both gates must have been released). The rapid-toggle guard also tells whose rail drift it is: a drift with the rail's right still at the anchor is the swallowed-rail race in dsh-widgets itself (2/30 storms with this plugin inert, 1/60 with it live), so the check passes only on that signature and fails on a drift with the rail already claiming 0px — which would be our residue.New — the top bar glides with the panel, and its badges retire before the title is crushed
_headerUtilities and the tab strip never moving at all after the snap, against the transcript's 509.1 → 312 ease. The header cannot be covered the way the column is — its left edge is pinned to the left column and its right edge is the panel's, so its geometry is a width, not an offset, and a relative offset cannot stretch the title column (covering the hosts that have no position: fixed descendant left the title truncating instantly). Measured: setting header.style.width back to its closed value while the panel is open reproduces the closed header exactly — 280,1427,50, title column 212px, strip 867.5,139,26, against the open state's 280,659,78 / 41.5px / 697,87,58.instant-track.ts grew a width mode next to left and transform, sharing one driver, reseat and release path, and arms it on the header from a width read in the same capture-phase click. Measured on the three-badge reference session (1707×1067): 12 distinct painted widths, 1427 → 659 on open (18 in the first run) and 22 on close, first frame at the closed width. Everything inside follows for free — title, badges, tab strip, right-hand buttons — so no per-host covers and no transform on any header node.flex: 0 1 auto + min-width: auto, so the cluster's shrink squeezed the strip until its capsule labels wrapped — strip 26 → 58px and the header 50 → 78px. One declaration, flex: none, holds the strip at its content width and moves the whole deficit onto _crumbs, which the product already ships with min-width: 0; overflow: hidden. Measured 139x26 and a 50px header in all ten arms (1707/1440/1280/1152/1024 × open/closed) and on every frame of two live toggles (57 and 69 sampled frames).harness/header-fit.ts spends the badges in the order asked for — 标准模式 → 智能体团队 → 子智能体, any badge it cannot name last — until _crumbs clears a 160px legibility floor, with 24px of hysteresis so a threshold cannot flip-flop. Measured on the reference session with the panel open: title 0px → 165px with two of three badges retired; the rung is monotone through the open (0 of 56 frame pairs went backwards) and closing restores all three plus the title's full 212px.aria-haspopup="tree" for the subagent catalog, data-team-action for the team action, a bare span for the preset label. The fit reads _crumbs.clientWidth after applying a candidate rung rather than computing from badge widths, because those widths are not constants: the product's own @container rules zero the 标准模式 label below a 540px row and the 智能体团队 label below 480px, and the background counter's width follows the job count.scripts/verify-header-fit.cjs (14 checks — the closed baseline and the ladder's badge set, the glide's distinct widths and its starting value, zero wrapping frames through a live toggle, a monotone rung, the settled prefix + floor, full restore on close, size at four widths in both panel states, no residue attribute, no page errors).Refactor — the settings page header is ONE skeleton with ONE geometry, on every page
scripts/probes/settings/probe-header-geometry.mjs, all eight pages that have a header): the title's viewport position was already identical everywhere (titleTopFromDialog = 54), but the title→description distance was not — 4px on this plugin's own pages, 12px on the official ones, 0px on Command Code (its container is block flow, so no gap at all and the description touched the title) and 16px on 侧边卡片. The official value, read with this plugin's stylesheets temporarily disabled: 12px on models / agent presets / bundled plugins — the section container's own gap. The "official 4px" the older docs claimed was this plugin's own compression, and it is retracted.src/client/settings-page.ts now produces exactly the skeleton a page needs and nothing more: h2.enhc-page-title + p.enhc-page-intro as siblings. This plugin's own pages render it through SettingsPageHeader, foreign pages get the pair stamped on their real nodes, and the head wrapper is gone — so a DevTools inspection shows the same two-node header shape on every page, official or third-party.display: contents slot outlets) and writes the correction onto the description (margin-top = 12px − container gap), plus a compensating negative margin-top on the title when a container contributes its own padding-top/border-top. After the fix every page reads title y=134, intro y=172, gap=12px while the containers still differ (4 / none / 12 / 16 / 2px).Fixed — the sidebar account popup was narrower than the row it hangs from
src/client/menu-anchor.ts pins a portalled menu opened from the settings-launcher seat to its trigger row's width (box-sizing: border-box), scoped to that seat: the composer's model/permission pickers and every other menu keep their own recipe.Fixed — a third-party settings page rendered completely unstyled (loader claim-pass bug)
claimStyles(id) runs after every module factory materializes and adopts every untagged <style> in the document for that plugin (style:not([data-plugin])), while removeOwnedStyles(id) deletes every style[data-plugin=id] when that adopter reloads, unloads or is pruned. A plugin that hand-injects its stylesheet once in apply() (dsh-notification: #dsh-notification-style) therefore loses it permanently for the session — measured live: the 通知 page rendered as a raw text dump (no cards, no badges, default buttons) and #dsh-notification-style was simply absent.src/client/style-keeper.ts snapshots hand-injected stylesheets (data-plugin-css is the discriminator: bundler-emitted tags are the loader's own and stay untouched) and restores one that disappears, after a grace period that lets a legitimately hot-reloaded owner re-inject first. A restored sheet keeps its authored attributes — the thief's data-plugin claim is dropped.1px/12px/flex → after the theft no sheet and 0px/0px/block → after the grace period 38 rules and the card styled again; 9/9 checks pass.Cleanup
enhancer.module.css comments (an old PowerShell Get-Content -Raw/Set-Content round trip had re-encoded the file as GBK: — → 鈥?, × → 脳, one Chinese phrase destroyed). Comment-only damage — the build was never affected — rewritten as UTF-8 by a Node script, never through a shell.New — Harmony Contract (cross-plugin UI contract)
<html> that any plugin or theme can read without depending on this one: --enhc-contract (revision), --enhc-surface-solid, --enhc-glass-aware, --enhc-solid-fill, --enhc-content-width, --enhc-sidebar-scale.ctx.get('uiHarmony') using the product's own ctx.reflect.provide idiom (the same one behind ctx.sidebarRight): neighbours declare what they occupy with registerSurface({ id, role, occupies, widthVariable, transition, tokens, opaque }) instead of being measured by guesswork. This plugin only arbitrates and reports; it declares its own surface too (the Settings → General rows and the UI Compatibility page).--dsw-alias-bg-base / -layer-1 / --dsw-specific-sidebar-fill), never from plugin identity. Under a glass theme --enhc-solid-fill becomes transparent, so the header/panels stop painting an unblurred opaque rectangle over the glass. Measured by simulation (no third-party theme installed): overriding --dsw-alias-bg-base to rgba(255,255,255,0.45) flips --enhc-glass-aware 0 to 1, --enhc-surface-solid 1 to 0, --enhc-solid-fill to transparent, and the session header background-color from rgb(255,255,255) to rgba(0, 0, 0, 0); removing the override restores all four.New — Harmony Doctor (local read-only compatibility audit)
data-* host, with live match counts (39 couplings reported automatically, a batch of them now matching 0 because better-sidebar 0.19.1 removed the classes).data-slot, cross-plugin data-*, published contract variable and declared surface, with consumers and counts.Fixed — the font setting never took effect
--dsw-font-markdown-* tokens were written into a <style> tag at head index 4, while the official theme's gradient-shadow-text.css (declaring the same tokens on body{}) sits at index 24. Identical selector and specificity, so the later official rule wins — all four font presets read back byte-identical token values, and CDP reported Segoe UI + Microsoft YaHei for every one of them. The picker was never broken and the fonts were never missing (HarmonyOS Sans SC, Microsoft YaHei, Noto Sans SC, Georgia, SimSun, Consolas and Courier New all resolve on this machine).<body> / <html> (order-independent, removed exactly on dispose), and the token sizes now reference the product's own --dsh-content-font-size / --dsh-content-font-delta instead of hardcoded px.serif → token Georgia..., CDP renders Georgia (10 glyphs) + SimSun (4); yahei with scope Whole UI → UI elements render Microsoft YaHei; default → no override left at all.--dsw-font-markdown-* tokens DSH 0.1.5 actually consumes, including the three the old code missed — -table-head, -code-block-small, -code-font-family. The other 90 declarations are never consumed; leaving them alone is free. Code blocks follow a monospace pick and stay on the product's code stack under a prose pick.document.fonts.check returns true for unknown families and is unusable here).Removed / simplified (absorbed by DSH 0.1.5 or already dead)
html.enhc-panel-open seat mechanism and the whole better-sidebar toggle-cluster seat are gone. Measured: better-sidebar 0.19.1's 192-key CSS-module map has no toggleCluster / panelHidden / panelResize, and it no longer publishes --dsh-sidebar-width (it publishes --dsh-title-bar-strip / --dsh-sidebar-height). Keeping the code was worse than dead: panelOpen was permanently true, so the "compact floating seat" became the default shape — the design running backwards, silently.[data-input-scroll] and user-bubble font-size overrides: the product already sizes both from --dsh-content-font-size, and our overrides froze the official row on those two surfaces (measured line-height 21px to 24px after removal).FontSizeRow (12-17px, ui-theme namespace): this plugin now consumes that channel instead of racing it for the same tokens. Kept because the product has nothing equivalent: the chat-width row, and the font family + scope (the product's --dsw-font-family is a fixed :root value with no user setting).--enhc-solid-fill) so glass themes keep their surface.Reproduce
node scripts/probes/plugin-eco/probe-harmony.mjs http://127.0.0.1:19387 out.json (scripts/lib/auth.mjs mints a browser-session cookie from the persisted secret, so it passes the UI gate without the one-time launch token and without disturbing the running session).docs/MEASUREMENTS.md.Compatibility — DSH 0.1.5 client-structure alignment:
conversation to a keyed main entry declaring main.conversation, and both anchors render with display:contents, so the old slot.parentElement returned an unmeasurable wrapper. findCenterColumn() now accepts either anchor and resolves the real column with closest('[class$="_centerCol"]'); the parent walk stays as a fallback. The rounded-card CSS :has() guard accepts both spellings.@deepseek-ai/dsh-client-runtime was retired in 0.1.5, so the client context type comes from @deepseek-ai/cordis and the package was dropped from peer/dev dependencies and the tsdown platform table.conversation.session.header > header, _titleCluster / _headerActions / _tabs / _viewArea / _composerSeat, the sidebar family, the settings quartet and the menu primitives all still exist; _flowItem / _bubble moved to the new dsh-client-ui-chat package with new hashes, which suffix selectors ignore.main.conversation (left=56px / width=694px), with no uncaught exception and no slot error.margin-top:10px that made the tabs sit low inside the title row is zeroed); the header's right-side reserved band for a floating better-sidebar cluster is removed, because 0.19 registers its controls into the product's own header seat and no longer publishes that variable._flowItem hash changes are cosmetic only; the .nArs4W_* rules (better-sidebar 0.14 class names) were already dead under 0.19.1 and have since been removed in 0.9.0.Perf — the sidebar squeeze keeps its smooth progressive glide at full frame rate (companion to dsh-widgets v1.2.3):
viewArea, composerSeat, header) animate margin-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.*_flowItem) now gets content-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. auto lets the browser remember each item's last rendered height, so scrollbar height stays stable; browsers without support simply ignore the rule.scripts/archive/dead-probes/verify-glide.cjs (npm i -D playwright-core && node scripts/archive/dead-probes/verify-glide.cjs [session]).Feature (i18n — Chinese/English locale adaptation):
zh-*.src/client/i18n.ts module 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.localStorage('dsh-language') (written by the official Settings panel) → <html lang="..."> attribute → navigator.language fallback.KNOWN_TITLES fallback 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.Fix (cross-plugin width hygiene — @omdsh-dev/dsh-genui render_ui panels & tool cards):
.panelToggle title span (long nowrap text such as "opencode-go Multi-key Mini Pool — Final Architecture") is a flex child missing min-width:0; flex default min-width:auto refuses 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-agnostic flex: 1 1 0%; min-width: 0 shrink 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).--enhancer-content-width (currently 840px), matching the official Md3f7G_column conversation-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:auto triggers shrink-to-fit on a flex cross axis and collapses the panel into a vertical sliver (regression); always pair an explicit width:100% with a max-width instead.banner reuses 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); steps gets 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.Feature (native-title tooltip harmonizer):
title attribute (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-bg chip, 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.data-enhc-no-tooltip opts 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):
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):
Fix (better-sidebar Files tab strip):
height: 36px broke better-sidebar's native align-items: stretch chain, leaving ~8px of dead space at the strip's bottom. Reverted to height: auto + explicit align-self: stretch — the 14px label and icons stay vertically centered inside the taller strip.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.max() path).Fix (dark-mode active-state glyphs):
--dsw-alias-label-primary-inverted, which resolves to a near-black bluish-800 on dark themes — black text on the blue fill.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 + white glyphs in light AND dark themes, matching the official nav-cell pattern.Meta — renamed package to dsh-ui-harmonizer:
harness-ui-enhancer → dsh-ui-harmonizer (dsh- prefix + "harmonizer" naming matches the ecosystem norm and search; old package deprecated, redirects here).Physicolor/harness-ui-enhancer → Physicolor/dsh-ui-harmonizer (old URL auto-redirects; stars/issues preserved).dsh plugin --profile web add dsh-ui-harmonizer. No data impact (client-only plugin, no persisted keys).Meta:
keywords (deepseek-harness / dsh / cordis / plugin / web-ui / ui-enhancement) so the package shows up in npm search; no code change.Fix:
--dsw-alias-bg-base) and sits one step above the shell's overlay layer (z-index: 21, still below better-sidebar's panels at 40 and modals): the dsh-widgets rail and its magnify overlay slide UNDER the header's white rectangle instead of visually stacking onto the header buttons. Header rules stay in the enhancer (the widgets plugin no longer touches official elements).Fix:
margin-right. The #root neutralization previously cleared only margin-right, leaving better-sidebar's width: calc(100% - var(--dsh-sidebar-width)) active — the width squeeze first narrowed the column to the panel's left edge, then the viewArea/composerSeat margin squeezed a second time, pushing the conversation one full panel-width short of the panel. Added width: 100% to fully neutralize #root, so the inner margin is now the single, correct squeeze (the chat's right edge meets the panel's left edge, minus the 8px scrollbar gutter).Removed:
New:
shell.overlay cover (top border + top-left radius + --dsw-shadow-lv3 shadow); the shadow bleeds left into the sidebar (card thickness), the top moves down 1px for the shadow gap; right/bottom are natural window edges with no drawn border; the left edge borrows the sidebar's own border-right; a ResizeObserver tracks the center column so it follows sidebar drags / collapse / details-column changes; the content's top-left is masked round by the center column's own border-radius; pure CSS gating (html.enhc-center-card-on), togglable and residue-free.Fix:
max(): gives 80px to the toggle cluster when the sidebar is closed, follows the sidebar width when open;margin-right;W-zNGW → nArs4W;top: 6px.FAQs
DeepSeek 设计规范:统一 Harness 界面语言,并为社区插件做针对性适配,附只读兼容性审计。
We found that dsh-ui-harmonizer demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.

Security News
On Front Lines’ Early Adopters: CISO podcast, Andrew Becherer shares lessons from Datadog, weighs the long-term costs of AI-built tools, and explains how founders can actually gain a CISO’s trust.