deepseek-harness
starsstreaming/beautiCode为 DSH Web 与 Desktop 设置本地图片与 MP4 视频背景(也支持 Codex、Cursor、豆包、WorkBuddy):侧栏「背景」面板、可保存命名主题、声音开关、恢复上次背景、只留背景的摸鱼模式,以及 /bg、/bg-theme、/bg-clear 命令。
DeepSeek Harness 外观一体化工具。对 8 个界面区域分别调节透明度与毛玻璃模糊(主背景、侧边栏、卡片面板、输入框与控件、设置面板、对话文本框、轨迹页、产出物/高亮内容;安装 dsh-better-sidebar 后为 9 个)。用任意图片或视频做壁纸,配有拖动平移、滚轮缩放的位置编辑器与 5 种布局模式(适应/填充/拉伸/平铺/居中);视频静音循环播放、刷新不丢失,并自动截取一帧用于预览、取色与构图。把多张图片加入轮换池(随机/顺序,每次刷新/每天/每周)自动更换。还包含 PS 风格色轮、一键从壁纸提取主题色、6 套预设 + 命名配置、昼夜自动切换与主题一键导出导入。
English | 中文
A DeepSeek Harness appearance plugin: custom theme color, background wallpaper (image / video / algorithmically generated), and fine-grained per-surface opacity & blur controls. Compatible with DSH `0.2.0-rc.1` through `0.2.1-rc.2` — and every host release above it (official-Sidebar UI such as the "Theme" card enables itself where the host exposes the Sidebar registry extension point, and is skipped silently where it does not).
Custom homepage · wallpaper + theme color applied
Theme color picker · PS-style wheel + precise HSL/RGB inputs
Per-part opacity and blur · main background, sidebar, cards, settings
Background editor · image/video wallpapers support drag-to-pan and scroll-to-zoom
Generated dynamic background · mesh gradient / Shader / geometric presets
Generated dynamic background · geometric low-poly mode preview
Export and import configs to share
backdrop-filter blur (0–60 px) per surface, including a real backdrop on the composer, the Cordis panel and popover surfaces via stable host selectors. The slider IS the frost amount, and 0 means no frost — including over the host's own default (--dsw-menu-backdrop-filter: blur(40px) saturate(150%)). Through 0.3.7 a 0 meant "leave the host alone", which made the bottom of the slider the blurriest setting it could produce (0 = the host's 40 px, 1 = 1 px) and left "turn the blur off" unreachable; now 0 is 0, and because every part still DEFAULTS to 30 px a stock install keeps the frosted look unless someone drags it down.--dsh-windows-sidebar-width), so the left part carries the sidebar's material and frost and the rest stays the 「标题栏」 card's. Drag the sidebar wider, or collapse it to 0px, and the cut moves with it. The split is deliberate (issue #29, item 3): the left strip IS the sidebar column's own surface, so the header-bar sliders are not meant to reach it.code highlight chips and produced chips share one opacity + blur slider. The opacity is the alpha of each surface's own background color (no second color stacked on top of the original), and the blur frosts that same layer so the wallpaper shows through the content.panelOpacity / blurs.panel), two identities: without dsh-better-sidebar it reads "右方侧边栏" (Right sidebar) and drives the official right Sidebar's surface tokens and frosted blur (works on 0.1.5-rc.2 through 0.1.7); with dsh-better-sidebar installed it reads "bettersidebar" and takes over that plugin's bottom workbench panel (the official sidebar keeps responding too). The row is always visible.conversation.session.header* slot anchors and tags the open popover at runtime instead of relying on class shapes. A popover counts as the header's when it is portaled to ` **or** mounted inside one of those anchors — the session-log … menu is the second kind (session-log-export/HeaderAction.tsx renders its Menu without portal), and until 0.3.9 only a fragile _denseList`-class arm reached it.::before underlay behind an isolation:isolate on the row — a stacking context that is NOT a backdrop root, which is what lets the overlays hanging off the row frost the wallpaper at all (through 0.3.8 the filter sat on the row itself, and a filtered row is a backdrop root: exactly those panels were left blurring a flat tint). Because that context still localizes the row's paint order, the row carries a compensating z-index:10 — the overlays it hosts (the background-job list through 0.2.1-alpha.1, the schedule task menu and the session-log … menu on every release) hang out of it with z-index:100 and would otherwise be confined under the conversation. 10 clears the in-column chrome it overlaps (seat 9, back-to-bottom 8) and stays under every page-level surface (100 / 1000 / 1100), so nothing about the part's look changes when a menu opens. z-index needs no position — the row is a flex item, where it applies on its own — and the underlay's position:relative is written inline only while a blur exists and only when the host left the row static. See v0.3.7 / v0.3.9. On the Windows desktop shell the band it drives is split with the sidebar (previous bullet), dims itself while a modal is open (the host mask deliberately stops below the caption), and its generated segment is paint-only — pointer-events:none and no app-region declaration — so it can never swallow clicks meant for the native caption menubar (issue #29). It also paints below thecontrols (z-index:-1) and carries its own ink (--dsh-any-caption-ink), so the sidebar toggle and the 应用 / 编辑 menubar stay readable even when a low sidebar opacity makes the strip light — and 「标题条带文字」 says who wins when the strip's brightness and the theme's direction disagree (自动 derives it from the strip, 跟随主题 leaves the host's own label ink alone, 始终白色 / 始终黑色 pins it; see issue #30). The anchor is expressed in CSS (the row is the conversation.session.header slot's direct parent) instead of guessing a hashed class or tagging a host element at runtime; before 0.1.7-alpha.1 the host mounts that slot bare with no such element, so there the rule matches nothing and the part is simply inert. On the Windows desktop shell the same slider pair also drives the window caption band — except for the segment that sits above the left sidebar, which belongs to that sidebar's own pair (see Left sidebar + the caption band above it) — and the native minimize/maximize/close block is handed transparent so Windows paints no background of its own, leaving the band itself showing through. A blank session marks the row headerBlank (a placeholder left with only its padding) and the plugin skips claiming it.
sidebarRightTabs + the sidebar.right.pane.tab keyed seat); with dsh-better-sidebar installed, the page registers in that plugin's sidebar instead and the official guide card withdraws itself, so the two never duplicate. Settings panel, official sidebar and better-sidebar all share one page implementation and one state store — a change in any of them shows up everywhere. The shell adapts to the panel width, and a narrow panel tightens padding and falls back to a single column. On a host without the right Sidebar the registration silently never happens.dsh-any-theme.json (config + wallpaper, video embedded as a data URL) and import to restore it anywhere.@font-face; toggle it off or remove it at any time. Fonts stream as raw bytes and persist in the plugin data dir; code blocks keep their monospace stack.-webkit-text-stroke: width 0–4 px (0 = off) and a color of auto-contrast / gray / black / white / accent / custom. Code blocks, inline code, icons and the host's background-clip: text shimmer chrome (the "深度求索中" turn-status line and the turn-process rows) are exempted automatically, so multi-color syntax never smears and gradient text is never flattened into a stroke-coloured blob.Auto both the surface and font directions follow the accent's lightness (dark pick → light fonts, light pick → dark fonts), falling back to the wallpaper's perceived brightness when no color is picked.~/.dsh/.dsh-any-background-data/, not localStorage.npm test round-trips it, including an unknown value collapsing to auto.header-tag.ts accepted a popover only when its parent was `, so the session-log … menu — which session-log-export/HeaderAction.tsx renders WITHOUT the portal prop, inside [data-slot="conversation.session.header.utilities"] — was never tagged, and the only thing styling it at all was the header group's [role="menu"][class*="_denseList"]:not([class*="_portal"]) arm. That arm dies the moment the host drops dense (or ships a header popover that is not a dense menu), and until then the panel followed the 「卡片」 slider while sitting in the title row. A candidate is now "portaled to , **or** a descendant of one of the header's own slot anchors": a modal is still never claimed, and the composer's in-place lists stay with the card group that owns them. The observer's flush also moved off requestAnimationFrame onto the microtask queue, which is the other half of "still doesn't work": a queued frame does not run while a window is not painting (hidden tab, occluded or minimized desktop window, background Electron surface), so the tag simply never landed and the panel kept whatever the card group gave it — measured live in dsh web`, an open header menu stayed untagged indefinitely on a non-painting page, and the same menu was tagged on the next microtask after this change. A microtask coalesces a mutation burst exactly as an animation frame did (the observer callback is already per-batch) with no dependency on the compositor, and it lands in the frame the popover mounts, so the panel no longer flashes the card look on open either.backdrop-filter on the row made the row a BACKDROP ROOT as well as a stacking context, so an in-place header popover could only ever blur what the row itself paints — a flat tint — and the header blur slider read dead on exactly the panel that hangs off the row. The frost now rides a ::before underlay behind an isolation:isolate (a stacking context that is NOT a backdrop root), which is the recipe this plugin already uses for the frame, the composer capsule and every part column; the row keeps its compensating z-index:10, and the underlay's position:relative is written inline only while a blur exists and only when the host left the row static. Measured in the real dsh web app (the plugin linked from this working tree, driven through the actual 「顶栏选项」 sliders on the 界面 page): the in-place … menu and the portaled open-in-app menu both report rgba(243,247,252,1) at opacity 100 / blur 4 and rgba(243,247,252,0.08) at opacity 8 / blur 44. Measured again in a browser harness running the plugin's real client code over the host's own built CSS: the row reports backdrop-filter: none, its ::before reports the slider's blur, and every header popup shape — portaled dense MenuSurface, in-place dense, in-place non-dense, the role-less job-list surface, the lineage tree — takes the 顶栏 pair's fill and frost instead of the card's.readHostOpacityTokens() sampled getComputedStyle(:root), but design-platform.css declares this whole token set inside a body { … } block and a custom property is inherited downward only — so with no picked theme color every read came back empty, applyCustomTokensNow took its surfaces === null exit, and the 侧栏 / 卡片 / 输入框 / 顶栏选项 alphas were never emitted at all. The sample now comes off body, cached per host generation (its dark-mode marker plus the platform attribute), and the plugin's own body{} token sheet is switched off while reading — otherwise the second sample returns the plugin's own substituted literal and the surfaces freeze at whatever color they first had instead of following the host. class gate (dab-card-frost / dab-header-frost) so a slider parked at 0 would not delete the host's own --dsw-menu-backdrop-filter: blur(40px) saturate(150%) — an element's own declaration always beats what it inherits, so the un-gated var(--x, none) really did clear it. The gate turned out to be the wrong trade: it made the slider's LOW end its HIGH end (0 → the host's 40 px, 1 → 1 px), so 「把模糊度关掉」 was unreachable and the control read as dead at exactly the position the user was testing. The frost token is now asserted at every value with the none` fallback winning at 0, and the gate machinery is gone. Nothing loses the host look by accident: every part default is 30 px, so a stock install is frosted and only an explicit drag to 0 clears it.0.2.0-rc.1, 0.2.1-alpha.3 and 0.2.1-rc.2 bundles: both utilities controls, MenuSurface's data-menu-material + .material painter and the menu token set are identical across the three, so the 0.2.1 adapter's facts still hold.normalizeConfig → theme-config.json), and its copy of the config shape had never learned that card's three keys: blurs.headerBar fell back to 30 px, strokes.headerBar to width 0, and headerBarOpacity was dropped outright at write time — while the browser half restored the file with its own defaults (clamp01(undefined, .5) → 50%), so the card looked like it saved and then reset on every reload. The profile sanitizer had the same hole, and was also dropping blurs.header from saved profiles. All three keys now exist in the host's PartBlurs, DEFAULT_CONFIG, STROKE_GROUPS, both normalizers and ProfileAppearance; npm test boots the built host half against a throwaway $DSH_HOME and asserts the whole round trip (disk + reload + profile), including the "ignoring unknown config field" warning staying silent.-webkit-app-region items in paint order, and the drag annotation 0.3.7 put on the generated band segment (::after) overrode the no-drag holes the native caption menubar punches for its own buttons — the top-left navigation stopped responding while dragging still worked. The segment is paint-only now: pointer-events:none and no app-region declaration at all; the host's own .frame::before still carries the band's drag region.--dsh-frame-chrome-top = the titlebar height) so the native drag strip keeps working, which left the top 40 px bright once a wallpaper was showing. The band is the plugin's own paint, so it dims itself while a modal exists — [data-windows-titlebar]:has([role="dialog"][aria-modal="true"]), with the host's own --dsw-alias-bg-mask-1 and no interaction change. Measured in a browser: box-shadow: none closed → inset mask while open → none again.--dsw-alias-label-secondary, which the plugin flips white against the PICKED PALETTE, while the strip shows that palette at the sidebar opacity over the wallpaper: at 22% over a bright wallpaper the strip is light, and white ink disappeared (the reported desktop regression). The segment now paints at z-index:-1 — above the wallpaper, below every control — and the caption strip derives its own ink from what it is actually pain