跪拜 Guibai
← All articles
AI Programming

How a Floating Pet Plugin Exposes the UI Extension Model Inside DeepSeek Harness

By 唐悦玮 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Most AI coding tools lock down their interfaces; DeepSeek Harness exposes a slot-based UI plugin system that lets developers reshape the editor they stare at all day. The dual-entry pattern keeps front-end dependencies out of the Node process, a practical separation that other plugin architectures often miss.

Summary

DeepSeek Harness supports two plugin types: server-side tools that give models new capabilities, and client-side UI plugins that modify the interface itself. A new open-source salted-fish pet plugin demonstrates the latter, injecting React components into `shell.overlay` slots to add a floating companion and a wallpaper picker. The plugin uses a dual-entry structure—an empty Node-side `apply` function satisfies the Cordis loader, while the real component logic loads only in the browser—so installing a UI plugin adds zero weight to the server process. Cross-slot state between the pet and wallpaper modules is handled through a module-level shared store with listener subscriptions, since the two components live in separate React trees. Wallpaper selection persists via localStorage and broadcasts changes through CustomEvents on the window object. The build pipeline auto-scales and base64-encodes images, so adding a wallpaper means dropping a file into a directory. The plugin registers into the user's persistent profile through a single npx command that patches the bundle manifest, surviving terminal restarts.

Takeaways
DeepSeek Harness plugins split into server-side tools (Node, registered for model invocation) and client-side UI plugins (browser, injected into interface slots).
A client-side UI plugin uses two entry files: an empty Node-side `apply` to satisfy the Cordis loader, and a browser-side entry that mounts React components into named slots.
The `shell.overlay` slot is a root-scoped list slot where multiple plugins can register independent UI components.
Cross-slot state between separate React trees is handled through a module-level shared store with a boolean and a Set of listener callbacks, consumed via `useSyncExternalStore`.
Wallpaper images are auto-scaled to 1920px and base64-encoded at build time; selection persists in localStorage and syncs across components through CustomEvents.
Making the theme background transparent requires overriding `--dsw-alias-bg-base` with `!important` so the body's wallpaper shows through.
Applying `backdrop-filter` directly to a sidebar column traps `position: fixed` portal descendants inside it; the fix places the filter on a `::before` pseudo-element with `z-index: -1`.
CSS Modules with hashed class names are targeted using suffix attribute selectors like `[class$="sidebarCol"]`, which breaks if the host renames its local classes.
Mouse-following glow effects write coordinates to CSS custom properties, letting the compositor handle updates without triggering repaints.
The plugin installs via `npx @deepseek-ai/dsh plugin --profile web add` into a persistent profile directory, surviving npx cache clears and terminal restarts.
Conclusions

Slot-based UI extension models invert the typical plugin approach: instead of plugins reaching into the host, the host exposes designated injection points with explicit contracts, which constrains what plugins can break but also limits what they can reach.

The dual-entry pattern solves a real dependency problem—an empty Node-side entry means UI plugins don't pollute the server runtime with React and DOM libraries, a separation that many Electron-based tools fail to enforce.

Using `backdrop-filter` on a parent element silently changes the containing block for all `position: fixed` children, a CSS behavior documented on MDN but still surprising in practice because it breaks portal-rendered panels that assume the viewport as their container.

CSS Modules' hashed class names create a deliberate encapsulation boundary that UI plugins must pierce with fragile attribute selectors; this tension between host encapsulation and plugin extensibility has no clean resolution in current web standards.

The plugin's known limitations—no persistence, no config panel, DOM-dependent button positioning—are honest trade-offs for a display-only plugin, but they also highlight how much infrastructure a "real" UI plugin needs before it feels like a first-class feature.

Concepts & terms
Cordis Loader
The plugin loading system used by DeepSeek Harness. It reads a `cordis.yml` manifest to build a plugin tree, and requires every plugin to export an `apply` function as its entry point.
Slot injection
A UI extension pattern where the host application divides its interface into named slots, and plugins register components into those slots rather than manipulating the DOM directly. dsh uses `shell.overlay` as a root-scoped list slot.
Containing block (CSS)
The reference box for positioning descendants. Applying `backdrop-filter` to an element makes it the containing block for its `position: fixed` children, which can trap portal-rendered panels inside a narrow sidebar instead of letting them use the viewport.
useSyncExternalStore
A React hook that subscribes to an external store and returns its current value. Used here to connect React components in separate trees to a shared module-level state store without prop drilling or context.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗