-
Notifications
You must be signed in to change notification settings - Fork 475
tsunami framework (waveapps v2) #2315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 131 commits
a4def07
0bfffcc
6398a5f
819f3ab
cd8ae61
73b3527
ec54dc9
e58d726
fc9f1e1
d7934eb
73badad
1379d9a
432d333
41d04b7
e256dfd
336dc29
44f943c
53ff0c3
58eaf16
d3d6684
db9cfb1
89635ed
e29e47f
0f776ed
7bdad4b
45f94c3
4b9154e
e290924
be39586
12318a4
0cfa0f8
927be3f
a46558c
e9d0258
eabda0d
4c5c1af
7ae7cf8
35a98fe
bea4592
b5ac154
f57ace9
296ac34
013e4f4
5eb75b2
384699a
131a3ee
f8cbbe0
48b8580
aceff3a
f4e100b
2893a07
1068fc5
41ff2e1
bedfe33
6bff9aa
ce86ded
964ccec
3f01b49
0483923
ee30814
6c6e504
c416026
d7a4b5f
489285d
c2596cc
a75e474
bac641a
cfdff41
64bd41f
8674095
e49dfa1
2300333
a48c80b
731aaf8
638c0bd
6791461
bf4a417
2731633
7fa0786
352182e
40969b2
20a68e9
9dd37ed
d4f8687
58a7a77
dda17b4
ac85035
c8f8e1b
492c238
4fc183a
7b94e94
78c3391
cdf66cc
9ef0110
8c796c8
6e614aa
5ad3a14
393ce9a
db74ad9
8234325
77f8a00
3d18fa7
bb523ba
6bdbc63
28e79db
b6f12b3
af19bbf
99ea01d
60bd7c0
0a3fc8f
0e5f6b1
da8c718
ee13114
a7bab98
40b4ae4
5d3d6c6
beda3c4
ba5655f
f24b736
1c91358
c33fc7e
6cc1969
c3fa716
9f200f3
1bd88e8
9db6f91
094dbbe
f0305f6
14f2c99
388f338
fc03346
d54fd16
fb071a2
656eea2
82aec17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,7 @@ | |
"gopls": { | ||
"analyses": { | ||
"QF1003": false | ||
} | ||
}, | ||
"directoryFilters": ["-tsunami/frontend/scaffold"] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -438,3 +438,90 @@ tasks: | |
ignore_error: true | ||
- cmd: '{{.RMRF}} "dist"' | ||
ignore_error: true | ||
|
||
tsunami:demo:todo: | ||
desc: Run the tsunami todo demo application | ||
cmd: go run demo/todo/*.go | ||
dir: tsunami | ||
env: | ||
TSUNAMI_LISTENADDR: "localhost:12026" | ||
|
||
tsunami:frontend:dev: | ||
desc: Run the tsunami frontend vite dev server | ||
cmd: npm run dev | ||
dir: tsunami/frontend | ||
|
||
tsunami:frontend:build: | ||
desc: Build the tsunami frontend | ||
cmd: yarn build | ||
dir: tsunami/frontend | ||
|
||
tsunami:frontend:devbuild: | ||
desc: Build the tsunami frontend in development mode (with source maps and symbols) | ||
cmd: yarn build:dev | ||
dir: tsunami/frontend | ||
|
||
tsunami:scaffold: | ||
desc: Build scaffold for tsunami frontend development | ||
deps: | ||
- tsunami:frontend:build | ||
cmds: | ||
- task: tsunami:scaffold:internal | ||
|
||
tsunami:devscaffold: | ||
desc: Build scaffold for tsunami frontend development (with source maps and symbols) | ||
deps: | ||
- tsunami:frontend:devbuild | ||
cmds: | ||
- task: tsunami:scaffold:internal | ||
|
||
tsunami:scaffold:internal: | ||
desc: Internal task to create scaffold directory structure | ||
dir: tsunami/frontend | ||
internal: true | ||
cmds: | ||
- cmd: "{{.RMRF}} scaffold" | ||
ignore_error: true | ||
- mkdir scaffold | ||
- cd scaffold && npm --no-workspaces init -y --init-license Apache-2.0 | ||
- cd scaffold && npm pkg set name=tsunami-scaffold | ||
- cd scaffold && npm pkg delete author | ||
- cd scaffold && npm pkg set author.name="Command Line Inc" | ||
- cd scaffold && npm pkg set author.email="[email protected]" | ||
- cd scaffold && npm --no-workspaces install tailwindcss @tailwindcss/cli | ||
- cp -r dist scaffold/ | ||
- cp ../templates/app-main.go.tmpl scaffold/app-main.go | ||
- cp ../templates/tailwind.css scaffold/ | ||
- cp ../templates/gitignore.tmpl scaffold/.gitignore | ||
|
||
tsunami:build: | ||
desc: Build the tsunami binary. | ||
cmds: | ||
- cmd: "{{.RM}} bin/tsunami*" | ||
ignore_error: true | ||
- mkdir -p bin | ||
- cd tsunami && go build -ldflags "-X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.TsunamiVersion={{.VERSION}}" -o ../bin/tsunami{{exeExt}} cmd/main-tsunami.go | ||
sources: | ||
- "tsunami/**/*.go" | ||
- "tsunami/go.mod" | ||
- "tsunami/go.sum" | ||
generates: | ||
- "bin/tsunami{{exeExt}}" | ||
|
||
tsunami:clean: | ||
desc: Clean tsunami frontend build artifacts | ||
dir: tsunami/frontend | ||
cmds: | ||
- cmd: "{{.RMRF}} dist" | ||
ignore_error: true | ||
- cmd: "{{.RMRF}} scaffold" | ||
ignore_error: true | ||
|
||
godoc: | ||
desc: Start the Go documentation server for the root module | ||
cmd: $(go env GOPATH)/bin/pkgsite -http=:6060 | ||
|
||
tsunami:godoc: | ||
desc: Start the Go documentation server for the tsunami module | ||
cmd: $(go env GOPATH)/bin/pkgsite -http=:6060 | ||
dir: tsunami |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
module github.com/wavetermdev/waveterm | ||
|
||
go 1.24.2 | ||
go 1.24.6 | ||
|
||
require ( | ||
github.com/alexflint/go-filemutex v1.3.0 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -172,6 +172,7 @@ | |
}, | ||
"packageManager": "[email protected]", | ||
"workspaces": [ | ||
"docs" | ||
"docs", | ||
"tsunami/frontend" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
bin/ |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,123 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Copyright 2025, Command Line Inc. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
package app | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
"log" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
"reflect" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
"runtime" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/wavetermdev/waveterm/tsunami/engine" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/wavetermdev/waveterm/tsunami/util" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// logInvalidAtomSet logs an error when an atom is being set during component render | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func logInvalidAtomSet(atomName string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
_, file, line, ok := runtime.Caller(2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Printf("invalid Set of atom '%s' in component render function at %s:%d", atomName, file, line) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Printf("invalid Set of atom '%s' in component render function", atomName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// sameRef returns true if oldVal and newVal share the same underlying reference | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// (pointer, map, or slice). Nil values return false. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func sameRef[T any](oldVal, newVal T) bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
vOld := reflect.ValueOf(oldVal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
vNew := reflect.ValueOf(newVal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !vOld.IsValid() || !vNew.IsValid() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
switch vNew.Kind() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
case reflect.Ptr: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// direct comparison works for *T | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return any(oldVal) == any(newVal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
case reflect.Map, reflect.Slice: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if vOld.Kind() != vNew.Kind() || vOld.IsZero() || vNew.IsZero() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return vOld.Pointer() == vNew.Pointer() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+35
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix nil-pointer handling in sameRef for pointer types The docs say “Nil values return false,” but pointer case returns true for two nil pointers. Add an IsZero check before comparing. Apply this diff: case reflect.Ptr:
- // direct comparison works for *T
- return any(oldVal) == any(newVal)
+ // guard nils to honor "nil values return false"
+ if vOld.IsZero() || vNew.IsZero() {
+ return false
+ }
+ // direct comparison works for *T
+ return any(oldVal) == any(newVal) 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// primitives, structs, etc. → not a reference type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// logMutationWarning logs a warning when mutation is detected | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func logMutationWarning(atomName string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
_, file, line, ok := runtime.Caller(2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Printf("WARNING: atom '%s' appears to be mutated instead of copied at %s:%d - use app.DeepCopy to create a copy before mutating", atomName, file, line) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Printf("WARNING: atom '%s' appears to be mutated instead of copied - use app.DeepCopy to create a copy before mutating", atomName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Atom[T] represents a typed atom implementation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
type Atom[T any] struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
name string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
client *engine.ClientImpl | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// AtomName implements the vdom.Atom interface | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (a Atom[T]) AtomName() string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return a.name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Get returns the current value of the atom. When called during component render, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// it automatically registers the component as a dependency for this atom, ensuring | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// the component re-renders when the atom value changes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (a Atom[T]) Get() T { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
vc := engine.GetGlobalRenderContext() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if vc != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
vc.UsedAtoms[a.name] = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
val := a.client.Root.GetAtomVal(a.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
typedVal := util.GetTypedAtomValue[T](val, a.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return typedVal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Set updates the atom's value to the provided new value and triggers re-rendering | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// of any components that depend on this atom. This method cannot be called during | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// render cycles - use effects or event handlers instead. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (a Atom[T]) Set(newVal T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
vc := engine.GetGlobalRenderContext() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if vc != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
logInvalidAtomSet(a.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Check for potential mutation bugs with reference types | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
currentVal := a.client.Root.GetAtomVal(a.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
currentTyped := util.GetTypedAtomValue[T](currentVal, a.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if sameRef(currentTyped, newVal) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
logMutationWarning(a.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err := a.client.Root.SetAtomVal(a.name, newVal); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Printf("Failed to set atom value for %s: %v", a.name, err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
a.client.Root.AtomAddRenderWork(a.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
// SetFn updates the atom's value by applying the provided function to the current value. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// The function receives a copy of the current atom value, which can be safely mutated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// without affecting the original data. The return value from the function becomes the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
// new atom value. This method cannot be called during render cycles. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (a Atom[T]) SetFn(fn func(T) T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+109
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SetFn does not provide a safe copy; can mutate current state in place Comment promises “receives a copy … can be safely mutated,” but Apply this diff: func (a Atom[T]) SetFn(fn func(T) T) {
@@
- err := a.client.Root.SetFnAtomVal(a.name, func(val any) any {
- typedVal := util.GetTypedAtomValue[T](val, a.name)
- return fn(typedVal)
- })
+ err := a.client.Root.SetFnAtomVal(a.name, func(val any) any {
+ typedVal := util.GetTypedAtomValue[T](val, a.name)
+ // ensure user code mutates a copy, not the live state
+ copied := DeepCopy(typedVal)
+ result := fn(copied)
+ if sameRef(typedVal, result) {
+ logMutationWarning(a.name)
+ }
+ return result
+ }) If DeepCopy isn’t available for all T, adjust the comment to remove the “safe to mutate” claim and document that callers must copy first (but the above is preferred). Also applies to: 120-129 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
vc := engine.GetGlobalRenderContext() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
if vc != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
logInvalidAtomSet(a.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
currentVal := a.Get() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
copiedVal := DeepCopy(currentVal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
newVal := fn(copiedVal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
a.Set(newVal) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+109
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainSetFn is not atomic; risk of lost updates Get–transform–Set can lose concurrent updates. Do the read-modify-write atomically in engine (per-atom lock), and keep the DeepCopy + mutation-warning logic. Apply in this file: func (a Atom[T]) SetFn(fn func(T) T) {
vc := engine.GetGlobalRenderContext()
if vc != nil {
logInvalidAtomSet(a.name)
return
}
- currentVal := a.Get()
- copiedVal := DeepCopy(currentVal)
- newVal := fn(copiedVal)
- a.Set(newVal)
+ err := a.client.Root.SetFnAtomVal(a.name, func(val any) any {
+ typedVal := util.GetTypedAtomValue[T](val, a.name)
+ copied := DeepCopy(typedVal)
+ result := fn(copied)
+ if sameRef(typedVal, result) {
+ logMutationWarning(a.name)
+ }
+ return result
+ })
+ if err != nil {
+ log.Printf("Failed to SetFn atom %s: %v", a.name, err)
+ return
+ }
+ a.client.Root.AtomAddRenderWork(a.name)
} And in engine/rootelem.go (see comments there) reintroduce SetFnAtomVal and extend genAtom with SetFnVal(fn func(any) any) error, performing lookup under r.atomLock then calling atom.SetFnVal outside that lock. Please verify genAtom implementations already support SetFnVal. 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== repo root ==="
ls -la
echo "=== ripgrep for relevant symbols ==="
rg -n --hidden "SetFnAtomVal|SetFnVal|AtomAddRenderWork|AtomAddRender|genAtom|rootelem|atomLock|DeepCopy|GetTypedAtomValue|sameRef" -S || true
echo "=== show tsunami/app/atom.go (first 240 lines) ==="
if [ -f "tsunami/app/atom.go" ]; then
sed -n '1,240p' tsunami/app/atom.go
else
echo "tsunami/app/atom.go not found"
fi
echo "=== show engine/rootelem.go (first 400 lines) ==="
if [ -f "engine/rootelem.go" ]; then
sed -n '1,400p' engine/rootelem.go
else
echo "engine/rootelem.go not found"
fi
echo "=== list engine/ dir ==="
ls -la engine || true
echo "=== search for files named like gen*atom*.go or gen_*atom*.go ==="
rg -n --hidden "gen.*atom" -S || true
echo "=== search for methods named SetFnVal or SetFnVal signatures ==="
rg -n --hidden "func\s+\([^\)]*\)\s+SetFnVal\(" -S || true
echo "=== done ===" Length of output: 12395 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== show tsunami/engine/rootelem.go (first 400 lines) ==="
if [ -f "tsunami/engine/rootelem.go" ]; then
sed -n '1,400p' tsunami/engine/rootelem.go
else
echo "tsunami/engine/rootelem.go not found"
fi
echo
echo "=== search for genAtom and related methods across tsunami/ ==="
rg -n --hidden "type genAtom interface|SetAtomVal\(|GetAtomVal\(|SetFnVal\(|SetFnAtomVal\(|AtomAddRenderWork\(|AtomSetUsedBy\(" tsunami -S || true
echo
echo "=== search for implementations that might satisfy genAtom ==="
rg -n --hidden "struct .*Atom|func .*SetAtomVal\(|func .*SetFnVal\(" tsunami -S || true
echo
echo "=== show tsunami/util/util.go (GetTypedAtomValue) ==="
sed -n '1,240p' tsunami/util/util.go || true
echo
echo "=== done ===" Length of output: 16884 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== list tsunami/engine ==="
ls -la tsunami/engine || true
echo
echo "=== search for GetVal/SetVal/SetUsedBy/GetUsedBy implementations ==="
rg -n --hidden -S "func\s+\([^)]+\)\s+GetVal\(" tsunami || true
rg -n --hidden -S "func\s+\([^)]+\)\s+SetVal\(" tsunami || true
rg -n --hidden -S "func\s+\([^)]+\)\s+SetUsedBy\(" tsunami || true
rg -n --hidden -S "func\s+\([^)]+\)\s+GetUsedBy\(" tsunami || true
echo
echo "=== search for Atom struct types ==="
rg -n --hidden -S "type\s+\w*Atom\s+struct" tsunami || true
echo
echo "=== show files that implement genAtom by looking for SetVal/GetVal usages ==="
rg -n --hidden -S "SetVal\\(|GetVal\\(" tsunami -g '!**/node_modules/**' -C3 || true
echo
echo "=== done ===" Length of output: 1283 Make SetFn atomic — move read→modify→write into the engine
🤖 Prompt for AI Agents
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// Copyright 2025, Command Line Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package app | ||
|
||
import ( | ||
"encoding/json" | ||
"io/fs" | ||
"net/http" | ||
|
||
"github.com/wavetermdev/waveterm/tsunami/engine" | ||
"github.com/wavetermdev/waveterm/tsunami/vdom" | ||
) | ||
|
||
func DefineComponent[P any](name string, renderFn func(props P) any) vdom.Component[P] { | ||
return engine.DefineComponentEx(engine.GetDefaultClient(), name, renderFn) | ||
} | ||
|
||
func SetGlobalEventHandler(handler func(event vdom.VDomEvent)) { | ||
engine.GetDefaultClient().SetGlobalEventHandler(handler) | ||
} | ||
|
||
// RegisterSetupFn registers a single setup function that is called before the app starts running. | ||
// Only one setup function is allowed, so calling this will replace any previously registered | ||
// setup function. | ||
func RegisterSetupFn(fn func()) { | ||
engine.GetDefaultClient().RegisterSetupFn(fn) | ||
} | ||
|
||
// SendAsyncInitiation notifies the frontend that the backend has updated state | ||
// and requires a re-render. Normally the frontend calls the backend in response | ||
// to events, but when the backend changes state independently (e.g., from a | ||
// background process), this function gives the frontend a "nudge" to update. | ||
func SendAsyncInitiation() error { | ||
return engine.GetDefaultClient().SendAsyncInitiation() | ||
} | ||
|
||
func ConfigAtom[T any](name string, defaultValue T) Atom[T] { | ||
fullName := "$config." + name | ||
client := engine.GetDefaultClient() | ||
atom := engine.MakeAtomImpl(defaultValue) | ||
client.Root.RegisterAtom(fullName, atom) | ||
return Atom[T]{name: fullName, client: client} | ||
} | ||
|
||
func DataAtom[T any](name string, defaultValue T) Atom[T] { | ||
fullName := "$data." + name | ||
client := engine.GetDefaultClient() | ||
atom := engine.MakeAtomImpl(defaultValue) | ||
client.Root.RegisterAtom(fullName, atom) | ||
return Atom[T]{name: fullName, client: client} | ||
} | ||
|
||
func SharedAtom[T any](name string, defaultValue T) Atom[T] { | ||
fullName := "$shared." + name | ||
client := engine.GetDefaultClient() | ||
atom := engine.MakeAtomImpl(defaultValue) | ||
client.Root.RegisterAtom(fullName, atom) | ||
return Atom[T]{name: fullName, client: client} | ||
} | ||
|
||
// HandleDynFunc registers a dynamic HTTP handler function with the internal http.ServeMux. | ||
// The pattern MUST start with "/dyn/" to be valid. This allows registration of dynamic | ||
// routes that can be handled at runtime. | ||
func HandleDynFunc(pattern string, fn func(http.ResponseWriter, *http.Request)) { | ||
engine.GetDefaultClient().HandleDynFunc(pattern, fn) | ||
} | ||
|
||
// RunMain is used internally by generated code and should not be called directly. | ||
func RunMain() { | ||
engine.GetDefaultClient().RunMain() | ||
} | ||
|
||
// RegisterEmbeds is used internally by generated code and should not be called directly. | ||
func RegisterEmbeds(assetsFilesystem fs.FS, staticFilesystem fs.FS, manifest []byte) { | ||
client := engine.GetDefaultClient() | ||
client.AssetsFS = assetsFilesystem | ||
client.StaticFS = staticFilesystem | ||
client.ManifestFileBytes = manifest | ||
} | ||
|
||
// DeepCopy creates a deep copy of the input value using JSON marshal/unmarshal. | ||
// Panics on JSON errors. | ||
func DeepCopy[T any](v T) T { | ||
data, err := json.Marshal(v) | ||
if err != nil { | ||
panic(err) | ||
} | ||
var result T | ||
err = json.Unmarshal(data, &result) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return result | ||
} | ||
|
||
// QueueRefOp queues a reference operation to be executed on the DOM element. | ||
// Operations include actions like "focus", "scrollIntoView", etc. | ||
// If the ref is nil or not current, the operation is ignored. | ||
// This function must be called within a component context. | ||
func QueueRefOp(ref *vdom.VDomRef, op vdom.VDomRefOperation) { | ||
if ref == nil || !ref.HasCurrent { | ||
return | ||
} | ||
if op.RefId == "" { | ||
op.RefId = ref.RefId | ||
} | ||
client := engine.GetDefaultClient() | ||
client.Root.QueueRefOp(op) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Invalid go directive version; use major.minor and add toolchain
The go directive must not include a patch version. Use go 1.24 and specify the patch via the toolchain directive to ensure reproducible builds.
Apply:
Run to check for other modules with patch-level go directives:
🏁 Script executed:
Length of output: 148
🏁 Script executed:
Length of output: 110
🏁 Script executed:
Length of output: 312
Update all go.mod files to use major.minor go directive and add toolchain
Every go.mod in the repo (root and under tsunami/) currently has a patch-level directive (
go 1.24.6
), which is invalid. On line 3 of each file, replace:Apply this to:
📝 Committable suggestion
🤖 Prompt for AI Agents