From c65d870ed9057b6bd257b5c4a13c1946d18b8b56 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 10:26:19 -0800 Subject: [PATCH 1/4] telemetry fixes (app:activity and app:startup) --- cmd/server/main-server.go | 19 ++++++++++++++++--- frontend/app/store/keymodel.ts | 12 ++++++------ frontend/types/gotypes.d.ts | 4 ++++ pkg/telemetry/telemetry.go | 3 +++ pkg/telemetry/telemetrydata/telemetrydata.go | 3 +++ 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index 1fd3a415bf..4e43c24531 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -247,6 +247,21 @@ func startupActivityUpdate(firstLaunch bool) { shellType = "error" shellVersion = "" } + userSetOnce := &telemetrydata.TEventUserProps{ + ClientInitialVersion: "v" + WaveVersion, + } + tosTs := telemetry.GetTosAgreedTs() + var cohortTime time.Time + if tosTs > 0 { + cohortTime = time.UnixMilli(tosTs) + } else { + cohortTime = time.Now() + } + cohortMonth := cohortTime.Format("2006-01") + year, week := cohortTime.ISOWeek() + cohortISOWeek := fmt.Sprintf("%04d-W%02d", year, week) + userSetOnce.CohortMonth = cohortMonth + userSetOnce.CohortISOWeek = cohortISOWeek props := telemetrydata.TEventProps{ UserSet: &telemetrydata.TEventUserProps{ ClientVersion: "v" + WaveVersion, @@ -259,9 +274,7 @@ func startupActivityUpdate(firstLaunch bool) { LocalShellType: shellType, LocalShellVersion: shellVersion, }, - UserSetOnce: &telemetrydata.TEventUserProps{ - ClientInitialVersion: "v" + WaveVersion, - }, + UserSetOnce: userSetOnce, } if firstLaunch { props.AppFirstLaunch = true diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index 0c9b037bd7..f02d002b42 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -152,16 +152,16 @@ function uxCloseBlock(blockId: string) { return; } } - + const blockAtom = WOS.getWaveObjectAtom(WOS.makeORef("block", blockId)); const blockData = globalStore.get(blockAtom); const isAIFileDiff = blockData?.meta?.view === "aifilediff"; - + const layoutModel = getLayoutModelForStaticTab(); const node = layoutModel.getNodeByBlockId(blockId); if (node) { fireAndForget(() => layoutModel.closeNode(node.id)); - + if (isAIFileDiff && isAIPanelOpen) { setTimeout(() => WaveAIModel.getInstance().focusInput(), 50); } @@ -199,16 +199,16 @@ function genericClose() { simpleCloseStaticTab(); return; } - + const layoutModel = getLayoutModelForStaticTab(); const focusedNode = globalStore.get(layoutModel.focusedNode); const blockId = focusedNode?.data?.blockId; const blockAtom = blockId ? WOS.getWaveObjectAtom(WOS.makeORef("block", blockId)) : null; const blockData = blockAtom ? globalStore.get(blockAtom) : null; const isAIFileDiff = blockData?.meta?.view === "aifilediff"; - + fireAndForget(layoutModel.closeFocusedNode.bind(layoutModel)); - + if (isAIFileDiff && isAIPanelOpen) { setTimeout(() => WaveAIModel.getInstance().focusInput(), 50); } diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 721f913cd6..d875847c81 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -1042,6 +1042,8 @@ declare global { "client:buildtime"?: string; "client:osrelease"?: string; "client:isdev"?: boolean; + "cohort:month"?: string; + "cohort:isoweek"?: string; "autoupdate:channel"?: string; "autoupdate:enabled"?: boolean; "localshell:type"?: string; @@ -1113,6 +1115,8 @@ declare global { "client:buildtime"?: string; "client:osrelease"?: string; "client:isdev"?: boolean; + "cohort:month"?: string; + "cohort:isoweek"?: string; "autoupdate:channel"?: string; "autoupdate:enabled"?: boolean; "localshell:type"?: string; diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 2d73bcfc73..7a14fc25c3 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -153,6 +153,9 @@ func mergeActivity(curActivity *telemetrydata.TEventProps, newActivity telemetry curActivity.OpenMinutes += newActivity.OpenMinutes curActivity.WaveAIActiveMinutes += newActivity.WaveAIActiveMinutes curActivity.WaveAIFgMinutes += newActivity.WaveAIFgMinutes + if newActivity.AppFirstDay { + curActivity.AppFirstDay = true + } } // ignores the timestamp in tevent, and uses the current time diff --git a/pkg/telemetry/telemetrydata/telemetrydata.go b/pkg/telemetry/telemetrydata/telemetrydata.go index f4984731d5..1ff89679e0 100644 --- a/pkg/telemetry/telemetrydata/telemetrydata.go +++ b/pkg/telemetry/telemetrydata/telemetrydata.go @@ -60,6 +60,9 @@ type TEventUserProps struct { ClientOSRelease string `json:"client:osrelease,omitempty"` ClientIsDev bool `json:"client:isdev,omitempty"` + CohortMonth string `json:"cohort:month,omitempty"` + CohortISOWeek string `json:"cohort:isoweek,omitempty"` + AutoUpdateChannel string `json:"autoupdate:channel,omitempty"` AutoUpdateEnabled bool `json:"autoupdate:enabled,omitempty"` From a1d0516d6e55e95d6c20fab6ef076db0b597111d Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 10:26:34 -0800 Subject: [PATCH 2/4] fix regression in term-model --- frontend/app/view/term/term-model.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/app/view/term/term-model.ts b/frontend/app/view/term/term-model.ts index 04cd78025f..83575261eb 100644 --- a/frontend/app/view/term/term-model.ts +++ b/frontend/app/view/term/term-model.ts @@ -3,6 +3,7 @@ import { BlockNodeModel } from "@/app/block/blocktypes"; +import { appHandleKeyDown } from "@/app/store/keymodel"; import { waveEventSubscribe } from "@/app/store/wps"; import { RpcApi } from "@/app/store/wshclientapi"; import { makeFeBlockRouteId } from "@/app/store/wshrouter"; @@ -528,6 +529,12 @@ export class TermViewModel implements ViewModel { this.forceRestartController(); return false; } + const appHandled = appHandleKeyDown(waveEvent); + if (appHandled) { + event.preventDefault(); + event.stopPropagation(); + return false; + } return true; } From aadaf3c7ddbec72c9eb0e0b288c0ca8136c8e365 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 11:04:32 -0800 Subject: [PATCH 3/4] implement cmd-f for preview directory --- .../app/view/preview/preview-directory.tsx | 20 +++++++++++++++---- frontend/app/view/preview/preview-model.tsx | 2 ++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/frontend/app/view/preview/preview-directory.tsx b/frontend/app/view/preview/preview-directory.tsx index 15a0e8fddb..87f358084e 100644 --- a/frontend/app/view/preview/preview-directory.tsx +++ b/frontend/app/view/preview/preview-directory.tsx @@ -109,6 +109,7 @@ function DirectoryTable({ newFile, newDirectory, }: DirectoryTableProps) { + const searchActive = useAtomValue(model.directorySearchActive); const fullConfig = useAtomValue(atoms.fullConfigAtom); const setErrorMsg = useSetAtom(model.errorMsgAtom); const getIconFromMimeType = useCallback( @@ -346,6 +347,7 @@ function TableBody({ setRefreshVersion, osRef, }: TableBodyProps) { + const searchActive = useAtomValue(model.directorySearchActive); const dummyLineRef = useRef(null); const warningBoxRef = useRef(null); const conn = useAtomValue(model.connection); @@ -447,12 +449,15 @@ function TableBody({ return (
- {search !== "" && ( -
- Searching for "{search}" + {(searchActive || search !== "") && ( +
+ {search === "" ? "Type to search (Esc to cancel)" : `Searching for "${search}"`}
setSearch("")} + onClick={() => { + setSearch(""); + globalStore.set(model.directorySearchActive, false); + }} > setFocusIndex(idx)} onContextMenu={(e) => handleFileContextMenu(e, row.original)} @@ -650,8 +656,13 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { useEffect(() => { model.directoryKeyDownHandler = (waveEvent: WaveKeyboardEvent): boolean => { + if (checkKeyPressed(waveEvent, "Cmd:f")) { + globalStore.set(model.directorySearchActive, true); + return true; + } if (checkKeyPressed(waveEvent, "Escape")) { setSearchText(""); + globalStore.set(model.directorySearchActive, false); return; } if (checkKeyPressed(waveEvent, "ArrowUp")) { @@ -676,6 +687,7 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { } model.goHistory(selectedPath); setSearchText(""); + globalStore.set(model.directorySearchActive, false); return true; } if (checkKeyPressed(waveEvent, "Backspace")) { diff --git a/frontend/app/view/preview/preview-model.tsx b/frontend/app/view/preview/preview-model.tsx index 2ab8f0b29e..a68585c8ae 100644 --- a/frontend/app/view/preview/preview-model.tsx +++ b/frontend/app/view/preview/preview-model.tsx @@ -160,6 +160,7 @@ export class PreviewModel implements ViewModel { showHiddenFiles: PrimitiveAtom; refreshVersion: PrimitiveAtom; + directorySearchActive: PrimitiveAtom; refreshCallback: () => void; directoryKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean; codeEditKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean; @@ -173,6 +174,7 @@ export class PreviewModel implements ViewModel { let showHiddenFiles = globalStore.get(getSettingsKeyAtom("preview:showhiddenfiles")) ?? true; this.showHiddenFiles = atom(showHiddenFiles); this.refreshVersion = atom(0); + this.directorySearchActive = atom(false); this.previewTextRef = createRef(); this.openFileModal = atom(false); this.openFileModalDelay = atom(false); From 70302d1ffab6807ae9853bab18a083d1ecca9736 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 11:15:30 -0800 Subject: [PATCH 4/4] move pprof control to a setting --- cmd/server/main-server.go | 30 ++++++++++++------------------ frontend/types/gotypes.d.ts | 3 +++ pkg/wconfig/metaconsts.go | 4 ++++ pkg/wconfig/settingsconfig.go | 16 ++++++++++------ schema/settings.json | 9 +++++++++ 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index 4e43c24531..b52d97491c 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -8,7 +8,6 @@ import ( "fmt" "log" "os" - "strconv" "runtime" "sync" @@ -352,18 +351,19 @@ func clearTempFiles() error { return nil } -func maybeStartPprofServer() error { - pprofPortStr := os.Getenv("WAVETERM_PPROFPORT") - if pprofPortStr == "" { - return nil +func maybeStartPprofServer() { + settings := wconfig.GetWatcher().GetFullConfig().Settings + if settings.DebugPprofMemProfileRate != nil { + runtime.MemProfileRate = *settings.DebugPprofMemProfileRate + log.Printf("set runtime.MemProfileRate to %d\n", runtime.MemProfileRate) } - defer os.Unsetenv("WAVETERM_PPROFPORT") - pprofPort, err := strconv.Atoi(pprofPortStr) - if err != nil { - return fmt.Errorf("invalid WAVETERM_PPROFPORT value '%s': %v", pprofPortStr, err) + if settings.DebugPprofPort == nil { + return } + pprofPort := *settings.DebugPprofPort if pprofPort < 1 || pprofPort > 65535 { - return fmt.Errorf("WAVETERM_PPROFPORT must be between 1 and 65535, got %d", pprofPort) + log.Printf("[error] debug:pprofport must be between 1 and 65535, got %d\n", pprofPort) + return } go func() { addr := fmt.Sprintf("localhost:%d", pprofPort) @@ -372,22 +372,15 @@ func maybeStartPprofServer() error { log.Printf("[error] pprof server failed: %v\n", err) } }() - return nil } func main() { - err := maybeStartPprofServer() - if err != nil { - log.Printf("[error] %v\n", err) - return - } - log.SetFlags(log.LstdFlags | log.Lmicroseconds) log.SetPrefix("[wavesrv] ") wavebase.WaveVersion = WaveVersion wavebase.BuildTime = BuildTime - err = grabAndRemoveEnvVars() + err := grabAndRemoveEnvVars() if err != nil { log.Printf("[error] %v\n", err) return @@ -476,6 +469,7 @@ func main() { sigutil.InstallShutdownSignalHandlers(doShutdown) sigutil.InstallSIGUSR1Handler() startConfigWatcher() + maybeStartPprofServer() go stdinReadWatch() go telemetryLoop() go updateTelemetryCountsLoop() diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index d875847c81..f132999b2b 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -977,6 +977,9 @@ declare global { "conn:*"?: boolean; "conn:askbeforewshinstall"?: boolean; "conn:wshenabled"?: boolean; + "debug:*"?: boolean; + "debug:pprofport"?: number; + "debug:pprofmemprofilerate"?: number; }; // waveobj.StickerClickOptsType diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index 9856a2ebe6..c49ce705f9 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -97,5 +97,9 @@ const ( ConfigKey_ConnClear = "conn:*" ConfigKey_ConnAskBeforeWshInstall = "conn:askbeforewshinstall" ConfigKey_ConnWshEnabled = "conn:wshenabled" + + ConfigKey_DebugClear = "debug:*" + ConfigKey_DebugPprofPort = "debug:pprofport" + ConfigKey_DebugPprofMemProfileRate = "debug:pprofmemprofilerate" ) diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 07ca15b864..7de5714dc8 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -143,6 +143,10 @@ type SettingsType struct { ConnClear bool `json:"conn:*,omitempty"` ConnAskBeforeWshInstall *bool `json:"conn:askbeforewshinstall,omitempty"` ConnWshEnabled bool `json:"conn:wshenabled,omitempty"` + + DebugClear bool `json:"debug:*,omitempty"` + DebugPprofPort *int `json:"debug:pprofport,omitempty"` + DebugPprofMemProfileRate *int `json:"debug:pprofmemprofilerate,omitempty"` } func (s *SettingsType) GetAiSettings() *AiSettingsType { @@ -333,7 +337,7 @@ func resolveEnvReplacements(m waveobj.MetaMapType) { if m == nil { return } - + for key, value := range m { switch v := value.(type) { case string: @@ -367,7 +371,7 @@ func resolveEnvValue(value string) (string, bool) { if !strings.HasPrefix(value, "$ENV:") { return "", false } - + envSpec := value[5:] // Remove "$ENV:" prefix parts := strings.SplitN(envSpec, ":", 2) envVar := parts[0] @@ -375,12 +379,12 @@ func resolveEnvValue(value string) (string, bool) { if len(parts) > 1 { fallback = parts[1] } - + // Get the environment variable value if envValue, exists := os.LookupEnv(envVar); exists { return envValue, true } - + // Return fallback if provided, otherwise return empty string if fallback != "" { return fallback, true @@ -414,12 +418,12 @@ func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.Meta } cerrs = append(cerrs, ConfigError{File: fileName, Err: err.Error()}) } - + // Resolve environment variable replacements if rtn != nil { resolveEnvReplacements(rtn) } - + return rtn, cerrs } diff --git a/schema/settings.json b/schema/settings.json index 89441a03e6..c83dc69e97 100644 --- a/schema/settings.json +++ b/schema/settings.json @@ -241,6 +241,15 @@ }, "conn:wshenabled": { "type": "boolean" + }, + "debug:*": { + "type": "boolean" + }, + "debug:pprofport": { + "type": "integer" + }, + "debug:pprofmemprofilerate": { + "type": "integer" } }, "additionalProperties": false,