# AGENT Instructions for veans Things to know before touching this submodule that aren't obvious from reading the code. The parent repo's `CLAUDE.md` covers the rest of Vikunja; this file is veans-specific. ## Module layout - `veans/` is its own Go module (`code.vikunja.io/veans`), separate from the parent. Don't try to import `code.vikunja.io/api/...` — that pulls XORM into the CLI binary. Wire types live in `internal/client/types.go` as plain JSON-tagged structs that mirror the parent models. - License headers are enforced by `goheader` in `veans/.golangci.yml`. Every new `.go` file needs the AGPLv3 banner from `veans/code-header-template.txt` (a copy of the parent's, kept local so the linter resolves the path relative to this module). ## Building and testing - `mage build` → `./veans` binary. The `Aliases` map in `magefile.go` routes bare names like `mage test` to `Test.All` — without aliases, mage rejects namespace invocations ("Unknown target specified"). - Unit tests: `mage test` (passes `-short`) or `go test -short ./...`. The e2e package's `TestMain` gates the suite on `-short`, mirroring the parent monorepo's `pkg/webtests` convention. Without `-short` and without `VEANS_E2E_API_URL` set, the e2e tests fail loudly with a "configure or pass -short" hint. - E2e tests: `mage test:e2e` (no `-short`). Assumes an externally- running Vikunja at `VEANS_E2E_API_URL`. The harness seeds its own admin user via `PATCH /api/v1/test/users` — same mechanism the playwright suite uses — so the API must be booted with `VIKUNJA_SERVICE_TESTINGTOKEN=` and the same value passed in via `VEANS_E2E_TESTING_TOKEN`. Alternative path: `VEANS_E2E_ADMIN_TOKEN=` skips the seed and uses the given token as-is, for driving a long-lived Vikunja the suite shouldn't mutate user rows on. - Local e2e loop: from the parent repo root, build the API (`mage build:build`), run it with sqlite-memory + a known JWT secret + `VIKUNJA_SERVICE_TESTINGTOKEN`, then `mage test:e2e` from `veans/` with `VEANS_E2E_API_URL` + `VEANS_E2E_TESTING_TOKEN`. No manual seeding step — the test harness handles it. - CI: the `test-veans-e2e` job in `.github/workflows/test.yml` consumes the existing `vikunja_bin` artifact from `api-build`; don't recompile the API in a parallel workflow. The `veans-test` job runs unit tests with `-short` for fast feedback, independent of `api-build`. ## Vikunja wire-format gotchas Most failures surface when crossing the JSON boundary. The list below is what's bitten me; if a new endpoint behaves oddly, suspect one of these: - **`ProjectView.view_kind` and `bucket_configuration_mode` are strings**, not ints. The parent enums (`ProjectViewKind`, `BucketConfigurationModeKind`) have custom `MarshalJSON` that emits `"kanban"` / `"manual"` etc. Use the string constants in `internal/client/types.go`. - **`Task.BucketID` is always 0** in `GET /tasks/:id`. The model has `xorm:"-"` on it — the actual bucket lives in a separate `task_buckets` table. Fetch with `?expand=buckets` and use `task.CurrentBucketID(viewID)` to read it. - **`POST /tasks/{id}` does NOT move tasks between buckets.** The task↔bucket relation is row-shaped; use `client.MoveTaskToBucket()` which hits `POST /projects/{p}/views/{v}/buckets/{b}/tasks`. The Update path on the server only auto-moves on `done` flips. - **Bot user creation is `PUT /user/bots`**, not `/bots` — the routes are registered under the `/user` subgroup. Same prefix for `GET /user/bots`. - **`APIToken.expires_at` is required.** The struct field has `valid:"required"` upstream; sending it omitted or zero fails validation. Use `client.FarFuture` (year 9999) when you mean "no expiry" — the frontend does the same. - **Task descriptions and comments are HTML, not markdown.** The Vikunja web UI uses TipTap, which calls `getHTML()` on save. The stored field is therefore HTML. The agent prompt template (`internal/commands/prompt.tmpl`) teaches agents the canonical TipTap shapes — most importantly `