diff --git a/assets/theme-v2/css/gamefoundrystudio.css b/assets/theme-v2/css/gamefoundrystudio.css index 5786394a2..b92e7101e 100644 --- a/assets/theme-v2/css/gamefoundrystudio.css +++ b/assets/theme-v2/css/gamefoundrystudio.css @@ -1184,6 +1184,10 @@ body.tool-focus-mode .tool-column:last-of-type { grid-template-rows: repeat(32, minmax(0, 1fr)) } +.sprite-canvas-shell[data-sprites-zoom-level="0.5"] .sprite-canvas-grid { + width: min(260px, 50%) +} + .sprite-canvas-shell[data-sprites-zoom-level="2"] .sprite-canvas-grid { width: min(720px, 160%) } @@ -1212,26 +1216,31 @@ body.tool-focus-mode .tool-column:last-of-type { background: var(--text) } +.sprite-canvas-cell.is-painted.sprite-canvas-cell--ink, .sprite-canvas-cell--ink, .sprite-color-chip--ink { background: var(--text) } +.sprite-canvas-cell.is-painted.sprite-canvas-cell--orange, .sprite-canvas-cell--orange, .sprite-color-chip--orange { background: var(--molten-orange) } +.sprite-canvas-cell.is-painted.sprite-canvas-cell--gold, .sprite-canvas-cell--gold, .sprite-color-chip--gold { background: var(--forge-gold) } +.sprite-canvas-cell.is-painted.sprite-canvas-cell--green, .sprite-canvas-cell--green, .sprite-color-chip--green { background: var(--green) } +.sprite-canvas-cell.is-painted.sprite-canvas-cell--blue, .sprite-canvas-cell--blue, .sprite-color-chip--blue { background: var(--electric-blue) diff --git a/assets/toolbox/sprites/js/index.js b/assets/toolbox/sprites/js/index.js index e6d249c52..48f8ba56e 100644 --- a/assets/toolbox/sprites/js/index.js +++ b/assets/toolbox/sprites/js/index.js @@ -1,7 +1,7 @@ const DEFAULT_GRID_SIZE = 16; const DEFAULT_FRAME_DURATION_MS = 120; const SUPPORTED_GRID_SIZES = Object.freeze([16, 32]); -const SUPPORTED_ZOOM_LEVELS = Object.freeze([1, 2, 4]); +const SUPPORTED_ZOOM_LEVELS = Object.freeze([0.5, 1, 2, 4]); const SHAPE_TOOLS = Object.freeze(["line", "rectangle", "circle"]); const EDITOR_TOOLS = Object.freeze(["pencil", "eraser", "fill", "picker", "zoom", ...SHAPE_TOOLS]); const EDITOR_COLOR_KEYS = Object.freeze(["ink", "orange", "gold", "green", "blue"]); diff --git a/dev/tests/playwright/tools/SpritesToolShell.spec.mjs b/dev/tests/playwright/tools/SpritesToolShell.spec.mjs index b04cb375d..3282b79fa 100644 --- a/dev/tests/playwright/tools/SpritesToolShell.spec.mjs +++ b/dev/tests/playwright/tools/SpritesToolShell.spec.mjs @@ -219,6 +219,25 @@ async function previewCellHasPaint(page, row, column) { }, { column, gridSize, row }); } +async function previewCellColor(page, row, column) { + const gridSize = Number(await page.locator("[data-sprites-pixel-grid]").getAttribute("data-sprites-grid-size")); + return page.locator("[data-sprites-preview-canvas]").evaluate((canvas, coordinates) => { + const context = canvas.getContext("2d"); + if (!context || !Number.isFinite(coordinates.gridSize) || coordinates.gridSize <= 0) { + return ""; + } + const cellSize = canvas.width / coordinates.gridSize; + const x = Math.max(0, Math.min(canvas.width - 1, Math.floor((coordinates.column - 0.5) * cellSize))); + const y = Math.max(0, Math.min(canvas.height - 1, Math.floor((coordinates.row - 0.5) * cellSize))); + const pixel = context.getImageData(x, y, 1, 1).data; + return `rgb(${pixel[0]}, ${pixel[1]}, ${pixel[2]})`; + }, { column, gridSize, row }); +} + +async function centerCellColor(page, row, column) { + return spriteCell(page, row, column).evaluate((cell) => getComputedStyle(cell).backgroundColor); +} + async function canvasHasAnyPaint(locator) { return locator.evaluate((canvas) => { const context = canvas.getContext("2d"); @@ -240,6 +259,9 @@ async function expectCenterAndPreviewPainted(page, row, column, colorClass = nul await expect(cell).toHaveClass(/is-painted/); if (colorClass) { await expect(cell).toHaveClass(new RegExp(colorClass)); + const centerColor = await centerCellColor(page, row, column); + expect(centerColor).not.toBe("rgb(255, 255, 255)"); + expect(await previewCellColor(page, row, column)).toBe(centerColor); } expect(await previewCellHasPaint(page, row, column)).toBe(true); } @@ -337,6 +359,7 @@ test("Sprite Creator shell loads with visible tool, canvas, details, and status await expect(page.locator("[data-sprites-pixel-grid] [role='gridcell']")).toHaveCount(256); await expect(page.locator("[data-sprites-grid-status]")).toContainText("Canvas display mode: 16x16"); await expect(page.locator("[data-sprites-zoom-status]")).toContainText("100%"); + await expect(page.getByRole("button", { name: "50%" })).toBeVisible(); await page.getByRole("button", { name: "32x32" }).click(); await expect(page.locator("[data-sprites-pixel-grid]")).toHaveAttribute("aria-label", "Sprite Creator 32 by 32 pixel canvas"); await expect(page.locator("[data-sprites-pixel-grid] [role='gridcell']")).toHaveCount(1024); @@ -412,6 +435,10 @@ test("Sprite Creator shell loads with visible tool, canvas, details, and status await expect(page.locator("[data-sprites-zoom-status]")).toContainText("400%"); await page.getByRole("button", { name: "100%" }).click(); await expect(page.locator("[data-sprites-grid-shell]")).toHaveAttribute("data-sprites-zoom-level", "1"); + await page.getByRole("button", { name: "50%" }).click(); + await expect(page.locator("[data-sprites-grid-shell]")).toHaveAttribute("data-sprites-zoom-level", "0.5"); + await expect(page.locator("[data-sprites-zoom-status]")).toContainText("50%"); + await expect(page.locator("[data-sprites-pixel-grid] [role='gridcell']")).toHaveCount(256); const gridCells = page.locator("[data-sprites-pixel-grid] [role='gridcell']"); await page.getByRole("button", { name: "Clear Canvas" }).click(); await page.getByRole("button", { name: "Green editor color" }).click(); @@ -472,14 +499,14 @@ test("Sprite Creator canvas grid dimensions stay exact across modes and zoom", a await page.goto(`${server.baseUrl}/toolbox/sprites/index.html`, { waitUntil: "networkidle" }); await expectExactGridDimensions(page, 16); - for (const zoomLabel of ["200%", "400%", "100%"]) { + for (const zoomLabel of ["50%", "200%", "400%", "100%"]) { await page.getByRole("button", { name: zoomLabel }).click(); await expectExactGridDimensions(page, 16); } await page.getByRole("button", { name: "32x32" }).click(); await expectExactGridDimensions(page, 32); - for (const zoomLabel of ["200%", "400%", "100%"]) { + for (const zoomLabel of ["50%", "200%", "400%", "100%"]) { await page.getByRole("button", { name: zoomLabel }).click(); await expectExactGridDimensions(page, 32); } diff --git a/docs_build/dev/reports/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix.md b/docs_build/dev/reports/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix.md new file mode 100644 index 000000000..aa665b793 --- /dev/null +++ b/docs_build/dev/reports/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix.md @@ -0,0 +1,78 @@ +# PR_26179_CHARLIE_039-sprites-color-and-zoom-fix + +Team: CHARLIE + +Mode: Batch governance stacked bug-fix workflow + +Base branch: PR_26179_CHARLIE_038-sprites-grid-dimension-fix + +Branch: PR_26179_CHARLIE_039-sprites-color-and-zoom-fix + +## Summary + +Fixed Sprite Creator center canvas selected-color rendering and added a 50% zoom option. The center grid now uses color-specific painted-cell selectors with enough specificity to override the generic painted fallback, so Pencil, Fill, and Shape tools display the selected palette color instead of defaulting visually to the fallback color. The Playwright coverage now compares the center cell computed color with the right preview canvas pixel color to prove both surfaces use the same selected-color page-session state. + +## Branch Validation + +PASS + +- Built from `PR_26179_CHARLIE_038-sprites-grid-dimension-fix`. +- Scope stayed limited to selected color rendering, 50% zoom, and regression coverage. +- No DB/API/schema changes. +- No browser-owned authoritative product data added. +- No start_of_day files changed. +- ZIP package created at the requested path. + +## Requirement Checklist + +PASS - Center canvas drawing color follows selected palette color. + +PASS - Pencil applies selected color to center grid. + +PASS - Fill applies selected color to center grid. + +PASS - Shape tools apply selected color to center grid. + +PASS - Center grid, right preview, frame data, animation preview, and exports use the same selected-color pixel state. + +PASS - Painted pixels do not default to white unless that is the selected palette color. + +PASS - 50% zoom option added. + +PASS - Existing 100%, 200%, and 400% zoom options retained. + +PASS - Zoom does not change grid dimensions or pixel state. + +PASS - Export path still works. + +PASS - No DB/API/schema changes. + +PASS - No browser-owned authoritative product data. + +PASS - No start_of_day changes. + +## Validation Lane + +PASS - `node --check assets/toolbox/sprites/js/index.js` + +PASS - `node --check dev/tests/playwright/tools/SpritesToolShell.spec.mjs` + +PASS - `git diff --check -- toolbox/sprites/index.html assets/toolbox/sprites/js/index.js assets/theme-v2/css/gamefoundrystudio.css dev/tests/playwright/tools/SpritesToolShell.spec.mjs` + +PASS - Runtime guard scan found no prohibited inline style/script/event-handler or stale placeholder text in touched runtime files. + +PASS - `npx playwright test dev/tests/playwright/tools/SpritesToolShell.spec.mjs --workers=1 --reporter=list` + +## Manual Validation Notes + +1. Open `toolbox/sprites/index.html`. +2. Select a non-white palette color such as Gold or Blue. +3. Draw with Pencil and confirm the center grid cell uses the selected color. +4. Confirm the right preview shows the same selected color. +5. Use Fill and Shape tools with a selected color and confirm the center grid matches. +6. Use 50%, 100%, 200%, and 400% zoom and confirm grid dimensions and pixels do not change. +7. Download PNG and Animation Strip and confirm export status still completes. + +## ZIP + +`dev/workspace/zip/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix_delta.zip` diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 52e5e47e7..06b902964 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,6 +1,7 @@ assets/theme-v2/css/gamefoundrystudio.css +assets/toolbox/sprites/js/index.js dev/tests/playwright/tools/SpritesToolShell.spec.mjs -docs_build/dev/reports/PR_26179_CHARLIE_038-sprites-grid-dimension-fix.md +docs_build/dev/reports/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix.md docs_build/dev/reports/codex_changed_files.txt docs_build/dev/reports/codex_review.diff toolbox/sprites/index.html diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 12b7a1fbc..fe4994612 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,162 +1,165 @@ diff --git a/assets/theme-v2/css/gamefoundrystudio.css b/assets/theme-v2/css/gamefoundrystudio.css -index 83a48ff98..5786394a2 100644 +index 5786394a2..b92e7101e 100644 --- a/assets/theme-v2/css/gamefoundrystudio.css +++ b/assets/theme-v2/css/gamefoundrystudio.css -@@ -1164,21 +1164,24 @@ body.tool-focus-mode .tool-column:last-of-type { +@@ -1184,6 +1184,10 @@ body.tool-focus-mode .tool-column:last-of-type { + grid-template-rows: repeat(32, minmax(0, 1fr)) } - .sprite-canvas-grid { -- --sprite-grid-size: 16; -+ box-sizing: border-box; - display: grid; -- grid-template-columns: repeat(var(--sprite-grid-size), minmax(0, 1fr)); -+ gap: 0; - width: min(100%, 520px); - aspect-ratio: 1; -+ overflow: hidden; - border: 1px solid var(--line); - background: var(--panel) ++.sprite-canvas-shell[data-sprites-zoom-level="0.5"] .sprite-canvas-grid { ++ width: min(260px, 50%) ++} ++ + .sprite-canvas-shell[data-sprites-zoom-level="2"] .sprite-canvas-grid { + width: min(720px, 160%) + } +@@ -1212,26 +1216,31 @@ body.tool-focus-mode .tool-column:last-of-type { + background: var(--text) } - .sprite-canvas-grid[data-sprites-grid-size="16"] { -- --sprite-grid-size: 16 -+ grid-template-columns: repeat(16, minmax(0, 1fr)); -+ grid-template-rows: repeat(16, minmax(0, 1fr)) ++.sprite-canvas-cell.is-painted.sprite-canvas-cell--ink, + .sprite-canvas-cell--ink, + .sprite-color-chip--ink { + background: var(--text) } - .sprite-canvas-grid[data-sprites-grid-size="32"] { -- --sprite-grid-size: 32 -+ grid-template-columns: repeat(32, minmax(0, 1fr)); -+ grid-template-rows: repeat(32, minmax(0, 1fr)) ++.sprite-canvas-cell.is-painted.sprite-canvas-cell--orange, + .sprite-canvas-cell--orange, + .sprite-color-chip--orange { + background: var(--molten-orange) } - .sprite-canvas-shell[data-sprites-zoom-level="2"] .sprite-canvas-grid { -@@ -1190,6 +1193,7 @@ body.tool-focus-mode .tool-column:last-of-type { ++.sprite-canvas-cell.is-painted.sprite-canvas-cell--gold, + .sprite-canvas-cell--gold, + .sprite-color-chip--gold { + background: var(--forge-gold) + } + ++.sprite-canvas-cell.is-painted.sprite-canvas-cell--green, + .sprite-canvas-cell--green, + .sprite-color-chip--green { + background: var(--green) } - .sprite-canvas-cell { -+ box-sizing: border-box; - min-width: 0; - min-height: 0; - padding: 0; ++.sprite-canvas-cell.is-painted.sprite-canvas-cell--blue, + .sprite-canvas-cell--blue, + .sprite-color-chip--blue { + background: var(--electric-blue) +diff --git a/assets/toolbox/sprites/js/index.js b/assets/toolbox/sprites/js/index.js +index e6d249c52..48f8ba56e 100644 +--- a/assets/toolbox/sprites/js/index.js ++++ b/assets/toolbox/sprites/js/index.js +@@ -1,7 +1,7 @@ + const DEFAULT_GRID_SIZE = 16; + const DEFAULT_FRAME_DURATION_MS = 120; + const SUPPORTED_GRID_SIZES = Object.freeze([16, 32]); +-const SUPPORTED_ZOOM_LEVELS = Object.freeze([1, 2, 4]); ++const SUPPORTED_ZOOM_LEVELS = Object.freeze([0.5, 1, 2, 4]); + const SHAPE_TOOLS = Object.freeze(["line", "rectangle", "circle"]); + const EDITOR_TOOLS = Object.freeze(["pencil", "eraser", "fill", "picker", "zoom", ...SHAPE_TOOLS]); + const EDITOR_COLOR_KEYS = Object.freeze(["ink", "orange", "gold", "green", "blue"]); diff --git a/dev/tests/playwright/tools/SpritesToolShell.spec.mjs b/dev/tests/playwright/tools/SpritesToolShell.spec.mjs -index 5a803eb54..b04cb375d 100644 +index b04cb375d..3282b79fa 100644 --- a/dev/tests/playwright/tools/SpritesToolShell.spec.mjs +++ b/dev/tests/playwright/tools/SpritesToolShell.spec.mjs -@@ -249,6 +249,52 @@ async function expectCenterAndPreviewEmpty(page, row, column) { - expect(await previewCellHasPaint(page, row, column)).toBe(false); +@@ -219,6 +219,25 @@ async function previewCellHasPaint(page, row, column) { + }, { column, gridSize, row }); } -+async function gridDimensionMetrics(page) { -+ return page.locator("[data-sprites-pixel-grid]").evaluate((grid) => { -+ const cells = Array.from(grid.querySelectorAll("[role='gridcell']")); -+ const gridRect = grid.getBoundingClientRect(); -+ let maxRight = 0; -+ let maxBottom = 0; -+ const styles = getComputedStyle(grid); -+ const templateTrackCount = (value) => value.split(" ").filter(Boolean).length; -+ -+ for (const cell of cells) { -+ const rect = cell.getBoundingClientRect(); -+ maxRight = Math.max(maxRight, rect.right); -+ maxBottom = Math.max(maxBottom, rect.bottom); ++async function previewCellColor(page, row, column) { ++ const gridSize = Number(await page.locator("[data-sprites-pixel-grid]").getAttribute("data-sprites-grid-size")); ++ return page.locator("[data-sprites-preview-canvas]").evaluate((canvas, coordinates) => { ++ const context = canvas.getContext("2d"); ++ if (!context || !Number.isFinite(coordinates.gridSize) || coordinates.gridSize <= 0) { ++ return ""; + } -+ -+ const lastRowCells = cells.slice(-Number(grid.dataset.spritesGridSize || 0)); -+ return { -+ cellCount: cells.length, -+ columnCount: templateTrackCount(styles.gridTemplateColumns), -+ heightDelta: Math.abs(maxBottom - gridRect.bottom), -+ lastCellColumn: cells.at(-1)?.dataset.spritePixelColumn || "", -+ lastCellRow: cells.at(-1)?.dataset.spritePixelRow || "", -+ lastRowCellCount: lastRowCells.length, -+ lastRowColumns: lastRowCells.map((cell) => cell.dataset.spritePixelColumn), -+ lastRowRows: lastRowCells.map((cell) => cell.dataset.spritePixelRow), -+ rowCount: templateTrackCount(styles.gridTemplateRows), -+ widthDelta: Math.abs(maxRight - gridRect.right), -+ }; -+ }); ++ const cellSize = canvas.width / coordinates.gridSize; ++ const x = Math.max(0, Math.min(canvas.width - 1, Math.floor((coordinates.column - 0.5) * cellSize))); ++ const y = Math.max(0, Math.min(canvas.height - 1, Math.floor((coordinates.row - 0.5) * cellSize))); ++ const pixel = context.getImageData(x, y, 1, 1).data; ++ return `rgb(${pixel[0]}, ${pixel[1]}, ${pixel[2]})`; ++ }, { column, gridSize, row }); +} + -+async function expectExactGridDimensions(page, expectedSize) { -+ await expect(page.locator("[data-sprites-pixel-grid] [role='gridcell']")).toHaveCount(expectedSize * expectedSize); -+ const metrics = await gridDimensionMetrics(page); -+ expect(metrics.cellCount).toBe(expectedSize * expectedSize); -+ expect(metrics.columnCount).toBe(expectedSize); -+ expect(metrics.rowCount).toBe(expectedSize); -+ expect(metrics.lastRowCellCount).toBe(expectedSize); -+ expect(metrics.lastCellRow).toBe(String(expectedSize)); -+ expect(metrics.lastCellColumn).toBe(String(expectedSize)); -+ expect(metrics.lastRowRows.every((row) => row === String(expectedSize))).toBe(true); -+ expect(metrics.lastRowColumns).toEqual(Array.from({ length: expectedSize }, (_, index) => String(index + 1))); -+ expect(metrics.widthDelta).toBeLessThanOrEqual(1); -+ expect(metrics.heightDelta).toBeLessThanOrEqual(1); ++async function centerCellColor(page, row, column) { ++ return spriteCell(page, row, column).evaluate((cell) => getComputedStyle(cell).backgroundColor); +} + - test("Sprite Creator shell loads with visible tool, canvas, details, and status regions", async ({ page }) => { - const server = await startSpriteShellTestServer(); - const failures = collectPageFailures(page); -@@ -418,6 +464,34 @@ test("Sprite Creator shell loads with visible tool, canvas, details, and status + async function canvasHasAnyPaint(locator) { + return locator.evaluate((canvas) => { + const context = canvas.getContext("2d"); +@@ -240,6 +259,9 @@ async function expectCenterAndPreviewPainted(page, row, column, colorClass = nul + await expect(cell).toHaveClass(/is-painted/); + if (colorClass) { + await expect(cell).toHaveClass(new RegExp(colorClass)); ++ const centerColor = await centerCellColor(page, row, column); ++ expect(centerColor).not.toBe("rgb(255, 255, 255)"); ++ expect(await previewCellColor(page, row, column)).toBe(centerColor); } - }); + expect(await previewCellHasPaint(page, row, column)).toBe(true); + } +@@ -337,6 +359,7 @@ test("Sprite Creator shell loads with visible tool, canvas, details, and status + await expect(page.locator("[data-sprites-pixel-grid] [role='gridcell']")).toHaveCount(256); + await expect(page.locator("[data-sprites-grid-status]")).toContainText("Canvas display mode: 16x16"); + await expect(page.locator("[data-sprites-zoom-status]")).toContainText("100%"); ++ await expect(page.getByRole("button", { name: "50%" })).toBeVisible(); + await page.getByRole("button", { name: "32x32" }).click(); + await expect(page.locator("[data-sprites-pixel-grid]")).toHaveAttribute("aria-label", "Sprite Creator 32 by 32 pixel canvas"); + await expect(page.locator("[data-sprites-pixel-grid] [role='gridcell']")).toHaveCount(1024); +@@ -412,6 +435,10 @@ test("Sprite Creator shell loads with visible tool, canvas, details, and status + await expect(page.locator("[data-sprites-zoom-status]")).toContainText("400%"); + await page.getByRole("button", { name: "100%" }).click(); + await expect(page.locator("[data-sprites-grid-shell]")).toHaveAttribute("data-sprites-zoom-level", "1"); ++ await page.getByRole("button", { name: "50%" }).click(); ++ await expect(page.locator("[data-sprites-grid-shell]")).toHaveAttribute("data-sprites-zoom-level", "0.5"); ++ await expect(page.locator("[data-sprites-zoom-status]")).toContainText("50%"); ++ await expect(page.locator("[data-sprites-pixel-grid] [role='gridcell']")).toHaveCount(256); + const gridCells = page.locator("[data-sprites-pixel-grid] [role='gridcell']"); + await page.getByRole("button", { name: "Clear Canvas" }).click(); + await page.getByRole("button", { name: "Green editor color" }).click(); +@@ -472,14 +499,14 @@ test("Sprite Creator canvas grid dimensions stay exact across modes and zoom", a + await page.goto(`${server.baseUrl}/toolbox/sprites/index.html`, { waitUntil: "networkidle" }); -+test("Sprite Creator canvas grid dimensions stay exact across modes and zoom", async ({ page }) => { -+ const server = await startSpriteShellTestServer(); -+ const failures = collectPageFailures(page); -+ -+ try { -+ await page.goto(`${server.baseUrl}/toolbox/sprites/index.html`, { waitUntil: "networkidle" }); -+ -+ await expectExactGridDimensions(page, 16); -+ for (const zoomLabel of ["200%", "400%", "100%"]) { -+ await page.getByRole("button", { name: zoomLabel }).click(); -+ await expectExactGridDimensions(page, 16); -+ } -+ -+ await page.getByRole("button", { name: "32x32" }).click(); -+ await expectExactGridDimensions(page, 32); -+ for (const zoomLabel of ["200%", "400%", "100%"]) { -+ await page.getByRole("button", { name: zoomLabel }).click(); -+ await expectExactGridDimensions(page, 32); -+ } -+ -+ expect(failures.failedRequests).toEqual([]); -+ expect(failures.pageErrors).toEqual([]); -+ expect(failures.consoleErrors).toEqual([]); -+ } finally { -+ await server.close(); -+ } -+}); -+ - test("Sprite Creator keeps center canvas and right preview in sync", async ({ page }) => { - const server = await startSpriteShellTestServer(); - const failures = collectPageFailures(page); -diff --git a/docs_build/dev/reports/PR_26179_CHARLIE_038-sprites-grid-dimension-fix.md b/docs_build/dev/reports/PR_26179_CHARLIE_038-sprites-grid-dimension-fix.md + await expectExactGridDimensions(page, 16); +- for (const zoomLabel of ["200%", "400%", "100%"]) { ++ for (const zoomLabel of ["50%", "200%", "400%", "100%"]) { + await page.getByRole("button", { name: zoomLabel }).click(); + await expectExactGridDimensions(page, 16); + } + + await page.getByRole("button", { name: "32x32" }).click(); + await expectExactGridDimensions(page, 32); +- for (const zoomLabel of ["200%", "400%", "100%"]) { ++ for (const zoomLabel of ["50%", "200%", "400%", "100%"]) { + await page.getByRole("button", { name: zoomLabel }).click(); + await expectExactGridDimensions(page, 32); + } +diff --git a/docs_build/dev/reports/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix.md b/docs_build/dev/reports/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix.md new file mode 100644 -index 000000000..4d8010eb4 +index 000000000..aa665b793 --- /dev/null -+++ b/docs_build/dev/reports/PR_26179_CHARLIE_038-sprites-grid-dimension-fix.md -@@ -0,0 +1,66 @@ -+# PR_26179_CHARLIE_038-sprites-grid-dimension-fix ++++ b/docs_build/dev/reports/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix.md +@@ -0,0 +1,78 @@ ++# PR_26179_CHARLIE_039-sprites-color-and-zoom-fix + +Team: CHARLIE + +Mode: Batch governance stacked bug-fix workflow + -+Base branch: PR_26179_CHARLIE_037-sprites-animation-export ++Base branch: PR_26179_CHARLIE_038-sprites-grid-dimension-fix + -+Branch: PR_26179_CHARLIE_038-sprites-grid-dimension-fix ++Branch: PR_26179_CHARLIE_039-sprites-color-and-zoom-fix + +## Summary + -+Fixed Sprite Creator center canvas grid dimensions. The Sprite Creator page now loads the shared `gamefoundrystudio.css` stylesheet that owns the canvas styles, and the canvas grid uses explicit 16x16 and 32x32 CSS row/column templates instead of an invalid custom-property repeat count. This prevents orphan or partial rows/columns and keeps zoom display controls from changing logical grid dimensions. ++Fixed Sprite Creator center canvas selected-color rendering and added a 50% zoom option. The center grid now uses color-specific painted-cell selectors with enough specificity to override the generic painted fallback, so Pencil, Fill, and Shape tools display the selected palette color instead of defaulting visually to the fallback color. The Playwright coverage now compares the center cell computed color with the right preview canvas pixel color to prove both surfaces use the same selected-color page-session state. + +## Branch Validation + +PASS + -+- Built from `PR_26179_CHARLIE_037-sprites-animation-export`. -+- Scope stayed limited to Sprite Creator grid dimensions and regression coverage. ++- Built from `PR_26179_CHARLIE_038-sprites-grid-dimension-fix`. ++- Scope stayed limited to selected color rendering, 50% zoom, and regression coverage. +- No DB/API/schema changes. +- No browser-owned authoritative product data added. +- No start_of_day files changed. @@ -164,15 +167,25 @@ index 000000000..4d8010eb4 + +## Requirement Checklist + -+PASS - 16x16 mode renders exactly 16 columns and 16 rows. ++PASS - Center canvas drawing color follows selected palette color. ++ ++PASS - Pencil applies selected color to center grid. ++ ++PASS - Fill applies selected color to center grid. + -+PASS - 32x32 mode renders exactly 32 columns and 32 rows. ++PASS - Shape tools apply selected color to center grid. + -+PASS - Prevents orphan or extra cells at bottom/right edge. ++PASS - Center grid, right preview, frame data, animation preview, and exports use the same selected-color pixel state. + -+PASS - Zoom levels 100%, 200%, and 400% do not change row/column count. ++PASS - Painted pixels do not default to white unless that is the selected palette color. + -+PASS - Preview/export behavior unchanged. ++PASS - 50% zoom option added. ++ ++PASS - Existing 100%, 200%, and 400% zoom options retained. ++ ++PASS - Zoom does not change grid dimensions or pixel state. ++ ++PASS - Export path still works. + +PASS - No DB/API/schema changes. + @@ -180,13 +193,13 @@ index 000000000..4d8010eb4 + +PASS - No start_of_day changes. + -+PASS - Targeted Playwright coverage added. -+ +## Validation Lane + ++PASS - `node --check assets/toolbox/sprites/js/index.js` ++ +PASS - `node --check dev/tests/playwright/tools/SpritesToolShell.spec.mjs` + -+PASS - `git diff --check -- toolbox/sprites/index.html assets/theme-v2/css/gamefoundrystudio.css dev/tests/playwright/tools/SpritesToolShell.spec.mjs` ++PASS - `git diff --check -- toolbox/sprites/index.html assets/toolbox/sprites/js/index.js assets/theme-v2/css/gamefoundrystudio.css dev/tests/playwright/tools/SpritesToolShell.spec.mjs` + +PASS - Runtime guard scan found no prohibited inline style/script/event-handler or stale placeholder text in touched runtime files. + @@ -195,36 +208,38 @@ index 000000000..4d8010eb4 +## Manual Validation Notes + +1. Open `toolbox/sprites/index.html`. -+2. Confirm the 16x16 canvas shows a complete square with 16 rows and 16 columns. -+3. Switch to 32x32 and confirm a complete 32-row, 32-column grid. -+4. Switch through 100%, 200%, and 400% zoom and confirm the cell count and rows/columns do not change. -+5. Confirm no partial trailing row or right-edge orphan cells are visible. ++2. Select a non-white palette color such as Gold or Blue. ++3. Draw with Pencil and confirm the center grid cell uses the selected color. ++4. Confirm the right preview shows the same selected color. ++5. Use Fill and Shape tools with a selected color and confirm the center grid matches. ++6. Use 50%, 100%, 200%, and 400% zoom and confirm grid dimensions and pixels do not change. ++7. Download PNG and Animation Strip and confirm export status still completes. + +## ZIP + -+`dev/workspace/zip/PR_26179_CHARLIE_038-sprites-grid-dimension-fix_delta.zip` ++`dev/workspace/zip/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix_delta.zip` diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt -index 736fb51d1..52e5e47e7 100644 +index 52e5e47e7..06b902964 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt -@@ -1,6 +1,6 @@ --assets/toolbox/sprites/js/index.js -+assets/theme-v2/css/gamefoundrystudio.css +@@ -1,6 +1,7 @@ + assets/theme-v2/css/gamefoundrystudio.css ++assets/toolbox/sprites/js/index.js dev/tests/playwright/tools/SpritesToolShell.spec.mjs --docs_build/dev/reports/PR_26179_CHARLIE_037-sprites-animation-export.md -+docs_build/dev/reports/PR_26179_CHARLIE_038-sprites-grid-dimension-fix.md +-docs_build/dev/reports/PR_26179_CHARLIE_038-sprites-grid-dimension-fix.md ++docs_build/dev/reports/PR_26179_CHARLIE_039-sprites-color-and-zoom-fix.md docs_build/dev/reports/codex_changed_files.txt docs_build/dev/reports/codex_review.diff toolbox/sprites/index.html diff --git a/toolbox/sprites/index.html b/toolbox/sprites/index.html -index 8e934f9b4..78d0e76c4 100644 +index 78d0e76c4..46b480acb 100644 --- a/toolbox/sprites/index.html +++ b/toolbox/sprites/index.html -@@ -9,6 +9,7 @@ - - - -+ - - -
+@@ -112,6 +112,7 @@ + + +