Files
pocket-id/AGENTS.md
ItalyPaleAle 2aa13fe06c feat: add FRANCIS_HOST to connect to a standalone Francis runtime
FRANCIS_HOST decides where the Francis actor runtime lives. When it is
empty or set to "embedded" (the default), Pocket ID starts the runtime inside its own process, backed by its own database.

Any other value is the address, or a comma-separated list of addresses, of a standalone Francis runtime. Pocket ID then connects to it as a remote actor host and starts no embedded runtime.

Note: connecting to a standalone runtime also needs FRANCIS_HOST_PSK or FRANCIS_HOST_JWT, and optionally (but recommended) FRANCIS_CA.
2026-08-31 14:56:45 +02: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: