Skip to content

Commit 8783fd6

Browse files
authored
Merge pull request #282 from naomiaro/feature/engine-extraction
feat: add @waveform-playlist/engine package
2 parents 032278d + 2282a16 commit 8783fd6

18 files changed

+4138
-6
lines changed

CLAUDE.MD

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pnpm publish --filter './packages/*' --no-git-checks
5555
npm install @waveform-playlist/browser
5656
```
5757

58-
**Version Bumping:** All 11 `package.json` files (root + 10 packages) must be bumped in sync:
58+
**Version Bumping:** All 12 `package.json` files (root + 11 packages) must be bumped in sync:
5959
```bash
6060
sed -i '' 's/"version": "OLD"/"version": "NEW"/g' package.json packages/*/package.json
6161
```
@@ -159,8 +159,9 @@ pnpm publish --filter @waveform-playlist/NEW-PACKAGE --no-git-checks --access pu
159159

160160
- **Build packages**: `pnpm build` - Build all packages
161161
- **TypeScript check**: `pnpm typecheck` (enforced in build scripts)
162-
- **Lint**: `pnpm lint` - ESLint across all packages. **Always run before committing.**
162+
- **Lint**: `pnpm lint` - ESLint across all packages. **Always run before committing.** This is a root-only script; run from repo root or use `pnpm -w lint`.
163163
- **Dev server**: `pnpm --filter website start` - Docusaurus dev server
164+
- **Engine unit tests**: `cd packages/engine && npx vitest run` — vitest (not Playwright)
164165
- **Hard refresh**: Always use Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux) after builds
165166

166167
**pnpm Build Ordering:** `pnpm recursive run` determines build order from `dependencies` and `devDependencies` only — **not** `peerDependencies`. If package A needs package B's types at build time (e.g., for DTS generation), B must be in A's `devDependencies` even if it's already a `peerDependency`. Without this, CI builds fail because packages build in parallel/alphabetical order.
@@ -183,6 +184,30 @@ pnpm publish --filter @waveform-playlist/NEW-PACKAGE --no-git-checks --access pu
183184

184185
**Git Safety:** Always make intermediate commits before running `git stash` or switching branches. A failed `git stash pop` + `git checkout -- .` can destroy all uncommitted work permanently.
185186

187+
### Engine Package (`@waveform-playlist/engine`)
188+
189+
**Purpose:** Framework-agnostic timeline engine extracted from React hooks. Enables Svelte/Vue/vanilla bindings.
190+
191+
**Architecture:** Two layers — pure operations functions + stateful `PlaylistEngine` class with event emitter.
192+
193+
**Build:** Uses tsup (not vite) — `pnpm typecheck && tsup`. Outputs ESM + CJS + DTS.
194+
195+
**Testing:** vitest unit tests in `src/__tests__/`. Run with `npx vitest run` from `packages/engine/`.
196+
197+
**Key types:** `PlayoutAdapter` (pluggable audio backend interface), `EngineState` (state snapshot), `EngineEvents` (statechange, timeupdate, play/pause/stop).
198+
199+
**Operations:** `clipOperations.ts` (drag constraints, trim, split), `viewportOperations.ts` (bounds, chunks, scroll threshold), `timelineOperations.ts` (duration, zoom, seek).
200+
201+
**No React, no Tone.js** — zero framework dependencies. Only peer dependency is `@waveform-playlist/core`.
202+
203+
**Design doc:** `docs/plans/2026-02-24-engine-extraction-design.md`
204+
205+
**Patterns:**
206+
- All mutating methods (moveClip, trimClip, removeTrack, setZoomLevel) guard against no-op statechange emissions — bail early when constrained delta is 0, track not found, or zoom unchanged
207+
- `setTracks()` copies input array; `getState()` copies output tracks — defensive at both boundaries
208+
- `PlayoutAdapter.isPlaying()` is defined but not called by engine (engine tracks own `_isPlaying`). Known design gap.
209+
- Engine uses `seek()` while browser package uses `seekTo()` — naming divergence, noted in "Common Doc Drift"
210+
186211
---
187212

188213
## Architectural Decisions
@@ -642,6 +667,7 @@ const analyser = context.createAnalyser();
642667
21. **Canvas Cleanup on Chunk Changes** - `useChunkedCanvasRefs` runs cleanup on every render (no dependency array) because the virtualizer can unmount canvases between any render. SpectrogramChannel's worker registration effect uses `visibleChunkIndices` as a dependency so it re-runs when chunks mount/unmount, cleaning stale registrations and transferring new canvases in a single pass.
643668
22. **Virtual Scrolling Chunk Offsets** - Canvas registries may contain non-consecutive chunks (e.g., chunks 50-55). Use `extractChunkNumber(canvasId)` to get the real chunk index — never compute offsets by summing widths from array index 0.
644669
23. **Multi-Channel Rendering Fairness** - Render visible chunks for ALL channels before background batches. Sequential per-channel rendering causes channel starvation when generation aborts interrupt background work.
670+
24. **Guard Against No-Op State Emissions** - In stateful classes with event emitters, check if an operation would actually change state before emitting. Zero-delta moves/trims, removing non-existent items, and setting zoom to the same level should bail early to avoid wasted listener calls and UI re-renders.
645671

646672
### Playlist Loading Detection
647673

0 commit comments

Comments
 (0)