Add "Scan a packet" to the editor's Plants mode
Build image / build-and-push (push) Successful in 7s

Surfaces the seed-packet scan (shipped in #102) inside the editor's Plants mode — a capability-gated "📷 Scan packet" entry in the shared PlantPlacementTools cluster (desktop focus toolbar + mobile strip) and in the mobile pre-focus hint — so a variety can be added mid-planting without leaving the garden. Follow-up to #102 per Steve's deferred design call.

Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #120.
This commit is contained in:
2026-07-22 17:25:39 +00:00
committed by steve
parent cf37e57808
commit a72ddefc99
+34 -1
View File
@@ -13,6 +13,7 @@ import { PlantPicker } from '@/editor/PlantPicker'
import { Palette } from '@/editor/Palette'
import { SeedTray } from '@/editor/SeedTray'
import { RecentPlants } from '@/editor/RecentPlants'
import { ScanPacketModal } from '@/components/plants/ScanPacketModal'
import { ClearBedModal } from '@/editor/ClearBedModal'
import { EditorHint } from '@/editor/EditorHint'
import { SeasonBanner, SeasonPicker } from '@/editor/SeasonPicker'
@@ -108,6 +109,10 @@ export function GardenEditorPage() {
const [picker, setPicker] = useState<'place' | 'change' | null>(null)
const [sharing, setSharing] = useState(false)
const [clearing, setClearing] = useState(false)
const [scanning, setScanning] = useState(false)
// Whether to offer packet scanning — a vision model is configured. Read once
// here so all three Plants-mode entry points gate identically.
const canScan = !!capabilities.data?.vision
const nudgeTimer = useRef<number | null>(null)
const nudgeFire = useRef<(() => void) | null>(null)
@@ -632,6 +637,8 @@ export function GardenEditorPage() {
onClear={() => setClearing(true)}
onFill={fillBed}
filling={fillObject.isPending}
canScan={canScan}
onScan={() => setScanning(true)}
/>
) : (
<span className="text-xs text-muted">Not plantable</span>
@@ -706,6 +713,8 @@ export function GardenEditorPage() {
onClear={() => setClearing(true)}
onFill={fillBed}
filling={fillObject.isPending}
canScan={!!capabilities.data?.vision}
onScan={() => setScanning(true)}
/>
<Button variant="ghost" className="ml-auto px-2 py-1 text-xs" onClick={exitFocus}>
Done planting
@@ -724,7 +733,18 @@ export function GardenEditorPage() {
</div>
)
) : (
<p className="px-1 text-xs text-muted">Tap a bed, then 🌱 Plant here to start planting.</p>
<div className="flex items-center gap-2">
<p className="px-1 text-xs text-muted">Tap a bed, then 🌱 Plant here to start planting.</p>
{canScan && (
<Button
variant="ghost"
className="ml-auto px-2 py-1 text-xs"
onClick={() => setScanning(true)}
>
📷 Scan packet
</Button>
)}
</div>
))}
</div>
)}
@@ -741,6 +761,8 @@ export function GardenEditorPage() {
{sharing && <ShareGardenModal garden={g} onClose={() => setSharing(false)} />}
{scanning && <ScanPacketModal unit={garden.unitPref} onClose={() => setScanning(false)} />}
{clearing && focusedObject && (
<ClearBedModal
objectId={focusedObject.id}
@@ -768,6 +790,8 @@ function PlantPlacementTools({
onClear,
onFill,
filling,
canScan,
onScan,
}: {
recentPlants: Plant[]
trayPlants: Plant[]
@@ -780,6 +804,10 @@ function PlantPlacementTools({
onClear: () => void
onFill: (layout: FillLayout) => void
filling: boolean
// Scanning a packet adds a variety to the catalog mid-planting; only offered
// where a vision model is configured.
canScan: boolean
onScan: () => void
}) {
return (
<>
@@ -791,6 +819,11 @@ function PlantPlacementTools({
onRemove={onRemove}
onOpenPicker={onOpenPicker}
/>
{canScan && (
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={onScan}>
📷 Scan packet
</Button>
)}
{armedPlant && <FillControl onFill={onFill} busy={filling} />}
{armedPlant && (
<Button variant="ghost" className="px-2 py-1 text-xs" onClick={onDisarm}>