Bug: timeSinceStart only updated when an envelope event fired
(combatStart, attack-roll, etc.). If the combat was idle — a
player thinking about their turn, between turns — the timer froze.
Fix: 1Hz self-rescheduling setTimeout in the constructor that
bumps timeSinceStart and triggers a throttled render while
_state.isActive is true. Stopped on unwireHooks (combat end).
TDD: Section O (5 assertions) added BEFORE the fix.
- O.1 initial timeSinceStart reflects _combatStartedAt
- O.2 advances without an envelope event
- O.3 tick interval is ~1s
- O.4 timer does not tick when combat is inactive
- O.5 timer resumes when combat becomes active again
Tests: 65/65 passing in ~2s. Playwright 31/31.
- Requires GM slot (skipped in CI / when user has GM open).
- Test 1: create/start combat with 2+ combatants, advance one turn,
assert HUD shows new combatant as current.
- Test 2: reload the page mid-combat, assert HUD auto-opens with
the resumed encounter state.
- npm run test:turn-update-live
Bug 1: current-turn indicator not updated on combatant switch.
Foundry's combatTurn hook fires BEFORE the new state is committed,
so game.combat.combatant is the OLD combatant at that moment. The
event-translation layer now resolves the new combatant from
combat.turns[newTurn] and passes combatantId + combatantName +
combatantTokenId through. The HUD stashes these on _state._latestTurn
and uses it as a tiebreaker when the encounter's combatantId is
stale (which battle-focus's encounter always is, since bf doesn't
update currentTurn on turn events).
Bug 2: HUD doesn't open on session-open with a pending/active combat.
At ready, check battle-focus.api.getActiveEncounter(); if it's
live (not endedAt), open the HUD and populate from the encounter.
This handles browser-refresh mid-combat and 'pending combat'
(tracker exists but Start Combat not yet clicked).
Bonus: encounter.combatantId key resolution. The encounter's
combatantId is the Foundry combatant document id, but the
combatants map may be keyed by tokenId, actorId, or combatantId.
The currentTurn resolver now tries all three.
Tests: 60/60 passing in <1s. Sections M (turn event) + N
(session-open) added.
verify-hub-e2e-matrix.mjs verifies that adding or removing any of
the 4 modules from the active world doesn't break the others'
lifecycle or the hub's behavior. Each cell sets
core.moduleConfiguration, reloads, and runs a focused subset of
assertions.
Cells:
0. baseline (all 4 on)
1. remove hooks-lib
2. remove battle-focus
3. remove its-achievable
4. remove combat-hud-hub
5. only combat-hud-hub
6. only its-achievable
7. all off (control)
All 53 assertions pass. combat-hud-hub's core sections correctly
register iff battle-focus is active. pinned-achievements section
correctly registers iff its-achievable is active. No thrown
errors in any cell.
- Wait for api.isReady() before checking section list.
- Auto-create a combat + place tokens if no encounter exists.
- Force a render after pushFeedEntry so the section's render fn
actually fires.
- Capture full-install screenshot at tests/screenshots/.
- Screenshot shows: Round 1, Fire Giant (only actor in demo world),
Dice Streak 0, Pinned Achievements 'None yet' — its-achievable
integration with combat-hud-hub working end-to-end.
- New api.isReady() returns true after the ready hook fires (and
core sections have registered). Tests now wait for isReady()
before asserting on the section list.
- 3-place version rule (module.json + package.json + main.js
MODULE_VERSION) — v0.2.2 was missing the source bump; corrected
by going to v0.2.3 across all three places.
- Tests: 50/50 passing in <1s.
Missed the 3-place version rule on the v0.2.2 bump. MODULE_VERSION
was still v0.2.0 in scripts/main.js, so Foundry's init log showed
the old version and the live system didn't have getFeed/clearFeed.
Caught during the e2e Playwright run when Foundry loaded v0.2.0
of the main.js but v0.2.2 of the module.json.
- api.getFeed(sectionId) returns a shallow copy of the section's
feed entries. Useful for tests + consumer introspection.
- api.clearFeed(sectionId) empties a section's feed.
- Smoke: 50/50 passing. Section C extended with new assertions.
- This unblocks the its-achievable v0.3.0 Playwright test, which
needs getFeed to verify chatBubble → pushToHubFeed → chh feed.
- New tests/verify-combat-hud-hub-foundry.mjs (12 assertion sections).
Covers live Foundry v14: API surface, soft-dep presence, section
registry round-trip, pushFeedEntry, HUD open on combatStart, core
section rendering, current-turn indicator, screenshot, no errors.
- New tests/PLAN.md (per-repo test plan).
- .gitignore: tests/screenshots/ excluded.
- Combatants row whose tokenId matches encounter's current combatant
gets data-chh-current-turn=true, an accent-color border + tint, an
outlined portrait, and a '▶' marker before the name.
- Header section prepends '▶' before the turn name.
- buildRenderContext now resolves currentTurn from the encounter
singleton (encounter.currentTurn / encounter.combatantId) instead
of reading game.combat directly. battle-focus owns the encounter.
- Tests: 46/46 in <1s, new section L covers the indicator.
- Public API: addSection, removeSection, pushFeedEntry, listSections,
getHud, openHud, closeHud.
- Soft-dep on foundry-hooks-lib (envelope subscription) + battle-focus
(encounter seam).
- Built-in core sections (round, current turn, per-PC damage, dice
streak) register only when both deps are present at ready.
- Smoke tests: 22/22 in <1s covering sections A-G of tests/PLAN.md.
- HUD instance is a stub in v0.1.0; ApplicationV2 mounting in v0.2.0.