Compare commits

...

4 Commits

Author SHA1 Message Date
Nico
e4774ecd6a chore: update repository registry name
All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 41s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 2s
Build & Deploy Costco Grocery List / deploy (push) Successful in 15s
Build & Deploy Costco Grocery List / notify (push) Successful in 0s
2026-05-25 14:36:45 -07:00
Nico
d4b1de452c test: tighten Playwright selectors 2026-05-25 11:48:12 -07:00
Nico
d2fe873956 chore: fix npm audit vulnerabilities 2026-05-25 11:47:45 -07:00
Nico
767def37cf docs: improve project onboarding 2026-05-25 11:47:21 -07:00
25 changed files with 932 additions and 2532 deletions

View File

@ -0,0 +1,40 @@
---
name: fiddy-verify
description: Run and report the Fiddy repository verification loop. Use when Codex is finishing changes, checking repo health, validating docs/scripts/config updates, or deciding which lint/typecheck/test/build commands are appropriate for this Express/Vite/npm project.
---
# Fiddy Verification
Use this workflow from the repo root.
## Before Running Commands
- Read `AGENTS.md`, `PROJECT_INSTRUCTIONS.md`, and any doc related to the touched area.
- Check `git status --short --branch` so user work is not mistaken for Codex changes.
- Do not print real `.env` values. Inspect keys only if environment context is needed.
- Do not run DB migrations unless the user explicitly asked for migration execution.
## Choose Checks
- Docs-only changes: validate affected Markdown links/content where practical, then run JSON/script sanity checks if package files changed.
- Root or frontend script changes: run `npm run lint`, `npm run typecheck`, and the relevant build command.
- Backend behavior changes: run `npm test`; add focused Jest/Supertest coverage for changed API behavior.
- Frontend behavior changes: run `npm run lint`, `npm run typecheck`, and focused Playwright tests when a browser flow changed.
- Dependency or lockfile changes: run `npm run audit` after install/update commands.
- Migration changes: run `npm run db:migrate:stale:check` and `npm run db:migrate:verify` only against the intended external DB environment.
## Default Safe Loop
Run these when dependencies are already installed and the touched files justify them:
```bash
npm run lint
npm run typecheck
npm run audit
npm test
npm run build:backend
npm run build:frontend
```
## Report
- List exact commands run and pass/fail.
- For failures, include the short relevant error and whether it appears caused by the current changes.
- If a check is skipped, state the concrete reason.
- End with unresolved risks and the smallest useful next step.

View File

@ -5,7 +5,7 @@ on:
branches: [ "main" ] branches: [ "main" ]
env: env:
REGISTRY: git.nicosaya.com/nalalangan/costco-grocery-list REGISTRY: git.nicosaya.com/nalalangan/grocery-app
jobs: jobs:
build: build:
@ -125,4 +125,3 @@ jobs:
curl -d "$MSG" \ curl -d "$MSG" \
https://ntfy.nicosaya.com/gitea https://ntfy.nicosaya.com/gitea

View File

@ -5,7 +5,7 @@ on:
branches: [ "main-new" ] branches: [ "main-new" ]
env: env:
REGISTRY: git.nicosaya.com/nalalangan/costco-grocery-list REGISTRY: git.nicosaya.com/nalalangan/grocery-app
# REGISTRY: grocery-app # REGISTRY: grocery-app
IMAGE_TAG: main-new IMAGE_TAG: main-new
@ -147,4 +147,3 @@ jobs:
curl -d "$MSG" \ curl -d "$MSG" \
https://ntfy.nicosaya.com/gitea https://ntfy.nicosaya.com/gitea

144
AGENTS.md
View File

@ -1,57 +1,111 @@
# AGENTS.md - Fiddy (External DB) # AGENTS.md - Fiddy
## Authority ## Authority
- Source of truth: `PROJECT_INSTRUCTIONS.md` (repo root). If conflict, follow it. - Source of truth: `PROJECT_INSTRUCTIONS.md` in the repo root.
- Bugfix protocol: `DEBUGGING_INSTRUCTIONS.md` (repo root). - Bugfix protocol: `DEBUGGING_INSTRUCTIONS.md`.
- Do not implement features unless required to fix the bug. - Current-stack mapping: `docs/AGENTIC_CONTRACT_MAP.md`.
- If files conflict, follow `PROJECT_INSTRUCTIONS.md`.
## Non-negotiables ## Project Overview
- External DB: `DATABASE_URL` points to on-prem Postgres (NOT a container). - Full-stack grocery list app with household/group behavior, RBAC, image support, and Postgres persistence.
- Dev/Prod share schema via migrations in `packages/db/migrations`. - Backend: Express 5 CommonJS API in `backend/`.
- No cron/worker jobs. Fixes must work without background tasks. - Frontend: React 19 + Vite SPA in `frontend/`, with partial TypeScript.
- Server-side RBAC only. Client checks are UX only. - Database: external on-prem Postgres. Do not add or assume a DB container.
- Canonical migrations live in `packages/db/migrations`.
## Security / logging (hard rules) ## Important Directories
- Never log secrets (passwords/tokens/cookies). - `backend/routes`, `backend/controllers`: route registration and request/response handling.
- Never log receipt bytes. - `backend/models`, `backend/services`, `backend/middleware`, `backend/db`: DB access, domain logic, auth, RBAC, request IDs, image handling.
- Never log full invite codes; logs/audit store last4 only. - `frontend/src/api`: client API wrappers using the shared Axios instance.
- `frontend/src/context`, `frontend/src/hooks`, `frontend/src/components`, `frontend/src/pages`: UI state and screens.
- `frontend/tests`: Playwright e2e tests.
- `backend/tests`: Jest/Supertest backend tests.
- `scripts`: DB migration helpers.
- `docs`: practical maps, runbooks, architecture notes, and archived implementation history.
## Non-regression contracts ## Setup
- Sessions are DB-backed (`sessions` table) and cookies are HttpOnly. - Install root tools: `npm ci`
- Receipt images stored in `receipts` (`bytea`). - Install backend tools: `npm --prefix backend ci`
- Entries list endpoints must NEVER return receipt bytes. - Install frontend tools: `npm --prefix frontend ci`
- API responses must include `request_id`; audit logs must include `request_id`. - Use Node.js 20.19+ or 22.12+ for frontend/Vite commands.
- Frontend actions that manipulate database state must show a toast/bubble notification with basic outcome info (action + target + success/failure). - Configure backend env from `backend/.env.example`; never commit real `.env` values.
- Progress-type notifications must reuse the existing upload toaster pattern (`UploadQueueContext` + `UploadToaster`). - For migration scripts, set `DATABASE_URL` in the shell before running root DB commands.
## Architecture boundaries (follow existing patterns; do not invent) ## Run Commands
1) API routes: `app/api/**/route.ts` - Dev with Docker: `docker compose -f docker-compose.dev.yml up`
- Thin: parse/validate + call service, return JSON. - Backend only: `npm run dev:backend`
2) Server services: `lib/server/*` - Frontend only: `npm run dev:frontend`
- Own DB + authz. Must include `import "server-only";`. - Backend default port: `5000`
3) Client wrappers: `lib/client/*` - Frontend Docker-mapped port: `3010`; Vite direct default is `5173` unless overridden.
- Typed fetch + error normalization; always send credentials.
4) Hooks: `hooks/use-*.ts`
- Primary UI-facing API layer; components avoid raw `fetch()`.
## Next.js dynamic route params (required) ## Verification Commands
- In `app/api/**/[param]/route.ts`, treat `context.params` as async: - Backend unit/API tests: `npm test`
- `const { id } = await context.params;` - Frontend lint: `npm run lint`
- Frontend typecheck: `npm run typecheck`
- Vulnerability audit: `npm run audit`
- Backend build: `npm run build:backend`
- Frontend build: `npm run build:frontend`
- Full build: `npm run build`
- E2E tests: `npm run test:e2e`
- Migration status: `npm run db:migrate:status`
- Migration verification: `npm run db:migrate:verify`
## Working style ## Environment Notes
- Scan repo first; do not guess file names or patterns. - `backend/.env` is used by the backend and Docker dev service.
- Make the smallest change that resolves the issue. - `frontend/.env` may define `VITE_API_URL` and `VITE_ALLOWED_HOSTS`.
- Religiously commit work in small, verified slices; prefer frequent checkpoint commits over large end-state batches. - Do not print, log, or commit secrets, tokens, cookies, DB URLs, receipt bytes, or full invite codes.
- Follow the commit discipline in `PROJECT_INSTRUCTIONS.md` for every slice, including Conventional Commit messages and related-file-only scope. - Logs/audit entries for invite codes may include last4 only.
- Keep touched files free of TS warnings and lint errors.
- Add/update tests when API behavior changes (include negative cases).
- Keep text encoding clean (no mojibake).
## Response icon legend ## Coding Conventions
Use the same status icons defined in `PROJECT_INSTRUCTIONS.md` section "Agent Response Legend (required)": - Preserve the current stack; do not migrate to Next.js or another framework.
- Keep Express routes/controllers thin; put DB-heavy work in models/services and authz in backend layers.
- Keep frontend network calls in `frontend/src/api` wrappers and UI state in context/hooks.
- Always send credentials through the shared Axios client when touching authenticated frontend API calls.
- Enforce RBAC server-side; client guards are UX only.
- Frontend DB-mutating actions must show toast/bubble outcome notifications.
- Progress notifications must reuse `UploadQueueContext` and `UploadToaster`.
- Keep encoding clean; do not introduce mojibake.
## Dependency Rules
- npm is the package manager; do not introduce pnpm, yarn, bun, or another build tool.
- Do not add production dependencies unless the task clearly requires it and the repo has no practical existing option.
- Do not add cron, workers, polling daemons, or background job frameworks.
## Testing Expectations
- Add or update tests when API behavior changes.
- Include negative cases for authz, membership, and invalid input where applicable.
- For UI behavior changes, prefer focused Playwright tests.
- Run the narrowest relevant checks first, then broader checks when risk warrants it.
## Done Means
- Behavior is preserved unless the task explicitly requires a change.
- Relevant tests/lint/typecheck/build commands were run or the reason they could not run is documented.
- Touched files have no known TS/lint errors.
- Migrations are in `packages/db/migrations` when schema changes are required.
- Documentation is updated for changed commands, contracts, or workflows.
## Codex Must Not
- Do not read or expose real `.env` values; inspect variable names only when needed.
- Do not touch production data or run DB migrations without explicit operator intent.
- Do not log secrets, receipt bytes, or full invite codes.
- Do not delete user work or generated artifacts unless explicitly asked.
- Do not make broad refactors, file moves, or framework changes for cleanup alone.
## Deeper Docs
- `docs/PROJECT_MAP.md`: quick repo orientation.
- `docs/DEVELOPMENT.md`: setup, run, verify, and troubleshooting.
- `docs/DB_MIGRATION_WORKFLOW.md`: migration runbook.
- `docs/PLANS.md`: template for multi-step work.
- `.agents/skills/fiddy-verify/SKILL.md`: repo-specific verification workflow for Codex.
## Response Icon Legend
- `🔄` in progress - `🔄` in progress
- `✅` completed - `✅` completed
- `🧪` verification/test result - `🧪` verification/test result
- `⚠️` risk/blocker/manual action - `📄` documentation update
- `❌` failure - `🗄️` database or migration change
- `🧭` recommendation/next step - `🚀` deploy/release step
- `⚠️` risk, blocker, or manual operator action needed
- `❌` failed command or unsuccessful attempt
- `` informational context
- `🧭` recommendation or next-step option

View File

@ -2,6 +2,8 @@
A full-stack web application for managing grocery shopping lists with role-based access control, image support, and intelligent item classification. A full-stack web application for managing grocery shopping lists with role-based access control, image support, and intelligent item classification.
> Current maintainer notes: `PROJECT_INSTRUCTIONS.md` is the source of truth for project constraints. For current setup, run, and verification commands, start with `docs/DEVELOPMENT.md` and `docs/PROJECT_MAP.md`; some older sections below are historical and should be checked against current code before changing behavior.
![License](https://img.shields.io/badge/license-MIT-blue.svg) ![License](https://img.shields.io/badge/license-MIT-blue.svg)
![Node](https://img.shields.io/badge/node-20.x-green.svg) ![Node](https://img.shields.io/badge/node-20.x-green.svg)
![React](https://img.shields.io/badge/react-19.2.0-blue.svg) ![React](https://img.shields.io/badge/react-19.2.0-blue.svg)
@ -324,8 +326,8 @@ router.post("/add",
1. **Clone the repository** 1. **Clone the repository**
```bash ```bash
git clone https://github.com/your-org/costco-grocery-list.git git clone https://git.nicosaya.com/nalalangan/grocery-app.git
cd costco-grocery-list cd grocery-app
``` ```
2. **Configure environment variables** 2. **Configure environment variables**
@ -421,7 +423,7 @@ Authorization: Bearer <your_jwt_token>
## 📁 Project Structure ## 📁 Project Structure
``` ```
costco-grocery-list/ grocery-app/
├── .gitea/ ├── .gitea/
│ └── workflows/ │ └── workflows/
│ └── deploy.yml # CI/CD pipeline configuration │ └── deploy.yml # CI/CD pipeline configuration
@ -747,7 +749,7 @@ This project is licensed under the MIT License - see the LICENSE file for detail
## 👤 Author ## 👤 Author
**Nico Saya** **Nico Saya**
- Repository: [git.nicosaya.com/nalalangan/costco-grocery-list](https://git.nicosaya.com/nalalangan/costco-grocery-list) - Repository: [git.nicosaya.com/nalalangan/grocery-app](https://git.nicosaya.com/nalalangan/grocery-app)
--- ---

62
backend/build.js Normal file
View File

@ -0,0 +1,62 @@
"use strict";
const fs = require("fs");
const path = require("path");
const rootDir = __dirname;
const distDir = path.join(rootDir, "dist");
const directoriesToCopy = [
"config",
"constants",
"controllers",
"db",
"middleware",
"models",
"routes",
"services",
"utils",
"public",
];
const filesToCopy = ["app.js", "server.js", "package.json", "package-lock.json"];
function copyFile(sourcePath, targetPath) {
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
fs.copyFileSync(sourcePath, targetPath);
}
function copyDirectory(sourceDir, targetDir) {
if (!fs.existsSync(sourceDir)) {
return;
}
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
const sourcePath = path.join(sourceDir, entry.name);
const targetPath = path.join(targetDir, entry.name);
if (entry.isDirectory()) {
copyDirectory(sourcePath, targetPath);
continue;
}
if (entry.isFile()) {
copyFile(sourcePath, targetPath);
}
}
}
fs.mkdirSync(distDir, { recursive: true });
for (const directory of directoriesToCopy) {
copyDirectory(path.join(rootDir, directory), path.join(distDir, directory));
}
for (const file of filesToCopy) {
const sourcePath = path.join(rootDir, file);
if (fs.existsSync(sourcePath)) {
copyFile(sourcePath, path.join(distDir, file));
}
}
console.log(`Backend build copied runtime files to ${path.relative(rootDir, distDir)}`);

2208
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,13 +10,12 @@
"sharp": "^0.34.5" "sharp": "^0.34.5"
}, },
"devDependencies": { "devDependencies": {
"cpx": "^1.5.0",
"esbuild": "^0.25.5", "esbuild": "^0.25.5",
"nodemon": "^3.1.11", "nodemon": "^3.1.11",
"rimraf": "^6.0.1" "rimraf": "^6.0.1"
}, },
"scripts": { "scripts": {
"build": "rimraf dist && node build.js && cpx \"public/**/*\" dist/public", "build": "rimraf dist && node build.js",
"dev": "nodemon server.js" "dev": "nodemon server.js"
} }
} }

View File

@ -1,6 +1,6 @@
services: services:
backend: backend:
image: git.nicosaya.com/nalalangan/costco-grocery-list/backend:main-new image: git.nicosaya.com/nalalangan/grocery-app/backend:main-new
# image: grocery-app/backend:main-new # image: grocery-app/backend:main-new
restart: always restart: always
env_file: env_file:
@ -9,7 +9,7 @@ services:
- "5001:5000" - "5001:5000"
frontend: frontend:
image: git.nicosaya.com/nalalangan/costco-grocery-list/frontend:main-new image: git.nicosaya.com/nalalangan/grocery-app/frontend:main-new
# image: grocery-app/frontend:main-new # image: grocery-app/frontend:main-new
restart: always restart: always
env_file: env_file:

View File

@ -1,6 +1,6 @@
services: services:
backend: backend:
image: git.nicosaya.com/nalalangan/costco-grocery-list/backend:latest image: git.nicosaya.com/nalalangan/grocery-app/backend:latest
restart: always restart: always
env_file: env_file:
- ./backend.env - ./backend.env
@ -8,7 +8,7 @@ services:
- "5000:5000" - "5000:5000"
frontend: frontend:
image: git.nicosaya.com/nalalangan/costco-grocery-list/frontend:latest image: git.nicosaya.com/nalalangan/grocery-app/frontend:latest
restart: always restart: always
env_file: env_file:
- ./frontend.env - ./frontend.env

95
docs/DEVELOPMENT.md Normal file
View File

@ -0,0 +1,95 @@
# Development
Use this as the practical setup and verification guide. `PROJECT_INSTRUCTIONS.md` remains the source of truth for constraints.
## Prerequisites
- Node.js 20.19+ or 22.12+ for the current Vite toolchain.
- npm.
- PostgreSQL client tools if running migration scripts (`psql` must be on `PATH`).
- Access to the external Postgres database through `DATABASE_URL` or backend DB variables.
- Docker is optional for local app containers.
## Install
Run installs separately because this repo does not define npm workspaces.
```bash
npm ci
npm --prefix backend ci
npm --prefix frontend ci
```
## Environment
- Copy `backend/.env.example` to `backend/.env` for backend runtime configuration.
- Copy `frontend/.env.example` to `frontend/.env` if the frontend needs non-default API or host settings.
- Do not commit real `.env` files.
- Root DB migration scripts read `DATABASE_URL` from the shell environment; they do not load `backend/.env` automatically.
Important variables:
| Variable | Used by | Notes |
| --- | --- | --- |
| `DATABASE_URL` | backend, root migration scripts | Preferred external Postgres connection string. |
| `DB_USER`, `DB_PASS`, `DB_HOST`, `DB_PORT`, `DB_NAME` | backend fallback | Used only when `DATABASE_URL` is absent. |
| `JWT_SECRET` | backend auth | Required for token/session-compatible auth paths. |
| `ALLOWED_ORIGINS` | backend CORS | Comma-separated allowed frontend origins. |
| `SESSION_COOKIE_NAME`, `SESSION_TTL_DAYS` | backend cookies | Optional; defaults are defined in code. |
| `VITE_API_URL` | frontend | Defaults to `http://localhost:5000`. |
| `VITE_ALLOWED_HOSTS` | Vite dev server | Comma-separated host allowlist. |
| `PLAYWRIGHT_BASE_URL` | Playwright | Defaults to `http://localhost:3010`. |
## Run Locally
Docker dev stack:
```bash
docker compose -f docker-compose.dev.yml up
```
Separate terminals:
```bash
npm run dev:backend
npm run dev:frontend
```
Default local endpoints:
- Backend: `http://localhost:5000`
- Frontend through Docker compose port mapping: `http://localhost:3010`
- Frontend direct Vite default: `http://localhost:5173`
## Verification Commands
Root entry points:
```bash
npm test
npm run lint
npm run typecheck
npm run audit
npm run build:backend
npm run build:frontend
npm run build
npm run test:e2e
```
Migration checks:
```bash
npm run db:migrate:status
npm run db:migrate:verify
npm run db:migrate:stale:check
```
Do not run `npm run db:migrate` against a shared or production database unless that is the explicit operator task.
## Common Troubleshooting
- `DATABASE_URL is required`: export/set `DATABASE_URL` in the shell before root migration commands.
- `psql executable was not found in PATH`: install PostgreSQL client tools.
- Frontend cannot reach backend: confirm `VITE_API_URL`, backend port `5000`, and backend `ALLOWED_ORIGINS`.
- Playwright starts the frontend but API calls fail: start the backend separately or use the Docker dev stack.
- CORS blocked origin: add the exact frontend origin to `ALLOWED_ORIGINS`.
## Before Finishing Work
- Re-read `AGENTS.md` and any relevant deeper doc.
- Run the smallest useful checks first.
- For API behavior changes, add/update Jest tests with negative cases.
- For user-facing UI behavior changes, add/update focused Playwright coverage.
- Summarize any command that failed, whether it appears pre-existing, and the unresolved risk.

36
docs/PLANS.md Normal file
View File

@ -0,0 +1,36 @@
# Planning Template
Use this template for multi-step features, refactors, risky bugfixes, or DB work. Keep plans short and update them as evidence changes.
## Goal
- What user-visible or operator-visible outcome should be true?
## Context
- Relevant files, routes, data tables, docs, tests, and prior decisions.
- Current behavior and desired behavior.
## Constraints
- External DB only; migrations go in `packages/db/migrations`.
- No cron, workers, or background jobs.
- RBAC must be enforced server-side.
- No secrets, receipt bytes, or full invite codes in logs.
- Preserve current behavior outside the target area.
## Milestones
1. Recon and evidence.
2. Minimal design.
3. Implementation slice.
4. Tests and verification.
5. Documentation update.
## Validation
- Commands to run.
- Manual checks needed.
- Negative cases to cover.
## Rollback
- Files or migrations that would need reverting.
- Data or operator action needed, if any.
## Open Questions
- Only questions that materially affect architecture, runtime behavior, public APIs, data storage, security, deployment, package manager, or dependency footprint.

61
docs/PROJECT_MAP.md Normal file
View File

@ -0,0 +1,61 @@
# Project Map
This is the fast orientation map for Fiddy.
## Stack
- Backend: Express 5 on Node 20, CommonJS modules, PostgreSQL via `pg`.
- Frontend: React 19 + Vite, mostly JSX with partial TypeScript.
- Package manager: npm.
- Database: external on-prem Postgres. Migrations are canonical in `packages/db/migrations`.
## Root
- `PROJECT_INSTRUCTIONS.md`: source-of-truth constraints and delivery contract.
- `AGENTS.md`: concise Codex/human working guide.
- `DEBUGGING_INSTRUCTIONS.md`: required bugfix workflow.
- `package.json`: root DB, test, lint, typecheck, build, and e2e command entry points.
- `docker-compose.dev.yml`: local app containers with backend env loaded from `backend/.env`.
- `.gitea/workflows`: deploy workflows for `main` and `main-new`.
- `.github/copilot-instructions.md`: compatibility shim that points back to root instructions.
## Backend
- `backend/server.js`: starts the Express app.
- `backend/app.js`: middleware, CORS, route mounting, and error handling.
- `backend/build.js`: copies runtime backend files into `backend/dist` for the existing backend build script.
- `backend/routes`: Express routers.
- `backend/controllers`: request handlers.
- `backend/models`: database query modules.
- `backend/services`: domain service logic where present.
- `backend/middleware`: auth, optional auth, RBAC, request IDs, rate limiting, and image upload processing.
- `backend/utils`: logging, HTTP response helpers, cookies, redaction.
- `backend/tests`: Jest/Supertest tests run from the root Jest config.
- `backend/migrations`: legacy/reference SQL only; do not add new canonical migrations here.
## Frontend
- `frontend/src/main.tsx`: browser entry point.
- `frontend/src/App.jsx`: top-level routing and providers.
- `frontend/src/config.ts`: API base URL.
- `frontend/src/api`: API wrappers and shared Axios client.
- `frontend/src/context`: app state providers.
- `frontend/src/hooks`: reusable UI-facing hooks.
- `frontend/src/pages`: route-level pages.
- `frontend/src/components`: shared and domain UI components.
- `frontend/src/styles`: global, page, component, and theme CSS.
- `frontend/tests`: Playwright e2e tests.
## Database and Scripts
- `packages/db/migrations`: canonical SQL migration set.
- `packages/db/migrations/stale-files.json`: known skipped/stale migration filenames.
- `scripts/db-migrate*.js`: migration apply/status/verify/new/stale helpers.
- `docs/DB_MIGRATION_WORKFLOW.md`: operator runbook for DB changes.
## Documentation
- `docs/DEVELOPMENT.md`: setup, run, verification, and troubleshooting.
- `docs/AGENTIC_CONTRACT_MAP.md`: maps Next.js-oriented architecture language to the current Express/Vite stack.
- `docs/PLANS.md`: lightweight template for multi-step work.
- `docs/architecture`, `docs/features`, `docs/guides`, `docs/migration`: deeper reference docs.
- `docs/archive`: historical implementation notes; useful context, not necessarily current.
## Known Maintainability Hotspots
- `frontend/src/pages/GroceryList.jsx` is large and should be split only during focused UI work.
- `frontend/src/components/manage/ManageHousehold.jsx`, `backend/services/group-invites.service.js`, and `backend/models/group-invites.model.js` are also large enough to review carefully before editing.
- Some older README/guides may describe pre-session or pre-household behavior; verify against current code and root instructions before relying on them.

View File

@ -1,77 +1,41 @@
# Documentation Index # Documentation Index
This directory contains all project documentation organized by category. This directory contains practical project documentation. Root-level rules still take precedence:
## 📁 Directory Structure 1. `../PROJECT_INSTRUCTIONS.md`
2. `../DEBUGGING_INSTRUCTIONS.md` for bugfix work
3. `../AGENTS.md` for Codex and human workflow shortcuts
### `/architecture` - System Design & Structure ## Start Here
- **[component-structure.md](architecture/component-structure.md)** - Frontend component organization and patterns - `PROJECT_MAP.md`: quick repo structure and ownership map.
- **[multi-household-architecture-plan.md](architecture/multi-household-architecture-plan.md)** - Multi-household system architecture design - `DEVELOPMENT.md`: install, run, test, lint, build, and troubleshooting.
- `DB_MIGRATION_WORKFLOW.md`: external Postgres migration runbook.
- `AGENTIC_CONTRACT_MAP.md`: maps source-of-truth architecture language to the current Express/Vite stack.
- `PLANS.md`: lightweight template for multi-step work.
### `/features` - Feature Implementation Details ## Architecture
- **[classification-implementation.md](features/classification-implementation.md)** - Item classification system (zones, types, groups) - `architecture/component-structure.md`: frontend component organization and patterns.
- **[image-storage-implementation.md](features/image-storage-implementation.md)** - Image storage and handling (bytea, MIME types) - `architecture/multi-household-architecture-plan.md`: multi-household system planning notes.
### `/guides` - How-To & Reference Guides ## Features
- **[api-documentation.md](guides/api-documentation.md)** - REST API endpoints and usage - `features/classification-implementation.md`: item classification notes.
- **[frontend-readme.md](guides/frontend-readme.md)** - Frontend development guide - `features/image-storage-implementation.md`: image storage and handling notes.
- **[MOBILE_RESPONSIVE_AUDIT.md](guides/MOBILE_RESPONSIVE_AUDIT.md)** - Mobile-first design guidelines and audit checklist
- **[setup-checklist.md](guides/setup-checklist.md)** - Development environment setup steps
### `/migration` - Database Migrations & Updates ## Guides
- **[MIGRATION_GUIDE.md](migration/MIGRATION_GUIDE.md)** - Multi-household migration instructions (also in `backend/migrations/`) - `guides/api-documentation.md`: REST API reference. Verify against current code before changing APIs.
- **[POST_MIGRATION_UPDATES.md](migration/POST_MIGRATION_UPDATES.md)** - Required updates after migration - `guides/frontend-readme.md`: frontend development notes.
- `guides/MOBILE_RESPONSIVE_AUDIT.md`: mobile design and audit checklist.
- `guides/setup-checklist.md`: older setup checklist; prefer `DEVELOPMENT.md` for current commands.
### `/archive` - Completed Implementation Records ## Migration
Historical documentation of completed features. Useful for reference but not actively maintained. - `migration/MIGRATION_GUIDE.md`: historical multi-household migration instructions.
- `migration/POST_MIGRATION_UPDATES.md`: historical post-migration notes.
- **[ACCOUNT_MANAGEMENT_IMPLEMENTATION.md](archive/ACCOUNT_MANAGEMENT_IMPLEMENTATION.md)** - Phase 4: Display name and password change ## Archive
- **[code-cleanup-guide.md](archive/code-cleanup-guide.md)** - Code cleanup checklist (completed) Files in `archive/` are historical implementation records. They are useful for context, but they are not guaranteed to describe current behavior.
- **[HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md](archive/HOUSEHOLD_MANAGEMENT_IMPLEMENTATION.md)** - Household management UI implementation
- **[IMPLEMENTATION_STATUS.md](archive/IMPLEMENTATION_STATUS.md)** - Multi-household migration sprint status
- **[settings-dark-mode.md](archive/settings-dark-mode.md)** - Dark mode implementation notes
- **[TEST_SUITE_README.md](archive/TEST_SUITE_README.md)** - Testing infrastructure documentation
--- ## Documentation Rules
- Keep docs concise and linked to real files or commands.
## 📄 Root-Level Documentation - Prefer updating `PROJECT_MAP.md` and `DEVELOPMENT.md` before adding new top-level docs.
- Move completed implementation narratives to `archive/` when they are no longer active runbooks.
These files remain at the project root for easy access: - Keep text encoding clean.
- **[../README.md](../README.md)** - Project overview and quick start
- **[../PROJECT_INSTRUCTIONS.md](../PROJECT_INSTRUCTIONS.md)** - Canonical project constraints and delivery contract
- **[../AGENTS.md](../AGENTS.md)** - Agent behavior and guardrails
- **[../DEBUGGING_INSTRUCTIONS.md](../DEBUGGING_INSTRUCTIONS.md)** - Required bugfix workflow
- **[../.github/copilot-instructions.md](../.github/copilot-instructions.md)** - Copilot compatibility shim to root instructions
---
## 🔍 Quick Reference
**Setting up the project?** → Start with [setup-checklist.md](guides/setup-checklist.md)
**Understanding the API?** → See [api-documentation.md](guides/api-documentation.md)
**Working on mobile UI?** → Check [MOBILE_RESPONSIVE_AUDIT.md](guides/MOBILE_RESPONSIVE_AUDIT.md)
**Need architecture context?** → Read [AGENTIC_CONTRACT_MAP.md](AGENTIC_CONTRACT_MAP.md) and [../PROJECT_INSTRUCTIONS.md](../PROJECT_INSTRUCTIONS.md)
**Running migrations?** → Follow [DB_MIGRATION_WORKFLOW.md](DB_MIGRATION_WORKFLOW.md)
---
## 📝 Contributing to Documentation
When adding new documentation:
1. **Guides** (`/guides`) - General how-to, setup, reference
2. **Features** (`/features`) - Specific feature implementation details
3. **Architecture** (`/architecture`) - System design, patterns, structure
4. **Migration** (`/migration`) - Database migrations and upgrade guides
5. **Archive** (`/archive`) - Completed implementation records (for reference only)
Keep documentation:
- ✅ Up-to-date with code changes
- ✅ Concise and scannable
- ✅ Linked to relevant files (use relative paths)
- ✅ Organized by category

2
frontend/.env.example Normal file
View File

@ -0,0 +1,2 @@
VITE_API_URL=http://localhost:5000
VITE_ALLOWED_HOSTS=localhost,127.0.0.1

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"typecheck": "tsc -b",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
@ -19,8 +20,8 @@
"react-router-dom": "^7.9.6" "react-router-dom": "^7.9.6"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.52.0",
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@playwright/test": "^1.58.2",
"@types/node": "^24.10.0", "@types/node": "^24.10.0",
"@types/react": "^19.2.5", "@types/react": "^19.2.5",
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.2",

View File

@ -170,7 +170,7 @@ export default function ConfirmSlideModal({
onPointerCancel={handlePointerCancel} onPointerCancel={handlePointerCancel}
aria-label="Slide to confirm" aria-label="Slide to confirm"
> >
> &gt;
</button> </button>
</div> </div>
</div> </div>

View File

@ -150,8 +150,8 @@ test("manage stores opens a modal to edit and delete household store items", asy
const managerModal = page.locator(".store-items-modal"); const managerModal = page.locator(".store-items-modal");
await expect(managerModal).toBeVisible(); await expect(managerModal).toBeVisible();
await expect(managerModal.getByText("milk")).toBeVisible(); await expect(managerModal.getByText("milk", { exact: true })).toBeVisible();
await expect(managerModal.getByText("apples")).toBeVisible(); await expect(managerModal.getByText("apples", { exact: true })).toBeVisible();
await managerModal.locator(".store-items-table-row").filter({ hasText: "apples" }).getByRole("button", { name: "Edit Settings" }).click(); await managerModal.locator(".store-items-table-row").filter({ hasText: "apples" }).getByRole("button", { name: "Edit Settings" }).click();
const editorModal = page.locator(".available-item-editor-modal"); const editorModal = page.locator(".available-item-editor-modal");

View File

@ -271,7 +271,9 @@ test("add-details modal validates with toasts and persists classification detail
const yogurtRow = page.locator(".glist-li").filter({ hasText: "yogurt" }); const yogurtRow = page.locator(".glist-li").filter({ hasText: "yogurt" });
await expect(yogurtRow).toBeVisible(); await expect(yogurtRow).toBeVisible();
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Added item"); await expect(
page.locator(".action-toast.action-toast-success").filter({ hasText: "Added item" })
).toContainText("Added item");
await openEditModal(yogurtRow, page); await openEditModal(yogurtRow, page);
@ -302,7 +304,9 @@ test("edit modal supports zone-only updates and shows API error toasts", async (
await editModal.locator(".edit-modal-select").nth(1).selectOption("Checkout Area"); await editModal.locator(".edit-modal-select").nth(1).selectOption("Checkout Area");
await editModal.getByRole("button", { name: "Save Changes" }).click(); await editModal.getByRole("button", { name: "Save Changes" }).click();
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Updated item"); await expect(
page.locator(".action-toast.action-toast-success").filter({ hasText: "Updated item" })
).toContainText("Updated item");
await expect(editModal).toBeHidden(); await expect(editModal).toBeHidden();
await openEditModal(yogurtRow, page); await openEditModal(yogurtRow, page);

View File

@ -39,14 +39,14 @@ test("new users with no households see create and join actions instead of a load
await expect(page.getByRole("button", { name: "Create or Join Household" })).toBeVisible(); await expect(page.getByRole("button", { name: "Create or Join Household" })).toBeVisible();
await expect(page.getByRole("heading", { name: "No household yet" })).toBeVisible(); await expect(page.getByRole("heading", { name: "No household yet" })).toBeVisible();
await expect(page.getByRole("button", { name: "Create Household" })).toBeVisible(); await expect(page.getByRole("button", { name: "Create Household", exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "Join Household" })).toBeVisible(); await expect(page.getByRole("button", { name: "Join Household", exact: true })).toBeVisible();
await page.getByRole("button", { name: "Join Household" }).click(); await page.getByRole("button", { name: "Join Household", exact: true }).click();
await expect(page.getByLabel("Invite Code or Link")).toBeVisible(); await expect(page.getByLabel("Invite Code or Link")).toBeVisible();
await page.getByRole("button", { name: "Close household dialog" }).click(); await page.getByRole("button", { name: "Close household dialog" }).click();
await page.getByRole("button", { name: "Create Household" }).click(); await page.getByRole("button", { name: "Create Household", exact: true }).click();
await expect(page.getByLabel("Household Name")).toBeVisible(); await expect(page.getByLabel("Household Name")).toBeVisible();
await page.getByRole("button", { name: "Close household dialog" }).click(); await page.getByRole("button", { name: "Close household dialog" }).click();
@ -54,6 +54,6 @@ test("new users with no households see create and join actions instead of a load
await expect(page.getByRole("heading", { name: "Manage" })).toBeVisible(); await expect(page.getByRole("heading", { name: "Manage" })).toBeVisible();
await expect(page.getByRole("heading", { name: "No household yet" })).toBeVisible(); await expect(page.getByRole("heading", { name: "No household yet" })).toBeVisible();
await expect(page.getByRole("button", { name: "Create Household" })).toBeVisible(); await expect(page.getByRole("button", { name: "Create Household", exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "Join Household" })).toBeVisible(); await expect(page.getByRole("button", { name: "Join Household", exact: true })).toBeVisible();
}); });

View File

@ -71,7 +71,7 @@ test("join household modal accepts invite links but rejects legacy invite codes"
}); });
await page.goto("/manage"); await page.goto("/manage");
await page.getByRole("button", { name: "Join Household" }).click(); await page.getByRole("button", { name: "Join Household", exact: true }).click();
await page.getByLabel("Invite Link").fill("HABC123"); await page.getByLabel("Invite Link").fill("HABC123");
await page.getByRole("button", { name: "Open Invite" }).click(); await page.getByRole("button", { name: "Open Invite" }).click();

View File

@ -38,7 +38,7 @@ test("login failure shows inline error and error toast", async ({ page }) => {
await page.getByPlaceholder("Password").fill("bad-password"); await page.getByPlaceholder("Password").fill("bad-password");
await page.getByRole("button", { name: "Login" }).click(); await page.getByRole("button", { name: "Login" }).click();
await expect(page.getByText("Invalid credentials")).toBeVisible(); await expect(page.getByText("Invalid credentials", { exact: true })).toBeVisible();
await expect(page.locator(".action-toast.action-toast-error")).toContainText("Login failed"); await expect(page.locator(".action-toast.action-toast-error")).toContainText("Login failed");
await expect(page.locator(".action-toast.action-toast-error")).toContainText("Invalid credentials"); await expect(page.locator(".action-toast.action-toast-error")).toContainText("Invalid credentials");
}); });

48
package-lock.json generated
View File

@ -1,10 +1,10 @@
{ {
"name": "Costco-Grocery-List", "name": "grocery-app",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "Costco-Grocery-List", "name": "grocery-app",
"devDependencies": { "devDependencies": {
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"jest": "^30.2.0", "jest": "^30.2.0",
@ -1544,9 +1544,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.2", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
@ -3130,9 +3130,9 @@
} }
}, },
"node_modules/jest-util/node_modules/picomatch": { "node_modules/jest-util/node_modules/picomatch": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@ -3421,12 +3421,12 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
@ -3671,9 +3671,9 @@
"dev": true "dev": true
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
@ -3746,9 +3746,9 @@
] ]
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.1", "version": "6.15.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"side-channel": "^1.1.0" "side-channel": "^1.1.0"
@ -4191,9 +4191,9 @@
} }
}, },
"node_modules/test-exclude/node_modules/brace-expansion": { "node_modules/test-exclude/node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
@ -4222,9 +4222,9 @@
} }
}, },
"node_modules/test-exclude/node_modules/minimatch": { "node_modules/test-exclude/node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"

View File

@ -1,5 +1,18 @@
{ {
"name": "grocery-app",
"private": true,
"scripts": { "scripts": {
"dev:backend": "npm --prefix backend run dev",
"dev:frontend": "npm --prefix frontend run dev",
"build": "npm run build:backend && npm run build:frontend",
"build:backend": "npm --prefix backend run build",
"build:frontend": "npm --prefix frontend run build",
"lint": "npm --prefix frontend run lint",
"typecheck": "npm --prefix frontend run typecheck",
"audit": "npm run audit:root && npm run audit:backend && npm run audit:frontend",
"audit:root": "npm audit",
"audit:backend": "npm --prefix backend audit",
"audit:frontend": "npm --prefix frontend audit",
"db:migrate": "node scripts/db-migrate.js", "db:migrate": "node scripts/db-migrate.js",
"db:migrate:status": "node scripts/db-migrate-status.js", "db:migrate:status": "node scripts/db-migrate-status.js",
"db:migrate:verify": "node scripts/db-migrate-verify.js", "db:migrate:verify": "node scripts/db-migrate-verify.js",