merge issue-89-metadata-lock → main (Create-string model, #89/#90)

Drop the metadata_locked column + "Save and lock" UI end-to-end. The
review endpoint now builds the deterministic title/short_description on
every call; the owner's Create title / Create short desc buttons fire
the build and write the result (force-overwrite contract). commitReview
auto-fills only empty fields (no owner-clobber) and folds the
deterministic copy into its single PUT (kills the client_rev 409
cascade).

Taken wholesale from the branch: photo strip, wood primary/secondary,
server-side thickness band (hidden from UI, passed to Woo on publish),
and fixes #80/#82/#83/#84.

- backend: remove metadata_locked from schema.sql, db sentinels,
  slabs.py client fields/exposure, review.py lock action; review
  always computes title/short_desc
- frontend: slabs.ts + Review.svelte drop the lock model, add
  Create-string workflow + wood primary + photo strip
- e2e: m3-create + review-photo-strip rewritten for the Create-string
  contract; thickness-band test asserts absence (per #90)
- deploy: recreate LAN binds (override-m3.yml was absent); verify
  metadata_locked gone, review builds title, PUT persists

Gates: backend 117/117, unit 88/88, tsc clean, e2e 6/6 (m3-create +
photo-strip). Live UAT verified at 192.168.1.202:10881.
This commit is contained in:
Ty
2026-09-21 11:54:48 -07:00
parent e5acef4c9b
commit 1a50fad0a5
11 changed files with 234 additions and 314 deletions
-13
View File
@@ -39,8 +39,6 @@ export interface Slab {
short_description: string | null;
description: string | null;
content_source: string | null;
// #89: set true once the owner has locked the taxonomy (deterministic copy built).
metadata_locked: boolean;
species_confidence: number | null;
figure_confidence: number | null;
grade_confidence: number | null;
@@ -271,8 +269,6 @@ export interface ReviewResult {
price: number | null;
price_suggestion: number | null;
primary_wood_category_id: number | null;
// #89: whether the metadata is locked (deterministic copy built/persisted).
locked: boolean;
}
export interface ReviewInput {
@@ -284,8 +280,6 @@ export interface ReviewInput {
description?: string;
price_source?: string;
content_source?: string;
// #89: 'lock' triggers the explicit metadata-lock + deterministic copy build.
action?: 'lock';
}
export function reviewSlab(slabId: string, payload: ReviewInput = {}): Promise<ReviewResult> {
@@ -296,13 +290,6 @@ export function reviewSlab(slabId: string, payload: ReviewInput = {}): Promise<R
}).then((r) => parse<ReviewResult>(r));
}
// #89: explicit lock of the confirmed taxonomy. The server builds and persists
// the deterministic title/short_description from the locked set (owner prose
// already on the slab wins). Distinct from reviewSlab() which only reports.
export function lockSlab(slabId: string, payload: ReviewInput = {}): Promise<ReviewResult> {
return reviewSlab(slabId, { ...payload, action: 'lock' });
}
// ---------------------------------------------------------------------------
// Publish (PUBLISH-*)
// ---------------------------------------------------------------------------