Closes all 5 items of fix-stext.md for the <SText> canvas-measured text engine. None
were implemented before; T1 was a P0 correctness bug, T2 a P1 memory leak.
- T1 (P0) font-load invalidation: `invalidateFont` / `invalidateFontFamily` in
measurement.ts (granular, vs the old all-or-nothing clear) + `useFontReady(dom)` hook
that subscribes to `document.fonts` `loadingdone` via `ActiveDom.listen` (iframe/popup
-safe, no raw listener), evicts the loaded family's cache FIRST, then bumps a reactive
`epoch`. s-text.svelte folds `epoch` into the layout getter. Fixes the line count
lying after a web font swaps in (getComputedStyle reports the requested family,
unchanged on load, and the cache is keyed by the font string).
- T2 (P1) bounded caches: `LruCache` (Map-backed, move-to-recent + evict-oldest) caps
the per-font segment cache at 4096 entries and at most 24 fonts. invalidateFont reuses
it cleanly.
- T3: single getComputedStyle per reactive pass (merged `font` + `lineHeightPx`).
- T4: hydration flash documented in s-text.svelte.
- T5: bidi levels opt-in via `PrepareOptions.computeBidiLevels` (default false) — the
walker never consumed `segLevels`, so it was wasted compute on every prepare.
- tests: new canvas-text.test.ts (9 tests, deterministic canvas stub) covering LruCache
semantics, invalidateFont/Family, per-font bounding, and bidi opt-in + line-count
invariance. (The engine had ZERO tests before.)
Invariants kept: no change to line-break decisions, no canvas painting, pure files
(measurement/layout) stay Svelte-free, SSR-safe, strict TS. check 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>