Address #127 review: quote the garden name, validate rectangles, require plantId
Build image / build-and-push (push) Successful in 17s
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]>
This commit is contained in:
@@ -276,6 +276,14 @@ func (s *Service) fillLoaded(ctx context.Context, actorID int64, o *domain.Garde
|
||||
}
|
||||
|
||||
region = region.clampTo(o.WidthCM/2, o.HeightCM/2)
|
||||
if region.MaxX <= region.MinX || region.MaxY <= region.MinY {
|
||||
// An explicit rectangle that misses the object, or only touches its edge.
|
||||
// Planting nothing and reporting success would read as "done" to a caller
|
||||
// that aimed at the wrong coordinates (typically the agent mixing up the
|
||||
// garden frame and the object's local one) — and a rectangle clamped to a
|
||||
// line would get hexCenters' one-plop-in-the-middle rule, on the edge.
|
||||
return nil, fmt.Errorf("%w: the region lies outside the object", domain.ErrInvalidInput)
|
||||
}
|
||||
centers, total := hexCenters(region, radius, edgeInset(radius, spacing, layout), maxFillPlops)
|
||||
if total > maxFillPlops {
|
||||
return nil, domain.ErrInvalidInput // region too large for this spacing; ask for less
|
||||
|
||||
@@ -797,4 +797,15 @@ func TestFillByRectangleAttributesSeed(t *testing.T) {
|
||||
t.Errorf("empty rectangle %+v: err = %v, want ErrInvalidInput", r, err)
|
||||
}
|
||||
}
|
||||
// A rectangle that misses the bed (it is 240 wide, so ±120) — or only
|
||||
// touches its edge — is an error, not a successful fill of nothing.
|
||||
for _, r := range []Region{{MinX: 200, MinY: -10, MaxX: 300, MaxY: 10}, {MinX: 120, MinY: -10, MaxX: 200, MaxY: 10}} {
|
||||
if _, err := s.Fill(ctx, owner, bed.ID, FillSpec{Region: r, PlantID: garlic.ID}); !errors.Is(err, domain.ErrInvalidInput) {
|
||||
t.Errorf("off-bed rectangle %+v: err = %v, want ErrInvalidInput", r, err)
|
||||
}
|
||||
}
|
||||
// Partly outside is fine: the part inside gets planted.
|
||||
if created, err := s.Fill(ctx, owner, bed.ID, FillSpec{Region: Region{MinX: 80, MinY: -10, MaxX: 300, MaxY: 10}, PlantID: garlic.ID}); err != nil || len(created) == 0 {
|
||||
t.Errorf("overhanging rectangle: %d plops, %v; want some", len(created), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,18 +186,12 @@ func (s *Service) UpdatePlanting(ctx context.Context, actorID, plantingID int64,
|
||||
}
|
||||
|
||||
// RemovePlanting soft-removes a single plop — the one-plop counterpart to
|
||||
// ClearObject. It stamps removed_at from the service clock (s.now()), the same
|
||||
// UTC day ClearObject and the fill path default to; RemovePlantingOn is the
|
||||
// form for a caller that knows the gardener's local day. Both delegate to
|
||||
// UpdatePlanting for the editor-role check, version guard and history record.
|
||||
func (s *Service) RemovePlanting(ctx context.Context, actorID, plantingID, version int64) (*domain.Planting, error) {
|
||||
return s.RemovePlantingOn(ctx, actorID, plantingID, version, nil)
|
||||
}
|
||||
|
||||
// RemovePlantingOn is RemovePlanting with the removal date supplied (YYYY-MM-DD)
|
||||
// by a caller that knows the person's local day. nil keeps the service clock's
|
||||
// UTC today.
|
||||
func (s *Service) RemovePlantingOn(ctx context.Context, actorID, plantingID, version int64, removedAt *string) (*domain.Planting, error) {
|
||||
// ClearObject, used by the agent's remove_planting tool. removedAt (YYYY-MM-DD)
|
||||
// is the day the caller knows it happened — the gardener's local day; nil
|
||||
// stamps the service clock's UTC today, the same default ClearObject and the
|
||||
// fill path use. Delegates to UpdatePlanting for the editor-role check, version
|
||||
// guard and history record.
|
||||
func (s *Service) RemovePlanting(ctx context.Context, actorID, plantingID, version int64, removedAt *string) (*domain.Planting, error) {
|
||||
if !validDatePtr(removedAt) {
|
||||
return nil, fmt.Errorf("%w: removedAt must be a YYYY-MM-DD date", domain.ErrInvalidInput)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user