English | 简体中文
A small, self-contained DSH WebUI plugin for quicker navigation through long conversations, with a little lightweight motion on top.
It has exactly two features:
- History — a floating conversation-history strip on the right edge of the conversation. One bar per user turn; hover shows the question and a pin control; click scrolls smoothly to that turn.
- Motion — entrance animation for transcript rows, the sidebar session tree, the active sidebar row's selection trace, and a new conversation's welcome dialog. On by default, with one master switch.
dsh-quicknav is especially useful if you run DSH on a server and access its WebUI remotely through a reverse proxy. That is the environment it was originally built for.
It works on a non-loopback WebUI (for example, https://<your-dsh-host>) without an SSH tunnel, without opening settings.describe or any other privileged RPC, and without weakening DSH's remote security boundary.
Version 0.1.0 · MIT · target DSH Web runtime 0.1.0-rc.8 (CLI 0.1.0-rc.7)
This plugin started with two things we liked in
dsh-client-ui-custom:
its conversation-history navigation and its motion.
The original plugin is a much broader UI-customization project. Some of its features depend on DSH host settings, which are intentionally unavailable when the WebUI is accessed remotely through a non-loopback connection. That makes several of those features unavailable in the server + reverse-proxy setup we actually use.
Rather than work around that security boundary, we wanted something much smaller:
- keep the history navigation we use in long conversations;
- keep a small set of fixed entrance animations;
- use no privileged host settings or settings RPC;
- store the few user preferences locally in the browser;
- leave the rest of DSH alone.
So dsh-quicknav is deliberately not a general UI-customization suite. It does two small things and tries to do them without changing DSH's security model.
Implemented: history and motion.
Deliberately not implemented: Markdown rendering (DSH renders Markdown natively), appearance/theming, keyboard shortcuts, usage statistics, plugin marketplace, per-feature style pickers and presets, or settings-panel motion.
- Target host: a DeepSeek DSH WebUI profile.
- Verified versions: DSH CLI
0.1.0-rc.7and client/runtime packages0.1.0-rc.8. This is the only combination this release has been tested against. - DSH is still in release-candidate phase. This plugin relies on several internal or semi-internal UI hooks — DOM attributes, slots and snapshot shapes. Compatibility with future rc or stable versions is not guaranteed.
- No runtime npm dependencies: the browser bundle consumes the DSH shell's own React seed (
react,react/jsx-runtime,react-dom; React 18.3.1 in the verified shell). - Browser: a modern browser with
localStorage, which is used for preferences. - Building from source: Node.js >= 20. This is only needed for maintainers and developers. Users installing the prebuilt release artifact do not need Node.
Because DSH is still changing, check the upgrade notes after upgrading DSH.
Download dsh-quicknav-<version>.tgz from the corresponding GitHub Release, then install it into your DSH WebUI profile:
dsh plugin --profile <profile> add file:./dsh-quicknav-<version>.tgzReload the WebUI after installation.
That's it. The release artifact already contains the built browser bundle; no Node.js toolchain or manual profile editing is required.
For development, a source checkout can also be linked directly:
npm ci
npm run build
dsh plugin --profile <profile> add link:<path-to-dsh-quicknav>This is a developer workflow rather than the recommended installation method.
dsh plugin normally reconciles the package and dsh.profile.bundles automatically. Do not manually add dsh-quicknav a second time if it is already registered.
If installation behaves unexpectedly, inspect the affected profile under:
$DSH_HOME/profiles/<profile>/
The relevant files are typically package.json, cordis.patch.yml and pnpm-lock.yaml.
The package's dsh.bundle.patch mounts the host half, while dsh.client exposes the browser bundle through DSH's client-module system.
Download the new release artifact and run the same command against the same profile:
dsh plugin --profile <profile> add file:./dsh-quicknav-<version>.tgzThere is no separate upgrade subcommand. Re-running add with the new artifact updates the profile dependency.
Reload the WebUI afterward.
Your quicknav preferences survive an update because they are stored in the browser under:
localStorage['dsh-quicknav:prefs:v1']
After upgrading DSH itself, see Upgrade risk before assuming the plugin is still compatible.
dsh plugin --profile <profile> remove dsh-quicknavReload the WebUI afterward.
The plugin does not write DSH host settings, so there are no host-side preferences to remove. Its browser-side preference entry is intentionally left behind:
localStorage['dsh-quicknav:prefs:v1']
Clear that entry manually if you also want to remove the saved preferences.
If an older or unusual DSH version leaves dsh-quicknav in dsh.profile.bundles after removal, remove that stale entry manually. A normal uninstall on the verified DSH version cleans it up automatically.
- Floats over the conversation's right edge from the frame's
shell.overlaylayer. It is additive: it takes no column, replaces no panel, and does not touch DSH's tool-details column. - One bar per user turn, derived from the mounted chat snapshot.
- Hovering a bar shows a tooltip with the question preview and reveals a pin control for that turn.
- Clicking a bar smooth-scrolls to the message and briefly highlights its position.
- The bar for the turn currently on screen keeps a subtle accent tint.
- Older history is paged in automatically, with throttling and a safety cap.
- Pinned turns ignore the count limit and are drawn with an accent frame.
- The control at the top of the strip lets you choose how many entries to show (5 / 10 / 20 / all) and toggle motion globally.
- The strip stays hidden on a blank, brand-new session.
Motion uses a small set of fixed effects — no picker and no presets.
| Surface | Effect |
|---|---|
| Transcript rows | fade-up (8px rise), staggered 40ms/row, capped at 320ms; replayed on every conversation open/switch |
| Transcript column | panel fade + 6px rise on each open/switch |
| Sidebar tree | slide-left on wholesale loads only; group expansion and search results appear without delay |
| Active sidebar row | a selection box traced in and kept while the row stays active |
| New conversation | the welcome dialog fades in with a 4px rise (420ms) |
prefers-reduced-motion: reduce downgrades every entrance to a 100ms opacity-only cross-fade and removes the selection trace.
The motion master switch is on by default. Turning it off immediately removes every class the motion engine applied.
Preferences are stored in the browser under:
localStorage['dsh-quicknav:prefs:v1']
The document looks like this:
{
"limit": 10,
"pinned": {
"<sessionId>": [3, 7]
},
"motionEnabled": true
}limit— how many recent turns the strip shows (5 / 10 / 20 / all).pinned— pinned turn numbers per session ID; pinned turns ignorelimit.motionEnabled— the entrance-motion master switch.
Only these values are persisted.
No message text is ever written to localStorage. Tooltip previews are read transiently from the current DSH session snapshot at render time.
Preference reads are sanitized: unknown or corrupt documents fall back to defaults, and an unavailable or read-only store degrades safely.
The plugin creates no DSH settings namespace and performs no host round-trip for preferences.
- The plugin makes no network requests and calls no remote APIs.
- It sends no telemetry or analytics.
- It does not use DSH host settings (
settingsScope/settings.*) and performs no privileged settings RPC. - The only persistent state is the preference document in the current browser's
localStorage. - Message previews are read from the live session snapshot and are not persisted by the plugin.
Nothing is sent anywhere by dsh-quicknav.
- Compatibility is not guaranteed across DSH versions. DSH is still in rc and several hooks used here are internal UI details.
- "All" is not infinite history. The pager is currently capped at 120 bars. Older turns remain reachable in the conversation itself.
- A blank session shows no strip. There are no user turns to list yet.
- Motion is cosmetic and structural. If DSH changes the relevant transcript or sidebar DOM, an animation may simply stop applying.
- If the session snapshot is temporarily unavailable, the strip renders nothing rather than crashing the page.
dsh-quicknav intentionally has very little integration surface, but some of that surface is internal to DSH.
| Risk | Breaks |
|---|---|
High — data-chat-anchor-key changes |
jump + transcript motion |
High — data-chat-flow / data-composer-seat change |
panel + welcome entrances |
Medium — [role="tree"] / [role="treeitem"] / aria-selected change |
sidebar motion + selection trace |
Medium — shell.overlay disappears or its pointer-events behaviour changes |
history strip |
Medium — ctx.sessions.list / binding() shape changes |
history strip |
Medium — chat node payloads (content, time, location) change |
previews/times degrade |
Low — client ModuleLoader / dsh.client / dsh.bundle.patch changes |
bundle fails to load |
| Low — locale registration forms change | labels fall back to raw keys |
Low — --dsw-alias-* variables change |
colours degrade |
Most UI-hook failures degrade quietly rather than crashing DSH: a missing chat snapshot renders no strip, a missing anchor makes a jump a no-op, and missing motion hooks simply leave the native UI unchanged.
Maintainers can revalidate the known contracts after a DSH upgrade with:
node scripts/check-dsh-contract.mjs
npm run verifyThis section documents the DSH internals the plugin currently relies on. Regular users do not need any of this to install or use quicknav.
The broader dsh-client-ui-custom plugin registers several features behind a settings-snapshot readiness check:
const snapshot = scope.getSnapshot()
if (snapshot.status !== 'ready' || snapshot.value === undefined) returnDSH intentionally gives non-loopback WebUI connections a non-host settings scope:
// dsh-client-ui-settings — SettingsScopeBinder.bind()
new SettingsScopeController(
api,
spec,
mirror,
connection.isLoopback ? 'host' : 'memory',
schema,
)In that memory scope, persistent host settings are unavailable.
This is part of DSH's remote security model, not a bug.
dsh-quicknav therefore does not try to make host settings available remotely. It simply does not participate in the settings transport: its few preferences live in browser localStorage, and its runtime functionality uses the existing client-side session and UI surfaces.
| Service | Used for |
|---|---|
ctx.slots |
inject('shell.overlay') + register(...) |
ctx.sessions |
current session, conversation snapshot and loadOlder |
ctx.locale |
labels and locale updates |
ctx.effect |
fiber-scoped cleanup |
Not used: settingsScope, connection, remote, workspaces, layout, conversation, or any settings.* RPC.
The history strip uses shell.overlay, an additive root-level list slot provided by DSH's UI layout.
The entry renders as a fixed overlay and opts into pointer events. It does not replace or resize any native DSH panel.
| Hook | Used for |
|---|---|
[data-chat-anchor-key] |
jump target + transcript motion rows |
[data-chat-flow] |
transcript panel entrance |
[data-composer-seat] |
new-conversation welcome entrance |
[role="tree"] [role="treeitem"] |
sidebar motion |
aria-selected on tree items |
active-row selection trace |
--dsw-alias-* CSS variables |
strip / tooltip colouring |
The history implementation currently reads these DSH snapshot concepts:
ConversationSnapshot—blank,openState,hasMore,loadingOlder,chatChatSnapshot—order,nodes.get(key)ChatConversationViewNode—kind,data,locationUserMessageNode—content,time
These are not treated as stable public APIs.
Regular users should install the prebuilt Release artifact. This section is for people building, extending or maintaining the plugin.
npm install # Node >= 20
npm run typecheck # tsc --noEmit (strict)
npm run build # esbuild -> lib/index.js + lib/client.js
npm test # vitest (jsdom); bundle rebuilt first via pretest
npm run verify # typecheck + build + testsTwo additional maintainer tools require an installed DSH and are intentionally not part of npm run verify:
node scripts/check-dsh-contract.mjs
node scripts/verify-live-bundle.mjs --html <live-index.html>check-dsh-contract.mjs revalidates the known host hooks against an installed DSH. DSH home resolution is:
argument > $DSH_HOME > ~/.dsh
verify-live-bundle.mjs materializes the built client bundle through DSH's real client module system against a captured WebUI page. See the script header for the capture procedure.
DSH's own client bundles are produced by an internal monorepo pipeline that is not independently installable. This project reproduces the output format expected by the runtime:
window.__ModuleLoader__.load({
id: 'dsh-quicknav',
factory: (require) => {
var module = { exports: {} }
/* CJS body */
return module.exports
},
})scripts/build.mjs bundles src/client/index.ts with esbuild (CJS, React external) and wraps the result in that envelope.
The build also enforces a purity gate: every require() in the generated client bundle must be one of the static modules provided by the DSH shell.
The current release requires only:
react
react/jsx-runtime
react-dom
React itself is not bundled into dsh-quicknav.
dsh-quicknav is released under the MIT License. See LICENSE.
Parts of the history and motion implementation are adapted from
dsh-client-ui-custom by Yoli-mi, also released under MIT.
See NOTICE.md for the pinned upstream revision, exact derived-file mapping and upstream license text. Adapted files also carry provenance in their source headers.
Special thanks to Yoli-mi for open-sourcing dsh-client-ui-custom. dsh-quicknav grew out of its history-navigation and motion ideas, which gave this little plugin its starting point.