Skip to content

Fix Foundry v14.364 errors + V1→V2 DialogV2 migration#69

Merged
keyxmakerx merged 6 commits into
mainfrom
claude/jolly-lamport-755efm
Jun 21, 2026
Merged

Fix Foundry v14.364 errors + V1→V2 DialogV2 migration#69
keyxmakerx merged 6 commits into
mainfrom
claude/jolly-lamport-755efm

Conversation

@keyxmakerx

Copy link
Copy Markdown
Owner

Summary

Diagnoses and fixes the errors reported from a live Foundry v14.364 / Chronicle Sync v0.1.19 session, plus completes the Foundry V1 → V2 (DialogV2) migration. Investigation was done by parallel read-only audit agents, then re-audited (security+correctness and regression+modularity) — both found no defects. Full test suite 499 → 535 green.

The complete verified root-cause record (with cross-repo file:line anchors into the Chronicle backend) lives in AUDIT-2026-06-21-foundry-errors.md.

Reported issues

  1. chronicle-package.json 404 error popup — false alarm. The Chronicle backend deliberately strips the descriptor from served zips (foundry_vtt/handler.go, tested), but module.mjs errored on the resulting 404 on every correctly-served install. Now soft-skips a 404; still flags a genuinely malformed served descriptor.
  2. Calendar "not found" — the dashboard Calendar tab showed the Chronicle calendar but "Foundry: Unable to read". _getLocalCalendarDate only read the legacy game.Calendaria.getDate() (absent on Calendaria 1.x) → always null. Now reads modern CALENDARIA.api.getCurrentDateTime() first (legacy fallback retained). Also fixes the stuck "Out of Sync" badge and the no-op Push-date button.
  3. "Debug button gone" — the buttons exist; the dashboard just becomes unreachable on v14 (the sidebar pill is wiped on re-render). Added a guarded renderSidebar re-attach and a version-independent escape hatch: game.modules.get('chronicle-sync').api.copyDebug() (DOM-independent; copies + returns the System Debug report).

Found during the audit

  1. (HIGH / security) HTML ingress sanitization was silently skipped on v14cleanHTML moved to foundry.applications.ux.TextEditor.implementation. The resolver now probes .implementation first; re-enabled + pinned with v14 tests.
  2. /sync/lookup 404 noise — the 11 "sync mapping not found" lines are benign lookup-before-create; now scrubbed from the dashboard error log. Also fixed a latent 409-vs-400 conflict-matching bug (backend returns 409 ConflictError, code checked for 400) and corrected API-CONTRACT.md drift for /sync/lookup + /sync/mappings.

V1 → V2 DialogV2 migration

New scripts/_dialogs.mjs shim (confirmDialog / promptDialog) — DialogV2-first with a V1 fallback for the v12 floor and on any DialogV2 error, so nothing breaks on v14. Verified against the official DialogV2 docs (confirmtrue/false, null on dismiss with rejectClose:false; not overriding the built-in yes/no buttons). Migrated all 10 call sites and all 8 render(true)render({ force: true }).

Testing

  • Suite: 535 passing (+36 new across test-html-sanitizer, test-sync-mapping-conflict, test-sync-calendar-probe-state, test-dashboard-local-calendar-date, test-dialogs).
  • New static regression pins ensure no V1 Dialog API or render(true) reappears.

⚠️ Needs a live-client smoke test

Foundry/Calendaria globals and dialog rendering can't be exercised headlessly. Before/after merge, on a v14 client please verify: the migrated dialogs (resync / pull / push confirms, the "create entity type" prompt, map pin & marker delete, calendar cleanup), and the dashboard Calendar tab (Foundry local date now renders; In/Out-of-Sync badge; Push-date). If the In/Out-of-Sync badge looks wrong, it'd be a month/day indexing nuance between Calendaria and Chronicle — easy follow-up.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 6 commits June 21, 2026 17:46
Diagnosis-only checkpoint (no code changes yet) capturing 5 parallel
audits + cross-repo verification for the reported v0.1.19 / Foundry
v14.364 errors:

- #1 chronicle-package.json 404: false alarm — Chronicle backend strips
  the descriptor from served zips by design (foundry_vtt/handler.go:461,
  tested), but module.mjs _runtimeValidateDescriptor errors on the 404.
- #4 (not reported, HIGH/security): HTML ingress sanitization silently
  skipped on v14 — _html-sanitizer.mjs never probes the relocated
  foundry.applications.ux.TextEditor.implementation.cleanHTML.
- #5 the 11 "sync mapping not found" 404s are benign lookup-before-create
  noise; plus a verified latent 409-vs-400 conflict-matching bug
  (backend NewConflict=409, comment claims 400) and API-CONTRACT.md drift.
- #3 debug button "gone": buttons exist in the Status tab; dashboard is
  unreachable on v14 (sidebar pill wiped on re-render).
- #2 calendar "not found": Chronicle likely has no calendar for this
  campaign (404 swallowed); needs observability + one datum to confirm.

Each item includes root cause with file:line and a concrete fix. DialogV2
migration deferred (v16-only, not runtime-testable here). Implementation
order at the end of the doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u6LRytWumBcDkbm1ZUenC
… conflict

Addresses the testable core from the v14.364 audit (no UI surfaces yet):

#4 (HIGH/security) _html-sanitizer.mjs: on Foundry v14 the TextEditor class
moved to foundry.applications.ux.TextEditor.implementation, so the resolver
found no cleanHTML and SILENTLY skipped ingress sanitization of Chronicle
HTML. Probe .implementation first (then namespace, then deprecated global);
add v14 test cases that pin the regressed shape.

#1 module.mjs: _runtimeValidateDescriptor errored on the chronicle-package.json
404 and showed a sticky "reinstall from a fresh release" notification on every
correctly-served install. Chronicle strips that descriptor from served zips by
design (foundry_vtt/handler.go), so a 404 is normal — soft-skip it; the check
still fires for a genuinely malformed descriptor that WAS shipped.

#5 sync-manager.mjs / api-client.mjs:
 - findMapping / findMappingByExternal now scrub the benign "no mapping yet"
   404 from the dashboard error log (it was the source of the alarming
   "Recent sync errors (11)" — every lookup-before-create logged a 404).
 - _isMappingConflict matched "Chronicle API error 400", but the backend
   returns 409 (apperror.NewConflict) wrapped as ConflictError — a real
   concurrent-create conflict would have propagated. Now matches 409/
   ConflictError (keeps 400 defensively), guarded by the "already exists"
   discriminator so unrelated 409s aren't absorbed.
 - dropLastErrorLogEntry accepts status as number OR array; the conflict
   scrub now matches [400, 409].

Redact operator hostname from the audit doc (test-no-instance-hostname).
New test: tools/test-sync-mapping-conflict.mjs (10). Full suite 499 -> 509, green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u6LRytWumBcDkbm1ZUenC
…c drift

#3 (debug button "gone"): the dashboard's Copy-Debug buttons were never
removed — the dashboard just becomes unreachable on Foundry v14 because the
sidebar status pill is wiped on sidebar re-render. Add:
 - module.api.copyDebug() — a version-independent, DOM-independent escape hatch
   (SyncDashboard.copyDebugReport) so an operator can always produce the System
   Debug snapshot from the console; returns the string and copies to clipboard.
 - a guarded renderSidebar hook that re-attaches the status indicator after the
   v13+ sidebar re-renders (purely additive; degrades to "no indicator" if the
   DOM shape differs, never throws).
 - _buildDebugText shared by the button and the escape hatch (modularity).

#2 (calendar "not found"): the probe collapsed every non-404 /calendar failure
to "unreachable" and a 401/403 read as the same ambiguous state. Distinguish
404/calendar_not_configured (absent — import one) from 401/403/invalid_token
(auth — re-check key / reinstall) with a new 'auth' banner state + localized
message (both banner copies). Underlying cause is environmental (Chronicle has
no calendar for this campaign, or token rotated); this makes it actionable.

#5c docs: correct API-CONTRACT.md drift for POST /sync/mappings (real body is
chronicle_type/chronicle_id/external_system/external_id/sync_direction) and
GET /sync/lookup (real query is chronicle_type+chronicle_id OR external_system+
external_id; response is the full SyncMapping; 404 is the normal not-mapped
signal). Matches the backend; the superseded API.md was already correct.

Full suite green (509).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u6LRytWumBcDkbm1ZUenC
…re-audit

Follow-up addressing the two re-audits (both found no correctness/security
bugs; these are the LOW/NIT polish items):

- Extract the calendar probe's status→state classification into a pure,
  testable helper scripts/_calendar-probe-state.mjs (matches the existing
  _html-sanitizer/_descriptor-validator helper pattern). It now anchors on the
  numeric status (err.status, or the authoritative "Chronicle API error
  <status>:" prefix) instead of a bare \b404\b body match, so a response body
  containing "404" (e.g. an entity named "Room 404") can't misclassify the
  banner. New tests: tools/test-sync-calendar-probe-state.mjs (12) — incl. the
  false-positive guard and a static pin for the NotAuthorized lang key +
  both 'auth' banner branches.
- sync-dashboard.mjs _buildDebugExport: the connection field read
  this.api.connected (never defined) → always "disconnected". Use this.api.state
  so the System Debug report (now reachable via api.copyDebug()) is accurate.
- Docs: record the deferred V1→V2 DialogV2 migration as a tracked CLAUDE.md TODO;
  update the audit doc status to "implemented + re-audited".

Full suite 509 -> 521, green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u6LRytWumBcDkbm1ZUenC
…ot found")

The dashboard Calendar tab showed the Chronicle calendar fine but "Foundry:
Unable to read", a permanent "Out of Sync" badge, and a no-op Push-date button.
Root cause: _getLocalCalendarDate read ONLY the legacy game.Calendaria.getDate(),
which doesn't exist on Calendaria 1.x — so it always returned null. (calendar-sync
already uses game.Calendaria only as a guarded legacy fallback; the dashboard
helper never got the modern path.)

Fix: read globalThis.CALENDARIA.api.getCurrentDateTime() first (the same surface
the Sync Calendar editor + calendar-sync use, and exactly the shape the user's
diagnostics showed), with getCurrentDate() then the legacy getDate() as
fallbacks. This restores the Foundry local date, the In-Sync comparison, and
the Foundry→Chronicle Push-date action.

This — not the Chronicle-side /calendar probe — was the actual cause of the
reported calendar "not found" (confirmed via user clarification: dashboard
8-tab window, Chronicle present, Foundry side not found).

Test: tools/test-dashboard-local-calendar-date.mjs (3). Audit doc updated.
Full suite 521 -> 524, green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u6LRytWumBcDkbm1ZUenC
…roofing)

Completes the deferred Foundry V1→V2 work (the "V1 Application framework is
deprecated" warnings in the console). Adds scripts/_dialogs.mjs — a DialogV2-
first shim (confirmDialog / promptDialog) that falls back to the V1 Dialog
global when DialogV2 is absent (v12 floor) or if a DialogV2 call throws, so
nothing breaks on v14 even if a V2 option shape were off.

Verified against the official Foundry DialogV2 API docs:
 - DialogV2.confirm resolves true (yes) / false (no), or null on dismiss with
   rejectClose:false → shim coerces to a strict boolean and does NOT override
   the yes/no buttons (overriding would replace their built-in true/false
   callbacks and break the boolean result — a bug caught during this review).
 - DialogV2 button callback is (event, button, dialog); promptDialog passes the
   dialog's root element so the caller's root.querySelector('form') still works.

Migrated all call sites: sync-dashboard.mjs (1 prompt + 6 confirm),
map-viewer.mjs (2 confirm), sync-calendar.mjs (1 confirm); and all 8
render(true) → render({ force: true }).

Tests: tools/test-dialogs.mjs (11) — behavioral (DialogV2 path, close→cancel
coercion, V1 fallback) + static pins that no V1 Dialog API or render(true)
remains outside the shim. Full suite 524 → 535, green. Docs/TODO updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u6LRytWumBcDkbm1ZUenC
@keyxmakerx keyxmakerx merged commit 18300f6 into main Jun 21, 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