Examples
Learn Pancake through working examples. Clone the repo and explore these patterns.
Example Projects
The Pancake repository includes several example projects:
Basic React Server
React views with hot reload, actions, and data endpoints
Cohort Heatmap
Data visualization with interactive charts
Scenario Modeler
Complex forms with multiple components
Three.js Integration
3D graphics inside AI conversations
Basic Server Example
A complete Pancake app with views, actions, and data:
Server Setup
// src/index.ts
import { createApp, defineView, defineAction, defineData, discoverViews } from '@pancake-apps/server';
import { z } from 'zod';
const app = createApp({
name: 'my-app',
version: '0.1.0',
views: {
...discoverViews('./src/views'),
},
actions: {
submitFeedback: defineAction({
description: 'Submit user feedback',
input: z.object({
rating: z.number().min(1).max(5),
comment: z.string().optional(),
}),
output: z.object({
success: z.boolean(),
id: z.string(),
}),
handler: async ({ rating, comment }) => {
const id = crypto.randomUUID();
console.log('Feedback:', { rating, comment, id });
return { success: true, id };
},
}),
},
data: {
getItems: defineData({
description: 'Fetch items list',
input: z.object({
limit: z.number().optional().default(10),
}),
output: z.array(z.object({
id: z.string(),
name: z.string(),
})),
handler: async ({ limit }) => {
return Array.from({ length: limit }, (_, i) => ({
id: String(i + 1),
name: `Item ${i + 1}`,
}));
},
}),
},
config: {
debug: true,
tunnel: {
provider: 'cloudflare',
},
},
});
app.start({ port: 3000 });React View
// src/views/hello/index.tsx
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import {
PancakeProvider,
useViewState,
useTheme,
useAction,
useData,
useNavigation,
} from '@pancake-apps/web';
function HelloView() {
const [count, setCount] = useViewState(0);
const theme = useTheme();
const { dispatch } = useAction();
const { getData } = useData();
const { say } = useNavigation();
const [items, setItems] = useState<{ id: string; name: string }[]>([]);
const handleLoadItems = async () => {
const result = await getData<{ id: string; name: string }[]>('getItems', { limit: 5 });
setItems(result);
};
const handleSubmitFeedback = async () => {
const result = await dispatch<{ success: boolean; id: string }>('submitFeedback', {
rating: 5,
comment: 'Great app!',
});
console.log('Feedback submitted:', result.id);
};
return (
<div style={{
padding: '2rem',
fontFamily: 'system-ui',
backgroundColor: theme === 'dark' ? '#1a1a1a' : '#fff',
color: theme === 'dark' ? '#fff' : '#000',
minHeight: '100vh',
}}>
<h1>Hello from Pancake!</h1>
<section style={{ marginTop: '1rem' }}>
<h2>Counter</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
<button onClick={() => setCount(c => c - 1)}>Decrement</button>
</section>
<section style={{ marginTop: '1rem' }}>
<h2>Data</h2>
<button onClick={handleLoadItems}>Load Items</button>
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</section>
<section style={{ marginTop: '1rem' }}>
<h2>Actions</h2>
<button onClick={handleSubmitFeedback}>Submit Feedback</button>
</section>
<section style={{ marginTop: '1rem' }}>
<h2>AI Communication</h2>
<button onClick={() => say('Tell me more about this app')}>
Ask AI
</button>
</section>
</div>
);
}
function App() {
return (
<PancakeProvider>
<HelloView />
</PancakeProvider>
);
}
createRoot(document.getElementById('root')!).render(<App />);View Metadata
// src/views/hello/metadata.json
{
"description": "A greeting view with counter, data loading, and actions",
"visibility": "both"
}Vanilla HTML Example
For simpler views without React:
<!-- src/views/simple.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Simple View</title>
<style>
body {
font-family: system-ui;
padding: 2rem;
margin: 0;
}
.dark {
background: #1a1a1a;
color: #fff;
}
button {
padding: 0.5rem 1rem;
margin: 0.25rem;
cursor: pointer;
}
</style>
</head>
<body>
<h1 id="greeting">Loading...</h1>
<div id="content"></div>
<div>
<button id="loadBtn">Load Data</button>
<button id="actionBtn">Submit Action</button>
<button id="sayBtn">Ask AI</button>
</div>
<pre id="output"></pre>
<script type="module">
// Wait for pancake to initialize
const waitForPancake = () => new Promise((resolve) => {
if (window.pancake) return resolve(window.pancake);
const check = setInterval(() => {
if (window.pancake) {
clearInterval(check);
resolve(window.pancake);
}
}, 50);
});
const pancake = await waitForPancake();
const output = document.getElementById('output');
// Apply theme
if (pancake.hostContext.theme === 'dark') {
document.body.classList.add('dark');
}
// Display view data
const data = pancake.adaptor.getToolOutput();
if (data?.greeting) {
document.getElementById('greeting').textContent = data.greeting;
}
// Load data button
document.getElementById('loadBtn').addEventListener('click', async () => {
const items = await pancake.unified.data('getItems', { limit: 5 });
output.textContent = JSON.stringify(items, null, 2);
});
// Action button
document.getElementById('actionBtn').addEventListener('click', async () => {
const result = await pancake.unified.action('submitFeedback', {
rating: 5,
comment: 'Great!',
});
output.textContent = JSON.stringify(result, null, 2);
});
// Say button
document.getElementById('sayBtn').addEventListener('click', () => {
pancake.unified.say('Tell me more about this app');
output.textContent = 'Message sent to AI';
});
</script>
</body>
</html>Patterns
Loading States
function DataView() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState([]);
const { getData } = useData();
const load = async () => {
setLoading(true);
setError(null);
try {
const result = await getData('fetchData', {});
setData(result);
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <DataList items={data} />;
}Form Submission
function FeedbackForm() {
const { dispatch } = useAction();
const [submitting, setSubmitting] = useState(false);
const [success, setSuccess] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSubmitting(true);
const form = e.target as HTMLFormElement;
const formData = new FormData(form);
const result = await dispatch('submitFeedback', {
rating: Number(formData.get('rating')),
comment: formData.get('comment'),
});
setSubmitting(false);
setSuccess(result.success);
};
if (success) return <div>Thank you for your feedback!</div>;
return (
<form onSubmit={handleSubmit}>
<label>
Rating:
<input type="number" name="rating" min="1" max="5" required />
</label>
<label>
Comment:
<textarea name="comment" />
</label>
<button type="submit" disabled={submitting}>
{submitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}Theme-Aware Styling
function ThemedCard({ children }) {
const theme = useTheme();
const styles = {
light: {
background: '#fff',
color: '#000',
border: '1px solid #e0e0e0',
},
dark: {
background: '#2a2a2a',
color: '#fff',
border: '1px solid #404040',
},
};
return (
<div style={{
...styles[theme],
padding: '1rem',
borderRadius: '8px',
}}>
{children}
</div>
);
}Navigation Between Views
function ProductCard({ product }) {
const { navigate } = useNavigation();
return (
<div onClick={() => navigate('product-detail', { id: product.id })}>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
);
}Running Examples
# Clone the repo
git clone https://github.com/anthropics/pancake-sdk.git
cd pancake-sdk
# Install dependencies
pnpm install
# Run an example
cd examples/basic-server-react
pnpm devEach example has its own README with specific instructions.
Next Steps
- Getting Started: Build your own app
- API Reference: Complete API docs
- Deployment: Ship to production