The correctness finding is a real bug and the interesting one: arming a plant from the Seed Tray never attributed a seed lot, not even in the unambiguous single-lot case. The tray is the QUICKEST way to place a plant, so the fastest path was the one that silently lost which packet the seed came from — and it would have looked fine, because attribution is invisible until you go looking at a remaining count that's wrong. armPlant now does the same single-lot auto-attribution the picker does. Several lots stays unattributed rather than guessing; the picker is where that choice belongs. A single exhausted lot was also indistinguishable from having no lot, because the "available" filter dropped it before the count was taken. That silently dropped attribution with no signal. attributableLots now offers exhausted lots too, just not first: the count records what was written down, not a fact about the packet, so refusing to attribute a planting because the number says zero makes a wrong number harder to correct rather than easier. It's also now one rule in one place instead of the same filter written twice. The seed lot form had no 409 handling, alone among the version-guarded forms here. A concurrent edit made you retype everything; it now rebases onto the current row and says so, like the garden and plant forms do. Deduplication: the outbound-link anchor was duplicated verbatim between the plant card and the lot rows, including the security-relevant rel attribute — now one SourceLink that re-checks the URL and renders nothing when it isn't safe, so callers can't forget to guard. remainingLabel had its own copy of the unit-agreement rule that summarizeLots already owns; it now calls it. formatQuantity moved out of a component file into lib/seedLots.ts with the other formatters. STATE_CLASS.over and .low were identical. "Over-planted" is a discrepancy to look at, not a shortage to act on, so it now reads as its own warning. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { useState } from 'react'
|
||||
import { PlantIcon } from './PlantIcon'
|
||||
import { LotStateChip, SeedLotList, formatQuantity } from './SeedLotList'
|
||||
import { LotStateChip, SeedLotList } from './SeedLotList'
|
||||
import { SourceLink } from './SourceLink'
|
||||
import { cardActionClass, cardDangerClass } from '@/components/ui/cardActions'
|
||||
import { CATEGORY_LABELS, isBuiltin, type Plant } from '@/lib/plants'
|
||||
import { safeExternalUrl, summarizeLots, type SeedLot } from '@/lib/seedLots'
|
||||
import { formatQuantity, summarizeLots, type SeedLot } from '@/lib/seedLots'
|
||||
import { formatSpacing, type UnitPref } from '@/lib/units'
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ export function PlantCard({
|
||||
const builtin = isBuiltin(plant)
|
||||
const [showLots, setShowLots] = useState(false)
|
||||
const summary = summarizeLots(lots)
|
||||
const sourceUrl = safeExternalUrl(plant.sourceUrl)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col rounded-xl border border-border bg-surface">
|
||||
<div className="flex items-start gap-3 p-4">
|
||||
@@ -54,21 +55,10 @@ export function PlantCard({
|
||||
<p className="mt-0.5 text-sm text-muted">
|
||||
{CATEGORY_LABELS[plant.category]} · {formatSpacing(plant.spacingCm, unit)} spacing
|
||||
</p>
|
||||
{(plant.vendor || sourceUrl) && (
|
||||
{(plant.vendor || plant.sourceUrl) && (
|
||||
<p className="mt-0.5 flex flex-wrap items-center gap-1.5 text-xs text-muted">
|
||||
{plant.vendor && <span>{plant.vendor}</span>}
|
||||
{sourceUrl && (
|
||||
<a
|
||||
href={sourceUrl}
|
||||
target="_blank"
|
||||
// The href is a URL somebody pasted; noopener keeps it from
|
||||
// getting a handle back to this window.
|
||||
rel="noopener noreferrer"
|
||||
className="underline decoration-dotted underline-offset-2 hover:text-fg"
|
||||
>
|
||||
source ↗
|
||||
</a>
|
||||
)}
|
||||
<SourceLink url={plant.sourceUrl} />
|
||||
</p>
|
||||
)}
|
||||
{plant.notes && <p className="mt-1 line-clamp-2 text-xs text-muted">{plant.notes}</p>}
|
||||
|
||||
Reference in New Issue
Block a user