feat(dsh): adapt to DSH 0.1.5-alpha.2

Baseline moves to DSH 0.1.5-alpha.2 only (peer floor ^0.1.5-alpha.2,
CI pin, manifest engines); alpha.1 users stay on v0.19.0-alpha.0.

Host-contract changes handled:
- the native guide entry lost its `description` field (entries are
  icon+title capsules now): `TabDescriptor.description` and the six
  `guideDesc*` keys plus the `nativeGuideDesc` fallback are removed.
- the file address grammar changed: `fileAddressFor` always returns a
  session-scoped address (absolute paths keep their leading `/`),
  `parseFileAddress` is prefix-based and strips `?`/`#`. The plugin's
  purity-gated mirror follows.
- global main panels replaced the `conversation` root slot with the
  keyed `main` slot (`main.conversation`): the bottom workbench's
  center-column locator now accepts both keys and skips
  `display: contents` slot hosts.

Fixes a pre-existing fill bug the alpha.2 probe made obvious: the native
tab body host is a block scroller with a definite height, so tab roots
that only declared `flex: 1` collapsed to content height and the side
chat composer drifted off the pane bottom. The native adapter now wraps
every tab body in a `height: 100%` column flex host.

Also promotes the dependencies `dsh-client-ui-primitives@0.1.5-alpha.2`
dropped from its manifest (anser / shiki / mdast-util-* / micromark-* /
katex / @shikijs/langs) and the newly unmet `dsh-session-persistence`
peer into devDependencies.
This commit is contained in:
Menghuan1918
2026-09-09 23:09:48 +08:00
parent d4765819c0
commit 0b6ac5d267
43 changed files with 944 additions and 930 deletions
+1 -14
View File
@@ -43,7 +43,7 @@ interface NativeTabRegistry {
priority?: 'extension' | 'builtin' | 'fallback'
canOpen?: (address: string) => boolean
title: (address: string) => string
guide?: readonly { order: number; title: () => string; description: () => string; icon?: unknown }[]
guide?: readonly { order: number; title: () => string; icon?: unknown }[]
}): () => void
}
@@ -63,17 +63,6 @@ function titleOf(descriptor: TabDescriptor): string {
return typeof descriptor.title === 'function' ? descriptor.title() : descriptor.title
}
/**
* The descriptor's guide line, evaluated fresh for the current locale. The
* host's new-tab list renders one line per entry; without a declared
* description every plugin page would read the same generic sentence.
*/
function descriptionOf(descriptor: TabDescriptor | undefined): string {
const description = descriptor?.description
if (description === undefined) return t('nativeGuideDesc')
return typeof description === 'function' ? description() : description
}
/**
* The guide row's glyph for a descriptor icon (nothing when it has none).
* The native guide renders `entry.icon`, so a takeover registered without one
@@ -192,7 +181,6 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
guide: [{
order: descriptor.order ?? 100,
title: () => titleOf(descriptor),
description: () => descriptionOf(descriptor),
...guideIconOf(icon),
}],
}),
@@ -219,7 +207,6 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
guide: [{
order: 10,
title: () => t('files'),
description: () => descriptionOf(editor),
// The takeover IS the editor descriptor's page, so it carries the
// editor's glyph: without it the "Files" row is the only guide
// entry with an empty icon slot.
+39 -23
View File
@@ -281,33 +281,49 @@ export function NativeTabBody(props: NativeBodyInjected & NativeBodyFrameworkPro
})
useEffect(() => () => { records.drop(nativeTab.id) }, [records, nativeTab.id])
if (descriptor === undefined) {
return createElement(OrphanedTab, { ctx, store, scope, tab: view.tab, visible: nativeTab.visible })
// The orphaned fallback sits in the SAME native host as a live body, so
// it gets the same full-height box (its own root also relies on the
// `flex: 1` contract the column host restores). The host div carries
// `data-dsh-native-tab-host` (empty value) so the e2e lane can assert
// the fill, mirroring `data-dsh-better-sidebar`.
return createElement(
'div',
{ className: css.nativeTabHost, 'data-dsh-native-tab-host': '' },
createElement(OrphanedTab, { ctx, store, scope, tab: view.tab, visible: nativeTab.visible }),
)
}
return createElement(
RenderBoundary,
{ className: css.tabBoundaryError },
createElement(descriptor.component, {
ctx,
store,
scope,
tab: view.tab,
visible: nativeTab.visible,
expanded: view.expanded,
revealed: view.revealed,
onToggleDir: (path: string) => { records.toggleExpanded(nativeTab.id, path) },
onReferenceFile: (path: string, isDir: boolean) => { referenceInChat(ctx, sessionId, cwd, path, isDir) },
onOpenDiff: (tab: SidebarTab) => {
service.openTab({
type: 'diff',
title: tab.title,
id: tab.id,
...(tab.diff === undefined ? {} : { diff: tab.diff }),
}, scope)
},
onSubagentJump: (childSessionId: string) => {
service.openTab({ type: 'subagent', meta: { childSessionId } }, scope)
},
}),
// The full-height host wrapper (see the `.nativeTabHost` rule in
// sidebar.module.css for the native `.paneBody` contract); its
// `data-dsh-native-tab-host` attribute lets the e2e lane assert the fill.
createElement(
'div',
{ className: css.nativeTabHost, 'data-dsh-native-tab-host': '' },
createElement(descriptor.component, {
ctx,
store,
scope,
tab: view.tab,
visible: nativeTab.visible,
expanded: view.expanded,
revealed: view.revealed,
onToggleDir: (path: string) => { records.toggleExpanded(nativeTab.id, path) },
onReferenceFile: (path: string, isDir: boolean) => { referenceInChat(ctx, sessionId, cwd, path, isDir) },
onOpenDiff: (tab: SidebarTab) => {
service.openTab({
type: 'diff',
title: tab.title,
id: tab.id,
...(tab.diff === undefined ? {} : { diff: tab.diff }),
}, scope)
},
onSubagentJump: (childSessionId: string) => {
service.openTab({ type: 'subagent', meta: { childSessionId } }, scope)
},
}),
),
)
}