feat(chess): live-sync FEN textarea with board edits in the editor

Previously the FEN textarea held an independent draft that only
synced to the board when the user clicked Reset. Placing a piece
left the FEN showing stale content, which was confusing — the
field looked like a proxy for the board but wasn't.

Now the textarea mirrors the live board FEN by default:
- Placing, erasing, or clearing pieces updates the textarea
  immediately.
- Starting to type into the textarea marks it 'dirty' (via a ref
  so we don't rerender), pausing the auto-sync until the user
  Loads (accept the draft) or Discards (restore the live FEN).
- The 'Unsaved edit' indicator appears while dirty.
- Load / Discard buttons disable when draft equals live FEN —
  nothing to apply.
- Library load also resets the dirty flag, as that's a clean
  state reset.
- Removed the redundant 'Current: <fen>' helper line below the
  textarea; the textarea itself is the live FEN now.

E2E: 2 new tests
- FEN field updates live as pieces are placed.
- Typing into FEN field pauses live sync until Load or Discard.

25/25 Playwright tests passing. 1025 unit tests green.
This commit is contained in:
Joey Yakimowich-Payne 2026-04-18 20:43:11 -06:00
commit 8828908a59
No known key found for this signature in database
2 changed files with 110 additions and 19 deletions

View file

@ -168,6 +168,54 @@ test.describe('LayoutEditor modal', () => {
).toBeVisible();
});
test('FEN field updates live as pieces are placed on the board', async ({
page,
}) => {
const fenField = page.getByTestId('editor-fen');
// Empty board → all-eights FEN.
await expect(fenField).toHaveValue('8/8/8/8/8/8/8/8');
// Place a white king on e1.
await page.getByTestId('palette-white-king').click();
await page.getByTestId('editor-square-4').click();
// Textarea now reflects the board without any Load/Reset step.
await expect(fenField).toHaveValue('8/8/8/8/8/8/8/4K3');
// Place a black king on e8.
await page.getByTestId('palette-black-king').click();
await page.getByTestId('editor-square-60').click();
await expect(fenField).toHaveValue('4k3/8/8/8/8/8/8/4K3');
});
test('typing into FEN field stops live sync until Load or Discard', async ({
page,
}) => {
// Place a king so the live FEN has something meaningful.
await page.getByTestId('palette-white-king').click();
await page.getByTestId('editor-square-4').click();
const fenField = page.getByTestId('editor-fen');
await expect(fenField).toHaveValue('8/8/8/8/8/8/8/4K3');
// Start editing the textarea — live sync should now pause.
await fenField.fill('garbage in progress');
// "Unsaved edit" indicator appears; the Load button is enabled.
await expect(page.getByTestId('editor-fen-dirty')).toBeVisible();
// Adding more pieces to the board does NOT clobber the draft.
await page.getByTestId('palette-black-king').click();
await page.getByTestId('editor-square-60').click();
await expect(fenField).toHaveValue('garbage in progress');
// Discard restores the live FEN and clears the dirty indicator.
await page.locator('[data-action="sync-fen"]').click();
await expect(fenField).toHaveValue('4k3/8/8/8/8/8/8/4K3');
await expect(page.getByTestId('editor-fen-dirty')).toHaveCount(0);
});
test('Use This Layout commits a custom layout to the lobby', async ({ page }) => {
// Build minimal valid layout.
await page.getByTestId('palette-white-king').click();