Foundry v13 deprecated renderChatMessage in favor of
renderChatMessageHTML (which passes an HTMLElement, not a jQuery
wrapper). Subscribing to the deprecated hook re-emits Foundry's
compatibility warning on every chat render in worlds still running
v13.351 (the foundry-hooks-lib module's tests run against such a
world).
v0.3.0 already narrowed scope to Foundry v14 only (HOOK_CONTRACT.md
section 9), but the registered hook set still included
renderChatMessage as a legacy fallback. There is no Foundry v14
hook by that name, so the entry was dead weight — and worse, any
v13.351 world running the v14-only library would still see the
deprecation warning every chat render.
Changes:
- registered-hooks.js: replace renderChatMessage entry with
renderChatMessageHTML. Update arg shape (HTML passes HTMLElement,
not jQuery). Add comment explaining the deprecation.
- README.md / HOOK_CONTRACT.md section 6: list renderChatMessageHTML
instead of renderChatMessage.
- tests/verify-hooks-lib.mjs: update stub arg shape from
[{id}, {}, {}] to [{id}, {}] (v14 signature).
Verification:
- node tests/verify-hooks-lib.mjs: 546/546 (unchanged)
- node tests/perf.mjs: 6/6, median 0.0003ms/fire (well under
the 0.1ms budget in HOOK_CONTRACT.md section 7)
- node --check on all scripts + tests: clean
Push: Gitea only.
Note: battle-focus's own main.js line 144 still has a
Hooks.on('renderChatMessage', ...) listener for its 'Open in
Journal' button wiring. That listener fires the deprecation warning
on the user's console. Fixing it is a battle-focus change, out of
scope for this turn (hooks-lib only).
The v0.4.0 zip was built but blocked by .gitignore (only
0.2.0 and 0.3.0 zips were explicitly re-included). Add the
0.4.0 inclusion so the release artifact is committed.
User directive: 'update the plan. v14 only'. Implementation:
**scope change (Foundry v14 only):**
- registered-hooks.js: renderChatInput entry drops the v13
renderChatLog name. Single subscription to the v14 name.
- anti-corruption.js: combatRound arg-normalization no longer
detects the v13 round-num position. v14's updateOptions.round
is the only path. Removed the v13 comments from the other
arg shapes (combatEnd, combatTurn).
- module.json: compatibility.minimum is now 14 (was 13).
verified stays 14.
- package.json: version 0.3.0 -> 0.4.0 (semver-breaking: dropping
v13 support is a breaking change for v13 consumers).
- package.json description: 'Foundry VTT v14-only module' prefix.
**test plan:**
- tests/PLAN.md: v14-only scope documented at the top of the file
and in Section E. Status line bumps 554 to 546 assertions
(v13-only assertions dropped). Test files table re-scoped to
v0.4.0.
- tests/verify-hooks-lib.mjs: dropped the v13-only assertions
(E.2 'renderChatLog in installed', E.3's 'both v13 and v14
produce' check, E.4's v13 round shape). Kept the v14-only
assertions + added an inverse assertion: 'renderChatLog is NOT
in installed raw hooks' to lock the v14-only scope.
- tests/verify-hooks-lib.mjs: stub table at line ~520 drops
renderChatLog (dead in production now).
**doc updates:**
- README.md: new 'v0.4.0 — Foundry v14 only' section explaining
the change + migration note for v13 consumers.
- docs/HOOK_CONTRACT.md: v0.3.0 header. §9 marks the v13 column
as historical. §10 example uses the v14 shape only.
**artifact:**
- foundry-hooks-lib-0.4.0.zip built (82KB, 46 entries, inner
version 0.4.0, inner compatibility.minimum 14).
**verified:**
- npm test: 546/546 assertions passed
- npm run test:foundry: 30/30 assertions passed
- npm run test:perf: 6/6 assertions passed (median 0.0003ms/fire)
- battle-focus E2E (consumer): 125/125 still green
- its-achievable smoke (consumer): 75/75 still green
**consumer follow-up (separate commits in their own repos):**
- battle-focus/module.json: relationships.requires[0].version
bumped to ^0.4.0
- its-achievable/module.json: relationships.requires[0].minimum
bumped to 0.4.0
The v0.3.0 zip in commit d038eb8 (the rename) was built before
tests/verify-hooks-lib-foundry.mjs existed. This rebuild includes
it (45 entries vs 36, +1 file in tests/).
Inner version 0.3.0, id foundry-hooks-lib. All other contents
unchanged.
Implements tests/PLAN.md § Playwright run (Section F's release
gate). 30 assertions in tests/verify-hooks-lib-foundry.mjs,
runs in ~10s against a live Foundry v14 instance.
What the Playwright test verifies that the no-Foundry smoke
test CAN'T:
- The library's init hook runs in Foundry's real lifecycle
(not a stubbed manual init call).
- mod.api is set on game.modules.get('foundry-hooks-lib') with
the documented surface.
- install() at init calls Hooks.on for every raw hook in the
registered set.
- A real Foundry-fired hook (combatStart, combatRound) delivers
an envelope to a consumer that subscribed AFTER init.
- A synthetic Hooks.callAll fire delivers envelopes to
subscribers (sync-mode hooks via direct dispatch,
async-mode hooks via microtask).
- subscribeAll receives envelopes from every registered hook.
- subscribe with an unknown hook name throws TypeError.
- A throwing consumer does NOT break the dispatch chain; the
second subscriber still fires; the error is logged via
console.error with the [foundry-hooks-lib] prefix.
- subscribe + unsubscribe correctly gate delivery.
**Bumps package.json to 0.3.0** (was 0.2.0 — version lagged
behind module.json's 0.3.0 from the rename commit).
**Adds test:foundry and test:all npm scripts.**
**Marks tests/PLAN.md status as Implemented** (was Proposed).
The Definition of done gate (npm run test:foundry exits 0 in
<30s) is now met.
**Adds playwright-core as a devDependency** (1 package, ~no
runtime impact since this repo's module doesn't depend on it at
runtime — it's a test-only dep).
**Final tallies:**
- npm test: 554/554 in ~0.4s (no-Foundry smoke)
- npm run test:foundry: 30/30 in ~10s (Playwright)
- npm run test:perf: 6/6 in ~5s (median 0.0003ms/fire)
- npm run test:all: all of the above
User callout: 'Hax' is Kaysser's nickname. The module id should
not use it. Rename the Foundry module id from 'hax-hooks-lib' to
'foundry-hooks-lib'. Gitea repo name stays as 'hooks-lib' (kept
for the user-facing URL); the Gitea manifest URL is unchanged.
**Scope of rename:**
- module.json: id, title, version (0.2.0 -> 0.3.0), download URL
- package.json: name
- README.md, HOOK_CONTRACT.md, LICENSE: branding text
- All 6 production JS files: MODULE_ID constant + comments
- 4 active test files: console.log strings + test descriptions
- Rename of release zips in git: hooks-lib-X.Y.Z.zip ->
foundry-hooks-lib-X.Y.Z.zip (preserves the v0.1.0 and v0.2.0
zips as historical artifacts; the v0.3.0 zip is the new
release artifact)
- .gitignore: glob + un-ignore lines updated to match
**Out of scope (deliberate):**
- Gitea repo name 'kaykayyali/hooks-lib' stays. Per the user's
direction, only the module id is renamed; the Gitea URL path
is preserved for the existing 'url', 'manifest', 'download'
fields.
- scripts/_archive/v0.1.0/*: historical v0.1.0 code is left
as-is. Those files tested 'hax-hooks-lib v0.1.0'; rewriting
the history would be misleading.
- tests/_archive_v0.1.0_*.mjs: same reason, left untouched.
- .hermes/plans/* session-historian plans that reference
'Hax's Tools split': session artifact, not a release asset.
**Verification:** 554/554 smoke assertions pass, 6/6 perf
assertions pass, median 0.0004ms/fire (well under 0.1ms
budget). No logic change; rename is string-only.
**Consumer action required:** battle-focus and its-achievable
both declare 'relationships.requires' pointing to
'hax-hooks-lib'. The next commits on those repos will update
their relationships to 'foundry-hooks-lib' + bump their
versions. Foundry instances with v0.2.0 of the old id
installed will need to be reinstalled as v0.3.0 of the new
id.
Adds tests/PLAN.md — the test plan that implements HOOK_CONTRACT.md
section 14. Defines what we test (sections A-G: envelope shape,
subscriber API, error containment, lifecycle, anti-corruption,
performance budget, system adapter loading), what we don't test
(consumer domain logic, Foundry's correctness, system-specific
derived events, older Foundry versions), and the definition of
done (100% coverage of 'what we test', smoke <2s, Foundry <30s,
perf budget <0.1ms median per fire).
The v0.1.0 verify-hooks-lib.mjs (20 assertions) covers the wrong
shape — it's slated for archive when v0.2.0 test files ship.
Listed in 'future turns' as a git mv into tests/archive/v0.1.0/.
Push: Gitea only.
Adds docs/HOOK_CONTRACT.md — the spec for hooks-lib v0.2.0. The
v0.1.0 implementation (18 hand-written event handlers, the
scripts/systems/ adapter pair, the encounter.js stub) is the wrong
shape. v0.2.0 is a generic Foundry hook facade:
- Subscribes to every Foundry hook in §6 (lifecycle, document CRUD
generic, combat, chat/canvas/UI, dnd5e v2 roll hooks)
- Emits a uniform {ts, hook, args} envelope with no domain
interpretation
- Provides subscribe / subscribeMany / subscribeAll / unsubscribeAll
primitives
- Registers system adapters (separate repos) via a manifest with
semver range matching for system + Foundry versions
- Dispatches sync for pre-* / combat* / applyActiveEffect /
get*Context hooks; async (microtask) for everything else
- Absorbs Foundry rename churn (renderChatInput vs renderChatLog)
and arity shifts (combatRound signature) so consumers stay stable
No code changed. The contract is the gate for v0.2.0 implementation.
Push: Gitea only (this turn). No GitHub mirror.