|
1 | 1 | # Tsunami Framework Guide |
2 | 2 |
|
3 | | -Wave Terminal includes a powerful Tsunami framework that lets developers create rich HTML/React-based UI applications directly from Go code. The system translates Go components and elements into React components that are rendered within Wave Terminal's UI. It's particularly well-suited for administrative interfaces, monitoring dashboards, data visualization, configuration managers, and form-based applications where you want a graphical interface but don't need complex browser-side interactions. |
| 3 | +The Tsunami framework brings React-style UI development to Go, letting you build rich graphical applications that run inside Wave Terminal. If you know React, you already understand Tsunami's core concepts - it uses the same patterns for components, props, hooks, state management, and styling, but implemented entirely in Go. |
4 | 4 |
|
5 | | -This guide explains how to use the Tsunami framework (and corresponding VDOM, virtual DOM, component) to create interactive applications that run in Wave Terminal. While the patterns will feel familiar to React developers (components, props, hooks), the implementation is pure Go and takes advantage of Go's strengths like goroutines for async operations. Note that complex browser-side interactions like drag-and-drop, rich text editing, or heavy JavaScript functionality are not supported - the framework is designed for straightforward, practical applications. |
| 5 | +## React Patterns in Go |
6 | 6 |
|
7 | | -You'll learn how to: |
| 7 | +Tsunami mirrors React's developer experience: |
8 | 8 |
|
9 | | -- Create and compose components |
10 | | -- Manage state and handle events |
11 | | -- Work with styles and CSS |
12 | | -- Handle async operations with goroutines |
13 | | -- Create rich UIs that render in Wave Terminal |
| 9 | +- **Components**: Define reusable UI pieces with typed props structs |
| 10 | +- **JSX-like syntax**: Use `vdom.H()` to build element trees (like `React.createElement`) |
| 11 | +- **Hooks**: `UseState`, `UseEffect`, `UseRef` work exactly like React hooks |
| 12 | +- **Props and state**: Familiar patterns for data flow and updates |
| 13 | +- **Conditional rendering**: `vdom.If()` and `vdom.IfElse()` for dynamic UIs |
| 14 | +- **Event handling**: onClick, onChange, onKeyDown with React-like event objects |
| 15 | +- **Styling**: Built-in Tailwind v4 CSS classes, plus inline styles via `style` prop |
14 | 16 |
|
15 | | -The included todo-main.go provides a complete example application showing these patterns in action. |
| 17 | +The key difference: everything is pure Go code. No JavaScript, no build tools, no transpilation. You get React's mental model with Go's type safety, performance, and ecosystem. |
16 | 18 |
|
17 | | -## App Setup and Registration |
| 19 | +## Built for AI Development |
18 | 20 |
|
19 | | -Tsunami applications define components using the default client: |
| 21 | +Tsunami is designed with AI code generation in mind. The framework maps directly to React concepts that AI models understand well: |
20 | 22 |
|
21 | 23 | ```go |
22 | | -// Components are defined using the default client |
23 | | -var MyComponent = app.DefineComponent("MyComponent", |
24 | | - func(ctx context.Context, props MyProps) any { |
25 | | - // component logic |
26 | | - }, |
| 24 | +// This feels like React JSX, but it's pure Go |
| 25 | +return vdom.H("div", map[string]any{ |
| 26 | + "className": "flex items-center gap-4 p-4", |
| 27 | +}, |
| 28 | + vdom.H("input", map[string]any{ |
| 29 | + "type": "checkbox", |
| 30 | + "checked": todo.Completed, |
| 31 | + "onChange": handleToggle, |
| 32 | + }), |
| 33 | + vdom.H("span", map[string]any{ |
| 34 | + "className": vdom.Classes("flex-1", vdom.If(todo.Completed, "line-through")), |
| 35 | + }, todo.Text), |
27 | 36 | ) |
| 37 | +``` |
| 38 | + |
| 39 | +AI models can leverage their React knowledge to generate Tsunami applications, while developers get the benefits of Go's concurrency, error handling, and type system. |
28 | 40 |
|
29 | | -// The main "App" component can set the app title |
| 41 | +## How It Works |
| 42 | + |
| 43 | +Tsunami applications run as Go programs that generate virtual DOM structures. Wave Terminal renders these as HTML/CSS in its interface, handling the React-like reconciliation and updates. You write Go code using familiar React patterns, and Wave Terminal handles the browser complexity. |
| 44 | + |
| 45 | +## Creating a Tsunami Application |
| 46 | + |
| 47 | +A Tsunami application is simply a Go package with an `App` component. Here's a minimal "Hello World" example: |
| 48 | + |
| 49 | +```go |
| 50 | +package main |
| 51 | + |
| 52 | +import ( |
| 53 | + "context" |
| 54 | + "github.com/wavetermdev/waveterm/tsunami/app" |
| 55 | + "github.com/wavetermdev/waveterm/tsunami/vdom" |
| 56 | +) |
| 57 | + |
| 58 | +// The App component is the required entry point for every Tsunami application |
30 | 59 | var App = app.DefineComponent("App", |
31 | 60 | func(ctx context.Context, _ struct{}) any { |
32 | | - vdom.UseSetAppTitle(ctx, "My Tsunami App") // Only works in top-level App component |
| 61 | + vdom.UseSetAppTitle(ctx, "Hello World") |
33 | 62 |
|
34 | | - return vdom.H("div", nil, |
35 | | - // app content |
36 | | - ) |
| 63 | + return vdom.H("div", map[string]any{ |
| 64 | + "className": "flex items-center justify-center h-screen text-xl font-bold", |
| 65 | + }, "Hello, Tsunami!") |
37 | 66 | }, |
38 | 67 | ) |
39 | 68 | ``` |
40 | 69 |
|
| 70 | +Key Points: |
| 71 | + |
| 72 | +- Must use package main. |
| 73 | +- The `App` component is required. It serves as the entry point to your application. |
| 74 | +- Do NOT add a `main()` function, that is provided by the framework when building. |
| 75 | +- Uses Tailwind v4 for styling - you can use any Tailwind classes in your components. |
| 76 | +- Use React-style camel case props (`className`, `onClick`) |
| 77 | + |
41 | 78 | ## Building Elements with vdom.H() |
42 | 79 |
|
43 | 80 | The H function creates virtual DOM elements following a React-like pattern. It takes a tag name, a props map, and any number of children: |
@@ -174,16 +211,69 @@ vdom.H("ul", nil, |
174 | 211 |
|
175 | 212 | Helper functions: |
176 | 213 |
|
177 | | -- `vdom.Fragment(...any)` - Combines elements into a group without adding a DOM node. Useful with conditional functions. |
178 | 214 | - `vdom.If(cond bool, part any) any` - Returns part if condition is true, nil otherwise |
179 | | -- `vdom.IfElse(cond bool, truePart any, falsePart any) any` - Returns truePart if condition is true, falsePart otherwise |
180 | | -- `vdom.ForEach[T any](items []T, fn func(T) any) []any` - Maps over items without index |
181 | | -- `vdom.ForEachIdx[T any](items []T, fn func(T, int) any) []any` - Maps over items with index |
182 | | -- `vdom.Filter[T any](items []T, fn func(T) bool) []T` - Filters items based on condition |
183 | | -- `vdom.FilterIdx[T any](items []T, fn func(T, int) bool) []T` - Filters items with index access |
184 | | - |
185 | | -- The same If/IfElse functions are used for both conditional rendering and conditional classes, always following the pattern of condition first, then value(s). |
186 | | -- Remember to use vdom.IfElse if you need a true ternary condition. vdom.If will return nil on false and does not allow a 3rd argument. |
| 215 | +- `vdom.IfElse(cond bool, part any, elsePart any) any` - Returns part if condition is true, elsePart otherwise |
| 216 | +- `vdom.Ternary[T any](cond bool, trueRtn T, falseRtn T) T` - Type-safe ternary operation, returns trueRtn if condition is true, falseRtn otherwise |
| 217 | +- `vdom.ForEach[T any](items []T, fn func(T, int) any) []any` - Maps over items with index, function receives item and index |
| 218 | +- `vdom.Classes(classes ...any) string` - Combines multiple class values into a single space-separated string, similar to JavaScript clsx library (accepts string, []string, and map[string]bool params) |
| 219 | + |
| 220 | +- The vdom.If and vdom.IfElse functions can be used for both conditional rendering of elements, conditional classes, and conditional props. |
| 221 | +- For vdom.If and vdom.IfElse, always follow the pattern of condition first (bool), then value(s). |
| 222 | +- Use vdom.IfElse for conditions that return different types, use Ternary when the return values are the same type. |
| 223 | + |
| 224 | +## Using Hooks in Tsunami |
| 225 | + |
| 226 | +Functions starting with `vdom.Use*` are hooks in Tsunami, following the exact same rules as React hooks. |
| 227 | + |
| 228 | +**Key Rules:** |
| 229 | + |
| 230 | +- ✅ Only call hooks inside `app.DefineComponent` functions |
| 231 | +- ✅ Always call hooks at the **top level** of your component function |
| 232 | +- ✅ Call hooks before any early returns or conditional logic |
| 233 | +- 🔴 Never call hooks inside loops, conditions, or after conditional returns |
| 234 | + |
| 235 | +```go |
| 236 | +var MyComponent = app.DefineComponent("MyComponent", |
| 237 | + func(ctx context.Context, props MyProps) any { |
| 238 | + // ✅ Good: hooks at top level |
| 239 | + count := vdom.UseState(ctx, 0) |
| 240 | + vdom.UseEffect(ctx, func() { /* effect */ }, nil) |
| 241 | + |
| 242 | + // Now safe to have conditional logic |
| 243 | + if someCondition { |
| 244 | + return vdom.H("div", nil, "Early return") |
| 245 | + } |
| 246 | + |
| 247 | + return vdom.H("div", nil, "Content") |
| 248 | + }, |
| 249 | +) |
| 250 | +``` |
| 251 | + |
| 252 | +**Common Hooks (React-like):** |
| 253 | + |
| 254 | +- `UseState[T any](ctx context.Context, initialVal T) (T, func(T), func(func(T) T))` - Component state management (React `useState`) |
| 255 | +- `UseEffect(ctx context.Context, fn func() func(), deps []any)` - Side effects after render (React `useEffect`) |
| 256 | +- `UseRef[T any](ctx context.Context, val T) *VDomSimpleRef[T]` - Mutable refs for arbitrary values (React `useRef`) |
| 257 | +- `UseVDomRef(ctx context.Context) *VDomRef` - DOM element references (React `useRef` for DOM elements) |
| 258 | +- `UseSetAppTitle(ctx context.Context, title string)` - Sets the application title (used in every app, only works in top-level "App" component) |
| 259 | + |
| 260 | +**Global Data Hooks (Jotai-like atoms):** |
| 261 | + |
| 262 | +- `UseSharedAtom[T any](ctx context.Context, atomName string) (T, func(T), func(func(T) T))` - Shared state across components |
| 263 | +- `UseConfig[T any](ctx context.Context, atomName string) (T, func(T), func(func(T) T))` - Access to global config values |
| 264 | +- `UseData[T any](ctx context.Context, atomName string) (T, func(T), func(func(T) T))` - Access to global data values |
| 265 | + |
| 266 | +These allow applications to easily share data between components. When an atom is updated, all components using it will re-render. |
| 267 | + |
| 268 | +**Specialty Hooks (less common):** |
| 269 | + |
| 270 | +- `UseId(ctx context.Context) string` - Component's unique identifier |
| 271 | +- `UseRenderTs(ctx context.Context) int64` - Current render timestamp |
| 272 | +- `UseResync(ctx context.Context) bool` - Whether current render is a resync operation |
| 273 | + |
| 274 | +Most applications won't need these specialty hooks, but they're available for advanced use cases. |
| 275 | + |
| 276 | +This ensures hooks are called in the same order every render, which is essential for Tsunami's state management. |
187 | 277 |
|
188 | 278 | ## Style Handling |
189 | 279 |
|
|
0 commit comments