diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml
index 76bfdef1a..4f7545bac 100644
--- a/.github/workflows/deno.yml
+++ b/.github/workflows/deno.yml
@@ -367,6 +367,54 @@ jobs:
FRONTEND_URL=http://localhost:8000/ \
deno task integration
+ seeder-integration-test:
+ name: "Seeder Integration Tests"
+ runs-on: ubuntu-latest
+ needs: ["build-binaries"]
+ environment: ci
+ services:
+ redis:
+ image: redis
+ ports:
+ - 6379:6379
+ options: >-
+ --health-cmd "redis-cli ping"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+ steps:
+ - name: ๐ฅ Checkout repository
+ uses: actions/checkout@v4
+
+ - name: ๐ฆ Setup Deno 2.2.2
+ uses: denoland/setup-deno@v2
+ with:
+ deno-version: "2.2.2"
+
+ - name: ๐ฆ Cache Deno dependencies
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.deno
+ ~/.cache/deno
+ key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.json') }}
+
+ - name: ๐ฅ Download built binaries
+ uses: actions/download-artifact@v4
+
+ - name: ๐ Start Toolshed server for testing
+ run: |
+ chmod +x ./common-binaries/toolshed
+ CTTS_AI_LLM_ANTHROPIC_API_KEY=fake \
+ CACHE_DIR=${GITHUB_WORKSPACE}/jumble/integration/cache \
+ ./common-binaries/toolshed &
+
+ - name: ๐งช Run seeder integration tests
+ working-directory: seeder
+ run: |
+ TOOLSHED_API_URL=http://localhost:8000/ \
+ deno task start --tag smol --no-verify --no-report --name ${{ github.run_id }}
+
# Automatic deployment to staging (toolshed)
deploy-toolshed:
name: "Deploy to Toolshed (Staging)"
diff --git a/.github/workflows/evals.yml b/.github/workflows/evals.yml
index 5ad0791d2..fcd97a511 100644
--- a/.github/workflows/evals.yml
+++ b/.github/workflows/evals.yml
@@ -106,7 +106,7 @@ jobs:
key: llm-cache-${{ github.run_id }}
restore-keys: llm-cache
- - name: โ๏ธ List LLM Cache Load
+ - name: โ๏ธ List LLM Cache Load
run: |
mkdir -p ./llm_cache_dir/llm-api-cache
ls -1 ./llm_cache_dir/llm-api-cache > ./cache-log-pre
@@ -138,17 +138,24 @@ jobs:
FLAGS="--tag 10x10"
fi
- TOOLSHED_API_URL=http://localhost:8000/ \
- OPENAI_API_KEY=${{ secrets.CTTS_AI_LLM_OPENAI_API_KEY }} \
- deno task start --name ${{ env.target_sha }} $FLAGS
+ # Wrap in an if/else, we don't want the job to fail yet
+ # until we can confidently expect passing results.
+ if
+ TOOLSHED_API_URL=http://localhost:8000/ \
+ OPENAI_API_KEY=${{ secrets.CTTS_AI_LLM_OPENAI_API_KEY }} \
+ deno task start --name ${{ env.target_sha }} $FLAGS ; then
+ echo "All charms have been successfully verified!"
+ else
+ echo "Some charms have failed verification."
+ fi
- - name: โ๏ธ List LLM Cache Save
+ - name: โ๏ธ List LLM Cache Save
run: |
mkdir -p ./llm_cache_dir/llm-api-cache
ls -1 ./llm_cache_dir/llm-api-cache > ./cache-log-post
echo "$(cat ./cache-log-post | wc -l) items found in cache."
cat ./cache-log-post
-
+
- name: ๐ฆ LLM Cache Status
run: |
if diff ./cache-log-pre ./cache-log-post > ./cache-analysis-diff ; then
@@ -158,7 +165,7 @@ jobs:
ADDITIONS=$(cat ./cache-analysis-diff | wc -l)
echo "LLM cache has added $((ADDITIONS / 2)) entries."
fi
-
+
- name: ๐ค Upload reports
uses: actions/upload-artifact@v4
with:
diff --git a/deno.jsonc b/deno.jsonc
index a690f6578..a4d79c8f4 100644
--- a/deno.jsonc
+++ b/deno.jsonc
@@ -112,4 +112,4 @@
"zod-to-json-schema": "npm:zod-to-json-schema@^3.24.1",
"zod": "npm:zod@^3.24.1"
}
-}
+}
\ No newline at end of file
diff --git a/jumble/integration/cache/llm-api-cache/3d0fbce7fd401830cddcbafc815811755891a538c1c3ad8401399a51f353d7c8.json b/jumble/integration/cache/llm-api-cache/3d0fbce7fd401830cddcbafc815811755891a538c1c3ad8401399a51f353d7c8.json
deleted file mode 100644
index 3dabb45e8..000000000
--- a/jumble/integration/cache/llm-api-cache/3d0fbce7fd401830cddcbafc815811755891a538c1c3ad8401399a51f353d7c8.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "model": "anthropic:claude-3-7-sonnet-latest",
- "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n\n**This task is part of a 2-Phase Process.**\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n\n\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write \n\n\n- React and ReactDOM are pre-imported - don't import them again\n- All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n- Follow React hooks rules - never nest or conditionally call hooks\n- For form handling, use `onClick` handlers instead of `onSubmit`\n\n\n\n- **useReactiveCell(keyPath: string[])** - Persistent data storage with reactive updates\n- **generateText({ system, messages })** - Generate text via Large Language Model\n- **generateObject({ system, messages })** - Generate JSON object via Large Language Model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n \n ## Important Note About useReactiveCell(keyPath: string[])\n - **useReactiveCell is a React Hook** and must follow all React hook rules\n - It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n - Only call useReactiveCell at the top level of your React function components or custom hooks\n - Do not call useReactiveCell inside loops, conditions, or nested functions\n - Do not call useReactiveCell directly inside the onReady function - it can only be used inside React components\n - The onReady function itself is not a React component and cannot use React hooks directly\n - Example of correct usage:\n ```jsx\n function onReady(mount, sourceData, modules) {\n // This is NOT a React component, so do NOT use hooks here!\n \n // Create a React component to use hooks properly:\n function MyApp() {\n // React hooks can be used here\n const [data, setData] = useReactiveCell('myData', defaultValue);\n return
{/* your JSX */}
;\n }\n \n // Render the React component to the mount point\n const root = ReactDOM.createRoot(mount);\n root.render();\n }\n ```\n \n\n\n\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n\n\n\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"simplevalue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"displayData\": {\n \"type\": \"object\",\n \"title\": \"Display Data\",\n \"description\": \"The data being displayed from simpleValue2\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"title\": \"Values\",\n \"description\": \"The string values from simpleValue2\",\n \"items\": {\n \"type\": \"string\"\n },\n \"default\": []\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Last Updated\",\n \"description\": \"When the display was last refreshed\"\n }\n },\n \"required\": [\n \"values\"\n ]\n },\n \"displaySettings\": {\n \"type\": \"object\",\n \"title\": \"Display Settings\",\n \"description\": \"Configuration for how the data is displayed\",\n \"properties\": {\n \"showTimestamp\": {\n \"type\": \"boolean\",\n \"title\": \"Show Timestamp\",\n \"description\": \"Whether to show when the data was last updated\",\n \"default\": true\n },\n \"refreshInterval\": {\n \"type\": \"integer\",\n \"title\": \"Refresh Interval\",\n \"description\": \"How often to refresh the display (in seconds)\",\n \"default\": 30\n }\n }\n }\n },\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"A component to display the current values stored in the simpleValue2 charm.\"\n}\n\n\n\n# SDK Usage Guide\n\n\n## 1. `useReactiveCell` Hook\n\nThe `useReactiveCell` hook binds to a reactive cell given a key path and returns a tuple `[doc, setDoc]`:\n\nAny keys from the view-model-schema are valid for useReactiveCell, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useReactiveCell called at top level of component\n const [counter, setCounter] = useReactiveCell(\"counter\");\n const [maxValue, setMaxValue] = useReactiveCell(['settings', 'maxValue']);\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n\n\nSeveral APIs exist for generating text, JSON or image urls.\n\n\n`generateText({ system, messages}): Promise`\n\n```jsx\nasync function fetchLLMResponse() {\n const result = await generateText({\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n })\n console.log('LLM responded:', result);\n}\n```\n\n\n\n\n`window.generateObject({ system, messages }): Promise\n\n\n\n\nSynchronous, generates a URL that will load the image.\n\n```jsx\nfunction ImageComponent() {\n return ;\n}\n```\n\n\n\n\n\n`readWebpage(url: string): Promise` Returns markdown format.\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n\n\nAll generated code must follow this pattern:\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return ;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n
{label}
\n
{description}
\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n );\n}\n\n// Main application code with modules passed as third parameter\nfunction onReady(mount, sourceData, libs) {\n const { useState, useEffect } = React; // React is available globally\n const { useSpring, animated } = libs['@react-spring/web']; // Access imported module\n\n function MyApp() {\n const [count, setCount] = useReactiveCell('count');\n const [todos, setTodos] = useReactiveCell('todos');\n\n const props = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 }\n });\n\n return (\n
\n \n \n \n \n
\n );\n }\n\n // Use the client API for React 18\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```\n\n",
- "messages": [
- {
- "role": "user",
- "content": "The user asked you to create the source code with the following specification:\n```\n\nshow the data from `simpleValue2`\n\nShow the data from the `simpleValue2` charm, displaying its current value and any associated metadata.\n\n1. Access the simpleValue2 data from the existing schema\n2. Create a display component that renders the values array as a list\n3. Implement auto-refresh functionality to keep the display in sync with data changes\nThe SimpleValue2 Viewer will display all string values currently stored in the simpleValue2.values array. It will present the values in a clean, readable list format. The viewer will automatically update when the underlying data changes.\n```"
- },
- {
- "role": "assistant",
- "content": "\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nconst title = 'SimpleValue2 Viewer';\n\nfunction ValuesList({ values }) {\n return (\n
\n );\n}\n\nfunction SimpleValue2Viewer() {\n // Access the data from simpleValue2\n const [simpleValue2Data, setSimpleValue2Data] = useReactiveCell(['simplevalue2', 'values'], []);\n \n // Access display data\n const [displayData, setDisplayData] = useReactiveCell(['displayData', 'values'], []);\n const [lastUpdated, setLastUpdated] = useReactiveCell(['displayData', 'lastUpdated'], '');\n \n // Access display settings\n const [showTimestamp, setShowTimestamp] = useReactiveCell(['displaySettings', 'showTimestamp'], true);\n const [refreshInterval, setRefreshInterval] = useReactiveCell(['displaySettings', 'refreshInterval'], 30);\n \n // Local state for UI\n const [isRefreshing, setIsRefreshing] = React.useState(false);\n \n // Function to update the display data\n const refreshData = React.useCallback(() => {\n setIsRefreshing(true);\n \n // Update the display data with the current simpleValue2 values\n setDisplayData(simpleValue2Data || []);\n setLastUpdated(new Date().toISOString());\n \n setTimeout(() => {\n setIsRefreshing(false);\n }, 500);\n }, [simpleValue2Data, setDisplayData, setLastUpdated]);\n \n // Set up auto-refresh\n React.useEffect(() => {\n refreshData(); // Initial refresh\n \n const intervalId = setInterval(() => {\n refreshData();\n }, refreshInterval * 1000);\n \n return () => clearInterval(intervalId);\n }, [refreshData, refreshInterval]);\n \n // Watch for changes in simpleValue2Data and refresh when it changes\n React.useEffect(() => {\n refreshData();\n }, [simpleValue2Data, refreshData]);\n \n return (\n
\n
\n
SimpleValue2 Viewer
\n \n
\n \n
\n \n \n
\n \n \n
\n );\n}\n\nfunction onReady(mount, sourceData) {\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```"
- }
- ],
- "stop": "\n```",
- "maxTokens": 64000
-}
\ No newline at end of file
diff --git a/jumble/integration/cache/llm-api-cache/4276002ae5287209ed4074671d4be9b326c9da271b39d0f198809db1756daa39.json b/jumble/integration/cache/llm-api-cache/4276002ae5287209ed4074671d4be9b326c9da271b39d0f198809db1756daa39.json
new file mode 100644
index 000000000..55150d852
--- /dev/null
+++ b/jumble/integration/cache/llm-api-cache/4276002ae5287209ed4074671d4be9b326c9da271b39d0f198809db1756daa39.json
@@ -0,0 +1,16 @@
+{
+ "model": "anthropic:claude-3-7-sonnet-20250219",
+ "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n\n**This task is part of a 2-Phase Process.**\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n\n\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write \n\n\n- React and ReactDOM are pre-imported - don't import them again\n- All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n- Follow React hooks rules - never nest or conditionally call hooks\n- For form handling, use `onClick` handlers instead of `onSubmit`\n\n\n\n- **useReactiveCell(keyPath: string[])** - Persistent data storage with reactive updates\n- **generateText({ system, messages })** - Generate text via Large Language Model\n- **generateObject({ system, messages })** - Generate JSON object via Large Language Model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n \n ## Important Note About useReactiveCell(keyPath: string[])\n - **useReactiveCell is a React Hook** and must follow all React hook rules\n - It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n - Only call useReactiveCell at the top level of your React function components or custom hooks\n - Do not call useReactiveCell inside loops, conditions, or nested functions\n - Do not call useReactiveCell directly inside the onReady function - it can only be used inside React components\n - The onReady function itself is not a React component and cannot use React hooks directly\n - Example of correct usage:\n ```jsx\n function onReady(mount, sourceData, modules) {\n // This is NOT a React component, so do NOT use hooks here!\n \n // Create a React component to use hooks properly:\n function MyApp() {\n // React hooks can be used here\n const [data, setData] = useReactiveCell('myData', defaultValue);\n return
{/* your JSX */}
;\n }\n \n // Render the React component to the mount point\n const root = ReactDOM.createRoot(mount);\n root.render();\n }\n ```\n \n\n\n\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n\n\n\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"board\": {\n \"type\": \"array\",\n \"title\": \"Game Board\",\n \"description\": \"4x4 grid of number tiles (0 represents empty)\",\n \"default\": [\n [\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0\n ],\n [\n 0,\n 0,\n 0,\n 0\n ]\n ],\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n },\n \"score\": {\n \"type\": \"integer\",\n \"title\": \"Current Score\",\n \"description\": \"Player's current score in this game\",\n \"default\": 0,\n \"minimum\": 0\n },\n \"highScore\": {\n \"type\": \"integer\",\n \"title\": \"High Score\",\n \"description\": \"Highest score achieved across games\",\n \"default\": 0,\n \"minimum\": 0\n },\n \"gameState\": {\n \"type\": \"string\",\n \"title\": \"Game State\",\n \"description\": \"Current state of the game\",\n \"enum\": [\n \"playing\",\n \"won\",\n \"lost\"\n ],\n \"default\": \"playing\"\n },\n \"highestTile\": {\n \"type\": \"integer\",\n \"title\": \"Highest Tile\",\n \"description\": \"Value of the highest tile on the board\",\n \"default\": 2,\n \"minimum\": 2\n }\n },\n \"title\": \"2048 Sliding Puzzle Game\",\n \"description\": \"A game charm to play the classic 2048 sliding tile puzzle with score tracking.\"\n}\n\n\n\n# SDK Usage Guide\n\n\n## 1. `useReactiveCell` Hook\n\nThe `useReactiveCell` hook binds to a reactive cell given a key path and returns a tuple `[doc, setDoc]`:\n\nAny keys from the view-model-schema are valid for useReactiveCell, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useReactiveCell called at top level of component\n const [counter, setCounter] = useReactiveCell(\"counter\");\n const [maxValue, setMaxValue] = useReactiveCell(['settings', 'maxValue']);\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n\n\nSeveral APIs exist for generating text, JSON or image urls.\n\n\n`generateText({ system, messages}): Promise`\n\n```jsx\nasync function fetchLLMResponse() {\n const result = await generateText({\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n })\n console.log('LLM responded:', result);\n}\n```\n\n\n\n\n`window.generateObject({ system, messages }): Promise\n\n\n\n\nSynchronous, generates a URL that will load the image.\n\n```jsx\nfunction ImageComponent() {\n return ;\n}\n```\n\n\n\n\n\n`readWebpage(url: string): Promise` Returns markdown format.\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n\n\nAll generated code must follow this pattern:\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return ;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n
{label}
\n
{description}
\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n {gameState === 'won' \n ? `Congratulations! You reached the 2048 tile!` \n : `Your highest tile was ${highestTile}.`}\n
\n \n
\n
\n );\n}\n\nfunction Game2048() {\n const [board, setBoard] = useReactiveCell(['board']);\n const [score, setScore] = useReactiveCell(['score']);\n const [highScore, setHighScore] = useReactiveCell(['highScore']);\n const [gameState, setGameState] = useReactiveCell(['gameState']);\n const [highestTile, setHighestTile] = useReactiveCell(['highestTile']);\n \n const handleKeyDown = React.useCallback((e) => {\n if (gameState !== 'playing') return;\n \n let result;\n \n switch (e.key) {\n case 'ArrowUp':\n result = moveUp(board);\n break;\n case 'ArrowDown':\n result = moveDown(board);\n break;\n case 'ArrowLeft':\n result = moveLeft(board);\n break;\n case 'ArrowRight':\n result = moveRight(board);\n break;\n default:\n return;\n }\n \n if (result.moved) {\n const newBoard = addRandomTile(result.board);\n const newScore = score + result.score;\n const newHighestTile = getHighestTile(newBoard);\n \n setBoard(newBoard);\n setScore(newScore);\n setHighestTile(Math.max(highestTile, newHighestTile));\n \n if (newHighestTile >= 2048 && gameState !== 'won') {\n setGameState('won');\n } else if (!hasAvailableMoves(newBoard)) {\n setGameState('lost');\n }\n \n if (newScore > highScore) {\n setHighScore(newScore);\n }\n }\n }, [board, score, highScore, gameState, highestTile]);\n \n const restartGame = () => {\n const newBoard = initializeBoard();\n setBoard(newBoard);\n setScore(0);\n setGameState('playing');\n setHighestTile(getHighestTile(newBoard));\n };\n \n // Initialize game if board is empty\n React.useEffect(() => {\n if (board.flat().every(cell => cell === 0)) {\n restartGame();\n }\n }, [board]);\n \n return (\n
\n
\n
\n
2048
\n \n
\n \n
\n Join the tiles, get to 2048! Use arrow keys or swipe to move.\n
\n \n \n \n
\n \n \n
\n
\n
\n );\n}\n\nfunction onReady(mount, sourceData, libs) {\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```"
+ }
+ ],
+ "stop": "\n```",
+ "maxTokens": 64000
+}
\ No newline at end of file
diff --git a/jumble/integration/cache/llm-api-cache/ad89522faed2f90e9a5b20a094e939e7f8cb79cd5089a84002dae9a3ff4239ef.json b/jumble/integration/cache/llm-api-cache/ad89522faed2f90e9a5b20a094e939e7f8cb79cd5089a84002dae9a3ff4239ef.json
new file mode 100644
index 000000000..b3c765ffe
--- /dev/null
+++ b/jumble/integration/cache/llm-api-cache/ad89522faed2f90e9a5b20a094e939e7f8cb79cd5089a84002dae9a3ff4239ef.json
@@ -0,0 +1,16 @@
+{
+ "model": "anthropic:claude-3-7-sonnet-20250219",
+ "system": "\nYou are creating a simple MVP based on the user's goal, using an existing data schema. Focus on the simplest implementation that works with the provided schema.\n\n\nGiven a user's feature request and an existing data schema, you will:\n1. Create a short title (2-5 words) that names the artifact\n2. Create a one-sentence description in the format \"A to \"\n3. Create a concise specification (3-5 sentences max) that works with the existing schema\n4. Generate a brief implementation plan (3 steps max)\n5. Design a minimal JSON schema that represents the core data model\n\n\n\nYour response must be structured as follows:\n\nFreeform reasoning for you to consider the solution.\n\n\n[Short title for the artifact, 2-5 words]\n\n\n\n[One-sentence description in the format \"A to \"]\n\n\n\n[Concise specification that captures only the essential requirements]\n\n\n\n[Brief 3-step implementation plan using the existing schema]\n\n\n\n[Minimal JSON Schema in valid JSON format that represents data created by the artifact]\n\n\n\n\n1. Keep it minimal:\n - Include only essential fields (5-7 properties max)\n - Focus on the core functionality\n - If user requested complex features, simplify for this first version\n\n2. Each property should have:\n - A descriptive \"title\" field\n - A brief \"description\" field\n - A sensible default value where appropriate\n\n3. Example of a simple schema:\n```json\n{\n \"type\": \"object\",\n \"title\": \"Note List\",\n \"description\": \"A simple note list for the user\",\n \"properties\": {\n \"notes\": {\n \"type\": \"array\",\n \"title\": \"Notes\",\n \"description\": \"List of user notes\",\n \"default\": [],\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"title\": \"Title\",\n \"description\": \"Title of the note\",\n \"default\": \"New Note\"\n },\n \"content\": {\n \"type\": \"string\",\n \"title\": \"Content\",\n \"description\": \"Content of the note\"\n },\n \"created\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Created Date\",\n \"description\": \"When the note was created\"\n }\n },\n \"required\": [\"title\", \"content\"]\n }\n }\n },\n \"required\": [\"title\", \"content\"]\n}\n```\n\nGUIDELINES:\n- Aim for the simplest possible solution that works with the existing schema\n- The specification should take into account the existing schema structure\n- Focus on what can be achieved quickly with the existing data model\n- Avoid suggesting modifications to the schema if possible\n\n\n\nIMPORTANT:\n- Focus on the simplest working version\n- Aim for fewer fields rather than more\n- But still capture all the important state the user is creating\n- The user can always iterate and improve the solution later\n\n",
+ "stream": false,
+ "messages": [
+ {
+ "role": "user",
+ "content": "\n\n\n2048 game\n\nCreate a ๐ฎ 2048 game charm with sliding ๐ข number tiles, score tracking, and game state management. The charm will need a grid layout, tile merging logic, and keyboard/swipe controls. Could add a ๐ high score feature and ๐ restart button.\n\n\n\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {}\n}\n```\n\n\nBased on this goal and the existing schema, please provide a title, description, any additional schema, detailed specification, and implementation plan.\n"
+ },
+ {
+ "role": "assistant",
+ "content": "\nThe user wants to create a 2048 game charm. The 2048 game is a sliding tile puzzle where players combine tiles with the same numbers to create a tile with the number 2048. \n\nKey components needed:\n1. A grid (typically 4x4) to hold the number tiles\n2. Logic for sliding tiles in four directions (up, down, left, right)\n3. Merging logic when two tiles with the same number collide\n4. Score tracking\n5. Game state management (in progress, won, lost)\n6. Optional: High score tracking and restart functionality\n\nThe existing schema is empty, so I'll need to design a complete schema that captures:\n- The game board (grid of numbers)\n- Current score\n- Game state\n- High score (as mentioned in optional features)\n\nFor the implementation plan, I'll focus on the core game mechanics first, then the UI, and finally the additional features.\n\n\n\n2048 Sliding Puzzle Game\n\n\n\nA game charm to play the classic 2048 sliding tile puzzle with score tracking.\n\n\n\nThe 2048 game will feature a 4x4 grid where numbered tiles can be slid in four directions (up, down, left, right). When two tiles with the same number collide, they merge into a single tile with their sum. The game tracks the current score, game state (playing, won, lost), and highest tile achieved. A high score feature will persist the best score across game sessions.\n\n\n\n1. Implement the core game board with grid representation and tile movement/merging logic\n2. Create the UI with responsive grid layout and keyboard/swipe controls\n3. Add game state management, scoring system, and restart functionality\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"2048 Game State\",\n \"description\": \"Represents the current state of a 2048 game\",\n \"properties\": {\n \"board\": {\n \"type\": \"array\",\n \"title\": \"Game Board\",\n \"description\": \"4x4 grid of number tiles (0 represents empty)\",\n \"default\": [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [0, 0, 0, 0]\n ],\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"integer\",\n \"minimum\": 0\n }\n }\n },\n \"score\": {\n \"type\": \"integer\",\n \"title\": \"Current Score\",\n \"description\": \"Player's current score in this game\",\n \"default\": 0,\n \"minimum\": 0\n },\n \"highScore\": {\n \"type\": \"integer\",\n \"title\": \"High Score\",\n \"description\": \"Highest score achieved across games\",\n \"default\": 0,\n \"minimum\": 0\n },\n \"gameState\": {\n \"type\": \"string\",\n \"title\": \"Game State\",\n \"description\": \"Current state of the game\",\n \"enum\": [\"playing\", \"won\", \"lost\"],\n \"default\": \"playing\"\n },\n \"highestTile\": {\n \"type\": \"integer\",\n \"title\": \"Highest Tile\",\n \"description\": \"Value of the highest tile on the board\",\n \"default\": 2,\n \"minimum\": 2\n }\n },\n \"required\": [\"board\", \"score\", \"gameState\"]\n}\n"
+ }
+ ],
+ "maxTokens": 64000
+}
\ No newline at end of file
diff --git a/jumble/integration/cache/llm-api-cache/b0a1a973797ea0e477e6bf4d1cc83cf9ec59127763bea39a7428ec03e7ca2313.json b/jumble/integration/cache/llm-api-cache/b0a1a973797ea0e477e6bf4d1cc83cf9ec59127763bea39a7428ec03e7ca2313.json
index 05195068b..32d4df6a5 100644
--- a/jumble/integration/cache/llm-api-cache/b0a1a973797ea0e477e6bf4d1cc83cf9ec59127763bea39a7428ec03e7ca2313.json
+++ b/jumble/integration/cache/llm-api-cache/b0a1a973797ea0e477e6bf4d1cc83cf9ec59127763bea39a7428ec03e7ca2313.json
@@ -9,7 +9,7 @@
},
{
"role": "assistant",
- "content": "\nLet's analyze what we have:\n\n1. The user wants to display data from a `simpleValue2` charm.\n2. The existing schema shows that `simpleValue2` contains an object with a property called `values`, which is an array of strings.\n\nThe user wants a simple way to view this data. I'll create a simple viewer component that displays the current values in the array.\n\nFor the schema, I'll keep it minimal since we're just displaying existing data. The result schema will represent what the viewer component will display - the values from the array and potentially some metadata about the display.\n\n\n\nSimpleValue2 Viewer\n\n\n\nA component to display the current values stored in the simpleValue2 charm.\n\n\n\nThe SimpleValue2 Viewer will display all string values currently stored in the simpleValue2.values array. It will present the values in a clean, readable list format. The viewer will automatically update when the underlying data changes.\n\n\n\n1. Access the simpleValue2 data from the existing schema\n2. Create a display component that renders the values array as a list\n3. Implement auto-refresh functionality to keep the display in sync with data changes\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"Display configuration for simpleValue2 data\",\n \"properties\": {\n \"displayData\": {\n \"type\": \"object\",\n \"title\": \"Display Data\",\n \"description\": \"The data being displayed from simpleValue2\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"title\": \"Values\",\n \"description\": \"The string values from simpleValue2\",\n \"items\": {\n \"type\": \"string\"\n },\n \"default\": []\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Last Updated\",\n \"description\": \"When the display was last refreshed\"\n }\n },\n \"required\": [\"values\"]\n },\n \"displaySettings\": {\n \"type\": \"object\",\n \"title\": \"Display Settings\",\n \"description\": \"Configuration for how the data is displayed\",\n \"properties\": {\n \"showTimestamp\": {\n \"type\": \"boolean\",\n \"title\": \"Show Timestamp\",\n \"description\": \"Whether to show when the data was last updated\",\n \"default\": true\n },\n \"refreshInterval\": {\n \"type\": \"integer\",\n \"title\": \"Refresh Interval\",\n \"description\": \"How often to refresh the display (in seconds)\",\n \"default\": 30\n }\n }\n }\n },\n \"required\": [\"displayData\"]\n}\n"
+ "content": "\nLet's analyze what we have:\n\n1. The user wants to display data from a `simpleValue2` charm.\n2. The existing schema shows that `simplevalue2` is an object with a property called `values`, which is an array of strings.\n3. The user wants to see the current value and any associated metadata.\n\nFrom the schema, it seems like the main data is in the `values` array. There doesn't appear to be explicit metadata in the schema, just the array of string values.\n\nFor this MVP, I'll create a simple viewer that displays the array of strings from the `simplevalue2.values` property. Since there's no explicit metadata in the schema, I'll focus on displaying the values themselves in a clean, readable format.\n\nThe artifact will be a viewer component that reads from the existing schema and presents the data in a user-friendly way.\n\n\n\nSimpleValue2 Viewer\n\n\n\nA viewer component to display the contents of the simpleValue2 charm.\n\n\n\nThe SimpleValue2 Viewer will read and display all string values stored in the simplevalue2.values array. It will present these values in a clean, readable list format. The viewer will automatically update when the underlying data changes.\n\n\n\n1. Create a component that accesses the simplevalue2.values array from the existing schema\n2. Implement a display layout that renders each string value as a list item\n3. Add basic styling and empty state handling for when the values array is empty\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"Display component for simpleValue2 data\",\n \"properties\": {\n \"displayData\": {\n \"type\": \"object\",\n \"title\": \"Display Data\",\n \"description\": \"Formatted data for display\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"title\": \"Values\",\n \"description\": \"List of string values from simpleValue2\",\n \"default\": [],\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"count\": {\n \"type\": \"integer\",\n \"title\": \"Count\",\n \"description\": \"Number of values in the array\",\n \"default\": 0\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Last Updated\",\n \"description\": \"When the viewer last refreshed the data\"\n }\n },\n \"required\": [\"values\", \"count\"]\n }\n },\n \"required\": [\"displayData\"]\n}\n"
}
],
"maxTokens": 64000
diff --git a/jumble/integration/cache/llm-api-cache/cacf4ca8ff310d75d00121bae9caec8c1030970e950d3333386e4e461ee47650.json b/jumble/integration/cache/llm-api-cache/cacf4ca8ff310d75d00121bae9caec8c1030970e950d3333386e4e461ee47650.json
new file mode 100644
index 000000000..9661c1d4b
--- /dev/null
+++ b/jumble/integration/cache/llm-api-cache/cacf4ca8ff310d75d00121bae9caec8c1030970e950d3333386e4e461ee47650.json
@@ -0,0 +1,16 @@
+{
+ "model": "anthropic:claude-3-7-sonnet-latest",
+ "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n\n**This task is part of a 2-Phase Process.**\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n\n\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write \n\n\n- React and ReactDOM are pre-imported - don't import them again\n- All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n- Follow React hooks rules - never nest or conditionally call hooks\n- For form handling, use `onClick` handlers instead of `onSubmit`\n\n\n\n- **useReactiveCell(keyPath: string[])** - Persistent data storage with reactive updates\n- **generateText({ system, messages })** - Generate text via Large Language Model\n- **generateObject({ system, messages })** - Generate JSON object via Large Language Model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n \n ## Important Note About useReactiveCell(keyPath: string[])\n - **useReactiveCell is a React Hook** and must follow all React hook rules\n - It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n - Only call useReactiveCell at the top level of your React function components or custom hooks\n - Do not call useReactiveCell inside loops, conditions, or nested functions\n - Do not call useReactiveCell directly inside the onReady function - it can only be used inside React components\n - The onReady function itself is not a React component and cannot use React hooks directly\n - Example of correct usage:\n ```jsx\n function onReady(mount, sourceData, modules) {\n // This is NOT a React component, so do NOT use hooks here!\n \n // Create a React component to use hooks properly:\n function MyApp() {\n // React hooks can be used here\n const [data, setData] = useReactiveCell('myData', defaultValue);\n return
{/* your JSX */}
;\n }\n \n // Render the React component to the mount point\n const root = ReactDOM.createRoot(mount);\n root.render();\n }\n ```\n \n\n\n\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n\n\n\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"simplevalue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"displayData\": {\n \"type\": \"object\",\n \"title\": \"Display Data\",\n \"description\": \"Formatted data for display\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"title\": \"Values\",\n \"description\": \"List of string values from simpleValue2\",\n \"default\": [],\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"count\": {\n \"type\": \"integer\",\n \"title\": \"Count\",\n \"description\": \"Number of values in the array\",\n \"default\": 0\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Last Updated\",\n \"description\": \"When the viewer last refreshed the data\"\n }\n },\n \"required\": [\n \"values\",\n \"count\"\n ]\n }\n },\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"A viewer component to display the contents of the simpleValue2 charm.\"\n}\n\n\n\n# SDK Usage Guide\n\n\n## 1. `useReactiveCell` Hook\n\nThe `useReactiveCell` hook binds to a reactive cell given a key path and returns a tuple `[doc, setDoc]`:\n\nAny keys from the view-model-schema are valid for useReactiveCell, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useReactiveCell called at top level of component\n const [counter, setCounter] = useReactiveCell(\"counter\");\n const [maxValue, setMaxValue] = useReactiveCell(['settings', 'maxValue']);\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n\n\nSeveral APIs exist for generating text, JSON or image urls.\n\n\n`generateText({ system, messages}): Promise`\n\n```jsx\nasync function fetchLLMResponse() {\n const result = await generateText({\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n })\n console.log('LLM responded:', result);\n}\n```\n\n\n\n\n`window.generateObject({ system, messages }): Promise\n\n\n\n\nSynchronous, generates a URL that will load the image.\n\n```jsx\nfunction ImageComponent() {\n return ;\n}\n```\n\n\n\n\n\n`readWebpage(url: string): Promise` Returns markdown format.\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n\n\nAll generated code must follow this pattern:\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return ;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n
{label}
\n
{description}
\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n );\n}\n\n// Main application code with modules passed as third parameter\nfunction onReady(mount, sourceData, libs) {\n const { useState, useEffect } = React; // React is available globally\n const { useSpring, animated } = libs['@react-spring/web']; // Access imported module\n\n function MyApp() {\n const [count, setCount] = useReactiveCell('count');\n const [todos, setTodos] = useReactiveCell('todos');\n\n const props = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 }\n });\n\n return (\n
\n \n \n \n \n
\n );\n }\n\n // Use the client API for React 18\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```\n\n",
+ "messages": [
+ {
+ "role": "user",
+ "content": "The user asked you to create the source code with the following specification:\n```\n\nshow the data from `simpleValue2`\n\nShow the data from the `simpleValue2` charm, displaying its current value and any associated metadata.\n\n1. Create a component that accesses the simplevalue2.values array from the existing schema\n2. Implement a display layout that renders each string value as a list item\n3. Add basic styling and empty state handling for when the values array is empty\nThe SimpleValue2 Viewer will read and display all string values stored in the simplevalue2.values array. It will present these values in a clean, readable list format. The viewer will automatically update when the underlying data changes.\n```"
+ },
+ {
+ "role": "assistant",
+ "content": "\nfunction onLoad() {\n return []; // No additional modules needed\n}\n\nconst title = 'SimpleValue2 Viewer';\n\nfunction ValueItem({ value, index }) {\n return (\n
\n
\n {index + 1}.\n {value}\n
\n
\n );\n}\n\nfunction EmptyState() {\n return (\n
\n ๐\n
No values found
\n
The simpleValue2 data source is empty
\n
\n );\n}\n\nfunction SimpleValue2Viewer() {\n // Access the values from simplevalue2\n const [simpleValues, setSimpleValues] = useReactiveCell(['simplevalue2', 'values'], []);\n \n // Update display data whenever simpleValues changes\n const [displayData, setDisplayData] = useReactiveCell(['displayData'], {\n values: [],\n count: 0,\n lastUpdated: new Date().toISOString()\n });\n\n // Update the display data when simpleValues changes\n React.useEffect(() => {\n setDisplayData({\n values: simpleValues || [],\n count: (simpleValues || []).length,\n lastUpdated: new Date().toISOString()\n });\n }, [simpleValues, setDisplayData]);\n\n return (\n