- Garden and plant dialogs keep centimeters as the source of truth
(LengthField in lib/units.ts): a no-change Save no longer rewrites
900 cm as 899.922 or a 45 cm spacing as 44.958, bumping versions and
writing bogus history entries on the way.
- The UI stamps every date with the browser's local day (lib/dates.ts).
Journal notes already did; plop placement, fill and removal now do too,
so a 9 pm placement isn't "planted tomorrow". The fill endpoint gained an
optional plantedAt; API and agent callers still default to UTC today.
- Removing an object that holds plants asks first and says how many go
with it. An empty one still goes straight away (one Undo restores it).
- The expanded plant card's action row wraps instead of clipping "Delete".
- Monogram lettering switches to a dark ink on pale marker colors (garlic,
cabbage, marigold) instead of near-white on near-white.
- Copy-as-plan proposes the next free year and warns when the typed name
already exists, so two gardens can't both read as "the 2027 plan".
- Plan cards show the base name with a "2027 plan" tag, so the year — the
point of the name — survives truncation.
- A rejected model spec now says which model and why: a wrapped
ErrInvalidInput's reason reaches the client as the 400's message, and the
Settings field shows it inline instead of toasting "invalid input".
Also defuses a clock bomb in TestRemainingReturnsWhenAPlantingIsRemoved,
which only passed while the real date was before 2026-08-01.
Co-Authored-By: Claude Fable 5 <[email protected]>
The frontend is rebuilt screen by screen from the handoff: warm cream ground,
terracotta + sage accents, Caprasimo over Figtree, every control a pill. Same
React/Vite/TanStack stack and the same lib/ data layer; the presentation is new.
- Tokens: web/src/styles/index.css declares the handoff's styles.css variables
through Tailwind's @theme under the same names; dark mode is those variables
overridden on <html> by the handoff's pansy-theme.js, inlined in index.html
so it runs before first paint. Lucide glyphs at stroke 2.75; a small pill kit
(Button, Dialog, Field, Seg, Toggle, Tag, toast).
- Login / Register: the centered column over soft accent circles; OIDC button
and signup footer still follow /auth/providers.
- Gardens: cards with a real SVG plot thumbnail (objects + plant-colored dots
from /full), a `plan` tag for "<name> — <year>" copies, shares line, Open +
share/copy/edit/delete; New garden / Share / Plan-a-season dialogs.
- Plants: monogram markers derived from the name (collision-resolved across the
catalog — replaces emoji icons), category chips, expandable lot cards, the
scan-packet flow as a two-step dialog that never auto-creates.
- Settings: Appearance (theme seg), Who gets in (read-only sign-in config),
Garden assistant (self-saving toggle + chat/vision model fields), You.
- Editor: a new canvas with the prototype's pointer model (wheel-to-cursor,
pinch about the centroid, 3″ snap, one PATCH per drop, semantic-zoom
monograms/labels), plus corner resize handles; desktop three-card workspace
(toolkit | plan | rail with Plot/Journal/History/Assistant) and, below 760px
of container width, the phone chrome (header, peek panel, tool strip, mode
bar). Seasons as a segmented control over the years with data plus plan
copies; Undo re-reads history before reverting the newest step.
- Public read-only view and the register page restyled to match.
- GET /settings gains a read-only `auth` view (registration mode, local auth,
OIDC issuer) so the Settings page can show what's in force.
- README / DESIGN.md / CLAUDE.md updated; @use-gesture/react dropped.
Co-Authored-By: Claude Fable 5 <[email protected]>
Fixes from the PR #27 adversarial review (considered; not graded).
Correctness / error-handling
- Modal: focus once on mount and attach the keydown listener once (latest
onClose/busy via refs), so a parent re-render (e.g. a background refetch)
no longer re-fires focus() and steals it from the input being typed in.
- Modal takes a `busy` prop; while a save/delete is in flight, Escape and
backdrop clicks don't dismiss it (form/delete modals pass busy=pending),
so an action can't be interrupted mid-flight and lose its error.
- Form validates the *converted* centimeter values against the server's
[1cm, 100m] bounds, so sub-cm / over-100m sizes fail client-side with a
clear message instead of a generic server error.
- displayFromCm rounds imperial to 0.1 ft so an 8 ft garden (244 cm)
prefills as "8", not "8.01".
Maintainability
- Extracted ui/field.ts (useFieldId + fieldControlClass) shared by
TextField/TextArea/Select. Added a `danger` Button variant, used by the
delete modal; card edit/delete buttons gained focus-visible rings.
- useUpdateGarden takes the id in its mutation variables (no dummy 0 during
create). GardensPage shares a close handler; dropped a redundant zod
annotation; 409 rebase reuses dimString; renamed `del` -> `deletion`.
Verified in a browser: 8 ft round-trips to a prefilled "8"; a sub-cm value
is rejected client-side with the modal staying open. tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
The /gardens page is now home base, backed by the #7 API.
- lib/units.ts: cm <-> meters/feet conversion + formatCm/formatDimensions
(metric "m", imperial "ft/in"). Minimal for #8; #9's geometry lib
consolidates.
- lib/gardens.ts: zod gardenSchema + useGardens/useCreateGarden/
useUpdateGarden/useDeleteGarden (mutations invalidate the list) and
conflictGarden() to pull the fresh row out of a 409.
- GardensPage: loading/empty/error states; empty state prompts a first
garden; responsive card grid (single column on phones).
- GardenCard: name, dimensions formatted per the garden's unit, notes
preview; body links into /gardens/:id, edit/delete kept out of the link.
- GardenFormModal: create/edit with a unit selector; dimensions are entered
in the chosen unit and converted to cm (switching units converts the
current values). A 409 rebases the form onto the server's fresh row.
- DeleteGardenModal: confirmation before removing.
- ui/: Modal (Escape/backdrop close), TextArea, Select.
Verified in a real browser against the embedded binary: create appears
without reload; edit persists (version 2), form prefilled from stored cm
converted back to the garden's unit; delete confirms then removes -> empty
state; an imperial 4 ft x 8 ft garden stores 122 x 244 cm and shows
"4' 0" x 8' 0"". tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi