* feat(canvas): native field preservation, label materialization, batch workspace delete
- fillNativeFields() + repairContainerBinding() in db layer ensure every
element stored in SQLite is a complete, round-trippable Excalidraw element
with correct containerId ↔ boundElements bidirectional binding
- materializeLabel() in server.ts: shapes with label.text/text produce a
native bound text element at write time (create, update, batch) so text
follows its container when moved — matches VSCode Excalidraw extension behavior
- Batch workspace UI: Select/Unselect All, per-row checkboxes, Delete N
workspaces button with confirmation
- POST /api/tenants/batch-delete: delete up to 50 tenants in one request
- 42 new backend tests; 519 total passing
- Bump version 1.0.6 → 1.1.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tests): update e2e assertions for label materialization
phase2-regressions: label is now a native bound text element
(id: pos-stable-1-label) — check bound text element text instead
of container.label?.text which is no longer stored.
sync-flows FTS: search now matches the bound text element (fts-el-label)
rather than the container — accept either id as valid match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Block CONNECTING state in WS guard to prevent second connection seeding
knownContainerIdsRef before element_created fires
- Call handleCanvasChange() explicitly after element_created updateScene
(CaptureUpdateAction.NEVER suppresses onChange in Excalidraw 0.18)
- E2E: curved arrow stays deformable after sync round-trip
- E2E: auto-title injection test now reliably passes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>