cardamom
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Viewing readonly version of main branch: v287View latest version
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Recipe Parser & Shopping List App - A Val Town application that captures, parses, stores recipes from multiple sources and generates smart shopping lists.
- Runtime: Val Town (Deno runtime)
- Frontend: React 18.2.0 with TypeScript, TailwindCSS
- Backend: Hono API framework with TypeScript
- Database: SQLite with normalized schema
- AI: OpenAI GPT-4o-mini for recipe parsing
├── backend/
│ ├── database/
│ │ ├── migrations.ts # Database schema (4 tables)
│ │ └── queries.ts # Database operations
│ ├── routes/
│ │ ├── recipes.ts # Recipe CRUD operations
│ │ ├── parse.ts # Recipe parsing endpoints
│ │ └── shopping-lists.ts # Shopping list operations
│ └── index.ts # Main API entry point
├── frontend/
│ ├── components/
│ │ ├── App.tsx # Main app component
│ │ ├── RecipeForm.tsx # Recipe input form
│ │ ├── RecipeList.tsx # Recipe listing
│ │ ├── RecipeView.tsx # Individual recipe display
│ │ ├── ShoppingListCreator.tsx # Create shopping lists
│ │ ├── ShoppingListList.tsx # Shopping list overview
│ │ └── ShoppingListView.tsx # Interactive shopping list
│ ├── index.html # Main HTML template
│ └── index.tsx # Frontend entry point
└── shared/
├── types.ts # Shared TypeScript types
└── utils.ts # Shared utility functions
Since this is a Val Town project, there are no traditional build/test commands. Development happens directly in the Val Town environment:
- Running locally: Not applicable - code runs in Val Town's cloud environment
- Testing: Use the built-in test endpoints:
GET /api/health
- Health checkGET /api/test-delete
- Test database operationsGET /api/test-transaction
- Test transaction rollback behaviorGET /api/test-ingredients
- Test optimized ingredient updates
- Database migrations: Run automatically on startup via
migrations.ts
- Debugging: Check Val Town logs for server-side issues, browser console for client-side
GET /api/recipes
- Get all recipes (supports filtering)POST /api/recipes
- Save a new recipeGET /api/recipes/:id
- Get specific recipePUT /api/recipes/:id
- Update recipeDELETE /api/recipes/:id
- Delete recipe
POST /api/parse/url
- Parse from URLPOST /api/parse/pdf
- Parse from PDF (base64)POST /api/parse/image
- Parse from image (base64)POST /api/parse/text
- Parse from text
GET /api/shopping-lists
- Get all listsPOST /api/shopping-lists
- Create from recipe IDsGET /api/shopping-lists/:id
- Get specific listPUT /api/shopping-lists/:id
- Update list nameDELETE /api/shopping-lists/:id
- Delete listPUT /api/shopping-lists/items/:id
- Toggle item checked
- Uses OpenAI GPT-4o-mini for intelligent extraction
- Handles fractional Unicode characters (¼, ½, ¾)
- Robust JSON extraction with fallback parsing
- Automatically consolidates duplicate ingredients across recipes
- Maintains links between shopping list items and source recipes
- Real-time progress tracking with visual indicators
- Uses Val Town's
sqlite.batch()
method for atomic operations - All multi-table operations (recipe creation, updates, shopping list creation) are transactional
- Automatic rollback on any failure ensures data integrity
- N+1 query problem solved with batch fetching for
getAllRecipes()
andgetAllShoppingLists()
- Optimized ingredient updates using bulk INSERT instead of individual queries
- Database initialization re-enabled with proper
CREATE TABLE IF NOT EXISTS
patterns
- Provide complete, functional solutions rather than skeleton implementations
- Ensure all code follows Val Town's specific platform requirements
- If a section of code is getting too complex, refactor it into subcomponents
- Generate code in TypeScript or TSX
- Add appropriate TypeScript types and interfaces for all data structures
- Never bake in secrets into the code - always use environment variables
- Follow modern ES6+ conventions and functional programming practices
import { sqlite } from "https://esm.town/v/stevekrouse/sqlite";
const TABLE_NAME = 'recipes_v1';
// Change table name when modifying schema (e.g., _v2, _v3)
await sqlite.execute(`CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)`);
import { OpenAI } from "https://esm.town/v/std/openai";
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
messages: [{ role: "user", content: "Parse this recipe" }],
model: "gpt-4o-mini",
max_tokens: 1000,
});
import { blob } from "https://esm.town/v/std/blob";
await blob.setJSON("myKey", { hello: "world" });
let blobDemo = await blob.getJSON("myKey");
All imports use ESM.sh for browser/server compatibility:
// Use Val Town utility functions, NOT Hono's serveStatic
app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url));
app.get("/shared/*", c => serveFile(c.req.path, import.meta.url));
- Pin all React dependencies to version 18.2.0
- Include at the top of React files:
/** @jsxImportSource https://esm.sh/react@18.2.0 */
// Unwrap Hono errors to see original error details
app.onError((err) => Promise.reject(err));
// In Hono
c.redirect('/new-path');
// Outside Hono
return new Response(null, { status: 302, headers: { Location: "/new-path" }});
- NEVER use
import { serveStatic } from 'hono/middleware'
- NEVER use
import { cors } from "@hono/cors"
- Val Town handles CORS automatically - DO NOT use the Deno KV module for storage
- DO NOT use
alert()
,prompt()
, orconfirm()
in browser code
- When changing schemas, create new tables (e.g.,
recipes_v1
→recipes_v2
) - Run migrations on startup or comment out for performance
- Always run table creation before querying
- Environment: Val Town runs on Deno, not Node.js
- Shared code: Cannot use
Deno
keyword inshared/
directory - SQLite: Limited ALTER TABLE support - create new tables instead
- React: All dependencies must be pinned to 18.2.0
- Files: Val Town only supports text files, not binary
- OpenAI API key is automatically provided by Val Town's integration
- Use
Deno.env.get('keyname')
for custom environment variables - Prefer APIs that don't require keys when possible
Tests database transaction rollback behavior by:
- Creating a transaction with an intentional failure (invalid table)
- Verifying that successful operations are properly rolled back
- Checking for orphaned data to ensure transaction integrity
Expected Success Response:
{ "success": true, "message": "Transaction rollback working correctly - no orphaned data found" }
Tests optimized ingredient update performance by:
- Creating a test recipe with initial ingredients
- Updating with different ingredients using bulk INSERT pattern
- Verifying correct ingredient count and data integrity
- Cleaning up test data automatically
Expected Success Response:
{ "success": true, "message": "Optimized ingredient updates working correctly - bulk INSERT with 3 ingredients" }
// Convert queries to Val Town batch format
const batchQueries = queries.map(query => ({
sql: query.sql,
args: query.params // Note: 'args' not 'params'
}));
// Execute as atomic transaction
const results = await sqlite.batch(batchQueries);
- Before: N+1 queries for ingredient updates (1 DELETE + N INSERTs)
- After: 2 queries total (1 DELETE + 1 bulk INSERT)
- Improvement: ~80% reduction in database operations for typical recipes