9.6 KiB
Split @suaveplan/font-compress into compress + decompress packages
Packages: @suaveplan/font-compress (modified), @suaveplan/font-decompress (new)
Status: PROPOSED
Type: refactor / package-split
- Design: ./design.md
- Spec deltas: ./specs/font-compress/spec.md, ./specs/font-decompress/spec.md
Why
@suaveplan/font-compress currently ships both BinaryFontPacker and BinaryFontUnpacker from one package — pack and unpack are two distinct concerns wedged together in v0.0.0. The pending @suaveplan/font-decompress row in STATUS.md exists because consumers reasonably expect a decompress-only package: build-time tooling packs fonts once and ships them, while runtime renderers (browsers, R3F scenes, headless test environments) only need to decompress. A renderer pulling the full font-compress payload to use one class drags pack-side code, parse-path utilities, and dictionary builders it never executes. Splitting eliminates that.
Genesis has zero monorepo consumers of @suaveplan/font-compress today — verified via grep -rE "from ['\"]@suaveplan/font-compress" packages --include='*.ts' — so the split has no internal blast radius. External consumers (assuming there are any pre-publish) get a small migration: import BinaryFontUnpacker / createBinaryFontUnpacker from @suaveplan/font-decompress instead of @suaveplan/font-compress. The unpacker's public API is preserved bit-for-bit; only the import path changes.
This is also the pattern the no-deferrals memory captures: the existing font-compress package shipped at 0.0.0 with the decompress half folded in, leaving the named font-decompress row pending — a half-completed identity. The split lands BOTH packages at their full functional surface in one OpenSpec change rather than leaving one in STATUS.md ➖.
What changes
@suaveplan/font-decompress (new package at packages/fonts/font-decompress/)
- ADDED: package
@suaveplan/font-decompressv0.0.0, universal-tier,"sideEffects": false. - ADDED:
BinaryFontUnpackerclass +createBinaryFontUnpacker()factory. Public API is bit-identical to the class currently exported by@suaveplan/font-compress— sameunpack(data: Uint8Array): TypefaceFontsignature, same wire-format expectations, sameTypefaceFontreturn shape from@suaveplan/font-convert. - ADDED:
FONT_DECOMPRESS_CODESregistry viadefineCodes("FONT_DECOMPRESS", [...])with codesUNPACK_FAILEDandINVALID_DATA.FontDecompressError extends SuaveplanError<FontDecompressErrorCode>. - ADDED:
decodeString(data, offset)utility — moved fromfont-compress'sutils/encode-string.ts. The encode-side helper stays infont-compress. - ADDED: BTF format constants module (duplicated from
font-compress— see design.md). Both packages share the wire-format constants; duplicating 18 lines of magic numbers avoids a third "fonts-shared" package and keeps each package self-contained. - ADDED: Sibling
.mddocs for every implementation file. - ADDED: Subpath exports —
.,./unpacker/binary-font-unpacker,./error-codes,./utils/decode-string,./constants,./types/index(re-exportsTypefaceFontandPathCommandfrom@suaveplan/font-convertfor caller convenience). - ADDED: Dependencies:
@suaveplan/error,@suaveplan/font-convert,tslib. DevDeps:@suaveplan/testing,vitest,vite,vite-plugin-dts,@vitest/coverage-istanbul,fast-check,typescript,@types/node.
@suaveplan/font-compress (modified, stays at v0.0.0)
- REMOVED:
BinaryFontUnpackerclass andcreateBinaryFontUnpackerfactory from the public surface. Their source files move to@suaveplan/font-decompress. - REMOVED:
src/unpacker/directory in its entirety. - MODIFIED:
src/utils/encode-string.ts—decodeStringmoves to@suaveplan/font-decompress. The file keepsencodeStringonly. - MODIFIED:
FONT_COMPRESS_CODESregistry —UNPACK_FAILEDremoved (no longer thrown by any path in this package).PACK_FAILEDandINVALID_DATAretained. - MODIFIED: Subpath exports —
./unpacker/binary-font-unpackerremoved. - MODIFIED: Public surface declaration in
README.mdandsrc/index.ts. - The packer surface (
BinaryFontPacker,createBinaryFontPacker), packer error codes,parsePathutility,constants/, andFontCompressError(with the trimmed code registry) remain infont-compress.
Cross-cutting
- ADDED: New canonical capability
font-decompressatopenspec/specs/font-decompress/spec.md(created at fold time). - MODIFIED: New canonical capability
font-compressatopenspec/specs/font-compress/spec.md— first canonical spec for the package, capturing the post-split shape. - MODIFIED:
STATUS.md— flip@suaveplan/font-decompressfrom–to✅; update@suaveplan/font-compressdescription to reflect packer-only scope. - MODIFIED:
MANIFEST.md— regenerated viabun scripts/folder-reorg/generate-manifest.ts. Both packages appear;font-compressdep count drops by one (decodeString removed);font-decompresslands with the same dep graph asfont-compressplus tslib.
Out of scope
- WOFF / WOFF2 / EOT decompression. Upstream
@suaveplan/font-decompresswas BTF-format-only (Typeface.js binary), and that's what the package's name has come to mean inside genesis. WOFF/WOFF2 decoding lives in@suaveplan/font-convertalready; consumers compose the two when they need to go from.woff2bytes throughTypefaceFontto a rendered glyph. Adding WOFF/WOFF2 tofont-decompresswould duplicatefont-convert. Reason: orthogonal capability, already covered. - A third
@suaveplan/font-typefacetypes-only package. The sharedTypefaceFontandPathCommandtypes already live in@suaveplan/font-convert; both packages depend on it. Introducing a third package adds a coordinate without enabling anything new. Reason: existing dep already solves the type-sharing problem. - Bumping
@suaveplan/font-compresspast v0.0.0. CLAUDE.md says packages stay at0.1.0until graduation to1.0.0. The split is a v0.0.0-to-v0.0.0 refactor; no version bump, no CHANGELOG entry. External consumers (none in this monorepo) discover the import-path change via the new spec and the npm registry's package listing. Reason: established versioning policy.
Source mapping
Both packages are sourced from the existing packages/fonts/font-compress/ tree. Specifically:
| Source file (current) | Destination |
|---|---|
packages/fonts/font-compress/src/unpacker/binary-font-unpacker/binary-font-unpacker.ts |
packages/fonts/font-decompress/src/unpacker/binary-font-unpacker/binary-font-unpacker.ts |
packages/fonts/font-compress/src/unpacker/binary-font-unpacker/index.ts |
packages/fonts/font-decompress/src/unpacker/binary-font-unpacker/index.ts |
packages/fonts/font-compress/src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts |
packages/fonts/font-decompress/src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts |
packages/fonts/font-compress/src/unpacker/binary-font-unpacker/binary-font-unpacker.md |
packages/fonts/font-decompress/src/unpacker/binary-font-unpacker/binary-font-unpacker.md |
decodeString from src/utils/encode-string.ts |
new packages/fonts/font-decompress/src/utils/decode-string.ts |
src/constants/index.ts (duplicated) |
packages/fonts/font-decompress/src/constants/index.ts (verbatim copy; 19 lines of format magic) |
UNPACK_FAILED and INVALID_DATA from src/error-codes.ts |
new packages/fonts/font-decompress/src/error-codes.ts (own namespace FONT_DECOMPRESS) |
No bit-level changes to algorithm logic. The unpacker's class body, the decodeString helper, and the constants file move verbatim. Imports are updated to point at the new package-local error registry and constants module.
Reference docs consulted
- ✅
packages/fonts/font-compress/src/— full read of every TS file (unpacker, packer, utils, constants, error-codes, index) - ✅
packages/fonts/font-compress/package.json— exports + deps - ✅
../suaveplan/packages/universal/font-decompress/— upstream comparison; confirmed BTF-only scope and verified the upstream class is a stripped-down version of what genesis already ships - ✅
STATUS.mdrows for@suaveplan/font-compressand@suaveplan/font-decompress - ✅ Monorepo grep for
@suaveplan/font-compressconsumers — zero TS importers; only docs reference it
Impact
Affected dependents
None inside the monorepo (verified). External consumers (if any pre-publish) must rewrite imports of BinaryFontUnpacker / createBinaryFontUnpacker / the unpacker/ subpath to point at @suaveplan/font-decompress instead of @suaveplan/font-compress. The class signature and behaviour are unchanged.
Risk
- Low. Pure code-relocation; algorithm logic untouched. The unpacker's existing 100% test coverage carries forward to the new package along with the source files. The packer's tests in
font-compressstay there. New tests verify the moved-in error registry and the trimmed packer-side error registry. - Wire-format constants duplicated. If the BTF format ever changes, both packages need updates in lockstep. Documented in design.md; not a problem in practice because the format is locked at v0.0.0.
Migration notes
Single import-path rewrite for any external consumer of the unpacker:
// Before
import { BinaryFontUnpacker, createBinaryFontUnpacker } from "@suaveplan/font-compress";
// After
import { BinaryFontUnpacker, createBinaryFontUnpacker } from "@suaveplan/font-decompress";
Error code consumers that programmatically dispatch on FONT_COMPRESS.UNPACK_FAILED rewrite to FONT_DECOMPRESS.UNPACK_FAILED. Note the namespace change in addition to the package change.