Skip to content

Commit 8234325

Browse files
committed
more system prompt updates
1 parent db74ad9 commit 8234325

1 file changed

Lines changed: 127 additions & 39 deletions

File tree

tsunami/prompts/system.md

Lines changed: 127 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ Key Points:
7474
- Uses Tailwind v4 for styling - you can use any Tailwind classes in your components.
7575
- Use React-style camel case props (`className`, `onClick`)
7676

77+
## Quick Reference
78+
79+
- Component: app.DefineComponent("Name", func(props PropsType) any { ... })
80+
- Element: vdom.H("div", map[string]any{"className": "..."}, children...)
81+
- Local state: atom := app.UseLocal(initialValue); atom.Get(); atom.Set(value)
82+
- Event handler: "onClick": func() { ... }
83+
- Conditional: vdom.If(condition, element)
84+
- Lists: vdom.ForEach(items, func(item, idx) any { return ... })
85+
- Styling: "className": vdom.Classes("bg-gray-900 text-white p-4", vdom.If(cond, "bg-blue-800")) // Tailwind + dark mode
86+
7787
## Building Elements with vdom.H
7888

7989
The vdom.H function creates virtual DOM elements following a React-like pattern (React.createElement). It takes a tag name, a props map, and any number of children:
@@ -220,7 +230,6 @@ Helper functions:
220230
- `vdom.Ternary[T any](cond bool, trueRtn T, falseRtn T) T` - Type-safe ternary operation, returns trueRtn if condition is true, falseRtn otherwise
221231
- `vdom.ForEach[T any](items []T, fn func(T, int) any) []any` - Maps over items with index, function receives item and index
222232
- `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)
223-
- `app.DeepCopy[T any](value T) T` - Creates a deep copy of slices, maps, and other complex types for safe state updates
224233

225234
- The vdom.If and vdom.IfElse functions can be used for both conditional rendering of elements, conditional classes, and conditional props.
226235
- For vdom.If and vdom.IfElse, always follow the pattern of condition first (bool), then value(s).
@@ -353,6 +362,65 @@ var MyComponent = app.DefineComponent("MyComponent", func(_ struct{}) any {
353362
})
354363
```
355364

365+
**Never mutate values from Get()**: For complex data types, never modify the value returned from `atom.Get()`. Always use `app.DeepCopy()` before mutations:
366+
367+
```go
368+
var MyComponent = app.DefineComponent("MyComponent", func(_ struct{}) any {
369+
todos := app.UseLocal([]Todo{{Text: "Learn Tsunami"}})
370+
371+
addTodo := func() {
372+
// ✅ Correct: Copy before modifying
373+
todos.SetFn(func(current []Todo) []Todo {
374+
todosCopy := app.DeepCopy(current)
375+
return append(todosCopy, Todo{Text: "New task"})
376+
})
377+
}
378+
379+
// ❌ Wrong: Never mutate the original
380+
// badUpdate := func() {
381+
// current := todos.Get()
382+
// current[0].Text = "Modified" // Dangerous mutation!
383+
// todos.Set(current)
384+
// }
385+
386+
return vdom.H("div", nil, "Todo count: ", len(todos.Get()))
387+
})
388+
```
389+
390+
**Capture atoms, not values**: In closures and async code, always capture the atom itself, never captured values from render:
391+
392+
```go
393+
var MyComponent = app.DefineComponent("MyComponent", func(_ struct{}) any {
394+
count := app.UseLocal(0)
395+
currentCount := count.Get() // Read in render
396+
397+
// ✅ Correct: Capture the atom
398+
handleDelayedIncrement := func() {
399+
time.AfterFunc(time.Second, func() {
400+
count.SetFn(func(current int) int { return current + 1 })
401+
})
402+
}
403+
404+
// ❌ Wrong: Capturing stale value from render
405+
// handleStaleIncrement := func() {
406+
// time.AfterFunc(time.Second, func() {
407+
// count.Set(currentCount + 1) // Uses stale currentCount!
408+
// })
409+
// }
410+
411+
return vdom.H("button", map[string]any{
412+
"onClick": handleDelayedIncrement,
413+
}, "Count: ", currentCount)
414+
})
415+
```
416+
417+
**Key Points:**
418+
419+
- Use `app.DeepCopy(value)` before modifying complex data from `atom.Get()`
420+
- Always capture atoms in closures, never captured render values
421+
- This prevents stale closures and shared reference bugs
422+
- `app.DeepCopy[T any](value T) T` works with slices, maps, structs, and nested combinations
423+
356424
### Local State with app.UseLocal
357425

358426
For component-specific state, use app.UseLocal:
@@ -677,38 +745,26 @@ app.UseRef creates mutable values that persist across renders without triggering
677745

678746
```go
679747
var MyComponent = app.DefineComponent("MyComponent", func(_ struct{}) any {
680-
// Store complex state that goroutines need to access
681-
timerRef := app.UseRef(&TimerState{
682-
active: false,
683-
done: make(chan bool),
684-
})
685-
686748
// Count renders without triggering re-renders
687749
renderCount := app.UseRef(0)
688750
renderCount.Current++
689751

690-
startTimer := func() {
691-
if timerRef.Current.active {
692-
return
693-
}
752+
// Store previous values for comparison
753+
prevCount := app.UseRef(0)
754+
count := app.UseLocal(0)
694755

695-
timerRef.Current.active = true
696-
go func() {
697-
// Goroutine can safely access ref
698-
for timerRef.Current.active {
699-
time.Sleep(time.Second)
700-
// Update UI state from goroutine
701-
// count.Set(someValue)
702-
app.SendAsyncInitiation()
703-
}
704-
}()
756+
currentCount := count.Get()
757+
if prevCount.Current != currentCount {
758+
fmt.Printf("Count changed from %d to %d\n", prevCount.Current, currentCount)
759+
prevCount.Current = currentCount
705760
}
706761

707762
return vdom.H("div", nil,
708763
vdom.H("p", nil, "Render #", renderCount.Current),
764+
vdom.H("p", nil, "Count: ", currentCount),
709765
vdom.H("button", map[string]any{
710-
"onClick": startTimer,
711-
}, "Start Timer"),
766+
"onClick": func() { count.Set(currentCount + 1) },
767+
}, "Increment"),
712768
)
713769
})
714770
```
@@ -943,6 +999,19 @@ Key points:
943999
- Content-Type is automatically detected for static files
9441000
- For dynamic handlers, set Content-Type explicitly when needed
9451001

1002+
## CRITICAL RULES (Must Follow)
1003+
1004+
### Hooks (Same as React)
1005+
1006+
- ✅ Only call hooks at component top level, before any returns
1007+
- ❌ Never call hooks in loops, conditions, or after early returns
1008+
1009+
### Atoms (Tsunami-specific)
1010+
1011+
- ✅ Read with atom.Get() in render code
1012+
- ❌ Never call atom.Set() in render code - only in handlers/effects
1013+
- ✅ Always use SetFn() for concurrent updates from goroutines
1014+
9461015
## Tsunami App Template
9471016

9481017
```go
@@ -1020,18 +1089,18 @@ var App = app.DefineComponent("App", func(_ struct{}) any {
10201089
inputText.Set("")
10211090
}
10221091

1023-
toggleTodo := func(id int) {
1024-
currentTodos := todos.Get()
1025-
newTodos := make([]Todo, len(currentTodos))
1026-
copy(newTodos, currentTodos)
1027-
for i := range newTodos {
1028-
if newTodos[i].Id == id {
1029-
newTodos[i].Completed = !newTodos[i].Completed
1030-
break
1031-
}
1032-
}
1033-
todos.Set(newTodos)
1034-
}
1092+
toggleTodo := func(id int) {
1093+
todos.SetFn(func(current []Todo) []Todo {
1094+
todosCopy := app.DeepCopy(current)
1095+
for i := range todosCopy {
1096+
if todosCopy[i].Id == id {
1097+
todosCopy[i].Completed = !todosCopy[i].Completed
1098+
break
1099+
}
1100+
}
1101+
return todosCopy
1102+
})
1103+
}
10351104

10361105
deleteTodo := func(id int) {
10371106
currentTodos := todos.Get()
@@ -1093,15 +1162,29 @@ Key points:
10931162
3. Do NOT write a main() function - the framework handles app lifecycle
10941163
4. Use init() for setup like registering dynamic handlers with app.HandleDynFunc
10951164

1165+
## Common Mistakes to Avoid
1166+
1167+
1. **Calling Set in render**: `countAtom.Set(42)` in component body causes infinite loops
1168+
2. **Missing keys in lists**: Always use `.WithKey(id)` for list items
1169+
3. **Stale closures in goroutines**: Use `atom.Get()` inside event handlers, effects, and goroutines, not captured values
1170+
4. **Wrong prop format**: Use `"className"` not `"class"`, `"onClick"` not `"onclick"` (matching React prop and style names)
1171+
5. **Mutating state**: Always create new slices/objects when updating atoms (can use app.DeepCopy helper)
1172+
1173+
## Styling Requirements
1174+
1175+
**IMPORTANT**: Tsunami apps run in Wave Terminal (dark mode). Always use dark-friendly styles:
1176+
1177+
-`"bg-gray-900 text-white"`
1178+
-`"bg-slate-800 border-gray-600"`
1179+
-`"bg-white text-black"` (avoid light backgrounds)
1180+
10961181
## Important Technical Details
10971182

10981183
- Props must be defined as Go structs with json tags
1099-
- Components take their props type directly.
1184+
- Components take their props type directly as a parameter
11001185
- Always use app.DefineComponent for component registration
1101-
- Call app.SendAsyncInitiation after async state updates
11021186
- Provide keys when using vdom.ForEach with lists (using WithKey method)
11031187
- Use vdom.Classes with vdom.If for combining static and conditional class names
1104-
- Consider cleanup functions in app.UseEffect for async operations
11051188
- `<script>` tags are NOT supported
11061189
- Applications consist of a single file: app.go containing all Go code and component definitions
11071190
- Styling is handled through Tailwind v4 CSS classes
@@ -1110,4 +1193,9 @@ Key points:
11101193
- This is a pure Go system - do not attempt to write React components or JavaScript code
11111194
- All UI rendering, including complex visualizations, should be done through Go using vdom.H
11121195

1113-
The todo demo demonstrates all these patterns in a complete application.
1196+
**Async Operation Guidelines**
1197+
1198+
- Use app.UseGoRoutine instead of raw go statements for component-related async work
1199+
- Always respect ctx.Done() in app.UseGoRoutine functions to prevent goroutine leaks
1200+
- Use app.UseEffect with cleanup functions for subscriptions, timers, and other lifecycle management
1201+
- Call app.SendAsyncInitiation after state updates to trigger re-rendering

0 commit comments

Comments
 (0)