Skip to content

Commit 755d978

Browse files
authored
waveapps builder window (scaffolding, restructure AI panel to work in both builder and tab windows) (#2482)
1 parent b038b13 commit 755d978

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2712
-726
lines changed

aiprompts/tsunami-builder.md

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# Tsunami AI Builder - V1 Architecture
2+
3+
## Overview
4+
5+
A split-screen builder for creating Tsunami applications: chat interface on left, tabbed preview/code/files on right. Users describe what they want, AI edits the code iteratively.
6+
7+
## UI Layout
8+
9+
### Left Panel
10+
11+
- **💬 Chat** - Conversation with AI
12+
13+
### Right Panel
14+
15+
**Top Section - Tabs:**
16+
- **👁️ Preview** (default) - Live preview of running Tsunami app, updates automatically after successful compilation
17+
- **📝 Code** - Monaco editor for manual edits to app.go
18+
- **📁 Files** - Static assets browser (images, etc)
19+
20+
**Bottom Section - Build Panel (closable):**
21+
- Shows compilation status and output (like VSCode's terminal panel)
22+
- Displays success messages or errors with line numbers
23+
- Auto-runs after AI edits
24+
- For manual Code tab edits: auto-reruns or user clicks build button
25+
- Can be manually closed/reopened by user
26+
27+
### Top Bar
28+
29+
- Current AppTitle (extracted from app.go)
30+
- **Publish** button - Moves draft → published version
31+
- **Revert** button - Copies published → draft (discards draft changes)
32+
33+
## Version Management
34+
35+
**Draft mode**: Auto-saved on every edit, persists when builder closes
36+
**Published version**: What runs in main Wave Terminal, only updates on explicit "Publish"
37+
38+
Flow:
39+
40+
1. Edit in builder (always editing draft)
41+
2. Click "Publish" when ready (copies draft → published)
42+
3. Continue editing draft OR click "Revert" to abandon changes
43+
44+
## Context Structure
45+
46+
Every AI request includes:
47+
48+
```
49+
[System Instructions]
50+
- General system prompt
51+
- Full system.md (Tsunami framework guide)
52+
53+
[Conversation History]
54+
- Recent messages (with prompt caching)
55+
56+
[Current Context] (injected fresh each turn, removed from previous turns)
57+
- Current app.go content
58+
- Compilation results (success or errors with line numbers)
59+
- Static files listing (e.g., "/static/logo.png")
60+
```
61+
62+
**Context cleanup**: Old "current context" blocks are removed from previous messages and replaced with "[OLD CONTEXT REMOVED]" to save tokens. Only the latest app.go + compile results stay in context.
63+
64+
## AI Tools
65+
66+
### edit_appgo (str_replace)
67+
68+
**Primary editing tool**
69+
70+
- `old_str` - Unique string to find in app.go
71+
- `new_str` - Replacement string
72+
- `description` - What this change does
73+
74+
**Backend behavior**:
75+
76+
1. Apply string replacement to app.go
77+
2. Immediately run `go build`
78+
3. Return tool result:
79+
- ✓ Success: "Edit applied, compilation successful"
80+
- ✗ Failure: "Edit applied, compilation failed: [error details]"
81+
82+
AI can make multiple edits in one response, getting compile feedback after each.
83+
84+
### create_appgo
85+
86+
**Bootstrap new apps**
87+
88+
- `content` - Full app.go file content
89+
- Only used for initial app creation or total rewrites
90+
91+
Same compilation behavior as str_replace.
92+
93+
### web_search
94+
95+
**Look up APIs, docs, examples**
96+
97+
- Implemented via provider backend (OpenAI/Anthropic)
98+
- AI can research before making edits
99+
100+
### read_file
101+
102+
**Read user-provided documentation**
103+
104+
- `path` - Path to file (e.g., "/docs/api-spec.md")
105+
- User can upload docs/examples for AI to reference
106+
107+
## User Actions (Not AI Tools)
108+
109+
### Manage Static Assets
110+
111+
- Upload via drag & drop into Files tab or file picker
112+
- Delete files from Files tab
113+
- Rename files from Files tab
114+
- Appear in `/static/` directory
115+
- Auto-injected into AI context as available files
116+
117+
### Share Screenshot
118+
119+
- User clicks "📷 Share preview with AI" button
120+
- Captures current preview state
121+
- Attaches to user's next message
122+
- Useful for debugging layout/visual issues
123+
124+
### Manual Code Editing
125+
126+
- User can switch to Code tab
127+
- Edit app.go directly in Monaco editor
128+
- Changes auto-compile
129+
- AI sees manual edits in next chat turn
130+
131+
## Compilation Pipeline
132+
133+
After every code change (AI or user):
134+
135+
```
136+
1. Write app.go to disk
137+
2. Run: go build app.go
138+
3. Show build output in build panel
139+
4. If success:
140+
- Start/restart app process
141+
- Update preview iframe
142+
- Show success message in build panel
143+
5. If failure:
144+
- Parse error output (line numbers, messages)
145+
- Show error in build panel (bottom of right side)
146+
- Inject into AI context for next turn
147+
```
148+
149+
**Auto-retry**: AI can fix its own compilation errors within the same response (up to 3 attempts).
150+
151+
## Error Handling
152+
153+
### Compilation Errors
154+
155+
Shown in build panel at bottom of right side.
156+
157+
Format for AI:
158+
159+
```
160+
COMPILATION FAILED
161+
162+
Error at line 45:
163+
43 | func(props TodoProps) any {
164+
44 | return vdom.H("div", nil
165+
> 45 | vdom.H("span", nil, "test")
166+
| ^ missing closing parenthesis
167+
46 | )
168+
169+
Message: expected ')', found 'vdom'
170+
```
171+
172+
### Runtime Errors
173+
174+
- Shown in preview tab (not errors panel)
175+
- User can screenshot and report to AI
176+
- Not auto-injected (v1 simplification)
177+
178+
### Linting (Future)
179+
180+
- Could add custom Tsunami-specific linting
181+
- Would inject warnings alongside compile results
182+
- Not required for v1
183+
184+
## Secrets/Configuration
185+
186+
Apps can declare secrets using Tsunami's ConfigAtom:
187+
188+
```go
189+
var apiKeyAtom = app.ConfigAtom("api_key", "", &app.AtomMeta{
190+
Desc: "OpenAI API Key",
191+
Secret: true,
192+
})
193+
```
194+
195+
Builder detects these and shows input fields in UI for user to fill in.
196+
197+
## Conversation Limits
198+
199+
**V1 approach**: No summarization, no smart handling.
200+
201+
When context limit hit: Show message "You've hit the conversation limit. Click 'Start Fresh' to continue editing this app in a new chat."
202+
203+
Starting fresh uses current app.go as the beginning state.
204+
205+
## Token Optimization
206+
207+
- System.md + early messages benefit from prompt caching
208+
- Only pay per-turn for: current app.go + new messages
209+
- Old context blocks removed to prevent bloat
210+
- Estimated: 10-20k tokens per turn (very manageable)
211+
212+
## Example Flow
213+
214+
```
215+
User: "Create a counter app"
216+
AI: [calls create_appgo with full counter app]
217+
Backend: ✓ Compiled successfully
218+
Preview: Shows counter app
219+
220+
User: "Add a reset button"
221+
AI: [calls str_replace to add reset button]
222+
Backend: ✓ Compiled successfully
223+
Preview: Updates with reset button
224+
225+
User: "Make buttons bigger"
226+
AI: [calls str_replace to update button classes]
227+
Backend: ✓ Compiled successfully
228+
Preview: Updates with larger buttons
229+
230+
User: [switches to Code tab, tweaks color manually]
231+
Backend: ✓ Compiled successfully
232+
Preview: Updates
233+
234+
User: "Add a chart showing count over time"
235+
AI: [calls web_search for "go charting library"]
236+
AI: [calls str_replace to add chart]
237+
Backend: ✗ Compilation failed - missing import
238+
AI: [calls str_replace to add import]
239+
Backend: ✓ Compiled successfully
240+
Preview: Shows chart
241+
```
242+
243+
## Out of Scope (V1)
244+
245+
- Version history / snapshots
246+
- Multiple files / project structure
247+
- Collaboration / sharing
248+
- Advanced linting
249+
- Runtime error auto-injection
250+
- Conversation summarization
251+
- Component-specific editing tools
252+
253+
These can be added in v2+ based on user feedback.
254+
255+
## Success Criteria
256+
257+
- User can create functional Tsunami app through chat in <5 minutes
258+
- AI successfully fixes its own compilation errors 80%+ of the time
259+
- Iteration cycle (message → edit → preview) takes <10 seconds
260+
- Users can publish working apps to Wave Terminal
261+
- Draft state persists across sessions

emain/emain-builder.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2025, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { ClientService } from "@/app/store/services";
5+
import { RpcApi } from "@/app/store/wshclientapi";
6+
import { randomUUID } from "crypto";
7+
import { BrowserWindow } from "electron";
8+
import { globalEvents } from "emain/emain-events";
9+
import path from "path";
10+
import { getElectronAppBasePath, isDevVite, unamePlatform } from "./emain-platform";
11+
import { calculateWindowBounds, MinWindowHeight, MinWindowWidth } from "./emain-window";
12+
import { ElectronWshClient } from "./emain-wsh";
13+
14+
export type BuilderWindowType = BrowserWindow & {
15+
builderId: string;
16+
savedInitOpts: BuilderInitOpts;
17+
};
18+
19+
const builderWindows: BuilderWindowType[] = [];
20+
export let focusedBuilderWindow: BuilderWindowType = null;
21+
22+
export function getBuilderWindowById(builderId: string): BuilderWindowType {
23+
return builderWindows.find((win) => win.builderId === builderId);
24+
}
25+
26+
export function getBuilderWindowByWebContentsId(webContentsId: number): BuilderWindowType {
27+
return builderWindows.find((win) => win.webContents.id === webContentsId);
28+
}
29+
30+
export function getAllBuilderWindows(): BuilderWindowType[] {
31+
return builderWindows;
32+
}
33+
34+
export async function createBuilderWindow(appId: string): Promise<BuilderWindowType> {
35+
const builderId = randomUUID();
36+
37+
const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient);
38+
const clientData = await ClientService.GetClientData();
39+
const clientId = clientData?.oid;
40+
const windowId = randomUUID();
41+
42+
const winBounds = calculateWindowBounds(undefined, undefined, fullConfig.settings);
43+
44+
const builderWindow = new BrowserWindow({
45+
x: winBounds.x,
46+
y: winBounds.y,
47+
width: winBounds.width,
48+
height: winBounds.height,
49+
minWidth: MinWindowWidth,
50+
minHeight: MinWindowHeight,
51+
titleBarStyle: unamePlatform === "darwin" ? "hiddenInset" : "default",
52+
icon:
53+
unamePlatform === "linux"
54+
? path.join(getElectronAppBasePath(), "public/logos/wave-logo-dark.png")
55+
: undefined,
56+
show: false,
57+
backgroundColor: "#222222",
58+
webPreferences: {
59+
preload: path.join(getElectronAppBasePath(), "preload", "index.cjs"),
60+
webviewTag: true,
61+
},
62+
});
63+
64+
if (isDevVite) {
65+
await builderWindow.loadURL(`${process.env.ELECTRON_RENDERER_URL}/index.html`);
66+
} else {
67+
await builderWindow.loadFile(path.join(getElectronAppBasePath(), "frontend", "index.html"));
68+
}
69+
70+
const initOpts: BuilderInitOpts = {
71+
builderId,
72+
clientId,
73+
windowId,
74+
appId,
75+
};
76+
77+
const typedBuilderWindow = builderWindow as BuilderWindowType;
78+
typedBuilderWindow.builderId = builderId;
79+
typedBuilderWindow.savedInitOpts = initOpts;
80+
81+
console.log("sending builder-init", initOpts);
82+
typedBuilderWindow.webContents.send("builder-init", initOpts);
83+
84+
typedBuilderWindow.on("focus", () => {
85+
focusedBuilderWindow = typedBuilderWindow;
86+
console.log("builder window focused", builderId);
87+
setTimeout(() => globalEvents.emit("windows-updated"), 50);
88+
});
89+
90+
typedBuilderWindow.on("blur", () => {
91+
if (focusedBuilderWindow === typedBuilderWindow) {
92+
focusedBuilderWindow = null;
93+
}
94+
setTimeout(() => globalEvents.emit("windows-updated"), 50);
95+
});
96+
97+
typedBuilderWindow.on("closed", () => {
98+
console.log("builder window closed", builderId);
99+
const index = builderWindows.indexOf(typedBuilderWindow);
100+
if (index !== -1) {
101+
builderWindows.splice(index, 1);
102+
}
103+
if (focusedBuilderWindow === typedBuilderWindow) {
104+
focusedBuilderWindow = null;
105+
}
106+
setTimeout(() => globalEvents.emit("windows-updated"), 50);
107+
});
108+
109+
builderWindows.push(typedBuilderWindow);
110+
typedBuilderWindow.show();
111+
112+
console.log("created builder window", builderId, appId);
113+
return typedBuilderWindow;
114+
}

0 commit comments

Comments
 (0)