fix(review): carry Measure species into Create title/short-desc

Live overnight slabs after removing the Review species picker had
species_id null, so Create title stayed gated off. Require species on
Measure Calculate, stop the select sync from wiping choices before
options load, and have Review gate/PUT from slab.species_id with a
Capture handoff repair so deterministic title/short-desc always see it.
This commit is contained in:
grokbot_ty
2026-09-26 08:57:53 -07:00
parent 610b0883fe
commit 76f30262f3
7 changed files with 151 additions and 27 deletions
+34 -18
View File
@@ -98,12 +98,16 @@
}
}
// Keep local species select in sync when draft restore / session updates it.
$: {
const next = state.speciesId == null ? '' : String(state.speciesId);
if (speciesSel !== next) speciesSel = next;
// Sync local select only when session speciesId changes (draft restore / setSpecies).
// Do NOT depend on speciesSel — that fights bind:value and can clear a choice
// before setSpecies lands (or wipe restored ids while options are still loading).
let syncedSpeciesId: number | null | undefined = undefined;
$: if (syncedSpeciesId !== state.speciesId) {
syncedSpeciesId = state.speciesId;
speciesSel = state.speciesId == null ? '' : String(state.speciesId);
}
let speciesOptsReady = false;
onMount(() => {
void getTaxonomy()
.then((tax) => {
@@ -111,10 +115,12 @@
id: c.id,
name: c.name,
}));
speciesOptsReady = true;
})
.catch(() => {
speciesLoadErr = 'Could not load species list.';
speciesOpts = [];
speciesOptsReady = true;
});
});
@@ -170,7 +176,10 @@
setSpecies(speciesSel === '' ? null : Number(speciesSel));
};
$: canCalc = positiveInches(String(lengthIn)) !== null && positiveInches(String(thicknessIn)) !== null;
$: canCalc =
positiveInches(String(lengthIn)) !== null
&& positiveInches(String(thicknessIn)) !== null
&& speciesSel !== '';
const doCalc = async () => {
calcBusy = true;
@@ -294,19 +303,25 @@
<div class="field species">
<label class="label" for="measure-species">Species</label>
<select
id="measure-species"
class="sel"
data-testid="measure-species"
aria-label="Species"
bind:value={speciesSel}
on:change={commitSpecies}
>
<option value="">Select species…</option>
{#each speciesOpts as o (o.id)}
<option value={String(o.id)}>{o.name}</option>
{/each}
</select>
{#if !speciesOptsReady}
<select id="measure-species" class="sel" data-testid="measure-species" aria-label="Species" disabled>
<option value="">Loading species…</option>
</select>
{:else}
<select
id="measure-species"
class="sel"
data-testid="measure-species"
aria-label="Species"
bind:value={speciesSel}
on:change={commitSpecies}
>
<option value="">Select species…</option>
{#each speciesOpts as o (o.id)}
<option value={String(o.id)}>{o.name}</option>
{/each}
</select>
{/if}
{#if speciesLoadErr}
<p class="species-err" role="status">{speciesLoadErr}</p>
{/if}
@@ -330,6 +345,7 @@
<div><span>Sqft</span><span>{state.results.sqft.toFixed(2)}</span></div>
<div><span>Bdft</span><span>{state.results.bdft.toFixed(2)}</span></div>
<div><span>Scale</span><span>{state.results.scale.toFixed(2)} px/in</span></div>
<div data-testid="photos-ready-species"><span>Species</span><span>{speciesOpts.find((o) => o.id === state.speciesId)?.name ?? (state.speciesId != null ? `#${state.speciesId}` : '—')}</span></div>
</div>
{#if state.results.sourceBelowFloor}
<div class="warn">Source is below the 800px floor for the inventory PNG. Retake a larger photo.</div>