dsh-model-select-collapse
A DSH (DeepSeek Harness) web UI plugin that makes every provider group in the model selector collapsible — so a long model list stops being one endless scroll.
English · 简体中文
What this plugin does
Once installed, the model selector in the DSH composer gets clickable group headers:
| Before | After |
|---|---|
| Every provider's models are listed at full length, one long scroll | Each provider is a group with a header row that can be folded away |
| No way to hide providers you are not using | Click a header to collapse / expand that provider |
| State is lost every time the menu closes | Collapsed state is remembered per provider |
Concretely:
- Every provider group gets a header row with the provider name and the number of models in that group.
- Clicking the header collapses or expands that group, and the chevron beside it rotates to show the state.
- The state is stored per provider id in
localStorageunder the keydsh.modelSelect.collapsed, so it survives closing and reopening the menu and survives a page reload. - A provider with no stored record starts expanded, so providers you import later need no configuration.
Everything else about the picker behaves as before: the two-level menu (model list and reasoning effort), keyboard navigation, the loading / error / warning states, the retry action when a directory fails to load, and the toast shown when a selection fails.
The problem it solves
DSH lists every provider's models in one flat menu. With five or six providers that is already a long scroll; with a dozen it becomes a wall of entries, and the two or three you actually switch between are buried in the middle. This plugin keeps the same layout and ordering but lets you fold away the providers you are not using right now.
Behaviour details
| Behaviour | Detail |
|---|---|
| Toggle | Click a provider group header |
| Default | Expanded; only groups you collapsed are recorded as collapsed |
| Storage key | dsh.modelSelect.collapsed in localStorage (a { providerId: true } map) |
| Scope | Per browser profile — local state, never synced anywhere |
| Newly imported providers | Start expanded (no stored record) |
| Keyboard | The header is a real button, so it is reachable with Tab and activated with Enter / Space |
| Accessibility | Each group is a role="group" section labelled by its header, and the header exposes aria-expanded |
DSH 0.1.7-alpha.2 compatibility
DSH 0.1.7-alpha.2 renamed every icon export in @deepseek-ai/dsh-client-ui-primitives:
| Name before 0.1.7-alpha.2 | Name in 0.1.7-alpha.2 and later |
|---|---|
IconChevronDownOutline14 |
IconChevronDownOutlineRegular |
IconChevronRightOutline14 |
IconChevronRightOutlineRegular |
IconCheckOutline16 |
IconCheckOutlineRegular |
IconWarningOutline16 |
IconWarningOutlineRegular |
The old names resolve to undefined on the new version, and using undefined as a React element type throws Element type is invalid. DSH's slot system then abdicates the crashed entry and the official model selector silently takes its place — which looks exactly like "the collapse button disappeared", not like a broken plugin.
lib/client.js therefore probes both namings when it loads and keeps whichever is present, falling back to a no-op component if an icon is missing entirely. The result is one release that runs on both older and newer DSH versions. Toast exists in both versions with a compatible signature and is treated as optional the same way.
Installation
From GitHub (recommended)
dsh plugin --profile web add github:banana770/dsh-model-select-collapse
From a local checkout
dsh plugin --profile web add /path/to/dsh-model-select-collapse
After installing, reload the DSH web UI (restart DSH, or hard-refresh the page) and open the model selector from the composer.
Manual installation
Four files matter:
package.json manifest: declares the client entry (dsh.client) and the bundle patch
cordis.patch.yml mounts the plugin into the profile's bundle roster (insert: model-select-collapse)
lib/index.js host half: an empty no-op that keeps the loader entry alive
lib/client.js the real logic: shadows the model selector seat and adds group collapsing
Drop the directory into the profile's install location and make sure the insert entry in cordis.patch.yml (id: model-select-collapse, name: @dsh-external/dsh-model-select-collapse) is active. There is no build step.
Uninstalling
Remove dsh-model-select-collapse from the Installed list on the sidebar Plugins page, then reload the UI. For a manual install, delete the matching insert entry from cordis.patch.yml and restart DSH.
How it works
- Seat shadowing. DSH's composer model picker is the
conversation.input.modelslot. The official plugin registers it at the default priority0; this plugin registers at-1, and the rule for a single slot is "lowest priority wins". The plugin's view therefore replaces the official one. - No forked data layer. All provider and model data comes from DSH's own
modelDirectoriesservice (directoryFor(sessionId)returns aSnapshotStore, consumed here throughuseSyncExternalStore) — the same source the official view uses. The plugin only re-renders it. - Isolated styles. One stylesheet is injected into
document.headbehind adata-plugin-cssmarker, and every class name is namespaced with anmse_prefix, so nothing can collide with official DSH class names. - No dictionary registration. The official model-selection plugin already registers the
zh/endictionaries for themodelnamespace, and this plugin declares that same namespace when registering its seat so it reuses them — registering a duplicate namespace throws.
Customizing
| What you want to change | Where |
|---|---|
| Default state for providers with no stored record | loadCollapsed() in lib/client.js |
| Storage key | STORAGE_KEY in lib/client.js |
| Chevron direction when collapsed | .mse_groupChevronCollapsed in the css block |
| Width, spacing, colours | the css block in lib/client.js (built on --dsw-* theme variables) |
Privacy
- Nothing leaves the machine. The plugin makes no network requests of its own and contacts no third-party service; it reads and writes the same DSH model directory service that the official selector uses.
- No telemetry, no analytics, no accounts.
- No API keys are included in or requested by this plugin. Provider credentials stay wherever DSH already keeps them.
- One piece of local state is written: the
dsh.modelSelect.collapsedmap inlocalStorage, which records which provider groups you folded away. It holds provider ids and booleans and nothing else; deleting it simply resets every group to expanded. - No access to conversation content and no file access.
Compatibility
- Targets the DSH web profile (
platform: "web"). - Verified on DSH
0.1.7-alpha.2. Because the icon lookup probes both namings, it also runs on releases that still use the older icon names. - Uses the
reactthat DSH itself provides (peerDependencies: >=18 <19), so a second copy is never installed. - No build step:
lib/client.jsis the final, directly mountable bundle.
License
BSD-3-Clause — use, modify and redistribute freely.
No comments yet. Be the first to write one.