Commit Graph
186 Commits
Author SHA1 Message Date
steveandClaude Fable 5 a19fc2e7fc CLAUDE.md: the pointer-capture / dblclick gotcha
Build image / build-and-push (push) Successful in 9s
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 03:13:35 -04:00
steveandClaude Fable 5 916b2989f5 Canvas: make double-click-to-plant work on desktop
Build image / build-and-push (push) Successful in 19s
Gadfly review (reusable) / review (pull_request) Successful in 8m43s
Adversarial Review (Gadfly) / review (pull_request) Successful in 8m43s
"Double-click a bed to plant it" has done nothing since the Organic rebuild:
track() captures the pointer on the SVG root, and pointer capture retargets
the compatibility click/dblclick events to the root, so the onDoubleClick
handler on each object's <g> never fired. A double-click only selected.

The double press is now detected in objDown itself — two presses on the
same object within 400 ms and 12 px — which capture cannot retarget. The
second press focuses the bed and starts no drag, so its pointerup has
nothing to select into Plot; a plop in an unfocused bed already delegates
to objDown, so double-clicking a plant focuses its bed too.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 03:12:13 -04:00
steve a37122fc77 Merge pull request 'Assistant: scroll the thread to a new reply for real' (#134) from fix/assistant-thread-scroll into main
Build image / build-and-push (push) Successful in 13s
2026-08-23 07:10:26 +00:00
steveandClaude Fable 5 82fbeb121b Address #134 review: follow the thread only while pinned to its end
Build image / build-and-push (push) Successful in 11s
The instant scroll runs on every step of a turn, so it now follows new
content only while the view is at the end of the thread (within 80px).
Scrolling up to read something stays put until the person comes back
down or sends the next message, which returns them to the end.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 03:08:48 -04:00
steve 62c0ff2531 Merge pull request 'Editor: the toolkit is a rail tab on desktop, and the rail is wider' (#133) from feat/toolkit-in-the-rail into main
Build image / build-and-push (push) Successful in 8s
2026-08-23 07:05:20 +00:00
steveandClaude Fable 5 7a5b9d2ea1 Address #133 review: the toolkit has one home now
Build image / build-and-push (push) Successful in 12s
The `embedded` prop was always true, which left the card branch dead;
the component is simply the rail's tab now. The focus comment is one
line, and the default tab says why it is Plot and not the first tab.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 03:04:22 -04:00
steveandClaude Fable 5 e184ae5565 Assistant: scroll the thread to a new reply for real
Build image / build-and-push (push) Successful in 20s
Gadfly review (reusable) / review (pull_request) Successful in 2m2s
Adversarial Review (Gadfly) / review (pull_request) Successful in 2m3s
scrollIntoView({ behavior: 'smooth' }) on the thread's nested scroller never
moved it in Chrome — measured live: scrollTop stayed 0 after sending and
after the reply, while the instant form scrolled to the end. A long
conversation therefore showed its oldest messages after every turn, with
the new reply out of view below. Instant it is.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 03:02:50 -04:00
steveandClaude Fable 5 10275f5e1c Editor: the toolkit is a rail tab on desktop, and the rail is wider
Build image / build-and-push (push) Successful in 20s
Gadfly review (reusable) / review (pull_request) Successful in 5m31s
Adversarial Review (Gadfly) / review (pull_request) Successful in 5m31s
The handoff drew the toolkit as a 216px card left of the plan. That width
was better spent on the plan and the rail, so the toolkit is now the rail's
first tab — Toolkit / Plot / Journal / History / Assistant — rendered
`embedded` (no card chrome, no heading; the tab is the heading), the grid
is two columns, and the rail grows from 336 to 400px.

Focusing a bed (double-click) switches the rail to Toolkit, because that is
where the plant palette now lives; a single click still selects into Plot.
The phone chrome is untouched: it never used the card.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:54:45 -04:00
steve 0aabccf1bd Merge pull request 'Agent: a turn that changed nothing cannot say it did' (#132) from feat/agent-honest-turns into main
Build image / build-and-push (push) Successful in 18s
2026-08-23 06:52:43 +00:00
steveandClaude Fable 5 d4eb62a2ba Address #132 review: one verb list, self-reporting tools, rune-safe log
Build image / build-and-push (push) Successful in 8s
- changeClaim is built from one changeVerbs list; the opener is just
  done/fixed/undone so an informational "Updated totals:" can't trip it.
- public_link (get reads) and undo_change (nothing left to revert) are
  self-reporting: their success no longer counts as a change by name; the
  adapter says whether they changed something (noteChange / didChange).
- whenMissing covers the object and plant tools too (move/update/delete
  object, clear/remove plantings by object, update/delete plant).
- The step summary cuts on a rune boundary.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:49:55 -04:00
steveandClaude Fable 5 d0ca56b79b Agent: a turn that changed nothing cannot say it did
Build image / build-and-push (push) Successful in 23s
Gadfly review (reusable) / review (pull_request) Successful in 7m19s
Adversarial Review (Gadfly) / review (pull_request) Successful in 7m20s
Live, asked to delete a journal entry and later a seed lot, the model
answered "Done — I've deleted it" both times having deleted nothing; each
was still there a turn later. The prompt already forbade that. Now the run
catches it: honestReply appends a correction when the reply claims a change
("Done", "I've deleted…") and no non-read-only tool call succeeded, and logs
the steps so the mechanism can be read off the log next time.

Alongside: the id-taking tools turn a bare "not found" into a message that
names what was missing and which tool lists the ids ("nothing was changed"),
the two delete descriptions say to look the id up in THIS turn, and the
prompt says an error result means the thing did not happen.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:37:06 -04:00
steve 07f33e62db Merge pull request 'Agent: sharing tools that ask first, and a hard delete for a misplaced plop' (#131) from feat/agent-sharing-tools into main
Build image / build-and-push (push) Successful in 7s
2026-08-23 06:22:11 +00:00
steveandClaude Fable 5 608ef7c58e Address #131 review: unknown link action is unknown, typed views, one share shape
Build image / build-and-push (push) Successful in 13s
- public_link checks the action before the confirmation gate, so an unknown
  action is told so instead of being asked to confirm nothing in particular
  (the 4/4 finding).
- linkView is a struct like shareView; toShareView builds the five share
  results, and a fresh share is read back so it carries the person's name
  like every other path.
- PublicShareURL trims a trailing slash off a hand-built base URL.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:21:04 -04:00
steveandClaude Fable 5 f985c264f8 Agent: sharing tools that ask first, and a hard delete for a misplaced plop
Build image / build-and-push (push) Successful in 10s
Gadfly review (reusable) / review (pull_request) Successful in 4m18s
Adversarial Review (Gadfly) / review (pull_request) Successful in 4m18s
list_shares, share_garden, remove_share and public_link (get / enable /
rotate / disable) wrap the sharing service. They change who can see a garden
beyond the screen, so they are gated twice: the prompt tells the model to say
exactly what it would do and ask, and the tools refuse without confirmed=true,
which their descriptions allow only after a yes in the conversation. The
refusal names the action, so the question the model asks is precise.

share_garden changes the role of an existing share instead of failing on it;
remove_share takes the email list_shares reports; an unknown email explains
that the person has to sign in once first. public_link returns the address
(PANSY_BASE_URL + /g/<token>, via the new Service.PublicShareURL), never a
bare token.

delete_planting is the hard delete for a plop that was never really planted,
as opposed to remove_planting's "it came out"; it is recorded, so undoable.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:12:51 -04:00
steve 97008f5a92 Merge pull request 'Agent: catalog and garden tools, and a ready date on every describe group' (#130) from feat/agent-catalog-tools into main
Build image / build-and-push (push) Successful in 7s
2026-08-23 06:12:25 +00:00
steveandClaude Fable 5 c9076e84c4 Address #130 review: one toolCaller helper for the tool tests
Build image / build-and-push (push) Successful in 7s
The call/mustCall closures were copied between TestRecordKeepingTools and
TestCatalogAndGardenTools; both now use a file-level toolCaller. The other
notes are left as they are: the 'nothing to change' guard enumerates the
args on purpose (it is the tool's own contract, next to the struct it
checks), and wrapping a sentinel with %w is how every readable refusal in
this package is built.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:11:27 -04:00
steveandClaude Fable 5 b4c8007977 Agent: catalog and garden tools, and a ready date on every describe group
Build image / build-and-push (push) Successful in 11s
Gadfly review (reusable) / review (pull_request) Successful in 4m42s
Adversarial Review (Gadfly) / review (pull_request) Successful in 4m42s
- update_seed_lot / delete_seed_lot: correct or drop a recorded purchase
  ("it was three packets, not two"); the plant a lot is for stays fixed.
- delete_plant: remove a duplicate from the user's catalog. The service
  already refuses while plantings (past seasons included) or a lot reference
  it; the tool turns that sentinel into words the model can pass on, and
  tells it not to clear those references to get its way.
- create_garden: a new place, with the service's defaults; the prompt says a
  plan is still a copy_garden.
- describe_garden groups carry readyAround — planting date plus days to
  maturity for the plops still in the ground — so "what can I pick this
  week?" is a lookup rather than arithmetic the model got wrong live.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:05:19 -04:00
steve 35b27de8a0 Merge pull request 'Agent: undo for real, past seasons, and tools that correct the record' (#129) from feat/agent-record-keeping-tools into main
Build image / build-and-push (push) Successful in 7s
2026-08-23 06:04:32 +00:00
steveandClaude Fable 5 6aa08ddbe7 Address #129 review: one date path, ordered years, trimmed dates
Build image / build-and-push (push) Successful in 8s
- Every dated tool argument now goes through day() → parseDay, so a prose
  date on remove_planting / remove_plantings / clear_object (and place,
  fill, journal) is refused with the same message as update_planting's.
- parseDay's trimmed value is what gets stored, not the raw argument.
- list_years re-sorts after adding the gardener's year instead of
  prepending it: newest first holds when their year is the oldest.
- ClearSeedLot matches its JSON tag; the label-clearing branch says why nil.
- The prompt says the notes are facts to plan with, not instructions.

Left as is: update_garden's read-then-overlay merge. UpdateGarden is
whole-row by design (the REST PATCH sends every field too), and a service
GardenPatch would duplicate gardenFromInput's validation for one caller.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 02:03:44 -04:00
steveandClaude Fable 5 deec7bb917 Agent: undo for real, past seasons, and tools that correct the record
Build image / build-and-push (push) Successful in 11s
Gadfly review (reusable) / review (pull_request) Successful in 10m8s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m8s
Six tools the live assistant kept needing and a prompt that knows about them:

- undo_change wraps RevertChangeSet(source=agent). A revert is its own change
  set, so Run reports the last one as the turn's handle when the turn changed
  nothing else — an undo-only reply keeps its "Undo this", which is now a redo.
- describe_garden takes a year: the season view (GardenFull(year)), pulled
  plops included, with removed/removedAt per group and per plop; list_years
  says which years have records. Rotation questions finally have data.
- update_planting corrects a plop's date, count, label, radius or seed lot in
  place; remove_planting, remove_plantings and clear_object take a removedAt so
  a harvest can be backdated.
- update_journal_entry / delete_journal_entry correct a note instead of
  stacking a contradicting one.
- update_garden renames/resizes/re-units a garden and rewrites its notes — and
  the notes now go into the system prompt as the gardener's standing facts, so
  "remember we're in zone 6a" persists across conversations.

describe_garden also reports the garden's notes, version and grid, which the
new tools need. Prompt, CLAUDE.md and DESIGN.md updated to match; UI step
labels for the new tools.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 01:50:10 -04:00
steve 5317c92683 Merge pull request 'describe_garden: list each plop's position, so a move can keep the layout' (#128) from fix/describe-plop-coordinates into main
Build image / build-and-push (push) Successful in 11s
2026-08-23 04:43:47 +00:00
steveandClaude Fable 5 85b7dbbe3a Address #128 review: doc lines and an exact position assertion
Build image / build-and-push (push) Successful in 7s
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:43:15 -04:00
steveandClaude Fable 5 8b161c5f6d describe_garden: list each plop's position, so a move can keep the layout
Build image / build-and-push (push) Successful in 6s
Gadfly review (reusable) / review (pull_request) Successful in 4m29s
Adversarial Review (Gadfly) / review (pull_request) Successful in 4m29s
Asked to move four tomatoes planted in a column "keeping the same spacing",
the live assistant re-laid them as two pairs: the per-plop listing said
"north" and "south" and nothing else. Each listed plop (and list_plantings)
now carries xCm/yCm in the object's local frame.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:37:23 -04:00
steve d884f62762 Merge pull request 'Agent: what a day of live use asked for' (#127) from feat/agent-live-test-fixes into main
Build image / build-and-push (push) Successful in 8s
2026-08-23 04:29:22 +00:00
steveandClaude Fable 5 ac9f6e8c63 A fill aimed entirely outside its object is an error, and the test says so
Build image / build-and-push (push) Successful in 5s
TestFillRegionOutsideObjectPlantsNothing pinned the old silent success;
the #127 review asked for the error, and the agent is the caller it helps.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:28:44 -04:00
steveandClaude Fable 5 d3d7238259 Address #127 review: quote the garden name, validate rectangles, require plantId
Build image / build-and-push (push) Successful in 17s
- The plan-name line of the system prompt interpolates the garden's name
  with %q like the rest of the prompt: any editor can rename a garden, and a
  name with a newline in it must not read as an instruction.
- fill_region refuses an inverted rectangle with its corners named, and a
  rectangle that misses the bed (or only touches its edge) is an error from
  the service rather than a successful fill of nothing.
- remove_plantings requires plantId; omitted it would remove plant 0 and
  report success.
- historyEntry.Undo → UndoOf (it holds the reverted change set's id).
- remove_planting's description names list_plantings as an id source.
- RemovePlanting takes the removal date itself; the dateless wrapper had no
  callers left.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:27:48 -04:00
steveandClaude Fable 5 a1baf4b871 Fill: refuse an empty rectangle; list plops whose plant is gone unnamed
Build image / build-and-push (push) Successful in 12s
A blank region name with a zero-area Region reached hexCenters, whose
tiny-region rule plants one plop in the middle — a caller that said nothing
about where got a plop at the centre. ListObjectPlantings also failed the whole
listing if one plop's plant no longer existed; it now lists that plop unnamed.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:16:54 -04:00
steveandClaude Fable 5 bc14bbed0d Agent: what a day of live use asked for
Build image / build-and-push (push) Successful in 19s
Gadfly review (reusable) / review (pull_request) Successful in 10m2s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m2s
Twenty-one prompts against the live assistant found one fabricated success,
a model that believed it was 2025, and a describe_garden that was ~450 plop
entries per turn. This is the set of fixes, each traceable to a finding:

- The gardener's LOCAL day travels with the turn (`today` on POST /agent/chat,
  sent by the UI like plantedAt) into the system prompt and every dated tool
  default. Left to guess, the model dated journal entries a year back; left to
  the server, a 9 pm fill landed on UTC's tomorrow.
- describe_garden groups plops by plant — count, where, planted date, days to
  maturity — and lists ids only for groups of ≤ 8; list_plantings spells a big
  group out on demand and remove_plantings acts on one plant in a bed ("take
  the beets out, leave the garlic"), which used to mean 116 single removals.
- New tools: move_planting (keeps the planting date; across beds via the new
  MovePlanting, which is why the store's UPDATE now writes object_id),
  update_plant, read_history, copy_garden (the "<garden> — <year>" plan
  convention). fill_region takes an explicit local rectangle and a seedLotId;
  place_planting's radius defaults to one plant (spacing/2) instead of a guess.
- The system prompt states the date and the gardener's units, forbids claiming
  a change no tool made, says it cannot undo and points at the Undo button,
  asks before clearing beds on an ambiguous sentence, and stops narrating its
  own plantings into the journal.
- A mutation aimed at ANOTHER garden inside a turn is recorded under that
  garden as its own change set, not filed into the open scope.
- UI: the thread scrolls inside the Assistant panel so the composer stays
  put; every tool has a step label; wide tables stay inside the bubble.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:14:41 -04:00
steve f0aefb5378 Merge pull request 'Make request deadline extensions reach the socket behind the logging middleware' (#126) from fix/sse-deadlines-behind-middleware into main
Build image / build-and-push (push) Successful in 9s
2026-08-23 03:10:37 +00:00
steveandClaude Fable 5 68cb686d60 Address #126 review: one home for the middleware rationale
Build image / build-and-push (push) Successful in 20s
The why-a-controller-can't-reach-the-socket story was told in full in
deadlines.go, agent.go, the test, and CLAUDE.md. It lives in deadlines.go
now; the others say what they need to and point there.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 23:09:36 -04:00
steveandClaude Fable 5 2a903f6428 Make request deadline extensions reach the socket behind the logging middleware
Build image / build-and-push (push) Successful in 6s
Gadfly review (reusable) / review (pull_request) Successful in 10m9s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m10s
Long agent turns were cut at exactly 30s on the live instance with "The
connection dropped partway through." — the #78 failure, which its tests
said was fixed. The tests host openEventStream on a bare gin.New(); in
production, slog-gin replaces c.Writer with a wrapper that embeds the
gin.ResponseWriter interface, which has no Unwrap, so the ResponseController
built from the handler's writer can't reach the connection and every
SetWriteDeadline returns ErrNotSupported. The stream fell back to the
server's absolute WriteTimeout; the first write past it failed, cancelled
the request context, and closed the socket under the client mid-frame.
The scan upload's read/write extensions failed the same way, with the
errors discarded.

captureController now runs first on the engine and stashes a controller
built before anything wraps the writer; openEventStream and scanSeedPacket
take it from responseController(c). The regression tests run the stream
through New() — the real stack, in the real order — and check from the
client side; the scan path logs once instead of swallowing the error.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:56:21 -04:00
steve 5622b1accd Merge pull request 'Smoke-sweep fixes: exact saves, local dates, safer remove, readable markers' (#125) from fix/smoke-sweep into main
Build image / build-and-push (push) Successful in 7s
2026-08-23 02:26:06 +00:00
steveandClaude Fable 5 0d95578c6a Address #125 review: memoized ink, one fallback color, reactive copy name
Build image / build-and-push (push) Successful in 10s
- monogramInk is memoized by color string; the canvas asks for every
  visible plop on every frame of a pan (Gadfly, 2/4 models).
- FALLBACK_PLANT_COLOR lives in lib/plants and is used by the canvas, the
  inspector and the garden thumbnail instead of three raw '#97a97c's.
- CopyDialog keeps its proposed "<base> — <year>" in step with the gardens
  list until the person edits the name, so a list that loads after the
  dialog opens can't leave a taken year in the field.
- GardenCard: reflowed the summary comment; no dead fallback on a plan
  name that's already known to parse.
- today() has one import path (lib/dates); the journal re-export is gone.
- CLAUDE.md says what the inspector actually does (a text-compare guard)
  rather than claiming it uses LengthField.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:25:02 -04:00
steveandClaude Fable 5 157e04ed24 Skip no-op saves in the edit dialogs; clear a stale model-spec error
Build image / build-and-push (push) Successful in 26s
A Save that changed nothing still sent a PATCH, which bumped the row's
version and landed an "Edited garden settings" step in History that undid
nothing — the drift is gone since the last commit, but the write was still
there. Both dialogs now close without a request when every field matches
the loaded row.

In Settings, a rejected model spec's reason stayed under the field after
the field was blanked back to the saved value; committing an unchanged
value now clears it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:22:22 -04:00
steveandClaude Fable 5 27f658c1f7 Smoke-sweep fixes: exact saves, local dates, safer remove, readable markers
Build image / build-and-push (push) Successful in 2m55s
Gadfly review (reusable) / review (pull_request) Successful in 8m59s
Adversarial Review (Gadfly) / review (pull_request) Successful in 8m59s
- 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]>
2026-08-22 22:11:12 -04:00
steve 05392ee0db Merge pull request 'Replace the UI with the Organic design handoff' (#124) from feat/organic-ui into main
Build image / build-and-push (push) Successful in 6s
2026-08-22 23:40:23 +00:00
steveandClaude Fable 5 2af79012e4 Address #124 review: a failed history refetch must not undo anything
Build image / build-and-push (push) Successful in 11s
Gadfly (error-handling lens): react-query keeps the stale pages in `data`
when a refetch fails, so `useUndoLast` would fall through and revert the step
BEFORE the one just made — the exact outcome the refetch exists to prevent.
Bail out with a toast unless the refetch succeeded.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 19:36:51 -04:00
steve dc9ebbe51b Merge branch 'main' into feat/organic-ui
Build image / build-and-push (push) Successful in 7s
2026-08-22 19:16:57 -04:00
steveandClaude Fable 5 b6981fbcb1 ci: re-pin Gadfly to a reusable workflow whose image still exists
Build image / build-and-push (push) Successful in 15s
The pinned gadfly commit (c9dab69) hard-coded the reviewer image
gadfly:sha-b37cd09, which has since been pruned from the registry, so every
review on a new PR failed in one second at "manifest unknown" (#124's did).
gadfly's current main (8adeeea) runs the reviewer as a job container whose tag
resolves at run time — reviewer_tag input → GADFLY_REVIEWER_TAG var → a baked
fallback (sha-b850e35, verified present) — so a retired tag can't strand the
consumer stubs again. Inputs and secrets are a superset of what this stub
forwards.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 19:16:56 -04:00
steveandClaude Fable 5 5a9ae58a2a Tidy: store never writes an undefined ghost; plant card toggle is the face, not the card
Build image / build-and-push (push) Successful in 11s
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 19:14:08 -04:00
steveandClaude Fable 5 52b2c09a9e Replace the UI with the Organic design handoff (docs/design_handoff_pansy_ui)
Build image / build-and-push (push) Successful in 31s
Gadfly review (reusable) / review (pull_request) Failing after 1s
Adversarial Review (Gadfly) / review (pull_request) Failing after 1s
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]>
2026-08-22 19:12:29 -04:00
steve 18b36870d4 added claude design's ui framework 2026-08-22 18:13:36 -04:00
steve cac26286b1 Merge pull request 'Let plop notes be written from the UI (#85 item 5)' (#123) from feat/plop-journal-notes into main
Build image / build-and-push (push) Successful in 13s
2026-07-23 01:42:02 +00:00
steve c432fe9199 Merge pull request 'Agent: add the corrective tools the toolbox was missing (#85 item 3)' (#122) from feat/agent-corrective-tools into main
Build image / build-and-push (push) Successful in 9s
2026-07-23 01:41:38 +00:00
steveandClaude Opus 4.8 f14875557b Address #123 review: viewers can read plop notes; tighten the API
Build image / build-and-push (push) Successful in 11s
- The "add note" affordance no longer hides from viewers (it claimed
  parity with the bed inspector but gated on !readOnly). A viewer now sees
  "📓 Notes about this plant" and can open the plop's journal to read it;
  the composer stays edit-gated, so they can't write. Real parity now.
- onScopePlantingChange is required, matching onScopeChange (its bed twin),
  so a caller can't pass a plop scope with no way to clear it. Dropped the
  now-dead guard on the "Show all" button.
- Pulled the plop-over-bed scope-label priority into one `scopeLabel`,
  shared by the filter's sibling logic and the composer, so they can't
  drift; trimmed the invariant comment that was restated a third time.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-22 21:41:11 -04:00
steveandClaude Opus 4.8 7b150275ae Address #122 review: pageable read_journal, service-clock removal
Build image / build-and-push (push) Successful in 20s
- read_journal now takes an offset, so the hasMore it returns is
  actionable — an agent can page a journal longer than 50 entries.
- remove_planting goes through a new service RemovePlanting that stamps
  removed_at from s.now() (the injectable clock ClearObject and the fill
  path use), instead of the adapter computing the date off the wall clock.
  It delegates to UpdatePlanting, so the role check, version guard and
  history record are unchanged. Drops the now-unused time import.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-22 21:39:20 -04:00
steve 9e227e29eb Merge pull request 'Reclaim mobile chrome: drop the editor's top banner, un-cramp the assistant' (#121) from feat/mobile-space-polish into main
Build image / build-and-push (push) Successful in 20s
2026-07-23 01:32:59 +00:00
steveandClaude Opus 4.8 256fa4f29f Address #121 review: keep sign-out reachable, dvh peek, exact height
Build image / build-and-push (push) Successful in 12s
- Fold the account menu into the editor's mobile strip. Hiding the global
  header removed the only sign-out on mobile in the editor; the strip now
  carries it, so the space win stays but sign-out is one tap away.
- EditorRail peek cap vh → dvh, matching the dvh-bounded editor column, so
  it can't overrun the visible viewport and push the mode bar off-screen.
- Mobile editor height 4rem → 3rem: with the header hidden, only <main>'s
  py-6 (3rem) is outside the editor, so 4rem left ~16px dead. Comment
  corrected.
- Trim two comments that duplicated nearby docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-22 21:31:35 -04:00
steveandClaude Opus 4.8 7015148edf Let plop notes be written from the UI (#85 item 5)
Build image / build-and-push (push) Successful in 19s
Gadfly review (reusable) / review (pull_request) Successful in 5m29s
Adversarial Review (Gadfly) / review (pull_request) Successful in 5m30s
journal_entries.planting_id was modelled, accepted by the API, and the
JournalPanel already rendered a "planting" badge for such entries — but
nothing in the UI ever created one. A badge for a state the UI couldn't
produce.

Give the plop the same "add note" affordance the bed inspector has:
- PlopInspector gains an onAddNote button ("📓 Add a note about this
  plant"), shown only to an editor (a viewer can't write notes).
- The editor store gains a journalPlantingId scope beside journalObjectId.
  The two are mutually exclusive — each setter clears the other — so the
  journal filter is never double-scoped.
- JournalPanel filters by plantingId when that scope is set, shows a
  "Notes about one planting · Show all" banner, and its composer attaches
  new notes to the plop. Empty-state copy updated to match.

Two taps from a selected plant to typing, mirroring the bed flow. No
backend change — the API already accepted plantingId.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-22 21:28:12 -04:00
steveandClaude Opus 4.8 887a3c2cc6 Agent: add the corrective tools the toolbox was missing (#85 item 3)
Build image / build-and-push (push) Successful in 6s
Gadfly review (reusable) / review (pull_request) Successful in 10m8s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m8s
The toolbox could create and move but not delete or resize; write the
journal but not read it; clear a whole bed but not pull one plant; report
seed remaining but not record a purchase. Close those gaps with thin
adapters over the SAME service methods the REST API uses, so they inherit
the permission checks unchanged:

  read_journal    → ListJournal   (the write/read asymmetry, most visible)
  update_object   → UpdateObject  (resize / rotate / rename / plantable)
  delete_object   → DeleteObject  (counterpart to create_object)
  remove_planting → UpdatePlanting (soft-remove ONE plop, like clear does)
  list_seed_lots  → ListSeedLots
  record_seed_lot → CreateSeedLot (record a purchase; "I bought 2 packets")

To address a single plop the agent needs its id + version, so
DescribePlanting now carries both — the same way DescribeObject.Version
already lets it edit an object. remove_planting soft-removes (removed_at =
today), mirroring clear_object, so the plant stays in planting history and
the change is undoable.

Deferred deliberately: an undo/revert tool needs a way to list recent
change sets to get a changeSetId, which is a larger addition; noted on the
issue for a follow-up.

Tested through the tool layer (TestCorrectiveTools): resize, single-plop
removal, journal read-back, seed-lot record+list, and delete.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-22 21:23:46 -04:00