- Only create an abstraction if it's actually needed
- Prefer clear function/variable names over inline comments
- Avoid helper functions when a simple inline expression would suffice
- Use knip to remove unused code if making large changes
Read the relevant doc in @docs/ before structural changes, and update the matching doc whenever your change makes it out of date.
The root README.md and everything under user-docs/en/ are the English source of truth. Translations are machine-drafted, never hand-written:
- Edit only the English source. Never edit
user-docs/readme/README.<lang>.mdoruser-docs/<lang>/*.mdby hand. - Regenerate translations with the scripts (they translate via
pi):python3 scripts/build_readmes.py— rootREADME.md→user-docs/readme/README.<lang>.mdpython3 scripts/build_userdocs.py—user-docs/en/*.md→user-docs/<lang>/*.md- Pass language codes to rebuild a subset, e.g.
python3 scripts/build_userdocs.py es ja.
- To add a language: write the English first, add the code + native name to the
LANGS(andTRANSLATE_TARGET) lists in both scripts and the nav, then run the scripts for the new code.
make test # vitest + go test ./...
make check # lint + format-check + test + build + vet (run before pushing)
make e2e # Playwright E2E; needs `make e2e-setup` once. Not in test/check- Go: table-driven tests in
*_test.goalongside source. - Frontend: tests next to source (
foo.js→foo.test.js); DOM helpers take{ documentImpl, windowImpl }for DI. - Lint/format (frontend): ESLint (
eslint-plugin-svelte) + Prettier, config inweb/.make checkrunsfrontend-lint+frontend-format-check. Fix locally withcd web && npm run format(auto-format) andnpm run lint. Style is 2-space indent, single quotes (enforced by Prettier). - E2E: lives in
e2e/(Playwright, built binary across desktop/mobile/iPad, stubpi). Seedocs/dev/e2e-testing.md. - Always
make build, nevergo buildalone —//go:embedneedsweb/dist+internal/ui/embedded/export/export.jsfrom the frontend build.
- Live app and export are separate renders. Live = Svelte SPA via
internal/ui/embedded/app.html(spa_page.go). Export/share = static snapshot viainternal/ui/embedded/share-session.html(export.go), built fromweb/src/export/export-entry.jswhich reuses the liveweb/src/session/modules. Never leak live-only chrome (SPA scripts, SSE, chat) into the export. - Existing session files are append-only for
session_info(browser rename + auto-titling). Conversation entries come from thepi --mode rpcworker, not pi-web. - One worker per session. Reused; crashed = evicted + replaced; idle reaped after 10 min.
- Icons: Lucide only, via
web/src/shared/icons.js— no hand-drawn SVG or unicode glyphs. - i18n: user-facing strings go through
t()fromweb/src/shared/i18n.js. Edit onlyweb/src/shared/locales/en.js— it is the source of truth; add/change keys there. Never hand-write the other locale files (de.js,es.js, …); they are machine-drafted by translatingen.jsviapi, the same English-source-only approach as the localized docs (see Localized docs). Session content is never translated. - Default port
31415. State:~/.pi/agent/pi-web/pi-web-state.json. SSE topics:__all__for index-wide, session ID per-session. - Never start pi-web yourself (
make dev,./pi-web, etc.). The user runs it. Build/test withmake build/make test/make check, but leave running the server to the user.
If the user's instructions conflict with any rule in this document, ask for explicit confirmation before overriding. Only then execute their instructions.