Build image / build-and-push (push) Successful in 6s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
129 lines
6.1 KiB
Markdown
129 lines
6.1 KiB
Markdown
# CLAUDE.md
|
|
|
|
Working notes for Claude Code on pansy. [DESIGN.md](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.
|
|
|
|
## Keep the docs true
|
|
|
|
Docs rot silently and nobody notices until someone follows them and it doesn't
|
|
work. Treat them as part of the change, not follow-up work:
|
|
|
|
- **README.md** — update it in the *same commit* whenever you add or change an
|
|
environment variable, a route worth knowing about, a build or run command, or
|
|
anything in the Docker/Compose example. The env var table and the compose
|
|
snippet are the two things people copy, so they're the two that hurt most when
|
|
they're stale.
|
|
- **DESIGN.md** — update it when the architecture actually changes: a new table,
|
|
a new package, a new API surface, a decision that supersedes one written there.
|
|
Not for every implementation detail.
|
|
- **CLAUDE.md** — this file. Add a convention here the moment you find yourself
|
|
rediscovering it.
|
|
- **Examples must run.** If you change something an example depends on, fix the
|
|
example. A snippet that references an issue number as "once #5 lands" after #5
|
|
has landed is a bug in the docs.
|
|
|
|
When you touch a file, glance at whether the comments around your change are
|
|
still true. Stale comments are worse than none — the next reader believes them.
|
|
|
|
## 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:
|
|
|
|
```sh
|
|
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, in `web/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`, not `ErrForbidden`.** Existence is masked
|
|
deliberately. `ErrForbidden` means "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, `-y` is north. Moving or rotating a bed moves its plants free.
|
|
- **Soft removal**: "clear bed" sets `removed_at`; the editor reads
|
|
`removed_at IS NULL`. Hard delete is a different operation.
|
|
- **Migrations** are numbered `.sql` files in `internal/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 single `record` call 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: `OLLAMA_CLOUD_API_KEY`, `PANSY_AGENT_MODEL` (default
|
|
`ollama-cloud/glm-5.2:cloud`) and `PANSY_AGENT_ENABLED`, set in Komodo. Model
|
|
strings pass verbatim to `majordomo.Parse`, so a comma-separated spec gives
|
|
failover for free — don't parse that grammar in pansy.
|
|
|
|
`majordomo` is a **real dependency** now, resolved from the Gitea instance as a
|
|
pseudo-version. There is no `replace` directive and there must not be one: a
|
|
`replace` pointing at `../majordomo` builds on your laptop and breaks the Docker
|
|
build, which has no sibling checkout. `executus` is a sibling repo at `../` and
|
|
is not a dependency.
|
|
|
|
The `majordomo` build tag is **gone**. Don't reintroduce it — an untagged CI that
|
|
never compiles the agent is worse than a slightly larger binary.
|