|
1 | | -# Codex Commands - PR_26126_021-preview-generator-v2-last-generated-placement |
| 1 | +# Codex Commands - PR_26126_022-preview-generator-v2-no-inline-style-and-auto-rewrite |
2 | 2 |
|
3 | 3 | ```bash |
4 | | -codex run "Create PR_26126_021-preview-generator-v2-last-generated-placement. Fix Preview Generator V2 UI only. Preserve existing generation behavior. Under the \"Paths or IDs\" control in the left panel, add a \"Last Generated Image\" section that displays the most recently generated preview. This must update on every Generate Preview action, replace the previous image (no history), and show an empty state before first generate. Keep it within the left panel accordion flow, directly below the Paths or IDs control. Do not move existing controls. Do not modify samples. Do not add schema. Produce review artifacts." |
| 4 | +codex run "Create PR_26126_022-preview-generator-v2-no-inline-style-and-auto-rewrite. Fix Preview Generator V2 UI/behavior only. Preserve existing generation behavior. Remove all inline style attributes from HTML; move styling into the existing Preview Generator V2 stylesheet/classes using Palette Manager V2 style conventions. Do not show the text \"Only rewrite if preview.svg contains literal \\\"Capture timeout\\\"\" in the UI. Instead, if an existing preview.svg exists, automatically test its contents; only rewrite/regenerate when preview.svg contains the literal text \"Capture timeout\". If preview.svg exists and does not contain that text, skip rewrite and log the decision in the status textarea. Do not modify samples. Do not add schema. Produce review artifacts." |
5 | 5 | ``` |
6 | 6 |
|
7 | 7 | ## Validation Commands |
@@ -36,72 +36,73 @@ await page.addInitScript(() => { |
36 | 36 | class FakeFileHandle { |
37 | 37 | constructor(path, existing = null) { this.kind = 'file'; this.name = path.split('/').pop(); this.path = path; this._existing = existing; } |
38 | 38 | async getFile() { return new FakeFile(this._existing || ''); } |
39 | | - async createWritable() { const path = this.path; return { async write(content) { writes.push({ path, content: String(content) }); }, async close() {} }; } |
| 39 | + async createWritable() { const handle = this; const path = this.path; return { async write(content) { writes.push({ path, content: String(content) }); handle._existing = String(content); }, async close() {} }; } |
40 | 40 | } |
41 | 41 | class FakeDirectoryHandle { |
42 | 42 | constructor(name = 'HTML-JavaScript-Gaming', path = '') { this.kind = 'directory'; this.name = name; this.path = path; this.children = new Map(); } |
43 | 43 | async getDirectoryHandle(name) { const key = `dir:${name}`; if (!this.children.has(key)) { const nextPath = this.path ? `${this.path}/${name}` : name; this.children.set(key, new FakeDirectoryHandle(name, nextPath)); } return this.children.get(key); } |
44 | 44 | async getFileHandle(name, options = {}) { const key = `file:${name}`; if (!this.children.has(key)) { if (!options.create) throw new DOMException('Not found', 'NotFoundError'); const nextPath = this.path ? `${this.path}/${name}` : name; this.children.set(key, new FakeFileHandle(nextPath)); } return this.children.get(key); } |
45 | 45 | } |
| 46 | + async function addFile(root, path, text) { |
| 47 | + const parts = path.split('/').filter(Boolean); |
| 48 | + let current = root; |
| 49 | + for (const part of parts.slice(0, -1)) current = await current.getDirectoryHandle(part); |
| 50 | + const fileHandle = await current.getFileHandle(parts.at(-1), { create: true }); |
| 51 | + fileHandle._existing = text; |
| 52 | + } |
46 | 53 | window.__previewGeneratorV2Writes = writes; |
47 | | - window.showDirectoryPicker = async () => new FakeDirectoryHandle(); |
| 54 | + window.__previewGeneratorV2Root = new FakeDirectoryHandle(); |
| 55 | + window.__previewGeneratorV2Ready = (async () => { |
| 56 | + await addFile(window.__previewGeneratorV2Root, 'samples/phase-01/0107/assets/images/preview.svg', '<svg><text>Healthy preview</text></svg>'); |
| 57 | + await addFile(window.__previewGeneratorV2Root, 'samples/phase-01/0102/assets/images/preview.svg', '<svg><text>Capture timeout</text></svg>'); |
| 58 | + })(); |
| 59 | + window.showDirectoryPicker = async () => { |
| 60 | + await window.__previewGeneratorV2Ready; |
| 61 | + return window.__previewGeneratorV2Root; |
| 62 | + }; |
48 | 63 | }); |
49 | 64 |
|
50 | 65 | await page.goto(`${server.baseUrl}/tools/preview-generator-v2/index.html`, { waitUntil: 'domcontentloaded' }); |
51 | 66 | await page.waitForSelector('#shared-theme-header'); |
52 | 67 | await page.waitForFunction(() => Array.from(document.querySelectorAll('.preview-generator-v2 .accordion-v2__header')).every((header) => header.dataset.accordionV2Bound === 'true')); |
53 | | -
|
54 | | -const textareaBox = await page.locator('#sampleList').boundingBox(); |
55 | | -const lastGeneratedBox = await page.locator('#lastGeneratedImageSection').boundingBox(); |
56 | | -if (!textareaBox || !lastGeneratedBox || lastGeneratedBox.y <= textareaBox.y) throw new Error('Last Generated Image should render below Paths or IDs input.'); |
57 | | -if (!(await page.locator('#lastGeneratedImageEmpty').isVisible())) throw new Error('Last Generated Image empty state should be visible before first generate.'); |
58 | | -if (await page.locator('#lastGeneratedImagePreview').isVisible()) throw new Error('Last Generated Image preview should be hidden before first generate.'); |
| 68 | +const bodyText = await page.locator('body').innerText(); |
| 69 | +if (bodyText.includes('Only rewrite if preview.svg contains literal')) throw new Error('Manual capture-timeout rewrite text should not be visible.'); |
| 70 | +const previewInlineStyleCount = await page.locator('.preview-generator-v2 [style]').count(); |
| 71 | +if (previewInlineStyleCount !== 0) throw new Error(`Expected no inline style attributes inside Preview Generator V2 UI, got ${previewInlineStyleCount}`); |
59 | 72 |
|
60 | 73 | await page.fill('#baseUrl', server.baseUrl); |
61 | 74 | await page.fill('#waitMs', '3000'); |
62 | 75 | await page.fill('#sampleList', '0107'); |
63 | | -await page.check('#forceRewrite'); |
64 | 76 | await page.check('#targetTypeSamples'); |
65 | 77 | await page.click('#pickRepoBtn'); |
66 | 78 | await page.waitForFunction(() => !document.getElementById('executeBtn').disabled); |
67 | 79 | await page.click('#executeBtn'); |
68 | | -await page.waitForFunction(() => document.getElementById('lastGeneratedImagePreview') && !document.getElementById('lastGeneratedImagePreview').hidden, null, { timeout: 35000 }); |
69 | | -if (await page.locator('#lastGeneratedImageEmpty').isVisible()) throw new Error('Last Generated Image empty state should hide after generate.'); |
70 | | -const firstSrc = await page.locator('#lastGeneratedImage').getAttribute('src'); |
71 | | -const firstMeta = await page.locator('#lastGeneratedImageMeta').innerText(); |
72 | | -if (!firstSrc?.startsWith('blob:')) throw new Error(`Last Generated Image should use an object URL, got ${firstSrc}`); |
73 | | -if (!firstMeta.includes('0107')) throw new Error(`Last Generated Image meta should include first generated label, got ${firstMeta}`); |
74 | | -await page.waitForFunction(() => (window.__previewGeneratorV2Writes || []).length === 1); |
| 80 | +await page.waitForFunction(() => document.getElementById('log').textContent.includes('existing-preview-without-capture-timeout'), null, { timeout: 35000 }); |
| 81 | +let writes = await page.evaluate(() => window.__previewGeneratorV2Writes || []); |
| 82 | +if (writes.length !== 0) throw new Error(`Existing healthy preview should skip rewrite, got ${writes.length} writes.`); |
75 | 83 |
|
| 84 | +await page.click('#clearLogBtn'); |
76 | 85 | await page.fill('#sampleList', '0102'); |
77 | 86 | await page.waitForFunction(() => !document.getElementById('executeBtn').disabled); |
78 | 87 | await page.click('#executeBtn'); |
79 | | -await page.waitForFunction((previousSrc) => { |
80 | | - const img = document.getElementById('lastGeneratedImage'); |
81 | | - return img && img.getAttribute('src') && img.getAttribute('src') !== previousSrc; |
82 | | -}, firstSrc, { timeout: 35000 }); |
83 | | -const secondSrc = await page.locator('#lastGeneratedImage').getAttribute('src'); |
84 | | -const secondMeta = await page.locator('#lastGeneratedImageMeta').innerText(); |
85 | | -if (!secondSrc?.startsWith('blob:')) throw new Error(`Replacement Last Generated Image should use an object URL, got ${secondSrc}`); |
86 | | -if (secondSrc === firstSrc) throw new Error('Last Generated Image should replace the prior object URL.'); |
87 | | -if (!secondMeta.includes('0102')) throw new Error(`Last Generated Image meta should include second generated label, got ${secondMeta}`); |
88 | | -await page.waitForFunction(() => (window.__previewGeneratorV2Writes || []).length === 2); |
89 | | -const writes = await page.evaluate(() => window.__previewGeneratorV2Writes || []); |
90 | | -if (!writes[0].path.endsWith('samples/phase-01/0107/assets/images/preview.svg')) throw new Error(`Unexpected first write path: ${writes[0].path}`); |
91 | | -if (!writes[1].path.endsWith('samples/phase-01/0102/assets/images/preview.svg')) throw new Error(`Unexpected second write path: ${writes[1].path}`); |
| 88 | +await page.waitForFunction(() => document.getElementById('log').textContent.includes('contains Capture timeout; rewriting'), null, { timeout: 35000 }); |
| 89 | +await page.waitForFunction(() => document.getElementById('lastGeneratedImagePreview') && !document.getElementById('lastGeneratedImagePreview').hidden, null, { timeout: 35000 }); |
| 90 | +writes = await page.evaluate(() => window.__previewGeneratorV2Writes || []); |
| 91 | +if (writes.length !== 1) throw new Error(`Existing Capture timeout preview should rewrite once, got ${writes.length} writes.`); |
| 92 | +if (!writes[0].path.endsWith('samples/phase-01/0102/assets/images/preview.svg')) throw new Error(`Unexpected rewrite path: ${writes[0].path}`); |
92 | 93 | if (errors.length || consoleErrors.length) throw new Error([...errors, ...consoleErrors].join(' | ')); |
93 | 94 | await browser.close(); |
94 | 95 | await server.close(); |
95 | | -console.log('preview-generator-v2 last generated image placement smoke valid'); |
| 96 | +console.log('preview-generator-v2 automatic capture-timeout rewrite gate smoke valid'); |
96 | 97 | '@ | node --input-type=module - |
97 | 98 | ``` |
98 | 99 |
|
99 | 100 | ## Notes |
100 | 101 |
|
101 | | -The targeted Playwright smoke validates the empty state, placement below `Paths or IDs`, first generated preview render, and second Generate Preview replacement without history. |
| 102 | +The targeted Playwright smoke validates the visible manual capture-timeout checkbox text is absent, Preview Generator V2-owned UI has no inline style attributes, existing healthy previews skip rewrite with a status log, and existing timeout previews rewrite and update Last Generated Image. |
102 | 103 |
|
103 | 104 | `npm run test:workspace-v2` was attempted, but the script is not defined in this checkout. |
104 | 105 |
|
105 | | -Full samples smoke test was skipped because this PR is scoped to Preview Generator V2 UI only. |
| 106 | +Full samples smoke test was skipped because this PR is scoped to Preview Generator V2 UI/behavior only. |
106 | 107 |
|
107 | | -An unrelated unstaged sample preview SVG change was present before this PR and was left untouched. |
| 108 | +Unstaged sample preview SVG changes were present before this PR and were left untouched. |
0 commit comments