# VERIFICATION — dsh-split-screen 1.6.0 on the live DSH 0.2.1-rc.2 client **Date:** 2026-10-19 **Verifier:** independent verifier (Team task-5) **Final verdict: PASS on the shipped artifact (release-ready on 0.2.1-rc.2; residual risks in §10/§14)** **Verified artifact (final, shipped):** `lib/client.js` SHA256 `8306116804f96c358b22a17dea01f44c353bac3da111d2d358a52ecac0d95ba8`, 414,427 bytes / 7,935 lines · `package.json` 1.6.0 · `tests/safety.mjs` (117 checks) + unchanged `tests/smoke.mjs` (16 checks). Earlier artifacts on the road here: `b24eca13…` (full 99-check suite PASS), `0c80e969…` (TESTED_RELEASES flip; failed the extended in-pane ask-card check, see §14), then `83061168…` (ask-card fix; all re-checks PASS). **Host under test:** `@deepseek-ai/dsh-desktop` 0.2.1-rc.2 (release commit `332895e`, web rev `d7b474132579`) — the exact client the user's live app runs; plus a 0.2.0-rc.2 CLI mirror for backward compatibility (§14). **Not touched:** the live app/profile (`` — only read/hashed), and the plugin source (read-only for the verifier). Two defects were found and reported; the implementer fixed them and each fix was re-verified. **Failure history (important):** the first frozen 1.6.0 artifact, SHA256 `2CF7F8C06D66702458E11A2E63E1E9B80D762E52996700FBDF9E0E155E40E2EF` (397,870 B / 7,633 lines), **FAILED** on the user's own complaint path: panes activated but their bodies rendered the plugin's `"Session ended"` hint because the session faces never bound (`sessions.retain()` raced the 0.2.1 session catalog at mount, failed once, and the failure was swallowed with no retry — a regression from 1.5.1, proven by an A/B control below). The implementer shipped a bounded retry driver; the fixed artifact `b24eca13…` **PASSES** that scenario and the whole suite. All scenario evidence in §4–§8 is from the fixed artifact unless stated. A **second defect** (in-pane `ask_user_question` card failing with `TypeError: useQuestionCard is not a function`) was found during extended live verification on `0c80e969…` and fixed in the shipped `83061168…` — see §14 for the full FAIL → fix → PASS history. --- ## 1. Environment (isolated, no live-app contact) | Item | Value | |---|---| | Real 0.2.1 desktop host (Route B, faithful composition) | `\DeepSeek Harness.exe` with `ELECTRON_RUN_AS_NODE=1 --expose-internals \dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\index.js \dsh `, launched by the verifier's own copy `\launch-host.cjs`; IPC `ready` URL written to `…\evidence\vhost-ready.json` | | Isolated DSH home (copy of the diagnostician's scratch home) | `` (81 MB copy; sessions copied read-only) | | Mirror profile | `\profiles\` (desktop-profile copy); the plugin under test is its `node_modules\dsh-split-screen` (only `lib/client.js` + `package.json` replaced, backups `*.bak-151` kept) | | Browser | Playwright Chromium headless (1600×900 viewport), one fresh context per scenario | | Live app/profile | ``, `` — **never written, never restarted**; hashes checked before/after (§8) | | No geometry shim for the final artifact | The 0.2.1 frame activates natively; the diagnostician's `getComputedStyle` shim was used only for the 1.5.1 negative control | Exact repro commands are in §11. Evidence root: `\` (`evidence\final-B24ECA13\`, `evidence\baseline-2CF7F8C\`, `evidence\rehearsal-151-*.json`, `evidence\ab-151.png`, `evidence\vhost.log`, `evidence\vhost-ready.json`). **Frozen-target control:** `lib/client.js` SHA256 was recorded before the run (`b24eca13…`), re-checked during and after (unchanged), and the copy the host actually served reported `health().pluginVersion === "1.6.0"` + `release "1.6.0"`. `git -C status` stayed exactly: modified `CHANGELOG.md/README.md/lib/client.js/package.json`, new `tests/safety.mjs` (+ team docs). --- ## 2. Negative controls (why this harness can see the failures it is testing for) Run on the *same* Route-B host and the *same* `` copy, with the 1.5.1 artifact (`SHA256 52CB7902079907DFE4070F08F495E1DEF27F5EB5B266DBBA8322389699BA07C7`): | Control | Evidence | Result | |---|---|---| | 1.5.1, persisted 2-pane tree, **no** shim | `evidence\rehearsal-151-noshim.json/.png` | panes=0, overlay=0, exactly 1 warning `the conversation column could not be measured confidently…` → reproduces the 0.2.1 geometry-gate death (F1) | | 1.5.1, geometry shim (harness-only) | `evidence\rehearsal-151-shim.json/.png` | panes=2, overlay=1, console `renderFactorySlot is not a function` ×2, `renderSlot is not a function` ×2, `pane component failed` ×2 → reproduces the 0.2.1 dispatcher drift (F2) | | 1.5.1 "invisible active", no shim | `evidence\rehearsal-151-invisible.json` | persisted 2-pane tree, overlay absent, **Alt+T and Alt+Shift+ArrowRight both `defaultPrevented:true, cancelBubble:true`** → reproduces the chord-swallow (F3/B3) | | **A/B for the 1.6.0 FAIL** | `evidence\ab-151.png`, `evidence\pane-diag.json`, §3 | same host/home: 1.5.1 pane faces `{path:"retained", reference:true, hasSession:true, afterRetain:true}` + real transcripts; first 1.6.0 artifact `{path:"retained", reference:false, hasSession:false, afterRetain:false}` + `"Session ended"` | --- ## 3. FAIL found on artifact `2CF7F8C0…` and resolution **Repro (old artifact):** isolated Route-B host, seed `localStorage["dsh.split-view.tree.v1"]` with a 2-leaf tree on sessions `session-…` / `session-…`, reload, wait 16 s. Panes=2/overlay=1 (geometry OK), but both pane bodies rendered the plugin's `Session ended` hint. `window.__dshSplitView.composerChain.panes`: `{ session-9538…: {path:"retained", reference:false, hasSession:false, afterRetain:false, faces:null}, session-6d61…: same }`; `paneDebug.last === null` (prepareNative never ran); `health()` clean (`active:true, failures:0, lastError:null`); no console warning (failure swallowed); deterministic across 3 reloads and fresh contexts. 1.5.1 control on the same host rendered real transcripts (see §2). **Reported to the Lead immediately with repro + A/B evidence.** Root cause (confirmed by implementer): 1.6.0's sticky-face rewrite dropped 1.5.1's incidental retry (`exists` flip re-ran the retain effect); a mount-time `sessions.retain()` that raced the 0.2.1 session catalog returned undefined, was swallowed, and never retried. **Fix (artifact `b24eca13…`):** shared retry driver, backoff 200/600/1500/3000/5000 ms, max 6 attempts, re-armed by `reference.ready` / late session-list snapshots / pane remount; loading hint until exhausted; `lastRetainError` in `paneDebug.faces[sid]` + `health().lastError` + one console.warn on exhaustion. New regression tests in `tests/safety.mjs`. **Re-verification on `b24eca13…`:** both faces `{path:"bound", attempts:0, lastRetainError:null, reference:true, hasSession:true}`, `paneDebug.last` non-null (view:"chat", composer:true, header:true, 9 root faces), pane bodies real (content from two real sessions, no placeholders), zero warnings/errors/pageerrors. PASS. --- ## 4. PASS/FAIL table (final artifact `b24eca13…`, host 0.2.1-rc.2) All checks below are automated assertions with raw JSON evidence; **99/99 passed** across 11 scenarios (`evidence\final-B24ECA13\aggregate.json`, `summary.json`, `scen-*.json`). No `pageerror` and no unhandled rejection occurred in any scenario; every signature counter (`renderFactorySlot`, `renderSlot`, `renderSlotChain`, `#185`, `Maximum update depth`, `areHookInputsEqual`, `reading 'length'`, `out of memory`, `SlotOwnershipError`, `StaleAuthorizationError`, `pane component failed`, geometry warning) stayed **0**. | # | Criterion | Verdict | Evidence (all under `\evidence\final-B24ECA13\`) | |---|---|---|---| | A1 | `node --check lib/client.js` + tests | **PASS** | exit 0 for `lib/client.js`, `tests/smoke.mjs`, `tests/safety.mjs` | | A2 | `npm test` = smoke + safety | **PASS** | exit 0; **114 `ok`, 0 `not ok`** (= 16 smoke + 98 safety, matching the implementer's claim) | | A3 | version 1.6.0, only intended files | **PASS** | `package.json` version 1.6.0; `git diff --stat`: CHANGELOG +235, README +119, `lib/client.js` +2834/−431, package.json 4 — plus new `tests/safety.mjs` and docs; no other source touched | | A4 | public contract unchanged | **PASS** | loader `window.__ModuleLoader__.load({id:"dsh-split-screen"})`; `exports.inject = ["slots","sessions","locale","uiSession"]`; `package.json` `dsh.client.inject` and peerDependencies unchanged; `scripts.test` only addition; **no new dependencies, no build step** | | B1 | 2-pane persisted tree activates **natively** on 0.2.1 (no shim) | **PASS** | `scen-seed2.json`: panes=2, overlay=1, geometry `{ok:true,left:280,right:0,top:0,bottom:0}`, `blockedReason:null`, 0 "could not be measured" warnings; 15/15 checks | | B2 | 3-pane nested tree activates | **PASS** | `scen-seed3.json`: panes=3, overlay=1, same clean geometry; 15/15 checks | | B3 | fresh (non-persisted) pane at runtime via split picker | **PASS** | `scen-dynamic.json`: `.dsv-picker` opened from pane header, listed 6 sessions, picking created 3rd pane; `leafIds` 3 unique sessions; all 3 faces `{reference:true,hasSession:true}`; 7/7 checks | | B4 | 0.2.0 frame shape | **NOT RE-RUN live** | covered by `selectFrameColumns`/`computeColumnBounds` unit tests in `tests/safety.mjs` (0.2.0 + 0.2.1 frame fixtures) and the diagnostician's 0.2.0 control (`evidence\frame020.json`); see §10 | | C1 | zero pane-component failures | **PASS** | 0 × `renderFactorySlot/renderSlot/renderSlotChain is not a function`, 0 × `pane component failed`, 0 × React #185 / max-update-depth, 0 pageerror, 0 unhandled rejection in **every** scenario | | C2 | session faces bind; native re-host runs | **PASS** | every pane face `{path:"bound", reference:true, hasSession:true}`; `paneDebug.last` populated (rootFaces, kitFaces, `view:"chat"`, `composer:true`, `header:true`, viewEntries) | | C3 | real content renders in panes (user/assistant transcript) | **PASS** | pane innerText 3,831 / 4,130 / 7,884 chars with real task text; empty copied session renders its legitimate empty-state composer, never the `Session ended` hint. *Limitation:* evidence is transcript-level; a specific `UserStyleBubble` DOM node was not isolated (see §10) | | C4 | untested-host note only (informational) | **PASS (expected)** | `health()`: `hostRelease:"0.2.1-rc.2"`, `testedHost:false`, `testedReleases:["0.2.0-rc.2"]`; one info note per boot, split view still activates (as designed) | | D1 | crash-loop guard + safe mode + paused chip + Retry | **PASS** | `scen-crashloop.json`: seeded `health.v1` record (attempts=2, never healthy, release 1.6.0) → **no auto-activation** (panes=0, stock view), `health().safeMode:true`, `blockedReason:"safe-mode"`, chip `dsv-chip`: `"Split screen paused after a crash"` + `Retry` + `Disable` + dismiss `×`; `retry()` → `{ok:true}` and panes=2 | | D2 | corrupted / duplicate / oversized / deep / wide tree | **PASS** | `scen-corrupt.json` (12/12): invalid JSON, non-object, `{}`, wrong types → discarded/fail-closed, stock view, no crash, ping 0; duplicate leaves → deduped to 1 leaf, **never duplicate panes**; wide 40-leaf → deduped to 4 unique panes (≤ MAX_PANES 6); deep 20-level → depth-capped/fail-closed; 200 KB oversized → sanitized/fail-closed; no pageerror in any case | | D3 | kill switch + `setEnabled` persistence | **PASS** | `scen-killswitch.json` (8/8): `enabled.v1="0"` → inert (panes 0, overlay 0); `setEnabled(true)` → panes 2 and key `1`; `setEnabled(false)` → panes 0, key `0`; reload keeps disabled state | | D4 | `health()/help()/retry()/reset()/setEnabled()` sane + never throw | **PASS** | `scen-api.json` (7/7): all callable, JSON-serializable, `health()` shape has all 15 documented fields, `help()` has version/shortcuts/gestures/disable/reset/diagnostics/docs; `reset()` → `{reset:true}` then stock view (panes 0) | | E1 | chords while operational | **PASS** | `scen-chords.json` (8/8): Alt+T & Alt+Shift+Arrow `defaultPrevented:true`, Alt+T toggles TUI composer (`.dsv-tui-input` 0→1), focus moves between panes | | E2 | B3 invisible-active chords NOT swallowed when geometry blocked | **PASS** | `scen-chords.json`: forced `geometryBlocked:true` via documented `__test.noteGeometry` seam → `health()` reports `geometryBlocked:true` and **both chords `defaultPrevented:false, cancelBubble:false`** (1.5.1 control swallowed them, §2) | | E3 | Alt+T / focus / close / reload / sidebar swaps | **PASS** | `scen-interactions.json` (10/10): Alt+T toggles TUI; Alt+Shift+Arrow moves focus; ✕ closes the focused pane → stock 1-pane view (overlay gone); reload restores 2-pane tree; after close reload correctly stays stock (1-leaf tree persisted); 10 sidebar swaps with heap bounded (no growth), responsive | | E4 | inactive boot: stock untouched, chords not swallowed | **PASS** | `scen-inactive.json` (6/6): panes 0/overlay 0, stock composer present, Alt+T/Alt+Shift `defaultPrevented:false` | | F1 | ~9-minute 2–3 pane soak: heap flat, responsive | **PASS** | `scen-soak.json`: 602 s, 36 samples (3 panes throughout + periodic Alt+T/focus/sidebar/resize), JS heap (CDP) 43.8–78.6 MB, first 65.3 → last 53.2 MB (**no monotonic growth**), **event-loop ping 0 ms on all 36 samples**, 0 pageerror/rejection, 0 component errors | | G1 | live desktop profile + installed plugin unchanged | **PASS** | installed `…\.dsh\profiles\desktop\node_modules\dsh-split-screen\lib\client.js` = `52CB7902079907DFE4070F08F495E1DEF27F5EB5B266DBBA8322389699BA07C7` (313,427 B, mtime 2026-10-14) before and after; all other installed plugin files byte-identical with pre-session mtimes; live profile `package.json` mtime 2026-10-18 | | G2 | scratch harness untouched outside the verifier's own copy | **PASS** | diagnostician's `` plugin still `52CB79…`; all writes confined to `\` | | G3 | no leftover verifier servers/processes | **PASS** | verifier host stopped; ports 53627/59350/64246/57762/61786 free; no `launch-host.cjs`/`vprobe.py`/`chrome-headless-shell` processes remain | --- ## 5. Console / error budget (final artifact, all scenarios) From the per-scenario JSONs (`signalHits` aggregated in `aggregate.json`): across 11 scenarios and ~30 page loads there were **0** pageerrors, **0** unhandled rejections, **0** plugin errors/warnings of any kind. The only console `error` lines were Chromium's `Failed to load resource: 404` for `GET /api/present.open` and `GET /api/changes.summary` on the session that has attachments (3–4 per boot of that session), which the diagnostician documented as **copied-session mirror artifacts** (the mirror lacks those sessions' attachment/spill stores), not plugin defects; all other scenarios show none. The `tests/safety.mjs` retry-driver tests plus the `paneDebug.lastRetainError`/`health().lastError` fields give future boots a visible diagnostic if the race ever reappears. --- ## 6. Safety-layer observed behavior (raw) - **Safe mode (crash-loop guard):** record `{attempts:2,lastHealthyAt:0,release:"1.6.0"}` in `localStorage["dsh.split-view.health.v1"]` → boot logs one warning, renders **nothing** (stock view kept), chip `dsv-chip` = `Split screen paused after a crash [Retry] [Disable] [×]`; `health()` `safeMode:true, blockedReason:"safe-mode"`; `retry()` cleared counters, set `enabled=1`, activated the kept tree (panes 2). No dialog / no overlay / no crash. - **Corrupted trees:** every malformed payload was discarded or normalized before use; persisted value after boot was either removed or a valid bounded tree; no duplicate panes ever rendered; the 6-pane cap and 6-level depth cap held; the app stayed interactive (ping 0) in all 11 cases. - **Kill switch:** `localStorage["dsh.split-view.enabled.v1"]="0"` → plugin loads but never takes the viewport; `window.__dshSplitView.setEnabled(false/true)` persists `"0"`/`"1"` and immediately deactivates/activates; reload honors it. - **Diagnostics:** `health()` plain JSON as documented (§ API evidence), `help()` returns shortcuts/gestures/disable/reset/diagnostics/docs, `reset()` clears tree+counters and returns to stock, `retry()` re-activates, all wrapped so they never throw (`safeCall`), all serializable. --- ## 7. Responsiveness / memory (F) `scen-soak.json`: 3 panes, 602 s wall, 36 samples every ~15 s; heap series `65.3, 47.4, 47.5, 46.4, 53.7, 60.2, 43.8, 49.2, 51.3, 53.4, 65.0, 65.5, 78.6, 52.7, 54.8, 52.9, 55.3, 56.0, 61.0, 53.8, 56.2, 52.4, 58.3, 58.9, 57.4, 53.1, 58.0, 51.9, 65.3, 65.8, 55.7, 67.4, 51.7, 59.9, 52.5, 53.2` MB — oscillating GC cycles, no monotonic growth; `ping` 0 ms in all samples; panes stayed 3; periodic Alt+T/focus/sidebar-click/resize during the soak produced no errors. No renderer OOM/unresponsive event. --- ## 8. Side-effect proof (G) - Live profile installed plugin files (hash rec + mtimes from before the session): `lib/client.js` `52CB7902079907DFE4070F08F495E1DEF27F5EB5B266DBBA8322389699BA07C7` (313,427 B, 2026-10-14T02:18), `package.json` `984155232960CB15AD9607E1B4137500736722C48C93D167DB50C2E42CF75E73`, `lib/index.js` `F6B045FD…`, `cordis.patch.yml` `D61EE6AC…`, README/CHANGELOG/LICENSE unchanged — **identical after the run**. - Repo artifact hash `b24eca13…` / 409,276 B / package.json `E4D00F83…` recorded **before and after** all runs: unchanged (no moving target). - The diagnostician's scratch home (``) plugin is still `52CB79…` (untouched by the verifier). - All verifier servers/processes stopped; no listens on the verifier ports; no Playwright/headless leftovers. - The live desktop app (port ) was never restarted, killed, or contacted. --- ## 9. Scenarios per artifact hash | Artifact | Hash | Scenarios run | Outcome | |---|---|---|---| | 1.5.1 control | `52CB7902…A07C7` | seed2 no-shim, seed2 shim, invisible-active (chords), A/B pane-face probe | designed to fail · controls reproduced F1/F2/F3 | | 1.6.0 first freeze | `2CF7F8C0…E40E2EF` | static A, seed2, seed3, chords, killswitch, crashloop, api (partial), interactions (partial), pane-face probe | **FAIL** session-face retention (§3); safety-layer scenarios passed on this hash (killswitch 8/8, crashloop 5/5) | | **1.6.0 fixed (full suite)** | **`b24eca13…59ec416`** | static A, inactive, seed2, seed3, dynamic split-picker, chords, interactions, killswitch, corrupt, crashloop, api, soak (602 s) | **99/99 checks PASS** | | **1.6.0 final-final (TESTED_RELEASES flip)** | **`0c80e969…87096f2`** | static A + one-boot re-check on the real 0.2.1 desktop host (persisted 2-pane tree) | **10/10 checks PASS** (§13); the full 99-check suite ran on `b24eca13`, delta = TESTED_RELEASES constant + sign-off comment + docs/tests | | **1.6.0 shipped (ask-card fix)** | **`83061168…0d95ba8`** | static A; extended live checks (§14): streaming+Stop, in-pane ask_user_question card FAIL→fix→PASS, 0.2.0-rc.2 live compat, acceptance subset (2-pane/3-pane/dynamic-picker/chords/killswitch/crashloop/corrupt) | **PASS** (§14) — the in-pane ask card now renders and is answerable; artifact frozen and re-hashed unchanged after the runs | --- ## 10. Residual risks / unverified items (honest scope) 1. **`TESTED_RELEASES` — RESOLVED** (was `["0.2.0-rc.2"]` at the `b24eca13` PASS): the implementer flipped it to `["0.2.0-rc.2", "0.2.1-rc.2"]` after this verification, and the final-final artifact `0c80e969…` was re-checked on the real 0.2.1 host — `health().testedHost === true`, untested-host note absent, all one-boot assertions passed (§13). 2. **0.2.0 live frame not re-run** in this session (optional item). Covered by the new pure-function frame-shape unit tests (0.2.0 + 0.2.1 fixtures) and the diagnostician's 0.2.0 control evidence; not re-proven end-to-end here. 3. **Streaming turn — now covered (§14 CHECK 1).** A short live turn was streamed inside a pane on the isolated 0.2.1 host; assistant text grew incrementally, the *Stop generating* control worked, and the turn stopped. Longer/hours-long streaming is still untested; only a short turn was run to keep token cost minimal. 4. **Pending-interaction card — now covered (§14 CHECK 2).** The in-pane `ask_user_question` card FAILED on `0c80e969…` (`TypeError: useQuestionCard is not a function`) and PASSES on the shipped `83061168…`: the native card renders inside the pane, is answerable, and the turn resumes. Approval / plan-review card variants were not separately exercised (same composer-takeover machinery). 5. **Composer editor-root contention (M5)** is a known architectural residual, not addressed by 1.6.0; pane bodies were usable/recoverable and none of the tested paths wedged the editor, but typing/IME in pane composers was not stress-tested here. 6. **User-bubble DOM identity:** C3 evidence is pane-transcript-level (real text, no `Session ended`, no factory-dispatcher errors); the specific stock `UserStyleBubble` node was not isolated as a distinct DOM assertion. (The 1.5.1 control proved these errors DO surface in this harness, so their absence on the fix is meaningful.) 7. **Harness fidelity:** headless Chromium against the real 0.2.1 host/client packages, not an Electron `BrowserWindow`; shell/client code and layout under test are identical, window chrome differs. Copied sessions lack attachment sidecars → the 404 `/api/present.open`, `/api/changes.summary` lines are mirror artifacts (the diagnostician's finding). 8. **Soak length** 602 s (10 min asked); heap/ping evidence is flat but a longer run/hours-long usage is untested. 9. **Kill switch/`setEnabled` matrix:** verified `"0"`/`"1"` paths + reload; not every combination (e.g. setEnabled while inactive + reload) exhaustively. --- ## 11. Exact reproduce commands ```powershell # 1) artifact hash freeze Get-FileHash \lib\client.js -Algorithm SHA256 # b24eca13… (Get-Content \package.json -Raw | ConvertFrom-Json).version # 1.6.0 # 2) static A node --check \lib\client.js Push-Location ; npm test; Pop-Location # 114 ok, 0 not ok # 3) copy artifacts into the verifier mirror (never the live profile) $m='\profiles\\node_modules\dsh-split-screen' Copy-Item \lib\client.js "$m\lib\client.js" -Force Copy-Item \package.json "$m\package.json" -Force # 4) isolated real 0.2.1 desktop host (Route B; own launcher, own home) node \launch-host.cjs # URL in …\evidence\vhost-ready.json # 5) full suite (Playwright; each scenario = fresh browser context; no shim) python \scripts\vprobe.py ` "" \evidence\final-B24ECA13 ` "inactive,seed2,seed3,dynamic,chords,interactions,killswitch,corrupt,crashloop,api,soak" # 6) stop the verifier host afterwards (Stop-Process on its PIDs only), leave no servers ``` Scenario definitions live in `\scripts\vprobe.py` (+ `quick_probe.py`, `ux_probe.py`, `pane_diag.py`, `stock_diag2.py`, `aggregate.py`), and the launcher in `\launch-host.cjs`. They only touch `\` (own home/profile copy, own browser contexts) and never the live app/profile or the plugin source. --- ## 12. Evidence index (absolute) - Final aggregate + per-scenario: `\evidence\final-B24ECA13\` (`aggregate.json`, `summary.json`, `scen-{inactive,seed2,seed3,dynamic,chords,interactions,killswitch,corrupt,crashloop,api,soak}.json`) - FAIL-scenario re-verification: `…\evidence\fix-seed2.json`, `fix-seed2.png` - 1.5.1 controls: `…\evidence\rehearsal-151-noshim.json/.png`, `rehearsal-151-shim.json/.png`, `rehearsal-151-invisible.json`, `ab-151.png` - Old-artifact baseline: `…\evidence\baseline-2CF7F8C\scen-*.json`, `pane-diag.json`, `pane-diag.png` - Host/launcher logs: `…\evidence\vhost.log`, `vhost-ready.json`, `current-url.txt` - Static A logs: this report §4 and the commands in §11 (final artifact; npm test output captured in the session log) --- ## 13. Final artifact re-check (TESTED_RELEASES flip) — signed off 2026-10-19 **Final-final artifact (frozen, hash checked before and after the run):** `lib/client.js` SHA256 `0c80e96984221349eda6d69f78cdc5d56342f4cfb0ebd1a5285e590f487096f2`, **409,427 bytes / 7,855 lines**, mtime 2026-10-19T15:30:31; `package.json` 1.6.0, SHA256 `E4D00F832FA995B8BB08F6CF508D5017E1621BAD6AF210794A292CB14A7F6BDB` (**byte-identical to the `b24eca13` artifact**). **Delta vs `b24eca13` (full-suite PASS artifact):** `client.js` +151 bytes / +2 lines = the `TESTED_RELEASES` constant (now `["0.2.0-rc.2", "0.2.1-rc.2"]`, line 107) plus a 2-line sign-off comment adjacent to it, as seen in the pre-swap line comparison; `tests/safety.mjs` 33,831 → 34,727 B (assertion updates; 101 checks); README/CHANGELOG compatibility wording. No package/dependency/build changes, no other functional code change per the implementer. (The verifier's only byte copy of `b24eca13` was replaced by the final artifact, so the exact byte-level diff could not be retained; the size/line delta, identical `package.json`, and pre-swap comparison above are the recorded evidence. The 99/99-check suite result stands recorded for `b24eca13`.) **Static A (final-final):** `node --check` exit 0 for `lib/client.js`, `tests/smoke.mjs`, `tests/safety.mjs`; `npm test` exit 0 with **117 `ok` / 0 `not ok`** (= 16 smoke + 101 safety, matching the implementer's numbers); contract unchanged — loader `window.__ModuleLoader__.load({id:"dsh-split-screen"})`, `exports.inject = ["slots","sessions","locale","uiSession"]`, `dsh.client.inject` unchanged, **no dependencies/devDependencies, no build step**; `git diff --stat` only intended files (CHANGELOG +239, README +118, `lib/client.js` +2836/−431, package.json 4; new `tests/safety.mjs` + team docs); only `TESTED_RELEASES` flip visible in the hash delta. **One-boot re-check on the real 0.2.1-rc.2 desktop host** (isolated Route-B, own home/profile copy, persisted 2-leaf tree, no geometry shim), **10/10 assertions PASS** (`evidence\final-0C80E969\recheck.json` + `recheck.png`): - 2 panes + overlay on the persisted tree; - `health().testedHost === true`, `testedReleases === ["0.2.0-rc.2","0.2.1-rc.2"]`, and the untested-host info note is **absent**; - `health()` clean (`active:true`, `geometry.ok:true`, `failures:0`, `lastError:null`); - session faces bound for both panes (`{path:"bound", reference:true, hasSession:true}`), `paneDebug.last` non-null (prepareNative ran); - real transcripts rendered (no `Session ended` hint); - zero plugin console warnings/errors (the known copied-session mirror 404s excluded), zero pageerrors, zero unhandled rejections. Artifact re-hashed after the run: **unchanged** (`0c80e969…`); mirror copy matched; live profile installed plugin still `52CB7902…A07C7` (untouched); verifier host stopped, verifier ports free. **SIGN-OFF:** `dsh-split-screen` 1.6.0 at `lib/client.js` SHA256 `0c80e96984221349eda6d69f78cdc5d56342f4cfb0ebd1a5285e590f487096f2` is **verified on `@deepseek-ai/dsh-desktop` 0.2.1-rc.2** (the user's live host) with the full 99-check suite on its only-code-different predecessor `b24eca13` plus this 10/10 static+one-boot re-check, and remains consistent with the 0.2.0 baseline. Cleared for the install/release recommendation, subject only to the residual risks listed in §10 (notably: no live streaming turn, no pending-interaction card, the known M5 composer editor-root architectural caveat, and 0.2.0 live frame covered by unit tests rather than re-run end-to-end). > **Superseded by §14.** The extended live verification run afterwards found a second real defect in the in-pane pending-interaction card on this artifact (`useQuestionCard is not a function`). The shipped artifact is the fixed `lib/client.js` SHA256 `8306116804f96c358b22a17dea01f44c353bac3da111d2d358a52ecac0d95ba8` (414,427 B / 7,935 lines), which PASSES §14's checks — the record above stands for `0c80e969…` only. --- ## 14. Extended live verification (streaming / pending card / 0.2.0) — signed off 2026-10-19 **Shipped artifact (frozen; hash checked before and after every run):** `lib/client.js` SHA256 `8306116804f96c358b22a17dea01f44c353bac3da111d2d358a52ecac0d95ba8`, **414,427 bytes / 7,935 lines**; `package.json` 1.6.0. Delta vs `0c80e969…`: composer-chain election/props merge in `ChatPane` (inject face in stock order + `renderSlot` dispatchers + data-slot stamp), new pure `electComposerChain`/`composerChainProps` helpers, and store/inject merges for the composer bar and `conversation.input.model/plan/attachments` seats; `tests/safety.mjs` 117 checks; README/CHANGELOG wording. No dependency/build change; contract unchanged. **Environment (isolated, no live-app contact):** real `@deepseek-ai/dsh-desktop` 0.2.1-rc.2 host in Node mode (Route B) against a copied scratch home `` and mirror profile `/profiles/`; separate `` for the 0.2.0-rc.2 CLI mirror on a verifier port. Read-only copied sessions; no message was sent in the live app/profile ``; the plugin source was never edited. ### 14.1 CHECK 1 — streaming turn + Stop inside a pane: **PASS (10/10)** Fresh scratch session in pane 0 (created via the stock *New Session* action; its id is `session-`), one short prompt asking for a numbered list. Observed: - assistant text grew **incrementally** in that pane: measured growth deltas of **+69, +67, +71 chars** at ≈0.01 s / 0.75 s / 1.36 s after send (streaming, not one render at the end); - the **Stop** control appeared (`aria-label="Stop generating"`) and was clicked while the turn was running (1.36 s, pane text 403 chars); no further growth after Stop (stable at 367 chars over 2.5 s), Stop disappeared; - output landed in pane 0 only — the other pane's text length was unchanged ([5,002] chars before/after); the stock view behind the overlay mirrors the same current session (expected) but the covered pane was not written; - **zero pageerrors, zero console warnings/errors** (known copied-session mirror 404s excluded). Token cost: the whole extended round (2 short streaming turns, 2 ask-card turns, plus two provider-retry attempts on one session) cost **a negligible provider charge** on the copied test account (session cost counter, scratch home); turns were stopped early to keep this minimal. ### 14.2 CHECK 2 — native `ask_user_question` card inside a pane: **FAIL on `0c80e969…` → fix → PASS on `83061168…` (14/14)** **FAIL (artifact `0c80e969…`):** prompt in the pane composer asking the agent to call `ask_user_question('Proceed?', ['yes','no'])` → the agent called the tool and the turn stayed running/pending, but the stock `QuestionFlow` threw inside the pane: `TypeError: useQuestionCard is not a function` (at `…ui-user-questions/client.js`) → `[dsh-split-screen] pane component failed (rendering fallback)`; no `[data-question-key]` card appeared in any pane, and the user could not answer (deterministic, reproduced twice; evidence `live-final.json`, `ask-repro.json`, screenshots in `/evidence/`). Root cause: `ChatPane`'s manual composer-chain render omitted the elected entry's `entryInjectProps`, so the stock entry's keyed `questionCard` face never reached `QuestionFlow` as `useQuestionCard` (the generic `renderSlotChain` path already merged it). Reported to the Lead; implementer fixed the composer-chain merge. **PASS (artifact `83061168…`):** the same scenario now yields a **native question card inside the pane**: element `[data-question-key]` in the pane's composer area, header **“Confirm — Proceed?”**, options **“1 yes — Proceed with the next step.” / “2 no — Do not proceed.”**, plus Skip/Submit controls; console clean. `window.__dshSplitView.composerChain.last` at the card: `{entries: 3, pendingKind: "question", elected: "QuestionComposer", electionError: null, store: true, inject: true}` (election + inject face present). The card also rendered after a **reload while still pending**, and answering it worked: selected “yes” → **Submit** → card disappeared, the turn resumed (pending interaction cleared), other pane unchanged, **zero `useQuestionCard` errors, zero pane-failed warnings, zero console issues, zero pageerrors** (screenshots `ask-card-fixed-card.png`, `ask-card-fixed-answered.png`). ### 14.3 CHECK 3 — 0.2.0-rc.2 live backward compatibility: **PASS (9/9)** 0.2.0-rc.2 CLI mirror (`dsh --version` = `0.2.0-rc.2`; boot rev `6dbd6dbaa406`) on a separate scratch home, with only the shipped 1.6.0 artifact in the mirror profile and a persisted 2-leaf tree: - activated: 2 panes + overlay, `health()` `active:true`, geometry ok, `blockedReason:null`, `failures:0`; - real transcripts in both panes (3,831 / 4,130 chars), no `Session ended` hint; - session faces bound (`{path:"bound", hasSession:true}`; the debug `reference` flag is ownership-only, see 14.4); - **zero** `renderFactorySlot` / `renderSlot` / `renderSlotChain` / `useQuestionCard` / `is not a function` / `pane component failed` / geometry-warning / `#185` / max-update-depth counters, zero pageerrors, zero console warnings; `hostRelease` is `null` on a CLI mirror (expected — no untested-host note). ### 14.4 CHECK 4 — acceptance re-confirmation on the shipped artifact Re-ran the recorded acceptance subset on `83061168…` against the real 0.2.1 host: 2-pane and nested 3-pane activation (panes 2/3, overlay 1, geometry ok, **zero** “could not be measured”), real transcripts, fresh runtime pane via split-picker, kill switch **8/8**, crash-loop guard + Retry **5/5**, corrupted/duplicate/oversized/deep/wide tree sanitization **12/12**, B3 invisible-active chords (not swallowed when geometry blocked) — all pass, with zero pane-component dispatcher errors and zero pageerrors. Two recorded nuances (not defects): 1. **Face debug flag**: after the composer-chain refactor, `composerChain.panes.faces[sid].reference` can be `false` while `hasSession:true` and `path:"bound"` — it reports whether this pane owns the retained reference; a session already bound by the stock main view is borrowed instead of re-retained. The pane still renders the real transcript and there are no errors. (The scenario check originally required `reference:true`; that assertion is what shows as the lone “fail” in raw `fix-83061168/scen-{seed2,seed3,dynamic}.json` — interpretation corrected here.) 2. **Alt+T TUI toggle on a heavily mutated session**: the scenario-level TUI assertion failed on the copied session that had absorbed the live streaming/ask-card test turns (its pane showed a fallback-style composer). An independent clean-session probe (fresh session + untouched copied session) shows **Alt+T works**: focusing a pane and pressing it swaps the Lexical composer for `textarea.dsv-tui-input` and pressing again swaps back, with the mode icon updating (`tui-probe2.json`). Not a code regression. **Static A on the shipped artifact:** hash matched before/after; `node --check` exit 0 (`lib/client.js`, `tests/smoke.mjs`, `tests/safety.mjs`); `npm test` exit 0 = **133 ok / 0 not ok** (16 smoke + 117 safety); contract unchanged (loader id, `exports.inject` = `["slots","sessions","locale","uiSession"]`, `dsh.client.inject` unchanged, **no dependencies/devDependencies, no build step**); `TESTED_RELEASES` includes `0.2.0-rc.2` and `0.2.1-rc.2`; repository metadata only added `homepage`/`bugs` (+ a CI workflow) — no behavior change. ### 14.5 Side effects & limits Side effects: artifact SHA256 unchanged before/after every run; live profile installed plugin still `52CB7902…A07C7` (313,427 B, untouched); no live app/profile contact; all verifier servers stopped and all verifier ports free afterwards. Limits: only the generic question card was exercised (approval/plan-review variants share the same composer takeover but were not separately validated); streaming was a short turn, not a long soak (the 9-min flat-heap soak in §7 remains the memory evidence); the 0.2.0 run used the CLI web mirror, not the 0.2.0 desktop composition; the two nuances in 14.4 are recorded honestly. **Final sign-off:** `dsh-split-screen` 1.6.0 at `lib/client.js` SHA256 `8306116804f96c358b22a17dea01f44c353bac3da111d2d358a52ecac0d95ba8` is **verified on the real `@deepseek-ai/dsh-desktop` 0.2.1-rc.2 host** (persisted splits, live streaming + Stop, in-pane `ask_user_question` card render/answer/resume, safety layer, corrupt-tree handling) and **backward-compatible on 0.2.0-rc.2**, with the residual caveats in §10/§14.5. Cleared for the public 1.6.0 release. **Evidence (publication placeholders):** `/evidence/ask-card-fixed.json` (+ `-card.png`, `-answered.png`), `stream-clean-fixed.json` (+ `.png`), `compat020.json` (+ `.png`), `fix-83061168/scen-*.json`, `live-final.json` + `ask-repro.json` (the FAIL on `0c80e969…`), `tui-probe2.json`, `server020-verifier.log`; scripts `ask_card_new.py`, `stream_clean.py`, `compat020.py`, `tui_probe2.py` in `/scripts/`.