v0.3.0: rename module id hax-hooks-lib -> foundry-hooks-lib

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.
This commit is contained in:
2026-06-20 16:53:37 -04:00
parent 7f0d1bbff1
commit d038eb8c67
19 changed files with 55 additions and 54 deletions

10
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# Foundry install mirror
# /Data/modules/hax-hooks-lib/ is generated by scripts/copy-to-foundry.mjs (if/when added).
# /Data/modules/foundry-hooks-lib/ is generated by scripts/copy-to-foundry.mjs (if/when added).
Data/
# Dev environment
@@ -25,9 +25,9 @@ scripts/session.js
scripts/session-prompts.js
# Build artifacts (created by Python zip recipe or future build-zip.mjs).
# Keep this loose: hooks-lib-X.Y.Z.zip is the named release artifact;
# Keep this loose: foundry-hooks-lib-X.Y.Z.zip is the named release artifact;
# versioned so future rebuilds don't accidentally overwrite a released
# version. Add a new file rather than deleting old ones when bumping.
hooks-lib-*.zip
!hooks-lib-0.1.0.zip
!hooks-lib-0.2.0.zip
foundry-hooks-lib-*.zip
!foundry-hooks-lib-0.2.0.zip
!foundry-hooks-lib-0.3.0.zip

View File

@@ -1,5 +1,6 @@
UNLICENSED
This module is part of the Hax's Tools project and is not licensed for
This module is part of the Foundry module split (battle-focus +
its-achievable + this lib) and is not licensed for redistribution.
redistribution. Source is available at
https://git.homelab.local/kaykayyali/hooks-lib for collaborators.

View File

@@ -1,17 +1,17 @@
# Hax's Tools — Hooks Lib (`hax-hooks-lib`)
# Foundry Hooks Lib (`foundry-hooks-lib`)
Foundry VTT module (id: `hax-hooks-lib`) that turns Foundry's hook soup
Foundry VTT module (id: `foundry-hooks-lib`) that turns Foundry's hook soup
(dnd5e, combat, token updates, canvas/UI) into a clean, normalized event
stream. **Library-only** — no UI, no settings, no chat output. Designed to
be consumed by any module that wants Foundry events in a stable shape.
Part of the **Hax's Tools** umbrella. Consumers today: `battle-focus`
(encounter + journal + summary) and `its-achievable` (achievements,
rewards, wall, HUD). System-specific knowledge (dnd5e rolls, PF2e, etc.)
lives in separate adapter repos that declare Foundry + system version
ranges they support.
Part of the **Foundry module split** (battle-focus + its-achievable + this lib).
Consumers today: `battle-focus` (encounter + journal + summary) and
`its-achievable` (achievements, rewards, wall, HUD). System-specific knowledge
(dnd5e rolls, PF2e, etc.) lives in separate adapter repos that declare
Foundry + system version ranges they support.
## v0.2.0 — generic Foundry hook facade
## v0.3.0 — module id renamed (hax-hooks-lib → foundry-hooks-lib)
v0.2.0 is a complete rewrite. v0.1.0 shipped as a curated-event catalog
(a list of hand-written handlers for 18 specific Foundry events). v0.2.0
@@ -54,11 +54,11 @@ Consumers that want `{kind, actorId, delta}` build that themselves from
`args`. This is intentional: the library is the boundary that absorbs
Foundry version churn.
## Public API (on `game.modules.get("hax-hooks-lib").api`)
## Public API (on `game.modules.get("foundry-hooks-lib").api`)
```js
import { subscribe, subscribeMany, subscribeAll } from
game.modules.get("hax-hooks-lib").api;
game.modules.get("foundry-hooks-lib").api;
// Single hook:
const unsub = subscribe("updateActor", (envelope) => {
@@ -85,8 +85,8 @@ A system adapter is a separate Foundry module. At its `init`, it calls:
```js
hooksLib.api.registerSystemAdapter({
id: "hax-hooks-dnd5e",
moduleId: "hax-hooks-dnd5e",
id: "foundry-hooks-dnd5e",
moduleId: "foundry-hooks-dnd5e",
system: { id: "dnd5e", versions: ">=5.2.0 <5.3.0" },
foundryVersions: ">=13 <15",
factory: () => [ /* derived-event registrations */ ],
@@ -111,7 +111,7 @@ Full list: `scripts/internal/registered-hooks.js`.
## Error containment
If a consumer callback throws, the library catches it, logs via
`console.error` with the `[hax-hooks-lib]` prefix and the hook name,
`console.error` with the `[foundry-hooks-lib]` prefix and the hook name,
and continues dispatching to subsequent callbacks. Errors never
propagate to Foundry's hook chain.

View File

@@ -116,7 +116,7 @@ callbacks registered through any of the three primitives above.
If a consumer callback throws, the library:
1. Catches the error.
2. Logs it via `console.error` with the prefix `[hax-hooks-lib]` and
2. Logs it via `console.error` with the prefix `[foundry-hooks-lib]` and
the hook name.
3. Continues dispatching to subsequent callbacks (registration order).
@@ -437,7 +437,7 @@ export function listActiveAdapters() { ... }
```
`mod.api` mirrors these so consumers can also access via
`game.modules.get("hax-hooks-lib").api.subscribe(...)`.
`game.modules.get("foundry-hooks-lib").api.subscribe(...)`.
---

BIN
foundry-hooks-lib-0.3.0.zip Normal file

Binary file not shown.

View File

@@ -1,8 +1,8 @@
{
"id": "hax-hooks-lib",
"title": "Hax's Tools — Hooks Lib",
"id": "foundry-hooks-lib",
"title": "Foundry Hooks Lib",
"description": "Foundry VTT module: generic Foundry hook facade. Subscribes to every relevant Foundry hook (combat, document CRUD, canvas/UI, dnd5e v2 rolls), emits a uniform {ts, hook, args} envelope. No domain interpretation — consumers query the stream. See docs/HOOK_CONTRACT.md.",
"version": "0.2.0",
"version": "0.3.0",
"library": true,
"manifestPlusVersion": "1.2.0",
"authors": [
@@ -23,7 +23,7 @@
"esmodules": ["scripts/main.js"],
"url": "https://git.homelab.local/kaykayyali/hooks-lib",
"manifest": "https://git.homelab.local/kaykayyali/hooks-lib/raw/branch/main/module.json",
"download": "https://git.homelab.local/kaykayyali/hooks-lib/raw/branch/main/hooks-lib-0.2.0.zip",
"download": "https://git.homelab.local/kaykayyali/hooks-lib/raw/branch/main/foundry-hooks-lib-0.3.0.zip",
"readme": "https://git.homelab.local/kaykayyali/hooks-lib/blob/main/README.md",
"changelog": "https://git.homelab.local/kaykayyali/hooks-lib/commits/main",
"bugs": "https://git.homelab.local/kaykayyali/hooks-lib/issues",

View File

@@ -1,5 +1,5 @@
{
"name": "hax-hooks-lib",
"name": "foundry-hooks-lib",
"version": "0.2.0",
"private": true,
"description": "Foundry VTT module: generic Foundry hook facade. Library-only — no UI, no settings, no domain interpretation. v0.2.0 implements HOOK_CONTRACT.md and tests/PLAN.md.",

View File

@@ -33,7 +33,7 @@
import { matchRange } from "./semver.js";
const MODULE_ID = "hax-hooks-lib";
const MODULE_ID = "foundry-hooks-lib";
const _manifests = []; // adapter manifests in registration order
const _active = new Map(); // id -> { manifest, derivedNames }

View File

@@ -16,7 +16,7 @@ import { getEntryForRawName, getEntryForEnvelope } from "./registered-hooks.js";
import { ARG_SHAPES, maybeSynthesize } from "./anti-corruption.js";
import { dispatch } from "./subscribers.js";
const MODULE_ID = "hax-hooks-lib";
const MODULE_ID = "foundry-hooks-lib";
/**
* Build an envelope for a Foundry hook fire.

View File

@@ -20,7 +20,7 @@ import {
} from "./adapter-registry.js";
import { unsubscribeAll } from "./subscribers.js";
const MODULE_ID = "hax-hooks-lib";
const MODULE_ID = "foundry-hooks-lib";
// Track which Foundry hooks we've registered and the listener fn, so
// uninstall can remove them.

View File

@@ -20,7 +20,7 @@
// ANTI_CORRUPTION map below (per §9) lists the raw Foundry names
// that produce each normalized envelope.
const MODULE_ID = "hax-hooks-lib";
const MODULE_ID = "foundry-hooks-lib";
// mode: "sync" | "async"
export const HOOK_REGISTRY = [

View File

@@ -8,7 +8,7 @@
// unsubscribeAll() — purge everything
//
// Error containment (§3.5): throwing consumer callbacks are caught,
// logged via console.error with the [hax-hooks-lib] prefix and the
// logged via console.error with the [foundry-hooks-lib] prefix and the
// hook name, and the dispatch chain continues to the next callback.
// Errors never propagate to Foundry's hook chain.
//
@@ -17,7 +17,7 @@
import { REGISTERED_HOOKS } from "./registered-hooks.js";
const MODULE_ID = "hax-hooks-lib";
const MODULE_ID = "foundry-hooks-lib";
// Per-envelope callback list. Order = registration order.
const _callbacks = new Map(); // hookName (envelope) -> [fn, fn, ...]

View File

@@ -1,4 +1,4 @@
// hax-hooks-lib — module entry point (v0.2.0).
// foundry-hooks-lib — module entry point (v0.2.0).
//
// Generic Foundry hook facade per HOOK_CONTRACT.md. No domain
// interpretation. Subscribes to every Foundry hook in the registered
@@ -37,7 +37,7 @@ import {
} from "./internal/adapter-registry.js";
import { REGISTERED_HOOKS } from "./internal/registered-hooks.js";
const MODULE_ID = "hax-hooks-lib";
const MODULE_ID = "foundry-hooks-lib";
const MODULE_VERSION = "0.2.0";
function isClient() {

View File

@@ -1,4 +1,4 @@
# hooks-lib test plan — v0.2.0
# hooks-lib test plan — v0.3.0
**Status:** Proposed. Implements the contract in `docs/HOOK_CONTRACT.md`.
**Drives:** `tests/verify-hooks-lib.mjs` (no-Foundry smoke) and
@@ -6,8 +6,8 @@
Foundry instance).
The v0.1.0 test file (`tests/verify-hooks-lib.mjs`, 20 assertions) is
**out of scope** for v0.2.0 — its assertions cover the curated-event
catalog shape that v0.2.0 replaces. v0.2.0 ships a fresh test file;
**out of scope** for v0.3.0 — its assertions cover the curated-event
catalog shape that v0.3.0 replaces. v0.3.0 ships a fresh test file;
v0.1.0's stays in git history for reference but is gitignored from
the test runner.
@@ -70,7 +70,7 @@ A throwing consumer callback does not break the dispatch chain.
**Assertions:**
- Register two callbacks; first throws, second runs.
- The error is logged via `console.error` with the prefix
`[hax-hooks-lib]` and the hook name.
`[foundry-hooks-lib]` and the hook name.
- The library does NOT re-throw the error to Foundry's hook chain.
- 100 fires with a single throwing callback complete with the same
total fires-to-other-callbacks count as 100 fires without.
@@ -97,7 +97,7 @@ A throwing consumer callback does not break the dispatch chain.
library continues loading other adapters.
**`unregisterModule` cleanup:**
- After `unregisterModule("hax-hooks-lib")`, `listRegisteredHooks()`
- After `unregisterModule("foundry-hooks-lib")`, `listRegisteredHooks()`
returns `[]`.
- All consumer callbacks are removed (verified by re-registering
init + asserting the registered hook set is empty).
@@ -127,7 +127,7 @@ A throwing consumer callback does not break the dispatch chain.
firing with raw Foundry args produces an `args` array matching the
documented shape, regardless of Foundry version.
- The exact normalization shapes live in `docs/HOOK_ARG_SHAPES.md`.
v0.2.0 ships shapes for: `combatStart`, `combatEnd`, `combatTurn`,
v0.3.0 ships shapes for: `combatStart`, `combatEnd`, `combatTurn`,
`combatRound`, `preUpdateActor`, `updateActor`, `preUpdateToken`,
`updateToken`, `dnd5e.rollAttackV2`, `dnd5e.rollDamageV2`. The
remaining hooks either have stable arity or no normalization.
@@ -176,7 +176,7 @@ Beyond Section D's basic cases:
plan. hooks-lib's tests verify the adapter *protocol* (registration,
matching, lifecycle) but not the adapter's *content*.
- **Foundry-version compatibility beyond the latest two releases.**
v0.2.0 tests Foundry v13.351 and v14.364. Older versions are
v0.3.0 tests Foundry v13.351 and v14.364. Older versions are
"best effort" — the anti-corruption layer should handle them, but
no CI gate.
- **Real Foundry runtime for the smoke test.** The no-Foundry smoke
@@ -187,7 +187,7 @@ Beyond Section D's basic cases:
## Definition of done
A v0.2.0 release is "done" when:
A v0.3.0 release is "done" when:
1. **100% of the "What we test" bullets have an assertion.** No
silent gaps. If we can't test something (e.g. a Foundry-version
@@ -208,7 +208,7 @@ A v0.2.0 release is "done" when:
---
## Test files (v0.2.0)
## Test files (v0.3.0)
| File | Purpose |
|---|---|
@@ -225,10 +225,10 @@ for releases. Performance test runs weekly or on demand.
## Future turns (when this repo is no longer the focus)
- The v0.1.0 `tests/verify-hooks-lib.mjs` and `tests/test-helpers.mjs`
will be archived (git mv into `tests/archive/v0.1.0/`) once v0.2.0's
will be archived (git mv into `tests/archive/v0.1.0/`) once v0.3.0's
test files are stable. They stay in git history but not in the
test runner.
- When battle-focus migrates to hooks-lib v0.2.0, the
- When battle-focus migrates to hooks-lib v0.3.0, the
battle-focus test plan (separate `tests/PLAN.md` in that repo)
will reference hooks-lib's plan for the contract assertions and
add battle-focus-specific consumer assertions.
@@ -239,7 +239,7 @@ for releases. Performance test runs weekly or on demand.
## Open questions for this plan
These are settled when v0.2.0 implementation starts, not before:
These are settled when v0.3.0 implementation starts, not before:
1. **Stub fidelity for the no-Foundry smoke test.** How close does
the stub need to match Foundry's actual `Hooks.on` semantics
@@ -257,4 +257,4 @@ These are settled when v0.2.0 implementation starts, not before:
Both paths are tested.
Push back on any of these or on the plan as a whole before the
v0.2.0 implementation starts.
v0.3.0 implementation starts.

View File

@@ -27,7 +27,7 @@ function assert(name, cond, extra = "") {
}
async function main() {
console.log("--- hax-hooks-lib v0.2.0 perf test ---");
console.log("--- foundry-hooks-lib v0.3.0 perf test ---");
installStubs();
uninstall();
install();

View File

@@ -1,4 +1,4 @@
// tests/test-helpers.mjs — v0.2.0
// tests/test-helpers.mjs — v0.3.0
//
// Foundry hook stub for the no-Foundry smoke test. Installs globalThis.Hooks
// with the same semantics as Foundry v14:

View File

@@ -1,4 +1,4 @@
// tests/verify-hooks-lib.mjs — v0.2.0
// tests/verify-hooks-lib.mjs — v0.3.0
//
// Smoke test for the generic Foundry hook facade. Implements
// tests/PLAN.md sections A-G (envelope shape, subscriber API, error
@@ -64,7 +64,7 @@ function assertEq(name, actual, expected) {
}
async function mainTest() {
console.log("--- hax-hooks-lib v0.2.0 smoke test ---");
console.log("--- foundry-hooks-lib v0.3.0 smoke test ---");
// ----- Section F.1 — semver range matcher (foundation for G) -----
console.log("[F.1] semver range matcher");
@@ -222,10 +222,10 @@ async function mainTest() {
}
assertEq("C: second callback still fires after first throws", seenC, ["updateActor"]);
assert(
"C: error logged via console.error with [hax-hooks-lib] prefix and hook name",
"C: error logged via console.error with [foundry-hooks-lib] prefix and hook name",
consoleErrorCalls.length > 0 &&
consoleErrorCalls.some((args) =>
String(args[0]).includes("[hax-hooks-lib]") &&
String(args[0]).includes("[foundry-hooks-lib]") &&
String(args[0]).includes("updateActor")
)
);
@@ -480,7 +480,7 @@ async function mainTest() {
evaluateAtReady({ id: "dnd5e", version: "5.2.5" }, "13.351.0");
// Re-evaluation calls factory again. This is the documented behavior
// in §4.2 — adapters must be idempotent.
// v0.2.0 implementation re-evaluates on each ready. Adapters must
// v0.3.0 implementation re-evaluates on each ready. Adapters must
// handle this. We assert that the factory IS called twice (re-eval
// happened), since the adapter protocol requires idempotency.
assert("D.7: re-evaluation calls factory (adapter must be idempotent)", callsD7 === 2);