Files
suaveplan/openspec/archive/2026-05-23-enterprise-audio-sprite-player/tasks.md
2026-05-27 23:29:53 +01:00

8.6 KiB

Tasks: enterprise-audio-sprite-player

Ordered implementation checklist. Each task is a discrete, checkable item; an unchecked item at the end means the package is not done.

0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • 1.1 Create packages/media/audio/audio-sprite-player/ with package.json (name: "@suaveplan/audio-sprite-player", version: "0.1.0", private: false, sideEffects: false, type: "module").
  • 1.2 Declare runtime deps: @suaveplan/audio-core workspace:^, @suaveplan/audio-sprite-packer workspace:^, @suaveplan/error workspace:^, @suaveplan/types workspace:^, @suaveplan/events workspace:^, zod catalog:forms, @opentelemetry/api catalog:observability, tslib catalog:build.
  • 1.3 Declare dev deps via catalog: vitest, @vitest/coverage-istanbul, fast-check, mitata, tsup, typescript, @suaveplan/testing workspace:^.
  • 1.4 Add exports map for every subpath declared in the proposal module layout, plus the root entry.
  • 1.5 Add tsup.config.ts (ESM + CJS, dual emit, no minify).
  • 1.6 Add vitest.config.ts using createTestConfig() from @suaveplan/testing/config; provider: 'istanbul', pool: 'forks', fileParallelism: false.
  • 1.7 Add tsconfig.json extending @suaveplan/typescript-config/universal.json.
  • 1.8 Run bun scripts/folder-reorg/generate-manifest.ts to refresh MANIFEST.md.

2. Types, errors, schemas

  • 2.1 Author src/sprite-player/sprite-player.types.ts — SpritePlayerOptions, PlayOptions, SequenceOptions, ResolvedPlayOptions, SpritePlayHandle.
  • 2.2 Author src/adapter/adapter.types.ts — SpritePlaybackAdapter interface.
  • 2.3 Author src/error-codes.ts registering every AUDIO_SPRITE_PLAYER.* code via defineCodes(...) with i18n keys and severity.
  • 2.4 Author src/schemas.ts — Zod schemas for SpritePlayerOptionsSchema, PlayOptionsSchema, SequenceOptionsSchema, and a manifest validator re-using shapes from @suaveplan/audio-sprite-packer (type-only) by hand-defining the equivalent runtime Zod schema (since we cannot import runtime Zod schemas from the type-only consumer).

3. NullSpritePlaybackAdapter

  • 3.1 src/adapter/null/null.ts — implementation that records every call.
  • 3.2 src/adapter/null/null.test.ts — verifies calls order, handle behaviour, type-only-import compile check.
  • 3.3 src/adapter/null/null.md and src/adapter/adapter.md — documented contract.

4. VoiceManager + voice-stealing

  • 4.1 src/voice-manager/voice-manager.ts — tracks active handles, picks oldest, emits voice-stolen, exposes opt-out leading to VOICE_LIMIT_EXCEEDED.
  • 4.2 src/voice-manager/voice-manager.test.ts — covers steal-on-overflow, idempotent stop after steal, opt-out path.
  • 4.3 src/voice-manager/voice-manager.md.

5. SpritePlayer orchestrator

  • 5.1 src/sprite-player/sprite-player.ts — public class. Wires construction validation, load, play, playSequence, playRandom, stopAll, list, has, dispose.
  • 5.2 src/sprite-player/sprite-player.test.ts — covers every spec scenario named in the delta under this module path.
  • 5.3 src/sprite-player/sprite-player.md.

6. Events wiring

  • 6.1 src/events/events.ts — typed SpritePlayerEvents built on @suaveplan/events with the five event names.
  • 6.2 src/events/events.test.ts — covers play→complete, voice-stolen payload, error event on async failure.
  • 6.3 src/events/events.md.

7. OTel wiring

  • 7.1 src/telemetry/telemetry.ts — span audio-sprite-player.play, counter audio_sprite_player.play_total, gauge audio_sprite_player.voices_active, histogram audio_sprite_player.play_duration_ms.
  • 7.2 src/telemetry/telemetry.test.ts — uses @opentelemetry/api in-memory exporter to verify each instrument fires.
  • 7.3 src/telemetry/telemetry.md.

8. AbortSignal wiring

  • 8.1 Pre-aborted signal short-circuit on load, play, playSequence, playRandom.
  • 8.2 Mid-flight abort path stops in-flight voices and rejects with AUDIO_SPRITE_PLAYER.ABORTED.
  • 8.3 Tests for both paths in sprite-player.test.ts.

9. Barrel + exports

  • 9.1 src/index.ts uses explicit named re-exports only (rule 10) — no export *.
  • 9.2 Every feature folder's index.ts is an explicit named barrel.
  • 9.3 Smoke test in sprite-player.test.ts iterates package.json#exports and imports each subpath.

10. Tests

  • 10.1 All .test.ts files import test-runner primitives from @suaveplan/testing/runner ONLY (rule 11).
  • 10.2 No vi.mock / jest.mock (rule 2).
  • 10.3 Use FakeClock from @suaveplan/testing for deterministic clock.now() and scheduling tests.
  • 10.4 bunx turbo test --filter=@suaveplan/audio-sprite-player green.
  • 10.5 bunx turbo test:coverage --filter=@suaveplan/audio-sprite-player shows 100% on touched modules.

11. Benchmarks

  • 11.1 bench/play.bench.ts using mitata — measure play() overhead with NullSpritePlaybackAdapter (target: < 50 µs P50, voice-stealing path < 100 µs P50).
  • 11.2 bench/sequence.bench.ts — measure playSequence scaling at 1, 10, 100 entries.

12. Docs

  • 12.1 Every .ts (excl. .test.ts, .types.ts inside feature dirs, barrel index.ts) has a sibling .md meeting docs:check thresholds (≥200 words, all required sections, basic + advanced example).
  • 12.2 Author README.md at the package root (no category-level READMEs).
  • 12.3 bunx turbo docs:check --filter=@suaveplan/audio-sprite-player green.

13. Pre-publish gates

  • 13.1 bunx turbo lint --filter=@suaveplan/audio-sprite-player green; no // biome-ignore comments anywhere in src/ (rule 9).
  • 13.2 bunx turbo typecheck --filter=@suaveplan/audio-sprite-player green.
  • 13.3 bun scripts/lint/check-test-runner-imports.ts green for this package (rule 11 CI gate).
  • 13.4 grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only\|\bany\b\|console\.log" packages/media/audio/audio-sprite-player/src/ --include="*.ts" returns zero matches (§2).
  • 13.5 grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/media/audio/audio-sprite-player/src/ returns zero matches (rule 11 verification).
  • 13.6 Dist-purity check: dist/ references no browser/Node globals (rule: universal-tier purity in the spec delta).
  • 13.7 Dependent sweep: run bunx turbo ls --filter=...@suaveplan/audio-sprite-player and confirm the proposal's ### Affected dependents list still matches (see §14a).
  • 13.8 Numbered requirement-checklist (built from the spec delta) is fully ticked.

14. Fold into canonical spec

  • 14.1 Fold openspec/changes/enterprise-audio-sprite-player/specs/audio-sprite-player/spec.md into openspec/specs/audio-sprite-player/spec.md. This is the first time anything is created under openspec/specs/audio-sprite-player/.

15. Archive

  • 15.1 git mv openspec/changes/enterprise-audio-sprite-player openspec/archive/2026-05-15-enterprise-audio-sprite-player.
  • 15.2 Commit the archival move separately from the implementation commit so the diff is auditable.

16. Follow-up notes (NOT in scope for this change)

The following work is explicitly out of scope and must each open its own OpenSpec change before any code is authored:

  • @suaveplan/audio-engine-browser — add BrowserSpritePlaybackAdapter built on AudioContext + decodeAudioData. Its proposal MUST list @suaveplan/audio-sprite-player as an affected dependent and rerun this package's tests.
  • @suaveplan/audio-engine-server — add ServerSpritePlaybackAdapter built on a native binding (e.g. node-speaker, @suaveplan/audio-codecs decode + RtAudio). Same dependent-listing rule applies.
  • Priority-based voice-stealing (consumer-driven; opens a separate change against this package if/when needed).

Do NOT add either engine adapter inside this change. The universal package ships with NullSpritePlaybackAdapter only; engines provide real adapters in follow-up changes.