oxedyne/daimond/dev/shot_moderow.mjs
3.9 KiB, 1 run
created by r2519314175:165, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // shot_moderow.mjs — the Workspace mode row, with the root controls it grew. |
| 2 | // |
| 3 | // Three states worth seeing, because each used to be wrong in a different way: |
| 4 | // |
| 5 | // machine — the agent working in a real folder. The row now carries "Change folder…", which |
| 6 | // is the ONE control that opens a picker; the chip itself states the root's scope. |
| 7 | // scope — that statement, as the active chip says it, with "Forget this folder" beside it. |
| 8 | // remembered — back on the browser sandbox, with the last folder still named on the Machine |
| 9 | // chip. This state did not exist: switching to the sandbox deleted the handle, so |
| 10 | // the chip read a bare "Machine" and going back cost a native dialog. |
| 11 | // |
| 12 | // A picked folder is stood in for by an OPFS subdirectory, which is the same handle type and |
| 13 | // answers queryPermission with 'granted' -- the trick verify_fsa rests on, since Playwright cannot |
| 14 | // answer a directory picker. |
| 15 | import { open } from './harness.mjs'; |
| 16 | import path from 'node:path'; |
| 17 | import fs from 'node:fs'; |
| 18 | |
| 19 | const OUT = path.join(process.cwd(), 'dev', 'shots'); |
| 20 | fs.mkdirSync(OUT, { recursive: true }); |
| 21 | const FOLDER = 'Projects'; |
| 22 | |
| 23 | const s = await open({ name: 'moderow', connect: false }); |
| 24 | const p = s.page; |
| 25 | await p.waitForTimeout(1500); |
| 26 | |
| 27 | await p.evaluate(() => { try { DaimondPanels.show('work'); } catch (e) {} }); |
| 28 | await p.waitForTimeout(700); |
| 29 | |
| 30 | // Seed the stored handle where FsaDB looks, then drive the app's own reconnect: that is |
| 31 | // activateFolder, so `rootHandle` is set exactly as it is in ordinary use. |
| 32 | await p.evaluate(async (folder) => { |
| 33 | const root = await navigator.storage.getDirectory(); |
| 34 | const dir = await root.getDirectoryHandle(folder, { create: true }); |
| 35 | const db = await new Promise((res, rej) => { |
| 36 | const q = indexedDB.open('daimond-fsa', 1); |
| 37 | q.onupgradeneeded = () => q.result.createObjectStore('handles'); |
| 38 | q.onsuccess = () => res(q.result); |
| 39 | q.onerror = () => rej(q.error); |
| 40 | }); |
| 41 | await new Promise((res, rej) => { |
| 42 | const t = db.transaction('handles', 'readwrite'); |
| 43 | t.objectStore('handles').put(dir, 'workspace'); |
| 44 | t.oncomplete = res; |
| 45 | t.onerror = () => rej(t.error); |
| 46 | }); |
| 47 | db.close(); |
| 48 | }, FOLDER); |
| 49 | |
| 50 | await p.reload({ waitUntil: 'domcontentloaded' }); |
| 51 | const { signInAs } = await import('./harness.mjs'); |
| 52 | await signInAs(s, 'moderow'); |
| 53 | await p.waitForTimeout(2500); |
| 54 | await p.evaluate(() => { try { DaimondPanels.show('work'); } catch (e) {} }); |
| 55 | await p.waitForTimeout(900); |
| 56 | |
| 57 | const clip = async () => p.evaluate(() => { |
| 58 | const row = document.querySelector('.files-mode'); |
| 59 | if (!row) return null; |
| 60 | const r = row.getBoundingClientRect(); |
| 61 | return { x: Math.max(0, r.x - 8), y: Math.max(0, r.y - 8), width: r.width + 16, height: r.height + 16 }; |
| 62 | }); |
| 63 | const shoot = async (label) => { |
| 64 | const file = path.join(OUT, `moderow-${label}.png`); |
| 65 | let err = ''; |
| 66 | await p.screenshot({ path: file, clip: (await clip()) || undefined, timeout: 8000 }) |
| 67 | .catch((e) => { err = String(e && e.message ? e.message : e).split('\n')[0]; }); |
| 68 | console.log(' →', file, err ? 'FAILED: ' + err : ''); |
| 69 | }; |
| 70 | const clickChip = (pat) => p.evaluate((pat) => { |
| 71 | const c = [...document.querySelectorAll('.files-mode-chip')] |
| 72 | .find(x => new RegExp(pat, 'i').test(x.textContent)); |
| 73 | if (c) c.click(); |
| 74 | return !!c; |
| 75 | }, pat); |
| 76 | |
| 77 | const row = () => p.evaluate(() => { |
| 78 | const r = document.querySelector('.files-mode'); |
| 79 | if (!r) return null; |
| 80 | return { |
| 81 | chips: [...r.querySelectorAll('.files-mode-chip')].map(c => c.textContent.trim()), |
| 82 | btns: [...r.querySelectorAll('.files-mode-btn')].map(b => b.textContent.trim()), |
| 83 | msg: (r.querySelector('.files-mode-msg') || {}).textContent || '', |
| 84 | }; |
| 85 | }); |
| 86 | |
| 87 | console.log('machine:', JSON.stringify(await row())); |
| 88 | await shoot('machine'); |
| 89 | |
| 90 | await clickChip('Machine|' + FOLDER); |
| 91 | await p.waitForTimeout(500); |
| 92 | console.log('scope: ', JSON.stringify(await row())); |
| 93 | await shoot('scope'); |
| 94 | |
| 95 | await clickChip('Browser'); |
| 96 | await p.waitForTimeout(900); |
| 97 | console.log('remembered:', JSON.stringify(await row())); |
| 98 | await shoot('remembered'); |
| 99 | |
| 100 | await s.close(); |