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.
New global Side card setting `agentOpenTools` (default off, 常规 group):
when enabled, one `sidebar_open` tool is injected into the model so it can
actively open a local file, a local folder (tree rooted there), or an
HTTP(S) page in the calling session's sidebar.
- host: AgentOpenRegistry + registerOpenTool (src/agent-opens.ts); the tool
classifies http(s) URLs / stat-driven file/folder paths, refuses other
schemes, resolves relative paths against the session cwd, and reports
target-tab-disabled instead of silently no-oping. Gated like
agentTerminalTools (register/drain on settings watch).
- delivery: /sidebar/ws/agent-opens push (consume-on-send queue, replay on
attach); the tool result reports `delivered` so queued opens for
non-active sessions are honest.
- client: Sidebar subscribes and routes pushes to editor (file), folder
window (meta.dir, tree rooted at the folder — EditorHost), or browser
(URL) tabs; settings row in 常规 + 20 locale dictionaries.
- tests: agent-opens registry/tool spec, smoke gating + WS route list,
prefs/plugin-shape defaults, editor-host folder window, side-card rows.
- docs: design doc, AGENTS.md key lists + reference, README feature bullets.
PR #285 added the zh/en 'worktree' key but the 19 third-language dicts
(de/fr/pt/ko/ar/hi/id/tr/vi/th/ru/it/nl/sv/pl/zhHK/zhTW/zhMO) were missing
it — only ja was patched in the prior commit. The dicts are typed
Record<keyof typeof zh, string> via an unchecked 'as' cast, so tsc did
not catch the gap.
This commit:
- adds the worktree translation to all 19 dicts
- strengthens tests/locales.spec.ts to assert key-set equality for EVERY
shipped dictionary (not just zh/en/ja), preventing future regressions