|
1 | | -# Codex Commands - PR_26126_011-preview-generator-v2-rollback-and-cold-copy |
| 1 | +# Codex Commands - PR_26126_012-preview-generator-v2-reskin-from-working-base |
2 | 2 |
|
3 | 3 | ```bash |
4 | | -codex run "Create PR_26126_011-preview-generator-v2-rollback-and-cold-copy. Roll back all Preview Generator V2 changes made after the repo state before Preview Generator V2 was created. Remove the failed Preview Generator V2 implementation/docs/test changes from PR_26126_008 through PR_26126_010 unless needed only as review artifacts. Then create tools/preview-generator-v2/index.html by copying existing working preview.html cold as-is with no reskin, no rewrite, no renamed labels, no regrouping, no new controls, no removed controls, and no behavior changes. Preserve existing functionality exactly. Do not create a Preview Generator V2 schema. Do not modify samples. Add only the minimum registration/linking needed to open the new tool if required. Produce review artifacts showing rollback plus cold-copy result." |
| 4 | +codex run "Create PR_26126_012-preview-generator-v2-reskin-from-working-base. Using tools/preview-generator-v2/index.html (cold copy of preview.html), perform a reskin only. Do not change or break existing functionality, logic, or behavior. Do not remove existing inputs or labels; keep them and regroup them. Apply Palette Manager layout and header. Add NAV using palette-manager-v2__menu-sample with ONLY Generate Preview button. Reorganize UI: Left column groups existing controls into Repo Destination (rename Game Destination; folder selection behavior unchanged), Target Source, and Render Controls. Center column replaces visual Main Preview with a Paths or IDs input box (reuse existing input if present). Right column places Output Summary with Status under it. Do not add JSON UI. Do not create a schema. Do not modify samples. Ensure all existing preview generation still works after layout changes. Produce review artifacts." |
5 | 5 | ``` |
6 | 6 |
|
7 | 7 | ## Validation Commands |
8 | 8 |
|
9 | 9 | ```bash |
10 | | -Get-FileHash tools/preview/preview_svg_generator.html, tools/preview-generator-v2/index.html -Algorithm SHA256 |
11 | | -node --input-type=module -e "import('./tools/toolRegistry.js').then(({getToolById})=>{const tool=getToolById('preview-generator-v2'); if(!tool || tool.entryPoint !== 'preview-generator-v2/index.html') throw new Error('preview-generator-v2 registry entry invalid'); console.log('preview-generator-v2 registry entry valid');})" |
12 | | -node --input-type=module -e "import { chromium } from '@playwright/test'; import { startRepoServer } from './tests/helpers/playwrightRepoServer.mjs'; const server=await startRepoServer(); const browser=await chromium.launch({headless:true}); const page=await browser.newPage({viewport:{width:1280,height:900}}); const errors=[]; page.on('pageerror', error=>errors.push(error.message)); await page.goto(`${server.baseUrl}/tools/preview-generator-v2/index.html`, {waitUntil:'networkidle'}); if (await page.locator('h1').innerText() !== 'Preview SVG Generator') throw new Error('cold-copy heading mismatch'); if (await page.locator('#executeBtn').count() !== 1) throw new Error('executeBtn missing'); if (await page.locator('input[name=\"targetType\"]').count() !== 3) throw new Error('targetType radios missing'); if (await page.locator('#sampleList').count() !== 1) throw new Error('sampleList missing'); if (await page.locator('.preview-generator-v2, [data-preview-generator-v2-header], #shared-theme-header').count() !== 0) throw new Error('reskin shell still present'); if (errors.length) throw new Error(errors.join(' | ')); await browser.close(); await server.close(); console.log('preview-generator-v2 cold-copy browser smoke valid');" |
13 | 10 | git diff --check -- tools/preview-generator-v2/index.html tools/toolRegistry.js docs/dev/codex_commands.md docs/dev/commit_comment.txt |
| 11 | +git diff --name-only -- samples games start_of_day tools/shared tools/schemas |
14 | 12 | npm run test:workspace-v2 |
15 | 13 | npm run codex:review-artifacts |
16 | 14 | ``` |
17 | 15 |
|
| 16 | +```powershell |
| 17 | +@' |
| 18 | +import { chromium } from '@playwright/test'; |
| 19 | +import { startRepoServer } from './tests/helpers/playwrightRepoServer.mjs'; |
| 20 | +
|
| 21 | +const server = await startRepoServer(); |
| 22 | +const browser = await chromium.launch({ headless: true }); |
| 23 | +const page = await browser.newPage({ viewport: { width: 1366, height: 900 } }); |
| 24 | +const errors = []; |
| 25 | +const consoleErrors = []; |
| 26 | +page.on('pageerror', (error) => errors.push(error.message)); |
| 27 | +page.on('console', (message) => { |
| 28 | + if (message.type() === 'error') { |
| 29 | + consoleErrors.push(message.text()); |
| 30 | + } |
| 31 | +}); |
| 32 | +await page.route('https://cdn.jsdelivr.net/**', async (route) => { |
| 33 | + await route.fulfill({ status: 200, contentType: 'text/javascript', body: 'window.html2canvas = window.html2canvas || undefined;' }); |
| 34 | +}); |
| 35 | +await page.addInitScript(() => { |
| 36 | + const writes = []; |
| 37 | + class FakeFile { |
| 38 | + constructor(text) { |
| 39 | + this._text = text; |
| 40 | + } |
| 41 | + async text() { |
| 42 | + return this._text; |
| 43 | + } |
| 44 | + } |
| 45 | + class FakeFileHandle { |
| 46 | + constructor(path, existing = null) { |
| 47 | + this.kind = 'file'; |
| 48 | + this.name = path.split('/').pop(); |
| 49 | + this.path = path; |
| 50 | + this._existing = existing; |
| 51 | + } |
| 52 | + async getFile() { |
| 53 | + return new FakeFile(this._existing || ''); |
| 54 | + } |
| 55 | + async createWritable() { |
| 56 | + const path = this.path; |
| 57 | + return { |
| 58 | + async write(content) { |
| 59 | + writes.push({ path, content: String(content) }); |
| 60 | + }, |
| 61 | + async close() {} |
| 62 | + }; |
| 63 | + } |
| 64 | + } |
| 65 | + class FakeDirectoryHandle { |
| 66 | + constructor(name = 'HTML-JavaScript-Gaming', path = '') { |
| 67 | + this.kind = 'directory'; |
| 68 | + this.name = name; |
| 69 | + this.path = path; |
| 70 | + this.children = new Map(); |
| 71 | + } |
| 72 | + async getDirectoryHandle(name) { |
| 73 | + const key = `dir:${name}`; |
| 74 | + if (!this.children.has(key)) { |
| 75 | + const nextPath = this.path ? `${this.path}/${name}` : name; |
| 76 | + this.children.set(key, new FakeDirectoryHandle(name, nextPath)); |
| 77 | + } |
| 78 | + return this.children.get(key); |
| 79 | + } |
| 80 | + async getFileHandle(name, options = {}) { |
| 81 | + const key = `file:${name}`; |
| 82 | + if (!this.children.has(key)) { |
| 83 | + if (!options.create) { |
| 84 | + throw new DOMException('Not found', 'NotFoundError'); |
| 85 | + } |
| 86 | + const nextPath = this.path ? `${this.path}/${name}` : name; |
| 87 | + this.children.set(key, new FakeFileHandle(nextPath)); |
| 88 | + } |
| 89 | + return this.children.get(key); |
| 90 | + } |
| 91 | + } |
| 92 | + window.__previewGeneratorV2Writes = writes; |
| 93 | + window.showDirectoryPicker = async () => new FakeDirectoryHandle(); |
| 94 | +}); |
| 95 | +
|
| 96 | +await page.goto(`${server.baseUrl}/tools/preview-generator-v2/index.html`, { waitUntil: 'domcontentloaded' }); |
| 97 | +await page.waitForSelector('#shared-theme-header'); |
| 98 | +
|
| 99 | +const requiredSelectors = [ |
| 100 | + '.palette-manager-v2__menu-sample', |
| 101 | + '#executeBtn', |
| 102 | + '#pickRepoBtn', |
| 103 | + '#stopBtn', |
| 104 | + '#targetTypeSamples', |
| 105 | + '#targetTypeGames', |
| 106 | + '#targetTypeTools', |
| 107 | + '#baseUrl', |
| 108 | + '#waitMs', |
| 109 | + '#assetFolder', |
| 110 | + '#forceRewrite', |
| 111 | + '#onlyCaptureTimeout', |
| 112 | + '#captureModeFullScreen', |
| 113 | + '#captureModeCanvasOnly', |
| 114 | + '#sampleList', |
| 115 | + '#repoSelectedValue', |
| 116 | + '#writeFolderSampleValue', |
| 117 | + '#writeFolderActualValue', |
| 118 | + '#status', |
| 119 | + '#log', |
| 120 | + '#frame' |
| 121 | +]; |
| 122 | +for (const selector of requiredSelectors) { |
| 123 | + const locator = page.locator(selector); |
| 124 | + if (await locator.count() !== 1) { |
| 125 | + throw new Error(`Expected one ${selector}`); |
| 126 | + } |
| 127 | +} |
| 128 | +
|
| 129 | +const menuButtons = await page.locator('.palette-manager-v2__menu-sample button').evaluateAll((buttons) => buttons.map((button) => button.textContent.trim())); |
| 130 | +if (JSON.stringify(menuButtons) !== JSON.stringify(['Generate Preview'])) { |
| 131 | + throw new Error(`Unexpected menu buttons: ${JSON.stringify(menuButtons)}`); |
| 132 | +} |
| 133 | +
|
| 134 | +const forbiddenSelectors = [ |
| 135 | + '#applyToGameButton', |
| 136 | + '#exportImageButton', |
| 137 | + '#destinationDataInput', |
| 138 | + 'textarea[aria-label*="JSON" i]', |
| 139 | + 'textarea[id*="json" i]', |
| 140 | + 'pre[id*="json" i]' |
| 141 | +]; |
| 142 | +for (const selector of forbiddenSelectors) { |
| 143 | + if (await page.locator(selector).count() !== 0) { |
| 144 | + throw new Error(`Forbidden JSON/action UI found: ${selector}`); |
| 145 | + } |
| 146 | +} |
| 147 | +
|
| 148 | +const sectionNames = await page.locator('.accordion-v2__header span:first-child').evaluateAll((items) => items.map((item) => item.textContent.trim())); |
| 149 | +for (const expected of ['Repo Destination', 'Target Source', 'Render Controls', 'Paths or IDs', 'Output Summary', 'Status']) { |
| 150 | + if (!sectionNames.includes(expected)) { |
| 151 | + throw new Error(`Missing section: ${expected}`); |
| 152 | + } |
| 153 | +} |
| 154 | +
|
| 155 | +await page.fill('#baseUrl', server.baseUrl); |
| 156 | +await page.fill('#waitMs', '3000'); |
| 157 | +await page.fill('#sampleList', '0107'); |
| 158 | +await page.check('#forceRewrite'); |
| 159 | +await page.click('#pickRepoBtn'); |
| 160 | +await page.waitForFunction(() => !document.getElementById('executeBtn').disabled); |
| 161 | +await page.click('#executeBtn'); |
| 162 | +await page.waitForFunction(() => document.getElementById('log').textContent.includes('===== SUMMARY ====='), null, { timeout: 35000 }); |
| 163 | +const writes = await page.evaluate(() => window.__previewGeneratorV2Writes || []); |
| 164 | +if (writes.length !== 1) { |
| 165 | + throw new Error(`Expected exactly one preview write, got ${writes.length}`); |
| 166 | +} |
| 167 | +if (!writes[0].path.endsWith('samples/phase-01/0107/assets/images/preview.svg')) { |
| 168 | + throw new Error(`Unexpected write path: ${writes[0].path}`); |
| 169 | +} |
| 170 | +if (!writes[0].content.includes('<svg')) { |
| 171 | + throw new Error('Generated content is not SVG-like.'); |
| 172 | +} |
| 173 | +if (errors.length || consoleErrors.length) { |
| 174 | + throw new Error([...errors, ...consoleErrors].join(' | ')); |
| 175 | +} |
| 176 | +await browser.close(); |
| 177 | +await server.close(); |
| 178 | +console.log('preview-generator-v2 reskin browser smoke valid'); |
| 179 | +'@ | node --input-type=module - |
| 180 | +``` |
| 181 | + |
18 | 182 | ## Notes |
19 | 183 |
|
20 | | -`tools/preview/preview_svg_generator.html` was used as the existing working preview generator because there is no literal `preview.html` file in the repository. |
| 184 | +`tools/preview-generator-v2/index.html` remains the cold-copy working generator base with the existing inline generator script and functional element IDs preserved. |
| 185 | + |
| 186 | +The reskin only regroups the existing controls into Palette Manager-style header, menu, panels, and accordion sections. The nav contains only `Generate Preview`, using the existing `executeBtn` behavior. |
| 187 | + |
| 188 | +The targeted Playwright smoke verifies the reskinned layout, absence of JSON/schema UI controls, and the existing generator path by writing a fake `preview.svg` through the File System Access API shim. |
21 | 189 |
|
22 | 190 | `npm run test:workspace-v2` was attempted, but the script is not defined in this checkout. |
23 | 191 |
|
24 | | -Full samples smoke test was skipped because this PR is scoped to rolling back Preview Generator V2 and cold-copying one tool file. |
| 192 | +Full samples smoke test was skipped because this PR is scoped to Preview Generator V2 only. |
0 commit comments