feat(frontend): stat-block editor + infobox projection (#140) #209

Merged
claude-bot merged 2 commits from feat/140-statblock-editor into feat/v3.9-game-aware-systems 2026-07-18 17:36:32 +00:00
Contributor

Frontend for the v3.9.0 Game-Aware Systems pillar. Targets the integration branch. Builds on #139 (and #135). #145 (conversion wizard) depends on this.

Closes #140.

What

  • StatBlockEditor component — on npc/creature wiki articles whose campaign links a system with a schema, renders a grouped, typed form from GET /api/game-systems/{id}/schema: int→number, str→text, text→textarea, enum→select, bool→checkbox, list→repeatable rows. Prefilled from entry.stats.values, plus a visibility control (default gm). Saves via updateLoreEntry with {stats:{values,visibility}}; server 400s surface inline.
  • Infobox projection — when entry.stats is present, its values render as infobox rows alongside sidebar_fields, labelled from the schema. (The server already hides GM-only stat blocks from players.)
  • Read-only edge states — version mismatch (stats.schema_version < schema.version) → read-only + "Update to v{n}" (GM-confirmed migration, nothing auto-migrates); system mismatch / unlinked → "not the campaign's current system" badge + per-entry "Delete stat block" (stats: null).
  • fetchGameSystemSchema(systemId) added to api/gameSystems.js.

Verification (Node 20)

  • 331 frontend tests pass (32 files) + production build succeeds (node:20-alpine, npm ci && npm test && npm run build).

Review fix included

During verification the error-surfacing test surfaced a real design bug: the number inputs' native max/min made an out-of-range value :invalid, so native form validation silently blocked submit before the request reached the server — contradicting #139's "server is authoritative" contract. Fixed by adding noValidate to the form (min/max stay as spinner hints; the server validates and its error surfaces). Committed as ecf5acf on top of the implementation.

🤖 Generated with Claude Code

Frontend for the **v3.9.0 Game-Aware Systems** pillar. Targets the integration branch. Builds on #139 (and #135). **#145 (conversion wizard) depends on this.** Closes #140. ## What - **`StatBlockEditor` component** — on npc/creature wiki articles whose campaign links a system with a schema, renders a grouped, typed form from `GET /api/game-systems/{id}/schema`: int→number, str→text, text→textarea, enum→select, bool→checkbox, list→repeatable rows. Prefilled from `entry.stats.values`, plus a visibility control (default `gm`). Saves via `updateLoreEntry` with `{stats:{values,visibility}}`; server 400s surface inline. - **Infobox projection** — when `entry.stats` is present, its values render as infobox rows alongside `sidebar_fields`, labelled from the schema. (The server already hides GM-only stat blocks from players.) - **Read-only edge states** — version mismatch (`stats.schema_version < schema.version`) → read-only + "Update to v{n}" (GM-confirmed migration, nothing auto-migrates); system mismatch / unlinked → "not the campaign's current system" badge + per-entry "Delete stat block" (`stats: null`). - **`fetchGameSystemSchema(systemId)`** added to `api/gameSystems.js`. ## Verification (Node 20) - **331 frontend tests pass** (32 files) + production build succeeds (`node:20-alpine`, `npm ci && npm test && npm run build`). ## Review fix included During verification the error-surfacing test surfaced a real design bug: the number inputs' native `max`/`min` made an out-of-range value `:invalid`, so **native form validation silently blocked submit** before the request reached the server — contradicting #139's "server is authoritative" contract. Fixed by adding `noValidate` to the form (min/max stay as spinner hints; the server validates and its error surfaces). Committed as `ecf5acf` on top of the implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Consume the #139 stat-schema/stats contract in the wiki article view.

- gameSystems.js: add fetchGameSystemSchema(systemId) → GET
  /api/game-systems/{id}/schema (404 = no typed editor).
- New StatBlockEditor component: schema-driven grouped form for npc/creature
  entries (GM, edit mode) whose campaign links a system with a section for the
  entry type. Per-field-type inputs (int/str/text/enum/bool/list[str]/list[text]),
  prefilled from entry.stats.values, visibility control (default gm). Saves by
  PATCHing the lore entry {stats:{values,visibility}}; server 400s surface inline.
  Read-only edge states: version mismatch (Update-to-vN re-opens the editor
  against the current schema; no auto-migration) and system mismatch / unlinked
  campaign (badge + Delete stat block → PATCH stats:null).
- WikiArticle: fetch the linked system schema, render the editor in edit mode,
  and project entry.stats.values as infobox rows alongside sidebar_fields
  (schema field labels, else raw key). Server already hides GM-only stats.
- Tests: StatBlockEditor.test.jsx (form render, save PATCH, validation error,
  both read-only edge states, helpers) and WikiArticle.test.jsx additions
  (infobox projection, editor-in-page).

Frontend only; backend contract from #139 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Schema min/max render as native number-input hints, but native HTML form
validation blocked submit on an out-of-range value before it reached the
server — contradicting the server-authoritative design (#139) and preventing
a server error (or a value the server would accept) from ever surfacing. Add
`noValidate` to the form; add a call assertion to the error-surfacing test.

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