# Compress-SVG completion **Package**: `@suaveplan/compress-svg` **Status**: PROPOSED **Type**: spec-conformance + feature ## Why The genesis port of `@suaveplan/compress-svg` ships only the per-pass SVGO-style plugin core (`parser/svg`, `core/compress-svg`, `plugins/*`, `presets`, `types/common`) and a small set of in-place optimisation passes (cleanup-attrs, collapse-groups, convert-path-data, convert-transforms, mangle-ids, remove-comments, remove-doctype, remove-metadata). The legacy package at `../suaveplan/packages/universal/compress-svg/src` ships a substantially larger surface that is required by downstream tooling: a high-level `SvgOptimizer` façade, a structural-flattening pass (`SvgStructuralOptimizer`), cross-file content-addressed deduplication (`SvgDeduplicator`), a standalone `PathTokenizer` module, a CLI bin (`suaveplan-svg-optimize`), a binary archive format (`SvgPacker` + `SvgUnpacker` + `SvgArchiveBuilder` + `SvgArchiveUnpacker` totalling ~3 472 lines), and a browser unpacker (`SvgUnpackerBrowser` + `ArchiveUnpacker` ~1 592 lines) that consumes the archive without Node.js APIs. Genesis policy is "no half-baked or mediocre libraries" (CLAUDE.md §Workflow rule 3) and "Preserve the full public surface during rewrites … never trim on 'probably unused' grounds" (CLAUDE.md §Workflow rule 5). The audit on 2026-05-02 confirmed every one of the seven gaps below by file-listing diff between `/home/ubuntu/web/suaveplan/packages/universal/compress-svg/src` and `/home/ubuntu/web/genesis/packages/docs/compress-svg/src`. This change closes those gaps in one coordinated pass so the package matches its upstream contract before any consumer wires it up. ## What changes ### `@suaveplan/compress-svg` - ADDED: `SvgOptimizer` class — orchestrator that runs the configured plugin pipeline and surfaces a typed `OptimizationResult` (`{ originalBytes, optimizedBytes, pluginsApplied, warnings, durationMs, ratio }`); accepts a `SvgOptimizerConfig` selecting plugins, presets, and the dictionary/structural/dedup pre-passes; exposes `optimize(svg)` and `optimizeBatch(svgs)`. - ADDED: `SvgStructuralOptimizer` class — flatten redundant `` wrappers (single-child groups with no styling), collapse identical attribute sets, hoist common attributes to a parent ``, merge consecutive `` elements with identical fill/stroke/style, returns a `StructuralOptimizationResult` with per-rule counts. - ADDED: `SvgDeduplicator` class — cross-file content-addressed deduplication; SHA-256 hashes the canonicalised inner content of repeating subtrees; emits `` definitions in a hoisted `` and replaces duplicates with ``; returns a `DeduplicationReport` (`{ candidatesScanned, duplicatesFound, bytesSaved, symbolMap }`). - ADDED: `tokenizePath(d)` and `formatTokens(tokens)` — standalone path tokeniser that emits a typed `PathToken[]` stream (`{ command, args, absolute }`) and round-trips back to the original `d` string. - ADDED: `SvgPacker` and `SvgUnpacker` — binary archive format with version header, `SvgArchiveManifest`, and content-addressed payload table; pack/unpack by string SVG name → bytes; archive layout matches legacy on-disk format byte-for-byte for cross-tool compatibility. - ADDED: `ArchiveUnpacker` and `SvgUnpackerBrowser` — browser-only unpackers that read the binary archive without Node.js APIs (`Buffer`, `fs`, `zlib`); use Web Streams and `globalThis.crypto`; ship under the `./browser` subpath export. - ADDED: `bin/suaveplan-svg-optimize` — CLI command that takes glob patterns and writes optimised SVGs (in-place, to a directory, or to a binary archive); flags: `--in`, `--out`, `--preset`, `--archive`, `--dedupe`, `--structural`, `--dry-run`, `--quiet`, `--json`. - ADDED: subpath exports `./optimizer`, `./structural`, `./deduplicator`, `./path-tokenizer`, `./archive`, `./browser`, `./cli`. - ADDED: error codes registered on `@suaveplan/error` under `COMPRESS_SVG.*`: `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`. - MODIFIED: `package.json` `bin` field — adds `suaveplan-svg-optimize`. `exports` map gains the seven new subpaths. ## Out of scope - **Server-side rasterisation to PNG/JPG** — belongs in `@suaveplan/compress-image`; rasterisation requires a different dependency surface (`sharp` adapter or canvas) and the SVG package must remain a pure-string transformer. - **Sprite-sheet generation** — separate concern (atlas packing, layout, viewBox calc); deferred to a future `@suaveplan/svg-sprite` package if needed. - **Lossy colour quantisation beyond the existing plugin set** — the existing `cleanup-attrs` + `convert-path-data` passes already handle precision; introducing palette quantisation would require a colour-distance algorithm and is not what callers ask for. - **Drop-in SVGO-plugin compatibility** — the genesis plugin model is intentionally simpler than upstream SVGO; mapping arbitrary SVGO plugins is its own change. ## Source mapping This change derives from `../suaveplan/packages/universal/compress-svg/src`. There is no dedicated `enterprise/compress-svg.md` audit doc upstream, so the source mapping cites file:line ranges directly. | Requirement | Source file | Source lines | Notes | |---|---|---|---| | `SvgOptimizer` façade | `optimizer/SvgOptimizer.ts` | L1-L483 | Verbatim port; replace Node `zlib` with injectable `CompressorAdapter` for browser-safe build | | `OptimizationResult` and `SvgDictionary` types | `optimizer/SvgOptimizer.types.ts` | L1-L27 | Verbatim | | `SvgStructuralOptimizer` | `structural/SvgStructuralOptimizer.ts` | L1-L458 | Verbatim | | `SvgStructuralOptimizer` types | `structural/SvgStructuralOptimizer.types.ts` | (full file) | Verbatim | | `SvgDeduplicator` (cross-file dedup, `` references, `DeduplicationReport`) | `deduplicator/SvgDeduplicator.ts` | L1-L398 | Verbatim port; replace `crypto.createHash('sha256')` with `@suaveplan/crypto-hash` `sha256` for browser/server parity | | `tokenizePath` / `formatTokens` / `PathToken` | `PathTokenizer.ts` | L1-L559 | Verbatim | | `SvgPacker` / `SvgUnpacker` (binary archive) | `SvgPacker.ts` | L1-L2931 | Verbatim port; the on-disk layout matches legacy byte-for-byte for cross-tool compatibility | | `SvgArchiveBuilder` | `archive/SvgArchiveBuilder.ts` | L1-L177 | Verbatim | | `SvgArchiveUnpacker` | `archive/SvgArchiveUnpacker.ts` | L1-L182 | Verbatim | | `ArchiveUnpacker` (browser) | `browser/SvgArchiveUnpackerBrowser.ts` | L1-L440 | Verbatim | | `SvgUnpackerBrowser` | `browser/SvgUnpackerBrowser.ts` | L1-L1152 | Verbatim | | `suaveplan-svg-optimize` CLI bin | `cli.ts` | L1-L271 | Verbatim | ### Requirements with no source line | Requirement | Justification | |---|---| | `COMPRESS_SVG.*` error codes registered on `@suaveplan/error` | Genesis-wide policy that every thrown error route through `@suaveplan/error` (CLAUDE.md §Workflow rule 2). Upstream throws plain `Error`. | | Replace Node `zlib` with injectable `CompressorAdapter` in `SvgOptimizer` | Genesis ships universal-tier packages where browser tier matters; upstream binds to Node `zlib` directly. The adapter keeps the API identical while enabling the `./browser` subpath build. | | Replace `node:crypto` `createHash` with `@suaveplan/crypto-hash` `sha256` in `SvgDeduplicator` | Same browser/server parity reason; keeps a single SHA-256 implementation across the monorepo. | | `./optimizer`, `./structural`, `./deduplicator`, `./path-tokenizer`, `./archive`, `./browser`, `./cli` subpath exports | Genesis-wide subpath-per-feature rule (AGENTS.md §6). Upstream uses a single barrel; tree-shaking parity demands subpath splits. | ## Verification approach Each spec delta requirement carries a `**Verified by:**` line pointing at concrete `it(...)` titles. The test files that will exist after this change ships: - `packages/docs/compress-svg/src/optimizer/svg-optimizer.test.ts` — pipeline orchestration, plugin selection, `OptimizationResult` shape, batch mode - `packages/docs/compress-svg/src/optimizer/svg-optimizer.spec-conformance.test.ts` — injectable `CompressorAdapter` round-trip; pipeline-failed error code; OTel histogram - `packages/docs/compress-svg/src/structural/svg-structural-optimizer.test.ts` — flatten single-child ``; collapse identical attribute sets; hoist common attrs; merge consecutive paths - `packages/docs/compress-svg/src/structural/svg-structural-optimizer.spec-conformance.test.ts` — invalid-tree error code; per-rule counter accuracy - `packages/docs/compress-svg/src/deduplicator/svg-deduplicator.test.ts` — cross-file dedup, ``/`` emission, hash-collision rejection - `packages/docs/compress-svg/src/deduplicator/svg-deduplicator.spec-conformance.test.ts` — `DeduplicationReport` shape; injected hash function honoured; collision error code - `packages/docs/compress-svg/src/path-tokenizer/path-tokenizer.test.ts` — every SVG path command (`MmLlHhVvCcSsQqTtAaZz`); round-trip property test - `packages/docs/compress-svg/src/path-tokenizer/path-tokenizer.spec-conformance.test.ts` — malformed-path error code; absolute/relative flag preserved - `packages/docs/compress-svg/src/archive/svg-packer.test.ts` — binary header, manifest, payload table, content-addressed dedup, version round-trip - `packages/docs/compress-svg/src/archive/svg-packer.spec-conformance.test.ts` — version-mismatch error; checksum-failed error; payload-not-found error - `packages/docs/compress-svg/src/browser/svg-unpacker-browser.test.ts` — browser unpacker round-trip with the same fixtures as `svg-packer.test.ts` - `packages/docs/compress-svg/src/browser/svg-unpacker-browser.spec-conformance.test.ts` — Node `Buffer`/`fs`/`zlib` not referenced (static import scan); `BROWSER_UNSUPPORTED_ENVIRONMENT` thrown when `globalThis.crypto` is missing - `packages/docs/compress-svg/src/cli/cli.test.ts` — argument parsing, glob expansion, in-place vs out-dir vs archive modes, dry-run, JSON reporter - `packages/docs/compress-svg/src/cli/cli.spec-conformance.test.ts` — invalid-glob error code; exit codes match POSIX conventions - `tests/dep-graph.test.ts` — no Node.js API import on the `./browser` subpath; subpath exports resolve ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/compress-svg` to enumerate. Result of `grep -rl "@suaveplan/compress-svg" /home/ubuntu/web/genesis/packages/ --include="package.json"` on 2026-05-02 returned only the package itself (`packages/docs/compress-svg/package.json`). No in-repo dependents. - No dependents (verified by `grep -rl "@suaveplan/compress-svg" /home/ubuntu/web/genesis/packages/ --include="package.json"` returning only the package's own `package.json`). This change is **additive only** — every existing export is preserved, and the new exports land under new subpaths (`./optimizer`, `./structural`, `./deduplicator`, `./path-tokenizer`, `./archive`, `./browser`, `./cli`) plus root-level re-exports. The CLI bin is a new entry; existing users who depend on the per-pass plugin core continue to compile unchanged.