Bed filling and clearing are reachable only through the agent — and the UI's clear-bed writes N change sets #82

Closed
opened 2026-07-21 22:09:23 +00:00 by steve · 0 comments
Owner

Two problems with one fix.

1. FillRegion has no REST route

internal/service/ops.go header says these bulk ops live on *Service so that "agent tools and any future REST surface inherit the same ACL enforcement". The future REST surface never arrived:

  • FillRegion (ops.go:108), FillNamedRegion (ops.go:314), ClearObject (ops.go:331)
  • Exposed only as fill_region / clear_object in internal/agent/tools.go:36,44
  • The objects group in internal/api/api.go:118-121 has PATCH, DELETE, and POST-plantings. No fill, no clear.

On an instance with no OLLAMA_CLOUD_API_KEY, hex-packed bed filling does not exist at all. It's the most valuable bulk operation in a garden planner, it has the most carefully reasoned geometry in the codebase (hexCenters, the half-spacing edge rule from #75), and it's gated behind an optional LLM that a self-hoster may well not configure.

2. The UI's "clear bed" fires N PATCHes and produces N undo steps

web/src/lib/objects.ts:334-354:

const results = await Promise.allSettled(
  plops.map((p) => api.patch(`/plantings/${p.id}`, { removedAt: today, version: p.version })),
)

Every service mutation auto-scopes its own change set, so clearing a 40-plop bed writes 40 change sets. Undoing it means pressing Undo 40 times.

This directly violates a stated rule in CLAUDE.md:

Multi-row operations pass all their changes to a single record call so they undo as one unit.

ClearObject (ops.go:331) does exactly that — it snapshots the rows, clears them in one UPDATE, and records one change set. So the agent's clear-bed undoes in one click and the UI's clear-bed, for the identical user-facing action, does not. It's also N round trips and a partial-failure surface (objects.ts:346-348) that the service method doesn't have.

Fix

POST /objects/:id/fill and POST /objects/:id/clear, thin handlers over the existing service methods. Point useClearObject at the new endpoint and delete the client-side loop.

Fill needs a region in the body — either a named compass region (NamedRegion already parses "ne", "south half", "all") or an explicit rect. The named form is the ergonomic one and is already written.

The UI affordance for region-fill is the larger half of the work and could be a follow-up: the endpoint alone fixes the agent-only gap and the clear-bed undo bug, and a "fill this bed with…" button in the inspector is a natural next step.

Tests

Per CLAUDE.md, both new routes need API-level tests through the router — this is precisely the case the rule exists for. Worth asserting specifically that clear-bed produces one change set, since that's the behaviour being restored.

Two problems with one fix. ## 1. `FillRegion` has no REST route `internal/service/ops.go` header says these bulk ops live on `*Service` so that "agent tools and any future REST surface inherit the same ACL enforcement". The future REST surface never arrived: - `FillRegion` (`ops.go:108`), `FillNamedRegion` (`ops.go:314`), `ClearObject` (`ops.go:331`) - Exposed only as `fill_region` / `clear_object` in `internal/agent/tools.go:36,44` - The `objects` group in `internal/api/api.go:118-121` has PATCH, DELETE, and POST-plantings. No fill, no clear. **On an instance with no `OLLAMA_CLOUD_API_KEY`, hex-packed bed filling does not exist at all.** It's the most valuable bulk operation in a garden planner, it has the most carefully reasoned geometry in the codebase (`hexCenters`, the half-spacing edge rule from #75), and it's gated behind an optional LLM that a self-hoster may well not configure. ## 2. The UI's "clear bed" fires N PATCHes and produces N undo steps `web/src/lib/objects.ts:334-354`: ```ts const results = await Promise.allSettled( plops.map((p) => api.patch(`/plantings/${p.id}`, { removedAt: today, version: p.version })), ) ``` Every service mutation auto-scopes its own change set, so clearing a 40-plop bed writes **40 change sets**. Undoing it means pressing Undo 40 times. This directly violates a stated rule in CLAUDE.md: > Multi-row operations pass all their changes to a single `record` call so they undo as one unit. `ClearObject` (`ops.go:331`) does exactly that — it snapshots the rows, clears them in one UPDATE, and records one change set. So the **agent's** clear-bed undoes in one click and the **UI's** clear-bed, for the identical user-facing action, does not. It's also N round trips and a partial-failure surface (`objects.ts:346-348`) that the service method doesn't have. ## Fix `POST /objects/:id/fill` and `POST /objects/:id/clear`, thin handlers over the existing service methods. Point `useClearObject` at the new endpoint and delete the client-side loop. Fill needs a region in the body — either a named compass region (`NamedRegion` already parses "ne", "south half", "all") or an explicit rect. The named form is the ergonomic one and is already written. The UI affordance for region-fill is the larger half of the work and could be a follow-up: the endpoint alone fixes the agent-only gap and the clear-bed undo bug, and a "fill this bed with…" button in the inspector is a natural next step. ## Tests Per CLAUDE.md, both new routes need API-level tests through the router — this is precisely the case the rule exists for. Worth asserting specifically that clear-bed produces **one** change set, since that's the behaviour being restored.
steve closed this issue 2026-07-22 02:53:28 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#82