Bundle
@nkj/dsh-ui-pet
A canvas sprite-sheet pet that follows the pointer, rendered in the dsh web shell.overlay slot.
- Source
- Nkjv2
- License
- MIT
- Updated
- Updated 8 days ago
Readme
# @nkj/dsh-ui-pet A canvas **chibi cat** pet for the DeepSeek Harness web GUI. It is anchored to the bottom-right viewport corner (the body does **not** chase the pointer — the head turns toward its gaze and the pupils track the pointer) and is styled as a background decoration in the `shell.overlay` layer: `position:fixed` (no layout impact), fully click-through (`pointer-events:none`), a low z-index (below other overlay entries and dialogs), and < 1 opacity so the page content shows through. The cat is drawn procedurally on a DPR-aware canvas (no asset required) and reacts to activity, detected purely through DOM observation of stable `data-*` hooks (so it stays decoupled from the host's internal session/streaming state): - **Typing** — while you type in the composer, the cat leans toward the input, looks at it, and shows an animated "typing dots" bubble plus a pulsing activity dot; its tail sways faster. It returns to idle after a short pause. - **Streaming / working** — while the assistant streams a response, the cat tilts into a contemplative pose, looks up toward the conversation flow, and shows the same busy bubble + dot. - **Idle** — the cat breathes, blinks, its eyes follow the pointer, its head subtly turns toward the gaze, and its tail sways gently. ## How it plugs in The package is a dsh **bundle** (`dsh.bundle` → `cordis.patch.yml`) that is also a **client plugin** (`dsh.client`). Installing it into a profile with `dsh plugin --profile web add <pkg>` auto-registers it (the CLI reconciles installed dependencies that declare `dsh.bundle` into `dsh.profile.bundles`), then the client module system serves `/plugins/@nkj/dsh-ui-pet/client.js` on the next boot. No manual `cordis.yml` edit. ## Built-in dev loop ```sh # once: install build deps pnpm install # build both artifacts (lib/index.js host half + lib/client.js browser bundle) pnpm run build # iterate: rebuild the browser bundle on source change (the dsh host stat-polls # the served bundle and broadcasts a reload, so the browser refreshes on write) pnpm exec tsdown --watch ``` ## Verify locally (before publishing) ```sh dsh plugin --profile web add /absolute/path/to/dsh-ui-pet pnpm dsh web # bind http://127.0.0.1:3080 ``` In the browser at `http://127.0.0.1:3080`, confirm: 1. **In the tree** — `dsh --profile web --dump-config` contains the `ui-pet` row. 2. **Bundle served** — DevTools → Network shows `/plugins/@nkj/dsh-ui-pet/client.js` (`200`); or `curl http://127.0.0.1:3080/plugins/@nkj/dsh-ui-pet/client.js`. 3. **Rendered, and visible** — DevTools → Elements: the `<canvas>` is inside the `shell.overlay` layer, `position:fixed`, `pointer-events:none`, low z-index, and `opacity < 1`. It is anchored to the bottom-right viewport corner. 4. **Behavior** — the body stays put while the pointer moves (only the eyes follow it). Type in the composer: the pet leans toward the input and shows the busy bubble/dot. Send a message: while the assistant streams, the pet tilts into a working pose. The overlay never blocks clicks (the layer and canvas are `pointer-events:none`). While `SPRITE` is empty in `src/client/sprite.ts`, the pet draws a colorful placeholder blob, so step 4 is verifiable without any asset. ## Publish ```sh pnpm run build npm pack # confirm lib/, cordis.patch.yml, and src are in the tarball npm publish --access public ``` After publishing, a user installs it with: ```sh dsh plugin --profile web add @nkj/dsh-ui-pet ``` ## Customizing the pet - The cat look is parameterised in [`src/client/sprite.ts`](src/client/sprite.ts): the fur/ear/nose/chest palette (`FUR_TOP`, `FUR_BOTTOM`, `FUR_DARK`, `INNER_EAR`, `NOSE_PINK`, `CHEST_WHITE`, `WHISKER`) and the head/body proportions (`BODY_RX`, `BODY_RY`, `HEAD_RX`, `HEAD_RY`, `HEAD_OFFSET_Y`, `SIZE`). - Animations live in [`src/client/Pet.tsx`](src/client/Pet.tsx): the idle bob, head-turn toward the gaze, per-state body lean, pupil track, tail sway, the "typing dots" speech bubble, and the pulsing activity dot. - Make a sprite sheet to replace the vector cat: set `SPRITE` (asset path or URL) and tune `FRAME_COLS/FRAME_ROWS/FRAME_W/FRAME_H/FPS` in [`src/client/sprite.ts`](src/client/sprite.ts), and set `VECTOR=false`. - Busy-state detection tunables live in [`src/client/sprite.ts`](src/client/sprite.ts): `TYPING_IDLE_MS` (hold after the last composer keystroke) and `WORKING_IDLE_MS` (hold after the last conversation-flow mutation). - DOM hooks are the only host coupling: `[data-composer-card]` (composer, for typing) and `[data-chat-flow]` (conversation stream, for working). Both are optional — if absent, the pet simply idles. ## Model Experience None. The pet manages browser-only presentation; nothing reaches a model request. #### KV Cache effect None; this package neither assembles nor sends a provider request. ## Target version Runtime-compatible with dsh `0.1.1-rc.2` (the `web` profile). The host dsh install supplies the runtime `react`, `@deepseek-ai/cordis`, and the `slots` service. The published package is intentionally **build-dependency-free**: the source uses no `@deepseek-ai/*` imports (the DSH-specific types are left structural/untyped), so the standalone build needs only `react` + `tsdown` + `typescript`. The DSH integration is by convention: `apply` receives a ctx with a `slots` service, and the bundle is the standard `window.__ModuleLoader__.load` closure factory. ## Known Limitations and Deferred Work - The pet lives in the `shell.overlay` floating layer, so it paints above the app content (it is deliberately semi-transparent and small so it does not obscure it). Rendering it genuinely *behind* the content is impossible from a plugin without DSH source changes: the conversation/details/sidebar surfaces are opaque and would fully cover a behind-content canvas. Anchoring it to a corner (rather than chasing the pointer) is what keeps it clear of the text you are reading. - Busy detection is DOM-based and therefore heuristic. "Working" is inferred from conversation-flow DOM mutations, so brief non-streaming churn (e.g., a tool disclosure, or history loading on open) can also trigger the working pose for the debounce window. Distinguishing streaming specifically would require reading the host's `running`/stream state through a session hook. - The `ctx` / slot types are structural and untyped to keep the standalone build self-contained. When developing inside the dsh monorepo, restore the real `ClientContext` / `PropsRuntime<'shell.overlay'>` types for full checking. - The bundle format (closure factory + React externalization) is reproduced from the dsh `clientBundle` preset; iterate against a live `dsh web` to confirm exact output.
Install
dsh plugin --profile web add github:Nkjv2/dsh-ui-pet
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install nkj-dsh-ui-pet from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.