[Game Systems] Stat-block editor + infobox projection (frontend) #140

Closed
opened 2026-07-15 22:01:57 +00:00 by claude-bot · 2 comments
Contributor

Motivation/Context

Issue 4 (#139) lands versioned stat schemas and a validated stats JSONB envelope on
lore_entries, but there's no way for a GM to actually view or edit that structured
data — the existing wiki UI only knows about the loose, string-only sidebar_fields
infobox. This issue builds the schema-rendered editor and projects the player-visible
subset of stats into the existing infobox so there's a single display surface rather
than two competing ones.

Approach

Typed editor. On npc/creature wiki articles in
webapp/frontend/src/pages/WikiArticle.jsx, render a form generated from the entry's
resolved (system, schema_version) definition (fetched via the schema endpoint added
in #139): grouped fields per the schema's groups/fields structure, with the right
input widget per type (int → number input with min/max, enum → select with
choices, list[str]/list[text] → repeatable rows, bool → checkbox, str/text
text input/textarea). Writes go through the validated stats write path from #139, so
client-side validation is a UX nicety, not the source of truth — the server is
authoritative.

Infobox projection. Player-visible fields from stats.values (per the envelope's
visibility, matching the existing sidebar_fields visibility model at
routers/campaigns.py:387-432,469) render as additional infobox rows alongside
sidebar_fields, reusing the existing sidebar UI and viewer-based filtering rather than
building a second display surface. This is the "display projection" role Option A
(typed sidebar_fields) plays in the report's design (§3.2) — no new visibility model,
just a read-time merge into the existing infobox rendering.

Read-only/edge states.

  • Version-mismatch: if an entry's stats.schema_version is behind the campaign's
    current schema version, show a read-only view with an "update to v{n}" nudge the GM
    confirms explicitly (schemas are immutable per version; this is a deliberate
    opt-in migration of the entry, not a silent one).
  • System unlinked: if the campaign has no linked system (or the entry's stats
    references a system that's no longer the campaign's current one — e.g. after
    unlinking per §4.4 of the report), render the stored stats read-only with a "not the
    campaign's current system" badge rather than hiding or erroring. The GM may delete
    the stale envelope per-entry from this state.

Dependencies

  • #139 (Versioned stat schemas + stats storage + validation service) — provides the
    schema definitions to render, the validated write endpoint, and the envelope's
    version/system self-description this UI reads to pick its display state.

Out of scope

  • The schema-targeted generate_statblock LLM action — issue 6.
  • The opt-in conversion wizard (convert_stats draft mode, backfilling stats from
    existing free-form data) — issue 7.
  • Any backend schema/validation work — all in #139.
  • Editing sidebar_fields rows themselves (existing functionality, untouched).

Acceptance criteria

  • On an npc/creature article whose campaign has a linked system with an active schema,
    a typed stat-block form renders, grouped per the schema, with per-field type-correct
    inputs.
  • Saving the form writes through the validated stats endpoint; server-side validation
    errors surface inline per field.
  • Player-visible stats fields appear as infobox rows alongside sidebar_fields,
    correctly filtered by the viewer's role (GM vs. player) using the existing visibility
    model.
  • An entry whose stats.schema_version is stale renders read-only with an explicit
    "update to v{n}" action; nothing auto-migrates.
  • An entry whose stats.system doesn't match the campaign's current linked system (or
    the campaign has no linked system) renders read-only with a clear "not current
    system" indicator, and offers a per-entry delete of the stale envelope.
  • Frontend tests cover: rendering the editor from a schema fixture, a successful save,
    a validation-error save, the infobox projection respecting visibility, and both
    read-only edge states.
## Motivation/Context Issue 4 (#139) lands versioned stat schemas and a validated `stats` JSONB envelope on `lore_entries`, but there's no way for a GM to actually view or edit that structured data — the existing wiki UI only knows about the loose, string-only `sidebar_fields` infobox. This issue builds the schema-rendered editor and projects the player-visible subset of `stats` into the existing infobox so there's a single display surface rather than two competing ones. ## Approach **Typed editor.** On npc/creature wiki articles in `webapp/frontend/src/pages/WikiArticle.jsx`, render a form generated from the entry's resolved `(system, schema_version)` definition (fetched via the schema endpoint added in #139): grouped fields per the schema's `groups`/`fields` structure, with the right input widget per type (`int` → number input with `min`/`max`, `enum` → select with `choices`, `list[str]`/`list[text]` → repeatable rows, `bool` → checkbox, `str`/`text` → text input/textarea). Writes go through the validated `stats` write path from #139, so client-side validation is a UX nicety, not the source of truth — the server is authoritative. **Infobox projection.** Player-visible fields from `stats.values` (per the envelope's `visibility`, matching the existing `sidebar_fields` visibility model at `routers/campaigns.py:387-432,469`) render as additional infobox rows alongside `sidebar_fields`, reusing the existing sidebar UI and viewer-based filtering rather than building a second display surface. This is the "display projection" role Option A (typed `sidebar_fields`) plays in the report's design (§3.2) — no new visibility model, just a read-time merge into the existing infobox rendering. **Read-only/edge states.** - **Version-mismatch:** if an entry's `stats.schema_version` is behind the campaign's current schema version, show a read-only view with an "update to v{n}" nudge the GM confirms explicitly (schemas are immutable per version; this is a deliberate opt-in migration of the entry, not a silent one). - **System unlinked:** if the campaign has no linked system (or the entry's `stats` references a system that's no longer the campaign's current one — e.g. after unlinking per §4.4 of the report), render the stored `stats` read-only with a "not the campaign's current system" badge rather than hiding or erroring. The GM may delete the stale envelope per-entry from this state. ## Dependencies - #139 (Versioned stat schemas + `stats` storage + validation service) — provides the schema definitions to render, the validated write endpoint, and the envelope's version/system self-description this UI reads to pick its display state. ## Out of scope - The schema-targeted `generate_statblock` LLM action — issue 6. - The opt-in conversion wizard (`convert_stats` draft mode, backfilling `stats` from existing free-form data) — issue 7. - Any backend schema/validation work — all in #139. - Editing `sidebar_fields` rows themselves (existing functionality, untouched). ## Acceptance criteria - On an npc/creature article whose campaign has a linked system with an active schema, a typed stat-block form renders, grouped per the schema, with per-field type-correct inputs. - Saving the form writes through the validated `stats` endpoint; server-side validation errors surface inline per field. - Player-visible `stats` fields appear as infobox rows alongside `sidebar_fields`, correctly filtered by the viewer's role (GM vs. player) using the existing visibility model. - An entry whose `stats.schema_version` is stale renders read-only with an explicit "update to v{n}" action; nothing auto-migrates. - An entry whose `stats.system` doesn't match the campaign's current linked system (or the campaign has no linked system) renders read-only with a clear "not current system" indicator, and offers a per-entry delete of the stale envelope. - Frontend tests cover: rendering the editor from a schema fixture, a successful save, a validation-error save, the infobox projection respecting visibility, and both read-only edge states.
Author
Contributor

Picking this up on feat/140-statblock-editor (PR to target the feat/v3.9-game-aware-systems integration branch), in parallel with #137.

Builds on #139: consumes GET /api/game-systems/{system_id}/schema and the stats envelope now on lore responses. Delivers a schema-driven typed editor on npc/creature wiki articles (grouped fields, per-type widgets), infobox projection of stats.values alongside sidebar_fields, and the read-only edge states (version-mismatch "update to v{n}", system-unlinked/mismatch badge + per-entry delete). Writes go through the validated stats PATCH path (server authoritative). #145 (conversion wizard) builds on this.

Picking this up on `feat/140-statblock-editor` (PR to target the `feat/v3.9-game-aware-systems` integration branch), in parallel with #137. Builds on #139: consumes `GET /api/game-systems/{system_id}/schema` and the `stats` envelope now on lore responses. Delivers a schema-driven typed editor on npc/creature wiki articles (grouped fields, per-type widgets), infobox projection of `stats.values` alongside `sidebar_fields`, and the read-only edge states (version-mismatch "update to v{n}", system-unlinked/mismatch badge + per-entry delete). Writes go through the validated `stats` PATCH path (server authoritative). #145 (conversion wizard) builds on this.
Author
Contributor

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

Verification (Node 20 via node:20-alpine): 331 frontend tests pass (32 files) + production build succeeds.

Delivered: schema-driven StatBlockEditor (grouped typed form per definition[entry_type], per-type widgets, visibility control, save via the validated stats PATCH), infobox projection of stats.values alongside sidebar_fields, and the read-only edge states (version-mismatch "Update to v{n}", system-mismatch/unlinked badge + per-entry delete). fetchGameSystemSchema added.

Review fix (found during verification): the number inputs' native max/min made out-of-range values :invalid, so native HTML form validation was silently blocking submit before the request reached the server — contradicting #139's server-authoritative contract. Added noValidate to the form (min/max remain as spinner hints; the server validates and its error surfaces). Committed as ecf5acf.

Closing; ships to main with the v3.9.0 release. #145 (conversion wizard) builds on this.

Done and verified — merged into the integration branch via PR #209. **Verification (Node 20 via `node:20-alpine`):** **331 frontend tests pass** (32 files) + production build succeeds. **Delivered:** schema-driven `StatBlockEditor` (grouped typed form per `definition[entry_type]`, per-type widgets, visibility control, save via the validated `stats` PATCH), infobox projection of `stats.values` alongside `sidebar_fields`, and the read-only edge states (version-mismatch "Update to v{n}", system-mismatch/unlinked badge + per-entry delete). `fetchGameSystemSchema` added. **Review fix (found during verification):** the number inputs' native `max`/`min` made out-of-range values `:invalid`, so native HTML form validation was silently **blocking submit before the request reached the server** — contradicting #139's server-authoritative contract. Added `noValidate` to the form (min/max remain as spinner hints; the server validates and its error surfaces). Committed as `ecf5acf`. Closing; ships to `main` with the v3.9.0 release. #145 (conversion wizard) builds on this.
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#140
No description provided.