# DIAGNOSIS — dsh-split-screen v1.5.1 on the live DSH 0.2.1-rc.2 client **Date:** 2026-10-19 **Author:** runtime diagnostician (Team task-1) **Scope:** reproduce the user's "the plugin breaks my DeepSeek Harness" complaint against the **exact client the live app runs** (`@deepseek-ai/dsh-desktop` / client packages **0.2.1-rc.2**, release commit `332895e`, web rev `d7b474132579`), on a fully isolated scratch instance. The live GUI (`http://127.0.0.1:`), the live profile, and the running Electron app were never contacted or modified. **Tested plugin artifact:** `lib/client.js` **SHA256 `52CB7902079907DFE4070F08F495E1DEF27F5EB5B266DBBA8322389699BA07C7`**, 6,236 lines / 313,427 bytes — byte-identical to the copy installed in the live desktop profile at the start of this session (`…\.dsh\profiles\desktop\node_modules\dsh-split-screen\lib\client.js`). The plugin source in the worktree was being edited by the implementer while this report was written; **the findings below describe the 1.5.1 artifact the user is running**, and should be re-checked against the new build. --- ## 0. TL;DR 1. **The plugin is completely inert on the live 0.2.1 client.** It loads cleanly (0 console errors, `window.__dshSplitView` present) but its conversation-column measurement now fails on the 0.2.1 shell, so the split workspace never activates. Exact console line (once per boot): `[dsh-split-screen] the conversation column could not be measured confidently on this shell; the split workspace stays inactive.` 2. **Root cause (regression in the 0.2.1 shell):** the shell frame gained a new **static bottom row** (`
`, `grid-area: auto / 1 / auto / -1`, width = full window, height 0). `frameColumns()` in v1.5.1 only skips `[data-shell-overlay]` and `position:absolute|fixed` children, so it picks `bottomRow` as the *details column*: `right = 1600 − 0 = 1600` ⇒ column width `1600 − 280 − 1600 = −280 < COLUMN_MIN_WIDTH 360` ⇒ `ok:false` ⇒ render gate returns `null`. Proven on the **real 0.2.1 desktop host** and on a 0.2.1 CLI mirror; the identical probe on a 0.2.0 mirror returns `ok:true, width:1320`. 3. **Behind the geometry gate there is a second, independent 0.2.1 breakage.** With geometry corrected by a test shim (harness-side only, no plugin edit), 2- and 3-pane splits render real sessions again, but every pane throws **`TypeError: renderFactorySlot is not a function`** (stock `UserStyleBubble`, new 0.2.1 factory-kind slot dispatcher) and **`TypeError: renderSlot is not a function`** (stock `renderModelLabel`), the pane boundary catches them (`[dsh-split-screen] pane component failed (rendering fallback)`), and the pane degrades to the plugin's built-in fallback renderer. The 0.2.0 client has **zero** such errors. This is 0.2.1 renderer-contract drift in the pane re-host, not the old update storm. 4. **1.5.1's headline fixes still hold: no freeze, no OOM, no React error #185 / update storm was reproduced** under 2-pane, 3-pane, reload-loop (×6), resize sweep, Alt+T, and pane-close stress. Heap flat, event loop responsive (ping 0 ms), long tasks only at boot (max 272 ms). The crash class the user remembers is **not** currently reproducible. 5. **New "invisible active" failure mode (medium):** with a persisted ≥2-pane tree (the live profile has one) and failing geometry, the plugin is logically active while rendering nothing: **Alt+T and Alt+Shift+Arrow are `preventDefault()`ed with no effect** (`defaultPrevented: true` — vs `false` with a 1-pane tree), and the drag/swap code paths run against a workspace that does not exist. This is the only way the user can *feel* the plugin misbehave today while the UI shows nothing. 6. **"Removed inject packages break boot" hypothesis is disproved empirically** (agrees with `docs/AUDIT-2026-10-19.md` §2.2): the 0.2.1 package tree has no `dsh-client-ui-primitives` / `dsh-client-store` / `dsh-client-ui-slots`, yet the plugin's `client.js` is fetched (200) and applied with **0 console errors** — those modules are platform seed words in the 0.2.1 frontend bundle, and unknown `dsh.client.inject` rows are silently skipped by `dsh-client-modules`. 7. The three `404`s per pane boot (`/api/present.open`, `/api/changes.summary`) are **mirror artifacts** (copied sessions without their attachment/spill stores), not plugin defects. **Verdict for the user complaint today:** the plugin is not currently the thing freezing the harness — it is a **silent no-op** on 0.2.1 (plus swallowed chords when a stale 2-pane tree is persisted). The historical freeze/OOM class (v1.5.0 era) is fixed in 1.5.1 under the configurations tested. Two real defects must be fixed before the plugin is usable on 0.2.1: **the frame-column regression** and **the 0.2.1 render dispatcher drift**. --- ## 1. Isolated reproduction setup (verifier-reusable) Nothing below touches `` (live home), the live desktop profile, or the live app on port . Real sessions were **copied read-only**; no message was ever sent. | Item | Path / value | |---|---| | Scratch DSH home (isolated state) | `` | | Mirror profile (copy of desktop profile, name it with `--profile`) | `\profiles\` | | 0.2.1 CLI install (npm, scratch prefix) | `\dsh021` — `@deepseek-ai/dsh@0.2.1-rc.2` (`node …\lib\bin.js --version` → `0.2.1-rc.2`) | | Copied sessions (read-only seed for panes) | four seeded session UUIDs (redacted) under scratch home `sessions\\` | | Evidence / scripts | `\evidence\`, `…\scripts\` | | Tested plugin copy in mirror | `\profiles\\node_modules\dsh-split-screen\lib\client.js`, SHA256 `52CB79…` (same as installed/live-profile copy) | ### Route A — 0.2.1 CLI web mirror (fast loop) ``` $env:DSH_HOME='' $env:DSH_TELEMETRY_MODE='DISABLED'; Set-Location '' node \dsh021\node_modules\@deepseek-ai\dsh\lib\bin.js ` --profile --port --host 127.0.0.1 --no-open # prints: dsh web: http://127.0.0.1:/?token= (log: evidence\server021.log) ``` 0.2.0 comparison mirror (same command with the global `dsh`, port , log `evidence\server.log`). `--port ` + DSH 0.2.0 was used only for the pass/fail baseline. ### Route B — the REAL 0.2.1 desktop host, headless (faithful shell) The desktop app runs a private Node-mode host; the same host was launched directly against the scratch home + mirror profile, with **no Electron window**, using the documented Electron node-mode path (the app itself relies on `ELECTRON_RUN_AS_NODE=1` for its pnpm calls, so this is the sanctioned mode): ``` node \scripts\launch-desktop-host.cjs # spawns: "\DeepSeek Harness.exe" --expose-internals # \resources\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\index.js # \dsh # IPC `ready` URL+token is written to evidence\desktop-host-ready.json ``` Boot metadata observed: `release 0.2.1-rc.2`, commit `332895e`, web rev `d7b474132579`; plugin entry `dsh-split-screen` rev `82fcce542cc4`, inject `[dsh-client-ui-layout, dsh-client-locale, dsh-client-ui-primitives]`. One non-fatal host warning appears in this scratch boot (`desktop-product-telemetry: serviceVersion missing`, plus `product-analytics: pending`) — an artifact of launching the host outside Electron, unrelated to the plugin. **Test-harness shim (clearly labelled, not a plugin edit):** to exercise pane code on 0.2.1 at all, the scripts prepend a `getComputedStyle` proxy that reports `position:absolute` for `[data-shell-bottom]` nodes (`SHIM=1`). It changes no layout and is only the missing geometry fix simulated at runtime. Every "shim" result below must be read as *"after fixing the geometry defect, what else breaks"*. ### Instrumentation `page.on(console/pageerror/requestfailed/response)`, `PerformanceObserver("longtask")` + `long-animation-frame`, event-loop lag probe (`setTimeout(0)` round trip), `performance.memory` + CDP `Runtime.getHeapUsage`, DOM counters (`.dsv-overlay/.dsv-pane/.dsv-divider/[contenteditable]`/node count), synthetic cancelable `KeyboardEvent`/`DragEvent` dispatch, screenshots. Scripts: `baseline_probe.py`, `frame_probe.py`, `split_stress.py` (scenarios `cold|seed2|seed3|interactions|reloads|all`), `invisible_active_probe.py`, `dynamic_split_probe.py`, `launch-desktop-host.cjs`, `extract_asar.cjs`. --- ## 2. Finding F1 — **CRITICAL**: 0.2.1 shell frame breaks the geometry gate ⇒ split view never activates ### Exact measurement trace `evidence\021\frame021.json` (0.2.1 CLI) and `evidence\desktophost\frame-desktophost.json` (real desktop host) are identical in structure: ``` frame: div class="…_frame" display:grid grid-template-columns: 280px 1320px 0px grid-template-rows: 900px 0px [0] sidebarCol static 280x900 left 0 [1] centerCol static 1320x900 left 280 <- conversation column [2] rightbarCol relative 0x900 left 1600 [3] bottomRow static 1600x0 left 0 <- NEW in 0.2.1, data-shell-bottom [4] overlayLayer absolute 1600x900 data-shell-overlay [5] handle absolute 8x900 data-side=sidebar ``` `frameColumns()` (v1.5.1 lines 5587–5601) returns `[sidebarCol, centerCol, rightbarCol, bottomRow]`; `measureColumnBounds()` takes `columns[last] = bottomRow` as the details column: ``` sideRight = 280 (sidebarCol.right) detailLeft = 0 (bottomRow.left) width 1600 out.right = 1600 - 0 = 1600 width = 1600 - 280 - 1600 = -280 < COLUMN_MIN_WIDTH (360) ⇒ ok:false ``` The grid fallback never runs because `out.right` was already assigned (same bug shape as the 0.2.0 splitter bug fixed in v1.5.1 by `frameColumns` — the shell again appended a new non-column child; the audit predicted exactly this residual risk: "a new static child could be misread as the details column", `docs/AUDIT-2026-10-19.md` §must-fix B). **0.2.0 control (`evidence\frame020.json`):** same probe on the 0.2.0 CLI mirror — frame children `[sidebarCol, centerCol, rightbarCol, overlayLayer, handle]`, `grid-template-rows: 900px` (no bottom row) → `left=280, right=0, width=1320, ok:true`. So this is a **0.2.1 regression**, not a regression in the plugin's 1.5.1 changes. ### Runtime effect (no shim) | Probe | Result | |---|---| | Cold boot, no persisted tree (real desktop host) | 0 console lines, 0 page errors, `__dshSplitView` present, `panes=0` (split-only contract OK) | | Boot with seeded 2-pane tree (0.2.1 CLI and desktop host) | `panes=0 overlay=0`, tree still in `localStorage`, exactly **1 warning** (the geometry line above), 0 page errors, heap flat | | 20 s later | unchanged — it never recovers on later frames/resizes | | Resize to 1280×800 | unchanged (`ok:false`, same −280 math) | **User impact:** on the live 0.2.1 app split-screen is dead: shortcut, drag and reload can never show panes. Fail-closed means no freeze, but the user sees "the plugin does nothing" (and any stale ≥2-pane tree produces the chord-swallowing of F3). --- ## 3. Finding F2 — **HIGH**: 0.2.1 renderer-contract drift inside the pane re-host With the geometry defect compensated (shim), 2- and 3-pane splits do activate and mount real sessions (`evidence\desktophost\seed2-shot.png`, `seed3-shot.png`), but every boot logs: ``` TypeError: renderFactorySlot is not a function at UserStyleBubble (… ui-chat …) TypeError: renderSlot is not a function at renderModelLabel (… ui-model-selection …) Warning: [dsh-split-screen] pane component failed (rendering fallback): TypeError: renderFactorySlot is not a function Warning: [dsh-split-screen] pane component failed (rendering fallback): TypeError: renderSlot is not a function ``` Per 2-pane boot: **3× `renderFactorySlot`, 2× `renderSlot`, 2 pane-failure warnings** (`result-seed2.json`); 3-pane boot: 4×/3× (`result-seed3.json`); 7 boots: 18×/12× cumulative, always caught, pageerror count always 0. **0.2.0 control: zero such errors** — only the mirror 404s (`evidence\0210\result-seed2.json`, panes=2, no TypeErrors). Why (0.2.1 source, from `dsh021\node_modules\@deepseek-ai`): - 0.2.1 added a **second render dispatcher**: `renderFactorySlot` (`dsh-client-ui-renderer\lib\client.js:321-333`, `boundRenderFactorySlot` → `FactoryOutlet`); the stock kit carries it (`standardKit` at `…\dsh-client-ui-renderer\lib\client.js:736-767`: `kit = { ...standard, renderFactorySlot }`). The stock ChatView signature takes both (`ui-chat\lib\client.js:2744`, `ChatFlow` at 5642), and `UserStyleBubble` calls `renderFactorySlot("conversation.chat.user.content", …)` (`ui-chat\lib\client.js:5392, 5417`). It also added `renderSlot` ownership enforcement (`boundRenderSlot`, `…\renderer\lib\client.js:340-359` — `SlotOwnershipError` for undeclared children, `StaleAuthorizationError` after disposal). - v1.5.1's `makeRenderers()` builds only `renderSlot`, `renderSlotChain`, `renderList` and returns exactly those (tested artifact lines 3186–3388; `props.renderFactorySlot` is never attached — `FRAMEWORK_SEATS` mentions the name at 1634 only for filtering the *root standard*, not for re-host). So any stock component re-hosted in a pane that is handed only `renderSlot`/`renderSlotChain` throws when it reaches for `renderFactorySlot` (all user-message content bubbles), and one path (`ModelSelect`'s `renderModelLabel`, `ui-model-selection\lib\client.js:1464-1469`) still gets no usable `renderSlot`. - `PaneBoundary` catches, logs once per distinct message (plus a running total), and replaces the subtree with the plugin's fallback renderer → **degraded panes** (no native user bubbles / model label; silent apart from console). It does **not** cascade to the shell — the stock main view stayed interactive in all runs. **Impact:** with geometry fixed, panes work but are partially degraded on 0.2.1 and spam the console every boot/render. This is the next blocker after F1 and the concrete place the 0.2.1 contract must be adapted. --- ## 4. Finding F3 — **MEDIUM**: "invisible active" state (persisted ≥2-pane tree + failed geometry) `treeStore.isActive()` is purely `leafCount >= 2` (v1.5.1 line 933) and is independent of the geometry gate. The live profile's localStorage holds pane trees, so on 0.2.1 the plugin sits in this state (`evidence\desktophost\invisible-active.json`): | Probe | 1-pane tree (inactive) | persisted 2-pane tree (invisible active) | |---|---|---| | `.dsv-overlay` / `.dsv-pane` | 0 / 0 | **0 / 0** | | synthetic `Alt+T` keydown | `defaultPrevented:false` | **`defaultPrevented:true`, `cancelBubble:true`** | | synthetic `Alt+Shift+ArrowRight` | `defaultPrevented:false` | **`defaultPrevented:true`, `cancelBubble:true`** | | console | 0 | 1× geometry warning | | heap / event-loop ping over 16 s | 51 MB flat / 0 ms | **51 MB flat / 0 ms** | | long tasks | 1× 132 ms (boot) | 1× 132 ms (boot) | Code paths that run while logically active but rendered nothing: `onComposerModeKeydown`/`onPaneNavKeydown` consume the chords then find no pane (`5476-5520`), `registerDragBridge` early-returns "panes handle drops while split" so the inactive-mode drag-to-split never fires (`6021`, `6039`), and the sidebar click-to-swap effect (`5841-5851`, gated only on `active`) can rewrite the persisted tree with no visible pane. In this run the DOM-selection click did not change the tree (the mirror's stock session selection did not propagate through the DOM source), so the last item is a **code-path risk, not reproduced**; the chord swallowing is reproduced deterministically. **Impact:** a user with a stale pane tree (exactly the live profile) gets keys eaten with no visual explanation — the closest thing to "the plugin breaks my harness" that is actually reachable on 0.2.1 today. Also explains how persisted 2/3-pane trees can survive/evolve invisibly across restarts. --- ## 5. What did NOT reproduce (1.5.1 headline fixes hold) Stress matrix on the **real 0.2.1 desktop host**, forced activation where needed: | Scenario | Panes | Console errors | Page errors | Heap (`performance.memory` / CDP) | Event-loop ping | Long tasks | |---|---|---|---|---|---|---| | Cold boot, no tree | 0 | 0 | 0 | ~51 MB | — | 0 | | Persisted 2-pane, 20 s watch | 2 | 3× factorySlot, 2× renderSlot, 3× mirror-404 (=8) | 0 | 87.5 → 98.2 MB then flat (CDP steady) | 0 ms all 10 samples | 10 during boot only, max **272 ms**, sum 1237 ms (all ≤1.7 s) | | Persisted 3-pane (nested v split), 20 s watch | 3 | 4×/3×, +3× 404 | 0 | **124.9 MB flat** | 0 ms | boot-only | | Alt+T toggle / close pane / resize sweep 1600→500→1600 | 2→0 | same per boot | 0 | 98.2 MB flat; back to 47 MB CDP after split off | 0 ms | none | | Reload loop ×6, 2-pane tree persisted | 2 every boot | 8/boot (same 8-error mix) | 0 | CDP after boot 77.7 / 64.7 / 78.2 / 65.0 / 79.9 / 92.2 MB — **no monotonic growth** | 0 ms | boot-only | | Pane header "Split right/down", close | 2 → picker → close to 1 leaf | same 8 | 0 | 87.5 flat | 0 ms | boot-only | | 0.2.0 CLI control, 2-pane | 2 | **0 plugin errors** (3× 404 = mirror artifact) | 0 | 82.4 MB | 0 ms | boot-only | **No** `Maximum update depth`, `React error #185`, `[client-store] subscriber failed`, `areHookInputsEqual`, `reading 'length'`, renderer OOM or unresponsive-renderer event occurred in any 0.2.1 run (`signatureHits` all zero in every result JSON). The 1.5.1 grow-only root-standard harvest and list-kind `renderSlot` fix are effective on 0.2.1: the old update-storm mechanism does not fire. The 1.5.0→1.5.1 WER/oom mechanism also needs the *pane re-hosting to run*; with F1 the plugin never reaches it, and with F1 fixed F2 is caught by boundaries. So the freeze class is currently latent, not reachable. --- ## 6. Hypothesis clearance: inject-list version drift is not a load-path break - 0.2.1 npm package tree (`dsh021\node_modules\@deepseek-ai`) has **no** `dsh-client-ui-primitives`, `dsh-client-store`, `dsh-client-ui-slots`; the 0.2.0 CLI has all three. - Yet the plugin's `client.js` is served `200` and `window.__dshSplitView` is present on every 0.2.1 boot (**0 console lines, 0 page errors**, `evidence\boot021-baseline.json`, `evidence\desktophost\boot-baseline.json`); its boot entry is `{id: dsh-split-screen, rev: 82fcce542cc4}`. - Reason: 0.2.1 supplies those three as **platform seed modules** inside the frontend bundle; `dsh-client-modules` `require()` checks the seed table first, and unknown `dsh.client.inject` rows are silently skipped (`…\dsh-client-modules\lib\client.js:789-792, 833-841`). This matches `docs/AUDIT-2026-10-19.md` §2.2 independently. - **Conclusion: do not tell the user the inject list breaks boot.** The load path is clean on 0.2.1. --- ## 7. Ranked runtime bugs (with suggested fixes) | Rank | Bug | Severity | Evidence | Fix direction | |---|---|---|---|---| | **B1** | **0.2.1 frame geometry mis-measure**: `frameColumns()` treats the new static `[data-shell-bottom]` row as the details column ⇒ `width = −280` ⇒ workspace permanently inactive (fail-closed, one warning only) | **Critical** (feature dead on the live client) | `evidence\desktophost\frame-desktophost.json`, `evidence\021\frame021.json`, `evidence\frame020.json`, `…\invisible-active.json` | Don't infer columns by "first/last static child". Anchor horizontally on the conversation column itself (`[data-conversation-scroll]` rect / `[data-rightbar-col]` left) with the grid-track fallback; or select only children that occupy a single 1-col grid track (exclude `[data-shell-overlay]`, `[data-shell-bottom]`, `[data-side]`, absolute/fixed). Add a 0.2.1 frame fixture (4 static children + 0-height full-width bottom row) to `tests/smoke.mjs`; also add a visible "split view unavailable: column not measurable" status instead of a single console line. | | **B2** | **0.2.1 dispatcher drift in pane re-host**: no `renderFactorySlot`; `renderSlot` not supplied on all stock re-host paths (`renderModelLabel`); 0.2.1's `renderSlot` is authorization-bound (`SlotOwnershipError`/`StaleAuthorizationError`) | **High** (panes degrade to fallback, console TypeErrors every boot/render) | `result-seed2/3/interactions/reloads.json` `console`; `seed2-shot.png`; 0.2.0 control shows 0 | Extend `makeRenderers()` with a pane-local `renderFactorySlot` (emulate `FactoryOutlet`: render every factory-kind entry captured from the ledger, merge kit+inject+owner props, `PaneBoundary` each) and pass **both** dispatchers down wherever stock components are re-hosted (ChatView/ChatFlow/UserMessageNodeView/ModelSelect/composer chain), mirroring 0.2.1 `standardKit`. Keep `renderSlot`/`renderSlotChain` semantics; do not hand stock components the *stock* bound dispatcher (it would target the stock caller, not the pane). Add an e2e assertion: 2-pane boot with a session containing a user message → zero `… is not a function` console errors. | | **B3** | **Invisible-active**: hotkeys/drag/swap driven by persisted leaf count only; chords swallowed while overlay hidden | **Medium** (user-visible "keys eaten", invisible tree mutation risk) | `evidence\desktophost\invisible-active.json` | Define `operationalActive = active && bounds.ok`; gate `onComposerModeKeydown` / `onPaneNavKeydown` / drag bridge / sidebar-swap effect / `sessionsListSource.setActive` on it; on geometry failure leave the persisted tree untouched and expose a status. | | **B4** | Pending-interaction contract is wrong (`uiSession.pendingInteractions` does not exist on 0.2.x) and composer editor-root single-writer contention (architectural) | **Medium** (dead ask/approval cards in panes; the freeze mechanism class) | `docs/AUDIT-2026-10-19.md` §2.3 D1/D2; not re-reproduced here (copied sessions contain no pending interaction) | Read `useSessionStatus(...).get(sessionId)?.pendingInteraction` (0.2.1 `ui-conversation:17747`); longer term stop re-hosting the stock composer (audit's structural recommendation) or isolate it. | | — | 404s `/api/present.open`, `/api/changes.summary` in panes | Info (harness artifact) | `result-seed2.json` `badResponses` | Ignore: copied sessions lack their attachment/spill stores in the mirror; not a plugin defect. | --- ## 8. Pass/fail table (0.2.1 live client, otherwise stated) | # | Scenario | Expected | Result | Verdict | |---|---|---|---|---| | 1 | Plugin load on 0.2.1 (inject drift) | no harness-wide break | client.js 200, `__dshSplitView` present, 0 console/page errors | **PASS** | | 2 | Cold boot, no persisted tree | stock UI untouched, no overlay | 0 console, 0 errors, panes 0, contenteditable 1 | **PASS** | | 3 | Persisted 2-pane tree → split UI | 2 panes + overlay | **0 panes, 0 overlay**, 1 geometry warning | **FAIL (B1)** | | 4 | Persisted 3-pane (nested) tree → split UI | 3 panes | 0 panes | **FAIL (B1)** | | 5 | Geometry control on 0.2.0 mirror | activates | `ok:true`, width 1320, 2 panes | PASS (control) | | 6 | 2-pane panes render real sessions (geometry compensated shim) | stock components render | 2 panes, real titles/transcripts, **2× renderSlot + 3× renderFactorySlot TypeErrors/boot**, fallback banner in console | **FAIL (B2)** | | 7 | 3-pane nested (shim) | renders | 3 panes, same TypeError mix | **FAIL (B2)** | | 8 | Pane swap / Alt+T / close / dynamic split buttons (shim) | interactive, no freeze | Alt+T TUI 0→1, close 2→0 panes correct, resize 1600→500→1600 all panes intact, ping 0 | PASS (except B2 console noise) | | 9 | Reload loop ×6 with persisted tree | no storm/OOM/leak | 2 panes each boot, CDP heap 64.7–92.2 MB non-monotonic, no #185, ping 0 | **PASS** | | 10 | Invisible-active chord handling | chords no-op or work | Alt+T / Alt+Shift+Arrow **preventDefault + swallow**, no effect | **FAIL (B3)** | | 11 | Freeze/OOM/#185 regression | none | none in 10 runs | **PASS** | --- ## 9. Limitations - Headless Chromium against the real host/shell/client packages; **not** an Electron `BrowserWindow` (window chrome/titlebar absent, but the shell/client code under test is identical to the renderer's; layout is the same `…_frame` grid). - Copied sessions are only the four listed; no streaming turn, no pending `ask_user_question`/approval was exercised. Stop/streaming/queue paths and the plugin↔`dsh-session-hotkeys`/`dsh-result-only-view`/`dsh-im` runtime interactions remain untested live. - The forced-activation shim is a harness-side simulation of the missing B1 fix; results with it describe the post-B1 behavior, not current live behavior. - Plugin source was being edited concurrently (worktree hash changed from the tested `52CB79…`/6,236 lines during this session); re-run §1 scripts against the new mirror build before release verification. ### Verifier quick-start ``` # 0.2.1 CLI mirror (Route A) $env:DSH_HOME=''; $env:DSH_TELEMETRY_MODE='DISABLED' node \dsh021\node_modules\@deepseek-ai\dsh\lib\bin.js ` --profile --port --host 127.0.0.1 --no-open # token in evidence\server021.log # Real 0.2.1 desktop host (Route B) node \scripts\launch-desktop-host.cjs # URL in evidence\desktop-host-ready.json # Evidence scripts python scripts\frame_probe.py out.json python scripts\invisible_active_probe.py out.json $env:SHIM='1'; python scripts\split_stress.py outdir seed2 # seed3|interactions|reloads ``` Test servers were stopped after this report; the scratch home + mirror profile are left in place for the verifier/replacement-plugin spike.