Fix Foundry v14.364 errors + V1→V2 DialogV2 migration#69
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:lineanchors into the Chronicle backend) lives inAUDIT-2026-06-21-foundry-errors.md.Reported issues
chronicle-package.json404 error popup — false alarm. The Chronicle backend deliberately strips the descriptor from served zips (foundry_vtt/handler.go, tested), butmodule.mjserrored on the resulting 404 on every correctly-served install. Now soft-skips a 404; still flags a genuinely malformed served descriptor._getLocalCalendarDateonly read the legacygame.Calendaria.getDate()(absent on Calendaria 1.x) → always null. Now reads modernCALENDARIA.api.getCurrentDateTime()first (legacy fallback retained). Also fixes the stuck "Out of Sync" badge and the no-op Push-date button.renderSidebarre-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
cleanHTMLmoved tofoundry.applications.ux.TextEditor.implementation. The resolver now probes.implementationfirst; re-enabled + pinned with v14 tests./sync/lookup404 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 409ConflictError, code checked for 400) and correctedAPI-CONTRACT.mddrift for/sync/lookup+/sync/mappings.V1 → V2 DialogV2 migration
New
scripts/_dialogs.mjsshim (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 (confirm→true/false,nullon dismiss withrejectClose:false; not overriding the built-in yes/no buttons). Migrated all 10 call sites and all 8render(true)→render({ force: true }).Testing
test-html-sanitizer,test-sync-mapping-conflict,test-sync-calendar-probe-state,test-dashboard-local-calendar-date,test-dialogs).DialogAPI orrender(true)reappears.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