diff --git a/DESIGN.md b/DESIGN.md index cd0a8c3..052426e 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -7,7 +7,7 @@ Work is tracked in Gitea issues; the tracking epic links every piece in dependen ## Decisions - **Placement model:** freeform plops (not a square-foot grid), scaled by real plant spacing. Grid snapping may come later as a toggle. -- **Spacing is a plant-to-plant rule, so bed edges get half of it.** Spacing describes two plants competing for the same soil, light and water; a bed edge is not a competitor, so the outer row owes it only *half* the spacing — the arithmetic behind every square-foot chart (4/square = 6" apart, 3" from the edge). `FillRegion` centres its lattice and lets a plop, which is a *clump* 3 spacings across, cross the edge by up to half a spacing so its outermost plants land at that half-spacing. See `hexCenters`; #75 is what getting this wrong looked like. +- **Spacing is a plant-to-plant rule, so bed edges get half of it.** A bed edge is not a competitor for soil, light or water, so the outer row owes it half the spacing rather than a full one. `FillRegion` centres its lattice accordingly, and lets a plop — a *clump* three spacings across — cross the edge by up to half a spacing so its outermost plants land at that half-spacing. The rule, the square-foot-chart arithmetic behind it, and the failure mode it prevents are written out once in `hexCenters`; #75 is what getting it wrong looked like. - **Stack:** Go 1.26.x backend, module `gitea.stevedudenhoeffer.com/steve/pansy`; React + TypeScript + Vite + Tailwind frontend, production build embedded via `embed.FS` → one static binary (`CGO_ENABLED=0`). - **Users:** multi-user with ownership. Users own gardens; a garden can be shared with other users as viewer (read) or editor (edit content). Owner additionally shares/deletes. - **Auth:** OIDC-first (Authentik is the primary IdP), local argon2id passwords as an optional fallback. diff --git a/internal/service/ops.go b/internal/service/ops.go index 08e4b5f..cc00a13 100644 --- a/internal/service/ops.go +++ b/internal/service/ops.go @@ -114,9 +114,9 @@ func (s *Service) FillRegion(ctx context.Context, actorID, objectID int64, regio } // fillLoaded is the shared body of FillRegion/FillNamedRegion given an object -// already loaded and authorized (roleEditor). It clamps the region to the -// object's bounds, refuses fills over maxFillPlops, and inserts the whole batch -// in one transaction rather than one round-trip per plop. +// already loaded and authorized (roleEditor). It rejects a non-finite region, +// clamps the region to the object's bounds, refuses fills over maxFillPlops, and +// inserts the whole batch in one transaction rather than one round-trip per plop. func (s *Service) fillLoaded(ctx context.Context, actorID int64, o *domain.GardenObject, region Region, plantID int64, spacingOverride *float64) ([]domain.Planting, error) { if !o.Plantable { return nil, domain.ErrInvalidInput