feat: adapt to DSH 0.1.7-alpha.1 and yield the read-only previews
DSH 0.1.7-alpha.1 is 1299 commits past 0.1.6-alpha.2 and rewrites four
contracts this plugin was built on. The peer floor moves to
^0.1.7-alpha.1 (semver's prerelease rule means ^0.1.6-alpha.2 can never
match a 0.1.7 prerelease); 0.1.6-alpha.2 and earlier stay on v0.19.1.
Released as 0.21.0-alpha.1 (dist-tag alpha).
Hard breaks:
- dsh-settings was rewritten: register(ns, schema) is gone, a form is
addressed by the plugin row's Loader entry id and read from the
module's exported Config. The preferences are merged into Config and
marked volatile — which requires DSH's own schemastery, because only
that build wraps a volatile field in the cosmokit reference the
loader's volatile commit walks. With the public package a settings
write reports success and revision 1 while the effective value never
changes.
- ui-primitives renamed its whole icon family (IconXxx14/16 ->
IconXxxRegular/Medium): 26 imported symbols, 17 files. The failing
mode is render-time, not load-time. Five call sites that relied on the
old 14px default now pass size={14} explicitly.
- Session format v4 rejects source.kind === 'plugin'; the side-chat
boundary injection now writes plugin:dsh-better-sidebar, the same kind
DSH's own v3->v4 migration produces, so historical threads survive.
Tool results moved to role 'tool' with top-level content/isError; all
four parsers accept both shapes.
- The client session snapshot dropped subagentsByParent/jobsBySession
and the on-demand subagent-catalog observation API. The Tasks page
reads projectionsBySession and a new jobs.list route.
Yields and convergence:
- The read-only previews go to DSH's documentpreview (Office->PDF, a
spreadsheet worker, zoom, per-directory refresh); editor.canOpen
refuses those 26 extensions. Markdown, HTML (with its sandbox
switches) and the editable code buffer stay.
- The host browser kind is desktop-profile-only now, so the web e2e
lanes expect no browser entry and assert its absence.
- Link takeover claims only urlTarget links and passes the rest to the
host, fixing a regression from the 0.1.6 round: the fallback pointed
at a 'browser' type the plugin no longer registers, so http links in
its own markdown did nothing.
Also fixed, found along the way:
- The file tree refreshes live (new directory watch over the plugin's
own socket), since the plugin owns the `files` kind.
- ctx.sessions.list has no `current` field at either tag; the three
readers were dead. They now use ctx.sidebarRight.mounted.
- package.json.files was missing lib/client-locale.js.
- Two genuinely dead call sites: sessions.openSubagent/open/
refreshSubagents do not exist on the current baseline, so the
topology click, the jump-back and the retry were all no-ops.
- The test harness window shim lacked addEventListener, which the 0.1.7
primitives call at module-evaluation time; and the e2e token exchange
needs Playwright's request stack, not Node's fetch.
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
* from the chat's http(s) links) owns embedded pages. See
|
||||
* docs/plans/2026-09-21-dsh-0.1.6-alpha.2-adaptation.md.
|
||||
*/
|
||||
import { IconCodeOutline16, IconPanelLeftOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import { IconCodeOutlineRegular, IconPanelLeftOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import {
|
||||
changesTabIcon, filesTabIcon, sidechatTabIcon, tasksTabIcon,
|
||||
} from './tab-icons.tsx'
|
||||
@@ -27,7 +27,7 @@ import { consumeSidechatSeed, SideChatView, sidechatThreadIdOf } from '../SideCh
|
||||
import { api } from '../api.ts'
|
||||
import type { TabDescriptor } from '../service.ts'
|
||||
|
||||
/** The 6 built-in tab descriptors. */
|
||||
/** The 5 built-in tab descriptors (the host owns terminal and browser). */
|
||||
export function builtinTabs(): readonly TabDescriptor[] {
|
||||
return [
|
||||
{
|
||||
@@ -57,13 +57,13 @@ export function builtinTabs(): readonly TabDescriptor[] {
|
||||
options: [
|
||||
{
|
||||
value: true,
|
||||
icon: (size: number) => <IconPanelLeftOutline16 size={size} />,
|
||||
icon: (size: number) => <IconPanelLeftOutlineRegular size={size} />,
|
||||
title: () => t('editorExplorerMerged'),
|
||||
desc: () => t('editorExplorerMergedDesc'),
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
icon: (size: number) => <IconCodeOutline16 size={size} />,
|
||||
icon: (size: number) => <IconCodeOutlineRegular size={size} />,
|
||||
title: () => t('editorExplorerSplit'),
|
||||
desc: () => t('editorExplorerSplitDesc'),
|
||||
},
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
/**
|
||||
* The 6 built-in file viewer descriptors: every preview surface is a
|
||||
* registered viewer (image / pdf / markdown / html / code /
|
||||
* binary-download), exactly like external plugins register theirs. Office
|
||||
* previews (.docx / .xlsx / .pptx) are NOT built in anymore — they moved to
|
||||
* the recommended office plugin (see plugins-viewers.ts), which registers
|
||||
* the same ids through this service.
|
||||
* The 3 built-in file viewer descriptors (markdown / html / code), exactly
|
||||
* like external plugins register theirs.
|
||||
*
|
||||
* The `binary-download` viewer sniffs NUL bytes via `detect` for unknown
|
||||
* binaries and serves legacy doc/xls/ppt by extension; `code` is the
|
||||
* catch-all (`exts: []`, lowest priority) that claims any file no other
|
||||
* viewer did.
|
||||
* DSH 0.1.7 ships `ui-sidebar-documentpreview`, its own code / spreadsheet /
|
||||
* office / pdf / image / html / markdown / text previews with zoom and
|
||||
* auto-refresh, so this plugin yields every READ-ONLY preview it used to own
|
||||
* and keeps only the surfaces where it is not equivalent:
|
||||
* - `markdown` — this plugin's own renderer (front matter, embedded HTML
|
||||
* sanitation, floating TOC), which the user prefers to the host's;
|
||||
* - `html` — the sandboxed iframe preview plus the host-less
|
||||
* `htmlViewerNoSandbox` / `htmlViewerDefaultUnsafe` escape hatches;
|
||||
* - `code` — the catch-all (`exts: []`, lowest priority) that claims any
|
||||
* file no other viewer did, and it is an EDITABLE CodeMirror with save —
|
||||
* the host's equivalents are read-only previews.
|
||||
* The yielded ids (image / pdf / binary-download) are deliberately NOT
|
||||
* registered here; an external plugin may still claim them through this
|
||||
* service. Office previews (.docx / .xlsx / .pptx) were already not built
|
||||
* in — they live in the recommended office plugin (see plugins-viewers.ts),
|
||||
* which registers the same ids through this service.
|
||||
*
|
||||
* The heavy viewers (the CodeMirror-backed markdown/html/code) render
|
||||
* through {@link lazyChunkComponent} wrappers — their libraries are fetched
|
||||
* only when such a file is first opened (see chunk-loader.ts). The
|
||||
* descriptor metadata (id/exts/priority/detect) is identical either way,
|
||||
* so matching semantics and external-plugin overrides are unaffected; the
|
||||
* descriptor metadata (id/exts/priority) is identical either way, so
|
||||
* matching semantics and external-plugin overrides are unaffected; the
|
||||
* `component` wrapper keeps the descriptor contract `(props) => ReactNode`.
|
||||
*
|
||||
* Every viewer carries the declarative settings-surface fields — `title`
|
||||
* and `icon` — so the Side card settings page can render the enable/disable
|
||||
* inventory without hardcoding (eating our own dogfood).
|
||||
*/
|
||||
import { IconCodeOutline16, IconDownloadOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import { IconCodeOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import { lazyChunkComponent } from '../lazy-chunk.tsx'
|
||||
import { PdfView } from '../PdfView.tsx'
|
||||
import { BinaryDownload } from '../binary-download.tsx'
|
||||
import {
|
||||
IconImageOutline16,
|
||||
IconMarkdownOutline16,
|
||||
IconPdfOutline16,
|
||||
IconHtmlOutline16,
|
||||
} from '../icons.tsx'
|
||||
import type { ComponentType } from 'react'
|
||||
import type { FileViewerDescriptor, FileViewerProps } from '../service.ts'
|
||||
import { t } from '../locales.ts'
|
||||
import css from '../sidebar.module.css'
|
||||
|
||||
/**
|
||||
* Lazy wrapper over the chunk-resident viewer component. The `pick`
|
||||
@@ -45,31 +48,9 @@ import css from '../sidebar.module.css'
|
||||
*/
|
||||
const LazyTextEditor = lazyChunkComponent<FileViewerProps>('editor', (mod) => mod.TextEditor as ComponentType<FileViewerProps> | undefined)
|
||||
|
||||
/** The 6 built-in file viewer descriptors. */
|
||||
/** The 3 built-in file viewer descriptors. */
|
||||
export function builtinViewers(): readonly FileViewerDescriptor[] {
|
||||
return [
|
||||
{
|
||||
id: 'image',
|
||||
title: () => t('viewerImage'),
|
||||
icon: (size: number) => <IconImageOutline16 size={size} />,
|
||||
exts: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif'],
|
||||
fetchStrategy: 'mediaUrl',
|
||||
component: ({ mediaUrl: url, title }) => (
|
||||
<div className={css.editorImageWrap}>
|
||||
<img className={css.editorImage} src={url} alt={title} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'pdf',
|
||||
title: () => t('viewerPdf'),
|
||||
icon: (size: number) => <IconPdfOutline16 size={size} />,
|
||||
exts: ['pdf'],
|
||||
fetchStrategy: 'mediaUrl',
|
||||
component: ({ scope, path, title }) => (
|
||||
<PdfView scope={scope} path={path} title={title} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'markdown',
|
||||
title: () => t('viewerMarkdown'),
|
||||
@@ -103,23 +84,11 @@ export function builtinViewers(): readonly FileViewerDescriptor[] {
|
||||
{
|
||||
id: 'code',
|
||||
title: () => t('viewerCode'),
|
||||
icon: (size: number) => <IconCodeOutline16 size={size} />,
|
||||
icon: (size: number) => <IconCodeOutlineRegular size={size} />,
|
||||
exts: [],
|
||||
priority: -100,
|
||||
fetchStrategy: 'fsRead',
|
||||
component: (props) => <LazyTextEditor {...props} />,
|
||||
},
|
||||
{
|
||||
id: 'binary-download',
|
||||
title: () => t('viewerBinary'),
|
||||
icon: (size: number) => <IconDownloadOutline16 size={size} />,
|
||||
exts: ['doc', 'xls', 'ppt'],
|
||||
priority: -50,
|
||||
fetchStrategy: 'binary-download',
|
||||
// NUL probe: a file whose head bytes contain a NUL is binary — claimed
|
||||
// before the catch-all code viewer on the head re-match.
|
||||
detect: (_path, head) => head.includes(0),
|
||||
component: ({ scope, path }) => <BinaryDownload scope={scope} path={path} />,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user