🥞PancakeJS

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>
  );
}
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 dev

Each example has its own README with specific instructions.

Next Steps

On this page