[Game Systems] System selector UI + free-text coexistence + suggested-match nudge (frontend) #135

Closed
opened 2026-07-15 22:00:46 +00:00 by claude-bot · 1 comment
Contributor

Motivation/Context

With the GameSystem registry and campaigns.game_system_id FK in place (#133), GMs
need a way to actually pick a registry system from the UI — while free-text campaigns
keep working exactly as before. Most existing campaigns already have a free-text value
that names a known system ("D&D 5e", "PF2e", "5e", …); a cheap, safe suggestion
nudge covers the 90% case without any silent/automatic linking.

Approach

Selector UI. Add a registry dropdown to the campaign create form
(webapp/frontend/src/pages/Dashboard.jsx:107-111,138-142, input at :244-245) and the
campaign settings edit form (webapp/frontend/src/pages/CampaignDetail.jsx:340,387,1427-1428).
The existing free-text input is retained and presented as a "Free text / Other" option —
never removed, never gated behind the new selector. Campaign cards
(Dashboard.jsx:83-84) and the campaign header (CampaignDetail.jsx:965-966) continue
to render the resolved display string from the backend's resolution helper (#133), so no
client-side resolution logic is needed — just wire the new game_system_ref field
through if a distinct linked-system badge/icon is wanted.

Suggested-match nudge. On campaign settings load (GM only): if game_system_id IS NULL and lower(trim(game_system)) matches any registry aliases entry, show a
one-line banner — "This campaign's system looks like Pathfinder 2e — link it? (You
can undo this at any time.)"
Accepting sets game_system_id via the existing campaign
PATCH; the free-text column is left untouched. Declining is remembered (a
game_system_prompt_dismissed flag or localStorage) so the banner doesn't nag every
visit. This alias-matching check can run client-side against the registry list already
fetched for the dropdown, or server-side as a computed field on the campaign response —
implementer's call, but it must not write anything until the GM explicitly accepts.

Scope discipline. This is UI wiring only — the registry, the FK, the resolution
helper, and the game_system_ref schema field all come from #133 and are not
re-implemented here.

Dependencies

  • #133 (GameSystem registry + campaign linkage) — provides the registry list endpoint,
    game_system_id on create/update, and game_system_ref on read.

Out of scope

  • Any backend changes (registry CRUD, resolution helper, validation) — all in #133.
  • LLM prompt integration — issue 3.
  • Stat schemas, stat-block editor, conversion wizard — issues 4/5/7.
  • Automatic/silent linking of existing free-text values — the nudge is always an
    explicit GM opt-in.

Acceptance criteria

  • Campaign create form offers a registry dropdown (dnd5e, pf2e, generic) alongside a
    "Free text / Other" option that reveals the existing free-text input.
  • Campaign settings edit form offers the same selector and can switch a campaign
    between a linked system and free text at any time.
  • Campaign cards and campaign header continue to display the correct effective system
    name for both linked and free-form campaigns, sourced from the backend.
  • For a free-form campaign whose text matches a registry alias, a dismissible banner
    suggests linking; accepting sets game_system_id without altering the free-text
    column; declining persists across the session/page reloads.
  • No existing free-form campaign's behavior or display changes unless the GM explicitly
    accepts the nudge or picks a system manually.
  • Frontend tests cover: selector renders both modes, nudge appears/dismisses correctly,
    accepting the nudge issues the expected PATCH payload.
## Motivation/Context With the `GameSystem` registry and `campaigns.game_system_id` FK in place (#133), GMs need a way to actually pick a registry system from the UI — while free-text campaigns keep working exactly as before. Most existing campaigns already have a free-text value that names a known system (`"D&D 5e"`, `"PF2e"`, `"5e"`, …); a cheap, safe suggestion nudge covers the 90% case without any silent/automatic linking. ## Approach **Selector UI.** Add a registry dropdown to the campaign create form (`webapp/frontend/src/pages/Dashboard.jsx:107-111,138-142`, input at `:244-245`) and the campaign settings edit form (`webapp/frontend/src/pages/CampaignDetail.jsx:340,387,1427-1428`). The existing free-text input is retained and presented as a "Free text / Other" option — never removed, never gated behind the new selector. Campaign cards (`Dashboard.jsx:83-84`) and the campaign header (`CampaignDetail.jsx:965-966`) continue to render the resolved display string from the backend's resolution helper (#133), so no client-side resolution logic is needed — just wire the new `game_system_ref` field through if a distinct linked-system badge/icon is wanted. **Suggested-match nudge.** On campaign settings load (GM only): if `game_system_id IS NULL` and `lower(trim(game_system))` matches any registry `aliases` entry, show a one-line banner — *"This campaign's system looks like **Pathfinder 2e** — link it? (You can undo this at any time.)"* Accepting sets `game_system_id` via the existing campaign PATCH; the free-text column is left untouched. Declining is remembered (a `game_system_prompt_dismissed` flag or localStorage) so the banner doesn't nag every visit. This alias-matching check can run client-side against the registry list already fetched for the dropdown, or server-side as a computed field on the campaign response — implementer's call, but it must not write anything until the GM explicitly accepts. **Scope discipline.** This is UI wiring only — the registry, the FK, the resolution helper, and the `game_system_ref` schema field all come from #133 and are not re-implemented here. ## Dependencies - #133 (GameSystem registry + campaign linkage) — provides the registry list endpoint, `game_system_id` on create/update, and `game_system_ref` on read. ## Out of scope - Any backend changes (registry CRUD, resolution helper, validation) — all in #133. - LLM prompt integration — issue 3. - Stat schemas, stat-block editor, conversion wizard — issues 4/5/7. - Automatic/silent linking of existing free-text values — the nudge is always an explicit GM opt-in. ## Acceptance criteria - Campaign create form offers a registry dropdown (dnd5e, pf2e, generic) alongside a "Free text / Other" option that reveals the existing free-text input. - Campaign settings edit form offers the same selector and can switch a campaign between a linked system and free text at any time. - Campaign cards and campaign header continue to display the correct effective system name for both linked and free-form campaigns, sourced from the backend. - For a free-form campaign whose text matches a registry alias, a dismissible banner suggests linking; accepting sets `game_system_id` without altering the free-text column; declining persists across the session/page reloads. - No existing free-form campaign's behavior or display changes unless the GM explicitly accepts the nudge or picks a system manually. - Frontend tests cover: selector renders both modes, nudge appears/dismisses correctly, accepting the nudge issues the expected PATCH payload.
Author
Contributor

Done and verified — merged into the integration branch via PR #207.

Verification (Node 20 via node:20-alpine):

  • Frontend suite: 320 passed (31 files), including new coverage in Dashboard.test.jsx / CampaignDetail.test.jsx for both selector modes, the create/save payload shape, and nudge show/hide/dismiss/accept.
  • Production build succeeds (npm run build, Vite + PWA).

Delivered: a shared GameSystemSelect (registry dropdown + always-available free-text fallback, "Linked" badge), fetchGameSystems() client, selector wired into the create and settings forms (game_system_id threaded through), and a GM-only suggested-match nudge that links a campaign whose free text matches a registry short_name/name/alias — accept sends a partial {game_system_id} PATCH (free text untouched), dismiss persists to localStorage. Never writes until the GM accepts; the free-text input is never removed.

Closing; ships to main with the v3.9.0 release.

Done and verified — merged into the integration branch via PR #207. **Verification (Node 20 via `node:20-alpine`):** - Frontend suite: **320 passed** (31 files), including new coverage in `Dashboard.test.jsx` / `CampaignDetail.test.jsx` for both selector modes, the create/save payload shape, and nudge show/hide/dismiss/accept. - Production build succeeds (`npm run build`, Vite + PWA). **Delivered:** a shared `GameSystemSelect` (registry dropdown + always-available free-text fallback, "Linked" badge), `fetchGameSystems()` client, selector wired into the create and settings forms (`game_system_id` threaded through), and a GM-only suggested-match nudge that links a campaign whose free text matches a registry `short_name`/`name`/`alias` — accept sends a partial `{game_system_id}` PATCH (free text untouched), dismiss persists to `localStorage`. Never writes until the GM accepts; the free-text input is never removed. Closing; ships to `main` with the v3.9.0 release.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#135
No description provided.