Commit graph

9 commits

Author SHA1 Message Date
7bee3cbaa9
feat(ui): hide modifier tooltip on pieces with no active modifiers
The hover tooltip previously rendered on every piece regardless of
whether it had any modifier facts, showing just a piece-type header and
'No active modifiers' — noise with zero information the user can't
already see on the board.

Now returns null when there are no modifier rows. The pinned panel
(click-to-pin) keeps its empty-state copy because an explicit pin is a
deliberate inspect action where confirming 'nothing here' is valid.

Tests:
- Inverted the two T24 hover tests to assert the tooltip does NOT render
  on unmodified pieces (b1 knight, e2 pawn on a vanilla solo game).
- Added a positive test: hover a modified pawn (HP +1 from a seeded
  profile) and assert the tooltip + at least one row are visible.
2026-04-19 16:58:04 -06:00
567480a788
test(e2e): unblock the 3 fixme tests in modifier-profiles spec
P6 (source chain in pinned panel):
  Required two server-side changes to make the badge actually meaningful:
  - Add `profile` field to GameStatePayload schema (server emits it,
    client receives it) so multiplayer clients see the room's active
    profile metadata, not just the modifier facts.
  - Make `ChessEngine.activeProfile` mutable via `setActiveProfile()`
    so PredictionManager can sync it from `game.state` snapshots.
  Also wire `modifier-profile.updated` through GameClient + Prediction-
  Manager so hot-swap broadcasts update the engine's profile field
  reactively.
  Fix Lobby.handlePlaySolo's resetToFreshGame to forward the selected
  profile to the new ChessEngine — otherwise the local engine had
  modifier facts (via server reconcile) but no profile metadata,
  breaking source-chain attribution and any other profile-aware UI.

P7 (multiplayer propose → approve → both observe updated):
P8 (multiplayer propose → reject → no updated broadcast):
  Implemented at the WS-protocol level using two parallel raw sockets
  per test (mirrors multiplayer.spec.ts pattern). Critical sequencing:
  - Both sockets opened concurrently via Promise.all so opponent is
    listening BEFORE host's propose arrives at the server (otherwise
    proposal-pending broadcasts to nobody and the test deadlocks).
  - Token must travel at the envelope level, not in payload, for the
    server's reconnect-by-token path to fire (otherwise hits ROOM_FULL
    on the second connection from each player).
  - game.move payload uses algebraic notation strings ('a2', 'a3'), not
    square indices — the protocol schema only accepts strings.
  - Host re-uses original room.create token, opponent re-uses their
    join token. Server's reconnectManager treats both as grace-window
    reconnects since the original WS closed cleanly.

Verification:
  - 1231 unit tests pass (96 files)
  - 58/58 Playwright tests pass in 1.9 min (was 55 + 3 fixme)
  - Total Playwright surface coverage: solo-smoke (7) + multiplayer (2) +
    full-flow (1) + layouts (24) + modifier-profiles (24 — including all
    8 T2-polish tests, 0 fixme).
2026-04-19 13:15:15 -06:00
92dae32f31
test(e2e): T2 polish vertical slice + solo regression guards
Adds 8 Playwright scenarios to modifier-profiles.spec.ts under a new
'T2 polish' describe block:

  P1  editor undo/redo across 3 distinct type-modifier adds
  P2  copy / paste wire: Copy lights the Paste button with a count
  P3  paste-type-modifier disabled when clipboard empty (baseline)
  P4  conflict panel: seed an invuln-king profile via localStorage,
      bind layout=classic, Load, observe error + Fix clears it
  P5  modifier-indicator rendered without hover (create-room path,
      with the same no-WS-server test.skip fallback T26 uses)
  P6  source-chain in pinned panel — test.fixme; ModifierPinnedPanel
      computes row.source but does not render it yet
  P7  multiplayer propose->approve e2e — test.fixme; needs a
      two-context harness this spec doesn't have today. Protocol
      coverage lives at packages/server/src/ws.modifier-profile-
      consent.test.ts.
  P8  multiplayer propose->reject e2e — same harness gap as P7.

Adds 2 regression tests to solo-smoke.spec.ts:

  - Rules drawer: clicking the backdrop (far-left of viewport)
    closes the drawer and leaves the board interactive. Regression
    guard for the stuck-overlay pointer-events bug.
  - Modifier editor: Esc closes the editor but leaves the drawer
    open (capture-phase stopImmediatePropagation); a second Esc
    then closes the drawer. Documents the nested-Esc ordering
    contract and guards against a future change that would cascade
    both closes on one keystroke.

Result: 55 Playwright passing, 3 skipped (all documented fixme).
bun run check green.
2026-04-19 10:13:57 -06:00
c292695309
test(e2e): modifier profiles full vertical slice 2026-04-18 23:33:42 -06:00
278370a630
feat(ui): pinned modifier inspection panel
- Create ModifierPinnedPanel.tsx: fixed-position side panel with piece
  header, modifier list (label + describe() value), and close button
- Board.tsx: add onPieceClick prop, fire on piece click (distinct from drag)
- GameView.tsx: add pinnedPieceId state; clicking a piece toggles pin;
  clicking same piece again or × closes panel; panel renders fixed right-4
- 2 new e2e tests: click b1 pins panel with 'knight' text; × dismisses it
2026-04-18 23:28:33 -06:00
5f252e2dff
feat(ui): hover modifier tooltip
Adds ModifierTooltip component that reads MODIFIER_REGISTRY attrs from
engine.session for the hovered piece and renders them as labelled rows.
The tooltip always appears on piece hover (piece type + color header) and
shows modifier rows only when modifier facts are set on the entity.

Board.tsx gains an optional onPieceHover callback; GameView.tsx tracks
hoveredPieceId and renders the tooltip absolutely in the board wrapper.
A 120ms hide-delay prevents flicker when cursor briefly leaves a piece.

Two Playwright tests added: hover shows tooltip with piece name; hover
over unmodified piece shows zero modifier-tooltip-row elements.
2026-04-18 23:21:05 -06:00
cc0b7b0446
feat(ui): lobby profile picker integration
Adds a modifier profile picker next to the layout picker in the Lobby, and a header badge in GameView that surfaces the active profile's name.

Lobby:

- New <select data-testid="profile-picker"> loads entries from loadLibrary() on mount and refreshes when the ModifierProfileEditor closes (auto-selecting the most recently updated entry).

- Selecting a saved profile sets the active ModifierProfile; selecting 'Custom…' opens the existing editor modal.

- URL param ?modifierProfile=<b64> decodes + pre-selects even when the profile isn't in the local library, via a synthetic '<name> (from link)' option so the <select> can reflect the choice without collapsing it.

- handleCreate now sends payload.profile when a profile is selected and stashes modifier-profile-name in sessionStorage.

- handleJoin reads profile from the server's room.joined echo so late joiners see the badge on first paint.

GameView:

- New ModifierProfileBadge component mirrors LayoutBadge but reads modifier-profile-name from sessionStorage and uses fuchsia tones so it's visually distinct when both badges are present.

lobby-request.ts:

- OneShotRoomResult exposes the optional profile field the server now echoes (T19).

E2E:

- 2 new Playwright tests: 'create room with profile — badge shows in game' seeds the library via localStorage, selects the profile, creates the room, and asserts the badge text. 'URL pre-select loads profile in picker' base64-encodes a profile into ?modifierProfile= and verifies the picker shows the correct value + 'from link' synthetic label.

All 8 modifier-profiles e2e tests pass; bun run check green (1213/1213 unit tests).
2026-04-18 23:11:11 -06:00
28e03d06fa
feat(ui): per-instance modifier panel
- Export LayoutBoardView from LayoutEditor.tsx (reusable board display)
- Create PerInstancePanel.tsx: 8x8 board + per-square modifier list/form
- Wire PerInstancePanel into ModifierProfileEditor center panel
- Add bound-layout-picker select in editor header (uses LAYOUT_REGISTRY)
- 2 new e2e scenarios: no-layout prompt, attach modifier to b1 square
2026-04-18 22:56:31 -06:00
e23e69e0d0
feat(ui): modifier profile editor shell + rules drawer entry
Adds ModifierProfileEditor modal shell with 3 placeholder panels
(T21 catalog / T22 board preview / T23 profile list). Esc closes
the modal via a window keydown listener active only while isOpen.

Wires a 'Modifier Profiles' button into the RulesDrawer footer that
opens the editor. Adds e2e/modifier-profiles.spec.ts with 2 tests:
open-from-drawer and esc-to-close.
2026-04-18 22:35:06 -06:00