Active-block addressing by materialized-path id (words-activate.svelte +
engine/block-tree.ts): click sets the active block explicitly; inspector
follows it, immune to selection churn. Breadcrumb navigates ancestry.
Inspector controls:
- ToggleGroups (font / weight / align / border) always keep one option
selected; the default value stores as undefined to keep the model clean.
- Border gains a `none` chip that clears the border (BorderChipValue).
- Image full-width is a Switch; image src + column width/gap validate on
commit (sanitizeWordsUrl, CSS-length/flex checks) with a [data-invalid] ring.
- Corner-radius + border-width are slider-only (WordsNumRow `sliderOnly`):
the paired numeric stepper was redundant.
- Numeric fields clamp to their range (line-height ≤ 2.5, font-size 12-40).
Inspector chrome:
- Unified field frame + single accent focus ring (no inset outline over text,
no ghost/black-border mix).
- Section dividers + uppercase accordion headers; styled breadcrumb row.
- Depth-tied accent (primary/affirm/loss/risk) shared by header mark and the
canvas active-block ring.
- Selection ring is an ::after pseudo-element with a fixed radius, so a block
the user rounded no longer rounds its selection indicator.
- Raw hex/rgba replaced by --color-*-solid tokens (theme-aware, portal-safe).
Columns: empty on insert, gap control, per-column width; Add-block removed
from the Columns BLOCK panel (lives in the column inspector). Shift+Enter
soft break renders via white-space: pre-wrap.
npm run check: 1 pre-existing baseline error (visual.ts:147). Words soma
452/452. Verified end-to-end in the browser.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>