6.3 KiB
Capability Spec Delta: font-compress (font-compress)
Package: @suaveplan/font-compress
Status: PROPOSED
Every requirement below carries a
**Verified by:**line at authoring time. No exceptions.
ADDED Requirements
Requirement: Packer-only public surface
The package SHALL expose BinaryFontPacker class + createBinaryFontPacker() factory + FONT_COMPRESS_CODES registry + FontCompressError class, and SHALL NOT expose any unpacker functionality. BinaryFontUnpacker and createBinaryFontUnpacker are NOT exported from this package — they live in the sibling @suaveplan/font-decompress.
Scenario: root barrel exposes only the packer
- WHEN a consumer imports
@suaveplan/font-compress - THEN
BinaryFontPacker,createBinaryFontPacker,FONT_COMPRESS_CODES, andFontCompressErrorare present;BinaryFontUnpackeris undefined
Verified by: src/packer/binary-font-packer/binary-font-packer.test.ts::"root barrel exposes packer only"
Requirement: FONT_COMPRESS_CODES trimmed registry
The package SHALL declare an error-code registry via defineCodes("FONT_COMPRESS", [...]) containing exactly PACK_FAILED and INVALID_DATA. UNPACK_FAILED is NOT in this registry — it lives in @suaveplan/font-decompress as FONT_DECOMPRESS.UNPACK_FAILED. Every pack-side error path SHALL throw a FontCompressError carrying one of the two retained codes.
Scenario: every kept code is reachable
- WHEN the test suite exercises a successful pack and a malformed-input pack (e.g. missing
familyName) - THEN the malformed path raises
FontCompressErrorwhosecodematchesFONT_COMPRESS.INVALID_DATAorFONT_COMPRESS.PACK_FAILED
Scenario: UNPACK_FAILED removed from registry
- WHEN the test suite asserts on
Object.keys(FONT_COMPRESS_CODES).sort() - THEN the array is exactly
["INVALID_DATA", "PACK_FAILED"](sorted lexicographically)
Verified by: src/error-codes.test.ts::"every kept FONT_COMPRESS.* code is reachable" AND src/error-codes.test.ts::"UNPACK_FAILED removed from registry post-split"
Requirement: BinaryFontPacker public API
The package SHALL provide class BinaryFontPacker with method pack(font: TypefaceFont): Uint8Array and a createBinaryFontPacker(): BinaryFontPacker factory function. TypefaceFont is imported from @suaveplan/font-convert. The class SHALL emit the BTF wire format consumed by @suaveplan/font-decompress::BinaryFontUnpacker.
Scenario: round-trip TypefaceFont → BTF → TypefaceFont
- WHEN a known-vector
TypefaceFontis passed toBinaryFontPacker.pack(...)and the resulting bytes are then unpacked viaBinaryFontUnpacker.unpack(...)(from@suaveplan/font-decompress) - THEN the round-trip preserves
familyName,cssFontWeight,cssFontStyle,boundingBox, metrics, and every glyph entry (within the dictionary-encoding tolerance documented in the implementation)
Verified by: src/packer/binary-font-packer/binary-font-packer.test.ts::"round-trip preserves TypefaceFont shape via font-decompress unpacker"
Requirement: encodeString utility
The package SHALL provide an encodeString(value: string): Uint8Array helper used by the packer to emit length-prefixed UTF-8 strings to the BTF header. The function SHALL emit the bytes in the format [uint32 length][utf8 bytes]. decodeString is NOT in this package — it lives in @suaveplan/font-decompress.
Scenario: emits length-prefixed UTF-8
- WHEN
encodeString("Inter Variable")is called - THEN the returned
Uint8Arrayis14(uint32 length, little-endian) followed by the UTF-8 bytes of"Inter Variable"
Scenario: decodeString export removed
- WHEN
import { decodeString } from "@suaveplan/font-compress"is attempted at type-check time - THEN TypeScript reports
decodeStringis not exported (the test asserts the runtime absence viaimport * as ns from ...; expect(ns).not.toHaveProperty("decodeString"))
Verified by: src/utils/encode-string.test.ts::"emits length-prefixed UTF-8" AND src/utils/encode-string.test.ts::"decodeString export removed post-split"
Requirement: Universal-tier purity
The package SHALL ship as a universal-tier package — no imports from node:*, bun:*, or the bare specifiers fs/path/stream/net/http/crypto in any file under src/. package.json SHALL NOT declare a "browser" field, SHALL NOT pin engines.node or engines.bun, and SHALL declare "sideEffects": false.
Scenario: no platform imports in src/
- WHEN the CI step
grep -rnE "from ['\"](node:|bun:|fs|path|stream|net|http)['\"]" packages/fonts/font-compress/src/runs - THEN the command produces zero matches
Verified by: src/packer/binary-font-packer/binary-font-packer.test.ts::"tier purity: no node/bun imports"
Requirement: Subpath exports trimmed
package.json SHALL declare subpath exports for . (root barrel), ./error-codes, ./constants, ./utils/encode-string, ./utils/parse-path, ./packer/binary-font-packer. The ./unpacker/binary-font-unpacker subpath is removed (no longer present in dist).
Scenario: unpacker subpath removed
- WHEN a consumer attempts
import { BinaryFontUnpacker } from "@suaveplan/font-compress/unpacker/binary-font-unpacker" - THEN the import fails to resolve (the subpath is no longer in
package.jsonexports and the source files are no longer present)
Verified by: src/packer/binary-font-packer/binary-font-packer.test.ts::"unpacker subpath export removed"
Requirement: Banned patterns absent from src/
The package SHALL ship with zero matches of @deprecated, TODO, FIXME, ts-ignore, ts-expect-error, it.skip, test.skip, describe.skip, .only, raw any, or console.log in any .ts or .tsx file under src/. Zero // biome-ignore suppression comments. All test files import runner primitives from @suaveplan/testing/runner (rule §7).
Scenario: clean static-analysis grep
- WHEN the CI step
grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\\.skip\\|\\.only\\|\\bany\\b\\|console\\.log" packages/fonts/font-compress/src/ --include="*.ts"runs - THEN the command produces zero matches
Verified by: repo-wide CI grep gate + bun scripts/lint/check-test-runner-imports.ts