Instructions for AI coding agents working on this repository. Human developers: see README.md and CONTRIBUTING.md.
Shoppimo is a real-time collaborative shopping list app — no accounts required, share via UUID link, live sync via WebSockets. It is a full-stack monorepo with a React/TypeScript frontend and a Kotlin/Ktor backend backed by PostgreSQL.
| Layer | Technology & Version |
|---|---|
| Frontend | React 18, TypeScript 5.2, Vite 5, TailwindCSS 3, Vitest 4, Cypress 15 |
| Backend | Kotlin (JVM 17), Ktor 2, Exposed ORM, PostgreSQL |
| Infra | Docker Compose (dev / prod / monitoring stacks) |
| Monitoring | Prometheus (Micrometer), Grafana, Alertmanager |
| CI | GitHub Actions (.github/workflows/ci.yml) |
npm install # install dependencies
npm run dev # dev server → http://localhost:3000
npm run build # TypeScript check + Vite production build
npm test # run Vitest unit tests (non-interactive)
npm run test:watch # Vitest in watch mode
npm run lint # ESLint (TypeScript + React rules)
npm run cypress:run # E2E tests (headless)./gradlew run # API server → http://localhost:8080
./gradlew test # JUnit tests
./gradlew shadowJar # build fat JAR
./gradlew test --no-daemon # CI-style (no background daemon)cp .env.example .env # first time only
docker compose up -d # start all services (frontend, backend, postgres)
docker compose down # stop
docker compose logs -f # stream logs
shoppimo/
├── frontend/
│ ├── src/
│ │ ├── components/ # React UI components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── services/ # API client + WebSocket client
│ │ ├── translations/ # i18n JSON files (en, de)
│ │ └── types/ # TypeScript interfaces & types
│ └── public/ # Static assets (icons, manifest)
├── backend/
│ └── src/main/kotlin/com/shoppinglist/
│ ├── routes/ # HTTP + WebSocket handlers
│ ├── models/ # Data model classes
│ └── database/ # Exposed ORM schema + queries
├── monitoring/ # Prometheus, Grafana, Alertmanager config
├── scripts/ # release.sh, version-and-build.sh, helpers
├── config/ # Frontend runtime config
├── .github/
│ ├── workflows/ # ci.yml, release.yml, feature-docker.yml
│ ├── ISSUE_TEMPLATE/
│ └── PULL_REQUEST_TEMPLATE.md
├── docker-compose.yml # Development
├── docker-compose.prod.yml # Production
├── docker-compose.monitoring.yml # Monitoring stack
├── CHANGELOG.md # Keep up to date (see below)
└── AGENTS.md # This file
NEVER work directly on
main. Every change — no matter how small — lives on its own branch. NEVER create a Pull Request unless the human explicitly asks you to. Always keep CHANGELOG.md up to date when committing changes.
git checkout main
git pull origin mainUse the naming conventions from CONTRIBUTING.md:
| Prefix | When to use |
|---|---|
feat/ |
New features |
fix/ |
Bug fixes |
docs/ |
Documentation-only changes |
chore/ |
Maintenance, tooling, dependency updates, refactoring |
test/ |
Test additions or corrections |
git checkout -b feat/my-feature-name
# or
git checkout -b fix/short-description-of-bugBranch names: lowercase, hyphen-separated, concise (e.g. feat/swipe-to-delete, fix/websocket-reconnect).
Commit messages must follow Conventional Commits:
feat: add item reordering via drag-and-drop
fix(push): correct Base64 encoding for VAPID keys
docs: update environment variable table in README
chore: upgrade Ktor to 2.3.9
test: add unit tests for autocomplete hook
refactor(backend): extract push notification serviceFormat: type(optional-scope): short imperative description
Types: feat, fix, docs, chore, test, refactor, style, perf, ci
Rules:
- Subject line ≤ 72 characters
- Use imperative mood ("add", "fix", "remove" — not "added", "fixes")
- No period at the end of the subject line
- Reference issues with
Closes #123in the body when applicable
The CHANGELOG follows Keep a Changelog format with Semantic Versioning. Add your change under ## [Unreleased] at the top:
## [Unreleased]
### Added
- **Feature name** — One-sentence description of what was added and why it matters
### Fixed
- **Bug name** — What was broken and how it was fixed
### Changed
- **Change name** — What changed and the motivation
### Removed
- **Item** — What was removed and whyUse categories: Added, Changed, Fixed, Removed, Security, Deprecated.
Write entries for a human reader — not a developer. Explain impact, not implementation detail.
# Frontend
cd frontend && npm run lint && npm test && npm run build
# Backend
cd backend && ./gradlew test --no-daemonBoth must pass with zero errors before pushing.
git push origin feat/my-feature-nameDo NOT open the PR yourself. When the human is ready to merge, provide them with:
- PR Title — follows Conventional Commits style:
feat: add swipe-to-delete for mobile - PR Body — filled-in version of the PR template (
.github/PULL_REQUEST_TEMPLATE.md):
## Description
[Concise explanation of what changed and why, 2–4 sentences]
Closes #[issue number if applicable]
## Type of Change
- [x] New feature / Bug fix / Documentation update / etc.
## Testing
- [x] Unit tests pass (`npm test` / `./gradlew test`)
- [x] I have added tests for new behavior (if applicable)
- [x] Manual testing performed
[Brief manual testing notes: what was tested, what platforms/browsers]
## Checklist
- [x] Code follows the project's style guidelines
- [x] Documentation updated (README, CHANGELOG) if needed
- [x] No debug logging left in code
- [x] No secrets or hardcoded credentials introduced- CHANGELOG summary — paste the relevant
[Unreleased]section so the reviewer can see at a glance what changed.
Present this information to the human clearly. They will paste it into GitHub when opening the PR.
- TypeScript strictly — no
anytypes; define interfaces insrc/types/ - React hooks — prefer functional components + hooks; no class components
- i18n required — all user-visible strings must use
useTranslation()/t()keys; add to bothen.jsonandde.json - TailwindCSS — utility classes only; no custom CSS unless absolutely necessary
- Service layer — API calls go in
src/services/; never fetch directly from components - Error handling — all async operations must handle errors; never swallow exceptions silently
// ✅ Correct: typed interface, i18n, service layer
const { t } = useTranslation();
const items = await apiService.getItems(listId); // via src/services/api.ts
// ❌ Wrong: direct fetch, string literals, any type
const items: any = await fetch(`/api/lists/${id}/items`).then(r => r.json());- Ktor routing — define routes in
routes/directory, one file per resource - Exposed ORM — all DB access through Exposed DSL; no raw JDBC
- Coroutines — use
suspendfunctions andDispatchers.IOfor DB/IO work - Error responses — always return structured JSON
{ "error": "..." }with appropriate HTTP status - No secrets in code — read everything from environment variables; see
.env.example
// ✅ Correct: structured error, environment config
call.respond(HttpStatusCode.NotFound, mapOf("error" to "List not found"))
val dbUrl = System.getenv("DATABASE_URL") ?: error("DATABASE_URL not set")
// ❌ Wrong: plain string error, hardcoded credential
call.respondText("not found", status = HttpStatusCode.NotFound)
val password = "hardcoded_password"- Follow existing patterns in the file you are editing
- Keep PRs small and focused — one concern per branch
- Add or update tests for every behavioral change
- No
console.log/printlndebug statements left in committed code
- Create a new branch before making any change
- Follow Conventional Commits format for every commit message
- Update
CHANGELOG.mdunder[Unreleased]for every committed change - Run lint + tests + build before considering work complete
- Add i18n keys to both
en.jsonandde.jsonfor any new UI text - Keep secrets out of code — use environment variables
- Bumping major versions of core dependencies (React, Ktor, TypeScript)
- Adding new npm or Gradle dependencies
- Modifying Docker Compose service definitions
- Changing the public API contract (new/removed/renamed endpoints)
- Modifying CI/CD workflow files
- Performing a version release (
./scripts/release.sh)
- Work directly on the
mainbranch - Open a Pull Request (unless explicitly instructed by the human)
- Commit secrets, API keys, passwords, or
.envfiles - Delete or force-push to
main - Skip running tests before pushing
- Leave
TODO,FIXME, or debug statements in committed code - Remove or skip failing tests (fix them or ask the human)
Copy .env.example to .env before starting. Key variables:
| Variable | Required | Description |
|---|---|---|
POSTGRES_PASSWORD |
✅ | Database password |
VITE_API_URL |
✅ | Backend URL for the frontend |
VITE_WS_URL |
✅ | WebSocket URL for the frontend |
VAPID_SUBJECT |
Push only | Contact URI (mailto: or https://) |
VAPID_PUBLIC_KEY |
Push only | EC public key (generate with npx web-push generate-vapid-keys) |
VAPID_PRIVATE_KEY |
Push only | EC private key |
Full variable list: see .env.example and the table in README.md.
Three GitHub Actions workflows:
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml |
Push/PR to main |
Lint + test + build (frontend & backend) |
feature-docker.yml |
Feature branches | Builds Docker images for testing |
release.yml |
Git version tags | Pushes images to ghcr.io/hechi/shoppimo |
All CI checks must pass before merging. Never bypass them.
| Document | Purpose |
|---|---|
| README.md | Project overview, quick-start, API reference |
| CONTRIBUTING.md | Contribution guidelines for humans |
| CHANGELOG.md | Full release history |
| DEPLOYMENT.md | Production deployment guide |
| SECURITY.md | Security model and vulnerability reporting |
| VERSIONING.md | Release scripts and semantic versioning |
| .github/PULL_REQUEST_TEMPLATE.md | PR template to use when helping with PRs |