# Design — audio-shipped-file-size-cleanup This change is a pure mechanical refactor across three packages. The design is the **split strategy** per file: every public symbol stays exported from its current module path; the splits happen *below* the public surface. --- ## 1. `@suaveplan/audio-timeline` — `engine/engine.ts` (548 → ≤300) `TimelineEngine` is the single class in this file and currently owns: - Construction + adapter wiring (`setAdapter`, `dispose`) - Track CRUD (`addTrack`, `removeTrack`, `updateTrack`, `setTracks`) - Clip CRUD (`addClip`, `removeClip`, `updateClip`, `splitClip`, `joinClip`) - Mixer mutations (`setMasterVolume`, `setTrackVolume`, `setTrackMute`, `setTrackSolo`, `setTrackPan`) - Transport (`play`, `pause`, `stop`, `seek`, `setLoop`, `setTempo`) - Snapshot / restore + undo/redo dispatch - Event emission ### Proposed split Keep `TimelineEngine` in `engine.ts` as the public class, but extract helper modules called from its methods: ```text src/engine/ engine.ts ← class shell + delegating methods (≤300) engine-tracks.ts ← addTrack/removeTrack/updateTrack/setTracks helpers engine-tracks.md engine-clips.ts ← addClip/removeClip/updateClip/splitClip/joinClip helpers engine-clips.md engine-mixer.ts ← setMasterVolume/setTrackVolume/Mute/Solo/Pan helpers engine-mixer.md engine-transport.ts ← play/pause/stop/seek/setLoop/setTempo helpers engine-transport.md engine.test.ts ← class-level invariants only (≤500) engine-tracks.test.ts ← extracted track tests engine-clips.test.ts ← extracted clip tests engine-mixer.test.ts ← extracted mixer tests engine-transport.test.ts ← extracted transport tests ``` Each helper module exports pure functions of `(state, args) => newState` (where `state` is `TimelineEngine`'s internal record) plus any side-effect-free derived values. `TimelineEngine` methods become 1–3 liners that call the helper and forward the result. This pattern matches how `audio-core`'s `process/` directory is structured. The original `engine.test.ts` splits along the same four axes; the residual `engine.test.ts` keeps construction, adapter wiring, snapshot/restore, undo/redo dispatch, and event emission tests — the cross-cutting concerns no single helper owns. --- ## 2. `@suaveplan/audio-transport` — `transport/transport.ts` (419 → ≤300) `Transport` is the single class and owns: - Construction + clock injection - BPM + time-signature + PPQ state - Tick advance + lookahead scheduling loop - Schedule / cancel API - Loop region + swing - Tempo automation ### Proposed split ```text src/transport/ transport.ts ← class shell (≤300) transport-schedule.ts ← schedule/cancel/lookahead helpers transport-schedule.md transport-tempo.ts ← BPM + tempo automation + tick conversion helpers transport-tempo.md transport.test.ts ← stays as-is (already under cap) ``` No test splitting needed — `transport.test.ts` is already within the 500-line cap. --- ## 3. `@suaveplan/audio-metadata` — `parsers/id3v2/id3v2.ts` (536 → ≤300) `id3v2.ts` is the most over-budget src file. It owns: - Magic-byte detection - Header + extended-header parsing - Frame iteration - Per-frame-type decoders (TXXX, T..., COMM, USLT, APIC, UFID, WCOM, WCOP, …) - Text-encoding decoders (ISO-8859-1, UTF-16, UTF-16BE, UTF-8) - Unsynchronisation reversal ### Proposed split ```text src/parsers/id3v2/ id3v2.ts ← public parseId3v2() + frame dispatch (≤300) id3v2-header.ts ← header + extended-header parsing id3v2-header.md id3v2-frames-text.ts ← T... / TXXX text frame decoders id3v2-frames-text.md id3v2-frames-binary.ts ← APIC / UFID / GEOB binary frame decoders id3v2-frames-binary.md id3v2-frames-url.ts ← W... URL frame decoders id3v2-frames-url.md id3v2-text-encoding.ts ← ISO-8859-1 / UTF-16 / UTF-16BE / UTF-8 decoders id3v2-text-encoding.md id3v2-unsync.ts ← unsynchronisation reversal id3v2-unsync.md id3v2.test.ts ← integration parses on real fixture files (≤500) id3v2-header.test.ts ← header-specific id3v2-frames-text.test.ts ← text-frame-specific id3v2-frames-binary.test.ts ← binary-frame-specific id3v2-frames-url.test.ts ← url-frame-specific id3v2-text-encoding.test.ts ← encoding-specific id3v2-unsync.test.ts ← unsync-specific ``` The split mirrors the spec sections of the ID3v2.4 standard, so the file boundaries match how future maintainers will reason about the format. ### `id3v2.test.ts` (1340 → 7 files of ≤500 each) The current `id3v2.test.ts` mixes header tests, every frame type, encoding round-trips, and integration parses. Splitting by frame family (per the source split above) gives ~150–200 lines per file. The residual `id3v2.test.ts` keeps the end-to-end "parse a real fixture file" cases. --- ## 4. `@suaveplan/audio-metadata` — `parsers/mp4-atom/mp4-atom.ts` (351 → ≤300) Lightest split. The file owns atom-walking + per-atom decoders (`ftyp`, `moov`, `mvhd`, `mdat`, `ilst` and the iTunes metadata sub-atoms). Extract the iTunes-specific decoders: ```text src/parsers/mp4-atom/ mp4-atom.ts ← public parseMp4Atom() + container walker (≤300) mp4-atom-itunes.ts ← iTunes/ilst metadata decoders mp4-atom-itunes.md mp4-atom.test.ts ← container + non-iTunes tests (≤500) mp4-atom-itunes.test.ts ← iTunes-specific ``` --- ## 5. `@suaveplan/audio-metadata` — `parsers/ogg-vorbis/ogg-vorbis.test.ts` (803 → ≤500) Source file is already within cap. Only the test file needs splitting: ```text src/parsers/ogg-vorbis/ ogg-vorbis.ts ← unchanged ogg-vorbis.test.ts ← page parsing + comment header (≤500) ogg-vorbis-frames.test.ts ← extracted frame-level tests ``` --- ## 6. Cross-cutting rules for the split - **Public surface is invariant.** Every existing `export` from `/src/index.ts` must continue to resolve to the same symbol with the same signature. The split is *internal*. - **Subpath exports unchanged.** The `package.json` `exports` map for each package keeps every existing entry. New internal helper modules are NOT added to `exports` — they are implementation details. - **Barrels use explicit named re-exports** (§6a). No `export * from` in new feature barrels. - **Sibling `.md` files** added for every new `.ts` per §4 (200-word minimum, six required sections). - **Coverage stays 100/100/100/100** per `vitest.config.ts` thresholds. - **No new behavior.** A diff of `dist/*.d.ts` must show only path changes — no type-shape changes. ## 7. Risk Low. The packages have no published consumers; the refactor is mechanical; the tests are the contract and they all must continue to pass. The largest file (`id3v2.test.ts` at 1340 lines) is the highest-leverage win: shrinking it makes the format-spec correspondence explicit and gives future maintainers per-frame-family focus. The one non-mechanical risk is in `audio-timeline/engine.ts` — extracting helper modules implies extracting a `TimelineEngineState` shape, which is currently inlined as private class fields. The helpers operate on that shape; if the extraction inadvertently widens the visibility of private state, the public surface widens. Mitigation: keep `TimelineEngineState` as a non-exported type in `engine-state.ts` (or inline in `engine.ts`) and pass it explicitly to helpers; do not export it.