Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_docroundtrip.mjs

5.7 KiB, 1 run

created by r2519314175:375, 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// verify_docroundtrip.mjs — the Doc panel shows the FILE, and saving it changes nothing.
2//
3// `file_read` is a tool that renders a file FOR A MODEL. It numbers every line
4// (`1\t`), it says so when it truncates, and it explains itself when the bytes
5// are in cloud storage. All three are right for an agent and none of them is the
6// file. The viewer read the file that way and showed the result as the document.
7//
8// Two consequences, and the second is data loss:
9//
10// 1. With the line-number toggle on, the reader saw TWO columns of numbers --
11// the tool's, baked into the text, and the viewer's own gutter beside it.
12// 2. The editor was seeded with the same string, so OPENING A FILE AND PRESSING
13// SAVE WITHOUT TYPING ANYTHING wrote the line numbers into it, and did it
14// again over the top on every repeat. A truncated read would have written
15// the truncation the same way.
16//
17// So the property is not "the viewer looks right". It is that **a round trip
18// through the viewer is the identity function**, which is the thing a person
19// betting their files on this app needs to be true.
20//
21// node dev/verify_docroundtrip.mjs
22//
23// Needs dev/serve.mjs (DAIMOND_PORT, default 8777). No gateway, no mock LLM: nothing
24// here runs a turn.
25import fs from 'node:fs';
26import { open, scratch } from './harness.mjs';
27
28const PROFILE = scratch('pw', 'docroundtrip');
29fs.rmSync(PROFILE, { recursive: true, force: true });
30
31let bad = 0;
32const check = (pass, name, detail) => {
33 if (!pass) bad++;
34 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
35};
36
37const BODY = 'alpha\nbeta\ngamma\na line long enough that it wraps inside the panel and shows where a continuation goes\n';
38
39const s = await open({ name: 'docroundtrip', profile: PROFILE, connect: false });
40const { page } = s;
41
42try {
43 await page.waitForTimeout(1500);
44
45 /// The bytes as they actually sit in the account's OPFS, read past the app.
46 const raw = () => page.evaluate(async () => {
47 const dir = await DaimondCloud.opfsRoot();
48 return await (await (await dir.getFileHandle('round.md')).getFile()).text();
49 });
50
51 await page.evaluate(async (body) => {
52 const mod = await import('../pkg/oxedyne_daimond.js');
53 const app = new mod.DaimondApp('http://127.0.0.1/v1/chat/completions', '', 'none', 256, '', true);
54 await app.run_tool('file_write', JSON.stringify({ path: 'round.md', content: body }));
55 }, BODY);
56 check(await raw() === BODY, 'the fixture is on disk as written');
57
58 // Open it the way a person does, from the tree. `Files` is closure-scoped and
59 // not reachable from here, so the row is the door -- and the tree's listing
60 // races the write that made the file, so the panel is re-opened until the row
61 // is there rather than once with a hopeful pause. A verifier that flakes on
62 // its own fixture says nothing about the property it exists for.
63 let row = null;
64 for (let i = 0; i < 10 && !row; i++) {
65 await page.evaluate(() => {
66 try { DaimondPanels.hide('work'); DaimondPanels.show('work'); } catch (e) {}
67 });
68 await page.waitForTimeout(600);
69 row = await page.$('text=round.md');
70 }
71 check(!!row, 'the file is listed in the Workspace tree');
72 if (!row) throw new Error('fixture never appeared in the tree');
73 await row.click();
74 await page.waitForTimeout(1400);
75
76 // ── 1. What is on screen is the file ────────────────────────────
77 const shown = await page.evaluate(() => {
78 const b = document.querySelector('.files-view-body');
79 return b ? b.textContent : '';
80 });
81 check(shown.indexOf('alpha') !== -1, 'the document is on screen', JSON.stringify(shown.slice(0, 40)));
82 check(!/(^|\n)\s*\d+\t/.test(shown),
83 'and carries none of file_read\'s line-number prefixes',
84 JSON.stringify(shown.slice(0, 60)));
85
86 // ── 2. One gutter, not two ──────────────────────────────────────
87 const btn = await page.$('#doc-lineno');
88 check(!!btn && await btn.isVisible(), 'the line-number toggle is offered over a text file');
89 if (btn && await btn.isVisible()) {
90 if (await btn.getAttribute('aria-pressed') !== 'true') { await btn.click(); await page.waitForTimeout(600); }
91 const rows = await page.evaluate(() => [...document.querySelectorAll('.lnrow')].slice(0, 3).map(r => ({
92 gutter: (r.querySelector('.ln') || {}).textContent || '',
93 text: [...r.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join(''),
94 })));
95 check(rows.length >= 3, 'the numbered view renders a row per line', JSON.stringify(rows.length));
96 check(rows.every(r => !/^\d+\t/.test(r.text)),
97 'and each row\'s text is the line, not a second number',
98 JSON.stringify(rows));
99 check(rows[0] && rows[0].gutter === '1' && rows[1] && rows[1].gutter === '2',
100 'with the gutter counting from one', JSON.stringify(rows.map(r => r.gutter)));
101 }
102
103 // ── 3. The round trip is the identity ───────────────────────────
104 // Edit, then save, typing nothing. This is the check the whole file exists for.
105 await page.getByRole('button', { name: /Edit/ }).first().click();
106 await page.waitForTimeout(600);
107 const seeded = await page.evaluate(() => {
108 const ta = document.querySelector('.files-edit');
109 return ta ? ta.value : null;
110 });
111 check(seeded === BODY, 'the editor is seeded with the file, byte for byte',
112 JSON.stringify((seeded || '').slice(0, 40)));
113 await page.getByRole('button', { name: /Save/ }).first().click();
114 await page.waitForTimeout(1500);
115 const after = await raw();
116 check(after === BODY, 'and a save that typed nothing changed nothing',
117 JSON.stringify(after.slice(0, 60)));
118
119} finally {
120 await s.close();
121}
122
123console.log(bad === 0 ? '\nall checks passed' : `\n${bad} check(s) FAILED`);
124process.exit(bad === 0 ? 0 : 1);