The Review screen now has an explicit 'Lock & Build Title' step. Until the
owner locks, title/short_description stay empty (no 'ready' copy from an
unlocked AI draft). On lock the server validates the taxonomy, sets
metadata_locked=1, builds + persists the deterministic title/short_description
(owner prose wins), and returns the ready state.
- schema.sql: add metadata_locked INTEGER DEFAULT 0 to slabs
- db/__init__.py: bump _SCHEMA_SENTINELS for metadata_locked
- slabs.py: expose metadata_locked in GET; reject it in PUT (server-managed)
- review.py: POST /{id}/review action=lock path; regular review reports
stored (empty) copy until locked
- test_review.py: 8 tests covering lock gate, owner-prose-wins, stale taxonomy
- frontend/api/slabs.ts: Slab.metadata_locked, ReviewResult.locked,
ReviewInput.action, lockSlab() helper
- Review.svelte: lockMetadata() with correct commit-token ordering (bump AFTER
inner commit so the inner PUT isn't superseded), Lock button, locked banner
- e2e/m3-create.spec.ts: pare to single golden path (capture→review→lock→
ready→publish→done); mock respects optimistic concurrency (client_rev) so
stale PUTs 409 like the real backend
Scope .settings styles to the panel: light #eee text on the dark
#121212 surface for labels, and white inputs/selects/options with dark
text. Both pairs exceed WCAG 4.5:1. Add settings-contrast e2e as the
regression guard for the Settings form plus the sibling dev panels.
- test_exit_inference_off.py (QUALITY-20/120): full create->photo->review->publish
happy path with inference OFF, asserts zero inference_log rows and no raw codes/
tracebacks reach the owner; not-ready publish 422 with plain field-level message.
- test_integration_woo_mock.py (QUALITY-35): Woo mock covering success, error,
duplicate SKU, stale field (no raw code), and taxonomy anchor bump on change.
- m3-create.spec.ts (QUALITY-50): Playwright E2E — all M3 dev screens render,
Review/Publish controls load, Generate control visible only when enabled+reachable.
- deploy/uat_walk.sh (QUALITY-110): ready-to-run human walk for the real SLAB-UAT-*
draft (inference off) against the live store — NOT a mock.
Verified: pytest 61 passed; frontend unit 88 passed; e2e 4 passed; gates exit 0.