v0.105.0 — New character sheet: foundations
Shipped 2026-09-21 — built on version/0.105.0, twelve commits; the
merge hash and tag are in the changelog. The fifteenth of the
releases the near-term roadmap sequences from the
August 2026 heuristic evaluation; the roadmap page is
foundations, part one of
the new character sheet, whose page
carries the design. Module API 1.39.0; no module release. The developer
page is Character Sheet Renderer.
System-Platform Separation round 2. The Character Sheet API has the platform render a sheet from a description the module supplies — which is why every sheet looks like the platform rather than like its system. This release builds the seam that inverts it: a module may ship the sheet component itself, built from a kit the platform exports, writing only through the platform's one write path. Nothing user-visible changes except the dead sheet code going, the Sheet context existing (the quick view until v0.106.0), and the ability-score fix. Decisions NS-D1, D2, D5, D6, D7, D9 and D10 bind.
The seam
- Module API 1.39.0 (NS.3) —
CharacterSheetRendererExtensionthroughregisterCharacterSheetRendererfrom the client entry (pages,supports.print),SystemModule.hasSheetRendereron the server,SheetRendererProps,SheetApi,SheetEditIntent(the CS-8 four plusset-field,set-runtime,set-metadataunder the module's namespace — NS-D7 — andpatch),RecentChange. Additive; a module that registers none gets the platform's sheet. - The host (NS.3) —
SheetHost: the frame (the gated Build toggle, History, the⋯menu's zoom and Export PDF, the sheet as its own scroller), the kit handed in, the module's CS-9 pickers mounted onapi.openPicker, the fallback to the quick view. The Sheet context ('sheet'), the route/characters/:id/sheet, and three entries: the character page's⋯→ Sheet view, the Character context's header, the token menu's View Character Sheet (a new tab since v0.33.0; now the panel beside the map). SheetApi(NS.4, NS.5) —edit(the module'svalidateEditandapplyEditfirst, the platform's fields after),roll(the chat seam),runAction(the impersonated-token executor),sendMessage,openPicker,registerField,campaign.configuration(v0.102.0),treasury.holdings/.carrying(v0.103.0),can,notify,damage.apply/.prompt(v0.78.0's helpers).- The recent-changes feed (NS.4, NS-D6) — every runtime write the platform makes, recorded with its source and handed to the renderer for a window: approved and auto-approved damage, healing, conditions, rests, resource pools, the API's own writes, and a diff of consecutive records for what arrived from elsewhere. The module decides how to show it.
- The field registry (NS.4) — every field a stable id and a reading
order; the host moves Tab by order and can focus a field on request; the
sheetFocusStoreis the door the v0.108.0 walkthrough will knock on.
The kit
@purple-cat/sheet-kit(NS.2) — the layout family (Sheetwith tabs as a table of contents and the print rules,Pageas a CSS grid keyed from aPageLayoutand reflowing below its width,Region,Field,Checkbox,Lines,Portrait) and the editing family (EditableFieldwith saving / saved / failed,FieldEditor— opens on focus, closes on blur or Escape, traps focus —NumericStepper,Rollable,ActionButton,ValidationMark,RestButton,ResourcePips);skinandzBandover the platform's tokens; the field-registry contract; the geometry from alayout.json(pageLayoutFromFields,regionRect) and its guard (checkGeometry,measureRegions). Under jsdom, with the export guard and a coverage ratchet.- Print (NS.6) — the same component in print mode through the browser's print path; the kit's rules snap pages and hide controls; the host's stylesheet hides the app around the sheet.
- The fixture (NS.8) —
@purple-cat/sheet-kit/fixture's two-page, ten-fieldFixtureSheetfor thefixture-system, every feature once, run end to end against a recording API; the registry round-trip pinned in the web app; the sheet route at Playwright's auth boundary.
The house-keeping
- NS.0 — the ability-score rule (NS-D10). The
+/−already moved the declared step component; the typed total and the advanced rows still moved base. Now a typed total lands in the step component (floored at zero), base is read-only in the breakdown once a module names what+moves, and the row says Set at creation — Regenerate to change. - NS.1 — the inventory of system-specific code still in
apps/web, each site with its destination, on the developer page. - NS.5 — the conventional keys. The platform reads
startingLevel,abilityScoreMethods,rolledRerollsandpointBuyBudgetfrom a campaign's configuration without naming a system: a new character begins at the campaign's starting level; the generation modal offers the methods the table allows. - NS.7 — CS-15.12 at last.
PlayModeViewand its 26 tests, the classicCharacterDetail,TokenSheet/,CombatStatsand the siblings only they used — 7,824 lines nothing imported. Twenty-sixreadFileSyncguards judged (not fifteen: two inserver-coreread the web source), each re-anchored to the live surface or deleted with a note;combatStats.tsreduced topropKey; thesystemLiteralsbaseline 13 → 11 files. - NS.9 — guards. The seam's surfaces name no system; zod for the
registration,
RecentChangeandSheetEditIntent, validated at the registry and the write path, with three new parity pairs.
Traps met on the way
sharedis compiled without the DOM lib by the server: aHTMLElementin a shared type brokeserver-core's build. The seam typesobject.- A guard in
packages/server-corereadapps/websource (the v0.88.0 trap, twice more). - Mantine's Popover marks its dropdown inaccessible under jsdom until a
transition that never runs: query with
hidden: true. - A
FieldEditor's stepper and a runtimeNumericStepperboth say Increase: scope the query to the editor. - The hidden Chrome tab renders neither Mantine popovers nor the desktop sidebar: the route was verified live, the panel by contract.