/// /** * TEST PATTERN: Dumb Map Approach for generateObject * * CLAIM: The "dumb map approach" works for generateObject - just use .map() directly * SOURCE: folk_wisdom/llm.md - "The Dumb Map Approach Works for ALL Reactive Primitives" * * WHAT THIS TESTS: * - That calling generateObject inside a .map() on a list of items works correctly * - That each item's generateObject is cached independently (via hash in llm.ts:945-963) * - That the framework handles caching via refer(params).toString() hash * * FRAMEWORK EVIDENCE: * - packages/runner/src/builtins/llm.ts:945 - Hash creation: refer(generateObjectParams).toString() * - packages/runner/src/builtins/llm.ts:950-957 - Early return if hash matches cached requestHash * - Each item has unique content → unique prompt → unique hash → independent cache entry * * EXPECTED BEHAVIOR (if claim is TRUE): * - All items process independently with their own generateObject calls * - No need for complex caching layers or trigger patterns * - Adding/editing items only triggers LLM for changed items (new hash) * - Network tab shows requests = new/changed items only * * MANUAL VERIFICATION STEPS: * 1. Deploy pattern: deno task ct charm new test-llm-dumb-map-generateobject.tsx * 2. Add 3-5 items with different content (e.g., "I love this!", "This is terrible", "It's okay") * 3. Open browser DevTools → Network tab → filter for "anthropic" or "generate" * 4. Wait for all items to complete (all show sentiment results) * 5. Add ONE new item → verify only 1 new network request (not 4-6) * 6. Remove and re-add an item with slightly different text → verify only 1 request * 7. Check console for any "Tried to directly access opaque value" errors (should be none) */ import { Cell, Default, derive, generateObject, handler, NAME, pattern, UI, } from "commontools"; interface Item { id: string; content: string; } interface Sentiment { sentiment: "positive" | "neutral" | "negative"; confidence: number; keywords: string[]; } interface Input { items: Default; } const addItem = handler< { detail: { message: string } }, { items: Cell } >( ({ detail }, { items }) => { const content = detail?.message?.trim(); if (!content) return; items.push({ id: `item-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, content, }); }, ); const removeItem = handler; itemId: string }>( (_event, { items, itemId }) => { const current = items.get(); items.set(current.filter((item) => item.id !== itemId)); }, ); export default pattern(({ items }) => { // THE "DUMB MAP APPROACH" - just map directly over items // Framework caches each call via hash(prompt + schema + model + system) const sentimentAnalyses = items.map((item) => ({ itemId: item.id, content: item.content, analysis: generateObject({ system: "Analyze the sentiment of the following text. Return positive, neutral, or negative sentiment with confidence 0-1 and relevant keywords.", prompt: item.content, model: "anthropic:claude-sonnet-4-5", }), })); const pendingCount = derive( sentimentAnalyses.map((s) => s.analysis.pending), (pendingStates) => pendingStates.filter((p) => p).length, ); const completedCount = derive( sentimentAnalyses.map((s) => s.analysis.result), (results) => results.filter((r) => r !== undefined).length, ); return { [NAME]: "Test: Dumb Map with generateObject", [UI]: (

Dumb Map Approach: generateObject Test

Testing that .map() + generateObject works without custom caching. Each item gets independent caching via prompt content hashing.

Status: {completedCount}/{items.length} completed, {" "} {pendingCount} pending
{sentimentAnalyses.map((item) => (
Text: {item.content}
{derive( [ item.analysis.pending, item.analysis.result, item.analysis.error, ], ([pending, result, error]) => { if (pending) { return (
Analyzing sentiment...
); } if (error) { return (
Error: {String(error)}
); } const sentimentResult = result as Sentiment | undefined; if (sentimentResult) { return (
Sentiment:{" "} {sentimentResult.sentiment.toUpperCase()} {" "} ({Math.round(sentimentResult.confidence * 100)}% confidence)
Keywords:{" "} {sentimentResult.keywords.join(", ")}
); } return null; }, )}
Remove
))}

Manual Testing Instructions:

  1. Add 3-5 items with varying sentiments
  2. Open DevTools: Network tab → Filter for "anthropic"
  3. Wait for all items to complete
  4. Add ONE new item → Verify ONLY 1 new network request
  5. If you see 4+ requests, caching is NOT working
), items, sentimentAnalyses, }; });