Skip to content

Rewrite system from scratch for Foundry VTT v13/v14 - #3

Merged
snorith merged 21 commits into
developfrom
v13-rewrite
Aug 9, 2026
Merged

Rewrite system from scratch for Foundry VTT v13/v14#3
snorith merged 21 commits into
developfrom
v13-rewrite

Conversation

@snorith

@snorith snorith commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

Ground-up rebuild of the HELLAS system against current Foundry idioms (v13 stable, verified v14), using the legacy 0.7-era code strictly as a behavioral spec. The repo root becomes the system directory — zero build steps, plain JS ES modules + JSDoc, plain CSS with native nesting.

  • Data: TypeDataModel schemas for the character + all five item types with presence-guarded, delta-safe migrateData (node smoke tests in tools/test-migrations.mjs), plus a one-time GM-gated world migration runner that persists legacy-shape cleanup (sxithborn rename, blank-string→null).
  • Documents & rolls: HellasActor/HellasItem replace the legacy Proxy polymorphism; async dice; DialogV2 modifier prompt; omega-table outcomes; skill names/selectors normalized and persisted in _preCreate/_preUpdate instead of writes during data prep.
  • Sheets: ApplicationV2 + HandlebarsApplicationMixin throughout (one item sheet for all five types, four-tab actor sheet); every action isEditable-gated; core tooltips replace vendored tooltipster; no jQuery, no custom Handlebars helpers.
  • Compendia: NeDB journals deduped into per-document JSON sources (packs/_source/, 64 skills / 75 talents / 30 dynamisms), compiled to LevelDB via foundryvtt-cli (tools/build-packs.mjs); document ids preserved.
  • Best practices (phase 9a): locally bundled fonts (offline-safe, no Google CDN), setup media/background, hotReload flags, CHANGELOG, and a secret-guarded foundryvtt.com Package Release API step in the tag-push release workflow.
  • 20 legacy bugs fixed rather than ported (fate points never persisting, wrong tooltip content, missing/colliding i18n keys, and more — full list in z/SPEC.md).

Review & verification

  • Externally reviewed to convergence by three model families (codex/GPT-5.6, devin/SWE-1.7, droid/GLM-5.2) across five rounds plus separate plan/impl loops for phase 9a — every finding folded or rejected-with-reason in the ledgers (z/PLAN.md, z/phase-9a-impl-plan.md).
  • Static cross-reference audit (tools/audit.mjs) and migration smoke tests green; live manual testing has folded 8 findings so far (MT1–MT8), with the remaining checklist in z/CHECKLIST.md.

Before this merges to main / tags v0.4.0

  • Finish the manual checklist (z/CHECKLIST.md) — esp. §D rolls, §H legacy-world migration
  • Retire legacy src/ (keeping the src/system.json tombstone) and drop z/
  • Register the package on foundryvtt.com + add FOUNDRY_PACKAGE_TOKEN secret (release step skips cleanly until then)
  • Optional: author contact info in the manifest

🤖 Generated with Claude Code

snorith and others added 21 commits August 8, 2026 18:21
Adds CLAUDE.md with repository overview, build commands, architecture details, and coding conventions for AI assistant context. References it via AGENTS.md.
Ground-up rebuild against current Foundry idioms, using the legacy
0.7-era code as the behavioral spec (extracted to z/SPEC.md):

- Zero build steps: repo root is the system directory; plain JS ES
  modules + JSDoc, plain CSS with native nesting. Old gulp/rollup/
  TypeScript/Tailwind toolchain and committed release zips removed.
- Modern manifest: id/compatibility {13.347 -> 14}, documentTypes for
  character + 5 item types; template.json replaced by TypeDataModel
  schemas with presence-guarded migrateData (node smoke tests in
  tools/test-migrations.mjs).
- Documents & rolls: HellasActor/HellasItem replace the legacy Proxy
  polymorphism; async dice; DialogV2 modifier prompt; omega-table
  outcomes; skill names synced in _preCreate/_preUpdate instead of
  writes during prepareData.
- Sheets on ApplicationV2 + HandlebarsApplicationMixin: one item sheet
  for all five types, four-tab actor sheet; all actions (incl. rolls)
  gated on isEditable; core data-tooltip-html replaces vendored
  tooltipster; no jQuery, no custom Handlebars helpers.
- Compendia: NeDB journals deduped and converted to per-document JSON
  sources (packs/_source), compiled to LevelDB via foundryvtt-cli
  (tools/build-packs.mjs); document ids preserved.
- 19 legacy bugs fixed rather than ported (z/SPEC.md fix-don't-port
  list), incl. fate points never persisting, sxithborn data-key typo
  (with migration), and DB writes during render/prepare.
- Verification: tools/audit.mjs static cross-reference audit (actions,
  i18n incl. composed key families, form paths vs schemas, template
  paths, packs, manifest) is clean; manual checklist in z/CHECKLIST.md.
- Release: GitHub Actions on v* tag push; tombstone manifest kept at
  src/system.json for the legacy raw-GitHub update channel.

Legacy source stays under src/ as reference until the manual checklist
passes; z/ holds the rewrite plan, spec, and review ledger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Persist normalized skill selectors in _preCreate/_preUpdate and reset
  the name when skill is cleared (devin D1, major). Normalization stays
  out of migrateData: update deltas lack whole-document context.
- Gate #onItemEdit on isEditable like every other action (devin D2).
- Ledger in z/PLAN.md records all findings incl. rejected codex P1
  (scrollable [""] is the documented v13 idiom) and the verified
  NumberField integer-rounding behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Remove the <form> nested inside DialogV2 content (devin D6, major):
  DialogV2 supplies its own form; per the AppV2 conversion guide a nested
  form risks button.form missing the inputs entirely.
- Number()-parse dialog modifiers so exponent forms survive (codex C2).
- Clear dependent skill selectors when skill is emptied (devin D8).
- Fate label tooltip now uses the purpose-built fatepoints.title key —
  legacy formatted args into the placeholder-less description (devin
  D10, logged as legacy bug F20).
- Ledger updated; D7 recorded as accepted-by-design, D9 rejected with
  reason (core requires a name at Item.create).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both reviewers clean on the rev-2 folds (codex: no regression; devin:
all four folds sound, fresh sweep empty). Review loop terminal state
recorded in the z/PLAN.md ledger; only the execution-gated tombstone
check (D4) remains, carried to the manual checklist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Droid verified the full v13 API surface, data-prep ordering, roll
pipeline, templates, tools, and manifests: no blockers or majors.
Folds: STR-shortfall arithmetic uses one guarded minStr (droid's NaN
scenario disproven by execution — null coerces to 0 — folded for
clarity only); dead || true removed from audit.mjs; three unused
legacy i18n short-name keys carried wrong "Fate Points" values,
corrected. Adds the best-practices comparison report (z/BEST-PRACTICES.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…plan

- Ledger rev 5: droid round 2 clean — all three model families converged.
- z/FOUNDRY-BEST-PRACTICES.md: repo-agnostic v13/v14 best-practices
  reference (raw material behind the z/BEST-PRACTICES.md comparison).
- z/phase-9-plan.md: two-phase plan for the best-practices updates
  (9a pre-tag: migration runner, bundled fonts, manifest polish,
  Package Release API; 9b post-release: AE visibility, a11y,
  LOCALIZATION_PREFIXES, enum choices, form helpers).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the 2021-era .nvmrc (node v12); mise.toml now pins node 24 and
the release workflow builds on node 24 to match. All tooling verified
under 24.12.0: migration smoke tests, static audit, pack build and
round-trip. README dev setup mentions mise install.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two plan-review rounds (codex + devin): rev 1 folded recursive:false,
zero-failure stamping + activeGM gate, effects stripping, scope cut of
the unlinked-token pass (delta-override hazard), shell-script secret
guard (resolves a reviewer dispute over Actions env/if semantics),
scripted font fetching, jq -er guards; rev 2 confirmed with mechanical
i18n/checklist alignments. Ledger inside the plan doc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Setup-screen media and world-login background reuse the credited marble
photo; flags.hotReload uses the documented paths-only shape (the
extensions key does not exist in the official flag structure — verified
against the configuration article, resolving the plan's impl-time
check); changelog now points at a real CHANGELOG.md, shipped in the
release zip. Audit verifies media/background paths exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Roboto (variable woff2 covering weights 300-500 — Google serves one
identical file per weight, verified by checksum) and Caesar Dressing
now ship in fonts/ with their OFL license texts, fetched reproducibly
by tools/fetch-fonts.mjs (magic-byte + size verified). Offline/LAN
installs keep the full visual identity and clients no longer call a
third-party CDN. Synthetic bold remains, matching what the legacy
import produced. README credits the fonts; release zip ships fonts/;
audit verifies the files exist and no googleapis reference remains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
module/migrations/world.mjs: version-gated blanket re-save of world
actors and items so migrateData's in-memory normalizations (sxithborn
rename, blank-string -> null) finally reach the DB. Runs once per
NEEDS_MIGRATION_VERSION from the single active GM, stamps the
systemMigrationVersion world setting only after a zero-failure pass
(failures retry next launch; re-saving is idempotent). toObject(true)
+ {diff:false, recursive:false} keeps derived values out of the DB and
drops removed legacy keys; item/actor effects payloads stripped;
unlinked-token deltas and compendia deliberately out of scope (see
file header). SYSTEM_ID moved to module/config.mjs to keep the module
importable in bare node (7/7 smoke tests incl. the version gate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
POSTs each release to foundryvtt.com per the official Package Release
API (version-specific manifest asset URL, compatibility read from the
stamped system.json via jq -er guards, maximum:"" as in the official
example). Shell-level secret guard skips cleanly until the package is
registered and FOUNDRY_PACKAGE_TOKEN exists. Body construction
dry-verified locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restore the required hotReload extensions key (codex P2 — the core
design issue foundryvtt#9027 marks extensions required; the
configuration article's paths-only snippet is a simplification, and
dnd5e ships both keys). Harden the fetch-fonts url() regex (devin
nit). Devin's effects-preservation question is reasoned in the ledger
and pinned to a new manual-checklist row for execution confirmation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
en.json carried a dotted-key expansion collision (skill.specifier.title
both a string and a prefix of the stray ...title.label): v13 merges
translations via setProperty, which throws on strict-mode string
property assignment, aborting the ENTIRE language file — every UI
string rendered as its raw key. Stray key removed; the audit now fails
on any expansion collision. Setup-tile media switched to brushed gold
(white marble washed out at tile size).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tables now paint their own light background and ink instead of tinting
whatever core's theme put underneath (dark slabs in dark mode), and the
two-column layout uses minmax(0,1fr) so grid children can shrink
instead of pushing the right column past the window edge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
window-content becomes an overflow-hidden flex column; the active tab
section flexes to the remaining height and scrolls itself. PARTS'
scrollable [""] only preserves scroll positions across re-renders —
the actual overflow behavior is system CSS, which was missing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Core dark-theme headings are pale cream for dark grounds and vanished
on the marble; all headings now take the sheet ink. Default document
icons are white-line SVGs — a dark chip behind .profile-img keeps them
visible while raster portraits cover it entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The input+hint cells become flex rows: the hint text gets its own
flexible column next to the input and wraps within that column.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v13 icon-font checkbox states were illegible on the marble ground
(unchecked rendered as a solid dark block). Suppress core's glyph
pseudo-elements and draw the box + check with our own styles: empty
light box unchecked, dark check when checked, dimmed when disabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant