Files
suaveplan/openspec/changes/compress-svg-completion/tasks.md
2026-05-27 23:29:53 +01:00

145 lines
12 KiB
Markdown

# Tasks — Compress-SVG completion
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick. The seven gaps closed here were never claimed by an earlier change; this is the first ticket that touches them.
## 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.
## Audit Note (2026-05-02)
Status: **DEFERRED**. `packages/docs/compress-svg/src/` only contains `__benchmarks__`, `constants`, `core`, `parser`, `plugins`, `presets`, `types` plus `error-codes.ts`. None of the seven gaps are filled: no `optimizer/` (SvgOptimizer), no `structural/`, no `deduplicator/`, no `path-tokenizer/`, no `archive/` (SvgPacker / SvgUnpacker / SvgArchiveBuilder), no `browser/` unpacker, no `cli.ts` bin entry. This is a substantial migration estimated at ~6 000+ source lines verbatim; 0% is done.
## 1. Spec authoring
- [ ] 1.1 Read `../suaveplan/packages/universal/compress-svg/src` end-to-end (no dedicated `enterprise/compress-svg.md` exists)
- [ ] 1.2 Read `openspec/specs/compress-svg/spec.md` (does not exist yet — create on archive)
- [ ] 1.3 Read `openspec/AGENTS.md` Sections 0a (parity), 13 (verification), 14 (archival), 14a (affected dependents)
- [ ] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md`
- [ ] 1.5 Fill in `## Source mapping` table — every requirement cites a line range OR appears in the "no source line" table with justification
- [ ] 1.6 Draft `specs/compress-svg/spec.md` from `openspec/templates/spec-delta.md` covering all seven ADDED Requirements
- [ ] 1.7 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
- [ ] 1.8 `openspec change validate compress-svg-completion` exits with no errors
## 2. `SvgOptimizer` façade
- [ ] 2.1 Port `optimizer/SvgOptimizer.ts` (L1-L483) and `optimizer/SvgOptimizer.types.ts` into `packages/docs/compress-svg/src/optimizer/svg-optimizer.ts`
- [ ] 2.2 Define `CompressorAdapter` interface and ship a Node adapter (binds `zlib`) and a browser adapter (binds `CompressionStream`)
- [ ] 2.3 Wire the optimizer to the existing plugin pipeline (`core/compress-svg`); accept a `SvgOptimizerConfig` selecting plugins, presets, and the dictionary/structural/dedup pre-passes
- [ ] 2.4 Emit OTel histogram `compress_svg.optimizer.duration_ms` and counter `compress_svg.optimizer.bytes_saved`
- [ ] 2.5 Throw `COMPRESS_SVG.OPTIMIZER_PIPELINE_FAILED` when any selected plugin throws; chain the cause
- [ ] 2.6 Sibling `.md` (≥200 words, basic + advanced examples)
## 3. `SvgStructuralOptimizer`
- [ ] 3.1 Port `structural/SvgStructuralOptimizer.ts` (L1-L458) and its `.types.ts` into `packages/docs/compress-svg/src/structural/svg-structural-optimizer.ts`
- [ ] 3.2 Implement four rules: flatten single-child `<g>`; collapse identical attribute sets; hoist common attributes to a parent `<g>`; merge consecutive `<path>` elements with identical `fill`/`stroke`/`style`
- [ ] 3.3 Return a `StructuralOptimizationResult` with per-rule counts; expose each rule as a configurable boolean on `StructuralOptimizerConfig`
- [ ] 3.4 Throw `COMPRESS_SVG.STRUCTURAL_INVALID_TREE` on parser-rejectable input
- [ ] 3.5 Sibling `.md`
## 4. `SvgDeduplicator`
- [ ] 4.1 Port `deduplicator/SvgDeduplicator.ts` (L1-L398) into `packages/docs/compress-svg/src/deduplicator/svg-deduplicator.ts`
- [ ] 4.2 Replace `node:crypto.createHash('sha256')` with `@suaveplan/crypto-hash` `sha256` for browser/server parity
- [ ] 4.3 Hoist a `<defs>` block at the document root with `<symbol id="dedup-{shortHash}">` for each duplicate; replace duplicates with `<use href="#dedup-{shortHash}"/>`
- [ ] 4.4 Return a `DeduplicationReport` (`candidatesScanned`, `duplicatesFound`, `bytesSaved`, `symbolMap`)
- [ ] 4.5 Throw `COMPRESS_SVG.DEDUPE_HASH_COLLISION` when two distinct subtrees produce the same short hash (extremely rare; guarded for safety)
- [ ] 4.6 Sibling `.md`
## 5. `PathTokenizer`
- [ ] 5.1 Port `PathTokenizer.ts` (L1-L559) into `packages/docs/compress-svg/src/path-tokenizer/path-tokenizer.ts`
- [ ] 5.2 Export `tokenizePath(d): PathToken[]` and `formatTokens(tokens: PathToken[]): string`; round-trip property test on 10 000 random valid paths
- [ ] 5.3 Throw `COMPRESS_SVG.PATH_TOKEN_PARSE_FAILED` on malformed `d` strings; carry the byte offset of the first invalid character
- [ ] 5.4 Sibling `.md`
## 6. Binary archive (`SvgPacker` / `SvgUnpacker`)
- [ ] 6.1 Port `SvgPacker.ts` (L1-L2931) into `packages/docs/compress-svg/src/archive/svg-packer.ts`
- [ ] 6.2 Port `archive/SvgArchiveBuilder.ts` (L1-L177) and `archive/SvgArchiveUnpacker.ts` (L1-L182) into `packages/docs/compress-svg/src/archive/svg-archive-{builder,unpacker}.ts`
- [ ] 6.3 Preserve the legacy on-disk layout byte-for-byte (version header, manifest table, content-addressed payload table, checksum trailer)
- [ ] 6.4 Throw `COMPRESS_SVG.ARCHIVE_VERSION_MISMATCH`, `COMPRESS_SVG.ARCHIVE_CHECKSUM_FAILED`, `COMPRESS_SVG.ARCHIVE_PAYLOAD_NOT_FOUND` at the right call sites
- [ ] 6.5 Round-trip property test: pack a directory of N SVGs → unpack → byte-equal originals
- [ ] 6.6 Sibling `.md` for each implementation file
## 7. Browser unpacker
- [ ] 7.1 Port `browser/SvgArchiveUnpackerBrowser.ts` (L1-L440) and `browser/SvgUnpackerBrowser.ts` (L1-L1152) into `packages/docs/compress-svg/src/browser/`
- [ ] 7.2 Replace every `Buffer`, `node:fs`, `node:zlib`, `node:crypto` import with Web-Streams / `globalThis.crypto` equivalents
- [ ] 7.3 Static import scan in test asserts no Node-only API on the `./browser` subpath
- [ ] 7.4 Throw `COMPRESS_SVG.BROWSER_UNSUPPORTED_ENVIRONMENT` when `globalThis.crypto?.subtle` or `CompressionStream` is missing
- [ ] 7.5 Sibling `.md` for each implementation file
## 8. CLI bin
- [ ] 8.1 Port `cli.ts` (L1-L271) into `packages/docs/compress-svg/src/cli/cli.ts`
- [ ] 8.2 Wire `bin` field in `package.json`: `"suaveplan-svg-optimize": "./dist/cli/cli.js"`
- [ ] 8.3 Build the CLI atop `@suaveplan/cli` (argument parser, command router, output formatter, spinner, JSON reporter)
- [ ] 8.4 Flags: `--in <glob>`, `--out <dir>`, `--preset <name>`, `--archive <path>`, `--dedupe`, `--structural`, `--dry-run`, `--quiet`, `--json`
- [ ] 8.5 Throw `COMPRESS_SVG.CLI_INVALID_GLOB` when no files match `--in`; exit code 2 (POSIX EX_USAGE)
- [ ] 8.6 Sibling `.md`
## 9. Error codes
- [ ] 9.1 Register `COMPRESS_SVG.*` codes in `error-codes.ts`: `OPTIMIZER_PIPELINE_FAILED`, `STRUCTURAL_INVALID_TREE`, `DEDUPE_HASH_COLLISION`, `PATH_TOKEN_PARSE_FAILED`, `ARCHIVE_VERSION_MISMATCH`, `ARCHIVE_CHECKSUM_FAILED`, `ARCHIVE_PAYLOAD_NOT_FOUND`, `BROWSER_UNSUPPORTED_ENVIRONMENT`, `CLI_INVALID_GLOB`
- [ ] 9.2 Every code thrown by at least one production path; no orphan codes
## 10. Subpath exports
- [ ] 10.1 `package.json` `exports` map gains `./optimizer`, `./structural`, `./deduplicator`, `./path-tokenizer`, `./archive`, `./browser`, `./cli`
- [ ] 10.2 Each subpath is tree-shakeable (`"sideEffects": false` already set; verify by build artefact size)
- [ ] 10.3 `tests/dep-graph.test.ts` pins the subpath surface
## 11. Tests
- [ ] 11.1 `src/optimizer/svg-optimizer.test.ts` — pipeline orchestration, plugin selection, `OptimizationResult` shape, batch mode
- [ ] 11.2 `src/optimizer/svg-optimizer.spec-conformance.test.ts` — injectable `CompressorAdapter` round-trip; pipeline-failed error code; OTel histogram emitted
- [ ] 11.3 `src/structural/svg-structural-optimizer.test.ts` — every rule observable; per-rule counter accuracy
- [ ] 11.4 `src/structural/svg-structural-optimizer.spec-conformance.test.ts` — invalid-tree error code
- [ ] 11.5 `src/deduplicator/svg-deduplicator.test.ts` — cross-file dedup, `<symbol>`/`<use>` emission
- [ ] 11.6 `src/deduplicator/svg-deduplicator.spec-conformance.test.ts` — `DeduplicationReport` shape; injected hash honoured; collision error code
- [ ] 11.7 `src/path-tokenizer/path-tokenizer.test.ts` — every SVG path command; round-trip property test on 10 000 random paths
- [ ] 11.8 `src/path-tokenizer/path-tokenizer.spec-conformance.test.ts` — malformed-path error code; absolute/relative flag preserved
- [ ] 11.9 `src/archive/svg-packer.test.ts` — binary header, manifest, payload table, content-addressed dedup, version round-trip
- [ ] 11.10 `src/archive/svg-packer.spec-conformance.test.ts` — version-mismatch / checksum-failed / payload-not-found error codes
- [ ] 11.11 `src/browser/svg-unpacker-browser.test.ts` — browser unpacker round-trip with the same fixtures as the Node unpacker
- [ ] 11.12 `src/browser/svg-unpacker-browser.spec-conformance.test.ts` — static-import scan asserts no Node API; missing-`globalThis.crypto` error path
- [ ] 11.13 `src/cli/cli.test.ts` — argument parsing, glob expansion, in-place vs out-dir vs archive modes, dry-run, JSON reporter
- [ ] 11.14 `src/cli/cli.spec-conformance.test.ts` — invalid-glob error code; POSIX exit codes
- [ ] 11.15 `tests/dep-graph.test.ts` — subpath exports resolve, no Node API on `./browser`
- [ ] 11.16 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for archive round-trip tests)
## 12. Documentation
- [ ] 12.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference)
- [ ] 12.2 README links every co-located `.md` under `## Modules`
- [ ] 12.3 Update `CLAUDE.md` `compress-svg` package listing to reflect the restored surface
## 13. Pre-archive verification (the 5-gate triple)
- [ ] 13.1 `bun run check` — Biome 0/0/0 across the monorepo
- [ ] 13.2 `bunx turbo lint typecheck test --filter=@suaveplan/compress-svg` — green
- [ ] 13.3 `bunx turbo test:coverage --filter=@suaveplan/compress-svg` — 100/100/100/100
- [ ] 13.4 `bun scripts/openspec-conformance.ts @suaveplan/compress-svg` — exit 0, no `error` findings
- [ ] 13.5 `bunx turbo build --filter=@suaveplan/compress-svg` — green
## 14. Pre-archive 7-row checklist (AGENTS.md Section 13)
- [ ] 14.1 Generics threaded; `expectTypeOf` assertions on `OptimizationResult`, `DeduplicationReport`, `StructuralOptimizationResult`, `PathToken[]`
- [ ] 14.2 Zod schemas wired at every declared boundary (`SvgOptimizerConfig`, `StructuralOptimizerConfig`, `DeduplicationConfig`, `SvgArchiveManifest`, CLI args)
- [ ] 14.3 Every `COMPRESS_SVG.*` code thrown by at least one production path; no orphans
- [ ] 14.4 Strict-mode behaviors tested both on and off (e.g. `dedupe: true` vs `false`; archive with vs without checksum)
- [ ] 14.5 OTel histograms / counters asserted with a real in-memory `MeterProvider`
- [ ] 14.6 Lifecycle dedup tested where claimed: `SvgDeduplicator` idempotent on identical input; `SvgPacker` archive output stable byte-for-byte across runs (deterministic)
- [ ] 14.7 No retroactive tick — every box above reflects work done in this change
## 15. Archive
- [ ] 15.1 Fold spec delta into `openspec/specs/compress-svg/spec.md` (creating the file)
- [ ] 15.2 Move `openspec/changes/compress-svg-completion/` → `openspec/archive/<yyyy-mm-dd>-compress-svg-completion/`
- [ ] 15.3 Refresh `MANIFEST.md` if the package's exports list materially changed
- [ ] 15.4 Update `STATUS.md` if the package was previously listed as Pending