CoPet Agent Guide
May 21, 2026 · View on GitHub
Feature Module Development
Every new feature module follows the same workflow. Do not start coding on main.
1. Worktree-first
New feature modules must be developed in an isolated git worktree on a feature/* branch.
- Create a worktree with a
feature/<module-name>branch before writing any code. - Worktrees live under
.worktrees/at the repository root (e.g..worktrees/virtual-pet-list/). Do not place them under.claude/worktrees/or anywhere else. - Use
EnterWorktreeto create the worktree and switch into it; do not hand-craft worktrees with rawgit worktree addunless the harness is unavailable. - One worktree per feature module. Do not reuse a worktree for an unrelated feature.
- Keep the worktree and its branch after the feature is merged. Do not delete or prune them.
- Bug fixes, doc updates, and small refactors that touch a single file may be made directly on
mainwithout a worktree.
2. Branch naming
- Feature branches:
feature/<kebab-case-module-name>(e.g.feature/virtual-pet-list). - The module name should describe the user-visible capability, not the implementation detail.
- Do not use
feat/,feature_,dev/, or personal-name prefixes.
3. Code layout
- React components live flat under
src/components/. Do not create per-feature subdirectories (e.g.src/settings/,src/components/settings/). The only allowed subdirectory issrc/components/ui/for shadcn-style primitives. - Group related components by filename prefix, not by folder. Example:
SettingsNav.tsx,SettingsPetsSection.tsx,SettingsAboutSection.tsx. - Window-level entry components (
PetWindow.tsx,SettingsWindow.tsx) stay atsrc/top level. Auxiliary components they compose go flat intosrc/components/. - Hooks go in
src/hooks/, shared utilities insrc/lib/, static assets insrc/assets/. - Asset placement follows the consumer, not the file type:
src/assets/— assets the frontendimports at build time and Vite processes (URL/hashing). Example: agent icon SVGs,logo.png.src-tauri/assets/— content the Rust backend enumerates at runtime, ships through Tauribundle.resources, and exposes to the webview via theasset://protocol (convertFileSrc).- Decision rule: if Rust lists it with
fs::read_dir, or the frontend receives a filesystem path that must pass throughconvertFileSrc, the asset belongs undersrc-tauri/assets/. If the frontend imports it directly in TS/CSS, it belongs undersrc/assets/.
- Built-in pet packages live in
src-tauri/assets/pets/<pet-id>/withpet.json+spritesheet.{webp,png}. They are discovered byfs::read_dirinconfig_store.rs, declared asbundle.resourcesand allowlisted underassetProtocol.scope($RESOURCE/assets/pets/**) intauri.conf.json, and share the same package format as user-imported pets under~/.copet/pets/. Do not move built-in pets tosrc/assets/; doing so breaks runtime discovery, splits the built-in vs user-imported code paths, and forces a hardcoded manifest list. - Rust source goes in
src-tauri/src/; keep it production-only (no tests inline).
4. Tauri command boundary
- Frontend calls into Rust through Tauri commands; expose new Rust capability with a typed command and consume it from a hook in
src/hooks/. - Do not call
invokefrom inside presentational components — wrap it in a hook so tests can mock the Tauri layer with the shared harness.
5. Commit messages
Follow Conventional Commits with a scope matching the feature module:
<type>(<scope>): <imperative summary>
type:feat,fix,refactor,style,test,chore,docs,perf.scope: feature module or surface area (e.g.settings,window,pet).- Summary in the imperative mood, lowercase, no trailing period.
- Keep the subject short — under ~72 chars. If it doesn't fit, the commit is doing too much; split it.
- No body by default. Add one only when the why is non-obvious from the diff (hidden constraint, subtle interaction, workaround), and keep it brief — no multi-paragraph explanations.
Examples:
feat(settings): add SettingsAboutSectionfix(settings): align section heading IDs with tabpanel aria-labelledbyrefactor(settings): compose window from sectioned shell
6. Git hygiene
- Do not force-add ignored files. Never use
git add -forgit add --forceunless the user explicitly names the ignored file and asks for it to be tracked. - Respect
.gitignoreas authoritative. - Before every commit, check
git diff --cached --name-statusand verify the staged files match the user-requested scope. - Before every commit, you must explicitly check every staged path against ignore rules with
git check-ignore -v --no-index -- <path>. This check is mandatory and cannot be skipped, waived, inferred fromgit status, or replaced with any other form of reasoning. If any staged path matches.gitignoreor another ignore source, stop and do not commit until the ignored path is unstaged or the user explicitly changes the ignore rules.
7. Definition of done
A feature module is done only when all of the following hold:
- Branch is
feature/<module-name>in a dedicated worktree. - Components placed flat under
src/components/with prefix-based grouping. - Tauri commands wrapped in a hook, not invoked directly from components.
- Frontend behavior covered by Playwright specs in
src/tests/. - Rust behavior covered by Cargo integration tests in
src-tauri/tests/. -
pnpm test:frontend,pnpm test:rust,pnpm build, andcargo fmt --manifest-path src-tauri/Cargo.toml --checkall pass. - Commit history follows Conventional Commits with module scope.
Test Layout
Test files are split by runtime and must stay out of the repository-root tests directory.
- Frontend integration and end-to-end tests go in
src/tests. - Rust integration tests go in
src-tauri/tests. - Do not create or keep a repository-root
testsdirectory. - Rust test attributes such as
#[test],#[cfg(test)], and#[tokio::test]are only allowed insrc-tauri/tests/*.rs. - Do not put Rust test modules or test functions in
src-tauri/srcor other non-test.rsfiles.
All Rust coverage should be written as Cargo integration tests under src-tauri/tests. Cargo discovers these files automatically, so do not add explicit [[test]] entries unless a test needs a non-standard path or name.
Frontend Tests
Use Playwright for frontend integration and end-to-end coverage.
- Put Playwright specs in
src/tests/*.spec.ts. - Put shared Playwright harnesses or test helpers in
src/tests. - Use the shared Tauri mock harness pattern when testing UI flows that call Tauri commands or listen for Tauri events.
- Do not write tests for CSS styling or visual presentation details. Frontend tests should cover behavior, commands, state changes, and accessible UI contracts instead of class styling, computed CSS, layout geometry, shadows, borders, colors, transitions, or screenshots.
- Do not add Vitest, jsdom, or Testing Library tests for these integration workflows.
Run frontend tests with:
pnpm test:frontend
Rust Tests
Use Cargo integration tests for cross-module Rust behavior.
- Put Rust integration tests in
src-tauri/tests/*.rs. - Keep Rust tests out of
src-tauri/src; source.rsfiles should contain production code only. - Prefer real filesystem and real HTTP boundaries where practical.
Run Rust tests with:
pnpm test:rust
Some Rust integration tests bind local TCP ports. If a sandbox blocks local networking with Operation not permitted, rerun pnpm test:rust outside the sandbox.
Verification
Match the test budget to the blast radius of the change. Run the smallest closure that can actually break — do not run the full suite for every tweak.
| Change scope | Required check |
|---|---|
| Style/CSS only, copy/i18n string, single-file with no behavior change | pnpm build only — skip the spec suite |
| One component or one hook | That file's spec only: pnpm test:frontend src/tests/<name>.spec.ts |
| One Rust module or one command | That module's Cargo test only: cargo test --manifest-path src-tauri/Cargo.toml --test <name> |
| Major feature module, shared infrastructure (test harness, app state, runtime, hooks consumed across components), or a DoD claim | Full suites: pnpm test:frontend, pnpm test:rust, pnpm build, cargo fmt --manifest-path src-tauri/Cargo.toml --check |
Running the full Playwright matrix for a CSS tweak is wasted budget. Pick the narrowest command that exercises the changed surface; escalate to full suites only when crossing a feature-module boundary or shipping work.