Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions assets/toolbox/sprites/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ function applySnapshot(snapshot) {
}

function updateDraftStatus() {
applyPaintedPixelsToGrid();
const status = document.querySelector("[data-sprites-draft-status]");
if (status) {
status.textContent = draftStatusText();
Expand Down Expand Up @@ -217,6 +218,8 @@ function setZoomLevel(zoomLevel) {
if (status) {
status.textContent = `Canvas zoom display: ${normalizedZoomLevel * 100}%.`;
}
applyPaintedPixelsToGrid();
renderPreview();
}

function pickCellColor(cell) {
Expand All @@ -232,6 +235,8 @@ function pickCellColor(cell) {
if (status) {
status.textContent = `Picker selected ${colorLabel(colorKey)} from the unsaved editor canvas.`;
}
applyPaintedPixelsToGrid();
renderPreview();
}

function setPixel(row, column, colorKey) {
Expand Down
101 changes: 101 additions & 0 deletions dev/tests/playwright/tools/SpritesToolShell.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,38 @@ function collectPageFailures(page) {
return { consoleErrors, failedRequests, pageErrors };
}

function spriteCell(page, row, column) {
return page.locator(`[data-sprite-pixel-row="${row}"][data-sprite-pixel-column="${column}"]`);
}

async function previewCellHasPaint(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 false;
}
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)));
return context.getImageData(x, y, 1, 1).data[3] > 0;
}, { column, gridSize, row });
}

async function expectCenterAndPreviewPainted(page, row, column, colorClass = null) {
const cell = spriteCell(page, row, column);
await expect(cell).toHaveClass(/is-painted/);
if (colorClass) {
await expect(cell).toHaveClass(new RegExp(colorClass));
}
expect(await previewCellHasPaint(page, row, column)).toBe(true);
}

async function expectCenterAndPreviewEmpty(page, row, column) {
await expect(spriteCell(page, row, column)).not.toHaveClass(/is-painted/);
expect(await previewCellHasPaint(page, row, column)).toBe(false);
}

test("Sprite Creator shell loads with visible tool, canvas, details, and status regions", async ({ page }) => {
const server = await startSpriteShellTestServer();
const failures = collectPageFailures(page);
Expand Down Expand Up @@ -366,3 +398,72 @@ test("Sprite Creator shell loads with visible tool, canvas, details, and status
await server.close();
}
});

test("Sprite Creator keeps center canvas and right preview in sync", async ({ page }) => {
const server = await startSpriteShellTestServer();
const failures = collectPageFailures(page);

try {
await page.goto(`${server.baseUrl}/toolbox/sprites/index.html`, { waitUntil: "networkidle" });

await spriteCell(page, 1, 1).click();
await expectCenterAndPreviewPainted(page, 1, 1, "sprite-canvas-cell--ink");

await page.getByRole("button", { name: "Eraser tool" }).click();
await spriteCell(page, 1, 1).click();
await expectCenterAndPreviewEmpty(page, 1, 1);

await page.getByRole("button", { name: "Blue editor color" }).click();
await page.getByRole("button", { name: "Fill tool" }).click();
await expectCenterAndPreviewPainted(page, 1, 1, "sprite-canvas-cell--blue");

await page.getByRole("button", { name: "Clear Canvas" }).click();
await expectCenterAndPreviewEmpty(page, 1, 1);

await page.getByRole("button", { name: "Undo" }).click();
await expectCenterAndPreviewPainted(page, 1, 1, "sprite-canvas-cell--blue");

await page.getByRole("button", { name: "Redo" }).click();
await expectCenterAndPreviewEmpty(page, 1, 1);

await page.getByRole("button", { name: "Gold editor color" }).click();
await page.getByRole("button", { name: "Pencil tool" }).click();
await spriteCell(page, 2, 2).click();
await expectCenterAndPreviewPainted(page, 2, 2, "sprite-canvas-cell--gold");
await page.getByRole("button", { name: "Blue editor color" }).click();
await page.getByRole("button", { name: "Picker tool" }).click();
await spriteCell(page, 2, 2).click();
await expect(page.locator("[data-sprites-palette-status]")).toContainText("Active editor color: Gold");
await expectCenterAndPreviewPainted(page, 2, 2, "sprite-canvas-cell--gold");

await page.getByRole("button", { name: "Zoom tool" }).click();
await page.getByRole("button", { name: "200%" }).click();
await expect(page.locator("[data-sprites-grid-shell]")).toHaveAttribute("data-sprites-zoom-level", "2");
await expectCenterAndPreviewPainted(page, 2, 2, "sprite-canvas-cell--gold");

await page.getByRole("button", { name: "Clear Canvas" }).click();
await page.getByRole("button", { name: "Green editor color" }).click();
await page.getByRole("button", { name: "Line tool" }).click();
await spriteCell(page, 1, 1).click();
await spriteCell(page, 1, 4).click();
await expectCenterAndPreviewPainted(page, 1, 2, "sprite-canvas-cell--green");

await page.getByRole("button", { name: "Clear Canvas" }).click();
await page.getByRole("button", { name: "Rectangle tool" }).click();
await spriteCell(page, 2, 2).click();
await spriteCell(page, 4, 4).click();
await expectCenterAndPreviewPainted(page, 2, 3, "sprite-canvas-cell--green");

await page.getByRole("button", { name: "Clear Canvas" }).click();
await page.getByRole("button", { name: "Circle tool" }).click();
await spriteCell(page, 8, 8).click();
await spriteCell(page, 8, 11).click();
await expectCenterAndPreviewPainted(page, 8, 11, "sprite-canvas-cell--green");

expect(failures.failedRequests).toEqual([]);
expect(failures.pageErrors).toEqual([]);
expect(failures.consoleErrors).toEqual([]);
} finally {
await server.close();
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# PR_26179_CHARLIE_033-sprites-canvas-preview-sync

Team: CHARLIE

Mode: Batch governance stacked feature workflow

Base branch: PR_26179_CHARLIE_032-sprites-shape-tools

Branch: PR_26179_CHARLIE_033-sprites-canvas-preview-sync

## Summary

Fixed the Sprite Creator center-canvas and right-preview sync path so both render from the same page-session editor state. The center grid is repainted from `editorState.paintedPixels` whenever draft status/preview refreshes, and picker/zoom paths also trigger a repaint even though they do not mutate pixels.

## Branch Validation

PASS

- Built from `PR_26179_CHARLIE_032-sprites-shape-tools`.
- Scope stayed limited to canvas/preview synchronization and regression coverage.
- No DB/API/schema changes.
- No persistence or browser-owned authoritative product data added.
- No start_of_day files changed.
- ZIP package created at the requested path.

## Requirement Checklist

PASS - Right preview updates from page-session editor state.

PASS - Center editor canvas updates from the same page-session editor state.

PASS - Drawing repaints center canvas immediately.

PASS - Erase repaints center canvas immediately.

PASS - Fill repaints center canvas immediately.

PASS - Clear repaints center canvas immediately.

PASS - Undo repaints center canvas immediately.

PASS - Redo repaints center canvas immediately.

PASS - Picker repaints center canvas and preview without mutating pixels.

PASS - Zoom repaints center canvas and preview without mutating pixels.

PASS - Shape tools repaint center canvas immediately.

PASS - Playwright regression coverage added for center canvas and preview sync.

## 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 -- assets/toolbox/sprites/js/index.js 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. Draw a pixel and confirm the center grid and right preview both update.
3. Erase the pixel and confirm both surfaces clear.
4. Fill, clear, undo, and redo; confirm both surfaces stay matched.
5. Use Picker and Zoom; confirm both surfaces remain matched.
6. Draw Line, Rectangle, and Circle shapes; confirm center grid and right preview both show the shape.

## ZIP

`dev/workspace/zip/PR_26179_CHARLIE_033-sprites-canvas-preview-sync_delta.zip`
3 changes: 1 addition & 2 deletions docs_build/dev/reports/codex_changed_files.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
assets/toolbox/sprites/js/index.js
dev/tests/playwright/tools/SpritesToolShell.spec.mjs
docs_build/dev/reports/PR_26179_CHARLIE_032-sprites-shape-tools.md
docs_build/dev/reports/PR_26179_CHARLIE_033-sprites-canvas-preview-sync.md
docs_build/dev/reports/codex_changed_files.txt
docs_build/dev/reports/codex_review.diff
toolbox/sprites/index.html
Loading
Loading