Files
2026-05-27 23:29:53 +01:00

6.0 KiB

Capability Spec Delta: font-decompress (font-decompress)

Package: @suaveplan/font-decompress Status: PROPOSED

Every requirement below carries a **Verified by:** line at authoring time. No exceptions.

ADDED Requirements

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-decompress/src/ runs
  • THEN the command produces zero matches

Verified by: src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts::"tier purity: no node/bun imports"


Requirement: FONT_DECOMPRESS_CODES error registry

The package SHALL declare an error-code registry via defineCodes("FONT_DECOMPRESS", [...]) containing exactly UNPACK_FAILED and INVALID_DATA. Every public error path SHALL throw a FontDecompressError extends SuaveplanError<FontDecompressErrorCode> carrying one of those codes. The class SHALL NOT throw raw Error instances from any public method.

Scenario: every code is reachable from a real call site

  • WHEN the test suite exercises malformed wire bytes (truncated header, dictionary length overrun) and a successful unpack call
  • THEN each path raises (or returns) values consistent with the documented contract; the UNPACK_FAILED and INVALID_DATA codes are each reachable

Scenario: SuaveplanError contract preserved

  • WHEN a FontDecompressError is constructed with a cause (e.g. wrapping a RangeError from DataView.getUint32)
  • THEN error.cause is the original; error instanceof SuaveplanError is true

Verified by: src/error-codes.test.ts::"every FONT_DECOMPRESS.* code is reachable from a call site" AND src/error-codes.test.ts::"FontDecompressError preserves cause"


Requirement: BinaryFontUnpacker public API

The package SHALL provide class BinaryFontUnpacker with method unpack(data: Uint8Array): TypefaceFont and a createBinaryFontUnpacker(): BinaryFontUnpacker factory function. TypefaceFont is imported from @suaveplan/font-convert. The class SHALL parse the BTF wire format produced by @suaveplan/font-compress::BinaryFontPacker and produce a TypefaceFont object that round-trips that pack-then-unpack pair byte-for-byte to JSON-shape equivalence.

Scenario: round-trip BTF → TypefaceFont

  • WHEN a known-vector BTF buffer (produced offline by BinaryFontPacker.pack(...)) is passed to unpack(...)
  • THEN the resulting TypefaceFont has the expected familyName, cssFontWeight, cssFontStyle, boundingBox, glyphs (per-character ha/x_min/x_max/o path string), and metrics

Scenario: truncated buffer raises UNPACK_FAILED or INVALID_DATA

  • WHEN unpack(...) is called on a buffer truncated mid-dictionary
  • THEN a FontDecompressError is thrown with one of the registry's codes

Verified by: src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts::"round-trip pack/unpack preserves font shape" AND src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts::"truncated buffer raises FontDecompressError"


Requirement: decodeString utility

The package SHALL provide a decodeString(data: Uint8Array, offset: number): { value: string; offset: number } helper used by the unpacker to read length-prefixed UTF-8 strings from the BTF header. The function SHALL return the decoded string plus the offset of the next byte after the string. Malformed length prefixes (negative, beyond buffer end) SHALL raise FontDecompressError with code === "FONT_DECOMPRESS.INVALID_DATA".

Scenario: round-trip a UTF-8 string

  • WHEN decodeString is called on bytes produced by the matching encodeString (from @suaveplan/font-compress) for "Inter Variable"
  • THEN the returned value equals "Inter Variable" and offset points past the encoded bytes

Scenario: rejects length-overrun

  • WHEN decodeString is called on a buffer whose length prefix exceeds the buffer length
  • THEN a FontDecompressError with code === "FONT_DECOMPRESS.INVALID_DATA" is thrown

Verified by: src/utils/decode-string.test.ts::"round-trip a UTF-8 string" AND src/utils/decode-string.test.ts::"rejects length overrun"


Requirement: Subpath exports tree-shake per feature module

package.json SHALL declare subpath exports for . (root barrel), ./error-codes, ./constants, ./utils/decode-string, ./unpacker/binary-font-unpacker. Each subpath SHALL resolve to a built .js + .d.ts pair under dist/. sideEffects SHALL be false.

Scenario: subpath import resolves to feature module

  • WHEN a consumer writes import { BinaryFontUnpacker } from "@suaveplan/font-decompress/unpacker/binary-font-unpacker"
  • THEN the import resolves to the dedicated unpacker module, not the root barrel

Verified by: src/unpacker/binary-font-unpacker/binary-font-unpacker.test.ts::"subpath export resolves to unpacker module"


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-decompress/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