From 6a4382b541d2812612b5a6abba64836149562ebd Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 14 Nov 2025 13:30:19 -0800 Subject: [PATCH 1/8] store appmeta instead of separate keys for tsunami metadata in rtinfo --- frontend/app/view/tsunami/tsunami.tsx | 10 +-- frontend/types/gotypes.d.ts | 5 +- package-lock.json | 4 +- pkg/aiusechat/tools_tsunami.go | 30 ++++++--- pkg/blockcontroller/tsunamicontroller.go | 81 ++++++------------------ pkg/waveobj/objrtinfo.go | 7 +- 6 files changed, 50 insertions(+), 87 deletions(-) diff --git a/frontend/app/view/tsunami/tsunami.tsx b/frontend/app/view/tsunami/tsunami.tsx index f55048428..84e5706e5 100644 --- a/frontend/app/view/tsunami/tsunami.tsx +++ b/frontend/app/view/tsunami/tsunami.tsx @@ -62,14 +62,8 @@ class TsunamiViewModel extends WebViewModel { oref: WOS.makeORef("block", blockId), }); initialRTInfo.then((rtInfo) => { - if (rtInfo) { - const meta: AppMeta = { - title: rtInfo["tsunami:title"], - shortdesc: rtInfo["tsunami:shortdesc"], - icon: rtInfo["tsunami:icon"], - iconcolor: rtInfo["tsunami:iconcolor"], - }; - globalStore.set(this.appMeta, meta); + if (rtInfo && rtInfo["tsunami:appmeta"]) { + globalStore.set(this.appMeta, rtInfo["tsunami:appmeta"]); } }); this.appMetaUnsubFn = waveEventSubscribe({ diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index a66a25bb2..8d4c67bd6 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -914,10 +914,7 @@ declare global { // waveobj.ObjRTInfo type ObjRTInfo = { - "tsunami:title"?: string; - "tsunami:shortdesc"?: string; - "tsunami:icon"?: string; - "tsunami:iconcolor"?: string; + "tsunami:appmeta"?: AppMeta; "tsunami:schemas"?: any; "shell:hascurcwd"?: boolean; "shell:state"?: string; diff --git a/package-lock.json b/package-lock.json index ec70442dd..6a62724f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.12.2", + "version": "0.12.3-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.12.2", + "version": "0.12.3-beta.1", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ diff --git a/pkg/aiusechat/tools_tsunami.go b/pkg/aiusechat/tools_tsunami.go index 239b0155a..590692fe5 100644 --- a/pkg/aiusechat/tools_tsunami.go +++ b/pkg/aiusechat/tools_tsunami.go @@ -13,7 +13,9 @@ import ( "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" "github.com/wavetermdev/waveterm/pkg/blockcontroller" + "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/waveobj" + "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wstore" ) @@ -25,8 +27,11 @@ func handleTsunamiBlockDesc(block *waveobj.Block) string { blockORef := waveobj.MakeORef(waveobj.OType_Block, block.OID) rtInfo := wstore.GetRTInfo(blockORef) - if rtInfo != nil && rtInfo.TsunamiShortDesc != "" { - return fmt.Sprintf("tsunami widget - %s", rtInfo.TsunamiShortDesc) + if rtInfo != nil && rtInfo.TsunamiAppMeta != nil { + var appMeta wshrpc.AppMeta + if err := utilfn.ReUnmarshal(&appMeta, rtInfo.TsunamiAppMeta); err == nil && appMeta.ShortDesc != "" { + return fmt.Sprintf("tsunami widget - %s", appMeta.ShortDesc) + } } return "tsunami widget - unknown description" } @@ -111,8 +116,11 @@ func GetTsunamiGetDataToolDefinition(block *waveobj.Block, rtInfo *waveobj.ObjRT toolName := fmt.Sprintf("tsunami_getdata_%s", blockIdPrefix) desc := "tsunami widget" - if rtInfo != nil && rtInfo.TsunamiShortDesc != "" { - desc = rtInfo.TsunamiShortDesc + if rtInfo != nil && rtInfo.TsunamiAppMeta != nil { + var appMeta wshrpc.AppMeta + if err := utilfn.ReUnmarshal(&appMeta, rtInfo.TsunamiAppMeta); err == nil && appMeta.ShortDesc != "" { + desc = appMeta.ShortDesc + } } return &uctypes.ToolDefinition{ @@ -136,8 +144,11 @@ func GetTsunamiGetConfigToolDefinition(block *waveobj.Block, rtInfo *waveobj.Obj toolName := fmt.Sprintf("tsunami_getconfig_%s", blockIdPrefix) desc := "tsunami widget" - if rtInfo != nil && rtInfo.TsunamiShortDesc != "" { - desc = rtInfo.TsunamiShortDesc + if rtInfo != nil && rtInfo.TsunamiAppMeta != nil { + var appMeta wshrpc.AppMeta + if err := utilfn.ReUnmarshal(&appMeta, rtInfo.TsunamiAppMeta); err == nil && appMeta.ShortDesc != "" { + desc = appMeta.ShortDesc + } } return &uctypes.ToolDefinition{ @@ -174,8 +185,11 @@ func GetTsunamiSetConfigToolDefinition(block *waveobj.Block, rtInfo *waveobj.Obj } desc := "tsunami widget" - if rtInfo != nil && rtInfo.TsunamiShortDesc != "" { - desc = rtInfo.TsunamiShortDesc + if rtInfo != nil && rtInfo.TsunamiAppMeta != nil { + var appMeta wshrpc.AppMeta + if err := utilfn.ReUnmarshal(&appMeta, rtInfo.TsunamiAppMeta); err == nil && appMeta.ShortDesc != "" { + desc = appMeta.ShortDesc + } } return &uctypes.ToolDefinition{ diff --git a/pkg/blockcontroller/tsunamicontroller.go b/pkg/blockcontroller/tsunamicontroller.go index 41daaf760..0bffe0b4e 100644 --- a/pkg/blockcontroller/tsunamicontroller.go +++ b/pkg/blockcontroller/tsunamicontroller.go @@ -5,18 +5,15 @@ package blockcontroller import ( "context" - "encoding/json" "fmt" "io" "log" - "net/http" "os" "os/exec" "path/filepath" "runtime" "sync" "syscall" - "time" "github.com/wavetermdev/waveterm/pkg/tsunamiutil" "github.com/wavetermdev/waveterm/pkg/utilds" @@ -51,37 +48,31 @@ type TsunamiController struct { port int } - -func (c *TsunamiController) fetchAndSetSchemas(port int) { - url := fmt.Sprintf("http://localhost:%d/api/schemas", port) - client := &http.Client{ - Timeout: 10 * time.Second, - } - - resp, err := client.Get(url) +func (c *TsunamiController) setManifestMetadata(appId string) { + manifest, err := waveappstore.ReadAppManifest(appId) if err != nil { - log.Printf("TsunamiController: failed to fetch schemas from %s: %v", url, err) - return - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - log.Printf("TsunamiController: received non-200 status %d from %s", resp.StatusCode, url) - return - } - - var schemas any - if err := json.NewDecoder(resp.Body).Decode(&schemas); err != nil { - log.Printf("TsunamiController: failed to decode schemas response: %v", err) return } blockRef := waveobj.MakeORef(waveobj.OType_Block, c.blockId) - wstore.SetRTInfo(blockRef, map[string]any{ - "tsunami:schemas": schemas, + rtInfo := make(map[string]any) + rtInfo["tsunami:appmeta"] = manifest.AppMeta + if manifest.ConfigSchema != nil || manifest.DataSchema != nil { + schemas := make(map[string]any) + if manifest.ConfigSchema != nil { + schemas["config"] = manifest.ConfigSchema + } + if manifest.DataSchema != nil { + schemas["data"] = manifest.DataSchema + } + rtInfo["tsunami:schemas"] = schemas + } + wstore.SetRTInfo(blockRef, rtInfo) + wps.Broker.Publish(wps.WaveEvent{ + Event: wps.Event_TsunamiUpdateMeta, + Scopes: []string{waveobj.MakeORef(waveobj.OType_Block, c.blockId).String()}, + Data: manifest.AppMeta, }) - - log.Printf("TsunamiController: successfully fetched and cached schemas for block %s", c.blockId) } func (c *TsunamiController) clearSchemas() { @@ -92,7 +83,6 @@ func (c *TsunamiController) clearSchemas() { log.Printf("TsunamiController: cleared schemas for block %s", c.blockId) } - func isBuildCacheUpToDate(appPath string) (bool, error) { appName := build.GetAppName(appPath) @@ -136,7 +126,7 @@ func (c *TsunamiController) Start(ctx context.Context, blockMeta waveobj.MetaMap appPath := blockMeta.GetString(waveobj.MetaKey_TsunamiAppPath, "") appId := blockMeta.GetString(waveobj.MetaKey_TsunamiAppId, "") - + if appPath == "" { if appId == "" { return fmt.Errorf("tsunami:apppath or tsunami:appid is required") @@ -157,32 +147,8 @@ func (c *TsunamiController) Start(ctx context.Context, blockMeta waveobj.MetaMap } } - // Read and set app metadata from manifest if appId is available if appId != "" { - if manifest, err := waveappstore.ReadAppManifest(appId); err == nil { - blockRef := waveobj.MakeORef(waveobj.OType_Block, c.blockId) - rtInfo := make(map[string]any) - if manifest.AppMeta.Title != "" { - rtInfo["tsunami:title"] = manifest.AppMeta.Title - } - if manifest.AppMeta.ShortDesc != "" { - rtInfo["tsunami:shortdesc"] = manifest.AppMeta.ShortDesc - } - if manifest.AppMeta.Icon != "" { - rtInfo["tsunami:icon"] = manifest.AppMeta.Icon - } - if manifest.AppMeta.IconColor != "" { - rtInfo["tsunami:iconcolor"] = manifest.AppMeta.IconColor - } - if len(rtInfo) > 0 { - wstore.SetRTInfo(blockRef, rtInfo) - wps.Broker.Publish(wps.WaveEvent{ - Event: wps.Event_TsunamiUpdateMeta, - Scopes: []string{waveobj.MakeORef(waveobj.OType_Block, c.blockId).String()}, - Data: manifest.AppMeta, - }) - } - } + c.setManifestMetadata(appId) } appName := build.GetAppName(appPath) @@ -248,11 +214,6 @@ func (c *TsunamiController) Start(ctx context.Context, blockMeta waveobj.MetaMap }) go c.sendStatusUpdate() - // Asynchronously fetch schemas after port is detected - go func() { - c.fetchAndSetSchemas(tsunamiProc.Port) - }() - // Monitor process completion go func() { <-tsunamiProc.WaitCh diff --git a/pkg/waveobj/objrtinfo.go b/pkg/waveobj/objrtinfo.go index fcbef2331..ff88f7090 100644 --- a/pkg/waveobj/objrtinfo.go +++ b/pkg/waveobj/objrtinfo.go @@ -4,11 +4,8 @@ package waveobj type ObjRTInfo struct { - TsunamiTitle string `json:"tsunami:title,omitempty"` - TsunamiShortDesc string `json:"tsunami:shortdesc,omitempty"` - TsunamiIcon string `json:"tsunami:icon,omitempty"` - TsunamiIconColor string `json:"tsunami:iconcolor,omitempty"` - TsunamiSchemas any `json:"tsunami:schemas,omitempty"` + TsunamiAppMeta any `json:"tsunami:appmeta,omitempty" tstype:"AppMeta"` + TsunamiSchemas any `json:"tsunami:schemas,omitempty"` ShellHasCurCwd bool `json:"shell:hascurcwd,omitempty"` ShellState string `json:"shell:state,omitempty"` From f9234e7aee5e7305b1084bc6d29f143a6909703a Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 14 Nov 2025 14:09:10 -0800 Subject: [PATCH 2/8] working on build fixes (tsunamiscaffold dir unpacked), and having a feature flag to show the waveapp builder. new env var to pass the Wave.app resources path --- electron-builder.config.cjs | 12 ++++++-- emain/emain-menu.ts | 12 +++++--- emain/emain-platform.ts | 10 ++++++ emain/emain-util.ts | 1 + emain/emain-wavesrv.ts | 9 +++++- frontend/app/workspace/widgets.tsx | 5 +-- frontend/types/gotypes.d.ts | 1 + pkg/aiusechat/tools_tsunami.go | 39 ++++++++++++------------ pkg/blockcontroller/tsunamicontroller.go | 1 + pkg/waveapputil/waveapputil.go | 4 +-- pkg/wavebase/wavebase.go | 8 +++++ pkg/wconfig/metaconsts.go | 2 ++ pkg/wconfig/settingsconfig.go | 2 ++ schema/settings.json | 3 ++ tsunami/build/build.go | 1 + 15 files changed, 77 insertions(+), 33 deletions(-) diff --git a/electron-builder.config.cjs b/electron-builder.config.cjs index a92d50961..76f7049c5 100644 --- a/electron-builder.config.cjs +++ b/electron-builder.config.cjs @@ -22,14 +22,20 @@ const config = { { from: "./dist", to: "./dist", - filter: ["**/*", "!bin/*", "bin/wavesrv.${arch}*", "bin/wsh*"], + filter: ["**/*", "!bin/*", "bin/wavesrv.${arch}*", "bin/wsh*", "!tsunamiscaffold/**/*"], }, { from: ".", to: ".", filter: ["package.json"], }, - "!node_modules", // We don't need electron-builder to package in Node modules as Vite has already bundled any code that our program is using. + "!/node_modules/**", // We don't need electron-builder to package in Node modules as Vite has already bundled any code that our program is using. + ], + extraResources: [ + { + from: "dist/tsunamiscaffold", + to: "tsunamiscaffold", + }, ], directories: { output: "make", @@ -37,7 +43,6 @@ const config = { asarUnpack: [ "dist/bin/**/*", // wavesrv and wsh binaries "dist/schema/**/*", // schema files for Monaco editor - "dist/tsunamiscaffold/**/*", // tsunami scaffold files ], mac: { target: [ @@ -116,6 +121,7 @@ const config = { provider: "generic", url: "https://dl.waveterm.dev/releases-w2", }, + blockmap: false, afterPack: (context) => { // This is a workaround to restore file permissions to the wavesrv binaries on macOS after packaging the universal binary. if (context.electronPlatformName === "darwin" && context.arch === Arch.universal) { diff --git a/emain/emain-menu.ts b/emain/emain-menu.ts index 347e4900f..36efa8ec6 100644 --- a/emain/emain-menu.ts +++ b/emain/emain-menu.ts @@ -110,7 +110,7 @@ function makeEditMenu(): Electron.MenuItemConstructorOptions[] { ]; } -function makeFileMenu(numWaveWindows: number, callbacks: AppMenuCallbacks): Electron.MenuItemConstructorOptions[] { +function makeFileMenu(numWaveWindows: number, callbacks: AppMenuCallbacks, fullConfig: FullConfigType): Electron.MenuItemConstructorOptions[] { const fileMenu: Electron.MenuItemConstructorOptions[] = [ { label: "New Window", @@ -125,7 +125,8 @@ function makeFileMenu(numWaveWindows: number, callbacks: AppMenuCallbacks): Elec }, }, ]; - if (isDev) { + const featureWaveAppBuilder = fullConfig?.settings?.["feature:waveappbuilder"]; + if (isDev || featureWaveAppBuilder) { fileMenu.splice(1, 0, { label: "New WaveApp Builder Window", accelerator: unamePlatform === "darwin" ? "Command+Shift+B" : "Alt+Shift+B", @@ -310,18 +311,19 @@ function makeViewMenu( async function makeFullAppMenu(callbacks: AppMenuCallbacks, workspaceOrBuilderId?: string): Promise { const numWaveWindows = getAllWaveWindows().length; const webContents = workspaceOrBuilderId && getWebContentsByWorkspaceOrBuilderId(workspaceOrBuilderId); - const fileMenu = makeFileMenu(numWaveWindows, callbacks); const appMenuItems = makeAppMenuItems(webContents); const editMenu = makeEditMenu(); const isBuilderWindowFocused = focusedBuilderWindow != null; let fullscreenOnLaunch = false; + let fullConfig: FullConfigType = null; try { - const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient); + fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient); fullscreenOnLaunch = fullConfig?.settings["window:fullscreenonlaunch"]; } catch (e) { - console.error("Error fetching fullscreen launch config:", e); + console.error("Error fetching config:", e); } + const fileMenu = makeFileMenu(numWaveWindows, callbacks, fullConfig); const viewMenu = makeViewMenu(webContents, callbacks, isBuilderWindowFocused, fullscreenOnLaunch); let workspaceMenu: Electron.MenuItemConstructorOptions[] = null; try { diff --git a/emain/emain-platform.ts b/emain/emain-platform.ts index 86b49a07e..32320e4eb 100644 --- a/emain/emain-platform.ts +++ b/emain/emain-platform.ts @@ -149,6 +149,7 @@ function getWaveDataDir(): string { } function getElectronAppBasePath(): string { + // import.meta.dirname in dev points to waveterm/dist/main return path.dirname(import.meta.dirname); } @@ -156,6 +157,14 @@ function getElectronAppUnpackedBasePath(): string { return getElectronAppBasePath().replace("app.asar", "app.asar.unpacked"); } +function getElectronAppResourcesPath(): string { + if (isDev) { + // import.meta.dirname in dev points to waveterm/dist/main + return path.dirname(import.meta.dirname); + } + return process.resourcesPath; +} + const wavesrvBinName = `wavesrv.${unameArch}`; function getWaveSrvPath(): string { @@ -261,6 +270,7 @@ export { callWithOriginalXdgCurrentDesktop, callWithOriginalXdgCurrentDesktopAsync, getElectronAppBasePath, + getElectronAppResourcesPath, getElectronAppUnpackedBasePath, getWaveConfigDir, getWaveDataDir, diff --git a/emain/emain-util.ts b/emain/emain-util.ts index 89ef975a5..712aeb52a 100644 --- a/emain/emain-util.ts +++ b/emain/emain-util.ts @@ -5,6 +5,7 @@ import * as electron from "electron"; import { getWebServerEndpoint } from "../frontend/util/endpoints"; export const WaveAppPathVarName = "WAVETERM_APP_PATH"; +export const WaveAppResourcesPathVarName = "WAVETERM_RESOURCES_PATH"; export const WaveAppElectronExecPath = "WAVETERM_ELECTRONEXECPATH"; export function getElectronExecPath(): string { diff --git a/emain/emain-wavesrv.ts b/emain/emain-wavesrv.ts index 18f8c2eb1..8e9f176a5 100644 --- a/emain/emain-wavesrv.ts +++ b/emain/emain-wavesrv.ts @@ -8,6 +8,7 @@ import { WebServerEndpointVarName, WSServerEndpointVarName } from "../frontend/u import { AuthKey, WaveAuthKeyEnv } from "./authkey"; import { setForceQuit } from "./emain-activity"; import { + getElectronAppResourcesPath, getElectronAppUnpackedBasePath, getWaveConfigDir, getWaveDataDir, @@ -17,7 +18,12 @@ import { WaveConfigHomeVarName, WaveDataHomeVarName, } from "./emain-platform"; -import { getElectronExecPath, WaveAppElectronExecPath, WaveAppPathVarName } from "./emain-util"; +import { + getElectronExecPath, + WaveAppElectronExecPath, + WaveAppPathVarName, + WaveAppResourcesPathVarName, +} from "./emain-util"; import { updater } from "./updater"; let isWaveSrvDead = false; @@ -59,6 +65,7 @@ export function runWaveSrv(handleWSEvent: (evtMsg: WSEventType) => void): Promis envCopy["XDG_CURRENT_DESKTOP"] = xdgCurrentDesktop; } envCopy[WaveAppPathVarName] = getElectronAppUnpackedBasePath(); + envCopy[WaveAppResourcesPathVarName] = getElectronAppResourcesPath(); envCopy[WaveAppElectronExecPath] = getElectronExecPath(); envCopy[WaveAuthKeyEnv] = AuthKey; envCopy[WaveDataHomeVarName] = getWaveDataDir(); diff --git a/frontend/app/workspace/widgets.tsx b/frontend/app/workspace/widgets.tsx index 0390614a7..82a849172 100644 --- a/frontend/app/workspace/widgets.tsx +++ b/frontend/app/workspace/widgets.tsx @@ -229,6 +229,7 @@ const Widgets = memo(() => { magnified: true, }; const showHelp = fullConfig?.settings?.["widget:showhelp"] ?? true; + const featureWaveAppBuilder = fullConfig?.settings?.["feature:waveappbuilder"] ?? false; const widgetsMap = fullConfig?.widgets ?? {}; const filteredWidgets = hasCustomAIPresets ? widgetsMap @@ -342,9 +343,9 @@ const Widgets = memo(() => { ))}
- {isDev() || showHelp ? ( + {isDev() || featureWaveAppBuilder || showHelp ? (
- {isDev() ? ( + {isDev() || featureWaveAppBuilder ? (
Date: Fri, 14 Nov 2025 14:13:40 -0800 Subject: [PATCH 3/8] blockmap: false did not work, remove --- electron-builder.config.cjs | 1 - 1 file changed, 1 deletion(-) diff --git a/electron-builder.config.cjs b/electron-builder.config.cjs index 76f7049c5..ebff9514c 100644 --- a/electron-builder.config.cjs +++ b/electron-builder.config.cjs @@ -121,7 +121,6 @@ const config = { provider: "generic", url: "https://dl.waveterm.dev/releases-w2", }, - blockmap: false, afterPack: (context) => { // This is a workaround to restore file permissions to the wavesrv binaries on macOS after packaging the universal binary. if (context.electronPlatformName === "darwin" && context.arch === Arch.universal) { From 7657c577cab5334e2841a213faf7f968ef713ca4 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 14 Nov 2025 14:45:53 -0800 Subject: [PATCH 4/8] try to work around electron-builder packaging quirks --- Taskfile.yml | 2 ++ electron-builder.config.cjs | 2 +- tsunami/build/build.go | 28 ++++++++++++++-------------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 8b7cf332a..5b3e52043 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -536,6 +536,7 @@ tasks: - mkdir -p scaffold - cp ../templates/package.json.tmpl scaffold/package.json - cd scaffold && npm install + - mv scaffold/node_modules scaffold/nm - cp -r dist scaffold/ - mkdir -p scaffold/dist/tw - cp ../templates/app-main.go.tmpl scaffold/app-main.go @@ -556,6 +557,7 @@ tasks: - powershell New-Item -ItemType Directory -Force -Path scaffold - powershell Copy-Item -Path ../templates/package.json.tmpl -Destination scaffold/package.json - powershell -Command "Set-Location scaffold; npm install" + - powershell Move-Item -Path scaffold/node_modules -Destination scaffold/nm - powershell Copy-Item -Recurse -Force -Path dist -Destination scaffold/ - powershell New-Item -ItemType Directory -Force -Path scaffold/dist/tw - powershell Copy-Item -Path ../templates/app-main.go.tmpl -Destination scaffold/app-main.go diff --git a/electron-builder.config.cjs b/electron-builder.config.cjs index ebff9514c..d49f2da61 100644 --- a/electron-builder.config.cjs +++ b/electron-builder.config.cjs @@ -29,7 +29,7 @@ const config = { to: ".", filter: ["package.json"], }, - "!/node_modules/**", // We don't need electron-builder to package in Node modules as Vite has already bundled any code that our program is using. + "!node_modules", // We don't need electron-builder to package in Node modules as Vite has already bundled any code that our program is using. ], extraResources: [ { diff --git a/tsunami/build/build.go b/tsunami/build/build.go index 1c964fb15..7318fca0b 100644 --- a/tsunami/build/build.go +++ b/tsunami/build/build.go @@ -498,13 +498,13 @@ func verifyScaffoldFs(fsys fs.FS) error { return fmt.Errorf("package.json check failed: %w", err) } - // Check for node_modules directory - if err := isDirOrNotFoundFS(fsys, "node_modules"); err != nil { - return fmt.Errorf("node_modules directory check failed: %w", err) + // Check for nm directory + if err := isDirOrNotFoundFS(fsys, "nm"); err != nil { + return fmt.Errorf("nm (node_modules) directory check failed: %w", err) } - info, err = fs.Stat(fsys, "node_modules") + info, err = fs.Stat(fsys, "nm") if err != nil || !info.IsDir() { - return fmt.Errorf("node_modules directory must exist in scaffold") + return fmt.Errorf("nm (node_modules) directory must exist in scaffold") } return nil @@ -1075,33 +1075,33 @@ func ParseTsunamiPort(line string) int { func copyScaffoldFS(scaffoldFS fs.FS, destDir string, verbose bool, oc *OutputCapture) (int, error) { fileCount := 0 - // Handle node_modules directory - prefer symlink if possible, otherwise copy - if _, err := fs.Stat(scaffoldFS, "node_modules"); err == nil { + // Handle nm (node_modules) directory - prefer symlink if possible, otherwise copy + if _, err := fs.Stat(scaffoldFS, "nm"); err == nil { destPath := filepath.Join(destDir, "node_modules") // Try to create symlink if we have DirFS if dirFS, ok := scaffoldFS.(DirFS); ok { - srcPath := dirFS.JoinOS("node_modules") + srcPath := dirFS.JoinOS("nm") if err := os.Symlink(srcPath, destPath); err != nil { - return 0, fmt.Errorf("failed to create symlink for node_modules: %w", err) + return 0, fmt.Errorf("failed to create symlink for nm (node_modules): %w", err) } if verbose { - oc.Printf("[debug] Symlinked node_modules directory") + oc.Printf("[debug] Symlinked nm to node_modules directory") } fileCount++ } else { // Fallback to recursive copy - dirCount, err := copyDirFromFS(scaffoldFS, "node_modules", destPath, false) + dirCount, err := copyDirFromFS(scaffoldFS, "nm", destPath, false) if err != nil { - return 0, fmt.Errorf("failed to copy node_modules directory: %w", err) + return 0, fmt.Errorf("failed to copy nm (node_modules) directory: %w", err) } if verbose { - oc.Printf("Copied node_modules directory (%d files)", dirCount) + oc.Printf("Copied nm to node_modules directory (%d files)", dirCount) } fileCount += dirCount } } else if !os.IsNotExist(err) { - return 0, fmt.Errorf("error checking node_modules: %w", err) + return 0, fmt.Errorf("error checking nm (node_modules): %w", err) } // Copy package files instead of symlinking From a1e1c3a3f88c00f0d769f5322470458255ed6ab5 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 14 Nov 2025 15:05:18 -0800 Subject: [PATCH 5/8] update release notes about thinking mode toggle --- docs/docs/releasenotes.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/releasenotes.mdx b/docs/docs/releasenotes.mdx index a992b3621..eb51b43aa 100644 --- a/docs/docs/releasenotes.mdx +++ b/docs/docs/releasenotes.mdx @@ -12,6 +12,7 @@ Patch release with Wave AI model upgrade, new secret management features, and im **Wave AI Updates:** - **GPT-5.1 Model** - Upgraded to use OpenAI's GPT-5.1 model for improved responses +- **Thinking Mode Toggle** - New dropdown to select between Quick, Balanced, and Deep thinking modes for optimal response quality vs speed - [bugfix] Fixed path mismatch issue when restoring AI write file backups **New Features:** From 91f719c09771123604c1dab4bf2673d9caf6004c Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 14 Nov 2025 15:05:50 -0800 Subject: [PATCH 6/8] use resolved go path, not "go" when running go commands... --- tsunami/build/build.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tsunami/build/build.go b/tsunami/build/build.go index 7318fca0b..05f1ba5a3 100644 --- a/tsunami/build/build.go +++ b/tsunami/build/build.go @@ -109,6 +109,7 @@ func GetAppName(appPath string) string { type BuildEnv struct { GoVersion string + GoPath string TempDir string cleanupOnce *sync.Once } @@ -329,11 +330,12 @@ func verifyEnvironment(verbose bool, opts BuildOpts) (*BuildEnv, error) { return &BuildEnv{ GoVersion: goVersion, + GoPath: result.GoPath, cleanupOnce: &sync.Once{}, }, nil } -func createGoMod(tempDir, appNS, appName, goVersion string, opts BuildOpts, verbose bool) error { +func createGoMod(tempDir, appNS, appName string, buildEnv *BuildEnv, opts BuildOpts, verbose bool) error { oc := opts.OutputCapture if appNS == "" { appNS = "app" @@ -372,7 +374,7 @@ func createGoMod(tempDir, appNS, appName, goVersion string, opts BuildOpts, verb return fmt.Errorf("failed to add module statement: %w", err) } - if err := modFile.AddGoStmt(goVersion); err != nil { + if err := modFile.AddGoStmt(buildEnv.GoVersion); err != nil { return fmt.Errorf("failed to add go version: %w", err) } @@ -412,7 +414,7 @@ func createGoMod(tempDir, appNS, appName, goVersion string, opts BuildOpts, verb } // Run go mod tidy to clean up dependencies - tidyCmd := exec.Command("go", "mod", "tidy") + tidyCmd := exec.Command(buildEnv.GoPath, "mod", "tidy") tidyCmd.Dir = tempDir if verbose { @@ -428,6 +430,7 @@ func createGoMod(tempDir, appNS, appName, goVersion string, opts BuildOpts, verb } if err := tidyCmd.Run(); err != nil { + oc.Flush() return fmt.Errorf("go mod tidy failed (see output for errors)") } @@ -653,7 +656,7 @@ func TsunamiBuildInternal(opts BuildOpts) (*BuildEnv, error) { // Create go.mod file appName := GetAppName(opts.AppPath) - if err := createGoMod(tempDir, opts.AppNS, appName, buildEnv.GoVersion, opts, opts.Verbose); err != nil { + if err := createGoMod(tempDir, opts.AppNS, appName, buildEnv, opts, opts.Verbose); err != nil { return buildEnv, err } @@ -663,7 +666,7 @@ func TsunamiBuildInternal(opts BuildOpts) (*BuildEnv, error) { } // Build the Go application - outputPath, err := runGoBuild(tempDir, opts) + outputPath, err := runGoBuild(tempDir, buildEnv, opts) if err != nil { return buildEnv, err } @@ -744,7 +747,7 @@ func moveFilesBack(tempDir, originalDir string, verbose bool, oc *OutputCapture) return nil } -func runGoBuild(tempDir string, opts BuildOpts) (string, error) { +func runGoBuild(tempDir string, buildEnv *BuildEnv, opts BuildOpts) (string, error) { oc := opts.OutputCapture var outputPath string var absOutputPath string @@ -776,7 +779,7 @@ func runGoBuild(tempDir string, opts BuildOpts) (string, error) { // Build command with explicit go files args := append([]string{"build", "-o", outputPath}, ".") - buildCmd := exec.Command("go", args...) + buildCmd := exec.Command(buildEnv.GoPath, args...) buildCmd.Dir = tempDir if oc != nil || opts.Verbose { From 669d8b84af4bf944d32fd3ffa3b633d4de8a4871 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 14 Nov 2025 15:37:27 -0800 Subject: [PATCH 7/8] preserve symlinks --- tsunami/build/build.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsunami/build/build.go b/tsunami/build/build.go index 05f1ba5a3..9305e2acc 100644 --- a/tsunami/build/build.go +++ b/tsunami/build/build.go @@ -842,7 +842,8 @@ func generateAppTailwindCss(tempDir string, verbose bool, opts BuildOpts) error oc := opts.OutputCapture // tailwind.css is already in tempDir from scaffold copy tailwindOutput := filepath.Join(tempDir, "static", "tw.css") - tailwindCmd := exec.Command(opts.getNodePath(), "node_modules/@tailwindcss/cli/dist/index.mjs", + tailwindCmd := exec.Command(opts.getNodePath(), "--preserve-symlinks-main", "--preserve-symlinks", + "node_modules/@tailwindcss/cli/dist/index.mjs", "-i", "./tailwind.css", "-o", tailwindOutput) tailwindCmd.Dir = tempDir From b41b7da1d175c0947bfa040cf72e7b4306a04292 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 14 Nov 2025 16:25:11 -0800 Subject: [PATCH 8/8] fix issues (symlink + widget isdev) --- frontend/app/workspace/widgets.tsx | 4 ++-- tsunami/build/build.go | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/frontend/app/workspace/widgets.tsx b/frontend/app/workspace/widgets.tsx index 82a849172..100c9f686 100644 --- a/frontend/app/workspace/widgets.tsx +++ b/frontend/app/workspace/widgets.tsx @@ -373,7 +373,7 @@ const Widgets = memo(() => { ))}
- {isDev() ? ( + {isDev() || featureWaveAppBuilder ? (
{
) : null}
- {isDev() && appsButtonRef.current && ( + {(isDev() || featureWaveAppBuilder) && appsButtonRef.current && ( setIsAppsOpen(false)} diff --git a/tsunami/build/build.go b/tsunami/build/build.go index 9305e2acc..ab1e8e203 100644 --- a/tsunami/build/build.go +++ b/tsunami/build/build.go @@ -1084,17 +1084,20 @@ func copyScaffoldFS(scaffoldFS fs.FS, destDir string, verbose bool, oc *OutputCa destPath := filepath.Join(destDir, "node_modules") // Try to create symlink if we have DirFS + symlinked := false if dirFS, ok := scaffoldFS.(DirFS); ok { srcPath := dirFS.JoinOS("nm") - if err := os.Symlink(srcPath, destPath); err != nil { - return 0, fmt.Errorf("failed to create symlink for nm (node_modules): %w", err) - } - if verbose { - oc.Printf("[debug] Symlinked nm to node_modules directory") + if err := os.Symlink(srcPath, destPath); err == nil { + if verbose { + oc.Printf("[debug] Symlinked nm to node_modules directory") + } + fileCount++ + symlinked = true } - fileCount++ - } else { - // Fallback to recursive copy + } + + // Fallback to recursive copy if symlink failed or not attempted + if !symlinked { dirCount, err := copyDirFromFS(scaffoldFS, "nm", destPath, false) if err != nil { return 0, fmt.Errorf("failed to copy nm (node_modules) directory: %w", err)