|
1 | | -# Codex Commands - PR_26126_020-preview-generator-v2-target-source-and-control-placement |
| 1 | +# Codex Commands - PR_26126_021-preview-generator-v2-last-generated-placement |
2 | 2 |
|
3 | 3 | ```bash |
4 | | -codex run "Create PR_26126_020-preview-generator-v2-target-source-and-control-placement. Fix Preview Generator V2 UI only. Preserve existing generation behavior. Default Target Source to Games. Generate Preview must be visible but disabled/greyed out until required fields are provided; do not hide it. Move Capture mode into its own accordion/control section above Render Controls with options \"Full Screen (1600x900 HTML Page)\" and \"Canvas Only\". Move Asset folder into its own accordion/control section below Target Source with value \"assets/images\". Left and right columns must continue to use working accordion sections. Do not modify samples. Do not add schema. Produce review artifacts." |
| 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." |
5 | 5 | ``` |
6 | 6 |
|
7 | 7 | ## Validation Commands |
8 | 8 |
|
9 | 9 | ```bash |
10 | 10 | git diff --check -- tools/preview-generator-v2/index.html docs/dev/codex_commands.md docs/dev/commit_comment.txt |
11 | | -git diff --name-only -- samples games start_of_day tools/shared tools/schemas |
| 11 | +git diff --cached --name-only -- samples games start_of_day tools/shared tools/schemas |
12 | 12 | npm run test:workspace-v2 |
13 | 13 | npm run codex:review-artifacts |
14 | 14 | ``` |
@@ -51,66 +51,57 @@ await page.goto(`${server.baseUrl}/tools/preview-generator-v2/index.html`, { wai |
51 | 51 | await page.waitForSelector('#shared-theme-header'); |
52 | 52 | await page.waitForFunction(() => Array.from(document.querySelectorAll('.preview-generator-v2 .accordion-v2__header')).every((header) => header.dataset.accordionV2Bound === 'true')); |
53 | 53 |
|
54 | | -async function assertAccordion(selector) { |
55 | | - const header = page.locator(`${selector} .accordion-v2__header`).first(); |
56 | | - const content = page.locator(`${selector} .accordion-v2__content`).first(); |
57 | | - if (await header.count() !== 1) throw new Error(`${selector} missing accordion header`); |
58 | | - if (await content.count() !== 1) throw new Error(`${selector} missing accordion content`); |
59 | | - await header.click(); |
60 | | - await page.waitForFunction((target) => document.querySelector(`${target} .accordion-v2__header`)?.getAttribute('aria-expanded') === 'false', selector); |
61 | | - const collapsed = await content.evaluate((node) => ({ hidden: node.hidden, display: getComputedStyle(node).display, height: node.getBoundingClientRect().height })); |
62 | | - if (!collapsed.hidden || collapsed.display !== 'none' || collapsed.height !== 0) throw new Error(`${selector} did not collapse cleanly`); |
63 | | - await header.click(); |
64 | | - await page.waitForFunction((target) => document.querySelector(`${target} .accordion-v2__header`)?.getAttribute('aria-expanded') === 'true', selector); |
65 | | -} |
| 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.'); |
66 | 59 |
|
67 | | -const leftHeaders = await page.locator('.preview-generator-v2__left-accordion .accordion-v2__header').evaluateAll((headers) => headers.map((header) => header.textContent.trim().replace(/\s+/g, ' '))); |
68 | | -const expectedLeftHeaders = ['Repo Destination +', 'Target Source +', 'Asset folder +', 'Capture mode +', 'Render Controls +']; |
69 | | -if (JSON.stringify(leftHeaders) !== JSON.stringify(expectedLeftHeaders)) throw new Error(`Unexpected left accordion order: ${JSON.stringify(leftHeaders)}`); |
70 | | -
|
71 | | -for (const selector of [ |
72 | | - '.preview-generator-v2__left-accordion:nth-of-type(1)', |
73 | | - '.preview-generator-v2__left-accordion:nth-of-type(2)', |
74 | | - '.preview-generator-v2__left-accordion:nth-of-type(3)', |
75 | | - '.preview-generator-v2__left-accordion:nth-of-type(4)', |
76 | | - '.preview-generator-v2__left-accordion:nth-of-type(5)', |
77 | | - '#outputSummary', |
78 | | - '#statusAccordion' |
79 | | -]) { |
80 | | - await assertAccordion(selector); |
81 | | -} |
82 | | -
|
83 | | -if (!(await page.locator('#targetTypeGames').isChecked())) throw new Error('Games should be the default Target Source.'); |
84 | | -if (await page.locator('#targetTypeSamples').isChecked()) throw new Error('Samples should not be default Target Source.'); |
85 | | -if ((await page.locator('#assetFolder').inputValue()) !== 'assets/images') throw new Error('Asset folder should remain assets/images.'); |
86 | | -if (!(await page.locator('#executeBtn').isVisible())) throw new Error('Generate Preview should be visible before required fields are provided.'); |
87 | | -if (!(await page.locator('#executeBtn').isDisabled())) throw new Error('Generate Preview should be disabled before required fields are provided.'); |
88 | 60 | await page.fill('#baseUrl', server.baseUrl); |
89 | 61 | await page.fill('#waitMs', '3000'); |
90 | 62 | await page.fill('#sampleList', '0107'); |
91 | 63 | await page.check('#forceRewrite'); |
92 | | -if (!(await page.locator('#executeBtn').isVisible()) || !(await page.locator('#executeBtn').isDisabled())) throw new Error('Generate Preview should remain visible and disabled until repo folder is selected.'); |
93 | 64 | await page.check('#targetTypeSamples'); |
94 | 65 | await page.click('#pickRepoBtn'); |
95 | 66 | await page.waitForFunction(() => !document.getElementById('executeBtn').disabled); |
96 | | -await page.waitForFunction(() => document.getElementById('writeFolderActualValue').textContent === 'samples\\phase-01\\0107\\assets\\images'); |
97 | 67 | await page.click('#executeBtn'); |
98 | | -await page.waitForFunction(() => document.getElementById('log').textContent.includes('===== SUMMARY ====='), null, { timeout: 35000 }); |
| 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); |
| 75 | +
|
| 76 | +await page.fill('#sampleList', '0102'); |
| 77 | +await page.waitForFunction(() => !document.getElementById('executeBtn').disabled); |
| 78 | +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); |
99 | 89 | const writes = await page.evaluate(() => window.__previewGeneratorV2Writes || []); |
100 | | -if (writes.length !== 1) throw new Error(`Expected exactly one preview write, got ${writes.length}`); |
101 | | -if (!writes[0].path.endsWith('samples/phase-01/0107/assets/images/preview.svg')) throw new Error(`Unexpected write path: ${writes[0].path}`); |
102 | | -if (!writes[0].content.includes('<svg')) throw new Error('Generated content is not SVG-like.'); |
| 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}`); |
103 | 92 | if (errors.length || consoleErrors.length) throw new Error([...errors, ...consoleErrors].join(' | ')); |
104 | 93 | await browser.close(); |
105 | 94 | await server.close(); |
106 | | -console.log('preview-generator-v2 target source and control placement smoke valid'); |
| 95 | +console.log('preview-generator-v2 last generated image placement smoke valid'); |
107 | 96 | '@ | node --input-type=module - |
108 | 97 | ``` |
109 | 98 |
|
110 | 99 | ## Notes |
111 | 100 |
|
112 | | -The targeted Playwright smoke validates default Games target, visible disabled Generate Preview gating, Asset folder and Capture mode section placement, working left/right accordions, and preserved preview generation after switching to Samples. |
| 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. |
113 | 102 |
|
114 | 103 | `npm run test:workspace-v2` was attempted, but the script is not defined in this checkout. |
115 | 104 |
|
116 | 105 | Full samples smoke test was skipped because this PR is scoped to Preview Generator V2 UI only. |
| 106 | + |
| 107 | +An unrelated unstaged sample preview SVG change was present before this PR and was left untouched. |
0 commit comments