grocery-app/PROJECT_INSTRUCTIONS.md
2026-05-31 02:21:59 -07:00

291 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Project Instructions - Fiddy (External DB)
## 1) Core expectation
This project connects to an **external Postgres instance (on-prem server)**. Dev and Prod must share the **same schema** through **migrations**.
## 2) Authority & doc order
1) **PROJECT_INSTRUCTIONS.md** (this file) is the source of truth.
2) **DEBUGGING_INSTRUCTIONS.md** (repo root) is required for bugfix work.
3) Other instruction files (e.g. `.github/copilot-instructions.md`) must not conflict with this doc.
If anything conflicts, follow **this** doc.
---
## 3) Non-negotiables (hard rules)
### External DB + migrations
- `DATABASE_URL` points to **on-prem Postgres** (**NOT** a container).
- Dev/Prod share schema via migrations in: `packages/db/migrations`.
- Active migration runbook: `docs/DB_MIGRATION_WORKFLOW.md` (active set + status commands).
### Docker dev runtime
- After backend/API code changes while using `docker-compose.dev.yml`, rebuild and restart only the backend service:
- `docker compose -f docker-compose.dev.yml up -d --build backend`
- After backend env/CORS changes, recreate the backend service so `backend/.env` is reloaded:
- `docker compose -f docker-compose.dev.yml up -d --force-recreate --no-deps backend`
- For the Docker frontend on port `3010`, `ALLOWED_ORIGINS` must include the exact browser origin, for example `http://localhost:3010` and `http://127.0.0.1:3010`.
- Verify the restarted API with `GET http://127.0.0.1:5000/` and `GET http://127.0.0.1:5000/config`.
- Do not print or commit real `.env` values while checking or updating local Docker env.
### No background jobs
- **No cron/worker jobs**. Any fix must work without background tasks.
### Security / logging
- **Never log secrets** (passwords, tokens, session cookies).
- **Never log receipt bytes**.
- **Never log full invite codes** - logs/audit store **last4 only**.
### Server-side authorization only
- **Server-side RBAC only.** Client checks are UX only and must not be trusted.
---
## 4) Non-regression contracts (do not break)
### Auth
- Custom email/password auth.
- Sessions are **DB-backed** and stored in table `sessions`.
- Session cookies are **HttpOnly**.
### Receipts
- Receipt images are stored in Postgres `bytea` table `receipts`.
- **Entries list endpoints must never return receipt image bytes.**
- Receipt bytes are fetched only via a **separate endpoint** when inspecting a single item.
### Request IDs + audit
- API must generate a **`request_id`** and return it in responses.
- Audit logs must include `request_id`.
- Audit logs must never store full invite codes (store **last4 only**).
---
## 5) Architecture contract (Backend <-> Client <-> Hooks <-> UI)
### No-assumptions rule (required)
Before making structural changes, first scan the repo and identify:
- where `app/`, `components/`, `features/`, `hooks/`, `lib/` live
- existing API routes and helpers
- patterns already in use
Do not invent files/endpoints/conventions. If something is missing, add it **minimally** and **consistently**.
### Single mechanism rule (required)
For any cross-component state propagation concern, keep **one** canonical mechanism only:
- Context **OR** custom events **OR** cache invalidation
Do not keep old and new mechanisms in parallel. Remove superseded utilities/imports/files in the same PR.
### Layering (hard boundaries)
For every domain (auth, groups, entries, receipts, etc.) follow this flow:
1) **API Route Handlers** - `app/api/.../route.ts`
- Thin: parse/validate input, call a server service, return JSON.
- No direct DB queries in route files unless there is no existing server service.
2) **Server Services (DB + authorization)** - `lib/server/*`
- Own all DB access and authorization helpers.
- Server-only modules must include: `import "server-only";`
- Prefer small domain modules: `lib/server/auth.ts`, `lib/server/groups.ts`, `lib/server/entries.ts`, `lib/server/receipts.ts`, `lib/server/session.ts`.
3) **Client API Wrappers** - `lib/client/*`
- Typed fetch helpers only (no React state).
- Centralize fetch + error normalization.
- Always send credentials (cookies) and never trust client-side RBAC.
4) **Hooks (UI-facing API layer)** - `hooks/use-*.ts`
- Hooks are the primary interface for components/pages to call APIs.
- Components should not call `fetch()` directly unless there is a strong reason.
### API conventions
- Prefer consistent JSON error shape:
- `{ error: { code: string, message: string }, request_id?: string }`
- Validate inputs at the route boundary (shape/type), authorize in server services.
- Mirror existing REST style used in the project.
### Next.js route params checklist (required)
For `app/api/**/[param]/route.ts`:
- Treat `context.params` as **async** and `await` it before reading properties.
- Example: `const { id } = await context.params;`
### Frontend structure preference
- Prefer domain-first structure: `features/<domain>/...` + `shared/...`.
- Use `components/*` only for compatibility shims during migrations (remove them after imports are migrated).
### Maintainability thresholds (refactor triggers)
- Component files > **400 lines** should be split into container/presentational parts.
- Hook files > **150 lines** should extract helper functions/services.
- Functions with more than **3 nested branches** should be extracted.
---
## 6) Decisions / constraints (Group Settings)
- Add `GROUP_OWNER` role to group roles; migrate existing groups so the first admin becomes owner.
- Join policy default is `NOT_ACCEPTING`. Policies: `NOT_ACCEPTING`, `AUTO_ACCEPT`, `APPROVAL_REQUIRED`.
- Both owner and admins can approve join requests and manage invite links.
- Invite links:
- TTL limited to 1-7 days.
- Settings are immutable after creation (policy, single-use, etc.).
- Single-use does not override approval-required.
- Expired links are retained and can be revived.
- Single-use links are deleted after successful use.
- Revive resets `used_at` and `revoked_at`, refreshes `expires_at`, and creates a new audit event.
- No cron/worker jobs for now (auto ownership transfer and invite rotation are paused).
- Group role icons must be consistent: owner, admin, member.
---
## 7) Do first (vertical slice)
1) DB migrate command + schema
2) Register/Login/Logout (custom sessions)
3) Protected dashboard page
4) Group create/join + group switcher (approval-based joins + optional join disable)
5) Entries CRUD (no receipt bytes in list)
6) Receipt upload/download endpoints
7) Settings + Reports
---
## 8) Definition of done
- Works via `docker-compose.dev.yml` with external DB
- Migrations applied via `npm run db:migrate`
- Tests + lint pass
- RBAC enforced server-side
- No large files
- No TypeScript warnings or lint errors in touched files
- No new cron/worker dependencies unless explicitly approved
- No orphaned utilities/hooks/contexts after refactors
- No duplicate mechanisms for the same state flow
- Text encoding remains clean in user-facing strings/docs
---
## 9) Desktop + mobile UX checklist (required)
- Touch: long-press affordance for item-level actions when no visible button.
- Mouse: hover affordance on interactive rows/cards.
- Tap targets remain >= 40px on mobile.
- Modal overlays must close on outside click/tap.
- For every frontend action that manipulates database state, show a toast/bubble notification with basic outcome details (action + target + success/failure).
- Frontend destructive actions should use the shared `ConfirmSlideModal` pattern instead of browser `confirm()` unless there is a documented exception.
- Progress-type notifications must reuse the existing upload toaster pattern (`UploadQueueContext` + `UploadToaster`) for consistency.
- Add Playwright UI tests for new UI features and critical flows.
---
## 10) Tests (required)
- Add/update tests for API behavior changes (auth, groups, entries, receipts).
- Include negative cases where applicable:
- unauthorized
- not-a-member
- invalid input
---
## 11) Agent Response Legend (required)
Use emoji/icons in agent progress and final responses so status is obvious at a glance.
Legend:
- `🔄` in progress
- `✅` completed
- `🧪` test/lint/verification result
- `📄` documentation update
- `🗄️` database or migration change
- `🚀` deploy/release step
- `⚠️` risk, blocker, or manual operator action needed
- `❌` failed command or unsuccessful attempt
- `` informational context
- `🧭` recommendation or next-step option
Usage rules:
- Include at least one status icon in each substantive agent response.
- Use one icon per bullet/line; avoid icon spam.
- Keep icon meaning consistent with this legend.
---
## 12) Git Intake, Branching, Commit, and PR Discipline (required)
### Read-only intake before editing
Before editing, run this read-only intake:
- `git status --short --branch`
- `git branch -vv`
- `git log --oneline --decorate -8`
- `git ls-files --others --exclude-standard`
- Check current PR status when GitHub CLI is available.
- Check open PRs for overlapping work before editing shared or collision-prone areas.
### Branch suitability gate before editing
- Continue on the current branch only when the requested work belongs to that branch or PR.
- Start independent work from `main` after pulling latest.
- Start stacked work from the current PR branch when the work intentionally builds on that PR.
- If the current branch purpose does not match the request, stop before editing and switch or create the correct branch.
- If either `main` or the current branch could be valid, ask whether the work is independent side work or required follow-on work.
- Do not layer unrelated work on top of a dirty worktree.
- If unrelated local changes exist, pause and ask how to separate them.
### Branch creation and naming
- Create a descriptive branch before writing code.
- Preferred branch prefixes:
- `feature/<short-description>`
- `bugfix/<short-description>`
- `refactor/<short-description>`
- `chore/<short-description>`
- `spike/<short-description>`
- Do not include tracker numbers in branch names.
- Use standalone branches from `main` for independent work.
- Use stacked branches from the parent PR branch for follow-on work.
- Target standalone PRs at `main`.
- Target stacked PRs at the parent PR branch.
- Never push directly to `main`.
### Commit discipline
- Treat committing as a first-class part of the workflow: create frequent, verified checkpoint commits for completed work instead of accumulating large uncommitted changes.
- Commit after each coherent logical unit of work.
- Commit in small, logical slices (no broad mixed-purpose commits).
- Before committing:
1. Run `git diff --stat`.
2. Run relevant tests or checks when practical.
3. Stage only files that belong to the logical unit.
4. Run `git diff --cached --stat`.
5. Commit with an imperative, present-tense subject at or below 72 characters.
- Each commit must:
- follow Conventional Commits style (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`)
- include only related files for that slice
- exclude secrets, credentials, and generated noise
- Do not stage unrelated user or collaborator changes.
- Do not start a second unrelated task while the first has uncommitted work.
- If existing local changes are external/user changes, leave them untouched unless explicitly told otherwise.
- If asked to commit and external changes already exist, commit those separately on the proper branch before starting new work.
- Run verification before commit when applicable (lint/tests/build or targeted checks for touched areas).
- Prefer frequent checkpoint commits during agentic work rather than one large end-state commit.
- Before switching tasks or stopping after a completed change, check git status and either commit the finished slice or clearly document why it remains uncommitted.
- If a rule or contract changes, commit docs first (or in the same atomic slice as enforcing code).
### Push and PR coordination
- Push the branch before opening a PR.
- For this Gitea repo, use `docs/GITEA_PR_WORKFLOW.md` and `scripts/gitea-pr.js` for PR creation, lookup, and merge operations.
- PR tooling must read auth from `GITEA_TOKEN`/`GITEA_BASE_URL` shell environment or ignored `.codex-local.env` only; never commit tokens or print token values.
- Open a draft PR early for non-trivial, collision-prone, or multi-agent work once the first coherent commit exists.
- Use the PR body as the coordination record:
- `Owner:`
- `Status: proposed / in-progress / blocked / review / done`
- `Branch:`
- `Branch relationship: standalone from main / stacked on parent branch / continuing existing branch`
- `Likely modified areas:`
- `Actual modified files:`
- `Collision risk: low / medium / high`
- `Last meaningful update:`
- Collision risk levels:
- Low: isolated docs, tests, or one leaf component.
- Medium: shared stores/types, panels/components, handlers, helpers.
- High: interface contracts, broad app flows, core registries, cross-cutting behavior.
- If a branch already contains assigned feature work and has no current PR, stop before adding more feature commits. Push the branch and open a draft PR, or record the GitHub/auth blocker.
- Before writing or updating the final PR body, inspect:
- `git log <base>..HEAD`
- `git diff --stat <base>..HEAD`
- The PR should describe the cumulative branch diff against the target branch, not only the latest commit.
- Include:
- Summary of functional changes.
- Tests run, or a clear reason tests were not run.
- For broad branches, organize the summary by subsystem, workflow, or behavior area.
- Do not use auto-closing keywords such as `Closes`, `Fixes`, or `Resolves`.
- Merge PRs only after explicit operator approval, required checks, and a final `npm run pr:view -- --number <pr-number>` status check.