From d038eb8c6725ad229462738b049abeb291c8014d Mon Sep 17 00:00:00 2001 From: Kaysser Kayyali Date: Sat, 20 Jun 2026 16:53:37 -0400 Subject: [PATCH] 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. --- .gitignore | 10 +++---- LICENSE | 3 +- README.md | 26 +++++++++--------- docs/HOOK_CONTRACT.md | 4 +-- ...b-0.1.0.zip => foundry-hooks-lib-0.1.0.zip | Bin ...b-0.2.0.zip => foundry-hooks-lib-0.2.0.zip | Bin foundry-hooks-lib-0.3.0.zip | Bin 0 -> 49493 bytes module.json | 8 +++--- package.json | 2 +- scripts/internal/adapter-registry.js | 2 +- scripts/internal/envelope.js | 2 +- scripts/internal/lifecycle.js | 2 +- scripts/internal/registered-hooks.js | 2 +- scripts/internal/subscribers.js | 4 +-- scripts/main.js | 4 +-- tests/PLAN.md | 26 +++++++++--------- tests/perf.mjs | 2 +- tests/test-helpers.mjs | 2 +- tests/verify-hooks-lib.mjs | 10 +++---- 19 files changed, 55 insertions(+), 54 deletions(-) rename hooks-lib-0.1.0.zip => foundry-hooks-lib-0.1.0.zip (100%) rename hooks-lib-0.2.0.zip => foundry-hooks-lib-0.2.0.zip (100%) create mode 100644 foundry-hooks-lib-0.3.0.zip diff --git a/.gitignore b/.gitignore index cf2696c..634a9d8 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/LICENSE b/LICENSE index 8c0fed4..b503f39 100644 --- a/LICENSE +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md index e29f6d3..c804bd0 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docs/HOOK_CONTRACT.md b/docs/HOOK_CONTRACT.md index b7cfdd1..882c202 100644 --- a/docs/HOOK_CONTRACT.md +++ b/docs/HOOK_CONTRACT.md @@ -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(...)`. --- diff --git a/hooks-lib-0.1.0.zip b/foundry-hooks-lib-0.1.0.zip similarity index 100% rename from hooks-lib-0.1.0.zip rename to foundry-hooks-lib-0.1.0.zip diff --git a/hooks-lib-0.2.0.zip b/foundry-hooks-lib-0.2.0.zip similarity index 100% rename from hooks-lib-0.2.0.zip rename to foundry-hooks-lib-0.2.0.zip diff --git a/foundry-hooks-lib-0.3.0.zip b/foundry-hooks-lib-0.3.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..82de45d9d4b4c816255a7549cdbfd7a43fb8e129 GIT binary patch literal 49493 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 ZDo8_tZVrCjjFW?jfr`f7)&2PGzW~gxE4lyx literal 0 HcmV?d00001 diff --git a/module.json b/module.json index f3a6d83..2d0bc26 100644 --- a/module.json +++ b/module.json @@ -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", diff --git a/package.json b/package.json index cb593cf..efed280 100644 --- a/package.json +++ b/package.json @@ -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.", diff --git a/scripts/internal/adapter-registry.js b/scripts/internal/adapter-registry.js index 7b464a0..fc3733f 100644 --- a/scripts/internal/adapter-registry.js +++ b/scripts/internal/adapter-registry.js @@ -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 } diff --git a/scripts/internal/envelope.js b/scripts/internal/envelope.js index 14abb05..6c05231 100644 --- a/scripts/internal/envelope.js +++ b/scripts/internal/envelope.js @@ -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. diff --git a/scripts/internal/lifecycle.js b/scripts/internal/lifecycle.js index 526bd9d..9f2ec59 100644 --- a/scripts/internal/lifecycle.js +++ b/scripts/internal/lifecycle.js @@ -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. diff --git a/scripts/internal/registered-hooks.js b/scripts/internal/registered-hooks.js index dbfcaee..3a94c11 100644 --- a/scripts/internal/registered-hooks.js +++ b/scripts/internal/registered-hooks.js @@ -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 = [ diff --git a/scripts/internal/subscribers.js b/scripts/internal/subscribers.js index c4bc9b7..ef25805 100644 --- a/scripts/internal/subscribers.js +++ b/scripts/internal/subscribers.js @@ -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, ...] diff --git a/scripts/main.js b/scripts/main.js index d36211c..a7b96c4 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -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() { diff --git a/tests/PLAN.md b/tests/PLAN.md index c277eae..94a4ee3 100644 --- a/tests/PLAN.md +++ b/tests/PLAN.md @@ -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. \ No newline at end of file +v0.3.0 implementation starts. \ No newline at end of file diff --git a/tests/perf.mjs b/tests/perf.mjs index 473a989..0bab6cb 100644 --- a/tests/perf.mjs +++ b/tests/perf.mjs @@ -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(); diff --git a/tests/test-helpers.mjs b/tests/test-helpers.mjs index 3ca11cf..fe9d1e6 100644 --- a/tests/test-helpers.mjs +++ b/tests/test-helpers.mjs @@ -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: diff --git a/tests/verify-hooks-lib.mjs b/tests/verify-hooks-lib.mjs index 5973948..4171c77 100644 --- a/tests/verify-hooks-lib.mjs +++ b/tests/verify-hooks-lib.mjs @@ -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);