# SPIKE 2026-10-19 — does `dsh-split-view` work on the user's actual DSH 0.2.1-rc.2? **Run:** isolated spike · **Candidate:** `dsh-split-view` npm **0.2.7** (the top "adopt as base" candidate from [RESEARCH-2026-10-19.md](RESEARCH-2026-10-19.md)) **Host tested:** isolated spike copy of the diagnostician's harness — DSH CLI **0.2.1-rc.2** (`@deepseek-ai/dsh@0.2.1-rc.2`, release commit `332895e`, web rev `902dcf0c0d5b`), `DSH_HOME=`, profile mirror + a clean minimal profile, server `http://127.0.0.1:` (isolated scratch port), headless Chromium via Playwright. **Isolation:** independent copies only. ``, ``, ports /, `` and the live app/host on were **not touched**. Test server stopped at the end; artifacts left in `...\spike\`. --- ## Verdict (short) **PARTIALLY WORKS — but do NOT replace `dsh-split-screen` with it today.** - ✅ It **installs without a peer-gate skip and boots cleanly** on 0.2.1-rc.2 (`dsh-split-view` entry present in the boot graph, rev `e5391e27a486`, no `skipping profile bundle`), and the split UI works: **panes are real same-origin iframes, each running a full stock DSH client**, with per-pane titlebars, split-right/down, focus highlight, resizable splits, and a working composer (typed text confirmed in 2 panes). - ✅ Opening an existing conversation **inside a pane's own sidebar** renders full history, Chat/Trajectory tabs and the composer: **0 console messages, 0 page errors** in that path. - ❌ Its core promise — *each pane is pinned to its own session and the layout is restored on reload* — **is broken on 0.2.1-rc.2**: every programmatic pane-session seed throws `RangeError: Maximum call stack size exceeded` (logged as `dsh-split-view: pane session seed unavailable, pane falls back`), plus cascading `[client-store] subscriber failed` / `[session-controller] reference sources subscriber failed` / `Sidebar Session opening failed: … Session reference "…" is released`. Panes therefore start blank and **after every page reload the restored panes lose their conversations** (the layout tree is restored, the session binding is not). - ✅ No host crash, no OOM in the short window: 0 page errors, heap flat at **64 MB with 2 panes over 45 s idle** (82 MB with 3 panes over 60 s in the full mirror profile). Not a leak test, but the failure mode is *broken session binding*, not the freeze/storm/OOM class we are replacing. - **Would I recommend replacing `dsh-split-screen` with this today? NO.** It is the closest alternative and the architecture behaves materially better (iframe isolation, real native clients, no page errors), but on the user's 0.2.1-rc.2 build panes cannot hold/reload their sessions without manual per-pane sidebar clicks, and the console is full of session-store errors. **Fix-forward candidate, not a drop-in.** --- ## 1. Exact setup / commands The diagnoser's harness was reused as-is except for a full copy of the scratch home and a new profile copy: ```powershell $base = '' # 1) independent copy of the scratch home (base scratch home untouched) robocopy /E # 2) install candidate into the SPIKE profile only. # The 0.2.1 CLI path (intended route) FAILED on the copied home: node "\dsh021\node_modules\@deepseek-ai\dsh\lib\bin.js" plugin --profile add dsh-split-view # -> [ERR_PNPM_UNEXPECTED_STORE] node_modules linked from , # pnpm now wants (pnpm forwarded by `dsh plugin`; store # mismatch is an artifact of copying a pnpm-linked home; `--store-dir` / # npm_config_store_dir did not resolve it either) # => documented fallback: manual npm-tarball install into the throwaway profile Invoke-WebRequest 'https://registry.npmjs.org/dsh-split-view/-/dsh-split-view-0.2.7.tgz' -OutFile "\spike\npm\dsh-split-view-0.2.7.tgz" tar.exe -xzf "\spike\npm\dsh-split-view-0.2.7.tgz" -C "\spike\npm\x" Copy-Item "\spike\npm\x\package" "\profiles\\node_modules\dsh-split-view" -Recurse # then in the spike profile package.json only: # - remove "dsh-split-screen" from dependencies + dsh.profile.bundles (avoids two # root-slot plugins fighting; live/base profile untouched) # - add "dsh-split-view": "0.2.7" + "dsh-split-view" to dsh.profile.bundles # - write with UTF-8 NO BOM (PowerShell Set-Content wrote a BOM -> # `SyntaxError: Unexpected token ''` in readProfileManifest on first launch) # 3) clean control profile (also in , for confound-free result) # \profiles\\package.json: # dependencies: { "dsh-split-view": "0.2.7" } # dsh.profile.bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-split-view"] # 4) launch (Route A, my port) $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 # -> dsh web: http://127.0.0.1:/?token= (log: spike\evidence\server.log) # -> clean profile: same command --profile (log: spike\evidence\server-clean.log) ``` Install facts (from the tarball / boot): `dsh-split-view@0.2.7`, MIT, 8.9 MB tarball (demo assets included), **zero runtime dependencies**, `dsh.bundle.patch = ./cordis.patch.yml`, `dsh.client.platform = web`, inject `[@deepseek-ai/dsh-client-runtime, dsh-client-locale, dsh-client-ui-theme, dsh-client-ui-settings, dsh-client-ui-settings-general]`, `immediately: true`. **Boot result:** plugin mounted cleanly. Boot graph entry: ```json {"id":"dsh-split-view","url":"plugins/??dsh-split-view/client.js&rev=e5391e27a486", "rev":"e5391e27a486", "inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-locale", "@deepseek-ai/dsh-client-ui-theme","@deepseek-ai/dsh-client-ui-settings", "@deepseek-ai/dsh-client-ui-settings-general"],"immediately":true} ``` No `skipping profile bundle`, no peer/version-exemption message, no failed plugin rows, **0 page errors** at boot. (`/plugins/events` shows one `net::ERR_ABORTED` on reload — benign SSE reconnect.) --- ## 2. Feature checklist (clean profile = base + web-app + dsh-split-view only) | Check | Result | Evidence | |---|---|---| | Plugin loads on 0.2.1-rc.2, no gate skip | ✅ | boot graph entry above; `spike\evidence\clean-clean.json` (`graph`) | | Split UI appears | ✅ | pane titlebar buttons found in top DOM: `复制会话信息 / 向右分屏(Ctrl+Shift+D)/ 向下分屏(Ctrl+Shift+V)/ 放大(Ctrl+Shift+Enter)/ 刷新该面板 / 关闭面板(Ctrl+Shift+X)` | | 2 panes / 3 panes | ✅ 2 after one split-right; ✅ 3 after two (full-mirror run) | `clean-2-split.png`; `splitview-after-split2.png` | | Each pane is a **same-origin iframe with a real client** | ✅ | iframes: `/?dshPane=pcskzybr7mvewf7on&dshNew=1` and `/?dshPane=p0ve5ujwxmvewfhhy&dshWorkspace=…`; inside: full app chrome, sidebar, `contenteditable` composer, readyState complete | | Pane composer usable | ✅ typed `spike clean probe` into **both** panes (value read back) | `clean-clean.json` → `typing` | | Existing-session history inside a pane | ✅ **via the pane's own sidebar**: opened `an existing session`, got title, `Chat / Trajectory` tabs, 2-turn history text, composer; **0 console messages** | `clean-inpane.json`, `clean-pane0.png` | | Pane pinned to its own session (plugin's seed path) | ❌ `RangeError: Maximum call stack size exceeded` in `ClientSessions.retain` → `dsh-split-view: pane session seed unavailable, pane falls back` | console logs below | | Layout persistence across reload | ⚠️ **layout tree + iframes restored** (`dsh-split:layout` with 2 leaves, their sessions/workspaces), but **sessions do not re-bind** — panes reload to blank hero `Into the Unknown` | `clean-clean.json` → `layoutBefore`, `framesReload`; `clean-5-reload.png` | | Per-pane questions/approvals | ⏳ not exercised (would need a live model turn; no turn was sent to avoid spend/state) | — | | Console/page errors | 0 **page errors**; 46 console lines / 5 unique: 1 error ("Sidebar Session opening failed … released"), 3 warning/error "Maximum call stack size exceeded" classes | `clean-clean.json` → `consoleUnique` | | `skipping profile bundle` / peer gate | none | `server-clean.log` contains only the token URL | | Heap over 45 s idle, 2 panes | flat **64,000,000 B** (61 MiB) at 3 samples — no growth in window | `clean-clean.json` → `heap` | | Heap over 60 s idle, 3 panes (full mirror) | flat **81,400,000 B** (78 MiB) | `splitview-panes.json` → `idleHeapSamples` | Screenshots (all in `\spike\evidence\`): `clean-1-load.png` (single pane), `clean-2-split.png` (two iframe clients side by side, blue focus border), `clean-3-typed.png`, `clean-5-reload.png`, `clean-pane0.png` (pane showing loaded history), `splitview-after-split2.png` (3 panes), `splitview-history.png`, `splitview-reload.png`. --- ## 3. The failure, exactly Clean profile (`base + web-app + dsh-split-view` — no other third-party plugin loaded), after a split and again after reload: ```text [warning] dsh-split-view: pane session seed unavailable, pane falls back RangeError: Maximum call stack size exceeded at ClientSessions.retain (…/plugins/??…,dsh-split-view/client.js,@deepseek-ai/dsh-api-remote-access/client.js,…) [error] [client-store] subscriber failed: RangeError: Maximum call stack size exceeded at reconcile (…/plugins/??@deepseek-ai/dsh-api-gateway/client.js,…) [error] [session-controller] reference sources subscriber failed: RangeError: Maximum call stack size exceeded [error] Sidebar Session opening failed: Error: Session reference "session-…-cc431f56774d" is released at ClientSessionReference.release (…dsh-split-view/client.js…) ``` Reproduced in the full mirror profile too (100 console lines: 20 error / 80 warning), so **this is the plugin's own 0.2.1 incompatibility**, not interference from the other 5 mirror plugins. Same class as the known 0.2.x breakages in the research report (0.2.0 removed `sessions.open`; 0.2.1 apparently changed the `ClientSessions.retain`/reference-source semantics enough that the plugin's seeding path recurses until stack overflow and then "falls back" to a blank session). The plugin's latest release (0.2.7, 2026-10-19) targeted 0.2.0/0.2.1-alpha, **not 0.2.1-rc.2**; no 0.2.1-rc.2 fix has shipped. Practical user impact on this build: 1. Splitting works, but the new pane is a **blank new conversation**, not a pinned copy of the conversation you dragged/split from. 2. **Reload loses pane conversations** (layout restores, sessions fall back to blank) — the user must re-open each conversation manually from each pane's own sidebar. 3. Each iframe contains its **own full app chrome** (sidebar, header) — the top window is a thin container; so "global sidebar drag into a pane" (our plugin's core interaction) is not how this plugin works, and duplicated chrome makes panes heavier/noisier visually (screenshot `clean-2-split.png`). 4. Console spam from session-store errors on every seed; no page errors/crash observed. --- ## 4. What this means for the replacement decision - The **iframe architecture itself behaves**: it survives 0.2.1-rc.2, renders real native clients with working composers and history, and showed no memory growth or host crash in a 45–60 s window. That supports the RESEARCH recommendation that iframe isolation is the right technique — it is materially safer than in-process re-hosting (our plugin / `dsh-split-panes`). - But `dsh-split-view@0.2.7` **fails the specific contract we would be replacing**: panes pinned to sessions and restored after reload. Adopting it today would trade our freeze/OOM bug for "panes lose their conversation on every reload + console error spam" — a regression for the user's workflow. - Cheapest next step before a verdict of "adopt": **fix-forward test** — the plugin is MIT and ships readable `lib/client.js`; the seed path needs to be adjusted for 0.2.1's session-reference semantics (likely route navigation through the pane client's own UI/`uiWorkspace` path rather than a nested `retain`; the in-pane sidebar click works with 0 errors, which proves the per-pane client itself can render and navigate sessions). A patched local build could be re-spiked in <1 h with the same harness. - Fallback candidate `@ai-eks/dsh-docking-layout` (claims `^0.2.1-rc.2`) was **not** spiked — timebox; it remains the next test if the user wants a second iframe candidate. **Would I recommend replacing `dsh-split-screen` with `dsh-split-view` today?** **No.** Would I recommend it as the base to fix-forward / re-test once its 0.2.1-rc.2 seeding is patched? **Yes — it is the strongest option we have found**, ahead of `dsh-split-panes` (broken on 0.2.x, no npm, unmaintained) and layout libraries (which don't address the re-hosting layer). --- ## 5. Limitations of this spike - Headless Chromium against the **CLI web mirror** on port , not the Electron desktop window (leaving the desktop-host route to the verifier). The client code path is the same 0.2.1-rc.2 web bundle, but Electron-specific behaviour (window sizing, native dialogs) is untested. - No model turn was sent: streaming render, per-pane questions/approvals, and long-run memory under active streaming are **untested** (would need API spend; also the seeding failure makes per-pane turn execution moot until fixed). - Memory observation is 45–60 s idle only — not an OOM proof; long-run leak behaviour remains unverified. - The install was manual (tarball + package.json/bundles edit) because `dsh plugin`'s forwarded pnpm hit a store-location error on the *copied* home; this is a harness artifact, not a plugin defect. On a fresh machine `dsh plugin --profile web add dsh-split-view` should work. - Other mirror-profile plugins were removed from the spike profile for the main run (clean profile control); the full-mirror run is also reported and shows the same defect. ## 6. Artifacts ``` \spike\ probe_recon.py / probe_panes.py / probe_clean.py / probe_inpane.py evidence\server.log, server-clean.log evidence\splitview-recon.png|json, splitview-panes.json, splitview-after-split1|2.png, splitview-history.png, splitview-reload.png, splitview-typed.png evidence\clean-1-load.png, clean-2-split.png, clean-3-typed.png, clean-4-sidebar.png, clean-5-reload.png, clean-pane0.png, clean-clean.json, clean-inpane.json \ (throwaway home copy) profiles\ (mirror + split-view, split-screen removed) profiles\ (minimal: base + web-app + split-view) ``` Server stopped; no listeners on remain. No live app/profile/host was touched, no source edits to `dsh-split-screen`, no `dsh plugin add/remove` against any live profile.