Chat panel in the garden editor (#57) #71

Merged
steve merged 3 commits from feat/chat-panel into main 2026-07-21 06:43:22 +00:00
3 Commits
Author SHA1 Message Date
steveandClaude Opus 4.8 77bc672c19 Address Gadfly review on the chat panel
Build image / build-and-push (push) Successful in 9s
The worst finding is one I created two commits ago: the server's `warning` field
— added precisely because a failed save was being swallowed — was never read by
the client. The "I couldn't save this exchange" message went nowhere, which
recreated the exact silence the warning existed to break. It's wired now, and
SSE frames are validated with zod rather than type-asserted, so the next field
added server-side fails loudly instead of vanishing.

Aborting during the initial fetch reported "Could not reach the server." The
read loop already knew an abort was the caller's own doing; the request path did
not, so pressing Stop looked like the network had failed.

Unmount no longer aborts an in-flight turn, and that one is a design bug of my
own making: selecting an object auto-switches the rail to the inspector, so
clicking the canvas mid-turn silently killed the turn — while the canvas is
exactly what you're meant to be watching. The request continues, the exchange is
persisted server-side, and coming back to the tab shows it. Stop still aborts,
but only our READ: the turn keeps running server-side either way, which is why
its work still lands in History.

/capabilities reported cfg.Agent.Ready() while the routes require NewRunner to
have succeeded. A configured-but-unresolvable model would therefore advertise a
chat tab whose first message 404s. It reports the runner's actual state now, and
is a named handler like everything else in that file.

A failed history load rendered as an empty conversation, which looks like the
thread was lost — a much worse thing to believe than "it didn't load". Both the
loading and error states are shown, and a failed "Start over" says so.

Per-step refresh refetched the change history and the conversation, neither of
which can move until the turn commits: up to 2×(N−1) requests per turn for data
that cannot have changed. Split into a canvas-only refresh for steps and a full
one for the end.

describeUndo had its own copy of totalChanges' summation; it uses the helper,
which now accepts an optional counts list. React.ReactNode became an imported
ReactNode, matching every other component.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-21 02:42:47 -04:00
steveandClaude Opus 4.8 5a3fc20fc7 Say at startup why the assistant is off
Build image / build-and-push (push) Successful in 5s
Verifying the live deploy, the agent routes stayed 404 while every other route
from the same build answered — so the binary was current and Agent.Ready() was
simply false. Working out which of the three conditions failed meant reading the
source, because pansy logged the enabled case and said nothing at all about the
disabled one.

It now logs which condition failed, with the hint that actually matters: an
orchestrator's stack-level environment is not the container's environment, and
a key set in one but not passed through to the other looks exactly like a key
that was never set. README says the same thing next to the compose example.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-21 02:40:40 -04:00
steveandClaude Opus 4.8 6c12dcfe2a Chat panel in the garden editor (#57)
Build image / build-and-push (push) Successful in 16s
Gadfly review (reusable) / review (pull_request) Successful in 9m56s
Adversarial Review (Gadfly) / review (pull_request) Successful in 9m56s
The conversational surface, in the editor rather than on its own page. The
reason is the feedback loop: watching the canvas change as the agent works IS
the confirmation, which is exactly what makes "act freely without asking first"
tolerable. It also means the agent never has to guess which garden you mean.

Tool calls surface as they happen, in the app's own vocabulary — "Clearing a
bed", "Looking up a plant" — not raw tool names or JSON. That is the difference
between the panel feeling like it's doing something and feeling like it's hung,
which matters because a replant makes a dozen calls over tens of seconds. An
unknown tool degrades to readable words rather than showing snake_case at the
user, so the client can lag the server by a tool without looking broken.

The canvas refreshes as each step lands, not just at the end. Refreshing only on
completion would put the whole point of siting the chat here — watching it work
— behind the same wait that streaming exists to remove.

Undo sits on the turn itself, so the common case never involves opening the
History panel. It uses #49's useUndo, not a second implementation, which meant
giving that hook an UndoTarget: the chat knows a turn's change set id but not
its tally, and fabricating counts to satisfy the type would have produced a
confidently wrong "1 of 1 changes undone". describeUndo now says "Partly undone"
when it has no denominator rather than inventing one.

The panel is only offered when the instance actually has the assistant
configured, via a new /capabilities read. The routes 404 without a key, so
without this the client would have to probe for a 404 to find out — and a tab
that opens onto an apology is worse than no tab.

Streaming is hand-rolled over fetch rather than EventSource, which can only
issue GETs and this needs a POST body. The wire format is still SSE, so a proxy
that understands it doesn't buffer and the server wouldn't change if EventSource
became viable. Partial frames are buffered across chunks and a malformed frame
is skipped rather than killing a working stream.

Errors read as sentences, and as different sentences: a permission refusal, a
timeout and a model failure want different reactions. Every failure path
refreshes, because something may have landed before it failed — and says where
to look for it.

Closes #57

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