cc93715c03da3a2bb3e6acd722e3a9a1b53dcc79
Three fixes driven by building a real consumer (cyrille studio) on 0.0.1, each
pinned by tests:
- `coerceAttrs` treated the spec as a whitelist: any attribute a block
definition did not declare was silently deleted by the first
`normalizeDocument` pass — and since normalization runs on load and consumers
autosave, the erasure wrote itself back to storage. Coercion now fills
defaults and keeps unknown keys verbatim. Parse rules build attrs explicitly,
so pasted markup cannot smuggle keys through this path; the CRDT never calls
coercion, so replica semantics are unchanged.
- `AttrSpec.validate` was consulted only by `validateDocument`, which nothing
in the library calls — it looked like enforcement and was inert. A provided
value failing `validate` now falls back to the declared default,
deterministically (CRDT-safe given one spec) and loudly in dev.
- Undo recorded one entry per transaction — one keystroke per Ctrl+Z, and 200
keystrokes evicted the entire earlier history. Plain typing in one block now
coalesces within a 500ms window by concatenation, which preserves the replay
invariant (`inverted` stays in application order, replayed reversed), counts
as ONE entry against maxSize, and never merges across blocks, structural
changes, or a foreign transaction (remote setDoc, undo/redo, selection-only
moves interrupt the chain). `coalesceMs: 0` opts out.
Also: `component` in a block definition may now be a lazy loader
(`() => import('./Card.vue')`) — a registry imported for its schema (codecs,
tests, server-side normalizers) then carries no view graph; the view wraps the
loader in a cached async component on first render.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Description
Languages
TypeScript
66.7%
Vue
33.3%