101 lines
9.6 KiB
Markdown
101 lines
9.6 KiB
Markdown
# 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-decompress` v0.0.0, universal-tier, `"sideEffects": false`.
|
||
- ADDED: `BinaryFontUnpacker` class + `createBinaryFontUnpacker()` factory. Public API is bit-identical to the class currently exported by `@suaveplan/font-compress` — same `unpack(data: Uint8Array): TypefaceFont` signature, same wire-format expectations, same `TypefaceFont` return shape from `@suaveplan/font-convert`.
|
||
- ADDED: `FONT_DECOMPRESS_CODES` registry via `defineCodes("FONT_DECOMPRESS", [...])` with codes `UNPACK_FAILED` and `INVALID_DATA`. `FontDecompressError extends SuaveplanError<FontDecompressErrorCode>`.
|
||
- ADDED: `decodeString(data, offset)` utility — moved from `font-compress`'s `utils/encode-string.ts`. The encode-side helper stays in `font-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 `.md` docs for every implementation file.
|
||
- ADDED: Subpath exports — `.`, `./unpacker/binary-font-unpacker`, `./error-codes`, `./utils/decode-string`, `./constants`, `./types/index` (re-exports `TypefaceFont` and `PathCommand` from `@suaveplan/font-convert` for 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: `BinaryFontUnpacker` class and `createBinaryFontUnpacker` factory 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` — `decodeString` moves to `@suaveplan/font-decompress`. The file keeps `encodeString` only.
|
||
- MODIFIED: `FONT_COMPRESS_CODES` registry — `UNPACK_FAILED` removed (no longer thrown by any path in this package). `PACK_FAILED` and `INVALID_DATA` retained.
|
||
- MODIFIED: Subpath exports — `./unpacker/binary-font-unpacker` removed.
|
||
- MODIFIED: Public surface declaration in `README.md` and `src/index.ts`.
|
||
- The packer surface (`BinaryFontPacker`, `createBinaryFontPacker`), packer error codes, `parsePath` utility, `constants/`, and `FontCompressError` (with the trimmed code registry) remain in `font-compress`.
|
||
|
||
### Cross-cutting
|
||
|
||
- ADDED: New canonical capability `font-decompress` at `openspec/specs/font-decompress/spec.md` (created at fold time).
|
||
- MODIFIED: New canonical capability `font-compress` at `openspec/specs/font-compress/spec.md` — first canonical spec for the package, capturing the post-split shape.
|
||
- MODIFIED: `STATUS.md` — flip `@suaveplan/font-decompress` from `–` to `✅`; update `@suaveplan/font-compress` description to reflect packer-only scope.
|
||
- MODIFIED: `MANIFEST.md` — regenerated via `bun scripts/folder-reorg/generate-manifest.ts`. Both packages appear; `font-compress` dep count drops by one (decodeString removed); `font-decompress` lands with the same dep graph as `font-compress` plus tslib.
|
||
|
||
## Out of scope
|
||
|
||
- **WOFF / WOFF2 / EOT decompression.** Upstream `@suaveplan/font-decompress` was 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-convert` already; consumers compose the two when they need to go from `.woff2` bytes through `TypefaceFont` to a rendered glyph. Adding WOFF/WOFF2 to `font-decompress` would duplicate `font-convert`. Reason: orthogonal capability, already covered.
|
||
- **A third `@suaveplan/font-typeface` types-only package.** The shared `TypefaceFont` and `PathCommand` types 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-compress` past v0.0.0.** CLAUDE.md says packages stay at `0.1.0` until graduation to `1.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.md` rows for `@suaveplan/font-compress` and `@suaveplan/font-decompress`
|
||
- ✅ Monorepo grep for `@suaveplan/font-compress` consumers — 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-compress` stay 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:
|
||
|
||
```ts
|
||
// 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.
|