Skip to content

Commit 3c17e18

Browse files
authored
Merge pull request #47 from keyxmakerx/claude/setup-foundry-ai-env-Znu2E
FM-SYNCCAL-ROOT-FIX: wrap sync-calendar.hbs body in single root (unblock app render)
2 parents 95109b1 + ee15520 commit 3c17e18

4 files changed

Lines changed: 493 additions & 0 deletions

File tree

.ai.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ All sync modules use a `_syncing` boolean flag to prevent infinite loops:
6565
| `chronicle-package.json` | Repo-root serving descriptor (schema v1). Chronicle's `foundry_vtt` sub-plugin reads this from the extracted release zip via `PostInstallHook` to learn how to serve this package: which fields to rewrite, what URL templates to emit, whether per-campaign signed tokens are required. Source of truth for the install/update flow's URL shape. |
6666
| `tools/check-package-descriptor.mjs` | CI script. Validates `chronicle-package.json` against `module.json` (id match, file paths resolve, endpoint templates carry required placeholders). Run by `.github/workflows/check-descriptor.yml` on every push + PR. |
6767
| `tools/test-lang-expand.mjs` | CI guard for `lang/en.json` (FM-LANG-COLLISION-FIX). Simulates Foundry's `expandObject` and fails loud on any string-vs-object collision or duplicate-after-expansion path that would cause Foundry to reject the whole lang file at boot. See F-LANG-1 footgun below. |
68+
| `tools/test-template-roots.mjs` | CI guard for ApplicationV2 PARTS templates (FM-SYNCCAL-ROOT-FIX). Discovers every `PARTS.*.template` binding in `scripts/*.mjs`, statically analyzes each referenced `.hbs` (recursing through every `{{#if/unless/each/with}}` branch), and asserts max-root-count == 1 — Foundry's `_parsePartHTML` rejects multi-root parts at render time. See F-PR3-4 footgun below. |
6869
| `scripts/calendar-sync.mjs` | Adapter pattern for Calendaria and SimpleCalendar. 0-indexed↔1-indexed conversion |
6970
| `scripts/shop-widget.mjs` | Shop window (Application v1). Context menu, drag-to-character-sheet, real-time updates |
7071
| `scripts/actor-sync.mjs` | Actor ↔ character entity bidirectional sync. System adapter loading, hook registration |
@@ -478,6 +479,31 @@ operator sees no error. The pure validator in
478479
save path blocks on `Errors.RootMustBeGroup`,
479480
`Errors.EmptyGroup`, and `Errors.NotAnObject`.
480481

482+
### F-PR3-4: ApplicationV2 PARTS templates must render exactly one root HTML element
483+
484+
Foundry's `_parsePartHTML` (foundry.mjs:32135) rejects any `static PARTS`
485+
template whose rendered output contains more than one root HTML element
486+
— or zero — with `Error: Template part 'X' must render a single HTML
487+
element.` Foundry catches this only at app render time; the JSON parses,
488+
the Handlebars compiles, the app object instantiates fine. The crash
489+
fires on `app.render()`.
490+
491+
Worst part: the failure mode depends on which branch of `{{#if}}/{{else}}`
492+
fires. PR 3's `sync-calendar.hbs` had a `degraded` branch that emitted
493+
one `<section>` (clean) and an `else` branch that emitted three siblings
494+
(`<header>`, `<main>`, `<footer>`) — the app worked in degraded mode
495+
during PR 3 dev (no Calendaria connected, so the degraded branch fired)
496+
and crashed the moment the operator opened it against a real Calendaria.
497+
498+
Same footgun class as F-LANG-1 (silent Foundry-runtime contract that
499+
Node tests can't catch by accident). Fix: wrap the entire template body
500+
in a single root element (e.g. `<div class="sync-calendar-root">`) so
501+
EVERY branch — degraded, year-view, month-view, with or without import
502+
banner — emits exactly one root. **CI guard at
503+
`tools/test-template-roots.mjs`** statically analyzes every PARTS-
504+
registered template across the repo, recurses through every
505+
`{{#if/unless/each/with}}` branch, and asserts max-root-count == 1.
506+
481507
### F-LANG-1: `lang/en.json` cannot mix dotted-flat keys with sibling literal-key strings
482508

483509
Foundry's `Localization._loadTranslationFile` runs the parsed JSON

styles/sync-calendar-pr3.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@
33
* Moon strip + recurrence builder + empty-state Calendaria import
44
* ==================================================================== */
55

6+
/* --- FM-SYNCCAL-ROOT-FIX (#26) single-root wrapper -------------------
7+
* The PARTS body is wrapped in `<div class="sync-calendar-root">` so
8+
* Foundry's `_parsePartHTML` accepts both the degraded branch and the
9+
* three-sibling non-degraded branch. Layout migrates from the prior
10+
* `.sync-calendar .sync-calendar-content` container (which Foundry no
11+
* longer feeds the children directly) onto this wrapper. See F-PR3-4
12+
* in `.ai.md` and `tools/test-template-roots.mjs`. */
13+
14+
.sync-calendar .sync-calendar-root {
15+
display: flex;
16+
flex-direction: column;
17+
height: 100%;
18+
gap: 8px;
19+
}
20+
621
/* --- Moon-phase strip in month view --------------------------------- */
722

823
.sync-calendar .moon-strip-block {

templates/sync-calendar.hbs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,17 @@
1313
binds to the actions map in DEFAULT_OPTIONS. Form-field inputs use
1414
data-form-field="..." attributes that the application's #wireFormInputs
1515
hook (in _onRender) listens to.
16+
17+
Single-root wrapper (`<div class="sync-calendar-root">`) is required:
18+
ApplicationV2's `_parsePartHTML` rejects any PARTS template that emits
19+
more than one root HTML element across any branch of any conditional
20+
("Template part 'body' must render a single HTML element"). The
21+
`else` branch below has three siblings (header + main + footer) and
22+
must be wrapped. See F-PR3-4 in `.ai.md` and the CI guard at
23+
`tools/test-template-roots.mjs`.
1624
--}}
1725

26+
<div class="sync-calendar-root">
1827
{{#if degraded}}
1928
<section class="sync-calendar-degraded">
2029
<i class="fa-solid fa-triangle-exclamation"></i>
@@ -580,3 +589,4 @@
580589
<span class="footer-meta">schema v{{schemaVersion}}</span>
581590
</footer>
582591
{{/if}}
592+
</div>

0 commit comments

Comments
 (0)