Views, Actions, and Data
Pancake apps are built from three types of definitions: Views, Actions, and Data. Understanding when to use each is key to building effective AI-native applications.
Overview
| Type | Purpose | Has UI | Typical Use |
|---|---|---|---|
| Views | Display interactive UIs | Yes | Dashboards, search results, forms |
| Actions | Write operations | No | Submit, update, delete |
| Data | Read operations | No | Fetch, search, query |
Views
Views combine a server-side handler with a client-side UI. When the AI invokes a view, the handler runs first to fetch data, then the UI renders with that data.
import { defineView } from '@pancake-apps/server';
import { z } from 'zod';
views: {
dashboard: defineView({
description: 'Show user dashboard with stats',
input: z.object({
userId: z.string(),
}),
data: z.object({
name: z.string(),
stats: z.object({
views: z.number(),
clicks: z.number(),
}),
}),
handler: async ({ userId }) => {
const user = await getUser(userId);
const stats = await getStats(userId);
return { name: user.name, stats };
},
ui: { html: './src/views/dashboard.html' },
}),
}View Options
| Option | Type | Description |
|---|---|---|
description | string | What the AI sees when listing tools |
input | ZodSchema | Parameters the view accepts |
data | ZodSchema | Data returned by the handler |
handler | function | Server-side data fetching |
ui | object | Path to HTML or React component |
visibility | string | Who can invoke: 'both', 'model', or 'app' |
Visibility Options
- both (default): AI can invoke, users can navigate directly
- model: Only AI can invoke (hidden from app navigation)
- app: Only visible in app navigation (AI cannot invoke)
Actions
Actions are write operations. They handle mutations, submissions, and side effects. Unlike views, they don't render UI.
import { defineAction } from '@pancake-apps/server';
import { z } from 'zod';
actions: {
updateProfile: defineAction({
description: 'Update user profile information',
input: z.object({
name: z.string(),
email: z.string().email(),
}),
output: z.object({
success: z.boolean(),
updatedAt: z.string(),
}),
handler: async ({ name, email }) => {
const result = await updateUser({ name, email });
return { success: true, updatedAt: result.updatedAt };
},
}),
}Action Options
| Option | Type | Description |
|---|---|---|
description | string | What the AI sees |
input | ZodSchema | Required input parameters |
output | ZodSchema | What the handler returns |
handler | function | The mutation logic |
Calling Actions from Views
In React:
import { useAction } from '@pancake-apps/web';
function ProfileForm() {
const { dispatch } = useAction();
const handleSubmit = async (data) => {
const result = await dispatch('updateProfile', data);
if (result.success) {
// Show success message
}
};
}In vanilla HTML:
const result = await pancake.unified.action('updateProfile', {
name: 'John',
email: 'john@example.com',
});Data
Data endpoints are read operations. They query, search, and fetch information without modifying anything.
import { defineData } from '@pancake-apps/server';
import { z } from 'zod';
data: {
searchProducts: defineData({
description: 'Search products by name or category',
input: z.object({
query: z.string(),
category: z.string().optional(),
limit: z.number().default(10),
}),
output: z.array(z.object({
id: z.string(),
name: z.string(),
price: z.number(),
})),
handler: async ({ query, category, limit }) => {
return searchProducts({ query, category, limit });
},
}),
}Data Options
| Option | Type | Description |
|---|---|---|
description | string | What the AI sees |
input | ZodSchema | Query parameters |
output | ZodSchema | Shape of returned data |
handler | function | The query logic |
Fetching Data from Views
In React:
import { useData } from '@pancake-apps/web';
function ProductSearch() {
const { getData } = useData();
const [products, setProducts] = useState([]);
const search = async (query) => {
const results = await getData('searchProducts', { query });
setProducts(results);
};
}In vanilla HTML:
const products = await pancake.unified.data('searchProducts', {
query: 'laptop',
limit: 20,
});When to Use Each
| Scenario | Use |
|---|---|
| Display data with interactive UI | View |
| Form submission | Action |
| Delete or update operation | Action |
| Fetch data for display | Data |
| Search or filter | Data |
| Background mutation | Action |
Combining Them
A typical app uses all three together. Here's an example e-commerce flow:
const app = createApp({
name: 'shop',
version: '1.0.0',
// Views for displaying UI
views: {
productList: defineView({
description: 'Browse products',
handler: async () => fetchFeaturedProducts(),
ui: { html: './src/views/products.html' },
}),
cart: defineView({
description: 'View shopping cart',
handler: async ({ userId }) => getCart(userId),
ui: { html: './src/views/cart.html' },
}),
},
// Actions for mutations
actions: {
addToCart: defineAction({
description: 'Add product to cart',
input: z.object({ productId: z.string(), quantity: z.number() }),
output: z.object({ cartTotal: z.number() }),
handler: async ({ productId, quantity }) => {
return addToCart(productId, quantity);
},
}),
checkout: defineAction({
description: 'Complete purchase',
input: z.object({ paymentMethod: z.string() }),
output: z.object({ orderId: z.string() }),
handler: async ({ paymentMethod }) => {
return processCheckout(paymentMethod);
},
}),
},
// Data for queries
data: {
searchProducts: defineData({
description: 'Search products',
input: z.object({ query: z.string() }),
output: z.array(ProductSchema),
handler: async ({ query }) => searchProducts(query),
}),
},
});The view calls the data endpoint for search results, and calls actions when the user adds items or checks out.
Best Practices
Views
- Keep handlers focused on data fetching
- Let the UI handle interaction logic
- Use descriptive names that make sense to AI
Actions
- One action per mutation
- Return meaningful confirmation
- Handle errors gracefully
Data
- Keep queries simple and composable
- Use pagination for large result sets
- Cache when appropriate
Next Steps
- Building Widgets: Advanced view patterns
- API Reference: Complete API documentation
- Examples: Working code examples