feat(frontend): game system selector UI + free-text coexistence + suggested-match nudge (#135) #207

Merged
claude-bot merged 1 commit from feat/135-system-selector-ui into feat/v3.9-game-aware-systems 2026-07-18 17:03:46 +00:00
Contributor

Frontend for the v3.9.0 Game-Aware Systems pillar. Targets the integration branch. Consumes #133's backend.

Closes #135.

What

  • GameSystemSelect component — a registry dropdown (short_name per system, value = id) plus a "Free text / Other" option (always first, the default) that reveals the existing free-text input. The free-text input is never removed; selecting a registry system hides it, switching back reveals it. systems/loading are lifted to the parent so CampaignDetail shares one fetch between the selector and the nudge.
  • fetchGameSystems() API client (GET /api/game-systems).
  • Create form (Dashboard.jsx) and settings edit form (CampaignDetail.jsx) use the selector; game_system_id threaded through createCampaign/updateCampaign. A subtle "Linked" badge appears on cards/header when game_system_ref is set.
  • Suggested-match nudge (GM only, CampaignDetail) — when a campaign is unlinked, has non-blank free text, and it matches a registry system's short_name/name/aliases (case-insensitive), a dismissible banner offers to link it. Accept sends a partial {game_system_id} PATCH (free text untouched); dismiss persists to localStorage (qb:gameSystemNudgeDismissed:<id>) so it doesn't nag. Never writes until the GM explicitly accepts.

Verification (Node 20)

  • 320 frontend tests pass (31 files; new coverage in Dashboard.test.jsx / CampaignDetail.test.jsx: both selector modes, create/save payload shape, nudge show/hide/dismiss/accept) — via node:20-alpine (npm ci && npm test).
  • Production build succeeds (npm run build, Vite + PWA).

Notes

  • The badge in the edit form reflects the live dropdown selection (updates before save); the header/card badge reflects the persisted game_system_ref.
  • Frontend-only — no backend changes.

🤖 Generated with Claude Code

Frontend for the **v3.9.0 Game-Aware Systems** pillar. Targets the integration branch. Consumes #133's backend. Closes #135. ## What - **`GameSystemSelect` component** — a registry dropdown (`short_name` per system, value = id) plus a "Free text / Other" option (always first, the default) that reveals the existing free-text input. The free-text input is never removed; selecting a registry system hides it, switching back reveals it. `systems`/`loading` are lifted to the parent so `CampaignDetail` shares one fetch between the selector and the nudge. - **`fetchGameSystems()`** API client (`GET /api/game-systems`). - **Create form** (`Dashboard.jsx`) and **settings edit form** (`CampaignDetail.jsx`) use the selector; `game_system_id` threaded through `createCampaign`/`updateCampaign`. A subtle "Linked" badge appears on cards/header when `game_system_ref` is set. - **Suggested-match nudge** (GM only, `CampaignDetail`) — when a campaign is unlinked, has non-blank free text, and it matches a registry system's `short_name`/`name`/`aliases` (case-insensitive), a dismissible banner offers to link it. Accept sends a partial `{game_system_id}` PATCH (free text untouched); dismiss persists to `localStorage` (`qb:gameSystemNudgeDismissed:<id>`) so it doesn't nag. Never writes until the GM explicitly accepts. ## Verification (Node 20) - **320 frontend tests pass** (31 files; new coverage in `Dashboard.test.jsx` / `CampaignDetail.test.jsx`: both selector modes, create/save payload shape, nudge show/hide/dismiss/accept) — via `node:20-alpine` (`npm ci && npm test`). - **Production build succeeds** (`npm run build`, Vite + PWA). ## Notes - The badge in the edit form reflects the live dropdown selection (updates before save); the header/card badge reflects the persisted `game_system_ref`. - Frontend-only — no backend changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Wires the #133 game-system registry into the campaign create/settings forms:
a new GameSystemSelect dropdown (registry systems + "Free text / Other",
defaulting to free text) sits alongside the existing free-text input on both
Dashboard's create form and CampaignDetail's edit form, threading
game_system_id through create/update payloads without ever removing the
free-text fallback. Cards/header show a subtle "Linked" badge when
game_system_ref is set. CampaignDetail also adds a GM-only, dismissible
suggested-match nudge: on load, if the campaign is unlinked and its free-text
game_system matches a registry system's short_name/name/aliases, offer to
link it (PATCH game_system_id only, free text untouched); dismissal persists
per-campaign in localStorage.

Files:
- api/gameSystems.js (new): fetchGameSystems() client for GET /api/game-systems
- components/GameSystemSelect.jsx (new): shared dropdown + free-text control
- pages/Dashboard.jsx: create form selector, Linked badge on cards
- pages/CampaignDetail.jsx: edit form selector, Linked badge, suggested-match
  nudge (findGameSystemMatch, accept/dismiss handlers)
- pages/Dashboard.test.jsx, pages/CampaignDetail.test.jsx: coverage for both
  modes, create/save payloads, and nudge show/hide/accept/dismiss

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot merged commit b8d0440165 into feat/v3.9-game-aware-systems 2026-07-18 17:03:46 +00:00
Sign in to join this conversation.
No description provided.