Restaurant shift swap scheduler with manager approval
Internal web app where restaurant staff post and accept shift swaps, and a manager approves them, built with Next.js, TypeScript and SQLite.
nextjstypescriptsqliteprismavitest
Built 1 time, downloaded 0 times.
Install from your terminal
Run this in your project folder. It signs you in, shows what changes and asks before it overwrites a file.
npx claude-lab add restaurant-shift-swap-scheduler-with-manager-approval-952e7bCLAUDE.md# Restaurant shift swap scheduler
## What this app is
An internal web app where restaurant staff post shifts they want to swap. A coworker accepts the swap, and the manager approves or rejects it. It is for a small team and runs on one shared machine or a small server.
## Stack
Use this stack exactly unless the user changes it.
- Next.js latest stable with App Router
- TypeScript in strict mode
- Prisma ORM with a SQLite file at prisma/dev.db
- Vitest for unit tests
- ESLint with Next.js defaults
## Commands
- `npm install`: install dependencies (asks first, see permissions)
- `npm run dev`: start the app on port 3000
- `npm run typecheck`: TypeScript check
- `npm run lint`: ESLint check
- `npm test`: Vitest unit tests
- `npm run build`: production build
- `npx prisma migrate dev`: apply schema changes to the dev database
- `npx prisma studio`: browse the data in a browser
## Verification
Before saying a task is done, run typecheck, lint, test and build. Report the result of each one with its exit code. The `verify` skill does this.
## Business rules
- All swap rules live in SPEC.md. Read SPEC.md before changing swap logic.
- Never change SPEC.md without asking the user first.
- Swap rule logic lives in `src/lib/swaps.ts` and is tested there.
## Secrets
- Real secret values live only in `.env`. Never commit `.env`.
- `.env.example` holds placeholders only, such as `SESSION_SECRET=YOUR_SESSION_SECRET`.
- Never print real secret values in chat, code, tests or logs.
## Code style
- Keep functions small.
- Use one file per route group.
- Put business rules in `src/lib/swaps.ts`, not in routes or components.
- Do not add new dependencies without asking first.
## Review
Every change is judged against the checklist in @REVIEW.md.
## Gotchas
- SQLite allows one writer at a time. Wrap every approval change in a single Prisma transaction.
- Dates are stored in UTC. Display them in the restaurant's local time zone, set by `RESTAURANT_TZ` in `.env`.
- Staff login uses a signed cookie that depends on `SESSION_SECRET`. Keep that value out of git.
## Working habits
- Use `/clear` between unrelated tasks.
- Use `/compact` at natural breaks in a long task.
- Hand long test runs to the `test-runner` subagent so the main session stays small.
- Explore, plan, code, then verify. Give Claude a way to check its work.
.claude/agents/test-runner.md---
name: test-runner
description: Runs npm run lint and npm test for this app and returns only failing tests with file, line and first error line. Use it to keep long test output out of the main session.
model: haiku
effort: low
tools: Bash, Read
disallowedTools: Edit, Write
maxTurns: 12
---
You run the lint and test checks for the restaurant shift swap app and report results briefly. You never edit files.
## Steps
1. Run `npm run lint` and record its exit code.
2. Run `npm test` and record its exit code and the pass and fail counts.
3. If both exit codes are 0, reply with one line: `PASS` followed by the lint result and the test counts, for example `PASS: lint 0, tests 14 passed, 0 failed`.
## When something fails
For each failing test, list:
- the test name
- the file path
- the line number
- the first line of the error message
Do not paste full stack traces or the whole test output. Keep the reply under 30 lines.
For lint errors, list the file, line, and rule name for each error.
## Rules
- Never edit, create or delete files.
- Never print values from `.env` or any `.env.*` file. Read them only if a test needs a variable name, and report the name, never the value.
- If a command is missing or cannot start, say so in one line and stop.
.claude/settings.json{
"model": "sonnet",
"effort": "medium",
"permissions": {
"defaultMode": "default",
"blockWritesOutsideWorkingDirectories": true,
"allow": [
"Bash(npm run typecheck)",
"Bash(npm run lint)",
"Bash(npm test)",
"Bash(npm run build)",
"Bash(npm run dev)",
"Bash(npx prisma migrate dev)",
"Bash(npx prisma studio)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)"
],
"ask": [
"Bash(git push *)",
"Bash(git commit *)",
"Bash(npx prisma migrate reset *)",
"Bash(npm install *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git push --force *)",
"Bash(git reset --hard *)",
"Read(./.env)",
"Read(./.env.local)",
"Read(./.env.production)",
"Read(./.env.development)",
"Read(./.env.test)"
]
}
}
.claude/skills/verify/SKILL.md---
name: verify
description: Runs the typecheck, lint, test, build and health-route checks for the restaurant shift swap app and reports PASS or FAIL for each. Use before saying any task is done.
allowed-tools: Bash, Read
---
# Verify
Run this skill before you tell the user a task is done. It checks the restaurant shift swap app in this order and stops at the first failure.
## Steps
1. Run `npm run typecheck`. Show the exit code.
2. Run `npm run lint`. Show the exit code.
3. Run `npm test`. For long output, hand the run to the `test-runner` subagent and ask it to return only failures with file and line. Show the exit code.
4. Run `npm run build`. Show the exit code.
5. Smoke check: confirm `npm run dev` is running on port 3000. If it is not running, report FAIL and say the dev server is not running. Do not start it unless the user asks. If it is running, run `curl -s localhost:3000/api/health` and confirm the JSON contains `"ok": true`.
If a step fails, stop. Read the failing file and the first error line, report them, and skip the remaining steps. Mark the skipped steps as NOT RUN.
## Rules
- Never mark a check as passed without showing its exit code or the curl response.
- Never print values from `.env`. If an error message contains a secret, replace it with `[redacted]`.
- Do not edit files in this skill. Report what to fix and let the main session make the change.
## Output format
Reply with a four-line table, one row per check, then one smoke line, then one sentence.
| Check | Result |
|---|---|
| typecheck | PASS or FAIL (exit code) |
| lint | PASS or FAIL (exit code) |
| test | PASS or FAIL (exit code) |
| build | PASS or FAIL (exit code) |
Smoke (/api/health): PASS or FAIL
If anything failed, add one sentence saying which file and message to fix first. If everything passed, say "All checks passed."
INSTALL.md# Install and first run
This guide takes you from an empty folder to a running app with Claude Code set up. Follow the steps in order. Each step tells you how to check that it worked.
## Before you start
- Claude Code is an AI assistant that runs in your terminal. It reads your project, edits files and runs commands when you approve them.
- A terminal is a text window where you type commands. On Windows, use Windows Terminal or PowerShell. On Mac, use Terminal.
- You need a Claude account or an API key. Never paste an API key into a file that gets committed to git.
## 1. Install Node.js and Git
1. Install Node.js LTS from nodejs.org.
2. Check it: run `node --version`. You should see a version number.
3. Install Git from git-scm.com.
4. Check it: run `git --version`.
If a command is not found, close the terminal, open a new one and try again.
## 2. Install Claude Code
1. Install Claude Code with the official installer for your system.
2. Run `claude` once and log in with your account.
## 3. Create the project folder
1. Create a folder named restaurant-shift-swap.
2. Open a terminal inside it and run `git init`.
3. Copy these into the folder: CLAUDE.md, SPEC.md, PLAN.md, REVIEW.md, INSTALL.md and the .claude folder (settings.json, skills/verify/SKILL.md, agents/test-runner.md).
## 4. Add the plugins
The official plugin marketplace is already added. Only run this if you want the extra business plugins:
`claude plugin marketplace add anthropics/knowledge-work-plugins`
Then, inside Claude Code, install each plugin:
- `/plugin install code-review@claude-plugins-official` runs a structured review in Phase 5.
- `/plugin install commit-commands@claude-plugins-official` gives simple commit and branch commands.
- `/plugin install security-guidance@claude-plugins-official` flags risky login, secret and input patterns.
Check the list with `/plugin`.
## 5. Check the setup
Start Claude in the project folder:
`claude`
Then check:
- `/memory` shows that CLAUDE.md is loaded.
- `/permissions` shows the allow and ask rules from .claude/settings.json.
This project sets permission mode to default, so Claude asks before each new kind of action. That is intended while you are learning.
## 6. Fill in SPEC.md
Open SPEC.md and answer the open questions at the bottom with your restaurant's details. Do this before Phase 2.
## 7. Create your local secrets file
Create a file named .env in the project folder with:
```
SESSION_SECRET=a-long-random-string-you-make
RESTAURANT_TZ=America/Chicago
```
Use a long random value for SESSION_SECRET and your own timezone for RESTAURANT_TZ. Never commit .env.
Do not run `git add` yet. Phase 1 creates .gitignore, which keeps .env out of git. Commit only after that.
## 8. Start Phase 1
Paste this kickoff prompt as your first message in Claude Code:
```text
/goal Phase 1 Scaffold is complete when all of these hold. Create a Next.js
latest stable app with App Router, TypeScript strict mode, Prisma with SQLite
at prisma/dev.db, Vitest, and ESLint defaults, in the current folder. Add a
GET /api/health route that returns JSON with ok set to true. Add one passing
Vitest test for that route. Add .env.example with placeholder values only:
SESSION_SECRET=YOUR_SESSION_SECRET and RESTAURANT_TZ=YOUR_TIMEZONE. Add
.gitignore entries for .env, node_modules and prisma/dev.db. CHECK: run npm
run typecheck, npm run lint, npm test and npm run build; each must exit 0 and
show the exit code in the conversation. Then run npm run dev in the background
and curl localhost:3000/api/health and show the response. CONSTRAINTS: add no
dependencies beyond what Next.js, Prisma, Vitest and bcrypt-free scaffolding
need; do not create login or swap logic yet; do not edit SPEC.md, CLAUDE.md or
REVIEW.md; never write a real secret into any file; do not run git commit or
git push. TURN LIMIT: stop after 30 turns. If you stop early, report exactly
which check fails and what you tried.
```
`/goal` keeps Claude working until its check passes or the turn limit is reached. A small separate model judges whether the condition is met, so each goal has a measurable end state. Type `/goal clear` to stop a goal early.
## 9. Move between phases
When a phase is done:
1. Run `/clear`.
2. Read PLAN.md and the next phase.
3. Approve the plan Claude proposes.
4. Paste that phase's `/goal` prompt.
## What each file does
- CLAUDE.md: project memory that Claude reads every session. Leave it alone unless you want to change a command or rule.
- SPEC.md: the business rules. Fill in the open questions. Claude will not change it without asking you.
- PLAN.md: the five build phases and their `/goal` prompts.
- REVIEW.md: the checklist for every change.
- INSTALL.md: this guide.
- .claude/settings.json: what Claude may do in this project. Commit it.
- .claude/skills/verify/SKILL.md: the checks Claude runs before it says a task is done.
- .claude/agents/test-runner.md: a small helper that runs tests and returns only failures.
- .env: your secrets and timezone. You create it. Never commit it.
- .env.example: placeholders only. Phase 1 creates it. Safe to commit.
## Troubleshooting
- **Command not found:** close the terminal, open a new one and run the command again.
- **Claude asks to run something you do not recognise:** read the command before you approve it. If you are unsure, deny it and ask Claude what it does and why.
- **Too many tokens used:** run `/usage` to see where they went.
- **Switching tasks:** run `/clear` between unrelated tasks, and `/compact` at natural breaks.
- **A phase stops early:** read the report, which names the failing check. Fix that check, then start the phase's `/goal` prompt again.
## Backup habit
After each phase passes verify, read the changes with `git diff`, then commit. Claude asks before any `git commit`, so you approve it only after you have read the diff. You can use the commit-commands plugin to help write the message.
PLAN.md# Build plan
Five phases. Each phase follows the same steps: Explore, Plan, Code, Verify. Each phase ends with a /goal prompt. Paste it into a fresh Claude session and let it run until the check passes or the turn limit is reached.
Before you start Phase 1, complete the setup in INSTALL.md.
## Phase 1: Scaffold
Goal: a runnable Next.js app with a health route and one passing test.
1. **Explore:** Claude reads SPEC.md and CLAUDE.md. No code yet.
2. **Plan:** Claude lists the files it will create. You approve.
3. **Code:** Claude builds the scaffold.
4. **Verify:** Claude runs the checks in the goal below.
```
/goal Phase 1 Scaffold is complete when all of these hold. Create a Next.js latest stable app with App Router, TypeScript strict mode, Prisma with SQLite at prisma/dev.db, Vitest, and ESLint defaults, in the current folder. Add GET /api/health returning JSON with ok set to true. Add one passing Vitest test for that route. Add .env.example with placeholders only: SESSION_SECRET=YOUR_SESSION_SECRET and RESTAURANT_TZ=YOUR_TIMEZONE. Add .gitignore entries for .env, node_modules and prisma/dev.db. CHECK: run npm run typecheck, npm run lint, npm test and npm run build; each must exit 0, and show the exit codes. Then run npm run dev in the background and curl localhost:3000/api/health and show the response. CONSTRAINTS: no dependencies beyond the scaffold; no login or swap logic yet; do not edit SPEC.md, CLAUDE.md or REVIEW.md; never write a real secret into any file; do not run git commit or git push. TURN LIMIT: stop after 30 turns. If you stop early, report which check fails and what you tried.
```
What to do when it is done: run `/clear`, then start Phase 2.
## Phase 2: Data and rules
Goal: the database schema, seed data, and every SPEC.md rule tested as pure functions.
1. **Explore:** Claude reads SPEC.md and the scaffold files it will touch.
2. **Plan:** Claude lists the schema, seed script and test files. You approve.
3. **Code:** Claude writes the schema, seed script and rule functions.
4. **Verify:** Claude runs the checks in the goal below.
```
/goal Phase 2 Data and rules is complete when all of these hold. Create the Prisma schema from SPEC.md (User, Shift, SwapRequest) and run npx prisma migrate dev. Write a seed script with 2 managers and 6 staff using fake names. Put every SPEC.md rule in pure functions in src/lib/swaps.ts, with unit tests. Each acceptance example in SPEC.md has a passing test that uses a fixed clock, not the current date. CHECK: npm run typecheck, npm run lint, npm test and npm run build each exit 0, and show the exit codes. CONSTRAINTS: no new dependencies; do not edit SPEC.md, CLAUDE.md or REVIEW.md; no secrets in code or git; do not run git commit or git push. TURN LIMIT: stop after 35 turns. If you stop early, report which check fails and what you tried.
```
What to do when it is done: run `/clear`, then start Phase 3.
## Phase 3: Staff screens
Goal: staff can log in, see their shifts, request a swap, and accept an open swap.
1. **Explore:** Claude reads SPEC.md, src/lib/swaps.ts and the existing routes.
2. **Plan:** Claude lists the pages, API routes and the one new dependency, bcrypt, for login. You approve.
3. **Code:** Claude builds the login, the My Shifts page, the post-swap page and the accept-swap page.
4. **Verify:** Claude runs the checks in the goal below.
```
/goal Phase 3 Staff screens is complete when all of these hold. Build login with name and password, with passwords hashed by bcrypt and a signed session cookie using SESSION_SECRET from .env. Build a My Shifts page, a page to post a swap request, and a page to accept an open swap. Every flow is covered by API tests: login, list my shifts, post a swap, accept a swap, and the overtime and role-mismatch refusals. CHECK: npm run typecheck, npm run lint, npm test and npm run build each exit 0, and show the exit codes. CONSTRAINTS: bcrypt is the only new dependency, and it was approved at the Plan step; do not edit SPEC.md, CLAUDE.md or REVIEW.md; never write the SESSION_SECRET value into any file, log or test; do not run git commit or git push. TURN LIMIT: stop after 40 turns. If you stop early, report which check fails and what you tried.
```
What to do when it is done: run `/clear`, then start Phase 4.
## Phase 4: Manager screens
Goal: managers can approve or reject accepted swaps and view the schedule.
1. **Explore:** Claude reads SPEC.md, the swap rules and the staff routes.
2. **Plan:** Claude lists the approval queue, the schedule view and the transaction design. You approve.
3. **Code:** Claude builds the approval queue, approve and reject buttons, and the two-week schedule view.
4. **Verify:** Claude runs the checks in the goal below.
```
/goal Phase 4 Manager screens is complete when all of these hold. Build an approval queue of accepted swaps with approve and reject buttons, and a two-week schedule view. Approval runs in one Prisma transaction. Tests prove that approve reassigns the shift, reject leaves the shift unchanged, and staff get a 403 on the approve and reject routes. CHECK: npm run typecheck, npm run lint, npm test and npm run build each exit 0, and show the exit codes. CONSTRAINTS: no new dependencies; do not edit SPEC.md, CLAUDE.md or REVIEW.md; no secrets in code or git; do not run git commit or git push. TURN LIMIT: stop after 35 turns. If you stop early, report which check fails and what you tried.
```
What to do when it is done: run `/clear`, then start Phase 5.
## Phase 5: Hardening and review
Goal: a reviewed, documented app that builds from a clean clone.
1. **Explore:** Claude reads REVIEW.md and lists every changed file.
2. **Plan:** Claude lists the review findings it expects to fix and the README section. You approve.
3. **Code:** Claude runs the code-review plugin, fixes findings, and writes the README section.
4. **Verify:** Claude runs the checks in the goal below.
```
/goal Phase 5 Hardening and review is complete when all of these hold. Run the code-review plugin on all changes and fix every finding. Confirm no .env file is tracked, using git ls-files .env, which must print nothing. Write a README section on running the app. Clone the repo into a temporary folder, run npm install and npm run build there, and show the result. Every item in REVIEW.md is checked off. CHECK: npm run typecheck, npm run lint, npm test and npm run build each exit 0, and show the exit codes. CONSTRAINTS: no new dependencies; do not edit SPEC.md; no secrets in code, tests or git; do not run git commit or git push. TURN LIMIT: stop after 30 turns. If you stop early, report which check fails and what you tried.
```
What to do when it is done: review the diff, then commit the phase with git yourself or ask Claude to do it with commit-commands.
REVIEW.md# Review checklist
Claude checks this list before it says a task is done. You check it before you commit.
## Correctness
- [ ] Swap rules in SPEC.md are enforced on the server, not only in the UI.
- [ ] Each rule in SPEC.md has a server-side check in src/lib/swaps.ts.
## Authorization
- [ ] Staff can only act on their own shifts and swaps.
- [ ] Only managers can approve or reject a swap.
- [ ] Every API route checks the user's role before doing any work.
## Data safety
- [ ] Approval changes run in one Prisma transaction.
- [ ] No raw SQL is built from strings. Use Prisma queries.
- [ ] No shift history is deleted.
## Secrets
- [ ] No real keys, passwords or SESSION_SECRET values appear in code, tests, logs or git history.
- [ ] .env is never committed. .env.example holds placeholders only.
## Tests
- [ ] Every rule has a test.
- [ ] Tests do not depend on today's date unless they set a fixed clock.
## Simplicity
- [ ] No new dependency is added without a stated reason in the change summary.
- [ ] No unused code.
- [ ] No file is over 300 lines.
## UX
- [ ] Rule violations show a clear message, for example: "This swap would take Alex over 40 hours."
- [ ] Dates show in restaurant local time (RESTAURANT_TZ).
## Plain-English summary
- [ ] Each change ends with what changed, how it was verified, and any open risk.
SPEC.md# Shift swap spec
Status: draft. Answer the open questions at the bottom before Phase 2. Ask the owner before changing any rule below.
## Roles
- **Staff:** see their own shifts and open swaps, and can request and accept swaps.
- **Manager:** approves or rejects accepted swaps, and edits the schedule.
- Each user has exactly one role.
## Data model (sketch)
- **User:** id, name, role (staff or manager), role_title, weekly_hours_limit (default 40)
- **Shift:** id, user_id, starts_at, ends_at, station
- **SwapRequest:** id, shift_id, requested_by, accepted_by (nullable), status, created_at, decided_by (nullable), decided_at (nullable), note
Dates are stored in UTC and shown in the restaurant's local time.
## Swap lifecycle
| From | To | Who | When |
|----------|-----------|----------------------------|---------------------------------------------------|
| (none) | open | Staff (the shift owner) | They request a swap for their own shift |
| open | accepted | Staff (not the requester) | A coworker takes the shift |
| open | cancelled | Requester | They withdraw the request |
| accepted | approved | Manager | Manager confirms; shift moves to accepted_by |
| accepted | rejected | Manager | Manager declines; shift stays with the requester |
No other transitions are allowed. Approved, rejected and cancelled are final.
## Rules
1. A swap must be requested at least 24 hours before the shift starts.
2. The accepting coworker must have the same role_title as the shift owner.
3. After the swap, the accepting coworker must not go over weekly_hours_limit.
4. A shift can have only one open swap at a time.
5. Only a manager can move a swap to approved or rejected.
6. The server enforces every rule. The UI only shows the result.
## Notifications
None in v1. Staff check the app.
Idea for v2: notify staff when a swap is accepted or decided.
## Out of scope for v1
- Payroll
- Tips
- Clock-in
- Mobile app
- Email
- Multiple locations
## Acceptance examples
### Happy path
- **Given** Alex (server, 32 hours this week) owns a 6-hour dinner shift on Friday at 6 PM, more than 24 hours away, and Alex (server, 30 hours this week) has no open swap
- **When** Alex requests the swap, Alex accepts it, and a manager approves it
- **Then** the shift belongs to Alex, the swap status is approved, and Alex has 36 hours this week
### Rejected for overtime
- **Given** Alex owns a 6-hour shift, and Alex (server, 38 hours this week) is the same role
- **When** Alex tries to accept the swap
- **Then** the accept is refused with the message "This swap would take Alex over 40 hours this week," and the swap stays open
### Rejected for role mismatch
- **Given** Alex (server) owns a shift, and Alex is a line cook
- **When** Alex tries to accept the swap
- **Then** the accept is refused with the message "Alex is a line cook. This shift needs a server." and the swap stays open
## Open questions for the owner
- [ ] What is the restaurant's time zone? (Sets RESTAURANT_TZ)
- [ ] What are the exact role titles? (For example: server, line cook, host)
- [ ] Can managers swap their own shifts? (Yes or no)
- [ ] How many staff will use the app?
- [ ] Which day does the weekly hours count start on? (For example: Monday)