diff --git a/web/src/editor/SeedTray.tsx b/web/src/editor/SeedTray.tsx index deebb51..cbfe6f1 100644 --- a/web/src/editor/SeedTray.tsx +++ b/web/src/editor/SeedTray.tsx @@ -5,17 +5,17 @@ import type { Plant } from '@/lib/plants' /** * The Seed Tray strip in the focus-mode toolbar: a row of the garden's * kept-at-hand plants. Tap a chip to arm that plant for tap-to-place (the armed - * chip is highlighted); ✕ removes it from the tray; the trailing + chip opens - * the full catalog picker. See useSeedTray for persistence. + * chip is highlighted); ✕ removes it from the tray; the trailing "+ Plant" chip + * opens the full catalog picker. See useSeedTray for persistence. */ export function SeedTray({ - plants, + trayPlants, armedPlantId, onArm, onRemove, onOpenPicker, }: { - plants: Plant[] + trayPlants: Plant[] armedPlantId: number | null onArm: (plant: Plant) => void onRemove: (id: number) => void @@ -23,7 +23,7 @@ export function SeedTray({ }) { return (
- {plants.map((p) => { + {trayPlants.map((p) => { const active = p.id === armedPlantId return ( - + Plant + + Plant
) diff --git a/web/src/lib/seedTray.ts b/web/src/lib/seedTray.ts index 9c01916..8db0347 100644 --- a/web/src/lib/seedTray.ts +++ b/web/src/lib/seedTray.ts @@ -16,7 +16,8 @@ const TRAY_MAX = 24 // a working set, not a catalog; caps pathological growth function loadIds(gardenId: number): number[] { try { const v: unknown = JSON.parse(localStorage.getItem(KEY(gardenId)) ?? '[]') - return Array.isArray(v) ? v.filter((n): n is number => typeof n === 'number') : [] + const ids = Array.isArray(v) ? v.filter((n): n is number => typeof n === 'number') : [] + return ids.slice(-TRAY_MAX) // honor the cap even if storage was hand-edited } catch { return [] } @@ -30,17 +31,18 @@ function saveIds(gardenId: number, ids: number[]) { } } -export interface SeedTray { +export interface SeedTrayState { /** Tray plants in insertion order, resolved against the live catalog. */ trayPlants: Plant[] - /** Add a plant to the end of the tray (no-op if already present). */ + /** Add a plant to the end of the tray (no-op if already present; when the tray + * is full the oldest entry is evicted). */ add: (plant: Plant) => void /** Remove a plant from the tray. */ remove: (id: number) => void } /** Per-garden Seed Tray backed by localStorage and the plant catalog. */ -export function useSeedTray(gardenId: number): SeedTray { +export function useSeedTray(gardenId: number): SeedTrayState { const plants = usePlants() const [ids, setIds] = useState(() => loadIds(gardenId)) diff --git a/web/src/pages/GardenEditorPage.tsx b/web/src/pages/GardenEditorPage.tsx index c9f097b..77ef159 100644 --- a/web/src/pages/GardenEditorPage.tsx +++ b/web/src/pages/GardenEditorPage.tsx @@ -244,18 +244,26 @@ export function GardenEditorPage() { setArmedPlant(plant) } + // Removing the armed plant from the tray also disarms it, so placement doesn't + // silently continue for a plant the user just cleared out. + function removeFromTrayAndDisarm(id: number) { + removeFromTray(id) + if (armedPlant?.id === id) setArmedPlant(null) + } + // The picker hands back the full Plant (from the whole catalog), so use it // directly rather than re-resolving against the garden's referenced-plant map — // a not-yet-placed plant isn't in that map, which used to silently abort the - // pick (nothing armed, picker left open). + // pick (nothing armed, picker left open). Picking (place OR change) makes sure + // the plant renders and drops it into this garden's tray for quick reuse. function onPickPlant(plant: Plant) { if (!canEdit) return // defense in depth: viewers can't reach the picker anyway + ensurePlant(plant) addToTray(plant) if (picker === 'change' && selectedPlop) { - ensurePlant(plant) updatePlanting.mutate({ id: selectedPlop.id, version: selectedPlop.version, plantId: plant.id }) } else { - armPlant(plant) // 'place' mode: arm for repeat placement + setArmedPlant(plant) // 'place' mode: arm for repeat placement } setPicker(null) } @@ -284,30 +292,34 @@ export function GardenEditorPage() { ← {garden.name} {objectDisplayName(focusedObject)} - {canEdit && !focusedObject.plantable && Not plantable} - {canEdit && focusedObject.plantable && ( - setPicker('place')} - /> - )} - {canEdit && focusedObject.plantable && armedPlant && ( - - )} - {canEdit && focusedObject.plantable && focusedPlops.length > 0 && ( - - )} + {canEdit && + (focusedObject.plantable ? ( + <> + setPicker('place')} + /> + {armedPlant && ( + + )} + {focusedPlops.length > 0 && ( + + )} + + ) : ( + Not plantable + ))} )} @@ -355,7 +367,7 @@ export function GardenEditorPage() { setPicker(null)} - onSelect={(p) => onPickPlant(p)} + onSelect={onPickPlant} /> )}