Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
78338ab
Add defineScene authoring normalizer as single source of scene defaul…
Brad-Edwards May 30, 2026
4d5d2d3
Centralize per-mode behavior into a ModeProfile data table
Brad-Edwards May 30, 2026
d22bbb7
refactor(scene-loader): decompose buildLoad/runTarget below the compl…
Brad-Edwards May 30, 2026
9a26f11
refactor(timeline): extract presenter transport into an opt-in presen…
Brad-Edwards May 30, 2026
976a892
refactor(audio): close audio.ts cognitive-complexity suppressions
Brad-Edwards May 30, 2026
4f1b4fc
refactor(audio): single disposal gate, shared bed registration, table…
Brad-Edwards May 30, 2026
3cf332c
refactor(audio): fold sprite-map validation into assertSoundDefinition
Brad-Edwards May 30, 2026
05cd30f
refactor(audio): drop bespoke bed routing, collapse output policy to …
Brad-Edwards May 30, 2026
edb4bd6
refactor(runtime): simplify composition-resolver and scene-navigation…
Brad-Edwards May 30, 2026
daa0762
refactor(runtime): extract withFailureIsolation decorator over bare l…
Brad-Edwards May 30, 2026
d4b28d4
refactor(templates): author L2 templates against real DOM types
Brad-Edwards May 30, 2026
4b8c88f
test: relocate source-policy suite into a dedicated blocking gate
Brad-Edwards May 31, 2026
f1e90cd
refactor(audio): collapse AudioError subclasses to one categorized er…
Brad-Edwards May 31, 2026
635d80a
test: harden validator-fuzz field oracle and pin AudioError 'option' …
Brad-Edwards May 31, 2026
a9306c4
docs: drop stale complexity-gate narration from runtime comments
Brad-Edwards May 31, 2026
91f39c1
docs(runtime): collapse narrative comments to terse contracts
Brad-Edwards May 31, 2026
14ea31e
refactor(runtime): inline linter-noise helpers and collapse condition…
Brad-Edwards May 31, 2026
6e31da3
refactor(runtime): simplify audio/timeline patterns
Brad-Edwards May 31, 2026
b29e1f6
refactor: remove root DOM casts and trim system/deck boilerplate
Brad-Edwards May 31, 2026
f4f67b2
test: consolidate duplicated test fakes into tests/support/fakes.ts
Brad-Edwards May 31, 2026
8127170
test: cover the L2 template library + timing helpers; scope decks out…
Brad-Edwards May 31, 2026
684bd62
ci(sonar): exclude example decks from duplication detection
Brad-Edwards May 31, 2026
be7e627
docs(adr): add ADR-032 single imperative scene control plane
Brad-Edwards Jun 7, 2026
9a460fd
feat(runtime): single imperative scene control plane (spike, ADR-032)
Brad-Edwards Jun 7, 2026
157d2fa
feat(runtime): drive the real SceneModule contract through the contro…
Brad-Edwards Jun 7, 2026
74961a4
feat(runtime): per-scene preload + slice index in runSceneModules
Brad-Edwards Jun 7, 2026
ebc7baa
feat(runtime): wire the live app onto the run-loop (present-loader)
Brad-Edwards Jun 7, 2026
c398189
refactor(runtime): delete the master-timeline layer + speculative mod…
Brad-Edwards Jun 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Install pre-commit
run: pip install pre-commit
# typecheck and vitest hooks have dedicated jobs below that also
# upload coverage. Skip them here to avoid duplicate runs.
# typecheck, vitest, and policy hooks have dedicated jobs below.
# Skip them here to avoid duplicate runs.
- name: Run pre-commit
run: pre-commit run --all-files --show-diff-on-failure
env:
SKIP: typecheck,vitest
SKIP: typecheck,vitest,policy

typecheck:
name: Typecheck
Expand Down Expand Up @@ -89,6 +89,26 @@ jobs:
path: coverage/
retention-days: 7

policy:
# Structural code-shape audits (PUL-Q001/Q003/Q007, PUL-A001..A010,
# complexity-gate allowlist). Relocated out of the behavior suite
# into their own vitest project so their whole-tree AST scans no
# longer starve under parallel load — enforcement is unchanged and
# this step is blocking. Mirrored by the `policy` pre-commit hook.
name: Policy gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v5
with:
version: 9.15.0
- uses: actions/setup-node@v5
with:
node-version: '22'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm policy

build:
name: Build
runs-on: ubuntu-latest
Expand Down
11 changes: 11 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,14 @@ repos:
language: system
pass_filenames: false
files: ^(src/.*\.ts|tests/.*\.ts|vitest\.config\.ts|package\.json)$

# Structural code-shape audits, relocated out of the behavior
# suite into their own vitest project (vitest.policy.config.ts).
# Blocking — same enforcement set as before, just no longer mixed
# into `pnpm test`. Mirrored by the CI `policy` job.
- id: policy
name: pnpm policy (source-policy gate)
entry: bash -c 'pnpm policy'
language: system
pass_filenames: false
files: ^(src/.*\.ts|tests/.*\.ts|biome\.json|vitest\.policy\.config\.ts|package\.json)$
28 changes: 28 additions & 0 deletions changelog.d/+audio-engine-slimming.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Closed the three `src/runtime/audio.ts` cognitive-complexity
suppressions. `unlock()` now delegates its HTML5 and Web Audio
fallback branches to the `unlockHtml5Fallback` / `resumeWebAudioContext`
module helpers; `play()` delegates option validation to `validatePlay`
and per-instance engine output to `applyPlayToHandle`. The
`normalizeSources` offender was already covered by the hoisted
`normalizeAudioUrl`. Behavior is unchanged — the audio service public
methods, output policies, error families, composition bed routing, cue
gate, and master-mute semantics are byte-identical. The audio.ts rows
were removed from `docs/design/complexity-backlog.md` and the
complexity-gate policy oracle.

Slimmed the audio service internals without changing observable
behavior: the per-service `disposed` boolean and its scattered guards
were replaced by a single internal `AbortController` so the navigation
signal and an explicit `stopAll()` converge on one disposal gate and one
teardown; the composition bed is registered through the same
`registerSound` core scene sounds use under the reserved, non-kebab
`composition audio bed` id (the bespoke `startBed` arrow and `bedAllowed`
local are gone — the bed is now started inline through the shared core,
gated against its own declared `src`), keeping it unreachable from
`ctx.audio`; and the four per-option `assertPlayOption*` helpers were
folded into a table-driven `assertPlayOptions`. The validated
`outputPolicy` string is now collapsed once at construction into the two
orthogonal output axes the policy actually controls — `muted` (engine
mute) and `emitCues` (rehearsal cue sink) — so no service method
re-derives behavior from the policy string. The public `outputPolicy`
option and its allowlist validation are unchanged.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Collapsed the audio error hierarchy and de-duplicated the shape-validator tests. The nine `AudioError` subclasses
(`AudioSoundError` / `AudioGroupError` / `AudioRangeError` / `AudioSourceError`, etc.) are gone — no `src/` caller
discriminated them — replaced by one `AudioError` carrying a `category` discriminant (`sound` / `group` / `source` /
`range` / `option`) and module-private per-category constructors. `ctx.audio` runtime behavior, the throwable surface,
and every rejected input are unchanged. The brittle field-by-field `.each` validation loops for `assertSceneModule`,
`assertCompositionManifest`, and `assertAudioBedDeclaration` are replaced by one representative assert per shape plus a
shared seeded property fuzz (`tests/runtime/validator-fuzz.ts`) covering the same malformed-input classes (omission,
wrong type, out-of-range number, non-kebab id) and asserting the offending field is named.
14 changes: 14 additions & 0 deletions changelog.d/+authoring-real-dom.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
L2 template/scene authoring now uses real `lib.dom` types. The
structural fake-DOM types (`TemplateDomElement` / `TemplateDomFactory`
/ `TemplateStageElement`) and the per-call defensive ctx narrowing
(`isTemplateCtx` / `isStageShape` / `isGsapShape`) are gone; templates
take `HTMLElement` / `Document` directly and read `ctx` through one
`asTemplateCtx` view that checks only the genuine off-DOM
(`stage === null`) path. Every `as unknown as HTMLElement|Document`
cast in templates and decks is removed. Decks reference a shared
`TemplateTimeline` type instead of re-declaring a structural timeline
subset. The deck-only templates `operatorDossier`, `incidentPlate`,
and `haulCitations` moved into `src/decks/local-calgary-v2/templates/`
(with their CSS) since no other deck uses them. DOM-touching template
tests opt into `happy-dom` per file and assert against real rendering;
the runtime fake-stage suites stay node-env and unchanged.
8 changes: 8 additions & 0 deletions changelog.d/+comment-sweep.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Collapsed narrative/ceremony comments across the runtime to terse
one-line contracts. Trimmed module preambles, per-field JSDoc on
`WorkbenchSceneCtx` / `SceneLoaderOptions` / `LoadSceneNavigationTargetOptions`,
and review-cycle narration in `src/main.ts` and `src/runtime/{scene-loader,
scene-loader-ctx,scene-loader-guard,scene,navigation,scene-navigation,
composition-resolver,composition,validation,audio,timeline,presenter,
prompter,workbench-chrome}.ts`. Comments only — no code, behavior, or
public-API change.
23 changes: 23 additions & 0 deletions changelog.d/+composition-resolver-internals.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Refactored the composition-resolver and scene-navigation internals
without changing behavior. `SceneActivation` identity is now embedded
in each plan step at build time (no per-call reconstruction); the
three finalizers collapse into one `finalize()` that selects
aggregate-vs-reraise from whether `onSceneFailed` was supplied;
`onSceneFailed` is wrapped once at lifecycle-context build; the bare
mount→compose→run→cleanup engine (`runLifecycle`) is separated from
the scene-failure-isolation concern, which now lives in a named
`withFailureIsolation` decorator (owning the failure bucket, the
once-wrapped `onSceneFailed`, and the aggregate-vs-reraise routing)
that `resolveComposition` is composed from; the three composition-
resolution paths (from-start / scene / index) unify behind one
parameterized index finder; and the resolver's run-option builder is
reduced to a single strip. The bare engine (`runLifecycle`,
`orchestrate` via `buildPlan` + `buildLifecycleContext`) is exported so
it can be driven directly in tests with a plain collecting
`reportFailure`. Added both-path (onSceneFailed supplied / omitted)
regression coverage asserting cleanup-exactly-once-per-activation and
correct error routing for create-throw, timeline-throw, cleanup-throw,
abort-mid-mount, and a repeated scene id where one occurrence fails,
plus a bare-engine suite that drives `runLifecycle` without the
decorator. Public signatures, `data-pulsar-*` attributes, and error
wording are unchanged.
10 changes: 10 additions & 0 deletions changelog.d/+mode-profile-table.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Centralized per-mode workbench behavior into a single `ModeProfile`
data table (`src/runtime/mode-profile.ts`). The audio output policy,
composition-slice truncation, chrome visibility, audio-bed suppression,
scrub cue gate, and head-scene runner hints were previously scattered
as `mode === X` branches across `scene-loader.ts` and
`workbench-chrome.ts`; they now read one frozen profile per mode.
Behavior is unchanged (snapshot-equivalence test), and collapsing the
four runner-hint ternaries into a single `...runnerHints` spread dropped
`runLifecycle` below the cognitive-complexity gate, closing a
complexity-backlog entry.
14 changes: 14 additions & 0 deletions changelog.d/+policy-gate-relocation.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Relocated the policy / source-scan suites
(`tests/runtime/policy-*.test.ts`,
`tests/runtime/screenshot-determinism-source.test.ts`, and their shared
`source-policy.ts` AST framework) out of the default behavior suite
into a dedicated, still-blocking gate. They run via a new `pnpm policy`
script against `vitest.policy.config.ts` (serial, generous timeout) and
are excluded from `vitest.config.ts`, so `pnpm test` is now
behavior-only and no longer flakes on the structural AST scans starving
under parallel load (PUL-Q003 / PUL-Q007 5s timeouts). Enforcement is
unchanged: `pnpm policy` is wired as a blocking job in
`.github/workflows/ci.yml` and a blocking hook in
`.pre-commit-config.yaml`, running the identical violation set. No
policy check was dropped or weakened; the Biome complexity-gate
override for the cluster is untouched.
10 changes: 10 additions & 0 deletions changelog.d/+presenter-transport-extraction.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Extracted the presenter transport machinery (advance / hold / skip /
pause / resume command translation) out of the always-on timeline
composition path into a dedicated opt-in module
(`src/runtime/presenter-transport.ts`). The transport is wired onto the
master timeline only when a navigation forwards a presenter controller
(`mode=present`); a non-present navigation never instantiates it.
`timeline.ts` keeps the GSAP composition spine — `composeMasterTimeline`,
the scene label namespace, `assertSceneTimeline`, and the `MasterBeat`
beat query. No public signatures, `data-pulsar-*` attributes, or
cross-engine timing behavior changed.
12 changes: 12 additions & 0 deletions changelog.d/+scene-loader-decomposition.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Decomposed the scene-loader god-functions (`buildLoad`, `runTarget`)
into cohesive single-responsibility units below the cognitive-complexity
gate, deleting both `noExcessiveCognitiveComplexity` suppressions.
Per-navigation audio service / presenter pipe / ctx factory moved to
`src/runtime/scene-loader-ctx.ts`; the present-mode audio unlock-gate
predicate and the composition chrome dispatch policy to
`src/runtime/scene-loader-guard.ts`. The `beat` / `mode` grammar rules
are now sourced from a single `NAVIGATION_GRAMMAR` object in
`src/runtime/navigation.ts`, consumed by both `parseNavigationSearch`
and the loader's defense-in-depth re-check (the forged-target trust
seam is retained). `createSceneLoader`, all exported types, the
`data-pulsar-*` stage attributes, and runtime behavior are unchanged.
13 changes: 13 additions & 0 deletions changelog.d/+simplify-audio-timeline.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Simplified the audio/timeline runtime cluster without behavior change:
inlined the single-use `validateSpeed` / `validateRepeat` validators
into `GsapMasterTimeline.setSpeed` / `.repeat`, rebuilt
`buildRunComposeOptions` as a single conditional-spread literal instead
of an empty object with four `as`-cast field assignments, and collapsed
the `...(x === undefined ? {} : { x })` idiom to `...(x && { x })` for
the object/boolean-typed `sprite` / `mute` fields in
`createHowlerAudioEngine`. Also trimmed the audio-unlock-dom module
preamble, the orphaned/duplicated adapter JSDoc, and the codex-cycle
narration to terse contracts. Public signatures (`AudioService` /
`AudioError` / `MasterTimeline` / `TimelineEngine` / the audio-unlock
adapter), error strings, `data-pulsar-*` attributes, and the abort-race
isolation guard are unchanged.
11 changes: 11 additions & 0 deletions changelog.d/+simplify-runtime-patterns.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Simplified the scene-loader and navigation/composition runtime cluster
without behavior change: inlined linter-noise micro-helpers
(`setStageAttr` / `clearStageAttr` / `audioOutputPolicyFor` /
`audioServiceOptions`), collapsed the `...(x === undefined ? {} : { x })`
conditional-spread idiom to its positive `...(x ? { x } : {})` form,
replaced writable-intermediate-then-freeze object construction in
`parseNavigationSearch` / `composeSegments` / `buildPrompterScript` with
direct frozen literals, and merged the two-stage `chromeBehavior`
extraction in `scene-loader-guard`. Public signatures, `data-pulsar-*`
attributes, error strings, and PUL-Q008 attribute-literal handling are
unchanged.
8 changes: 8 additions & 0 deletions changelog.d/+simplify-system-bootstrap.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Made `createDomWorkbenchChrome` and `createDomAudioUnlockAdapter` generic
over their concrete element type so `src/main.ts` mounts a real
`HTMLElement` / `HTMLButtonElement` without `as unknown as Node` casts.
Trimmed narrative/ceremony comments in the chrome, prompter-window,
practice-renderer, and keyboard-source modules, and collapsed the
repeated `tl.fromTo` reveal boilerplate in the pulsar-intro deck behind a
local `reveal` helper (byte-identical timeline output). Behavior, exported
signatures, DOM attributes, and tests are unchanged.
11 changes: 11 additions & 0 deletions changelog.d/+template-coverage.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Behavioral test coverage for the under-tested L2 template library and
runtime timing helpers. New mount-and-assert suites exercise the real
rendered DOM, authored timeline beats, and cleanup for `terminal`,
`card-carousel`, `activity-feed-payoff`, `chat-pick-list`,
`split-dialogue-email`, `split-pane-terminal-doc`, `metric-ticker`, the
`_shared` template envelope, the `register` token barrel, and the
abortable-timing primitives in `helpers/timing`. Product line coverage
(runtime + template library) rises from ~93% to ~99%; `terminal.ts`
alone goes 35% to 99%. The example decks (demonstration content,
exercised by the Playwright E2E) are scoped out of the coverage gate —
the gate measures the product, not the sample decks.
11 changes: 11 additions & 0 deletions changelog.d/+test-fakes-fixture.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Extracted the duplicated jsdom-free test fakes into a single
`tests/support/fakes.ts` fixture: the `mode=*` fixture-scene stage stub
(was re-derived byte-for-byte in five `tests/scenes/*-fixture.test.ts`
files), the synthetic `HTMLElement`/`Document` tree the chrome pack
tests use (was duplicated across `chrome-slots` and `chrome-extras`),
and the event-emitting / no-op presenter controllers (was re-rolled in
`helpers` and `presenter-driven`). Typing the chrome fake as the real
DOM interfaces dropped every `as unknown as HTMLElement|FakeElement`
cast at the chrome-test call sites, and trimmed the copy-pasted
coverage-narration headers to one line each. Assertions, test counts,
and behavior coverage are unchanged.
8 changes: 8 additions & 0 deletions changelog.d/162.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
L1 runtime rewrite (ADR-032): scenes now run through a single imperative
control plane (`runSceneModules` in `src/runtime/spike/control-plane.ts`),
driven per navigation by `createPresentLoader` (`src/runtime/present-loader.ts`)
which `src/main.ts` boots through. Each scene mounts via `create`, plays its
own GSAP timeline standalone to completion, holds for presenter advance, then
tears down via `cleanup` — replacing the master-timeline composition resolver.
L2 template teardown moved from the timeline into the scene `cleanup` hook so
it fires on scene exit rather than at the timeline's natural end.
6 changes: 6 additions & 0 deletions changelog.d/162.removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Removed the master-timeline layer and speculative workbench modes (ADR-032):
the `standalone`, `loop`, `paused`, `scrub`, `screenshot`, and `rehearsal`
modes (only `present` and `prompter` remain), the master-timeline composition
resolver + presenter transport, the GSAP master composer/advance gates,
inter-scene transitions, and the scrub controls. The `mode=` grammar,
mode-profile table, and the speculative scene fixtures were trimmed to match.
Loading