Instructions for AI coding agents working on PictoPy. Read this before making changes.
This file is the project's rules. Multi-step procedures (adding an endpoint, adding a
feature, pre-PR checks, onboarding) live in agent-kit/ — see agent-kit/README.md.
More specific rules live in the nearest AGENTS.md to the file you are editing:
backend/, frontend/, frontend/src-tauri/, and sync-microservice/ each have one.
PictoPy is an offline, privacy-first desktop photo manager. Nothing leaves the user's machine; all AI runs on-device via ONNX.
Four moving parts:
frontend/src— React 19 + TypeScript UI (Vite, Redux Toolkit, Tailwind v4, shadcn/ui)frontend/src-tauri— Rust shell (Tauri v2), packaging and OS integrationbackend— Python FastAPI sidecar, entry pointbackend/main.py, SQLite storagesync-microservice— a second, separate FastAPI service
ONNX models are downloaded at runtime by backend/app/utils/model_bootstrap.py. They are
not vendored in the repository.
Run the ones for the areas you touched. CI runs the check column; use the fix column
locally so the check passes. Run everything from the repository root — the cd is wrapped in
a subshell so it does not leak into the next command.
| Task | Check (what CI runs) | Fix |
|---|---|---|
| Frontend lint | (cd frontend && npm run lint:check) |
npm run lint:fix |
| Frontend format | (cd frontend && npm run format:check) |
npm run format:fix |
| Frontend tests | (cd frontend && npm test) |
— |
| Python lint + format | pre-commit run --config .pre-commit-config.yaml --all-files |
rewrites in place |
| Backend tests | (cd backend && pytest) |
— |
| Rust format | (cd frontend/src-tauri && cargo fmt -- --check) |
cargo fmt |
| Version bump | npm run version:bump -- X.Y.Z |
— |
cargo test is not part of the PR check workflow, but run it for any frontend/src-tauri/
change. agent-kit/references/ci-gates.md maps every gate to the workflow job it comes from.
Setup is scripted: see docs/Script_Setup_Guide.md (Windows, Debian, Fedora) or
docs/Manual_Setup_Guide.md (everything else).
Before writing a new file, read the nearest existing file that does the same kind of job and follow its structure, naming, and idiom. Consistency with what is already here beats any general best practice. If you are about to introduce a pattern the codebase does not use — a new state library, a different test style, a second way to call the API — don't; extend the existing pattern, or raise it on the issue first.
Search before you create. Adding a second component that does what an existing one already does is the most common quality problem in this repository.
| Before adding a… | Search here first |
|---|---|
| UI primitive (button, dialog, input) | frontend/src/components/ui/ — 27 already exist |
| Shared helper | frontend/src/utils/, frontend/src/lib/utils.ts |
| Hook | frontend/src/hooks/ |
| Constant, route, or static list | frontend/src/constants/ |
| Python helper | backend/app/utils/ |
Extract shared code on the third occurrence, not the second — premature abstraction is also a cost. When you do extract, put it in the directories above so the next person finds it.
One module, one job. A route file handles HTTP; business logic belongs in app/utils/, and
data access in app/database/. A component renders; data fetching belongs in a hook, and
derived data in a selector. If a file has grown past a few hundred lines it is usually doing
more than one job.
- TypeScript:
tsconfig.jsonsetsstrict,noUnusedLocals, andnoUnusedParameters, andnpm run buildrunstsc. Type every export and every API boundary. The codebase still has someany— do not add more, and do not useasto silence a real type error. - Python: annotate function signatures and return types, as the existing modules do.
Table rows are
TypedDictclasses, not bare dicts. There is no mypy gate in CI, so annotations are for the reader and for the next agent — write them accordingly. - Frontend types must match the backend's Pydantic response model. The backend is the
source of truth; check
backend/app/routes/rather than guessing the shape.
One or two lines, explaining why rather than what. The codebase does not use long prose comment blocks or multi-paragraph docstrings — match what is already there. A comment that restates the code is noise; a comment that records a non-obvious reason is valuable.
Do not leave commented-out code. Delete it; git remembers.
These are the mistakes agents make most often in this repository.
Format Python with black (88 columns). backend/pyproject.toml sets Ruff's
line-length = 300, so ruff format would reflow the entire codebase to 300 columns and
the black hook in .pre-commit-config.yaml would immediately fight it. Ruff is configured
here as a linter only — ruff --fix is fine, ruff format is not.
frontend/.eslintrc.json sets no-warning-comments to error for those terms, and lint
runs with --max-warnings 0. A single leftover // TODO fails CI. If work is genuinely
incomplete, open an issue instead of leaving a marker in the code.
package.json, frontend/package.json, and frontend/src-tauri/Cargo.toml must always
agree. Only ever change them through npm run version:bump -- X.Y.Z. tauri.conf.json
has no version field in Tauri v2 — it inherits from Cargo.toml.
dist/, target/, gen/, node_modules/, __pycache__/, .mypy_cache/, .ruff_cache/,
htmlcov/, and frontend/src/components/ui/ (shadcn-generated). Leave
frontend/package-lock.json alone unless you actually changed dependencies.
markdownlint-cli2 runs over every .md in CI, configured by
.github/.markdownlint-cli2.jsonc. Line length (MD013) is off; duplicate headings are
allowed only among siblings.
- Do not open a PR for an issue that maintainers have not reviewed and labelled. Wait for the green light. This is the project's most-enforced process rule.
- PRs target
main. - The PR body must reference its issue as
#<number>—.github/workflows/linked-issue.ymlcopies labels from the linked issue onto the PR. - CodeRabbit reviews every PR automatically (
.coderabbit.yaml). - Commit messages: short imperative subject prefixed
fix:,feat:,docs:, ortest:.
A change is not finished until, for every area you touched, lint passes, formatting is clean, and tests pass locally. Do not hand back work that only compiles.
The pre-pr-check playbook in agent-kit/skills/pre-pr-check/ runs every gate CI runs, in
order. Use it before pushing.