# DSH Better Sidebar Research

> Research date: 2026-08-25
>
> Scope: public extension API and integration constraints for [`omdsh-dev/DSH-better-sidebar`](https://github.com/omdsh-dev/DSH-better-sidebar), limited to the repository's GitHub source at release commit [`f9153df`](https://github.com/omdsh-dev/DSH-better-sidebar/tree/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1) (`v0.16.1`).

## Executive Findings

- This is a dual-half DSH bundle: a Node host plugin and a React client plugin. The public third-party extension service, `ctx.betterSidebar`, exists **only in the client half**; host code must use the plugin's HTTP/WebSocket routes rather than the service.
- The supported extension points are `registerTab(descriptor)` and `registerFileViewer(descriptor)`. Both return a disposer. Consumers must declare `inject = ['betterSidebar']` and retain the disposer with `ctx.effect(...)`, otherwise a disable/HMR cycle leaves duplicate registrations.
- Version `0.16.1` exports a capability list rather than a stable semver-only contract. Gate optional use through `service.features`, especially `floatWindows`, `urlTarget`, `pluginSettings`, targeted opens, and lifecycle hooks.
- The package has tightly coupled DSH release-candidate peers (`^0.1.0-rc.8`) but develops against `0.1.1-rc.1`; its README claims tested support only through `0.1.1-rc.2`. It also requires Node >=20 and has a native `node-pty` dependency. These are material installation and compatibility risks.

## Package and Mount Shape

`package.json` identifies the published package as `dsh-better-sidebar` version `0.16.1`, ESM, Node >=20, with host `main`, public types, and client/service export paths. It declares a DSH bundle patch and requests client runtime, locale, slots, conversation, and module services ([package metadata, lines 2-55](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/package.json#L2-L55)). The marketplace manifest identifies `dsh-external/dsh-better-sidebar`, points the host at `lib/index.js`, and points the client registry at `lib/client-registry.js` ([manifest, lines 1-15](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/dsh.plugin.json#L1-L15)).

The bundle patch inserts one Cordis row with `id: better-sidebar` and `name: dsh-better-sidebar`. Its disabled expression attempts to avoid duplicate installation beneath an aggregate bundle, but only sees earlier loader entries; aggregate bundles must precede this bundle. A reversed order and leftover manual mount can double-mount the host routes ([patch, lines 33-49](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/cordis.patch.yml#L33-L49)).

## Client and Host Lifecycle

The host declares `inject = ['webServer', 'sessions', 'webRuntime', 'tools']`; it owns `/sidebar` APIs, media/HTML/bundle routes and terminal WebSocket upgrades ([host entry, lines 1-15 and 76-80](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/index.ts#L1-L15)). The host protects routes with the same Host trust fence as DSH's API gateway and scopes requests to a session's authoritative working directory ([host entry, lines 1-15](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/index.ts#L1-L15)). Its `fs.write` implementation is an atomic write endpoint, so the HTTP API is not read-only ([host API, lines 314-339](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/index.ts#L314-L339)).

The client declares `inject = ['slots', 'sessions', 'connection', 'workspaces', 'locale', 'modules']` ([client entry, lines 34-39](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/index.tsx#L34-L39)). During `apply()`, it creates the store and service, then immediately publishes `ctx.provide('betterSidebar', service)` before registering built-ins and mounting the UI ([client entry, lines 114-151](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/index.tsx#L114-L151)). The project documentation explicitly states that this service is client-only; host consumers should call `/sidebar/api/*`, `/sidebar/file`, or `/sidebar/ws/*` instead ([external guide, lines 17-21](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L17-L21)).

## Third-Party Registration Contract

A consumer should type-import the package for the `@deepseek-ai/cordis` Context augmentation, inject `betterSidebar`, then call registration inside `ctx.effect`. The guide's minimal shape is authoritative and shows both a tab and a viewer ([guide, lines 85-123](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L85-L123)). `registerTab` and `registerFileViewer` reject duplicate IDs and return identity-checked disposers ([service implementation, lines 536-562](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L536-L562)).

The runtime service API includes registration, registry lookup/subscription, enablement checks, viewer matching, `openTab`, `closeTab`, snapshots/state subscription, tab updates/activation, and `openFile` ([service interface, lines 343-428](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L343-L428)). `openTab` supports a target `SessionScope`; a content seed (`path` or `url`) auto-opens the landing panel only when the target session is currently visible ([service implementation, lines 603-742](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L603-L742)).

### Tab descriptor

A tab requires `id`, `title`, and `component`. Public optional controls include menu order/visibility, availability, `single` or `dedupeKey`, `createTab`, URL-interception `urlTarget`, settings, badge, and `onOpen`/`onActivate`/`onClose` hooks ([TabDescriptor, lines 160-242](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L160-L242)). The tab component receives `ctx`, state store, `{ sessionId, cwd? }` scope, a persisted tab object, and `visible`; use `visible` to stop polling when inactive or collapsed ([TabComponentProps, lines 141-158](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L141-L158)).

`urlTarget` is first-match by registration order and only participates in external-link interception. A throwing predicate is caught, and the builtin browser is the fallback ([URL target matching, lines 444-469](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L444-L469)). `available` disables only the plus-menu entry; `openTab` is still allowed unless the user has disabled the tab type in settings ([openTab contract, lines 368-390](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L368-L390)).

### File viewer descriptor

A viewer requires `id`, lowercase dotless `exts`, `fetchStrategy`, and `component`; optional `priority`, byte-sniffing `detect`, `load`, and settings are supported ([FileViewerDescriptor, lines 297-324](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L297-L324)). Strategies are `none`, `fsRead`, `mediaUrl`, `custom`, and `binary-download` ([FileFetchStrategy, lines 244-250](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L244-L250)). The matching algorithm is one pass by descending priority and stable registration order, with `detect` evaluated before extensions when bytes are available; `exts: []` is catch-all, but detect-bearing catch-alls yield when their sniff fails ([implementation, lines 573-600](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L573-L600)).

## Panel Slots and Size Constraints

This package does not offer third parties a DSH-wide panel-slot API. It exclusively owns the panel portal, and its guide says external tabs can render only inside that portal, not replace the full panel ([constraints, lines 664-675](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L664-L675)). A third-party page is therefore a tab registration, not a `slots.register` integration.

Content is session-scoped and persisted. If a stored tab type is missing because its plugin is absent, it renders as an orphan placeholder and is restored after the plugin returns ([lifecycle notes, lines 655-660](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L655-L660)). Free-window support is feature-gated: floated tabs are supported identically, open/dedupe raises a matching float, and normal close/activation lifecycle still applies ([feature documentation, lines 490-505](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L490-L505)).

The README describes the sizing behavior: two workbenches can split/merge tabs; mobile below 768px merges them into a full-width drawer, while free windows default to 390x780 and clamp to the viewport ([README, lines 49-55](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/README_EN.md#L49-L55); [release notes, lines 275-278](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/README_EN.md#L275-L278)). Consumer components must therefore be responsive and avoid relying on a fixed sidebar width, a permanent mount, or background work while `visible === false`.

## Settings Surface

Every registered tab/viewer becomes an entry in the Side Card settings inventory. An absent enablement key means enabled. Disabling blocks new tab opens but retains existing tabs; disabling a viewer causes fallback to the next matching viewer ([guide, lines 604-613](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L604-L613)). `settings.toggles` may only name host preference-schema fields. Third-party settings must use JSON-serializable `pluginToggles` or a `render` function, persisted under `pluginSettings[descriptor.id]` ([settings declaration, lines 114-139](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L114-L139)).

## HTTP Use From a Client Tab

Tabs/viewers share the browser origin and can call the host through `POST /sidebar/api/<method>` with `sessionId` and optional `cwd`; the guide shows the exact request shape ([guide, lines 464-476](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L464-L476)). The public guide lists `fs.tree`, `fs.read`, `fs.write`, Git, terminal close, and settings methods, and states that paths are bounded by the session's authoritative workspace via real-path checks ([guide, lines 478-499](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L478-L499)). It explicitly says not to value-import the internal `api.ts`; duplicate the fetch pattern instead.

## Third-Party Examples

The upstream guide names `fuhefei/dsh-sentinel` as its first third-party integration: it uses an optional soft dependency, locally restates the minimal `registerTab` face, registers a single `dsh-sentinel:watches` tab at order 60, and avoids a runtime value import ([guide, lines 779-786](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L779-L786)). The project also names `ChenRuoT/dsh-sidebar-qa` as another third-party tab consumer ([guide, lines 788-790](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L788-L790)). These are upstream-declared examples; their own repositories should be consulted before treating their code as a reusable compatibility reference.

## Compatibility and Adoption Risks

- **DSH peer skew:** public peers are `^0.1.0-rc.8`, while development dependencies are pinned to `0.1.1-rc.1`; README support claims only `0.1.0-rc.8`, `0.1.1-rc.1`, and `0.1.1-rc.2` ([peer declarations, lines 86-105 and 143-175](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/package.json#L86-L105); [supported versions, lines 61-72](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/README_EN.md#L61-L72)). Treat newer DSH releases as unverified.
- **Cordis identity:** use `@deepseek-ai/cordis`, not public `cordis`, and keep `dsh-better-sidebar` as an optional peer dependency to avoid duplicate service/type instances ([guide, lines 25-53](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L25-L53)).
- **Client bundling:** client code may type-import the package, but must not value-import it or internal service modules. The project's bundle-purity gate blocks cross-plugin runtime imports; service calls are the intended interoperability mechanism ([constraints, lines 664-675](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md#L664-L675)).
- **Native install:** `node-pty` is a production dependency ([package metadata, lines 111-141](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/package.json#L111-L141)); upstream installation instructions require pnpm 10+, Node 20+, and may require approving/rebuilding native scripts ([README, lines 59-72 and 99-107](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/README_EN.md#L59-L72)).
- **Feature evolution:** use `features.includes(...)`, not undocumented assumptions. `SIDEBAR_FEATURES` currently contains `badge`, `tabLifecycle`, `updateTab`, `openFile`, `targetedOpen`, `stateSubscription`, `tabMeta`, `pluginSettings`, `urlTarget`, `settingSelect`, and `floatWindows` ([service, lines 471-506](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts#L471-L506)).

## Primary Sources

- [`docs/external-plugin-guide.md`](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/docs/external-plugin-guide.md)
- [`src/client/service.ts`](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/service.ts)
- [`src/client/index.tsx`](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/client/index.tsx)
- [`src/index.ts`](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/src/index.ts)
- [`package.json`](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/package.json)
- [`dsh.plugin.json`](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/dsh.plugin.json)
- [`cordis.patch.yml`](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/cordis.patch.yml)
- [`README_EN.md`](https://github.com/omdsh-dev/DSH-better-sidebar/blob/f9153dfc1ce47cf43445c1b351ee3ae47b4ad9f1/README_EN.md)
