Releases: jsdvjx/dsh-strata
Release list
v0.12.1 — the map no longer covers the settings dialog
Fixes #3: the minimap's rail, dots and clue wall could paint over the host's settings dialog.
What changed
- The map steps out while a modal dialog is up. The map lives in the host's frame-wide overlay layer (
z-index: 20), while the settings dialog renders in place inside the sidebar. Once anything opens a stacking context on that column (a theme, another plugin), the dialog'sz-index: 1000is trapped below the layer, and with the right-hand panel open, which puts the rail inside the dialog, the map was drawn on top of it. No z-index can get a layer entry under such a dialog, so the rail and the whole wall now govisibility: hidden(layout untouched) while anyaria-modaldialog is open, and come back when it closes. Pure CSS (:has()), no JS state.
Reproduced and verified on an isolated DSH 0.1.5-rc.1: before, the rail hit-tested above the dialog; after, the dialog is on top, and closing it brings the map back, clickable. An open wall hides completely under a modal and returns after it closes.
dsh plugin --profile web add dsh-strata@0.12.1v0.12.0 — adapted to DSH 0.1.2-rc.1
Adapted to DSH 0.1.2-rc.1 (the current latest) while staying compatible with 0.1.1-rc.2 — verified on a live 0.1.2 instance (27k-event session) and an isolated 0.1.1-rc.2; both run the replay suite green.
What changed
- Labels through the host's locale service. The built-in zh/en table is registered as the
dsh-stratanamespace and every label is translated throughctx.locale.bind, so a language switch in Settings → General relabels the rail, the dots, the switcher and the wall in place — no reload — and a language pack can translate the plugin into a third-party language (new in 0.1.2). A host without the service keeps the built-in table keyed on the document language. - The load chain survives 0.1.2's pager lifecycle. The host unmounts the "load older" control while a page is in flight and re-creates it after the rows land, in a later commit; a measure taken between the two saw rows and no pager, and a 全 or unloaded-card jump ended "incomplete" part-way. A vanished pager now gets 600 ms to return, and progress is measured by anchored rows landed — the topmost key alone missed a page that filled in a turn's older steps below that turn's folding control.
- The dot column owns its pointer. 0.1.2's content-width handle (40 px, col-resize, hover bar) sits under the dot column at laptop widths; a gap between two dots no longer shows the resize cursor or resizes the column on drag.
- Manifest names
@deepseek-ai/dsh-client-ui-chatindsh.client.inject(a soft ordering hint; hosts without the package skip it). README gains a compatibility table.
| DSH | Status |
|---|---|
| 0.1.2-rc.1 | tested — folding, turn navigator, width handle, locale switch, pager lifecycle |
| 0.1.1-rc.2 | tested — every seam degrades to the old behaviour |
dsh plugin --profile web add dsh-stratav0.11.0 — correctness and performance pass
A correctness and performance pass over the whole map, measured on a live 1,879-row session (DSH 0.1.1-rc.2) and a live 0.1.2-rc.1 with turn folding on. Every item was reproduced before and verified after through the headless replay.
Fixed
- Every tool call was painted twice. The tool renderer nests an anchor of its own (
call:<id>) inside the flow row, and the map took both for rows — a phantomunknownband over every tool tick and a duplicate error dot on every failed one (633 of 1,877 bands on the long session were phantoms). Only the outermost anchored element is a row now. - Turn folding (DSH 0.1.2, Compact mode). A folded turn hides its rows in place (
hidden), which the map did not watch: a collapse left zero-height bands still painted at their floor height, with their error dots. A row hiding or showing itself is structure now, hidden rows stay under size observation, and a band that collapses to nothing triggers the measure. - A page that folds the transcript no longer reads as a stall. Progress was scroll height; the page that completes the history switches folding on and the transcript ends up shorter, so an unloaded-card jump ended amber "incomplete" without landing. Progress is the topmost row's identity now.
- The host's turn rail is not the pager. Fully loaded, the positional fallback for the "load older" control picked a turn-navigator mark above the first row: the ⌃ stayed on and an auto-page near the top would have clicked a turn mark. Navigation chrome is excluded; position alone must be unambiguous.
- 近/中 painted sixty frames a second, idle, after any history load. The rescale morph is only displayed in 全 and the zoomed early return sat before its expiry, so it stayed armed forever. It expires on the clock in every mode (1 paint per 3s idle, as designed).
- A chain that finishes at once no longer leaves a phantom "active" record; a zoom goal that throws (scrollport gone) ends amber instead of pulsing forever; a retired export cancels its inflate reader; attribute churn outside rows no longer forces a full measure; a stood-down map leaves the accessibility tree; the README no longer documents the preview card removed in 0.7.3.
Performance
- Streaming while scrolling no longer measures the whole transcript on every growth — 27 of 27 growth bursts did, because the row observer runs after layout and a scroll-scheduled frame saw the growth first. The observer gets that frame now: 0 of 27.
- Hover repaints one band, not the session. The strata are cached as an offscreen base layer and composited.
- The 3s safety tick no longer repaints the canvas; one error scan per measure instead of a subtree query per row; identical writes to dots, lens, shades and wall strings are skipped.
On the 1,879-row session: streaming+scrolling 151 ms → 44 ms per burst set, hover 0.86 → 0.31 ms per frame, idle 2 full repaints per 6 s → 0.
dsh plugin --profile web add dsh-stratav0.10.0 — the host's turn rail steps aside
DSH 0.1.2-rc.1 ships a fixed-pitch turn navigator down the right edge of the transcript — the strip this map already took over. Its frame spans the anchor-dot column exactly, so the two overlapped and the host's rail sat on top of the dots and the clue wall (reported in #2).
The map now treats it exactly like the scrollbar. While the map is up the host rail is hidden; the moment the map stands down — Trajectory tab, no session, a transcript that does not scroll — it is handed straight back, and uninstalling leaves it as though the map had never been there. Nothing reflows either way: that rail is absolutely positioned inside a zero-height sticky slot.
Want both navigators? Set
localStorage['dsh-strata.native-turn-rail'] = 'keep'and reload.
How the seam is found
The suppression is a stylesheet rule rather than a written-on element, so it survives the rail's React remounts. The selector is read out of the component's own stylesheet by the --turn-rail-band property it declares — a rebuild that reshuffles the CSS-module hash still resolves, and a DSH that never shipped the rail resolves to nothing and is left alone. The probe is throttled to one CSSOM walk every 2s, so looking for a rail that is not there costs nothing per frame.
Compatibility
Verified against a live DSH 0.1.2-rc.1, where chat rendering moved to @deepseek-ai/dsh-client-ui-chat: every anchor the map reads (data-conversation-scroll, data-chat-anchor-key, data-chat-flow-kind, data-composer-seat) still publishes, and the map, the dots, the clue wall and the load chain behave as they do on 0.1.1-rc.2 — where there is no turn rail and nothing changes. The replay suite reports identically with and without this change.
dsh plugin --profile web add dsh-stratadsh-strata 0.9.0
Correctness and performance pass driven by the 2026-08-26 audit
(AUDIT_REPORT.md), plus the first automated tests.
Fixed:
- 近/中: the canvas cache key now includes the zoom mode, the window offset,
the scale and the device pixel ratio, and one display mapping is computed
per frame and shared by the canvas, the dots, the lens and the hit test —
scrolling in a zoomed scale no longer leaves the strata behind the anchors. - 近/中: dragging the rail maps against the window frozen at pointerdown, so
a pixel of pointer travel is always the same scroll distance; a 3px slop
separates a band click from a drag. - Clue wall: the full-log cache is validated by the LAST loaded user row's
anchor key, not by a count. Loading older history keeps it; a message you
send is grafted from the transcript instead of re-downloading the export;
anything else refetches. OnlysurfaceOp: "append"user messages count,
matching the host's projection. - Clue wall: export requests carry a generation and an AbortController; a
session switch retires them, and a late response from the previous session
can neither overwrite the cache nor render into the open wall. - History loading has one owner (
createHistoryLoader): auto-paging, 中/全
and unloaded-card jumps run through it. A page with no progress stops the
chain at once, the 60-page cap and a vanished pager are reported (amber
bar and ⌃) instead of shown as 100%, a mode switch cancels the old chain,
and 全 clicked during an auto page supersedes it instead of doing nothing. - Clue wall pagers keep their rows while hidden (visibility, not display),
so the card area measured before packing is the area the cards get. - The native scrollbar's thumb variables are restored to their previous
inline values and priorities, per element, not merely removed. - Rail wheel honours
deltaMode(line and page deltas scale by the
scroller's line height / viewport). - Keyboard and AT: cards are
role=button, focusable, Enter/Space jump;
Enter on the rail opens the wall on the prompt you are at and focuses its
card; Esc returns focus to the rail; zoom buttons carry labels and
aria-pressed.
Performance:
- Streaming output no longer re-measures the transcript: rows are watched by
a ResizeObserver whose border-box sizes update ONE band and shift the rest
arithmetically; the MutationObserver dropscharacterDataand only treats
rows appearing/leaving/failing as structural. - Canvas repaints and hit tests visit only the bands inside the display
window (binary search on the sorted bands); anchor sync starts from the
window too. - The clue wall is virtualised: only the visible window plus a small buffer
exists as DOM, heights are cached per column width and measured in
batches, and connector paths persist across frames with card rects re-read
only while a layout settles. - The export is parsed as a stream of lines while it inflates; failed
exports back off (2s doubling to 30s) instead of retrying on every hover. - The idle safety-net tick runs every 3s (idle while the tab is hidden)
instead of 800ms; session switches come from the session store, composer
growth from its own ResizeObserver.
Tests: npm test runs node --test over the DOM-free internals (window
mapping, cache key, scrub mapping, visible range, wheel units, thumb
restore, prompt parsing, prompt store, history loader). test/replay/
holds the headless-Chrome replay used against a live dsh web.