Budget student recipe blog with cost per portion
Astro content site where each recipe shows cost per portion from a price table, plus a print-friendly shopping list built from chosen recipes.
astrotypescriptzodvitestprettier
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 budget-student-recipe-blog-with-cost-per-portion-8f7c62CLAUDE.md# Budget recipes
A recipe blog for budget student meals. Every recipe page shows cost per portion and links to a printable shopping list.
## Stack
- Astro with Markdown content collections in `src/content/recipes`
- TypeScript (strict)
- Zod for the recipe schema
- Vitest for cost math
- Prettier for formatting
- Node 20 or newer
## Commands
- `npm install`: install dependencies
- `npm run dev`: start the local site
- `npm run build`: build to `dist/`
- `npm test`: run Vitest
- `npm run check`: run `astro check` and `tsc`
- `npm run format`: run Prettier
Use the `verify` skill before saying anything is done.
## Folder map
- `src/content/recipes/`: one `.md` file per recipe
- `src/data/prices.json`: ingredient price table, the only place prices live
- `src/lib/cost.ts`: cost per portion and unit conversions
- `src/lib/shopping.ts`: merges and groups ingredients for the shopping list
- `src/pages/shopping-list.astro`: print page
- `src/styles/print.css`: print styles
## Rules
Cost:
- Cost per portion = sum over ingredients of (quantity converted to price unit x price) / servings.
- Never hardcode a cost in a recipe file.
- Prices live only in `prices.json`.
Units:
- Store all quantities in metric base units: g, ml, or each.
- Conversions live in `cost.ts`. Do not invent conversions in prose.
Content:
- Every recipe needs servings, prep time, cook time, ingredients with quantities, steps, and a "budget tip" line.
- No affiliate links.
- No secrets in the repo.
Prices:
- Do not guess prices in `prices.json`.
- If you are unsure of a price, add a `TODO` comment and tell the user.
Working style:
- Plan first for anything touching `cost.ts` or `shopping.ts`.
- Make small commits.
- Run `npm test` after each change.
- Ask before adding a dependency.
Review:
- Read REVIEW.md before opening a pull request or saying work is ready.
@REVIEW.md
.claude/agents/recipe-checker.md---
name: recipe-checker
description: Use to check recipe files for missing fields, bad units, or missing prices, and to run npm test and report failures only.
model: haiku
effort: low
maxTurns: 12
tools: Read, Grep, Glob, Bash(npm test), Bash(npm run build)
disallowedTools: Read(./.env), Read(./.env.*)
---
You check the budget recipe site in this project. You never edit files.
Checks, in order:
1. Every file in src/content/recipes/*.md has servings, prepMinutes, cookMinutes, ingredients, steps, and a budget tip line.
2. Every ingredient quantity uses a metric base unit (g, ml, or each). Flag any other unit.
3. No price, cost, or currency amount appears inside a recipe file. Prices belong only in src/data/prices.json.
4. Every ingredient name exists in src/data/prices.json. Flag missing ones as "cost unavailable".
5. Run `npm test` and `npm run build`. Report failures only, with the first error line.
Output format: one line per problem, as
file, line, problem, suggested fix
If nothing is wrong, reply with one line: "No problems found in N recipe files. Tests: PASS. Build: PASS."
Keep the whole reply under 25 lines. Do not paste full logs. Do not open .env files.
.claude/settings.json{
"model": "sonnet",
"effortLevel": "medium",
"env": {
"CLAUDE_CODE_SUBAGENT_MODEL": "haiku"
},
"permissions": {
"defaultMode": "acceptEdits",
"blockWritesOutsideWorkingDirectories": true,
"allow": [
"Bash(npm run *)",
"Bash(npm test)",
"Bash(npm install)",
"Bash(npx astro *)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git push --force *)",
"Bash(git reset --hard *)",
"Read(./.env)",
"Read(./.env.*)"
],
"ask": [
"Bash(git push *)",
"Bash(npm publish *)"
]
}
}
.claude/skills/new-recipe/SKILL.md---
name: new-recipe
description: Use when the user asks to add or draft a budget recipe. Creates a schema-valid recipe file with cost computed from the price table, never a hardcoded cost.
argument-hint: "<recipe name>"
---
# New recipe
Use this skill to add one recipe to `src/content/recipes/`. The cost comes from `src/lib/cost.ts` and `src/data/prices.json`. Never write a cost into the recipe file.
## Step 1: Collect the details
Ask the user for these, one short question at a time if needed:
- Servings (positive whole number).
- Ingredients with quantities, as the user gives them.
- Approximate store, so the right prices are used.
## Step 2: Convert to base units
- Store every quantity in metric base units: `g`, `ml`, or `each`.
- Do the conversion only with the rules in `src/lib/cost.ts`. If the user gives a unit with no rule there, ask the user for the weight or volume. Do not invent a conversion.
## Step 3: Check prices
- Compare each ingredient name with `src/data/prices.json`.
- List every ingredient with no price entry.
- Ask the user before adding any price. Never guess a price. If the user wants a placeholder, add a `TODO` comment and tell the user.
## Step 4: Write the file
Create `src/content/recipes/<slug>.md` with the frontmatter fields from the collection schema in `src/content.config.ts`:
- `slug`, `title`, `servings`, `prepMinutes`, `cookMinutes`
- `equipment`: string array
- `ingredients`: array of `name`, `quantity` (metric base unit), `unit` (`g`, `ml`, or `each`)
- `steps`: string array
- `budgetTip`: one plain-English line
Then add the body text the page needs, with steps in order and the budget tip.
Rules for the file:
- Keep the recipe under 80 lines of Markdown.
- Use plain English, with no jargon.
- No prices, costs, affiliate links, or secrets.
- If a needed equipment item is not in the schema, ask before adding it to the schema.
## Step 5: Verify
1. Run `npm test`.
2. Run the verify skill.
3. Report the computed cost per portion from `src/lib/cost.ts` to the user, with the date from `lastChecked` for the prices used.
If verify fails, fix the recipe file first. Only change the schema after asking the user.
.claude/skills/verify/SKILL.md---
name: verify
description: Use after any code or content change to prove it works, and before saying work is done. Runs the project checks in order and reports PASS or FAIL per step.
---
# Verify
Use this skill to prove a change works. Never say work is done without a full PASS.
## Steps in order
Run each step in this order. Stop to fix a failure before moving on.
1. **Install.** Run `npm install` only if `package-lock.json` changed since the last run.
2. **Types.** Run `npm run check` (astro check plus tsc).
3. **Tests.** Run `npm test`.
4. **Build.** Run `npm run build`.
5. **Dist.** Confirm `dist/` has one HTML file per recipe in `src/content/recipes/`. Count the recipe files and the recipe pages and compare.
## Content check
Run this after the build passes.
- Every file in `src/content/recipes/` parses against the collection schema.
- Every recipe has `servings`.
- Every recipe computes a cost per portion through `src/lib/cost.ts`.
- Report any recipe that shows "cost unavailable" and the ingredient or price missing from `src/data/prices.json`.
## Print check
Run this only if `src/lib/shopping.ts` or `src/styles/print.css` changed. Follow the print check described in REVIEW.md, then report the page count for the 6-recipe test list.
## Report format
Report a short list. One line per step:
```
install: PASS | SKIPPED (lockfile unchanged)
check: PASS
test: FAIL - <first error line>
build: not run
dist: not run
content: PASS (12 recipes, 0 cost unavailable)
print: not run
```
Do not paste full logs. Give only the first error line for each failure.
## On failure
1. Fix the smallest thing that could cause the failure.
2. Rerun only the failed step.
3. When every step passes, rerun the full verify once from the top.
## Rules
- Never mark work done with a failing step.
- Never skip a step to save time.
- Never edit `.env` files to make a step pass.
INSTALL.md# Setup guide for budget recipes
This guide is for a first-time Claude Code user. Follow the steps in order.
## What this is
Claude Code is an assistant that works inside a folder on your computer. It reads and edits files when you ask it to. A project folder with a file named `CLAUDE.md` gives it the rules and background for that project, so you do not need to explain them each time.
## Prerequisites
- Node 20 or newer. Check with `node --version`.
- git. Check with `git --version`.
- A Claude account, or an API key from your provider.
If you use an API key, store it in your own shell profile, never in this project. Add a line like this to `~/.zshrc` or `~/.bashrc`:
```
export ANTHROPIC_API_KEY=YOUR_API_KEY
```
Replace `YOUR_API_KEY` with your real key. Do not copy the key into any file in this folder.
## Set up the project
1. Create a folder named `budget-recipes` and open a terminal inside it.
2. Copy these files into the folder, keeping the same paths:
- CLAUDE.md, SPEC.md, PLAN.md, REVIEW.md, INSTALL.md
- .claude/settings.json
- .claude/skills/verify/SKILL.md
- .claude/skills/new-recipe/SKILL.md
- .claude/agents/recipe-checker.md
3. Create the Astro site in the same folder:
```
npm create astro@latest .
```
Choose the empty or minimal template, and pick TypeScript strict.
4. Install the testing and schema tools:
```
npm install zod
npm install --save-dev vitest
```
5. Start version tracking:
```
git init
git add .
git commit -m "Initial project setup"
```
## Start Claude Code
1. Run `claude` inside the `budget-recipes` folder.
2. Accept the trust prompt. It asks whether you trust the files in this folder. Say yes only for folders you made or checked.
3. Inside Claude, run `/model` to confirm Sonnet 5.5 is selected.
4. Run `/effort` to confirm it is set to medium.
## Install the plugins
Run these in your terminal, outside Claude:
```
claude plugin marketplace add anthropics/skills
claude plugin install frontend-design@anthropic-agent-skills
claude plugin install code-review@claude-plugins-official
```
- `frontend-design` helps make the recipe layout clean on phones.
- `code-review` gives a second look at the cost and shopping list code before you merge.
## Your first session
1. Type this and press Enter:
```
Read CLAUDE.md and SPEC.md, then summarize what you understand.
```
You can type `/init` instead if you want Claude to draft a new CLAUDE.md. You do not need to do this, because this project already has one.
2. Check that the summary matches what you want to build.
3. Paste the kickoff prompt from the plan to start Phase 1. It begins with `/goal`.
## Useful commands
- `/clear` wipes the conversation so the next task starts fresh. Use it between phases.
- `/compact` shortens a long conversation and keeps the key points. Use it at a natural break in the middle of a phase.
- `/usage` shows how many tokens the session has used.
- `/verify` runs the project's checks: install, type check, tests, build, and recipe checks.
## Plan mode
Plan mode lets Claude read and think without changing files. Press Shift+Tab to switch to it. Use it before any change to the cost or shopping list code, so you can read the plan first.
## What /goal does
`/goal` gives Claude a finish line. You write a condition that can be checked, such as "npm test passes with at least 12 cost tests." Claude keeps working until a small, fast model judges that the condition is met. The judge only sees what Claude has shown in the chat, so the goal must say how to check the result. Each phase in PLAN.md has a goal with a turn limit, so it stops if it gets stuck.
To stop a goal early, run `/goal clear`.
## Keep costs low
- Use one session per phase. Run `/clear` before each phase.
- Check spending with `/usage` at the end of each phase.
- Stay on Sonnet 5.5 for daily work. Only switch to a stronger model for hard planning.
## When something goes wrong
- If the build fails, copy the first error line into Claude and ask it to run `/verify`.
- If Claude edits a file you did not want changed, look at what changed:
```
git diff path/to/file
```
To undo changes to that one file only, run:
```
git checkout -- path/to/file
```
Replace `path/to/file` with the real file name. Do not run commands that reset the whole folder.
## Glossary
- Repo: a project folder that git tracks.
- Commit: a saved snapshot of your files, with a short message.
- Dependency: a tool or library your project needs to run, such as Astro.
- Build: the step that turns your source files into the finished website.
- Markdown: a plain-text format for writing headings, lists, and links with simple symbols.
- Frontmatter: the block at the top of a Markdown file between two lines of three dashes, holding details like title and servings.
PLAN.md# Build plan
Five phases. Each ends with a `/goal` prompt. Read CLAUDE.md and SPEC.md before starting any phase.
## Every phase
1. Run `/clear`.
2. Read CLAUDE.md and SPEC.md.
3. Plan in plan mode (shift-tab). For Phases 2 and 4, add `ultrathink` to the planning prompt, because those phases touch cost.ts and shopping.ts.
4. Run the `/goal` prompt for the phase.
5. Run `/verify` before marking the phase done.
6. Commit with a message that starts with `Phase N:`.
Token habits:
- Run `/compact` at the midpoint of each phase.
- Run `/usage` at the end of each phase.
- Send searching and test runs to the `recipe-checker` subagent.
Rollback: before each phase, note the current commit hash in your own notes. If a goal fails twice, return to that commit with `git reset --hard <hash>` on a clean working tree, or use `git checkout <hash> -- <file>` for single files. Confirm with the user before any reset.
## Phase 1: scaffold
Goal:
- Astro project scaffolds and runs with `npm run dev`.
- `src/content.config.ts` defines a `recipes` collection with Zod fields: `slug`, `title`, `servings` (positive integer), `prepMinutes`, `cookMinutes`, `equipment` (string array), `ingredients` (name, quantity in metric base units, unit from g, ml, or each), `steps` (string array), and `budgetTip` (string).
- `src/pages/index.astro` renders a placeholder list from the collection with no errors.
- `npm run check`, `npm test`, and `npm run build` each exit 0.
- `/verify` reports PASS for each step.
Files to touch: `package.json`, `astro.config.mjs`, `tsconfig.json`, `src/content.config.ts`, `src/pages/index.astro`.
Rollback: the commit before Phase 1 started.
```
/goal Phase 1 of PLAN.md is complete. Done means: (1) the Astro project runs with npm run dev; (2) src/content.config.ts defines a recipes collection with Zod fields slug, title, servings (positive integer), prepMinutes, cookMinutes, equipment (string array), ingredients (name, quantity in metric base units, unit one of g, ml, each), steps (string array), budgetTip (string); (3) src/pages/index.astro renders a placeholder list from the collection with no errors; (4) npm run check, npm test, and npm run build each exit 0, and /verify reports PASS for each step. Check: paste the /verify PASS/FAIL list in the transcript. Constraints: touch only package.json, astro.config.mjs, tsconfig.json, src/content.config.ts, src/pages/index.astro, and files CLAUDE.md lists in the folder map. Add no prices, costs, or recipes. Add no dependencies beyond astro, zod, and vitest. Do not edit .env files. Ask before any change outside those files. Stop after 20 turns and report what is blocked.
```
## Phase 2: price table and cost engine
Goal:
- `src/data/prices.json` holds the price table with fields `name`, `unit`, `pricePerUnit`, `currency`, `store`, `lastChecked`. Mark any price you are unsure of with a TODO comment and tell the user.
- `src/lib/cost.ts` computes cost per portion: sum over ingredients of (quantity converted to price unit times price), divided by servings.
- Conversions live only in `cost.ts`.
- A missing price returns "cost unavailable", not zero.
- Vitest tests cover unit conversion, missing prices, and servings scaling.
- `npm test` passes with at least 12 cost tests.
Files to touch: `src/data/prices.json`, `src/lib/cost.ts`, `src/lib/cost.test.ts`.
Rollback: the commit before Phase 2 started.
```
/goal Phase 2 of PLAN.md is complete. Done means: (1) src/data/prices.json has entries with name, unit, pricePerUnit, currency, store, lastChecked; any unsure price has a TODO comment; (2) src/lib/cost.ts exports a cost-per-portion function that sums (quantity converted to price unit times price) and divides by servings, and returns "cost unavailable" when a price is missing; (3) src/lib/cost.test.ts has at least 12 tests covering unit conversion, missing prices, and servings scaling; (4) npm test passes. Check: run npm test and paste the summary line with the test count. Constraints: touch only src/data/prices.json, src/lib/cost.ts, and src/lib/cost.test.ts. Do not guess prices without a TODO. Do not put any price or cost in a recipe file. Conversions go only in cost.ts. Do not add dependencies. Ask before any other change. Stop after 20 turns and report what is blocked.
```
## Phase 3: recipe pages and home list
Goal:
- Three example recipes exist in `src/content/recipes/`, each with real computed costs from the price table.
- `src/pages/recipes/[slug].astro` shows cost per portion with "estimate", the prices-updated date, the servings adjuster, equipment, ingredients, steps, and the budget tip.
- `src/pages/index.astro` lists recipes with cost per portion, sortable by cost and by time.
- `npm run build` generates one page per recipe, and each page shows a cost per portion.
Files to touch: `src/content/recipes/*.md` (three files), `src/pages/recipes/[slug].astro`, `src/pages/index.astro`, `src/components/` if needed.
Rollback: the commit before Phase 3 started.
```
/goal Phase 3 of PLAN.md is complete. Done means: (1) three recipe files exist in src/content/recipes/, each with required frontmatter, quantities in metric base units, steps, and a budgetTip, and no prices or costs; (2) src/pages/recipes/[slug].astro shows cost per portion with two decimals and the word "estimate", a note that prices are estimates, the prices-updated date, a servings adjuster that rescales quantities and cost, equipment, ingredients, steps, and the budget tip; (3) src/pages/index.astro lists recipes with cost per portion and can sort by cost and by time; (4) npm run build generates one page per recipe and each page shows a cost per portion. Check: list the built recipe pages in dist and paste the /verify PASS/FAIL list. Constraints: touch only the files named above and src/components/ if needed. Do not add prices to recipe files; costs come from src/lib/cost.ts. Do not add dependencies. Ask before any other change. Stop after 20 turns and report what is blocked.
```
## Phase 4: shopping list engine and print page
Goal:
- `src/lib/shopping.ts` merges duplicate ingredients when name and base unit match, keeps them separate when units differ, and groups items by store section.
- Tests cover merging and grouping.
- `src/pages/shopping-list.astro` reads checkboxes and `?recipes=slug-one,slug-two`.
- `src/styles/print.css` hides nav, footer, and buttons, and prints on A4 and US Letter.
- Add three recipes so the print check uses six. Their costs come from the price table.
- A six-recipe list prints to at most two A4 pages, checked headless or manually and documented.
Files to touch: `src/lib/shopping.ts`, `src/lib/shopping.test.ts`, `src/pages/shopping-list.astro`, `src/styles/print.css`, `src/content/recipes/*.md` (three new files), `docs/print-check.md`.
Rollback: the commit before Phase 4 started.
```
/goal Phase 4 of PLAN.md is complete. Done means: (1) src/lib/shopping.ts merges duplicate ingredients when name and base unit match, keeps them separate when units differ, and groups by store section; (2) src/lib/shopping.test.ts has tests for merging and grouping and npm test passes; (3) src/pages/shopping-list.astro reads checkbox selections and ?recipes=slug-one,slug-two; (4) src/styles/print.css hides nav, footer, and buttons; (5) six recipes exist in src/content/recipes/; (6) a six-recipe list prints to at most two A4 pages, documented in docs/print-check.md with the page count and method. Check: paste the npm test summary and the page count. Constraints: touch only the files listed in PLAN.md Phase 4. Costs come from cost.ts and prices.json only. Do not add dependencies; ask before adding a print or PDF library. Ask before any other change. Stop after 20 turns and report what is blocked.
```
## Phase 5: finish, accessibility, and deploy notes
Goal:
- Twelve recipes total, each with a computed cost and a budget tip.
- Accessibility pass: image alt text, heading order, color contrast, keyboard-usable servings control.
- Lighthouse accessibility score is 90 or higher on the home page and a recipe page. Record the scores.
- `README.md` has deploy notes.
- `npm run check` is clean.
Files to touch: `src/content/recipes/*.md` (nine new files), `src/pages/*.astro`, `src/components/*`, `src/styles/*`, `README.md`.
Rollback: the commit before Phase 5 started.
```
/goal Phase 5 of PLAN.md is complete. Done means: (1) twelve recipe files exist in src/content/recipes/, each with a computed cost per portion and a budgetTip; (2) images have alt text, headings go in order, color contrast passes, and the servings control works from the keyboard; (3) Lighthouse accessibility score is 90 or higher on the home page and on one recipe page, with both scores pasted in the transcript; (4) README.md has deploy notes covering build output and hosting steps; (5) npm run check, npm test, and npm run build each exit 0. Check: paste the /verify PASS/FAIL list and the two Lighthouse scores. Constraints: touch only the files listed in PLAN.md Phase 5. Do not add prices to recipe files. Do not add tracking scripts or dependencies without asking. Do not edit .env files. Ask before any other change. Stop after 20 turns and report what is blocked.
```
REVIEW.md# Review checklist
Check these before saying work is ready. Run the commands in the last section first, since they catch the most problems.
## Cost math
- At least one recipe has a hand calculation that matches its computed cost per portion.
- Changing servings scales the total ingredient cost in a straight line. Cost per portion must still match the hand calculation at the new servings.
- The formula is: sum of (quantity in price unit times price per unit) divided by servings.
- No cost is hardcoded in a recipe file.
## Units
- Every quantity in a recipe file has a metric base unit: g, ml, or each.
- Conversions live only in `src/lib/cost.ts`. Look for conversion words like "cup", "tbsp", or "oz" in recipe files and reject them.
## Prices
- No price appears in any recipe file.
- Every entry in `src/data/prices.json` has `lastChecked` and `store`.
- Any guessed or unsure price has a TODO comment, and the author has told the user.
## Shopping list
- Duplicate ingredients merge when name and base unit match.
- Ingredients with different units stay on separate lines.
- Print CSS in `src/styles/print.css` hides the nav, footer, and buttons.
- A 6-recipe list prints to at most two A4 pages.
## Accessibility
- Images have alt text.
- Headings go in order, with one `h1` per page.
- Text and background colors pass contrast checks.
- The servings control works with the keyboard alone.
## Student-facing copy
- Plain English, no jargon.
- Every recipe has a budget tip line.
- The recipe page shows "estimate" next to the cost and the date prices were last updated.
## Security
- No secrets, API keys, or personal paths in any file.
- No tracking scripts or third-party embeds added without asking.
- No affiliate links.
## Commands
- `npm run build` passes.
- `npm test` passes.
- `npm run check` passes with no type errors.
- The verify skill reports PASS for every step.
## Second pass
- Run the code-review plugin on `src/lib/cost.ts` and `src/lib/shopping.ts` before merging.
SPEC.md# Budget recipes: product spec (version 1)
## Who it is for
Students on a tight budget. They cook in small kitchens with one pan, and the fridge may be shared. Copy should be plain English, with no jargon.
## What the product does
A recipe blog where every recipe shows what one portion costs, based on a price table. A shopping list page combines chosen recipes into one printable list.
## Core pages
- **Home:** recipe list showing cost per portion and prep plus cook time. Sortable by cost and by time.
- **Recipe page:** one page per recipe, with the content below.
- **Shopping list page:** merges ingredients from selected recipes.
- **About page:** what the site is, how costs are estimated, and who runs it.
## Recipe page
- Title.
- Cost per portion in the chosen currency, with the date the prices were last updated.
- Servings adjuster. Changing it rescales ingredient quantities and cost together.
- Equipment list.
- Ingredients with quantities.
- Steps.
- Budget tip.
## Cost display rules
- Cost per portion shows two decimals and the word "estimate" next to it.
- A visible note explains that prices are estimates from the price table.
- If a recipe has a missing price, it shows "cost unavailable". It never shows zero.
## Price table
File: `src/data/prices.json`.
Each entry has these fields:
- `name`
- `unit` (base unit: g, ml, or each)
- `pricePerUnit`
- `currency`
- `store`
- `lastChecked` (date)
Prices live only in this file. Recipe files never contain prices or costs.
## Shopping list
- Students pick recipes with checkboxes, or pass `?recipes=slug-one,slug-two` in the URL.
- Duplicate ingredients merge by name and base unit. Different units stay on separate lines.
- Items group by store section.
- Prints cleanly on A4 and US Letter. Navigation, footer, buttons, and ads are hidden in print.
## Scope for version 1
- 12 to 15 recipes.
- Every recipe costs under 1.50 per portion at the default store.
- Mobile-first layout.
- Dark mode is optional.
## Out of scope for version 1
- User accounts.
- Comments.
- Live price scraping.
- Meal planners.
- Newsletter.
- Payments.
- Affiliate links.
- Tracking scripts.
## Acceptance criteria
1. `npm run build` passes.
2. `npm test` passes.
3. Every recipe has a computed cost per portion.
4. A shopping list for 6 recipes prints on one or two pages.
5. Lighthouse accessibility score is 90 or higher on the home page and a recipe page.
## Open questions
- Which store is the default? Decide before Phase 2 and record it in `prices.json`.
- Which currency is shown? Decide before Phase 2.