diff --git a/emain/emain-window.ts b/emain/emain-window.ts
index 5f481e30f1..470ae7d968 100644
--- a/emain/emain-window.ts
+++ b/emain/emain-window.ts
@@ -1,4 +1,4 @@
-// Copyright 2025, Command Line Inc.
+// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { ClientService, ObjectService, WindowService, WorkspaceService } from "@/app/store/services";
@@ -167,6 +167,7 @@ export class WaveBrowserWindow extends BaseWindow {
winOpts.titleBarStyle = "hiddenInset";
winOpts.titleBarOverlay = false;
winOpts.autoHideMenuBar = !settings?.["window:showmenubar"];
+ winOpts.acceptFirstMouse = true;
if (isTransparent) {
winOpts.transparent = true;
} else if (isBlur) {
diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx
index 37c22709fe..b903380544 100644
--- a/frontend/app/aipanel/aipanel.tsx
+++ b/frontend/app/aipanel/aipanel.tsx
@@ -571,6 +571,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps
onDrop={handleDrop}
onClick={handleClick}
inert={!isPanelVisible ? true : undefined}
+ data-aipanel="true"
>
{(isDragOver || isReactDndDragOver) && allowAccess && }
diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx
index 581b8c1a9f..e9c70a35df 100644
--- a/frontend/app/app.tsx
+++ b/frontend/app/app.tsx
@@ -8,6 +8,7 @@ import {
getBlockBadgeAtom,
} from "@/app/store/badge";
import { ClientModel } from "@/app/store/client-model";
+import { FocusManager } from "@/app/store/focusManager";
import { GlobalModel } from "@/app/store/global-model";
import { globalStore } from "@/app/store/jotaiStore";
import { getTabModelByTabId, TabModelContext } from "@/app/store/tab-model";
@@ -16,7 +17,7 @@ import { makeWaveEnvImpl } from "@/app/waveenv/waveenvimpl";
import { Workspace } from "@/app/workspace/workspace";
import { getLayoutModelForStaticTab } from "@/layout/index";
import { ContextMenuModel } from "@/store/contextmenu";
-import { atoms, createBlock, getSettingsPrefixAtom } from "@/store/global";
+import { atoms, createBlock, getSettingsPrefixAtom, refocusNode } from "@/store/global";
import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel";
import { getElemAsStr } from "@/util/focusutil";
import * as keyutil from "@/util/keyutil";
@@ -203,6 +204,83 @@ function AppFocusHandler() {
return null;
}
+const MacOSFirstClickHandler = () => {
+ useEffect(() => {
+ if (PLATFORM !== "darwin") {
+ return;
+ }
+ let windowFocusTime: number = null;
+ let cancelNextClick = false;
+ const handleWindowFocus = (e: FocusEvent) => {
+ windowFocusTime = Date.now();
+ };
+ const getBlockIdFromTarget = (target: EventTarget): string => {
+ let elem = target as HTMLElement;
+ while (elem != null) {
+ const blockId = elem.dataset?.blockid;
+ if (blockId) {
+ return blockId;
+ }
+ elem = elem.parentElement;
+ }
+ return null;
+ };
+ const isAIPanelTarget = (target: EventTarget): boolean => {
+ let elem = target as HTMLElement;
+ while (elem != null) {
+ if (elem.dataset?.aipanel) {
+ return true;
+ }
+ elem = elem.parentElement;
+ }
+ return false;
+ };
+ const handleMouseDown = (e: MouseEvent) => {
+ const timeDiff = Date.now() - windowFocusTime;
+ if (windowFocusTime != null && timeDiff < 50) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ cancelNextClick = true;
+ const blockId = getBlockIdFromTarget(e.target);
+ if (blockId != null) {
+ setTimeout(() => {
+ console.log("macos first-click, focusing block", blockId);
+ refocusNode(blockId);
+ }, 10);
+ } else if (isAIPanelTarget(e.target)) {
+ setTimeout(() => {
+ console.log("macos first-click, focusing AI panel");
+ FocusManager.getInstance().setWaveAIFocused(true);
+ }, 10);
+ }
+ console.log("macos first-click detected, canceled", timeDiff + "ms");
+ return;
+ }
+ cancelNextClick = false;
+ };
+ const handleClick = (e: MouseEvent) => {
+ if (!cancelNextClick) {
+ return;
+ }
+ cancelNextClick = false;
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ console.log("macos first-click (click event) canceled");
+ };
+ window.addEventListener("focus", handleWindowFocus);
+ window.addEventListener("mousedown", handleMouseDown, true);
+ window.addEventListener("click", handleClick, true);
+ return () => {
+ window.removeEventListener("focus", handleWindowFocus);
+ window.removeEventListener("mousedown", handleMouseDown, true);
+ window.removeEventListener("click", handleClick, true);
+ };
+ }, []);
+ return null;
+};
+
const AppKeyHandlers = () => {
useEffect(() => {
const staticKeyDownHandler = keyutil.keydownWrapper(appHandleKeyDown);
@@ -300,6 +378,7 @@ const AppInner = () => {
onContextMenu={handleContextMenu}
>
+
diff --git a/frontend/app/store/focusManager.ts b/frontend/app/store/focusManager.ts
index e0c0c99832..58f78951ee 100644
--- a/frontend/app/store/focusManager.ts
+++ b/frontend/app/store/focusManager.ts
@@ -1,9 +1,12 @@
+// Copyright 2026, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
import { waveAIHasFocusWithin } from "@/app/aipanel/waveai-focus-utils";
import { WaveAIModel } from "@/app/aipanel/waveai-model";
-import { atoms, getBlockComponentModel } from "@/app/store/global";
+import { getBlockComponentModel } from "@/app/store/global";
import { globalStore } from "@/app/store/jotaiStore";
-import { focusedBlockId } from "@/util/focusutil";
import { getLayoutModelForStaticTab } from "@/layout/index";
+import { focusedBlockId } from "@/util/focusutil";
import { Atom, atom, type PrimitiveAtom } from "jotai";
export type FocusStrType = "node" | "waveai";
diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts
index 01d4ebbc96..92ffe7a59b 100644
--- a/frontend/app/store/global.ts
+++ b/frontend/app/store/global.ts
@@ -319,7 +319,6 @@ function useBlockAtom(blockId: string, name: string, makeFn: () => Atom):
if (atom == null) {
atom = makeFn();
blockCache.set(name, atom);
- console.log("New BlockAtom", blockId, name);
}
return atom as Atom;
}
@@ -666,8 +665,8 @@ export {
getApi,
getBlockComponentModel,
getBlockMetaKeyAtom,
- getConnConfigKeyAtom,
getBlockTermDurableAtom,
+ getConnConfigKeyAtom,
getConnStatusAtom,
getFocusedBlockId,
getHostName,