normalizeBlock had no case for type='image' — image blocks coming from
parseWordsMarkdown / parseWordsHtml were silently dropped at the
default branch. Now:
- normalize.ts case 'image': validates src (non-empty + sanitizeWordsUrl),
passes through alt / caption / width (numeric) / height (numeric) /
align (validated via isWordsImageAlign) / status ('pending' | 'error').
- normalize.ts imports createImage + isWordsImageAlign from the
extension.
Image-extension cast widened with `as unknown as ErasedFactory` because
WordsImageBlock (no `children` field) doesn't satisfy the contract's
node shape strictly; the double-cast is the same pattern table-extension
already uses for typed factories.
3 new engine tests cover the F3 happy path:
- parses a block-level markdown image ( on its
own line yields a type:'image' block)
- round-trips an image through markdown (serialize → parse → matches)
- round-trips an image through HTML (figure>img+figcaption → ImageBlock
→ serialize back)
85/85 engine tests pass (was 82; +3 image tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>