@@ -128,7 +128,8 @@ Makefile (cd web && npm run build) → copy dist → CGO_ENABLED
React 19 + TypeScript + Vite + Tailwind 4 (`@tailwindcss/vite`), `@tanstack/react-router`, `@tanstack/react-query`, zod for API parsing; dev proxy `/api` → Go server.
- **Routes:** `/login`, `/register`, `/gardens` (list), `/gardens/:id` (editor, `?focus=objectId`), `/plants` (catalog). Auth guard on the router root via `/auth/me`.
- **State:** TanStack Query for all server state (editor keyed on `gardens/:id/full`; optimistic mutations with version-conflict rollback). One small Zustand store for ephemeral editor state only: viewport, selection, focused object, active tool, in-flight drag.
- **State:** TanStack Query for all server state (editor keyed on `gardens/:id/full`; optimistic mutations with version-conflict rollback). One small Zustand store for ephemeral editor state only: viewport, selection, focused object, active tool, in-flight drag, and the mobile `mode`.
- **Mobile-first editor: one primary mode (#99).** On a phone the canvas is the whole screen; a bottom mode bar switches which tools dock beneath it — **Fixtures** (the object palette), **Plants** (the seed tray, shown once a bed is focused; focusing a bed puts you in this mode), **Journal**, **Assistant** (the last two open the rail sheet; Assistant is hidden with no model). This replaces the old phone layout where a stacked control column shoved the garden into a corner. Desktop keeps its side-column layout (the mode bar is `md:hidden`) and treats `mode` as an inert hint. History stays reachable as a rail sub-tab rather than a fifth primary mode.
- **Editor components (`web/src/editor/`):** `GardenCanvas` (svg root + viewport g), `useViewport` (use-gesture pan/zoom/pinch), `ObjectShape`, `PlopMarker` (semantic-zoom branching), `Palette` (drag-to-place object kinds), `EditorRail` (the one side panel), `Inspector`, `HistoryPanel`, `PlantPicker`.
- **One rail, tabs inside it.** The inspector, history, journal and assistant all want the same strip of screen; rather than each bolting on its own chrome they are tabs in `EditorRail` — so the canvas is one width instead of a different width per panel, and adding a panel is adding a tab. Selecting an object switches to the Inspector tab automatically, so the rail is never something you operate before you can edit; on a phone the same tabs render in the bottom sheet the inspector already used. Pure geometry helpers (local↔world transforms, unit formatting) in `web/src/lib/geometry.ts`, unit-tested.
🔴Closing Journal/Assistant/History rail forces mode back to 'fixtures' even while a bed is still focused, contradicting the focus→plants invariant and hiding the seed tray behind the object palette
correctness, error-handling · flagged by 5 models
web/src/pages/GardenEditorPage.tsx:645 — Closing a panel rail (journal/history/chat) unconditionally calls setMode('fixtures'), which can violate the "focusing a bed puts you in Plants mode" invariant established at line 178-180. Reachable path: focus a bed (mode→plants via the effect) → tap Journal mode (selectMode('journal') opens the rail, mode=journal) → close the rail. The close handler sets mode back to fixtures while focusedObjectId is still set, so the mobile bottom s…
🪰 Gadfly · advisory
🔴 **Closing Journal/Assistant/History rail forces mode back to 'fixtures' even while a bed is still focused, contradicting the focus→plants invariant and hiding the seed tray behind the object palette**
_correctness, error-handling · flagged by 5 models_
- `web/src/pages/GardenEditorPage.tsx:645` — Closing a panel rail (journal/history/chat) unconditionally calls `setMode('fixtures')`, which can violate the "focusing a bed puts you in Plants mode" invariant established at line 178-180. Reachable path: focus a bed (mode→`plants` via the effect) → tap **Journal** mode (`selectMode('journal')` opens the rail, mode=`journal`) → close the rail. The close handler sets mode back to `fixtures` while `focusedObjectId` is still set, so the mobile bottom s…
<sub>🪰 Gadfly · advisory</sub>
{railTab&&(
<EditorRail
tabs={railTabs}
@@ -543,10 +651,17 @@ export function GardenEditorPage() {
onActivate={setRailTab}
onClose={()=>{
// Only the inspector is *about* the selection, so only closing it
// deselects; dismissing History leaves the canvas as you had it.
// deselects; dismissing a panel leaves the canvas as you had it. Any
// panel rail (journal/history/chat) drops back to a canvas mode so the
// mobile mode bar reappears.
if(railTab==='inspector'){
select(null)
selectPlanting(null)
}else{
// Back to a canvas mode so the mode bar reappears — Plants if you're
// still inside a bed, else Fixtures. (Hardcoding Fixtures here docked
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
🔴 Closing Journal/Assistant/History rail forces mode back to 'fixtures' even while a bed is still focused, contradicting the focus→plants invariant and hiding the seed tray behind the object palette
correctness, error-handling · flagged by 5 models
web/src/pages/GardenEditorPage.tsx:645— Closing a panel rail (journal/history/chat) unconditionally callssetMode('fixtures'), which can violate the "focusing a bed puts you in Plants mode" invariant established at line 178-180. Reachable path: focus a bed (mode→plantsvia the effect) → tap Journal mode (selectMode('journal')opens the rail, mode=journal) → close the rail. The close handler sets mode back tofixtureswhilefocusedObjectIdis still set, so the mobile bottom s…🪰 Gadfly · advisory