Commit graph

6 commits

Author SHA1 Message Date
9289e60beb
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).
2026-04-21 13:32:46 -06:00
8828908a59
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.
2026-04-18 20:43:11 -06:00
c6c79c678b
refactor(chess): unregister Empty layout — keep as internal fixture only
The Empty layout had no real user need:
- Can't Play Solo (board with no pieces, nothing to click).
- Server rejects it for Create Room (validator requires >=1 king
  per side).
- The 'blank canvas' use case is already handled by the
  LayoutEditor's Custom... flow, which opens with an empty piece
  list and lets the user compose.

It was UI clutter in the picker dropdown — users would see
'Empty Board' alongside real layouts and have no way to actually
use it.

EMPTY_LAYOUT stays exported for unit tests that need a zero-piece
starting state, but it no longer registers in LAYOUT_REGISTRY, is
not imported by the layouts barrel side-effects, and no longer
appears in the picker. Source flipped to 'custom' to signal it's
not a user-selectable premade.

Tests updated:
- premades.test.ts asserts 'empty' is NOT in the registry.
- e2e/layouts.spec.ts removes the Empty-solo test and asserts the
  'empty' option is absent from the picker dropdown.

1025 unit tests + 23 e2e tests green.
2026-04-18 20:39:08 -06:00
ee2c175963
test(chess): expand layouts e2e with 10 new scenarios
Covers gaps in the original Phase E spec:

- Solo play reaches the engine: picking Pawns-Only and Classic and
  asserting the rendered board matches the layout's piece placement
  (not just that the UI dropdown says so).
- Empty layout solo: documents current behavior (solo lets users
  poke at any layout, validator only gates multiplayer).
- Chess960 re-randomizes: takes 8 consecutive snapshots of the
  back rank and asserts they're not all identical (p(collision)^7
  is effectively zero).
- Valid ?fen query pre-selects Custom (inverse of the existing
  malformed-FEN test).
- Library operations: full save → star → unstar → delete round
  trip via the library drawer's aria-labeled buttons.
- Layout badge hidden for classic solo games.
- Multiplayer with Dunsany: full pipeline proof — picker →
  Create Room UI → server resolves + validates → room.created
  echo → joiner renders same Dunsany setup via room.joined echo.
  Compares rank-1 and rank-8 snapshots between two browser
  contexts to verify bit-identical rendering.
- Server-side rejection: sends a kingless custom layout via raw
  WebSocket and asserts LAYOUT_INVALID error with a human-readable
  king-related message.

New server-spawn block mirrors multiplayer.spec.ts so the layouts
tests can run in isolation or against an existing dev server.

24/24 Playwright tests passing (21 layouts + 2 multiplayer +
1 full-flow) in ~40s.
2026-04-18 20:28:56 -06:00
89c22d6bd5
fix(chess): global Esc handler + stable test selectors for layouts e2e
- LayoutEditor: move Esc handling to a window keydown listener so
  the modal closes regardless of where focus currently sits (the
  prior onKeyDown on the modal div only fired when the root div
  itself held focus).
- LayoutPicker: add data-testid on the description <p> so e2e
  assertions can target it unambiguously instead of relying on
  free-text matches that also select the hidden <option> labels.
- e2e: use the new testid.

All 12 layouts e2e tests pass; existing multiplayer + full-flow
e2e suites still green.
2026-04-18 20:23:07 -06:00
832ebe9cd6
feat(chess): custom-layout editor + saved library (Phase E)
Adds a full in-browser editor for authoring custom starting
layouts and a persistent library to save them across sessions.

persist/layout-library.ts:
- SavedLayout shape with id/name/pieces/starred/updatedAt.
- saveToLibrary / loadLibrary / deleteFromLibrary / setStarred /
  duplicateEntry / makeId helpers.
- Capacity cap at 20 entries; oldest non-starred is evicted on
  overflow; all-starred + full returns { ok: false, reason }
  so the UI can surface an actionable message.
- Localstorage key is versioned (houserules:layouts:v1) for a
  future migration path.
- 14 unit tests cover eviction, shape validation, star toggles,
  duplicate flow, and the crypto.randomUUID fallback.

ui/LayoutEditor.tsx:
- Modal overlay with three panels — palette (white/black pieces +
  Erase + Clear), interactive 8x8 board with click-to-place
  brushes, and an actions panel.
- Live FEN textarea (toFen/fromFen round-trips) with a Load
  button that replaces the board and a Reset-to-board sync.
- Live validation panel shows errors (block CTA) and warnings
  (inform but don't block). The 'Use This Layout' CTA is disabled
  until errors clear.
- Save to Library writes a SavedLayout; the integrated Library
  drawer lists saved layouts sorted by starred-first + most-recent,
  with Load / Star / Duplicate / Delete actions.
- Copy Share Link writes a \${origin}/?fen=...&name=... URL to the
  clipboard — pastes straight into the lobby's existing query-param
  pre-select flow from Phase D.
- Esc closes the modal.

ui/Lobby.tsx:
- Custom... entry in the layout picker opens the editor.
- onApply(layout) commits the custom layout as the lobby's
  current selection so Create Room ships it to the server.

e2e/layouts.spec.ts:
- Picker renders every premade + Custom entry.
- Selecting Dunsany updates description; ?layoutId=dunsany and
  malformed ?fen behave correctly.
- Editor opens on Custom, validates king count, erases pieces,
  loads FEN, commits via 'Use This Layout', saves to library
  with cross-reload persistence, closes on Esc.

1025 unit tests passing; bun run check clean. Playwright suite
requires dev server — run with \`bun run --filter @paratype/chess e2e\`
when needed.
2026-04-18 20:20:41 -06:00