DSH 0.1.6-alpha.2 ships a right-Sidebar terminal and a right-Sidebar browser of its own. Both were things this plugin had built from scratch, so keeping them meant two implementations of one surface plus a plugin that shadows a host type the product is actively developing. Terminal: the plugin's whole PTY stack goes — the UI-tab manager, the agent-owned registry, the lazy node-pty loader, the eight model-facing terminal_* tools, the xterm view and its font/link/scrollback helpers, and the cross-session 'pinned terminal' rail that existed only to keep a terminal alive across a session switch. The host's terminal kind becomes the only provider, which also retires the node-pty dependency, its pnpm allowBuilds entry, and the installer's -Repair mode (issue #140's whole class of 'node-pty failed to load' support). Model-facing capability note: the plugin used to inject terminal_* tools (off by default). Those are the model's only cross-call persistent terminal, and DSH's own equivalent (@deepseek-ai/dsh-tool-terminal) is not mounted by any shipped bundle, so removing them leaves the model with the one-shot bash/pwsh tools only. That is the accepted cost of not maintaining a second PTY surface; the README documents how to enable the upstream toolset. Browser: the plugin's browser tab type is dropped, so the host's own browser kind owns embedded pages. The DOM-level link takeover STAYS (it is the only thing that can route an https link to the system browser by protocol, which the host does not do) but now delegates to the host's browser tab via ctx.sidebarRight.openTab('browser', …) with a window.open fallback, and the two settings that only ever configured the plugin's own iframe (sandbox escape hatch, loopback allowlist) go with it — along with the host-side browser.probe route they fed. Cards: the empty-pane welcome cards adopt the host's guide-capsule recipe (0.5px l4 hairline, 24px radius, layer-1 fill, 56px floor, 14px/20px padding) so the plugin's 'pick what opens here' surface reads as the same design as DSH's own. A theme.spec guard pins the geometry and the token-only paint — including that the upstream-only --dsw-alias-bg-l1/-l2 spellings (defined nowhere, so their declarations are silently dropped upstream) never appear here. Also drops the dead state the removed features left behind (SidebarTab.pin, nextTerminal, agentWaits, bottomOpenedOnce, the agent-tab helpers, the pinned-tab memo key), re-points every test fixture that used them at surviving equivalents, and prunes the specs of the deleted features.
164 lines
5.8 KiB
TypeScript
164 lines
5.8 KiB
TypeScript
/**
|
|
* Consumer-facing type surface compile gate (v0.12.0+): this file exercises
|
|
* EVERY public type and descriptor field exactly the way an external plugin
|
|
* would, so `pnpm typecheck` fails the moment the shipped declaration
|
|
* surface (service.ts re-exports / descriptor fields / service methods)
|
|
* drifts from what consumers can name. Type-only — erased at runtime, never
|
|
* executed by vitest (no `*.spec` suffix).
|
|
*
|
|
* Mirrors the "external consumer" fixture: what is importable here from
|
|
* `../src/client/service.ts` must also be importable from
|
|
* `dsh-better-sidebar/client/service` in the built package.
|
|
*/
|
|
import type {} from '../src/client/service.ts'
|
|
import {
|
|
FOLDER_EXT,
|
|
FOLDER_OPEN_EXT,
|
|
SIDEBAR_FEATURES,
|
|
SIDEBAR_SERVICE_VERSION,
|
|
} from '../src/client/service.ts'
|
|
import type {
|
|
BetterSidebarService,
|
|
FileFetchStrategy,
|
|
FileIconDescriptor,
|
|
FileViewerDescriptor,
|
|
FileViewerProps,
|
|
OpenTabSeed,
|
|
SidebarSettingsDeclaration,
|
|
SidebarSettingsRenderProps,
|
|
SidebarSettingToggle,
|
|
SidebarSettingToggleType,
|
|
TabComponentProps,
|
|
TabDescriptor,
|
|
} from '../src/client/service.ts'
|
|
import type {
|
|
SessionScope,
|
|
SidebarDiffRef,
|
|
SidebarPrefs,
|
|
SidebarSnapshot,
|
|
SidebarState,
|
|
SidebarStore,
|
|
SidebarTab,
|
|
TabType,
|
|
} from '../src/client/service.ts'
|
|
|
|
/** A full-featured external tab descriptor using every v0.12.0 field. */
|
|
const tab: TabDescriptor = {
|
|
id: 'my-plugin:db',
|
|
title: () => 'Database',
|
|
icon: (_size: number) => null,
|
|
order: 50,
|
|
hidden: false,
|
|
available: (ctx, scope, state) => scope.sessionId !== '' && state.bottomOpen && ctx !== null,
|
|
single: false,
|
|
dedupeKey: (t: SidebarTab) => t.id,
|
|
createTab: (state: SidebarState) => ({
|
|
tab: { id: `my-plugin:db:${state.nextBrowser}`, type: 'my-plugin:db', title: 'DB', meta: { n: state.nextBrowser } },
|
|
patch: { nextBrowser: state.nextBrowser + 1 },
|
|
}),
|
|
badge: (ctx, scope, state) => (state.expanded.length > 0 ? state.expanded.length : null),
|
|
onOpen: (tab: SidebarTab, scope: SessionScope) => { void tab; void scope },
|
|
onActivate: (tab: SidebarTab, scope: SessionScope) => { void tab; void scope },
|
|
onClose: (tab: SidebarTab, scope: SessionScope) => { void tab; void scope },
|
|
settings: {
|
|
toggles: [{ key: 'autoOpenSubagent', title: 'Auto-open', type: 'switch' }],
|
|
pluginToggles: [{
|
|
key: 'pageSize',
|
|
title: 'Page size',
|
|
type: 'number',
|
|
min: 1,
|
|
max: 100,
|
|
unit: 'rows',
|
|
}],
|
|
render: (props: SidebarSettingsRenderProps) => {
|
|
props.updatePluginSetting('refresh', true)
|
|
props.close()
|
|
return null
|
|
},
|
|
},
|
|
component: (props: TabComponentProps) => {
|
|
const { ctx, store, scope, tab, visible } = props
|
|
void ctx; void store; void scope; void tab; void visible
|
|
return null
|
|
},
|
|
}
|
|
|
|
/** A full-featured external viewer using the v0.12.0 load signal. */
|
|
const viewer: FileViewerDescriptor = {
|
|
id: 'my-plugin:csv',
|
|
title: 'CSV',
|
|
exts: ['csv'],
|
|
priority: 10,
|
|
fetchStrategy: 'custom',
|
|
detect: (_path, head: Uint8Array) => head.length > 0,
|
|
load: async (path: string, scope: SessionScope, signal?: AbortSignal) => {
|
|
void path; void scope; void signal
|
|
return { rows: [] }
|
|
},
|
|
settings: { pluginToggles: [{ key: 'delimiter', title: 'Delimiter', type: 'text', placeholder: ',' }] },
|
|
component: (props: FileViewerProps) => {
|
|
const { customData, content, truncated, mediaUrl, viewerId, path, title } = props
|
|
void customData; void content; void truncated; void mediaUrl; void viewerId; void path; void title
|
|
return null
|
|
},
|
|
}
|
|
|
|
/** The full service surface, exercised exactly as consumers call it. */
|
|
declare const ctx: { betterSidebar: BetterSidebarService }
|
|
const service: BetterSidebarService = ctx.betterSidebar
|
|
service.registerTab(tab)
|
|
service.registerFileViewer(viewer)
|
|
service.getTabs()
|
|
service.getFileViewers()
|
|
service.getTab('my-plugin:db')
|
|
service.isTabEnabled('my-plugin:db')
|
|
service.isViewerEnabled('my-plugin:csv')
|
|
service.matchFileViewer('a.csv', new Uint8Array([1]))
|
|
service.closeTab('tab:1')
|
|
service.subscribe(() => {})
|
|
const seed: OpenTabSeed = { type: 'my-plugin:db', title: 'DB', path: '/p', id: 'x', meta: { a: 1 } }
|
|
service.openTab(seed)
|
|
service.openTab(seed, { sessionId: 's1', cwd: '/p' })
|
|
const _version: string = service.version
|
|
service.features.includes('badge')
|
|
const snapshot: SidebarSnapshot | undefined = service.getSnapshot()
|
|
void snapshot
|
|
service.subscribeState(() => {})
|
|
service.updateTab('tab:1', { title: 'T', path: '/p', meta: 1 })
|
|
service.activateTab('tab:1')
|
|
service.openFile({ sessionId: 's1', cwd: '/p' }, '/p/a.csv', 'Data')
|
|
|
|
/** File-icon registration surface (feature `fileIcons`). */
|
|
const icon: FileIconDescriptor = {
|
|
id: 'my-plugin:icons',
|
|
exts: ['csv', FOLDER_EXT, FOLDER_OPEN_EXT],
|
|
names: ['package.json'],
|
|
folderNames: ['node_modules'],
|
|
priority: 5,
|
|
icon: (path: string, size: number, open?: boolean) => {
|
|
void path; void size; void open
|
|
return null
|
|
},
|
|
}
|
|
service.registerFileIcon(icon)
|
|
service.getFileIcons()
|
|
service.matchFileIcon('/p/a.csv')
|
|
service.matchFolderIcon(true)
|
|
service.matchFolderIcon(true, 'node_modules')
|
|
service.fileIcon('/p/a.csv', 14)
|
|
service.folderIcon('/p', true, 14)
|
|
|
|
/** Named state vocabulary stays importable (the pre-0.12 gap). */
|
|
const diff: SidebarDiffRef = { kind: 'worktree', path: '/p/a.ts', staged: false }
|
|
const prefs: SidebarPrefs = { ...({} as SidebarPrefs) }
|
|
const store: SidebarStore = null as unknown as SidebarStore
|
|
const toggleType: SidebarSettingToggleType = 'number'
|
|
const toggle: SidebarSettingToggle = { key: 'k', title: 'K', type: toggleType }
|
|
const declaration: SidebarSettingsDeclaration = { toggles: [toggle], pluginToggles: [toggle] }
|
|
const typeName: TabType = 'my-plugin:db'
|
|
const version: string = SIDEBAR_SERVICE_VERSION
|
|
const features: readonly string[] = SIDEBAR_FEATURES
|
|
void version; void features
|
|
const strategy: FileFetchStrategy = 'mediaUrl'
|
|
void diff; void prefs; void store; void declaration; void typeName; void strategy
|