@hienlh/ppm 0.18.11 → 0.18.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +81 -23
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +1 -1
  5. package/package.json +1 -1
  6. package/.spike/cdp-client.ts +0 -116
  7. package/.spike/cleanup-ppm-workspace.mjs +0 -44
  8. package/.spike/group-protected-probe.ts +0 -21
  9. package/.spike/pip-eval.ts +0 -17
  10. package/.spike/pip-page-helpers.js +0 -119
  11. package/.spike/pip-results-editor-glue.json +0 -105
  12. package/.spike/pip-results-editor.json +0 -158
  13. package/.spike/pip-results-input-isolation.json +0 -28
  14. package/.spike/pip-results-key-probe.json +0 -58
  15. package/.spike/pip-results-listener-survival.json +0 -98
  16. package/.spike/pip-results-listeners.json +0 -36
  17. package/.spike/pip-results-pagehide.json +0 -92
  18. package/.spike/pip-results-resize.json +0 -104
  19. package/.spike/pip-results-terminal-debug.json +0 -99
  20. package/.spike/pip-results-terminal-v2.json +0 -191
  21. package/.spike/pip-results-terminal.json +0 -220
  22. package/.spike/pip-results-xterm-trace.json +0 -73
  23. package/.spike/pip-spike-common.ts +0 -83
  24. package/.spike/pip-spike-editor-glue.ts +0 -82
  25. package/.spike/pip-spike-editor.ts +0 -129
  26. package/.spike/pip-spike-input-isolation.ts +0 -38
  27. package/.spike/pip-spike-key-probe.ts +0 -34
  28. package/.spike/pip-spike-listener-survival.ts +0 -54
  29. package/.spike/pip-spike-listeners.ts +0 -19
  30. package/.spike/pip-spike-pagehide.ts +0 -59
  31. package/.spike/pip-spike-resize.ts +0 -42
  32. package/.spike/pip-spike-setup.ts +0 -63
  33. package/.spike/pip-spike-terminal-debug.ts +0 -81
  34. package/.spike/pip-spike-terminal-v2.ts +0 -139
  35. package/.spike/pip-spike-terminal.ts +0 -169
  36. package/.spike/pip-spike-xterm-trace.ts +0 -36
  37. package/.spike/probe-drag-memo-error.mjs +0 -67
  38. package/.spike/read-dev-db.ts +0 -15
  39. package/.spike/s.err +0 -63
  40. package/.spike/scratch-editor-target.ts +0 -7
  41. package/.spike/smoke-unified-chrome-pip.mjs +0 -353
  42. package/.spike/tunnel.err +0 -63
  43. package/.spike/vite-restart.err +0 -663
  44. package/.spike/w.err +0 -571
@@ -1,129 +0,0 @@
1
- // Stage 3: move the live Monaco editor tab wrapper into a Document PiP window and test it.
2
- import { connectApp, saveResults, sleep } from "./pip-spike-common";
3
-
4
- const TAB = "editor:.spike/scratch-editor-target.ts";
5
- const Q = `document.querySelector('[data-tab-pool-id="${TAB}"]')`;
6
- const W = `window.__spike.ew`;
7
- const E = `window.__spike.ed`;
8
- const R: Record<string, unknown> = {};
9
- const h = await connectApp();
10
- const { ev, cdp, sid } = h;
11
- await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: true }, sid);
12
-
13
- await ev(`(() => { const S = window.__spike; if (S.pip && !S.pip.closed) { if (S.ew && S.ew.ownerDocument !== document) S.moveBack(S.ew); S.pip.close(); } S.pip = null; S.results = {}; return 'reset'; })()`);
14
- await h.openViaUrl("/project/ppm/editor/.spike/scratch-editor-target.ts");
15
- await h.waitFor(`${Q} && ${Q}.style.display !== 'none' && ${Q}.querySelector('.monaco-editor .view-lines') && window.__spike.findEditor(${Q})`, 30000, "editor visible");
16
- await ev(`window.__spike.ew = ${Q}; window.__spike.ed = window.__spike.findEditor(window.__spike.ew); 'held'`);
17
- const wordWrapExpr = `${E}.getOption(window.monaco.editor.EditorOption.wordWrap)`;
18
-
19
- async function typeAt(targetSid: string, text: string) {
20
- await cdp.typeText(targetSid, text);
21
- await sleep(300);
22
- }
23
- // Baseline in main: put cursor at 1:1, type marker (trusted)
24
- await ev(`${E}.setPosition({ lineNumber: 1, column: 1 }); ${E}.focus(); 'f'`);
25
- await typeAt(sid, "MAIN1 ");
26
- await sleep(2000); // let the 1s autosave + dirty-title re-render settle before the move
27
- R.baseline = await ev(`({ line1: ${E}.getModel().getLineContent(1), layoutW: ${E}.getLayoutInfo().width, layoutH: ${E}.getLayoutInfo().height, wordWrap: ${wordWrapExpr}, minimapCanvases: ${W}.querySelectorAll('.minimap canvas').length })`);
28
-
29
- R.move = await h.runGesture("pip-editor", `async () => {
30
- const S = window.__spike; const w = ${W}; const ed = ${E};
31
- const pip = await documentPictureInPicture.requestWindow({ width: 900, height: 600 }); S.pip = pip;
32
- const styles = S.copyStyles(pip.document); const moved = S.moveToPip(pip, w); S.pinToPip(w); S.installClickProbes(pip, w);
33
- await new Promise(r => setTimeout(r, 800));
34
- const vl = w.querySelector('.view-lines'); const mm = w.querySelector('.minimap canvas');
35
- return { styles, moved, pipSize: [pip.innerWidth, pip.innerHeight], viewLines: vl.children.length, firstLineText: vl.children[0]?.textContent, layout: [ed.getLayoutInfo().width, ed.getLayoutInfo().height],
36
- minimap: mm ? { w: mm.width, h: mm.height } : null, editorRect: (() => { const r = ed.getDomNode().getBoundingClientRect(); return [r.width, r.height]; })(), monacoTheme: pip.getComputedStyle(w.querySelector('.monaco-editor')).backgroundColor, mainBg: getComputedStyle(document.body).backgroundColor };
37
- }`);
38
- const pipSid = await h.attachPip();
39
- R.pipAttached = !!pipSid;
40
- if (!pipSid) throw new Error("no pip target");
41
- await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: true }, pipSid);
42
- await sleep(600);
43
- R.shotInPip = await h.pipShot(pipSid, "spike-02-editor-in-pip.png");
44
-
45
- // Mouse: trusted click at line 3 in the PiP → cursor moves? then drag-select on line 2
46
- R.mouseInPip = await (async () => {
47
- const geo = await cdp.eval<any>(pipSid, `(() => { const lines = [...document.querySelectorAll('.view-line')]; const l3 = lines.find(l => l.textContent.includes('spikeLine3')) || lines[2]; if (!l3) return null; const r = l3.getBoundingClientRect(); return { x: r.x + 40, y: r.y + r.height / 2, text: l3.textContent }; })()`);
48
- if (!geo) return { error: "no .view-line in pip doc", inPip: await ev(`${W}.ownerDocument !== document`) };
49
- await cdp.click(pipSid, geo.x, geo.y);
50
- await sleep(300);
51
- const pos = await ev<any>(`(() => { const p = ${E}.getPosition(); return [p.lineNumber, p.column]; })()`);
52
- const g2 = await cdp.eval<any>(pipSid, `(() => { const l = [...document.querySelectorAll('.view-line')].find(l => l.textContent.includes('spikeLine2')); const r = l.getBoundingClientRect(); return { x: r.x + 4, y: r.y + r.height / 2 }; })()`);
53
- await cdp.send("Input.dispatchMouseEvent", { type: "mousePressed", x: g2.x, y: g2.y, button: "left", clickCount: 1 }, pipSid);
54
- for (let i = 1; i <= 8; i++) await cdp.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: g2.x + i * 20, y: g2.y, button: "left", buttons: 1 }, pipSid);
55
- await cdp.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: g2.x + 160, y: g2.y, button: "left", clickCount: 1 }, pipSid);
56
- await sleep(300);
57
- const sel = await ev<string>(`${E}.getModel().getValueInRange(${E}.getSelection())`);
58
- await h.pipShot(pipSid, "spike-02b-editor-in-pip-selection.png");
59
- return { clickedLineText: geo.text, positionAfterClick: pos, dragSelection: sel, dragPass: sel.length > 0 };
60
- })();
61
-
62
- // Keyboard: trusted typing in PiP at 1:1
63
- await ev(`${E}.setPosition({ lineNumber: 1, column: 1 }); 'p'`);
64
- await cdp.eval(pipSid, `document.querySelector('.monaco-editor textarea').focus(); document.activeElement.className`);
65
- await typeAt(pipSid, "PIP2 ");
66
- R.typingInPip = await ev(`({ line1: ${E}.getModel().getLineContent(1), pass: ${E}.getModel().getLineContent(1).startsWith('PIP2 MAIN1 ') })`);
67
- R.shotTyped = await h.pipShot(pipSid, "spike-02c-editor-in-pip-typed.png");
68
-
69
- // Resize: wrapper style (automaticLayout RO path) + pip resizeTo via trusted click
70
- R.resizeViaStyle = await ev(`(async () => { const w = ${W}; const ed = ${E}; const before = [ed.getLayoutInfo().width, ed.getLayoutInfo().height]; w.style.height = '50%'; w.style.width = '70%';
71
- await new Promise(r => setTimeout(r, 800)); const after = [ed.getLayoutInfo().width, ed.getLayoutInfo().height]; w.style.height = '100%'; w.style.width = '100%'; await new Promise(r => setTimeout(r, 800));
72
- return { before, after, restored: [ed.getLayoutInfo().width, ed.getLayoutInfo().height], relayout: before[0] !== after[0] }; })()`);
73
- R.resizeViaWindow = await (async () => {
74
- const pt = await cdp.eval<any>(pipSid, `(() => { const b = document.createElement('button'); b.id = 'rz'; b.textContent = 'rz'; b.style.cssText = 'position:fixed;right:4px;bottom:4px;z-index:99999'; b.onclick = () => window.resizeTo(700, 450); document.body.append(b); const r = b.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; })()`);
75
- const before = await ev<any>(`[${E}.getLayoutInfo().width, ${E}.getLayoutInfo().height]`);
76
- await cdp.click(pipSid, pt.x, pt.y);
77
- const s: unknown[] = [];
78
- for (const ms of [300, 1000, 3000]) { await sleep(ms); s.push(await ev(`({ afterMs: ${ms}, layout: [${E}.getLayoutInfo().width, ${E}.getLayoutInfo().height], pip: [window.__spike.pip.innerWidth, window.__spike.pip.innerHeight] })`)); }
79
- await cdp.eval(pipSid, `document.getElementById('rz').remove(); 'x'`);
80
- return { before, samples: s };
81
- })();
82
- R.shotResized = await h.pipShot(pipSid, "spike-02d-editor-in-pip-resized.png");
83
-
84
- // React onClick in PiP: the toolbar "Wrap" button toggles wordWrap through a zustand store → Monaco option
85
- R.reactClickInPip = await (async () => {
86
- const pt = await cdp.eval<any>(pipSid, `(() => { const b = [...document.querySelectorAll('button')].find(b => b.textContent.trim() === 'Wrap'); if (!b) return null; const r = b.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; })()`);
87
- if (!pt) return "no Wrap button in pip";
88
- const before = await ev(wordWrapExpr);
89
- await ev(`window.__spike.probe = { mainDocClick: 0, pipDocClick: 0, wrapperClick: 0, reactRootClick: 0 }; 'r'`);
90
- await cdp.click(pipSid, pt.x, pt.y);
91
- await sleep(600);
92
- return { wordWrapBefore: before, wordWrapAfter: await ev(wordWrapExpr), probe: await ev(`({ ...window.__spike.probe })`) };
93
- })();
94
-
95
- // Widgets: trigger suggest (Ctrl+Space) in PiP and see where the widget renders
96
- R.suggestWidget = await (async () => {
97
- await ev(`${E}.setPosition({ lineNumber: 7, column: 1 }); 'p'`);
98
- await cdp.eval(pipSid, `document.querySelector('.monaco-editor textarea').focus(); 'f'`);
99
- await cdp.typeText(pipSid, "spi");
100
- await sleep(1200);
101
- const r = await ev(`(() => { const w = ${W}; const sw = w.querySelector('.suggest-widget'); const mainSw = document.querySelector('.suggest-widget');
102
- return { inWrapper: !!sw, visible: sw ? sw.classList.contains('visible') : null, inMainDoc: !!mainSw, overflowContainer: !!w.querySelector('.overflowingContentWidgets'), fixedOverflow: ${E}.getOption(window.monaco.editor.EditorOption.fixedOverflowWidgets) }; })()`);
103
- await h.pipShot(pipSid, "spike-02e-editor-in-pip-suggest.png");
104
- await cdp.pressKey(pipSid, "Escape", "Escape", 27);
105
- for (let i = 0; i < 3; i++) await cdp.pressKey(pipSid, "Backspace", "Backspace", 8);
106
- return r;
107
- })();
108
-
109
- // Move back, then undo history: Ctrl+Z x2 should remove "PIP2 " then "MAIN1 "
110
- R.pullbackAttemptsWhileInPip = await ev(`({ attempts: window.__spike.pullbackAttempts, stillInPip: ${W}.ownerDocument !== document })`);
111
- R.back = await ev(`(async () => { const S = window.__spike; const w = ${W}; const r = S.moveBack(w); await new Promise(res => setTimeout(res, 800)); return { ...r, layout: [${E}.getLayoutInfo().width, ${E}.getLayoutInfo().height], line1: ${E}.getModel().getLineContent(1) }; })()`);
112
- await ev(`window.__spike.pip.close(); 'c'`);
113
- await sleep(500);
114
- await ev(`${E}.focus(); 'f'`);
115
- await cdp.pressKey(sid, "z", "KeyZ", 90, undefined, 2); // Ctrl+Z
116
- await sleep(300);
117
- const afterUndo1 = await ev(`${E}.getModel().getLineContent(1)`);
118
- await cdp.pressKey(sid, "z", "KeyZ", 90, undefined, 2);
119
- await sleep(300);
120
- const afterUndo2 = await ev(`${E}.getModel().getLineContent(1)`);
121
- await ev(`${E}.setPosition({ lineNumber: 1, column: 1 }); ${E}.focus(); 'f'`);
122
- await typeAt(sid, "BACK3 ");
123
- R.afterReturn = await ev(`(async () => { const S = window.__spike; const w = ${W}; const ed = ${E}; const btn = [...w.querySelectorAll('button')].find(b => b.textContent.trim() === 'Wrap'); const ww0 = ${wordWrapExpr}; btn && btn.click(); await new Promise(r => setTimeout(r, 500));
124
- return { afterUndo1: ${JSON.stringify(afterUndo1)}, afterUndo2: ${JSON.stringify(afterUndo2)}, undoPass: ${JSON.stringify(afterUndo2)}.startsWith('export const spikeLine1'), line1AfterTyping: ed.getModel().getLineContent(1), typingPass: ed.getModel().getLineContent(1).startsWith('BACK3 '), reactWrapToggle: { before: ww0, after: ${wordWrapExpr} } }; })()`);
125
- // restore wordWrap + undo the BACK3 edit so the scratch file autosaves clean-ish
126
- await ev(`(async () => { const w = ${W}; const btn = [...w.querySelectorAll('button')].find(b => b.textContent.trim() === 'Wrap'); if (${wordWrapExpr} !== 'off' && btn) btn.click(); ${E}.trigger('spike', 'undo', null); return 'ok'; })()`);
127
- await cdp.screenshot(sid, "C:/Users/PC/ppm/plans/reports/screenshots/spike-04-editor-after-return.png");
128
- await saveResults("pip-results-editor.json", R);
129
- cdp.close();
@@ -1,38 +0,0 @@
1
- // Debug: isolate "keys ignored" — round-tripped terminal vs fresh terminal, term.input() vs keyboard.
2
- import { connectApp, saveResults, sleep } from "./pip-spike-common";
3
-
4
- const R: Record<string, unknown> = {};
5
- const h = await connectApp();
6
- const { ev, cdp, sid } = h;
7
- await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: true }, sid);
8
-
9
- const T1 = `window.__spike.findTerminal(window.__spike.tw)`;
10
- // 1. term.input on the round-tripped terminal
11
- await h.openViaUrl("/project/ppm/terminal/1");
12
- await sleep(500);
13
- R.inputApiRoundTripped = await ev(`(async () => { const S = window.__spike; const t = ${T1}; S.dataLog = []; t.input('echo VIA-INPUT-API\\r', true);
14
- await new Promise(r => setTimeout(r, 1500)); return { dataLog: S.dataLog, echoed: S.termText(t, 80).includes('VIA-INPUT-API'), tail: S.termText(t, 3), rows: t.rows, cols: t.cols, visible: t.element.getBoundingClientRect().height }; })()`);
15
-
16
- // 2. Fresh terminal tab (never moved): trusted keys
17
- await h.openViaUrl("/project/ppm/terminal/2");
18
- const Q2 = `document.querySelector('[data-tab-pool-id^="terminal:2"]')`;
19
- await h.waitFor(`${Q2} && ${Q2}.querySelector('.xterm-helper-textarea')`, 15000, "terminal 2");
20
- await sleep(3000); // shell boot
21
- await ev(`window.__spike.tw2 = ${Q2}; window.__spike.t2 = window.__spike.findTerminal(window.__spike.tw2); window.__spike.dataLog2 = []; window.__spike.t2.onData(d => window.__spike.dataLog2.push(d)); 'ok'`);
22
- await ev(`window.__spike.tw2.querySelector('.xterm-helper-textarea').focus(); 'f'`);
23
- await cdp.typeText(sid, "echo FRESH-TERM");
24
- await cdp.pressKey(sid, "Enter", "Enter", 13, "\r");
25
- await sleep(1500);
26
- R.freshTerminalTrustedKeys = await ev(`(() => { const S = window.__spike; const t = S.t2; return { id: S.tw2.dataset.tabPoolId, dataLog: S.dataLog2.join(''), echoed: S.termText(t, 80).includes('FRESH-TERM'), tail: S.termText(t, 3), rows: t.rows, cols: t.cols }; })()`);
27
-
28
- // 3. Back to the round-tripped terminal: trusted keys with focus emulation on
29
- await h.openViaUrl("/project/ppm/terminal/1");
30
- await sleep(500);
31
- await ev(`window.__spike.dataLog = []; window.__spike.tw.querySelector('.xterm-helper-textarea').focus(); 'f'`);
32
- await cdp.typeText(sid, "echo RT-KEYS");
33
- await cdp.pressKey(sid, "Enter", "Enter", 13, "\r");
34
- await sleep(1500);
35
- R.roundTrippedTrustedKeys = await ev(`(() => { const S = window.__spike; const t = ${T1}; return { dataLog: S.dataLog.join(''), echoed: S.termText(t, 80).includes('RT-KEYS'), tail: S.termText(t, 3),
36
- textareaIsTermTextarea: t.textarea === S.tw.querySelector('.xterm-helper-textarea'), textareaDoc: t.textarea.ownerDocument === document, elementDoc: t.element.ownerDocument === document }; })()`);
37
- await saveResults("pip-results-input-isolation.json", R);
38
- cdp.close();
@@ -1,34 +0,0 @@
1
- // Debug: which keyboard events reach the xterm textarea (main doc) under CDP input; effect of focus emulation.
2
- import { connectApp, saveResults, sleep } from "./pip-spike-common";
3
-
4
- const TAB = "terminal:1@panel-8281c2e3";
5
- const Q = `document.querySelector('[data-tab-pool-id="${TAB}"]')`;
6
- const R: Record<string, unknown> = {};
7
- const h = await connectApp();
8
- const { ev, cdp, sid } = h;
9
-
10
- await ev(`(() => { const S = window.__spike; if (S.pip && !S.pip.closed) { if (S.tw && S.tw.ownerDocument !== document) S.moveBack(S.tw); S.pip.close(); } S.pip = null; return 'reset'; })()`);
11
- await h.openViaUrl("/project/ppm/terminal/1");
12
- await h.waitFor(`${Q} && ${Q}.style.display !== 'none'`, 15000, "terminal visible");
13
- await ev(`window.__spike.tw = ${Q}; (() => { const S = window.__spike; const ta = S.tw.querySelector('.xterm-helper-textarea'); S.keyLog = [];
14
- if (!S.keyProbe) { for (const t of ['keydown','keypress','input','beforeinput','keyup']) ta.addEventListener(t, e => S.keyLog.push(t + ':' + (e.key ?? e.data ?? '') + ':' + e.isTrusted)); S.keyProbe = true; }
15
- return 'probe'; })()`);
16
-
17
- async function probe(label: string) {
18
- await ev(`window.__spike.keyLog = []; window.__spike.dataLog = []; window.__spike.tw.querySelector('.xterm-helper-textarea').focus(); 'f'`);
19
- await cdp.typeText(sid, "ab");
20
- await sleep(300);
21
- R[label] = await ev(`({ hasFocus: document.hasFocus(), vis: document.visibilityState, active: document.activeElement && document.activeElement.className, keyLog: window.__spike.keyLog, dataLog: window.__spike.dataLog })`);
22
- }
23
- await probe("noEmulation");
24
- await cdp.send("Page.bringToFront", {}, sid);
25
- await sleep(300);
26
- await probe("afterBringToFront");
27
- await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: true }, sid);
28
- await sleep(300);
29
- await probe("withFocusEmulation");
30
- // Clean the typed junk from the shell line
31
- await cdp.pressKey(sid, "Escape", "Escape", 27);
32
- for (let i = 0; i < 8; i++) await cdp.pressKey(sid, "Backspace", "Backspace", 8);
33
- await saveResults("pip-results-key-probe.json", R);
34
- cdp.close();
@@ -1,54 +0,0 @@
1
- // Verify: do node-level event listeners survive adoption into a Document PiP window? Minimal, controlled.
2
- import { connectApp, saveResults, sleep } from "./pip-spike-common";
3
-
4
- const R: Record<string, unknown> = {};
5
- const h = await connectApp();
6
- const { ev, cdp, sid } = h;
7
-
8
- async function listenersOf(expr: string) {
9
- const r = await cdp.send("Runtime.evaluate", { expression: expr }, sid);
10
- if (!r.result?.objectId) return "no object";
11
- const l = await cdp.send("DOMDebugger.getEventListeners", { objectId: r.result.objectId }, sid);
12
- return (l.listeners as any[]).map((x) => `${x.type}${x.useCapture ? "(c)" : ""}`).sort();
13
- }
14
-
15
- // Fresh probe nodes in the main doc with listeners; a plain iframe-less popup for comparison is not possible w/o gesture, so PiP only.
16
- await ev(`(() => { const S = window.__spike; S.probeDiv = document.createElement('div'); S.probeTa = document.createElement('textarea'); S.probeDiv.append(S.probeTa);
17
- S.probeHits = { div: 0, ta: 0 }; S.probeDiv.addEventListener('click', () => S.probeHits.div++, true); S.probeTa.addEventListener('keydown', () => S.probeHits.ta++); S.probeTa.addEventListener('input', () => S.probeHits.ta++, true);
18
- document.body.append(S.probeDiv); return 'ok'; })()`);
19
- R.before = { div: await listenersOf("window.__spike.probeDiv"), ta: await listenersOf("window.__spike.probeTa") };
20
-
21
- // Also take the fresh terminal wrapper (tw2) along, checking its listeners right after adoption while PiP is open
22
- R.tw2Before = { ta: await listenersOf("window.__spike.tw2.querySelector('.xterm-helper-textarea')"), xterm: await listenersOf("window.__spike.tw2.querySelector('.xterm')") };
23
-
24
- R.move = await h.runGesture("pip-listener-test", `async () => {
25
- const S = window.__spike; if (S.pip && !S.pip.closed) S.pip.close();
26
- const pip = await documentPictureInPicture.requestWindow({ width: 800, height: 500 }); S.pip = pip; S.copyStyles(pip.document);
27
- pip.document.body.append(S.probeDiv);
28
- const moved = S.moveToPip(pip, S.tw2);
29
- await new Promise(r => setTimeout(r, 300));
30
- // Fire synthetic events on the adopted probe nodes: do the listeners run?
31
- S.probeHits = { div: 0, ta: 0 }; S.probeDiv.dispatchEvent(new MouseEvent('click', { bubbles: true })); S.probeTa.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true })); S.probeTa.dispatchEvent(new InputEvent('input', { data: 'a', inputType: 'insertText', bubbles: true }));
32
- return { moved, probeDivDoc: S.probeDiv.ownerDocument === pip.document, hitsAfterAdoption: { ...S.probeHits } };
33
- }`);
34
- R.afterAdoption = { div: await listenersOf("window.__spike.probeDiv"), ta: await listenersOf("window.__spike.probeTa") };
35
- R.tw2AfterAdoption = { ta: await listenersOf("window.__spike.tw2.querySelector('.xterm-helper-textarea')"), xterm: await listenersOf("window.__spike.tw2.querySelector('.xterm')") };
36
-
37
- // Type into the adopted fresh terminal via its PiP target (trusted)
38
- const pipSid = await h.attachPip();
39
- if (pipSid) {
40
- await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: true }, pipSid);
41
- await cdp.eval(pipSid, `document.querySelector('.xterm-helper-textarea').focus(); 'f'`);
42
- await ev(`window.__spike.dataLog2 = []; 'r'`);
43
- await cdp.typeText(pipSid, "echo PIP-FRESH");
44
- await cdp.pressKey(pipSid, "Enter", "Enter", 13, "\r");
45
- await sleep(1200);
46
- R.tw2TypingInPip = await ev(`({ dataLog: window.__spike.dataLog2.join(''), echoed: window.__spike.termText(window.__spike.t2, 60).includes('PIP-FRESH') })`);
47
- }
48
- // Move back while the PiP is still open, then check listeners again
49
- R.back = await ev(`(() => { const S = window.__spike; const r = S.moveBack(S.tw2); document.body.append(S.probeDiv); S.probeHits = { div: 0, ta: 0 }; S.probeDiv.dispatchEvent(new MouseEvent('click', { bubbles: true })); S.probeTa.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true })); return { ...r, hitsAfterReturn: { ...S.probeHits } }; })()`);
50
- R.afterReturn = { div: await listenersOf("window.__spike.probeDiv"), ta: await listenersOf("window.__spike.probeTa") };
51
- R.tw2AfterReturn = { ta: await listenersOf("window.__spike.tw2.querySelector('.xterm-helper-textarea')") };
52
- await ev(`window.__spike.pip.close(); 'c'`);
53
- await saveResults("pip-results-listener-survival.json", R);
54
- cdp.close();
@@ -1,19 +0,0 @@
1
- // Debug: compare registered DOM listeners on the round-tripped vs fresh xterm textarea (DOMDebugger.getEventListeners).
2
- import { connectApp, saveResults } from "./pip-spike-common";
3
-
4
- const R: Record<string, unknown> = {};
5
- const h = await connectApp();
6
- const { cdp, sid } = h;
7
-
8
- async function listeners(label: string, expr: string) {
9
- const r = await cdp.send("Runtime.evaluate", { expression: expr }, sid);
10
- if (!r.result?.objectId) { R[label] = "no object: " + JSON.stringify(r.result); return; }
11
- const l = await cdp.send("DOMDebugger.getEventListeners", { objectId: r.result.objectId }, sid);
12
- R[label] = (l.listeners as any[]).map((x) => `${x.type}${x.useCapture ? "(capture)" : ""}${x.passive ? "(passive)" : ""} @${x.lineNumber}:${x.columnNumber}`).sort();
13
- }
14
- await listeners("roundTrippedTextarea", `window.__spike.tw.querySelector('.xterm-helper-textarea')`);
15
- await listeners("freshTextarea", `window.__spike.tw2.querySelector('.xterm-helper-textarea')`);
16
- await listeners("roundTrippedXtermEl", `window.__spike.tw.querySelector('.xterm')`);
17
- await listeners("freshXtermEl", `window.__spike.tw2.querySelector('.xterm')`);
18
- await saveResults("pip-results-listeners.json", R);
19
- cdp.close();
@@ -1,59 +0,0 @@
1
- // Debug: does a synchronous moveBack inside the PiP 'pagehide' handler preserve listeners? Plus canvas inventory.
2
- import { connectApp, saveResults, sleep } from "./pip-spike-common";
3
-
4
- const R: Record<string, unknown> = {};
5
- const h = await connectApp();
6
- const { ev, cdp, sid } = h;
7
- await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: true }, sid);
8
-
9
- async function listenersOf(expr: string) {
10
- const r = await cdp.send("Runtime.evaluate", { expression: expr }, sid);
11
- if (!r.result?.objectId) return "no object";
12
- const l = await cdp.send("DOMDebugger.getEventListeners", { objectId: r.result.objectId }, sid);
13
- return (l.listeners as any[]).map((x) => `${x.type}${x.useCapture ? "(c)" : ""}`).sort();
14
- }
15
- R.t2TextareaListenersNow = await listenersOf("window.__spike.tw2.querySelector('.xterm-helper-textarea')");
16
- R.t2Canvases = await ev(`[...window.__spike.tw2.querySelectorAll('canvas')].map(c => ({ cls: c.className, parent: c.parentElement.className, w: c.width, h: c.height, gl: !!c.getContext('webgl2') }))`);
17
-
18
- // Fresh terminal 3 for a clean pagehide test
19
- await h.openViaUrl("/project/ppm/terminal/3");
20
- const Q3 = `document.querySelector('[data-tab-pool-id^="terminal:3"]')`;
21
- await h.waitFor(`${Q3} && ${Q3}.style.display !== 'none' && ${Q3}.querySelector('.xterm-helper-textarea')`, 15000, "terminal 3");
22
- await sleep(2500);
23
- await ev(`window.__spike.tw3 = ${Q3}; window.__spike.t3 = window.__spike.findTerminal(window.__spike.tw3); 'ok'`);
24
- R.t3Before = await listenersOf("window.__spike.tw3.querySelector('.xterm-helper-textarea')");
25
- async function typeMarker(marker: string) {
26
- await ev(`window.__spike.tw3.querySelector('.xterm-helper-textarea').focus(); 'f'`);
27
- await cdp.typeText(sid, `echo ${marker}`);
28
- await cdp.pressKey(sid, "Enter", "Enter", 13, "\r");
29
- await sleep(1300);
30
- return ev(`window.__spike.termText(window.__spike.t3, 60).includes(${JSON.stringify(marker)})`);
31
- }
32
- R.t3Baseline = await typeMarker("T3-BASE");
33
-
34
- // A: pagehide handler moves back synchronously, then pip.close()
35
- R.pagehideA = await h.runGesture("pip-pagehide-a", `async () => {
36
- const S = window.__spike; const w = S.tw3; S.phLog = [];
37
- const pip = await documentPictureInPicture.requestWindow({ width: 800, height: 500 }); S.pip = pip; S.copyStyles(pip.document); S.moveToPip(pip, w);
38
- pip.addEventListener('pagehide', (e) => { S.phLog.push('pagehide owner=' + (w.ownerDocument === pip.document ? 'pip' : 'other') + ' persisted=' + e.persisted); S.moveBack(w); S.phLog.push('moved back, owner main=' + (w.ownerDocument === document)); });
39
- pip.addEventListener('unload', () => S.phLog.push('unload'));
40
- await new Promise(r => setTimeout(r, 400));
41
- const beforeClose = { owner: w.ownerDocument === pip.document ? 'pip' : 'main' };
42
- pip.close();
43
- await new Promise(r => setTimeout(r, 400));
44
- return { beforeClose, phLog: S.phLog, ownerAfter: w.ownerDocument === document ? 'main' : 'other', closed: pip.closed };
45
- }`);
46
- R.t3AfterA = await listenersOf("window.__spike.tw3.querySelector('.xterm-helper-textarea')");
47
- R.t3TypingAfterA = await typeMarker("T3-AFTER-A");
48
-
49
- // B: explicit moveBack BEFORE close (the known-good path) as control
50
- R.controlB = await h.runGesture("pip-pagehide-b", `async () => {
51
- const S = window.__spike; const w = S.tw3;
52
- const pip = await documentPictureInPicture.requestWindow({ width: 800, height: 500 }); S.pip = pip; S.copyStyles(pip.document); S.moveToPip(pip, w);
53
- await new Promise(r => setTimeout(r, 400)); S.moveBack(w); pip.close(); await new Promise(r => setTimeout(r, 400));
54
- return { ownerAfter: w.ownerDocument === document ? 'main' : 'other' };
55
- }`);
56
- R.t3AfterB = await listenersOf("window.__spike.tw3.querySelector('.xterm-helper-textarea')");
57
- R.t3TypingAfterB = await typeMarker("T3-AFTER-B");
58
- await saveResults("pip-results-pagehide.json", R);
59
- cdp.close();
@@ -1,42 +0,0 @@
1
- // Debug: ResizeObserver (created in main window) vs PiP window resizes; does pip 'resize' + manual fit() work?
2
- import { connectApp, saveResults, sleep } from "./pip-spike-common";
3
-
4
- const R: Record<string, unknown> = {};
5
- const h = await connectApp();
6
- const { ev, cdp, sid } = h;
7
- await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: true }, sid);
8
- await h.openViaUrl("/project/ppm/terminal/3");
9
- await sleep(500);
10
- const W = `window.__spike.tw3`, T = `window.__spike.t3`;
11
-
12
- R.move = await h.runGesture("pip-resize", `async () => {
13
- const S = window.__spike; const w = ${W}; const t = ${T};
14
- const pip = await documentPictureInPicture.requestWindow({ width: 900, height: 600 }); S.pip = pip; S.copyStyles(pip.document); S.moveToPip(pip, w);
15
- S.roLog = []; const ro = new ResizeObserver(es => S.roLog.push('main-RO ' + Math.round(es[0].contentRect.width) + 'x' + Math.round(es[0].contentRect.height))); ro.observe(w);
16
- const pipRo = new pip.ResizeObserver(es => S.roLog.push('pip-RO ' + Math.round(es[0].contentRect.width) + 'x' + Math.round(es[0].contentRect.height))); pipRo.observe(w);
17
- pip.addEventListener('resize', () => S.roLog.push('pip-resize-event ' + pip.innerWidth + 'x' + pip.innerHeight));
18
- await new Promise(r => setTimeout(r, 500));
19
- return { pipSize: [pip.innerWidth, pip.innerHeight], rowsCols: [t.rows, t.cols], roLog: S.roLog.slice() };
20
- }`);
21
- const pipSid = await h.attachPip();
22
- await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: true }, pipSid!);
23
- // resizeTo inside a trusted click in the PiP
24
- const pt = await cdp.eval<any>(pipSid!, `(() => { const b = document.createElement('button'); b.id = 'rz'; b.textContent = 'rz'; b.style.cssText = 'position:fixed;right:4px;bottom:4px;z-index:99999'; b.onclick = () => window.resizeTo(700, 450); document.body.append(b); const r = b.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; })()`);
25
- await ev(`window.__spike.roLog = []; 'r'`);
26
- await cdp.click(pipSid!, pt.x, pt.y);
27
- const samples: unknown[] = [];
28
- for (const ms of [300, 1000, 3000]) {
29
- await sleep(ms);
30
- samples.push(await ev(`({ afterMs: ${ms}, pip: [window.__spike.pip.innerWidth, window.__spike.pip.innerHeight], wrapper: [${W}.offsetWidth, ${W}.offsetHeight], rowsCols: [${T}.rows, ${T}.cols], roLog: window.__spike.roLog.slice() })`));
31
- }
32
- R.afterResizeTo = samples;
33
- // Nudge: force a main-document rendering update; does the main RO now deliver?
34
- R.afterMainNudge = await ev(`(async () => { document.body.style.outline = '1px solid transparent'; document.body.offsetWidth; await new Promise(r => setTimeout(r, 600)); document.body.style.outline = ''; return { rowsCols: [${T}.rows, ${T}.cols], roLog: window.__spike.roLog.slice() }; })()`);
35
- // Glue candidate: manual fit via the FitAddon reached through the fiber (fitRef) — or xterm's proposeDimensions
36
- R.manualFit = await ev(`(() => { const S = window.__spike; const t = ${T}; const w = ${W}; let fit = null; let f = S.fiberOf(w.querySelector('.xterm').parentElement);
37
- for (let d = 0; f && d < 40 && !fit; d++, f = f.return) { let hk = f.memoizedState; for (let i = 0; hk && i < 200; i++, hk = hk.next) { const c = hk.memoizedState && hk.memoizedState.current; if (c && typeof c.fit === 'function' && typeof c.proposeDimensions === 'function') { fit = c; break; } } }
38
- if (!fit) return 'no fitAddon'; const before = [t.rows, t.cols]; const proposed = fit.proposeDimensions(); fit.fit(); return { before, proposed, after: [t.rows, t.cols] }; })()`);
39
- await h.pipShot(pipSid, "spike-01e-terminal-in-pip-after-manual-fit.png");
40
- await ev(`(() => { const S = window.__spike; S.moveBack(${W}); S.pip.close(); return 'done'; })()`);
41
- await saveResults("pip-results-resize.json", R);
42
- cdp.close();
@@ -1,63 +0,0 @@
1
- // Stage 1: authenticate the spike Chrome, open terminal + editor tabs, verify instances reachable.
2
- import { Database } from "bun:sqlite";
3
- import { homedir } from "node:os";
4
- import { join } from "node:path";
5
- import { CdpClient, sleep } from "./cdp-client";
6
-
7
- const PORT = 9333;
8
- const APP = "http://localhost:5173";
9
- const helpers = await Bun.file(join(import.meta.dir, "pip-page-helpers.js")).text();
10
-
11
- const db = new Database(join(homedir(), ".ppm", "ppm.dev.db"), { readonly: true });
12
- const token = JSON.parse((db.query("SELECT value FROM config WHERE key='auth'").get() as any).value).token as string;
13
-
14
- const cdp = await CdpClient.connect(PORT);
15
- const page = (await cdp.targets()).find((t) => t.type === "page" && t.url.startsWith(APP));
16
- if (!page) throw new Error("no app page target: " + JSON.stringify(await cdp.targets()));
17
- const sid = await cdp.attach(page.targetId);
18
- await cdp.send("Page.enable", {}, sid);
19
- await cdp.send("Runtime.enable", {}, sid);
20
-
21
- async function waitFor(expr: string, timeoutMs = 20000, label = expr) {
22
- const t0 = Date.now();
23
- while (Date.now() - t0 < timeoutMs) {
24
- if (await cdp.eval<boolean>(sid, `!!(${expr})`)) return;
25
- await sleep(250);
26
- }
27
- throw new Error(`timeout waiting: ${label}`);
28
- }
29
-
30
- // Auth
31
- const hasToken = await cdp.eval<boolean>(sid, `localStorage.getItem('ppm-auth-token') === ${JSON.stringify(token)}`);
32
- if (!hasToken) {
33
- await cdp.eval(sid, `localStorage.setItem('ppm-auth-token', ${JSON.stringify(token)}); 'set'`);
34
- await cdp.send("Page.navigate", { url: `${APP}/project/ppm` }, sid);
35
- await sleep(1500);
36
- }
37
- await waitFor(`document.querySelector('[data-panel-drop-zone]')`, 30000, "app shell");
38
- console.log("app shell ready, url:", await cdp.eval(sid, "location.href"));
39
-
40
- // Open terminal via URL routing (pushState + popstate → autoOpenFromUrl)
41
- async function openViaUrl(path: string) {
42
- await cdp.eval(sid, `history.pushState(null, '', ${JSON.stringify(path)}); dispatchEvent(new PopStateEvent('popstate')); 'ok'`);
43
- }
44
- await openViaUrl("/project/ppm/terminal/1");
45
- await waitFor(`document.querySelector('[data-tab-pool-id^="terminal"] .xterm-helper-textarea')`, 20000, "xterm");
46
- await openViaUrl("/project/ppm/editor/.spike/scratch-editor-target.ts");
47
- await waitFor(`document.querySelector('[data-tab-pool-id^="editor"] .monaco-editor .view-lines')`, 30000, "monaco");
48
-
49
- await cdp.eval(sid, helpers);
50
- const info = await cdp.eval(sid, `(() => {
51
- const S = window.__spike;
52
- const tw = document.querySelector('[data-tab-pool-id^="terminal"]');
53
- const ew = document.querySelector('[data-tab-pool-id^="editor"]');
54
- const term = S.findTerminal(tw); const ed = S.findEditor(ew);
55
- return {
56
- wrappers: [...document.querySelectorAll('[data-tab-pool-id]')].map(w => ({ id: w.dataset.tabPoolId, display: w.style.display, parent: w.parentElement?.className })),
57
- termFound: !!term, termSize: term && { rows: term.rows, cols: term.cols }, termTail: term && S.termText(term, 5),
58
- editorFound: !!ed, editorLine1: ed && ed.getModel().getLineContent(1), layout: ed && ed.getLayoutInfo().width,
59
- htmlClass: document.documentElement.className, sheets: document.styleSheets.length, monacoGlobal: !!window.monaco,
60
- };
61
- })()`);
62
- console.log(JSON.stringify(info, null, 2));
63
- cdp.close();
@@ -1,81 +0,0 @@
1
- // Debug stage: why does typing stop echoing after the PiP move, and does the DOM renderer survive?
2
- import { connectApp, saveResults, sleep } from "./pip-spike-common";
3
-
4
- const TAB = "terminal:1@panel-8281c2e3";
5
- const Q = `document.querySelector('[data-tab-pool-id="${TAB}"]')`;
6
- const W = `window.__spike.tw`;
7
- const T = `window.__spike.findTerminal(${W})`;
8
- const R: Record<string, unknown> = {};
9
- const h = await connectApp();
10
- const { ev, cdp, sid } = h;
11
-
12
- R.reset = await ev(`(() => { const S = window.__spike; const r = { hadPip: !!(S.pip && !S.pip.closed), twInPip: !!(S.tw && S.tw.ownerDocument !== document) };
13
- if (r.twInPip) S.moveBack(S.tw); if (r.hadPip) S.pip.close(); S.pip = null; S.results = {}; return r; })()`);
14
- await sleep(500);
15
- await h.openViaUrl("/project/ppm/terminal/1");
16
- await h.waitFor(`${Q} && ${Q}.style.display !== 'none' && ${Q}.querySelector('.xterm-helper-textarea')`, 15000, "terminal visible");
17
- await ev(`window.__spike.tw = ${Q}; 'held'`);
18
-
19
- // Instrument: xterm onData, WebSocket ref (fiber hook whose .current is a WebSocket), ws messages
20
- R.instrument = await ev(`(() => {
21
- const S = window.__spike; const w = ${W}; const t = ${T};
22
- S.dataLog = []; S.wsMsgs = 0; S.writes = 0;
23
- if (!S.instrumented) {
24
- t.onData(d => S.dataLog.push(d));
25
- const origWrite = t.write.bind(t); t.write = (d, cb) => { S.writes++; return origWrite(d, cb); };
26
- const container = w.querySelector('.xterm').parentElement; let f = S.fiberOf(container); let ws = null;
27
- for (let depth = 0; f && depth < 40 && !ws; depth++, f = f.return) {
28
- let hook = f.memoizedState;
29
- for (let i = 0; hook && i < 200; i++, hook = hook.next) { const c = hook.memoizedState && hook.memoizedState.current; if (c instanceof WebSocket) { ws = c; S.wsHook = hook.memoizedState; break; } }
30
- }
31
- if (ws) ws.addEventListener('message', () => S.wsMsgs++);
32
- S.instrumented = true;
33
- }
34
- const ws = S.wsHook && S.wsHook.current;
35
- return { wsFound: !!ws, wsState: ws && ws.readyState, wsUrl: ws && ws.url, rows: t.rows, cols: t.cols, textareaDoc: w.querySelector('.xterm-helper-textarea').ownerDocument === document };
36
- })()`);
37
-
38
- async function typeAndCheck(label: string, marker: string, targetSid: string, focusExpr: string) {
39
- await cdp.eval(targetSid, focusExpr);
40
- await ev(`window.__spike.dataLog = []; window.__spike.wsMsgs = 0; window.__spike.writes = 0; 'r'`);
41
- await cdp.typeText(targetSid, `echo ${marker}`);
42
- await cdp.pressKey(targetSid, "Enter", "Enter", 13, "\r");
43
- await sleep(1500);
44
- R[label] = await ev(`(() => { const S = window.__spike; const t = ${T}; const ws = S.wsHook && S.wsHook.current;
45
- return { dataSent: S.dataLog.join(''), wsMsgs: S.wsMsgs, writes: S.writes, wsState: ws && ws.readyState, echoed: S.termText(t, 80).includes(${JSON.stringify(marker)}), tail: S.termText(t, 4) }; })()`);
46
- }
47
-
48
- await typeAndCheck("mainBeforePip", "DBG-MAIN-1", sid, `window.__spike.tw.querySelector('.xterm-helper-textarea').focus(); 'f'`);
49
-
50
- R.move = await h.runGesture("pip-terminal-dbg", `async () => {
51
- const S = window.__spike; const w = ${W};
52
- const pip = await documentPictureInPicture.requestWindow({ width: 900, height: 600 });
53
- S.pip = pip; S.copyStyles(pip.document); const moved = S.moveToPip(pip, w);
54
- await new Promise(r => setTimeout(r, 400));
55
- const gl = [...w.querySelectorAll('canvas')].map(c => { const ctx = c.getContext('webgl2') || c.getContext('webgl'); return ctx ? { lost: ctx.isContextLost(), dbw: ctx.drawingBufferWidth, dbh: ctx.drawingBufferHeight } : { ctx2d: !!c.getContext('2d') }; });
56
- return { moved, gl, pipSize: [pip.innerWidth, pip.innerHeight], screenBg: pip.getComputedStyle(w.querySelector('.xterm-viewport')).backgroundColor };
57
- }`);
58
- const pipSid = await h.attachPip();
59
- R.pipAttached = !!pipSid;
60
- if (pipSid) {
61
- R.pipFocus = await cdp.eval(pipSid, `(() => { const ta = document.querySelector('.xterm-helper-textarea'); ta.focus(); return { active: document.activeElement === ta, hasFocus: document.hasFocus(), vis: document.visibilityState }; })()`);
62
- await typeAndCheck("pipTrusted", "DBG-PIP-1", pipSid, `document.querySelector('.xterm-helper-textarea').focus(); 'f'`);
63
- R.shotWebgl = await h.pipShot(pipSid, "spike-01c-terminal-pip-webgl-debug.png");
64
- // Force GL context loss → app's onContextLoss disposes the WebGL addon → DOM renderer
65
- R.forceLoss = await ev(`(async () => { const w = ${W}; const t = ${T}; const c = w.querySelector('canvas'); const gl = c && (c.getContext('webgl2') || c.getContext('webgl'));
66
- if (!gl) return 'no gl canvas'; const ext = gl.getExtension('WEBGL_lose_context'); if (!ext) return 'no lose_context ext'; ext.loseContext();
67
- await new Promise(r => setTimeout(r, 800)); t.refresh(0, t.rows - 1); await new Promise(r => setTimeout(r, 300));
68
- return { canvasesLeft: w.querySelectorAll('canvas').length, rowsDom: w.querySelectorAll('.xterm-rows > div').length, rowsText: [...w.querySelectorAll('.xterm-rows > div')].slice(0, 3).map(r => r.textContent.trim()).filter(Boolean) }; })()`);
69
- await sleep(500);
70
- R.shotDom = await h.pipShot(pipSid, "spike-01d-terminal-pip-dom-renderer.png");
71
- await typeAndCheck("pipTrustedAfterDomRenderer", "DBG-PIP-2", pipSid, `document.querySelector('.xterm-helper-textarea').focus(); 'f'`);
72
- // Resize check with DOM renderer
73
- R.resizeDom = await ev(`(async () => { const w = ${W}; const t = ${T}; const b = [t.rows, t.cols]; w.style.height = '50%'; await new Promise(r => setTimeout(r, 700)); const a = [t.rows, t.cols]; w.style.height = '100%'; await new Promise(r => setTimeout(r, 700)); return { before: b, after: a, restored: [t.rows, t.cols] }; })()`);
74
- }
75
- R.back = await ev(`(async () => { const S = window.__spike; const w = ${W}; const t = ${T}; const r = S.moveBack(w); await new Promise(res => setTimeout(res, 800)); return { ...r, rows: t.rows, cols: t.cols }; })()`);
76
- await ev(`window.__spike.pip.close(); 'c'`);
77
- await sleep(400);
78
- await typeAndCheck("mainAfterReturn", "DBG-MAIN-2", sid, `window.__spike.tw.querySelector('.xterm-helper-textarea').focus(); 'f'`);
79
- await cdp.screenshot(sid, "C:/Users/PC/ppm/plans/reports/screenshots/spike-03b-terminal-after-return-dom-renderer.png");
80
- await saveResults("pip-results-terminal-debug.json", R);
81
- cdp.close();