Skip to content

adapter: normalize Foundry Sets & pseudo-doc collections to JSON (Phase C)#75

Merged
keyxmakerx merged 2 commits into
mainfrom
claude/chronicle-sheet-sync-j2m9s4
Jun 28, 2026
Merged

adapter: normalize Foundry Sets & pseudo-doc collections to JSON (Phase C)#75
keyxmakerx merged 2 commits into
mainfrom
claude/chronicle-sheet-sync-j2m9s4

Conversation

@keyxmakerx

@keyxmakerx keyxmakerx commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Cites: Draw Steel sheet sync-expansion arc (Phase C) — Chronicle-Draw-Steel#25 manifest consumes this
Security implication: none — read-side field extraction; no new trust boundary, no token/URL handling
Consumer-verified: Chronicle-Draw-Steel/manifest.json hero preset projects Set-valued sub-fields (system.keywords, system.power.roll.characteristics, system.skills.value, actor.statuses) and the system.power.effects pseudo-document collection — all of which serialized to {} before this
Foundry compatibility: read-only actor traversal via the generic adapter; not version-gated (no v12/13/14-specific API)
Mockup: n/a

Note on scope: this branch also carries the "Sync Everything Now" dashboard implementation (sync-dashboard.mjs / .hbs) — the actual button handler that PR #74's description covered but which landed on the branch after #74's merge point, so it's surfaced here. It resyncs all journals + every linked character + maps under one confirm (per-item error isolation). The adapter change below is the headline.

What this changes

The generic adapter read live Foundry values straight into fields_data, but several Draw Steel fields are structures JSON.stringify renders as {}: Sets (ability keywords, skills, power-roll characteristics, actor.statuses) and a CollectionField of pseudo-documents (an ability's system.power.effects tier ladder). New normalizeFoundryValue() walks these — Set → array, Collection/Map → array of member toObject(), recursive (catches nested Sets like a damage tier's types), depth-guarded and defensive — and is wired into every scalar read and projected sub-field. A Set/array/object scalar on a string/json field is JSON-stringified so it arrives as parseable JSON (mirrors collection fields).

Why

Phase C of the Draw Steel sheet needs ability keywords, the tier ladder, skills, and conditions — every one of which is a Foundry Set or pseudo-doc collection. Without normalization they reach Chronicle as {}, so the manifest projections are inert. This is the adapter half; the manifest half is Chronicle-Draw-Steel#25.

Test plan

  • node --test tools/test-generic-adapter.mjs — passes (15 tests; +9 new for the normalizer: primitives pass-through, Set→array, nested Sets, Collection-via-.contents+toObject, projected Set sub-field, Set scalar → JSON string, actor.statuses)
  • node -c scripts/adapters/generic-adapter.mjs
  • node tools/check-package-descriptor.mjs — n/a (descriptor unchanged)
  • Manual in Foundry: re-sync a Draw Steel hero; confirm abilities_json keywords/tiers, skills_json, and conditions_json arrive as arrays (not {}) via Sync Dashboard ▸ Diagnostics; and "Sync Everything Now" refreshes a linked character's stale fields
  • CI passes

Tenet self-check

  • T-B1 security: read-only extraction; normalizeFoundryValue never executes data, only restructures it; no token/URL/auth changes
  • T-B2 plugin isolation: changes stay within this module's adapter + dashboard
  • T-B3 production UI: "Sync Everything Now" has a confirm + completion toast + per-item failure isolation; adapter change is non-UI
  • T-B4 dual-audience docs: WHY documented at the helper, the module-level collection-mapping comment, and the resync handler

Stop-and-flag

Flag any tenet violation by number during review.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LaDDiXB5GTVurEzJwYYopf

claude added 2 commits June 28, 2026 01:08
…+ maps

The Overview's quick-action button only ran a journal resync, so after a
manifest/path change a GM's character field data (and inventory/notes)
stayed stale — the button implied a full sync but wasn't one.

Replace it with 'Sync Everything Now' (new resync-everything action): one
confirm, then resync all journals, re-push every LINKED character via
ActorSync.repushActor (Foundry is source of truth for characters), and
resync all maps. Failures are isolated per item so one bad actor can't
abort the sweep. Unlinked actors stay on the Characters tab's 'Push All
Actors' (that one creates entities — deliberately not bundled into a
routine refresh).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LaDDiXB5GTVurEzJwYYopf
The generic adapter read live Foundry values straight into fields_data, but
several Draw Steel fields are structures JSON.stringify renders as {}:
  - Sets (ability keywords, skills, power-roll characteristics, actor.statuses)
  - a CollectionField of pseudo-documents (an ability's system.power.effects
    tier ladder)

Add normalizeFoundryValue(): Set→array, Collection/Map→array of member
toObject(), recursive (catches nested Sets, e.g. a damage tier's types), and
depth-guarded/defensive. Wire it into every scalar read and projected
sub-field; a Set/array/object scalar on a string/json field is JSON-stringified
so it arrives as parseable JSON (mirrors collection fields). +9 unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LaDDiXB5GTVurEzJwYYopf
@keyxmakerx keyxmakerx merged commit f76a293 into main Jun 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants