From 778681832e60fc3e317237bcf83f8f5ed5d05a8f Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 16 Dec 2025 14:35:27 -0800 Subject: [PATCH 1/6] ischatempty as an atom --- frontend/app/aipanel/aipanelinput.tsx | 12 +++++++++++- frontend/app/aipanel/waveai-model.tsx | 8 ++++---- frontend/app/store/keymodel.ts | 4 ++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/frontend/app/aipanel/aipanelinput.tsx b/frontend/app/aipanel/aipanelinput.tsx index 1d6ec3ce53..2ee65a609e 100644 --- a/frontend/app/aipanel/aipanelinput.tsx +++ b/frontend/app/aipanel/aipanelinput.tsx @@ -24,10 +24,20 @@ export interface AIPanelInputRef { export const AIPanelInput = memo(({ onSubmit, status, model }: AIPanelInputProps) => { const [input, setInput] = useAtom(model.inputAtom); const isFocused = useAtomValue(model.isWaveAIFocusedAtom); + const isChatEmpty = useAtomValue(model.isChatEmptyAtom); const textareaRef = useRef(null); const fileInputRef = useRef(null); const isPanelOpen = useAtomValue(model.getPanelVisibleAtom()); + let placeholder: string; + if (!isChatEmpty) { + placeholder = "Continue..."; + } else if (model.inBuilder) { + placeholder = "What would you like to build..."; + } else { + placeholder = "Ask Wave AI anything..."; + } + const resizeTextarea = useCallback(() => { const textarea = textareaRef.current; if (!textarea) return; @@ -141,7 +151,7 @@ export const AIPanelInput = memo(({ onSubmit, status, model }: AIPanelInputProps onKeyDown={handleKeyDown} onFocus={handleFocus} onBlur={handleBlur} - placeholder={model.inBuilder ? "What would you like to build..." : "Ask Wave AI anything..."} + placeholder={placeholder} className={cn( "w-full text-white px-2 py-2 pr-5 focus:outline-none resize-none overflow-auto bg-zinc-800/50" )} diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx index ded5057bd3..bbe4b095b1 100644 --- a/frontend/app/aipanel/waveai-model.tsx +++ b/frontend/app/aipanel/waveai-model.tsx @@ -66,7 +66,7 @@ export class WaveAIModel { codeBlockMaxWidth!: jotai.Atom; inputAtom: jotai.PrimitiveAtom = jotai.atom(""); isLoadingChatAtom: jotai.PrimitiveAtom = jotai.atom(false); - isChatEmpty: boolean = true; + isChatEmptyAtom: jotai.PrimitiveAtom = jotai.atom(true); isWaveAIFocusedAtom!: jotai.Atom; panelVisibleAtom!: jotai.Atom; restoreBackupModalToolCallId: jotai.PrimitiveAtom = jotai.atom(null) as jotai.PrimitiveAtom< @@ -271,7 +271,7 @@ export class WaveAIModel { this.useChatStop?.(); this.clearFiles(); this.clearError(); - this.isChatEmpty = true; + globalStore.set(this.isChatEmptyAtom, true); const newChatId = crypto.randomUUID(); globalStore.set(this.chatId, newChatId); @@ -450,7 +450,7 @@ export class WaveAIModel { try { const chatData = await RpcApi.GetWaveAIChatCommand(TabRpcClient, { chatid: chatIdValue }); const messages: UIMessage[] = chatData?.messages ?? []; - this.isChatEmpty = messages.length === 0; + globalStore.set(this.isChatEmptyAtom, messages.length === 0); return messages as WaveUIMessage[]; // this is safe just different RPC type vs the FE type, but they are compatible } catch (error) { console.error("Failed to load chat:", error); @@ -523,7 +523,7 @@ export class WaveAIModel { this.useChatSendMessage?.({ parts: uiMessageParts }); - this.isChatEmpty = false; + globalStore.set(this.isChatEmptyAtom, false); globalStore.set(this.inputAtom, ""); this.clearFiles(); } diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index 32b33abcf4..25f3712fea 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -146,7 +146,7 @@ function uxCloseBlock(blockId: string) { const isAIPanelOpen = workspaceLayoutModel.getAIPanelVisible(); if (isAIPanelOpen && getStaticTabBlockCount() === 1) { const aiModel = WaveAIModel.getInstance(); - const shouldSwitchToAI = !aiModel.isChatEmpty || aiModel.hasNonEmptyInput(); + const shouldSwitchToAI = !globalStore.get(aiModel.isChatEmptyAtom) || aiModel.hasNonEmptyInput(); if (shouldSwitchToAI) { replaceBlock(blockId, { meta: { view: "launcher" } }, false); setTimeout(() => WaveAIModel.getInstance().focusInput(), 50); @@ -184,7 +184,7 @@ function genericClose() { const isAIPanelOpen = workspaceLayoutModel.getAIPanelVisible(); if (isAIPanelOpen && getStaticTabBlockCount() === 1) { const aiModel = WaveAIModel.getInstance(); - const shouldSwitchToAI = !aiModel.isChatEmpty || aiModel.hasNonEmptyInput(); + const shouldSwitchToAI = !globalStore.get(aiModel.isChatEmptyAtom) || aiModel.hasNonEmptyInput(); if (shouldSwitchToAI) { const layoutModel = getLayoutModelForStaticTab(); const focusedNode = globalStore.get(layoutModel.focusedNode); From c8e4e8c575049f4e6060ff02f724bfc08b8dbef7 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 16 Dec 2025 14:35:39 -0800 Subject: [PATCH 2/6] update version --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa13af4079..9a81ab4bf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.13.0", + "version": "0.13.1-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.13.0", + "version": "0.13.1-beta.0", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ From a80178c88cb8ddf191ffe750a470f185ade6a0e5 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 16 Dec 2025 14:40:08 -0800 Subject: [PATCH 3/6] track clicks on conn dropdown --- frontend/app/block/blockutil.tsx | 3 ++- frontend/app/store/keymodel.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx index acec94916d..2deba1a8f4 100644 --- a/frontend/app/block/blockutil.tsx +++ b/frontend/app/block/blockutil.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { NumActiveConnColors } from "@/app/block/blockframe"; -import { getConnStatusAtom } from "@/app/store/global"; +import { getConnStatusAtom, recordTEvent } from "@/app/store/global"; import * as util from "@/util/util"; import clsx from "clsx"; import * as jotai from "jotai"; @@ -168,6 +168,7 @@ export const ConnectionButton = React.memo( const connColorNum = computeConnColorNum(connStatus); let color = `var(--conn-icon-color-${connColorNum})`; const clickHandler = function () { + recordTEvent("action:other", { "action:type": "conndropdown", "action:initiator": "mouse" }); setConnModalOpen(true); }; let titleText = null; diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index 25f3712fea..3ade22c748 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -15,6 +15,7 @@ import { getFocusedBlockId, getSettingsKeyAtom, globalStore, + recordTEvent, refocusNode, replaceBlock, WOS, @@ -580,6 +581,7 @@ function registerGlobalKeys() { globalKeyMap.set("Cmd:g", () => { const bcm = getBlockComponentModel(getFocusedBlockInStaticTab()); if (bcm.openSwitchConnection != null) { + recordTEvent("action:other", { "action:type": "conndropdown", "action:initiator": "keyboard" }); bcm.openSwitchConnection(); return true; } From fa3ae93dbfb4debf8addb40109a7be81e4f3d11b Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 16 Dec 2025 15:33:28 -0800 Subject: [PATCH 4/6] keybindings handled in waveconfig --- frontend/app/view/waveconfig/waveconfig.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/app/view/waveconfig/waveconfig.tsx b/frontend/app/view/waveconfig/waveconfig.tsx index 861fadeae2..3bb180f8f1 100644 --- a/frontend/app/view/waveconfig/waveconfig.tsx +++ b/frontend/app/view/waveconfig/waveconfig.tsx @@ -3,11 +3,13 @@ import { Tooltip } from "@/app/element/tooltip"; import { globalStore } from "@/app/store/jotaiStore"; +import { tryReinjectKey } from "@/app/store/keymodel"; import { CodeEditor } from "@/app/view/codeeditor/codeeditor"; import type { ConfigFile, WaveConfigViewModel } from "@/app/view/waveconfig/waveconfig-model"; -import { checkKeyPressed, keydownWrapper } from "@/util/keyutil"; +import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil"; import { cn } from "@/util/util"; import { useAtom, useAtomValue } from "jotai"; +import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; import { memo, useCallback, useEffect, useRef } from "react"; import { debounce } from "throttle-debounce"; @@ -108,6 +110,16 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps { model.editorRef.current = editor; + + editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => { + const waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent); + const handled = tryReinjectKey(waveEvent); + if (handled) { + e.stopPropagation(); + e.preventDefault(); + } + }); + const isFocused = globalStore.get(model.nodeModel.isFocused); if (isFocused) { editor.focus(); From 72a0a9aca1f54a5eba237e892cfbc6a1218f8ae8 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 16 Dec 2025 15:43:37 -0800 Subject: [PATCH 5/6] v0.13.1 onboarding screens --- frontend/app/onboarding/onboarding-common.tsx | 2 +- .../onboarding/onboarding-upgrade-patch.tsx | 7 ++ .../onboarding/onboarding-upgrade-v0131.tsx | 86 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 frontend/app/onboarding/onboarding-upgrade-v0131.tsx diff --git a/frontend/app/onboarding/onboarding-common.tsx b/frontend/app/onboarding/onboarding-common.tsx index 9371a21b20..cb3d892431 100644 --- a/frontend/app/onboarding/onboarding-common.tsx +++ b/frontend/app/onboarding/onboarding-common.tsx @@ -1,4 +1,4 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -export const CurrentOnboardingVersion = "v0.13.0"; +export const CurrentOnboardingVersion = "v0.13.1"; diff --git a/frontend/app/onboarding/onboarding-upgrade-patch.tsx b/frontend/app/onboarding/onboarding-upgrade-patch.tsx index ee1d7795db..cd8ad94709 100644 --- a/frontend/app/onboarding/onboarding-upgrade-patch.tsx +++ b/frontend/app/onboarding/onboarding-upgrade-patch.tsx @@ -18,6 +18,7 @@ import { UpgradeOnboardingModal_v0_12_1_Content } from "./onboarding-upgrade-v01 import { UpgradeOnboardingModal_v0_12_2_Content } from "./onboarding-upgrade-v0122"; import { UpgradeOnboardingModal_v0_12_3_Content } from "./onboarding-upgrade-v0123"; import { UpgradeOnboardingModal_v0_13_0_Content } from "./onboarding-upgrade-v0130"; +import { UpgradeOnboardingModal_v0_13_1_Content } from "./onboarding-upgrade-v0131"; interface VersionConfig { version: string; @@ -48,6 +49,12 @@ const versions: VersionConfig[] = [ version: "v0.13.0", content: () => , prevText: "Prev (v0.12.5)", + nextText: "Next (v0.13.1)", + }, + { + version: "v0.13.1", + content: () => , + prevText: "Prev (v0.13.0)", }, ]; diff --git a/frontend/app/onboarding/onboarding-upgrade-v0131.tsx b/frontend/app/onboarding/onboarding-upgrade-v0131.tsx new file mode 100644 index 0000000000..7584607384 --- /dev/null +++ b/frontend/app/onboarding/onboarding-upgrade-v0131.tsx @@ -0,0 +1,86 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +const UpgradeOnboardingModal_v0_13_1_Content = () => { + return ( +
+
+

+ Wave v0.13.1 focuses on Windows platform improvements, Wave AI visual updates, and enhanced + terminal navigation. +

+
+ +
+
+ +
+
+
+ Windows Platform Enhancements +
+
+
    +
  • + Integrated Window Layout - Cleaner interface with controls integrated + into the tab-bar header +
  • +
  • + Git Bash Auto-Detection - Automatically detects Git Bash installations +
  • +
  • + SSH Agent Fallback - Improved SSH agent support on Windows +
  • +
  • + Updated Focus Keybinding - Wave AI focus key changed to Alt:0 on + Windows +
  • +
+
+
+
+ +
+
+ +
+
+
Wave AI Updates
+
+
    +
  • + Refreshed Visual Design - Complete UI refresh with transparency + support for custom backgrounds +
  • +
  • + BYOK Without Telemetry - Wave AI now works with bring-your-own-key and + local models without requiring telemetry +
  • +
+
+
+
+ +
+
+ +
+
+
Terminal Improvements
+
+
    +
  • + New Scrolling Keybindings - Added Shift+Home, Shift+End, + Shift+PageUp, and Shift+PageDown for better navigation +
  • +
+
+
+
+
+ ); +}; + +UpgradeOnboardingModal_v0_13_1_Content.displayName = "UpgradeOnboardingModal_v0_13_1_Content"; + +export { UpgradeOnboardingModal_v0_13_1_Content }; \ No newline at end of file From 40be9adf640b34b822d60824515d7cbe741b7eff Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 16 Dec 2025 16:00:22 -0800 Subject: [PATCH 6/6] dispose keydown handler --- frontend/app/view/preview/preview-edit.tsx | 6 ++++-- frontend/app/view/waveconfig/waveconfig.tsx | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/app/view/preview/preview-edit.tsx b/frontend/app/view/preview/preview-edit.tsx index f2df68ba67..dd7d9f56ae 100644 --- a/frontend/app/view/preview/preview-edit.tsx +++ b/frontend/app/view/preview/preview-edit.tsx @@ -76,7 +76,7 @@ function CodeEditPreview({ model }: SpecializedViewProps) { function onMount(editor: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco): () => void { model.monacoRef.current = editor; - editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => { + const keyDownDisposer = editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => { const waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent); const handled = tryReinjectKey(waveEvent); if (handled) { @@ -90,7 +90,9 @@ function CodeEditPreview({ model }: SpecializedViewProps) { editor.focus(); } - return null; + return () => { + keyDownDisposer.dispose(); + }; } return ( diff --git a/frontend/app/view/waveconfig/waveconfig.tsx b/frontend/app/view/waveconfig/waveconfig.tsx index 3bb180f8f1..8eff903b3c 100644 --- a/frontend/app/view/waveconfig/waveconfig.tsx +++ b/frontend/app/view/waveconfig/waveconfig.tsx @@ -108,10 +108,10 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps { + (editor: MonacoTypes.editor.IStandaloneCodeEditor) => { model.editorRef.current = editor; - editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => { + const keyDownDisposer = editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => { const waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent); const handled = tryReinjectKey(waveEvent); if (handled) { @@ -125,6 +125,7 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps { + keyDownDisposer.dispose(); model.editorRef.current = null; }; },