Files
pocket-id/AGENTS.md
Alessandro (Ale) Segala 22cf4eab93 tests: run the E2E suite against a standalone Francis runtime
Adds a matrix entry that starts a SQLite-backed Francis runtime next to
Pocket ID and points FRANCIS_HOST at it, so the same Playwright suite runs
with the actor state, alarms, and placement owned by the runtime instead of
embedded in Pocket ID.

The suite needs no changes to work in that topology: the E2E reset seeds
every actor through actors.Service() and deliberately leaves the actor
store alone, so it behaves the same whichever side owns it.

The CLI spec is the exception, since export and import are the two commands
whose behaviour genuinely differs. It now picks the right Compose file,
expects an export to carry no francis.bin, feeds the import an archive
without one, and gains a case asserting that an archive that does carry one
is refused.

The runtime is reached over the Compose network on its UDP port, so nothing
is published to the host, and the cluster CA is left unpinned, which
exercises the same trust-on-first-use path an operator gets without
FRANCIS_CA. Pinning is covered by a unit test instead.
2026-08-31 05:27:04 +00:00

5.0 KiB

AGENTS.md

Pocket ID — a passkey-only OIDC provider. Go backend serves a SvelteKit SPA (embedded in the binary for production). This file lists what isn't obvious from reading the code.

Layout

  • backend/ — Go module (gin, GORM, ory/fosite fork). Its own toolchain, not part of the repository.
  • frontend/ — SvelteKit 5 SPA. Builds into backend/frontend/dist and is embedded via go:embed.
  • tests/ — Playwright end-to-end tests (drives a Dockerized full stack).

Build / test / lint

# backend/   — the exclude_frontend and unit tags are mandatory locally; CI uses them too
go test -tags=exclude_frontend,unit ./...                          # unit/integration tests
go test -tags=exclude_frontend,unit -run TestName ./internal/...   # a single test
golangci-lint run  # lint (config: backend/.golangci.yml - includes build tags)

# frontend/  (or root)
pnpm check        # svelte-check — the ONLY frontend type gate (no unit tests exist)
pnpm lint         # prettier --check && eslint  (note: not enforced by CI)
pnpm format       # prettier --write — REQUIRED before opening a PR

End-to-end (needs Docker; stop any local backend on :1411 first — see gotchas):

cd tests/setup && docker compose up -d --build   # rebuild after ANY code change, or you test stale code
# docker-compose-francis.yml runs the same suite against a standalone Francis runtime instead of the embedded one
cd ../.. && pnpm test                            # = playwright test in tests/

Critical gotchas

  • exclude_frontend and unit build tags. Without them plain go run/go test/golangci-lint fail to run. Always pass -tags exclude_frontend,unit for backend dev/test/lint.
  • Never edit generated files: frontend/src/lib/paraglide/** (Paraglide i18n output) and backend/frontend/dist/**. For i18n, only edit frontend/messages/en.json; other locales come from Crowdin.
  • Migrations are split by DB. Raw SQL via golang-migrate in backend/resources/migrations/{sqlite,postgres}/ — separate files and separate version timelines. Add a matching up/down pair to both. Not GORM AutoMigrate. SQLite migrations are not auto-wrapped in a transaction (NoTxWrap); wrap multi-statement ones manually (PRAGMA foreign_keys=OFF; BEGIN; … COMMIT; PRAGMA foreign_keys=ON;).

Backend (Go)

  • Config: global common.EnvConfig (caarlos0/env); any secret var supports a *_FILE variant.
  • Logging: stdlib log/slog only (bridged to OpenTelemetry). No zerolog/logrus in app code.
  • go.mod pins a fork of fosite (replace github.com/ory/fosite => github.com/pocket-id/fosite).

Frontend (SvelteKit)

  • Svelte 5 runes only: $state, $derived, $props, $bindable. No export let. Event modifiers are gone — use preventDefault from $lib/utils/event-util (onsubmit={preventDefault(fn)}).
  • Forms: use the custom createForm(schema, initial) from $lib/utils/form-util.ts with form-input.svelte. The vendored shadcn formsnap/superforms wrappers exist but app forms don't use them — match the surrounding file. Import zod as import { z } from 'zod/v4'.

Coding Style Guidelines

Comments

  • Exactly one sentence per line
  • There is NO maximum line width: never wrap a single sentence across multiple comment lines, no matter how long that sentence is
  • A new line in a comment means a new sentence; a wrapped line does not exist
  • No trailing period on single-line comments
  • Prefer comments that explain intent, invariants, or why a branch exists
  • Avoid comments that simply restate the next line of code
  • For multi-step logic, use short section comments to separate the steps and explain why each step exists
  • Inside a function, put a one-sentence comment above each major action; the comments double as visual separators between sections and should say what the step does and why, not how
  • Favor a few well-placed section comments over a wall of code; a reader should be able to skim the comments and understand the method's flow
// Wrong — one sentence wrapped across multiple lines
// This function performs the main validation logic. It checks
// the input against the schema and returns an error if the
// input is invalid.

// Wrong — trailing period on single-line comment
// Validate the input.

// Right — one sentence per line, each line as long as it needs to be
// This function performs the main validation logic
// It checks the input against the schema and returns an error if the input is invalid

// Right
// Validate the input

// Right
// Normalize the request host so callers can pass either Host or X-Forwarded-Host values

// Right
// Browsers do not accept a cookie Domain attribute set to an IP address
// Returning an empty domain tells the caller to set a host-only cookie instead

// Wrong — restates the code
// Trim whitespace and lowercase the host
host = strings.TrimSpace(strings.ToLower(host))

Section comments inside a function — one sentence per major action, describing what and why, acting as visual separators: