Skip to content

Commit e7cd584

Browse files
authored
tsunami framework (waveapps v2) (#2315)
Huge PR. 135 commits here to rebuild waveapps into the "Tsunami" framework. * Simplified API * Updated system.md prompt * Basic applications building and running * /api/config and /api/data support * tailwind styling * no need for async updates * goroutine/timer primitives for async routing handling * POC for integrating 3rd party react frameworks (recharts) * POC for server side components (table.go) * POC for interacting with apps via /api/config (tsunamiconfig) Checkpoint. Still needs to be tightly integrated with Wave (lifecycle, AI interaction, etc.) but looking very promising 🚀
1 parent fb30d7f commit e7cd584

96 files changed

Lines changed: 21457 additions & 17 deletions

Some content is hidden

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

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"gopls": {
6060
"analyses": {
6161
"QF1003": false
62-
}
62+
},
63+
"directoryFilters": ["-tsunami/frontend/scaffold"]
6364
}
6465
}

Taskfile.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,90 @@ tasks:
438438
ignore_error: true
439439
- cmd: '{{.RMRF}} "dist"'
440440
ignore_error: true
441+
442+
tsunami:demo:todo:
443+
desc: Run the tsunami todo demo application
444+
cmd: go run demo/todo/*.go
445+
dir: tsunami
446+
env:
447+
TSUNAMI_LISTENADDR: "localhost:12026"
448+
449+
tsunami:frontend:dev:
450+
desc: Run the tsunami frontend vite dev server
451+
cmd: npm run dev
452+
dir: tsunami/frontend
453+
454+
tsunami:frontend:build:
455+
desc: Build the tsunami frontend
456+
cmd: yarn build
457+
dir: tsunami/frontend
458+
459+
tsunami:frontend:devbuild:
460+
desc: Build the tsunami frontend in development mode (with source maps and symbols)
461+
cmd: yarn build:dev
462+
dir: tsunami/frontend
463+
464+
tsunami:scaffold:
465+
desc: Build scaffold for tsunami frontend development
466+
deps:
467+
- tsunami:frontend:build
468+
cmds:
469+
- task: tsunami:scaffold:internal
470+
471+
tsunami:devscaffold:
472+
desc: Build scaffold for tsunami frontend development (with source maps and symbols)
473+
deps:
474+
- tsunami:frontend:devbuild
475+
cmds:
476+
- task: tsunami:scaffold:internal
477+
478+
tsunami:scaffold:internal:
479+
desc: Internal task to create scaffold directory structure
480+
dir: tsunami/frontend
481+
internal: true
482+
cmds:
483+
- cmd: "{{.RMRF}} scaffold"
484+
ignore_error: true
485+
- mkdir scaffold
486+
- cd scaffold && npm --no-workspaces init -y --init-license Apache-2.0
487+
- cd scaffold && npm pkg set name=tsunami-scaffold
488+
- cd scaffold && npm pkg delete author
489+
- cd scaffold && npm pkg set author.name="Command Line Inc"
490+
- cd scaffold && npm pkg set author.email="info@commandline.dev"
491+
- cd scaffold && npm --no-workspaces install tailwindcss @tailwindcss/cli
492+
- cp -r dist scaffold/
493+
- cp ../templates/app-main.go.tmpl scaffold/app-main.go
494+
- cp ../templates/tailwind.css scaffold/
495+
- cp ../templates/gitignore.tmpl scaffold/.gitignore
496+
497+
tsunami:build:
498+
desc: Build the tsunami binary.
499+
cmds:
500+
- cmd: "{{.RM}} bin/tsunami*"
501+
ignore_error: true
502+
- mkdir -p bin
503+
- 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
504+
sources:
505+
- "tsunami/**/*.go"
506+
- "tsunami/go.mod"
507+
- "tsunami/go.sum"
508+
generates:
509+
- "bin/tsunami{{exeExt}}"
510+
511+
tsunami:clean:
512+
desc: Clean tsunami frontend build artifacts
513+
dir: tsunami/frontend
514+
cmds:
515+
- cmd: "{{.RMRF}} dist"
516+
ignore_error: true
517+
- cmd: "{{.RMRF}} scaffold"
518+
ignore_error: true
519+
520+
godoc:
521+
desc: Start the Go documentation server for the root module
522+
cmd: $(go env GOPATH)/bin/pkgsite -http=:6060
523+
524+
tsunami:godoc:
525+
desc: Start the Go documentation server for the tsunami module
526+
cmd: $(go env GOPATH)/bin/pkgsite -http=:6060
527+
dir: tsunami

frontend/app/view/vdom/vdom.scss

Lines changed: 0 additions & 8 deletions
This file was deleted.

frontend/app/view/vdom/vdom.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
validateAndWrapCss,
1717
validateAndWrapReactStyle,
1818
} from "@/app/view/vdom/vdom-utils";
19-
import "./vdom.scss";
2019

2120
const TextTag = "#text";
2221
const FragmentTag = "#fragment";
@@ -506,7 +505,7 @@ function VDomView({ blockId, model }: VDomViewProps) {
506505
model.viewRef = viewRef;
507506
const vdomClass = "vdom-" + blockId;
508507
return (
509-
<div className={clsx("view-vdom", vdomClass)} ref={viewRef}>
508+
<div className={clsx("overflow-auto w-full min-h-full", vdomClass)} ref={viewRef}>
510509
{contextActive ? <VDomInnerView blockId={blockId} model={model} /> : null}
511510
</div>
512511
);

frontend/types/gotypes.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@ declare global {
837837
"debug:panictype"?: string;
838838
"block:view"?: string;
839839
"ai:backendtype"?: string;
840+
"ai:local"?: boolean;
840841
"wsh:cmd"?: string;
841842
"wsh:haderror"?: boolean;
842843
"conn:conntype"?: string;

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/wavetermdev/waveterm
22

3-
go 1.24.2
3+
go 1.24.6
44

55
require (
66
github.com/alexflint/go-filemutex v1.3.0

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
},
173173
"packageManager": "yarn@4.6.0",
174174
"workspaces": [
175-
"docs"
175+
"docs",
176+
"tsunami/frontend"
176177
]
177178
}

public/logos/wave-logo-256.png

9.56 KB
Loading

tsunami/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bin/

tsunami/app/atom.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2025, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package app
5+
6+
import (
7+
"log"
8+
"reflect"
9+
"runtime"
10+
11+
"github.com/wavetermdev/waveterm/tsunami/engine"
12+
"github.com/wavetermdev/waveterm/tsunami/util"
13+
)
14+
15+
// logInvalidAtomSet logs an error when an atom is being set during component render
16+
func logInvalidAtomSet(atomName string) {
17+
_, file, line, ok := runtime.Caller(2)
18+
if ok {
19+
log.Printf("invalid Set of atom '%s' in component render function at %s:%d", atomName, file, line)
20+
} else {
21+
log.Printf("invalid Set of atom '%s' in component render function", atomName)
22+
}
23+
}
24+
25+
// sameRef returns true if oldVal and newVal share the same underlying reference
26+
// (pointer, map, or slice). Nil values return false.
27+
func sameRef[T any](oldVal, newVal T) bool {
28+
vOld := reflect.ValueOf(oldVal)
29+
vNew := reflect.ValueOf(newVal)
30+
31+
if !vOld.IsValid() || !vNew.IsValid() {
32+
return false
33+
}
34+
35+
switch vNew.Kind() {
36+
case reflect.Ptr:
37+
// direct comparison works for *T
38+
return any(oldVal) == any(newVal)
39+
40+
case reflect.Map, reflect.Slice:
41+
if vOld.Kind() != vNew.Kind() || vOld.IsZero() || vNew.IsZero() {
42+
return false
43+
}
44+
return vOld.Pointer() == vNew.Pointer()
45+
}
46+
47+
// primitives, structs, etc. → not a reference type
48+
return false
49+
}
50+
51+
// logMutationWarning logs a warning when mutation is detected
52+
func logMutationWarning(atomName string) {
53+
_, file, line, ok := runtime.Caller(2)
54+
if ok {
55+
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)
56+
} else {
57+
log.Printf("WARNING: atom '%s' appears to be mutated instead of copied - use app.DeepCopy to create a copy before mutating", atomName)
58+
}
59+
}
60+
61+
// Atom[T] represents a typed atom implementation
62+
type Atom[T any] struct {
63+
name string
64+
client *engine.ClientImpl
65+
}
66+
67+
// AtomName implements the vdom.Atom interface
68+
func (a Atom[T]) AtomName() string {
69+
return a.name
70+
}
71+
72+
// Get returns the current value of the atom. When called during component render,
73+
// it automatically registers the component as a dependency for this atom, ensuring
74+
// the component re-renders when the atom value changes.
75+
func (a Atom[T]) Get() T {
76+
vc := engine.GetGlobalRenderContext()
77+
if vc != nil {
78+
vc.UsedAtoms[a.name] = true
79+
}
80+
val := a.client.Root.GetAtomVal(a.name)
81+
typedVal := util.GetTypedAtomValue[T](val, a.name)
82+
return typedVal
83+
}
84+
85+
// Set updates the atom's value to the provided new value and triggers re-rendering
86+
// of any components that depend on this atom. This method cannot be called during
87+
// render cycles - use effects or event handlers instead.
88+
func (a Atom[T]) Set(newVal T) {
89+
vc := engine.GetGlobalRenderContext()
90+
if vc != nil {
91+
logInvalidAtomSet(a.name)
92+
return
93+
}
94+
95+
// Check for potential mutation bugs with reference types
96+
currentVal := a.client.Root.GetAtomVal(a.name)
97+
currentTyped := util.GetTypedAtomValue[T](currentVal, a.name)
98+
if sameRef(currentTyped, newVal) {
99+
logMutationWarning(a.name)
100+
}
101+
102+
if err := a.client.Root.SetAtomVal(a.name, newVal); err != nil {
103+
log.Printf("Failed to set atom value for %s: %v", a.name, err)
104+
return
105+
}
106+
a.client.Root.AtomAddRenderWork(a.name)
107+
}
108+
109+
// SetFn updates the atom's value by applying the provided function to the current value.
110+
// The function receives a copy of the current atom value, which can be safely mutated
111+
// without affecting the original data. The return value from the function becomes the
112+
// new atom value. This method cannot be called during render cycles.
113+
func (a Atom[T]) SetFn(fn func(T) T) {
114+
vc := engine.GetGlobalRenderContext()
115+
if vc != nil {
116+
logInvalidAtomSet(a.name)
117+
return
118+
}
119+
currentVal := a.Get()
120+
copiedVal := DeepCopy(currentVal)
121+
newVal := fn(copiedVal)
122+
a.Set(newVal)
123+
}

0 commit comments

Comments
 (0)