Review feedback from the live 3384 install, fixed at the root:
- COLORS: the new stylesheet used var(--dsw-alias-accent) in 17 places — a
token the DSH theme does NOT define, so every declaration was dropped
(older files only survived because they carry fallbacks); container edges
used border-l1 (4% black, invisible). Accent now rides
--dsw-alias-state-business-primary, container edges --dsw-alias-border-l4.
- CLICKS: the canvas called setPointerCapture on pointerdown, which
retargets the derived click to the container and killed every node (and
ⓘ) interaction; jsdom cannot see this, a real browser can. Capture is
gone and panning only starts from the background.
- LAYOUT: fit gave up silently at zero container size and never retried,
centered only horizontally, and capped at 100% while a five-child level
scaled to 35%. Now: ResizeObserver + first non-zero fit, both axes
centered, a 0.78 readability floor, and layoutTasksGraphForWidth picks
the flattest sibling arrangement that still meets that floor.
- CARDS: title (one line) + mono meta + live line only; display title,
team role, full model id and latest text moved into the ⓘ popover.
- PANEL: metrics follow the approved 360x660 mockup (132x46 cards, 112 row
stride), explicit line-heights, user-select:none, horizontal control
cluster with a labelled mode button, always-visible team board strip.
- Tokens for two new popover headings across all 20 dictionaries.
Tests: 3 new page-interaction specs (click after background pointerdown,
node gestures never pan, team board visible without a click) and a
rewritten 9-case layout suite (band wrap, row reservation, width solver,
reserved live row). Full suite 1400 passed / 133 files.
The Subagent page becomes the Tasks page per the approved Variant-D design:
- tasks-model.ts feeds BOTH modes from one pre-order node list: graph
canvas (TasksGraph: layered nodes over bezier edges, drag-pan,
wheel-zoom-to-cursor, phase frames, bottom-right control cluster) and
the classic tree (TasksTree: keyboard navigable) — the view toggle and
fold toggle stay reachable in both modes.
- Workflow runs (workflows.list) hang under their origin agent; member
agents re-parent under the run node, missing ones synthesize from run
data; completed leaf agents fold into per-parent aggregate nodes
(click or cluster toggle to expand/re-collapse).
- Agent Teams (experimental): roster enriches nodes; a header chip opens
the shared task board popover with basic CAS operations (complete /
reopen / delete / reassign / create / edit); no experimental layer →
the whole block hides (structural degradation).
- Background jobs move into a bottom drawer that auto-collapses at 8+
agents (manual toggle wins); job output opens as an anchored popover
(event replay, never the model's cursor); two-click kill preserved.
- Live activity lines render as glyph + tool + args (tool-icons map onto
host primitive icons).
- Node click jumps to the transcript; ⓘ opens the detail popover;
popovers follow the selection-popup dismissal contract (Escape /
outside click / anchor off-screen via IntersectionObserver).
- tasksViewMode pref (graph default) with a select row in settings;
58 new copy keys across zh/en/ja + 18 third-language dictionaries;
dead CSS from the old tree/dock pruned.
- Tests: 6 new page-interaction specs (auto-collapse, both-mode toggle,
jump, fold, popover); jobs-view suite re-pointed at the popover;
prefs/builtins/smoke lock tests updated for the new pref.
Move the support line from 0.1.7-alpha.1 to 0.1.7-rc.1 (peer floor
`^0.1.7-rc.1`). 0.21.0-alpha.1 never reached npm, and once the code-card
labels below are fixed the same build crashes on alpha.1 (its DiffBlock
still reads the `labels.files` upstream deleted), so declaring only the
host we verify against is the honest floor — and from rc.1 on the host
enforces peers at boot anyway: a row whose `@deepseek-ai/dsh-*` peers do
not satisfy the runtime is silently disabled.
Dependencies: 28 devDependency pins and 14 peers move to 0.1.7-rc.1.
rc.1 also raised the vendored runtime (cordis 4.0.4, schemastery 3.18.4,
cordis-plugin-group 1.0.4, cordis-plugin-include 1.0.9) and publishes DSH
cross-references as exact versions, so bumping schemastery/cordis alone
left four unmet transitive peers — the three vendor packages are hoisted
into devDependencies, which is what makes `pnpm peers check` clean again.
One compile break: rc.1 added `CodeToolbarLabels` (codeLabel / wrapLabel /
unwrapLabel, all required), made DiffBlockLabels / ReadBlockLabels extend
it and deleted `DiffBlockLabels.files`. Fixing SideChatView then leaving
markdown on the legacy banner would render two different code-card styles
inside one side-chat view, so the plugin adopts the card everywhere:
`markdownTextProps()` now passes `labels.code.toolbarLabels` with the
host's own wording (codeBlock.title|wrap|unwrap, added to all 20
dictionaries; `sideChatBlockFiles` retires with the deleted footer).
Adopting the card surfaced a silent regression the suite caught: the new
toolbar shows `codeLabel` for languages the highlighter does not know, and
mermaid has no shiki grammar, so the info string no longer reaches the DOM
and the banner-text probe in mermaid.tsx matched nothing — mermaid fences
stopped being swapped for diagrams. Detection now matches the FENCE BODY
from the source that produced the render.
`tests/market-manifest.spec.ts` pins every DSH peer as a floating caret
range: an exact pin (the devDependency style) or a comparator without a
prerelease would fail rc.1's preflight and silently stop the plugin from
loading, and `pnpm peers check` cannot see that — it validates the
resolved dev tree, not the range shape the host reads.
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.
Adds the per-version adaptation record (docs/plans/2026-09-21-…-adaptation.md),
rewrites AGENTS.md §3 for the new baseline (the two silent contract breaks, the
terminal/browser handoff, the new official plugin-manager surface, the three
ui-primitives breaks, the widened primitives bare-import set), and updates both
READMEs and the integration guide for the v0.20.0 feature set.
Also retires the support text the terminal deletion made false: the install
flow is one step again (no build-script approval), the FAQ's two node-pty rows
collapse into one pointing at DSH's own terminal, the platform note no longer
promises prebuilt native binaries (there are no native deps left), and the
installers drop the -Repair mode and their allowBuilds writes along with
pnpm-workspace.yaml's now-empty allowBuilds block. Removes the dead
browserNoSandboxWarning locale key (its setting went with the plugin's browser)
and tightens theme.spec's capsule guard so a literal colour fails it.
The e2e resolver now refuses a PATH that is not on the baseline line:
this machine's PATH is 0.1.6-alpha.1, and running the mount lane against
it fails at 'the bundle was not registered' with a zero exit from the CLI —
a misleading dead end worth one explicit error instead.
The peer floor had to move: semver's prerelease rule needs a comparator on the
same [major,minor,patch] tuple, so `^0.1.5-rc.1` can never match any 0.1.6
prerelease. The floor becomes `^0.1.6-alpha.2` (which does admit 0.1.6 alpha/rc
prereleases and excludes 0.1.5), and `engines.dsh` follows. This is a deliberate
support-line break, matching how v0.19.0 dropped 0.1.2: 0.1.5-rc hosts stay on
0.19.1.
Three upstream changes actually reach this plugin and are adapted here:
- `IconSendOutline16` no longer exists in ui-primitives (only
`IconSendOutline14`).
- `TerminalBlockLabels` gained a required `noExitCode` pill text.
- `ConnectionIndicator` dropped `reconnectLabel`: the outage label is now the
one static label that already names the retry action, so the label copy moves
there, matching the host's own '连接异常,点击立即重连'.
ui-primitives still declares no `dependencies` while its bundle bare-imports
more than it used to: `diff` and `simple-icons` were previously satisfied only
transitively and had to be hoisted into devDependencies, exactly like the
anser/shiki/katex group before them. `@deepseek-ai/dsh-code-runtime` is gone
upstream (the package no longer exists) and leaves the dev tree.
The e2e fallback npx spec is pinned rather than version-less: an unpinned
`npx @deepseek-ai/dsh` resolves npm's `latest`, which is still the 0.1.5 line
this plugin no longer supports.
Baseline moves to DSH 0.1.5-rc.1 — the 0.1.5 line's first release
candidate, and the npm `latest` dist-tag now: peer floor ^0.1.5-rc.1,
devDependency pins, the CI mount-lane pin and dsh.plugin.json engines
all follow. 0.1.5-alpha.2 users stay on v0.19.0-alpha.1.
The upstream delta from alpha.2 is small: 373 changed files, mostly
version bumps, sidebar preview polish and test snapshots. Exactly two
changes touch this plugin, and only one needs work:
- `SidebarRightGuideEntry.description` is back (optional). The native
guide renders it only while it lists at most 4 entries; a longer list
drops every description. The plugin restores `TabDescriptor.description`
and the six built-in `guideDesc*` lines (20 dictionaries) verbatim from
0b6ac5d^, but deliberately does NOT restore the old generic
`nativeGuideDesc` fallback: the host has no fallback of its own, so a
descriptor that declares none now ships no `description` field at all
and renders as a clean title-only capsule.
Consequence worth knowing: the plugin contributes six guide entries, so
the default composition is over the 4-entry limit and shows titles only.
The mount lane proves the restored field end to end by shrinking the
enabled set to three types through the plugin's own settings route
(scratch profile only) and requiring the Files capsule to grow its line.
- The built-in `files` type declares a guide description again and uses a
coloured folder glyph; the plugin owns that kind, so nothing to do.
Verified unchanged from alpha.2 (so nobody re-checks them): the global
main-panel model, the file address grammar (util/workspace-path changed
only its package.json), the native tab-body host contract (.paneBody is
still a block scroller with a definite height), the core/agent/session/
subagent host APIs and the ui-primitives export surface.
Restore caveat fixed in the lane: the settings route merges patches
key-wise, so undoing a temporary `tabsEnabled` change by posting the
original (empty) map leaves the disabled keys behind — the e2e restores
every key it touched explicitly.
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.
The native new-tab list rendered one generic line ("opens in the DSH
sidebar") under every plugin entry, so six plugin rows read identically.
`TabDescriptor.description` now carries the real purpose (string or
locale-following thunk; absent falls back to the generic line), and the
six visible builtins declare theirs across all 20 dictionaries.
- service.ts: `TabDescriptor.description` + docs (guide §4.1, §0 row).
- native/: the guide entry's description follows the descriptor; the
`files` takeover reuses the editor's line.
- builtins: files / changes / tasks / side chat / terminal / browser.
- tests: builtins assert every visible type declares a distinct line;
the lifecycle test asserts descriptor lines (and the takeover reuse).
DSH 0.1.5 owns the right column (its native Sidebar hosts every tab type
this plugin registers), so the plugin's own right panel, its width drag /
new-session seed prefs, and the free-window feature are gone. The bottom
workbench is the only plugin-owned surface left, and its expand/collapse
control is registered into DSH's session-header utilities list slot (the
header's corner seat belongs to the native sidebar's own control).
- state: one `bottomSplits` tree; `makeDefaultState()` takes no arguments;
`toggleBottomPanel` / `openTabInBottomPane` are the only landing
reducers; `setBottomHeight` and `sanitizeState` share the
`CONVERSATION_MIN` cap (a persisted tall height no longer snaps on the
first drag).
- service: every landing in the workbench (creation or focus) expands it;
`SIDEBAR_FEATURES` drops `'floatWindows'`; the open/close/activate tab
pools cover the single tree.
- shell: `Sidebar.tsx` renders only the bottom workbench; the toggle lives
in `sidebar/bottom-toggle.tsx`; layout-push pushes height only (no width,
no `--dsh-sidebar-width`).
- prefs: `openByDefault` / `defaultWidthPercent` / `changesDiffFloat`
removed from the schema, defaults, client parser, settings page, and all
20 dictionaries; the retired keys in stored documents are ignored.
- tests: free-window specs deleted, panel/float assertions dropped,
remaining suites adapted (124 files / 1287 tests); the mount lane asserts
the header toggle and sweeps the native sidebar's guide page.
- docs: README / README_EN / guide §0,§11 / AGENTS test map.
Phase 2 of the 0.1.5 adaptation: the plugin stops drawing its own right
panel and contributes tab TYPES to DSH's native right Sidebar, which owns
the right column. The bottom workbench (terminal dock) stays plugin-owned.
Registrations (src/client/native/):
- every TabDescriptor becomes a native tab type (kind = descriptor.id, the
default `extension` band) plus a keyed body in `sidebar.right.pane.tab`
and a live chip title in `sidebar.right.pane.tab.title`
- the `editor` type also claims `dsh-resource://file/**`, so it outranks
the built-in `text` preview (fallback) and every file the product opens
lands in the plugin's editor
- the built-in `files` page kind is TAKEN OVER by an extension
registration of the same kind: `openTab('files')` draws the plugin's
explorer, and the built-in resumes when the plugin unregisters
- registrations follow the plugin registry + the side-card settings: a
type registered later (external plugin) gets its native type too, a
disabled type is unregistered (absent from the guide, refuses opens)
Adapter (src/client/native/tab-adapter.tsx):
- one synthetic plugin `SidebarTab` per native tab id, minted from the
native record + navigation params (or the descriptor's own createTab,
which a native guide open bypasses), kept live across navigations and
updateTab writes
- the explorer's per-tab expansion/reveal sets and the per-kind instance
counter, which the plugin's own layout used to hold
Service routing (src/client/service.ts):
- `setSurface` installs the native write face; openTab/openFile land
natively (a path becomes a resource address, a path-less editor open
becomes the `files` page, the rest carry the seed as navigation params),
closeTab/updateTab/activateTab recognise native tab ids
- new optional `OpenTabSeed.target` ('right' default, 'bottom' keeps the
bottom workbench); the bottom panel's own + menu and auto-terminal pass
'bottom'
- src/client/native/surface.ts probes the controller's per-session writes
(`openTabIn`/`openResourceIn`/`closeIn`, not in `ISidebarRight`) and
queues opens for a session whose surface is not mounted yet
Right panel retired behaviourally (Phase 3 deletes the code): Sidebar
renders its bottom workbench only, keeps the panel collapsed, and hides
the right-panel toggle. The panel-specific e2e specs are removed; the
mount lane now drives the native guide (tab sweep), the plugin explorer
inside the native pane (editor/mermaid/README chunks) and the sidechat
host routes.
Verified: pnpm typecheck / lint / build / test (126 files, 1349 tests) /
check:consumer-types, plus pnpm test:mount against a real
@deepseek-ai/dsh@0.1.5-alpha.1 host (7/7).
Move the plugin onto the DSH 0.1.5-alpha.1 baseline (peer floor
^0.1.5-alpha.1, CI pinned to the alpha dist-tag) and fix the two host
contract changes that alpha line introduced.
Baseline:
- all @deepseek-ai/dsh-* peer/dev deps -> 0.1.5-alpha.1; optional peer
@deepseek-ai/dsh-client-ui-sidebar-right (declared, unused until the
native-surface phase); devDep @deepseek-ai/dsh-agent-loop (inbox fold)
- dsh.plugin.json engines >=0.1.5-alpha.1; version 0.19.0-alpha.0;
SIDEBAR_SERVICE_VERSION follows
Live assistant stream (assistant/chunk is gone):
- 0.1.5 publishes in-flight model deltas as process-local
agent/assistant-stream frames instead of durable assistant/chunk events
and embeds the settled stream in assistant/message / assistant/attempt.
src/assistant-live.ts folds the frames into a bounded per-session buffer;
sidechat.events serves the current attempt as `live` rows (replaced every
poll, never a delta) and the transcript renders them; the inherited
in-progress snapshot reads message content / attempt stream / live frames.
Cold session reads (persistence.inspect is gone):
- 0.1.5 reads a stored session through an explicit read handle
(open(id,'read') -> handle.read() -> close()). src/session-store.ts owns
that protocol; every cold read (sidechat transcript, cold-resume preset,
session.cwd, changes replay) goes through it.
Dead code and docs:
- drop the remote.session.openWorkspacePath interception: 0.1.5's chat opens
files through ctx.sidebarRight.openResource instead, so the wrapper had no
caller and would hijack the host's "open in app" gesture; the
interceptOpenPath pref, its settings row and its locale keys go with it
- AGENTS.md §3 + external-plugin-guide.md describe the new baseline
- vitest setup bridges Node's localStorage accessor to jsdom's store (the
pre-existing 26 failures on Node >= 22)
- desktop-layout e2e seeds through host RPC and types into the Lexical
composer (0.1.5 replaced the textarea) instead of driving the directory
picker
Verified: pnpm typecheck / lint / build / test (124 files, 1327 tests) /
check:consumer-types, plus pnpm test:mount against a real
@deepseek-ai/dsh@0.1.5-alpha.1 host (18/18).
.html/.htm/.xhtml op previews (read / write / edit, non-error) gain a
Render/Raw toggle in the preview pane head: the render view loads the
SAVED file through the same /sidebar/html route the editor's html viewer
uses, so relative assets (./style.css, img/x.png) resolve inside the
route and a segmented read still renders the whole document (the route
serves the file, not the op snapshot — the file-trace-style srcDoc
approach would render a cut-off document whose scripts never run).
The frame is ALWAYS sandboxed (HTML_IFRAME_SANDBOX, moved to the shared
html-preview.ts module — opaque origin, no allow-same-origin, no
top-navigation) plus the route's CSP sandbox header as
defense-in-depth; unlike the editor viewer this surface has no
no-sandbox escape hatch. The contract is pinned by new sandbox-spec
tests on the extracted HtmlRenderPreview component and the pane toggle
(off by default; absent for non-html and error targets).
Locales: zh/en + all 19 shipped third-language overrides (key-set-equal
gate green). Typecheck + eslint clean; sandbox/redact/diff-rows/frontmatter
suites 36/36 green. The full-suite host-side failures (fs-operations /
pty smoke / git / install-powershell) are pre-existing on the base
branch (verified via stash) and unrelated.
The changes-tab op previews replay read/write/edit payloads into the DOM
(diff rows, read rows, markdown reading mode, error text) — when the model
reads a credentials file, that plaintext must not land on screen or in
inspectable DOM. New display-only redaction layer, on by default with a
one-click pane toggle (persisted in localStorage):
- path layer: credential-shaped filenames (.env / *secret* / *credential* /
*token* / *api-key* / private keys...) mask whole files line-wise
- content layer: ordinary files mask only secret-shaped spans — secret
field assignments keep the field name (api_key: [REDACTED]), plus bare
token prefixes (sk- / AKIA / ghp_ / xox / ya29.), Bearer headers, and
PEM private-key block headers
- redacted payloads flow through the same viewOp shape into diff rows,
read rows, markdown reading mode, and error text
Known edge (documented in README): an unquoted mermaid node label
containing a redacted secret breaks the diagram and falls back to source;
workaround is quoting the label. Session data is untouched — this is
display-only and not a boundary against same-context malicious plugins.
Locales: zh/en dictionaries + all 19 shipped third-language overrides
(key-set-equal gate green). 9 new unit tests; typecheck clean; targeted
suites 40/40 green. The fs-operations failure is pre-existing on the base
branch (environment-dependent, verified via stash) and unrelated.
Catalog entry names (15 tab + 4 viewer) and the dsh-desktop shell preset
desc were raw hardcoded zh strings rendered straight into the add-plugin
modals and the title-bar scheme dropdown. Mirror the existing desc
pattern exactly: PluginEntry.name / ShellPreset.desc widen to the
i18n-friendly string-or-() =>-string union, the catalogs carry lazy
t('pluginXxxName') / t('presetDshDesktopDesc') lookups, and the
add-plugin modal (render, search filter, aria-labels) plus the scheme
SelectMenu resolve them like every other copy. The plain-string arm of
the union stays as the raw-name fallback, and t() itself never yields
empty text (missing keys render the key), so a dict gap can never blank
a name.
20 new dictionary keys added to zh/en (locales.ts), ja (locales-ja.ts),
and all 18 third-language dictionaries — tests/locales.spec.ts enforces
key-set equality against zh for every shipped dict (precedent: 9cc642b),
so the 'leave other locales-* untouched' shortcut would fail the hard
parity gate; real translations are provided instead.
Spec follow-ups (behavior change: names/descs now render through t()):
- plugin-list.spec.ts: name non-empty assertion resolves the union.
- add-plugin-modal.spec.tsx: sentinel/office name assertions resolve
from the catalog (en locale in tests) instead of pinning zh literals.
- shell-presets.spec.ts: desc non-empty assertion resolves the union.
The changes tab preview pane gains a Reading/Raw toggle for .md/.markdown/.mdx session ops (read, write, and edit alike). Reading mode renders the resulting document through the shared MarkdownText pass (the same stack as the editor preview): GFM tables with alignment, task lists, strikethrough, footnotes, math, and code fences, with local image destinations rewritten through the session-scoped /sidebar/file media route (rewriteLocalImageUrls). Content sources: read -> parseReadContent (envelope stripped, blank lines preserved — markdown block structure depends on them, which parseReadLines drops); write -> the written content; edit -> the reconstructed resulting document from the in-window prior content when known, else the new snippet. Ported from dsh-file-trace v0.2.0 (PR #471 lineage), re-based onto the unified changes tab (#475) and the repo shared-renderer conventions (markdownTextProps labels contract, skin tokens, 21-dictionary locale parity).
The core client bundle statically imported every third-language
dictionary (ja/de/fr/…/zh-HK/MO/TW, ~640KB of source — roughly half
the bundle's weight) for a code path most sessions never enter: the
sidebar's own t() only consults zh/en plus the better-locale override
store, and the dicts' ONLY consumer is that store's register() call.
They now ride the existing lazy-chunk machinery as lib/client-locale.js
(fetched from /sidebar/bundle only when better-locale is actually
installed; a generation counter keeps the async registration moot when
the integration effect re-runs or disposes mid-flight). Until the chunk
lands, t() keeps the zh/en chain; the store's own revision bump on
register re-renders the chrome once the dicts arrive.
Core client.js: 1.46MB → 822KB (-44%). The chunk-boundary key-set
check moves to the existing runtime test (every shipped dict stays
key-set-equal to zh).
Rendering-layer upgrade for the Side Chat transcript (data already rides
the raw events; the mapping just stopped discarding it):
- Tool rows with a derivable structure now render the host's
ui-primitives atoms instead of a generic double <pre>: bash calls map
onto TerminalBlock (command from call args, output + exit pill
recovered from the result's trailing exit marker), edit/write map onto
DiffBlock (call-time literal replacement, refined by the result meta's
contextual hunks), read maps onto ReadBlock (line-window meta, same
semantic narrowing the host applies). Every narrowing declines to the
generic row on malformed input.
- turn/end now emits a turnSummary row: token usage aggregated from
assistant/message.usage (output summed across steps, input from the
last request) plus the envelope-time wall duration, printed with the
main conversation's formatTokens/formatDurationMs cadence.
- The client entry injects 'connection' (DSH 0.1.2-alpha.2+) and the
thread view shows the host's ConnectionIndicator while the Remote
transport is down; the reconnect control calls reconnect() and a
disconnected -> connected transition pulls the transcript immediately
instead of waiting for the next poll tick.
19 new locale keys propagated across zh/en and all 19 third-language
dictionaries; unit coverage in sidechat-transcript.spec and a new
sidechat-view render spec (jsdom). Real-host mount smoke: 14/14.
Registers dsh-bilingual-reader in the better-sidebar add-tab-plugin catalog:
native-PDF reading with LLM selection translation, isolated from the main
conversation. Adds the catalog entry plus pluginBilingualReaderDesc across
all 20 locale dictionaries (zh/en source + 18 mirrors).
- New persisted built-in pref workspaceFence (default true) under the
files card's secondary settings modal; host reads it per request via
the settings seam (fenceEnabledOf — absent/malformed stays ON).
- ensureWorkspacePath/ensureWorkspaceWritePath gain a fence param:
disarmed skips the containment assert but keeps realpath resolution;
wired through fs.tree/fs.read/fs.write, the media + html routes, and
the upload path (writeWorkspaceUpload fence input).
- Editor load failures and file-tree level errors show the localized
reason plus a one-click global off (FenceErrorNotice) that flips the
pref, adopts it into the store, and auto-retries the failed operation
(reloadSeq / retryDir) — the raw wire text is never shown.
- GitView's two client-side pre-checks (hidden open action + no-op
defense) respect the pref; store threaded through TreePanel/FileTree.
- i18n: 4 new keys across all 21 dictionaries; tests: fence-off route
coverage, prefs parsing, schema default, editor toggles list;
docs: guide §8/§8.1 + plans/2026-08-29 design record.
M2 of pinned terminals (v0.17.0):
- TabBar: onPinTab prop (optional, hidden when unset for legacy callers)
- Terminal tabs get a 'Pin ▸' submenu (workspace/global) or 'Unpin' row
- UI vs Agent label distinction via isAgentTabId
- Non-terminal tabs get no pin entry (5-item legacy menu preserved)
- WorkbenchActions.pinTab threaded through split-pane LeafView
- Sidebar.onPinTab wires store.reduce(setTabPin) with cwd snapshot
- 12 new i18n keys synced across all 19 language dictionaries
Opening the source-control panel could freeze the whole sidebar, and the
frozen layout restored itself after every reload, leaving no way out
(issue #369). Three unbounded layers compounded:
- `git status --porcelain -z --untracked-files=all` shipped every row of a
pathological untracked set into one response; the browser main thread
froze on response.json() plus the unvirtualized change list, and the 2s
poll replayed the freeze. status() now caps entries at 2000 and reports
`truncated` (the panel shows a notice, mirroring fs.read's truncation
semantics); worktree change counts inherit the bound for free.
- repoRoots() probed every visible child directory of a non-repo cwd
(e.g. ~) serially, once per git.* call, each with the full 30s command
budget. Discovery now uses a 3s probe timeout, caps probes at 200
directories, and shares one in-flight scan plus a 60s TTL cache per cwd.
- A hung layout persisted `panelOpen: true` with the git tab active, so a
reload remounted straight back into the freeze with no escape hatch.
Loading with `?dsh-sidebar-reset` now drops the persisted layout (and
the shared width) and starts from the default.
statusTruncated copy synced across all 19 shipped dictionaries.