293 lines
14 KiB
Markdown
293 lines
14 KiB
Markdown
# 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.
|
||
- Avoid redundant nearby labels. If a tab, section title, count chip, row label, or control state already communicates the meaning, do not repeat it with an eyebrow label, explanatory zero-state sentence, or duplicate card label.
|
||
- Prefer compact label/value rows for dense settings controls instead of stacked labels with large vertical gaps.
|
||
- 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.
|