From 610b0883fefcab1ff67f45e4f2926fec8b9c63f3 Mon Sep 17 00:00:00 2001 From: grokbot_ty Date: Sat, 26 Sep 2026 08:47:40 -0700 Subject: [PATCH] feat(review): remove species picker; species stays from Measure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Species is chosen on the merged Measure screen; the Review select was a duplicate. Keep species_id on the slab from createSlab (Capture→draft→Woo) and gate Create title from the hydrated value without offering edit. --- frontend/src/capture/capture.types.ts | 2 +- frontend/src/create/flow.ts | 2 +- frontend/src/create/review/Review.svelte | 38 +++++++------------ frontend/tests/e2e/helpers/draft.ts | 4 ++ frontend/tests/e2e/m3-create.spec.ts | 4 +- frontend/tests/e2e/review-photo-strip.spec.ts | 8 +++- 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/frontend/src/capture/capture.types.ts b/frontend/src/capture/capture.types.ts index a076e92..498fe9c 100644 --- a/frontend/src/capture/capture.types.ts +++ b/frontend/src/capture/capture.types.ts @@ -118,7 +118,7 @@ export type Capture = axis: Axis; // editable tape until Calculate; frozen after results lengthEighths: Eighths; thicknessEighths: Eighths; - /** Woo species leaf id; optional until Review. Persisted on draft + createSlab. */ + /** Woo species leaf id; set on Measure. Persisted on draft + createSlab; Review does not edit. */ speciesId: number | null; results: Measures | null; /** Capture-order sibling results from the post-Calculate batch pass. */ diff --git a/frontend/src/create/flow.ts b/frontend/src/create/flow.ts index 9339c1b..33651bd 100644 --- a/frontend/src/create/flow.ts +++ b/frontend/src/create/flow.ts @@ -11,7 +11,7 @@ import type { PhotosReadyLineupItem } from '../capture/photosReadyLineup'; export interface CalibratedCapture { lengthIn: number; thicknessIn: number; - /** Woo species leaf from Measure; null if unset. Review can still change. */ + /** Woo species leaf from Measure; null if unset. Review does not edit. */ speciesId: number | null; sqft: number; bdft: number; diff --git a/frontend/src/create/review/Review.svelte b/frontend/src/create/review/Review.svelte index 9d2e14f..6fb1ea6 100644 --- a/frontend/src/create/review/Review.svelte +++ b/frontend/src/create/review/Review.svelte @@ -35,7 +35,6 @@ }; // --- Derived taxonomy option lists (resolve by slug, never hardcoded ids) --- - let speciesOpts: Opt[] = []; let woodCatGroups: import('./MultiSelect.svelte').Group[] = []; let edgeOpts: Opt[] = []; let figureOpts: Opt[] = []; @@ -47,10 +46,9 @@ function deriveOptions() { if (!tax) return; const cats: TaxCategory[] = tax.categories; - // Resolve the two top-level groupers BY SLUG (QUALITY-105 / METADATA-5 — never - // hardcode live Woo term ids): `species` → species options; `wcat` → wood - // categories. Both are parent_id:null groupers; their children are the leaves. - const speciesGrouper = cats.find((c) => c.parent_id === null && c.slug === 'species'); + // Resolve the wood-category grouper BY SLUG (QUALITY-105 / METADATA-5 — never + // hardcode live Woo term ids). Species is chosen on Measure and is not editable + // on Review; only `wcat` leaves are listed here. const wcatGrouper = cats.find((c) => c.parent_id === null && c.slug === 'wcat'); const childrenOf = (g: TaxCategory | undefined): TaxCategory[] => g ? cats.filter((c) => c.parent_id === g.id) : []; @@ -67,7 +65,6 @@ return { id: t.id, name: t.name, ai: false, lowIn: Number.isFinite(lowIn) ? lowIn : undefined }; }); }; - speciesOpts = childrenOf(speciesGrouper).map((c) => ({ id: c.id, name: c.name, ai: false })); // Wood categories: parent/child grouped (METADATA-10 — parents group only, never // assigned). Each direct child of `wcat` that has its own children becomes a parent // group with those children as leaves; a child with no children is a leaf under the @@ -94,12 +91,14 @@ } // --- Editable model (mirrors the live slab) --- + // Species is set on Measure / createSlab — Review only reads it (no picker). + // Kept as a string so canCreateTitle can gate on a non-empty value from hydrate. + let speciesSel: string = ''; // #90: single-selects bind a STRING var to the native - - {#each speciesOpts as o (o.id)} - - {/each} - - +
diff --git a/frontend/tests/e2e/helpers/draft.ts b/frontend/tests/e2e/helpers/draft.ts index a266a61..492d585 100644 --- a/frontend/tests/e2e/helpers/draft.ts +++ b/frontend/tests/e2e/helpers/draft.ts @@ -20,6 +20,8 @@ export interface SeedMeasure { sqft: number; bdft: number; widthIn: number; + /** Woo species leaf; required for Review-ready e2e now that Review has no species picker. */ + speciesId?: number | null; } const toEighths = (inches: number) => Math.round(inches * 8); @@ -58,6 +60,7 @@ export async function seedMeasureDraft( axisBbox: null, lengthEighths: payload.lengthEighths, thicknessEighths: payload.thicknessEighths, + species_id: payload.speciesId ?? null, results: { lengthEighths: payload.lengthEighths, thicknessEighths: payload.thicknessEighths, @@ -97,6 +100,7 @@ export async function seedMeasureDraft( widthEighths, sqft: d.sqft, bdft: d.bdft, + speciesId: d.speciesId ?? null, }, ); } diff --git a/frontend/tests/e2e/m3-create.spec.ts b/frontend/tests/e2e/m3-create.spec.ts index d0557a1..287f311 100644 --- a/frontend/tests/e2e/m3-create.spec.ts +++ b/frontend/tests/e2e/m3-create.spec.ts @@ -294,6 +294,7 @@ async function reachReview(page: Page) { await page.goto('/?noload'); await seedMeasureDraft(page, { sku: '', lengthIn: 48, thicknessIn: 2, sqft: 6.25, bdft: 12.5, widthIn: 11, + speciesId: SPECIES_ID, }); // Phase 2: reload WITHOUT ?noload so onMount probes IDB and shows Resume. await page.goto('/'); @@ -309,7 +310,8 @@ async function reachReview(page: Page) { /** Fill every ready-gating field to make the slab publishable. */ async function completeForReady(page: Page) { - await page.getByTestId('species').selectOption(String(SPECIES_ID)); + // Species comes from Measure/createSlab (seeded on the draft); no Review picker. + await expect(page.getByTestId('species')).toHaveCount(0); // Wood categories: open, select Live edge, Done. await page.getByTestId('wood-cat-btn').click(); await page.getByTestId(`wood-cat-opt-${LIVEEDGE_ID}`).click(); diff --git a/frontend/tests/e2e/review-photo-strip.spec.ts b/frontend/tests/e2e/review-photo-strip.spec.ts index 85c508d..c24b714 100644 --- a/frontend/tests/e2e/review-photo-strip.spec.ts +++ b/frontend/tests/e2e/review-photo-strip.spec.ts @@ -165,7 +165,10 @@ async function mockApi(page: Page) { async function reachReview(page: Page) { await page.goto('/?noload'); - await seedMeasureDraft(page, { sku: '', lengthIn: 48, thicknessIn: 2, sqft: 6.25, bdft: 12.5, widthIn: 11 }); + await seedMeasureDraft(page, { + sku: '', lengthIn: 48, thicknessIn: 2, sqft: 6.25, bdft: 12.5, widthIn: 11, + speciesId: SPECIES_ID, + }); await page.goto('/'); await expect(page.getByRole('heading', { name: 'Resume?' })).toBeVisible({ timeout: 15000 }); await page.getByRole('button', { name: 'Continue' }).click(); @@ -175,7 +178,8 @@ async function reachReview(page: Page) { } async function completeForReady(page: Page) { - await page.getByTestId('species').selectOption(String(SPECIES_ID)); + // Species comes from Measure/createSlab (seeded on the draft); no Review picker. + await expect(page.getByTestId('species')).toHaveCount(0); await page.getByTestId('wood-cat-btn').click(); await page.getByTestId(`wood-cat-opt-${LIVEEDGE_ID}`).click(); await page.getByTestId('wood-cat-done').click();