pansy is written by an LLM with a human directing. That belongs near the top of the README, not in a footnote — someone deciding whether to self-host this and point it at their own data should know before they decide, not after. The disclosure says what it actually means in practice (less scrutiny than a hand-written project, read the code, back up the database) rather than a vague "AI-assisted" hedge. CLAUDE.md collects the operational details that otherwise get rediscovered every session: GOWORK=off, the store/service/api seam and why rules go in the middle layer, the two unit scales, version-guarded writes, ErrNotFound-as-existence-mask, the branch/Gadfly/merge/deploy loop, and the env var names that aren't guessable. It also makes the disclosure a standing rule: if the README is ever rewritten, that section survives the rewrite, and the only acceptable edit is a clearer one. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
4.5 KiB
CLAUDE.md
Working notes for Claude Code on pansy. DESIGN.md is the architecture document and stays authoritative; this file is the operational stuff you'd otherwise rediscover every session.
This is a vibe-coded project — say so
pansy is written by an LLM, with a human directing. That is not a footnote, it is a property of the software people should know before they trust it with their garden plans.
Rules, not preferences:
- The README carries a prominent, near-the-top disclosure. It does not get moved below the fold, softened into "AI-assisted", or quietly dropped in a rewrite.
- If you rewrite the README, the disclosure survives the rewrite.
- Anywhere else the project introduces itself (a landing page, a docs site, a package description), it says the same thing.
If you find yourself editing that section, the only acceptable direction is clearer and more honest, never quieter.
Build and test
pansy is a standalone Go module inside a parent workspace, so GOWORK=off is
required or the build picks up sibling modules:
GOWORK=off go build ./...
GOWORK=off go test ./...
cd web && npx tsc --noEmit && npx vitest run && npm run build
make test # both halves
make build # web bundle → embed → CGO_ENABLED=0 static binary
gofmt -l internal/ before committing. Note internal/service/plants_test.go is
already unformatted on main — leave it alone unless you're touching it, so the
diff stays about your change.
Architecture in one paragraph
internal/store (hand-written SQL, modernc.org/sqlite, pure Go) →
internal/service (the seam: every permission check and invariant) →
internal/api (thin gin handlers: decode, call service, encode). Agent tools in
internal/agent are equally thin adapters over the same service methods, so
they inherit permission enforcement for free. If you are about to put a rule in a
handler, put it in the service instead.
Frontend: React 19 + Vite + Tailwind 4 + TanStack Router/Query, built into
internal/webdist/dist and embedded with embed.FS.
Conventions that bite if you miss them
- Everything is centimeters, stored as SQLite
REAL. Imperial is a display and entry concern only, inweb/src/lib/units.ts. Two distinct scales live there: dimension (m/ft — gardens, objects, the garden grid) and spacing (cm/in — plant spacing, bed grid). Mixing them is what #47 was about. - Optimistic concurrency everywhere. Every mutable row has
version; PATCH and DELETE carry it; the store returns(current row, ErrVersionConflict)on mismatch and the API answers 409 with the row under"current". - No-access is
ErrNotFound, notErrForbidden. Existence is masked deliberately.ErrForbiddenmeans "you can see it but may not do that". - Plops (plantings) live in their parent object's local frame, origin at the
object's center,
-yis north. Moving or rotating a bed moves its plants free. - Soft removal: "clear bed" sets
removed_at; the editor readsremoved_at IS NULL. Hard delete is a different operation. - Migrations are numbered
.sqlfiles ininternal/store/migrations/, run at startup, embedded. Never edit one that has shipped. - Every service mutation lands in history (#48). If you add one, record it —
see
internal/service/revisions.go. Multi-row operations pass all their changes to a singlerecordcall so they undo as one unit.
Workflow
Branch → PR → Gadfly reviews it automatically → consider every finding and
fix what's real → merge when the pipeline is green. Do not grade Gadfly findings.
A push to main builds the image and deploys to Komodo; the live instance at
pansy.orgrimmar.dudenhoeffer.casa updates a few minutes later.
Workflow- and config-only changes (CI, this file, docs) go straight to main
without the PR dance.
Planning happens in Gitea issues first: standalone issues under a tracking epic, implemented in later per-issue sessions.
Environment
PANSY_PORT, PANSY_DB, PANSY_BASE_URL, PANSY_REGISTRATION,
PANSY_LOCAL_AUTH, PANSY_OIDC_* — note it's PANSY_DB and PANSY_PORT, not
the _PATH/_ADDR names you might guess. Authentik is the primary IdP;
OIDC-first with local passwords as fallback.
Agent config (v2): OLLAMA_CLOUD_API_KEY and PANSY_AGENT_MODEL (default
ollama-cloud/glm-5.2:cloud), set in Komodo. Model strings pass verbatim to
majordomo.Parse, so a comma-separated spec gives failover for free.
majordomo and executus are sibling repos at ../.