From 9289e60beb274feed7b704d1ac16f06b681f7146 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Tue, 21 Apr 2026 13:32:46 -0600 Subject: [PATCH] feat(layouts): toggle-delete when clicking same brush-piece Clicking a square with the palette's currently-selected piece now deletes that piece instead of re-placing it. Makes tap-to-delete a natural single-gesture operation without switching to the Erase brush. A different brush-piece still replaces (unchanged behaviour). --- packages/chess/e2e/layouts.spec.ts | 45 ++++++++++++++++++++++++++ packages/chess/src/ui/LayoutEditor.tsx | 13 ++++++++ 2 files changed, 58 insertions(+) diff --git a/packages/chess/e2e/layouts.spec.ts b/packages/chess/e2e/layouts.spec.ts index 76ec9c5..54ba067 100644 --- a/packages/chess/e2e/layouts.spec.ts +++ b/packages/chess/e2e/layouts.spec.ts @@ -157,6 +157,51 @@ test.describe('LayoutEditor modal', () => { ).toHaveCount(0); }); + test('clicking a square with the same brush-piece toggles it off (delete)', async ({ + page, + }) => { + // Place a white king on e1 using the palette brush. + await page.getByTestId('palette-white-king').click(); + await page.getByTestId('editor-square-4').click(); + await expect( + page.getByTestId('editor-square-4').locator('img'), + ).toBeVisible(); + + // Same brush still selected — clicking the same square should + // TOGGLE the piece off instead of no-op / re-place. + await page.getByTestId('editor-square-4').click(); + await expect( + page.getByTestId('editor-square-4').locator('img'), + ).toHaveCount(0); + + // Clicking a 3rd time with the same brush re-adds the piece. + await page.getByTestId('editor-square-4').click(); + await expect( + page.getByTestId('editor-square-4').locator('img'), + ).toBeVisible(); + }); + + test('clicking a square with a DIFFERENT brush-piece replaces (does NOT delete)', async ({ + page, + }) => { + // Place a white king on e1. + await page.getByTestId('palette-white-king').click(); + await page.getByTestId('editor-square-4').click(); + + // Switch brush to black queen and click the same square — piece + // should be REPLACED, not deleted (only same-piece clicks toggle). + await page.getByTestId('palette-black-queen').click(); + await page.getByTestId('editor-square-4').click(); + await expect( + page.getByTestId('editor-square-4').locator('img'), + ).toBeVisible(); + // Alt text reflects the new piece (black queen), proving a + // replacement happened rather than a same-piece toggle. + await expect( + page.getByTestId('editor-square-4').locator('img'), + ).toHaveAttribute('alt', 'black queen'); + }); + test('FEN Load replaces the board', async ({ page }) => { const fen = '8/8/8/8/8/8/8/4K2k'; // white king e1, black king h1 (not a realistic position but valid) await page.getByTestId('editor-fen').fill(fen); diff --git a/packages/chess/src/ui/LayoutEditor.tsx b/packages/chess/src/ui/LayoutEditor.tsx index dd2fb3a..b5b565d 100644 --- a/packages/chess/src/ui/LayoutEditor.tsx +++ b/packages/chess/src/ui/LayoutEditor.tsx @@ -147,8 +147,21 @@ export function LayoutEditor({ function placeAt(square: number) { if (brush.type === 'none') return; setPlacements((prev) => { + const existing = prev.find((p) => p.square === square); const filtered = prev.filter((p) => p.square !== square); if (brush.type === 'erase') return filtered; + // Toggle-delete: clicking a square that already holds the + // exact piece the brush would place acts as an erase. This + // lets users tap-to-delete with the palette selection without + // first switching to the Erase brush — the same gesture that + // placed a piece undoes it. + if ( + existing !== undefined && + existing.type === brush.piece.type && + existing.color === brush.piece.color + ) { + return filtered; + } return [ ...filtered, { type: brush.piece.type, color: brush.piece.color, square },