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.
The reveal tint now uses the DSH business-blue surface
(--dsw-alias-state-business-tertiary, the same source the host's nav
items use for their active accent, skin-aware) instead of a neutral
accent wash, and drops the 2px left bar. Consecutive revealed rows
square off their touching edges (sibling combinator + :has), so a
multi-file highlight renders as ONE rounded block — per-row radius
notches no longer fragment it; a solo row keeps the full radius.
A reveal on a deep tree shifted the whole panel (tab bar included) out
of the viewport: the reveal used native scrollIntoView, which scrolls
every scrollable ancestor, and the fixed panel host
([data-dsh-panel-host], overflow hidden) is still a programmatically
scrollable container — the host scrolled instead of (or besides) the
tree, with no way to scroll back. Reported on DSH 0.1.2-alpha.1.
Two layers:
- FileTree: compute the centered, clamped target and scrollTo the tree
body only; scrollIntoView is never called.
- panel host: overflow hidden -> clip. A hidden box remains
programmatically scrollable; clip keeps the same viewport-edge
clipping (collapsed panels sliding off-screen) while making the host
impossible to scroll, killing the whole leak class (md-toc jumps,
future consumer scrollIntoView calls).
AGENTS.md (546 -> 63 lines) now carries only project-global development
rules: hard constraints, mount-smoke CI, DSH 0.1.2-alpha.1 adaptation
notes, npm release, a quick-rule list, and a docs/tests map. The
consumer plugin API reference lives solely in
docs/external-plugin-guide.md.
Reference sync:
- README/README_EN: collapse the dual 'full integration docs' links to
the guide; AGENTS.md stays linked as the repo rules
- shell-presets.ts / mount.e2e.ts comments: skinning contract pointer
moved from AGENTS.md §8 to guide §12
Dual-version compatibility: the plugin now works identically on
0.1.0-rc.8 ~ 0.1.1-rc.2 and 0.1.2-alpha.1 (the latter verified against a
source build of the dsh-v0.1.2-alpha.1 tag: mount smoke 14/14 + aggregate
double-mount; 0.1.1-rc.2 re-verified 14/14).
- MarkdownText: alpha.1 renamed the flat optional `codeLabels` prop to a
required nested `labels: { code: {copyLabel, copiedLabel}, footnotes }`;
the old prop alone crashes fence rendering with "reading 'code'". All
four render sites (mermaid / MarkdownHtml / TextEditor / SideChatView)
now go through src/client/markdown-labels.tsx (dual-shape object passed
under both prop names).
- e2e mount lanes speak both host dialects (tests/e2e/host-protocol.ts +
host.ts, unit-locked by tests/e2e-host-protocol.spec.ts):
- one-time-token browser auth: the launch URL grep extends past the
`/?token=` query (truncating at `/` loses the token), goto navigates
the token URL (browser cookie exchange), and stamped navigations
pre-seed the context cookie via addCookies — the exchange 303 drops
sibling query params;
- Remote gateway: dot endpoints first, slash endpoints on 404 fallback,
with args keyed by the controller parameter name (workspace/create ->
{request}, session/list -> {_request}; verified on a live host).
- package.json: drop the @deepseek-ai/dsh-client-runtime peer (package
removed upstream in alpha.1; zero references in the source — the chunk
externals entry stays and is resolution-tolerant).
- scripts/e2e-mount.sh: fix a latent set -u crash in the npx fallback
branch ($DSH_CMD followed by a full-width comma parsed as part of the
variable name).
- CI pin stays at @deepseek-ai/dsh@0.1.1-rc.2 until alpha.1 publishes to
npm (comment updated); README/AGENTS document the new contracts.
- Version lockstep bumped to 0.17.1.
1. getSessionStates() returns defensive copy (new Map) — callers can't
mutate the live bySession map at runtime
2. closePinnedInHome passes home cwd to ptyClose — the host resolves
the PTY in the correct workspace container (same scope the WS open
used); previously only sessionId was passed
3. setTabPin walk uses findIndex + conditional slice instead of map() —
only the leaf/path containing the target tab gets a new array;
idempotent no-ops return the same tab reference (=== holds)
4. setTabPin guards non-terminal tabs (tab.type !== 'terminal' early
return in apply); sanitizePersistedTab strips pin from non-terminal
tabs on hydration — keeps the terminal-only invariant defensive
TabBar's pin icon now shows for both virtual pinned tabs (injected
from other sessions) AND the home session's own pinned terminal tabs
(those with tab.pin !== undefined). Previously only virtual tabs got
the icon — the home tab looked like a regular terminal despite being
pinned.
M4 of pinned terminals (v0.17.0):
- New PinnedRail component: compact strip above the tab bar, renders
pinned terminals from other sessions (pin icon + terminal icon + title
+ tooltip with kind/scope/cwd)
- Click navigates to the home session (ctx.sessions.open) and activates
the tab (or raises its float) via the pinnedJumpRef effect
- Right-click menu: Unpin / Close Terminal; middle-click close
- IconPinOutline16 added to icons.tsx
- Rail styles in sidebar.module.css (token-driven, no-drag opt-out)
- Sidebar: collectPinnedTabs on store snapshot + session list, railRevision
state forces re-render after reduceFor (which doesn't notify)
- Cross-session close: reduceFor(closeTab/closeFloatByTab) + ptyClose/
agentPtyClose; cross-session unpin: reduceFor(setTabPin null)
M3 of pinned terminals (v0.17.0):
- reconcileAgentTerminals skips removal of agent tabs with pin !== undefined
(the agent closed them / pty exited, but the user pinned them)
- The xterm reconnect-failure banner is the user-visible 'disconnected'
signal (design M3 convergence: no title suffix, no meta write)
- A later push reviving the same uuid is a no-op (id already exists)
- Unpinned agent tabs still remove as before; toAdd path unaffected
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.
Adapted to HEAD's unified editor+explorer tab: the reveal gesture now
opens the editor home tab (the files window) instead of the deleted
explorer type. PR's revealInExplorer implementation, produced-files
engine-data selector, and tests are retained verbatim.