• Townie
    AI
  • Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
connnolly

connnolly

cardamom

Unlisted
Like
2
cardamom
Home
Code
13
.claude
1
.cursor
1
backend
4
frontend
3
shared
2
.vtignore
CLAUDE.md
README.md
claude-fixes.md
deno.json
meal-planning-implementation-plan.md
test-normalization-cache.ts
todo.md
Branches
2
Pull requests
Remixes
History
Environment variables
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.
Sign up now
Code
/
CLAUDE.md
Code
/
CLAUDE.md
Search
9/18/2025
Viewing readonly version of main branch: v397
View latest version
CLAUDE.md

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Recipe Parser & Shopping List App - A Val Town application that captures, parses, stores recipes from multiple sources and generates smart shopping lists.

Tech Stack

  • 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

Architecture

├── 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

Development Commands

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 check
    • GET /api/test-delete - Test database operations
    • GET /api/test-transaction - Test transaction rollback behavior
    • GET /api/test-ingredients - Test optimized ingredient updates
    • GET /api/test-pagination - Test pagination functionality
  • Database migrations: Run automatically on startup via migrations.ts
  • Debugging: Check Val Town logs for server-side issues, browser console for client-side

API Endpoints

Recipe Management

  • GET /api/recipes - Get all recipes (supports filtering and pagination)
    • Filtering: ?search=pasta&difficulty=easy&maxPrepTime=30
    • Pagination: ?paginated=true&page=1&limit=20
    • Combined: ?search=pasta&page=2&limit=10
  • POST /api/recipes - Save a new recipe
  • GET /api/recipes/:id - Get specific recipe
  • PUT /api/recipes/:id - Update recipe
  • DELETE /api/recipes/:id - Delete recipe

Recipe Parsing

  • POST /api/parse/url - Parse from URL
  • POST /api/parse/pdf - Parse from PDF (base64)
  • POST /api/parse/image - Parse from image (base64)
  • POST /api/parse/text - Parse from text

Shopping Lists

  • GET /api/shopping-lists - Get all lists (supports pagination)
    • Pagination: ?paginated=true&page=1&limit=25
  • POST /api/shopping-lists - Create from recipe IDs
  • GET /api/shopping-lists/:id - Get specific list
  • PUT /api/shopping-lists/:id - Update list name
  • DELETE /api/shopping-lists/:id - Delete list
  • POST /api/shopping-lists/:id/items - Add item manually to shopping list
  • PUT /api/shopping-lists/items/:id - Toggle item checked
  • DELETE /api/shopping-lists/items/:id - Remove item from list

Key Implementation Details

Recipe Parsing

  • Uses OpenAI GPT-4o-mini for intelligent extraction
  • Handles fractional Unicode characters (¼, ½, ¾)
  • Robust JSON extraction with fallback parsing

Shopping List Generation

  • Automatically consolidates duplicate ingredients across recipes
  • Maintains links between shopping list items and source recipes
  • Real-time progress tracking with visual indicators
  • Manual Item Addition: Users can add custom items directly to shopping lists
    • Items are automatically categorized (produce, dairy, etc.)
    • Manual items have empty recipe associations (recipeIds: [])
    • Clean UI with form validation and proper error handling

Database Transactions

  • 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

Performance Optimizations

  • N+1 query problem solved with batch fetching for getAllRecipes() and getAllShoppingLists()
  • Optimized ingredient updates using bulk INSERT instead of individual queries
  • Database initialization re-enabled with proper CREATE TABLE IF NOT EXISTS patterns
  • Pagination implemented to prevent memory exhaustion with large datasets

Pagination System

  • Default: 50 items per page, maximum 100 per page
  • Backward Compatible: Original non-paginated endpoints still work
  • Smart Parameters: Auto-detects pagination intent from query parameters
  • Efficient Queries: Uses LIMIT/OFFSET with separate count queries for optimal performance

Val Town Development Guidelines

Core Guidelines

  • 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

Code Standards

  • 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

Val Town Standard Libraries

SQLite

Create val
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 )`);

OpenAI

Create val
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, });

Blob Storage

Create val
import { blob } from "https://esm.town/v/std/blob"; await blob.setJSON("myKey", { hello: "world" }); let blobDemo = await blob.getJSON("myKey");

Val Town Platform Specifics

Import Style

All imports use ESM.sh for browser/server compatibility:

Create val
import { readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts";

Static File Serving

Create val
// 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));

React Configuration

  • Pin all React dependencies to version 18.2.0
  • Include at the top of React files:
Create val
/** @jsxImportSource https://esm.sh/react@18.2.0 */

Error Handling

Create val
// Unwrap Hono errors to see original error details app.onError((err) => Promise.reject(err));

Redirects

Create val
// In Hono c.redirect('/new-path'); // Outside Hono return new Response(null, { status: 302, headers: { Location: "/new-path" }});

Important Don'ts

  • 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(), or confirm() in browser code

Database Patterns

  • 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

Common Gotchas

  1. Environment: Val Town runs on Deno, not Node.js
  2. Shared code: Cannot use Deno keyword in shared/ directory
  3. SQLite: Limited ALTER TABLE support - create new tables instead
  4. React: All dependencies must be pinned to 18.2.0
  5. Files: Val Town only supports text files, not binary

Environment Variables

  • 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

Testing Infrastructure

Test Endpoints

GET /api/test-transaction

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" }

GET /api/test-ingredients

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" }

GET /api/test-pagination

Tests pagination functionality by:

  • Creating multiple test recipes
  • Testing page limits and offsets work correctly
  • Verifying no data overlap between pages
  • Testing shopping list pagination
  • Cleaning up test data automatically

Expected Success Response:

{ "success": true, "message": "Pagination working correctly - tested 5 recipes with page limits" }

Transaction Patterns

Using Val Town's Batch Method

Create val
// 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);

Performance Optimizations

  • 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

Pagination Implementation

Create val
// Paginated query with count const { page, limit, offset } = validatePaginationParams(pagination); // Get total count and paginated data in parallel const [countResult, dataResult] = await Promise.all([ sqlite.execute('SELECT COUNT(*) as total FROM recipes_v1', params), sqlite.execute('SELECT * FROM recipes_v1 ORDER BY created_at DESC LIMIT ? OFFSET ?', [...params, limit, offset]) ]); // Return with pagination metadata return { recipes: processedRecipes, paginationInfo: createPaginationInfo(page, limit, total) };

Response Format:

{ "success": true, "data": [...], "pagination": { "page": 1, "limit": 50, "total": 150, "totalPages": 3, "hasNext": true, "hasPrev": false } }

API Usage:

  • GET /api/recipes?paginated=true&page=2&limit=10
  • GET /api/recipes?search=pasta&page=1&limit=20
  • GET /api/shopping-lists?page=1&limit=25

Performance Optimizations

Ingredient Normalization Cache

The shopping list creation process includes intelligent ingredient consolidation that normalizes ingredient names (e.g., "kosher salt" → "salt", "fresh basil leaves" → "basil"). To optimize performance, an in-memory cache has been implemented:

Implementation:

// In-memory cache for ingredient name normalization (performance optimization) const normalizationCache = new Map<string, string>(); function normalizeIngredientName(name: string): string { // Check cache first for O(1) lookup if (normalizationCache.has(name)) { return normalizationCache.get(name)!; } // Perform normalization logic (20+ regex operations) const result = performNormalization(name); // Cache the result for future lookups (reduces O(n×m) to O(1) for repeat ingredients) normalizationCache.set(name, result); return result; }

Performance Impact:

  • 76.7% faster for ingredient processing with duplicate ingredients
  • 21.4% cache hit rate in typical usage scenarios
  • Reduces normalization operations from O(n×m) to O(1) for cached ingredients
  • Memory overhead: ~50 bytes per cached ingredient name

Test Endpoint: GET /api/test-normalization-cache

FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
ExploreDocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.