Skip to content

Commit a562833

Browse files
authored
Editable MCP server URL, Tool/Simulation dropdowns, demo mode (#18)
Replace inspectMode/prodTools with a unified Tool + Simulation dropdown model. The Tool dropdown is always visible and the Simulation dropdown shows available fixtures with a "None (call server)" option. The MCP server URL is always editable with connection status. Add demoMode prop for marketing embeds. The --prod-tools flag now controls whether external MCP callers (Claude, ChatGPT) get real handler results or simulation mocks.
1 parent bbfdc9b commit a562833

23 files changed

Lines changed: 1306 additions & 570 deletions

File tree

docs/api-reference/cli/dev.mdx

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,32 +56,33 @@ No manual configuration or entry point files needed!
5656
| Option | Short | Description |
5757
|--------|-------|-------------|
5858
| `--port <number>` | `-p` | Port for the simulator (default: 3000, also respects `PORT` env var) |
59-
| `--prod-tools` | | Start with **Prod Tools** mode enabled (real tool handlers). Can also be toggled from the sidebar at runtime. |
6059
| `--prod-resources` | | Serve production-built HTML from `dist/` instead of Vite HMR |
6160

62-
### `--prod-tools` and `--prod-resources` flags
61+
### Tool and Simulation selection
6362

64-
These two orthogonal flags let you test different combinations of UI and tool behavior:
63+
The simulator sidebar has two primary selectors:
6564

66-
| Flags | UI | Tools | Use case |
67-
|-------|-----|-------|----------|
68-
| *(none)* | HMR | Mocked | Day-to-day development |
69-
| `--prod-tools` | HMR | Real handlers | Integration testing with real backend logic |
70-
| `--prod-resources` | Production bundle | Mocked | Catch build regressions, CI/E2E testing |
71-
| `--prod-tools --prod-resources` | Production bundle | Real handlers | Final smoke test (simulator shell around production behavior) |
65+
- **Tool dropdown** — selects which tool to inspect. Always visible when tools exist.
66+
- **Simulation dropdown** — selects a simulation fixture with mock data. Shown when fixtures exist for the selected tool. Includes a "None (call server)" option to bypass mock data and call the real handler via the **Run** button.
7267

73-
```bash
74-
# Integration test: real tool handlers with HMR UI
75-
sunpeak dev --prod-tools
68+
For programmatic testing, these map to URL parameters:
7669

77-
# CI/E2E: production bundles with mock data
78-
sunpeak dev --prod-resources
70+
```bash
71+
# Mock data renders immediately (simulation fixture selected):
72+
http://localhost:3000/?simulation=show-albums&theme=dark
7973

80-
# Full smoke test: production bundles with real handlers
81-
sunpeak dev --prod-tools --prod-resources
74+
# No mock data — user clicks Run for real handler:
75+
http://localhost:3000/?tool=show-albums&theme=dark
8276
```
8377

84-
When `--prod-resources` is used, `sunpeak build` runs automatically before the dev server starts. The built HTML is served from `dist/`.
78+
### `--prod-resources` flag
79+
80+
When `--prod-resources` is used, `sunpeak build` runs automatically before the dev server starts. The built HTML is served from `dist/` instead of Vite HMR. Useful for catching build regressions in CI/E2E testing.
81+
82+
```bash
83+
# CI/E2E: production bundles with simulation mock data
84+
sunpeak dev --prod-resources
85+
```
8586

8687
## Configuration
8788

docs/api-reference/simulations/simulator.mdx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,19 @@ import { Simulator } from 'sunpeak/simulator';
4444
</ResponseField>
4545

4646
<ResponseField name="onCallTool" type="(params: { name: string; arguments?: Record<string, unknown> }) => Promise<CallToolResult> | CallToolResult">
47-
Override `callServerTool` resolution. When provided, enables the **Prod Tools** toggle in the sidebar. In Prod Tools mode, tool calls are forwarded to this callback instead of using simulation mock data. The `sunpeak dev` command wires this up automatically.
47+
Handler for tool calls. When the user clicks **Run**, or when the app calls `callServerTool`, calls are forwarded to this callback. The `sunpeak dev` command wires this up automatically.
4848
</ResponseField>
4949

50-
<ResponseField name="defaultProdTools" type="boolean" default="false">
51-
Initial state of the Prod Tools toggle. When `true`, the simulator starts in Prod Tools mode. Set by the `--prod-tools` CLI flag.
50+
<ResponseField name="onCallToolDirect" type="(params: { name: string; arguments?: Record<string, unknown> }) => Promise<CallToolResult> | CallToolResult">
51+
Direct tool handler call, bypassing MCP server mock data. Used by the **Run** button to call real handlers. Falls back to `onCallTool` if not provided.
5252
</ResponseField>
5353

5454
<ResponseField name="defaultProdResources" type="boolean" default="false">
5555
Initial state of the Prod Resources toggle. When `true`, the simulator loads resources from `dist/` production builds instead of HMR. Set by the `--prod-resources` CLI flag.
5656
</ResponseField>
5757

5858
<ResponseField name="hideSimulatorModes" type="boolean" default="false">
59-
Hide the Prod Tools and Prod Resources checkboxes in the sidebar. Useful for embedded or marketing contexts where these modes are not applicable.
59+
Hide the Prod Resources checkbox in the sidebar. Used in standalone inspect mode where Prod Resources is not applicable.
6060
</ResponseField>
6161

6262
## URL Parameters
@@ -67,7 +67,8 @@ The Simulator supports URL parameters for configuring the initial state. This is
6767

6868
| Parameter | Type | Default | Description |
6969
|-----------|------|---------|-------------|
70-
| `simulation` | `string` | First simulation | Simulation name to load |
70+
| `tool` | `string` | First tool | Tool name to select. When specified without `simulation`, no mock data is loaded ("Press Run" state). |
71+
| `simulation` | `string` | First fixture | Simulation fixture name to load. Mock data renders immediately. |
7172
| `host` | `'chatgpt' \| 'claude'` | `'chatgpt'` | Host shell (conversation chrome, theming, capabilities) |
7273
| `theme` | `'light' \| 'dark'` | `'dark'` | Color theme |
7374
| `displayMode` | `'inline' \| 'pip' \| 'fullscreen'` | `'inline'` | App display mode |
@@ -80,15 +81,17 @@ The Simulator supports URL parameters for configuring the initial state. This is
8081
| `safeAreaBottom` | `number` | `0` | Bottom safe area inset |
8182
| `safeAreaLeft` | `number` | `0` | Left safe area inset |
8283
| `safeAreaRight` | `number` | `0` | Right safe area inset |
83-
| `prodTools` | `'true' \| 'false'` | `'false'` | Enable Prod Tools mode (real tool handlers) |
8484
| `prodResources` | `'true' \| 'false'` | `'false'` | Enable Prod Resources mode (dist/ bundles) |
8585

8686
### Example URLs
8787

8888
```
89-
# Load albums simulation in light mode
89+
# Load albums simulation with mock data
9090
http://localhost:3000/?simulation=show-albums&theme=light
9191
92+
# Select a tool with no mock data (user clicks Run for real handler)
93+
http://localhost:3000/?tool=show-albums&theme=dark
94+
9295
# Fullscreen dark mode
9396
http://localhost:3000/?simulation=review-diff&theme=dark&displayMode=fullscreen
9497
@@ -147,6 +150,7 @@ createSimulatorUrl({
147150

148151
```typescript
149152
interface SimulatorUrlParams {
153+
tool?: string;
150154
simulation?: string;
151155
host?: string;
152156
theme?: 'light' | 'dark';
@@ -160,7 +164,6 @@ interface SimulatorUrlParams {
160164
safeAreaBottom?: number;
161165
safeAreaLeft?: number;
162166
safeAreaRight?: number;
163-
prodTools?: boolean;
164167
prodResources?: boolean;
165168
}
166169
```

examples/albums-example/tests/e2e/albums.spec.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ for (const host of hosts) {
9494

9595
test.describe('Prod Tools Mode', () => {
9696
test('should show empty state with Run button', async ({ page }) => {
97-
await page.goto(
98-
createSimulatorUrl({ simulation: 'show-albums', theme: 'dark', host, prodTools: true })
99-
);
97+
await page.goto(createSimulatorUrl({ tool: 'show-albums', theme: 'dark', host }));
10098

10199
// Should show the "Press Run to call the tool" empty state
102100
const emptyState = page.locator('text=Press Run to call the tool');
@@ -112,9 +110,7 @@ for (const host of hosts) {
112110
});
113111

114112
test('should have themed empty state colors in light mode', async ({ page }) => {
115-
await page.goto(
116-
createSimulatorUrl({ simulation: 'show-albums', theme: 'light', host, prodTools: true })
117-
);
113+
await page.goto(createSimulatorUrl({ tool: 'show-albums', theme: 'light', host }));
118114

119115
const emptyState = page.locator('text=Press Run to call the tool');
120116
await expect(emptyState).toBeVisible();
@@ -130,9 +126,7 @@ for (const host of hosts) {
130126
});
131127

132128
test('should have themed empty state colors in dark mode', async ({ page }) => {
133-
await page.goto(
134-
createSimulatorUrl({ simulation: 'show-albums', theme: 'dark', host, prodTools: true })
135-
);
129+
await page.goto(createSimulatorUrl({ tool: 'show-albums', theme: 'dark', host }));
136130

137131
const emptyState = page.locator('text=Press Run to call the tool');
138132
await expect(emptyState).toBeVisible();

examples/carousel-example/tests/e2e/carousel.spec.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ for (const host of hosts) {
6464

6565
test.describe('Prod Tools Mode', () => {
6666
test('should show empty state with Run button', async ({ page }) => {
67-
await page.goto(
68-
createSimulatorUrl({ simulation: 'show-carousel', theme: 'dark', host, prodTools: true })
69-
);
67+
await page.goto(createSimulatorUrl({ tool: 'show-carousel', theme: 'dark', host }));
7068

7169
const emptyState = page.locator('text=Press Run to call the tool');
7270
await expect(emptyState).toBeVisible();
@@ -79,9 +77,7 @@ for (const host of hosts) {
7977
});
8078

8179
test('should have themed empty state colors in light mode', async ({ page }) => {
82-
await page.goto(
83-
createSimulatorUrl({ simulation: 'show-carousel', theme: 'light', host, prodTools: true })
84-
);
80+
await page.goto(createSimulatorUrl({ tool: 'show-carousel', theme: 'light', host }));
8581

8682
const emptyState = page.locator('text=Press Run to call the tool');
8783
await expect(emptyState).toBeVisible();
@@ -95,9 +91,7 @@ for (const host of hosts) {
9591
});
9692

9793
test('should have themed empty state colors in dark mode', async ({ page }) => {
98-
await page.goto(
99-
createSimulatorUrl({ simulation: 'show-carousel', theme: 'dark', host, prodTools: true })
100-
);
94+
await page.goto(createSimulatorUrl({ tool: 'show-carousel', theme: 'dark', host }));
10195

10296
const emptyState = page.locator('text=Press Run to call the tool');
10397
await expect(emptyState).toBeVisible();

examples/map-example/tests/e2e/map.spec.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ for (const host of hosts) {
103103

104104
test.describe('Prod Tools Mode', () => {
105105
test('should show empty state with Run button', async ({ page }) => {
106-
await page.goto(
107-
createSimulatorUrl({ simulation: 'show-map', theme: 'dark', host, prodTools: true })
108-
);
106+
await page.goto(createSimulatorUrl({ tool: 'show-map', theme: 'dark', host }));
109107

110108
const emptyState = page.locator('text=Press Run to call the tool');
111109
await expect(emptyState).toBeVisible();
@@ -118,9 +116,7 @@ for (const host of hosts) {
118116
});
119117

120118
test('should have themed empty state colors in light mode', async ({ page }) => {
121-
await page.goto(
122-
createSimulatorUrl({ simulation: 'show-map', theme: 'light', host, prodTools: true })
123-
);
119+
await page.goto(createSimulatorUrl({ tool: 'show-map', theme: 'light', host }));
124120

125121
const emptyState = page.locator('text=Press Run to call the tool');
126122
await expect(emptyState).toBeVisible();
@@ -134,9 +130,7 @@ for (const host of hosts) {
134130
});
135131

136132
test('should have themed empty state colors in dark mode', async ({ page }) => {
137-
await page.goto(
138-
createSimulatorUrl({ simulation: 'show-map', theme: 'dark', host, prodTools: true })
139-
);
133+
await page.goto(createSimulatorUrl({ tool: 'show-map', theme: 'dark', host }));
140134

141135
const emptyState = page.locator('text=Press Run to call the tool');
142136
await expect(emptyState).toBeVisible();

examples/review-example/tests/e2e/review.spec.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,7 @@ for (const host of hosts) {
101101

102102
test.describe('Prod Tools Mode', () => {
103103
test('should show empty state with Run button', async ({ page }) => {
104-
await page.goto(
105-
createSimulatorUrl({ simulation: 'review-diff', theme: 'dark', host, prodTools: true })
106-
);
104+
await page.goto(createSimulatorUrl({ tool: 'review-diff', theme: 'dark', host }));
107105

108106
const emptyState = page.locator('text=Press Run to call the tool');
109107
await expect(emptyState).toBeVisible();
@@ -116,9 +114,7 @@ for (const host of hosts) {
116114
});
117115

118116
test('should have themed empty state colors in light mode', async ({ page }) => {
119-
await page.goto(
120-
createSimulatorUrl({ simulation: 'review-diff', theme: 'light', host, prodTools: true })
121-
);
117+
await page.goto(createSimulatorUrl({ tool: 'review-diff', theme: 'light', host }));
122118

123119
const emptyState = page.locator('text=Press Run to call the tool');
124120
await expect(emptyState).toBeVisible();
@@ -132,9 +128,7 @@ for (const host of hosts) {
132128
});
133129

134130
test('should have themed empty state colors in dark mode', async ({ page }) => {
135-
await page.goto(
136-
createSimulatorUrl({ simulation: 'review-diff', theme: 'dark', host, prodTools: true })
137-
);
131+
await page.goto(createSimulatorUrl({ tool: 'review-diff', theme: 'dark', host }));
138132

139133
const emptyState = page.locator('text=Press Run to call the tool');
140134
await expect(emptyState).toBeVisible();

packages/sunpeak/bin/commands/dev.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,11 @@ export async function dev(projectRoot = process.cwd(), args = []) {
159159
// Parse --no-begging flag
160160
const noBegging = args.includes('--no-begging');
161161

162-
// Parse --prod-tools and --prod-resources flags
162+
// Parse flags
163163
const isProdTools = args.includes('--prod-tools');
164164
const isProdResources = args.includes('--prod-resources');
165165

166-
if (isProdTools) console.log('Prod Tools enabled by default (toggle in simulator sidebar)');
166+
if (isProdTools) console.log('Prod Tools: MCP tool calls will use real handlers instead of simulation mocks');
167167
if (isProdResources) console.log('Prod Resources: resources will use production-built HTML from dist/');
168168

169169
console.log(`Starting dev server on port ${port}...`);
@@ -496,6 +496,8 @@ if (import.meta.hot) {
496496
// In --prod-resources mode, don't pass viteServer so the MCP server serves pre-built HTML.
497497
// Otherwise, pass it so ChatGPT gets Vite HMR.
498498
viteServer: isProdResources ? undefined : mcpViteServer,
499+
// When --prod-tools is set, UI tool calls use real handlers instead of simulation mocks.
500+
prodTools: isProdTools,
499501
});
500502

501503
// Wait for the MCP server to be listening before starting the inspector
@@ -520,7 +522,6 @@ if (import.meta.hot) {
520522
name: serverDisplayName,
521523
sandboxUrl: sandbox.url,
522524
frameworkMode: true,
523-
defaultProdTools: isProdTools,
524525
defaultProdResources: isProdResources,
525526
projectRoot,
526527
noBegging,

0 commit comments

Comments
 (0)