Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions emain/emain-tabview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export class WaveTabView extends WebContentsView {
}
}

let MaxCacheSize = 10;
let MaxCacheSize = 30;
const wcvCache = new Map<string, WaveTabView>();

export function setMaxTabCacheSize(size: number) {
Expand Down Expand Up @@ -268,11 +268,15 @@ function tryEvictEntry(waveTabId: string): boolean {
console.log("[error] WaveWindow not found for WaveTabView", tabView.waveTabId);
tabView.destroy();
return true;
} else {
// will trigger a destroy on the tabview
ww.removeTabView(tabView.waveTabId, false);
}
if (!ww.allLoadedTabViews.has(tabView.waveTabId)) {
// tab is no longer tracked by its window (e.g. after workspace switch cleared allLoadedTabViews)
tabView.destroy();
return true;
}
// will trigger a destroy on the tabview
ww.removeTabView(tabView.waveTabId, false);
return true;
}

function checkAndEvictCache(): void {
Expand Down Expand Up @@ -303,7 +307,7 @@ export function clearTabCache() {
// returns [tabview, initialized]
export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: string): Promise<[WaveTabView, boolean]> {
let tabView = getWaveTabView(tabId);
if (tabView) {
if (tabView && !tabView.isDestroyed) {
return [tabView, true];
}
const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient);
Expand Down
12 changes: 9 additions & 3 deletions emain/emain-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,9 +605,15 @@ export class WaveBrowserWindow extends BaseWindow {
if (!newWs) {
return;
}
console.log("processActionQueue switchworkspace newWs", newWs);
this.removeAllChildViews();
console.log("destroyed all tabs", this.waveWindowId);
console.log("processActionQueue switchworkspace newWs", newWs, "keep-alive tabs:", this.allLoadedTabViews.size);
// Keep BrowserViews alive (don't destroy) — position off-screen instead.
// They stay in contentView and wcvCache, and will be positioned on-screen
// when switching back to this workspace (setTabViewIntoWindow → finalizePositioning).
const curBounds = this.getContentBounds();
for (const tabView of this.allLoadedTabViews.values()) {
tabView.isActiveTab = false;
tabView.positionTabOffScreen(curBounds);
}
this.workspaceId = entry.workspaceId;
this.allLoadedTabViews = new Map();
tabId = newWs.activetabid;
Expand Down
7 changes: 4 additions & 3 deletions frontend/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ContextMenuModel } from "@/store/contextmenu";
import { atoms, createBlock, getSettingsPrefixAtom, refocusNode } from "@/store/global";
import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel";
import { getElemAsStr } from "@/util/focusutil";
import { termLog } from "@/util/termlog";
import * as keyutil from "@/util/keyutil";
import { PLATFORM } from "@/util/platformutil";
import * as util from "@/util/util";
Expand Down Expand Up @@ -245,16 +246,16 @@ const MacOSFirstClickHandler = () => {
const blockId = getBlockIdFromTarget(e.target);
if (blockId != null) {
setTimeout(() => {
console.log("macos first-click, focusing block", blockId);
termLog("[block]", "macos first-click, focusing block", blockId);
refocusNode(blockId);
}, 10);
} else if (isAIPanelTarget(e.target)) {
setTimeout(() => {
console.log("macos first-click, focusing AI panel");
termLog("[focus]", "macos first-click, focusing AI panel");
FocusManager.getInstance().setWaveAIFocused(true);
}, 10);
}
console.log("macos first-click detected, canceled", timeDiff + "ms");
termLog("[block]", "macos first-click detected, canceled", timeDiff + "ms");
return;
}
cancelNextClick = false;
Expand Down
5 changes: 3 additions & 2 deletions frontend/app/block/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { counterInc } from "@/store/counters";
import { getBlockComponentModel, registerBlockComponentModel, unregisterBlockComponentModel } from "@/store/global";
import { makeORef } from "@/store/wos";
import { focusedBlockId, getElemAsStr } from "@/util/focusutil";
import { termLog } from "@/util/termlog";
import { isBlank, useAtomValueSafe } from "@/util/util";
import clsx from "clsx";
import { useAtomValue } from "jotai";
Expand Down Expand Up @@ -165,9 +166,9 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => {

const handleChildFocus = useCallback(
(event: React.FocusEvent<HTMLDivElement, Element>) => {
console.log("setFocusedChild", nodeModel.blockId, getElemAsStr(event.target));
termLog("[block]", "handleChildFocus", nodeModel.blockId, getElemAsStr(event.target));
if (!isFocused) {
console.log("focusedChild focus", nodeModel.blockId);
termLog("[block]", "handleChildFocus: set isFocused", nodeModel.blockId);
nodeModel.focusNode();
}
},
Expand Down
3 changes: 3 additions & 0 deletions frontend/app/block/blockframe-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ const BlockFrame_Header = ({
</div>
)}
<HeaderTextElems viewModel={viewModel} blockId={nodeModel.blockId} preview={preview} error={error} />
<div className="text-[10px] font-mono text-muted opacity-50 shrink-0 mr-1 select-none" title={nodeModel.blockId}>
{nodeModel.blockId.substring(0, 8)}
</div>
<HeaderEndIcons viewModel={viewModel} nodeModel={nodeModel} blockId={nodeModel.blockId} />
</div>
);
Expand Down
9 changes: 8 additions & 1 deletion frontend/app/store/focusManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getBlockComponentModel, getBlockMetaKeyAtom } from "@/app/store/global"
import { globalStore } from "@/app/store/jotaiStore";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { termLog } from "@/util/termlog";
import { fireAndForget } from "@/util/util";
import { getLayoutModelForStaticTab } from "@/layout/index";
import { focusedBlockId } from "@/util/focusutil";
Expand Down Expand Up @@ -59,6 +60,7 @@ export class FocusManager {
if (!force && isAlreadyFocused) {
return;
}
termLog("[focus]", "setWaveAIFocused");
globalStore.set(this.focusType, "waveai");
this.refocusNode();
}
Expand All @@ -68,6 +70,7 @@ export class FocusManager {
if (!force && ftype == "node") {
return;
}
termLog("[focus]", "setBlockFocus");
globalStore.set(this.focusType, "node");
this.refocusNode();
}
Expand All @@ -81,10 +84,12 @@ export class FocusManager {
}

requestNodeFocus(): void {
termLog("[focus]", "requestNodeFocus");
globalStore.set(this.focusType, "node");
}

requestWaveAIFocus(): void {
termLog("[focus]", "requestWaveAIFocus");
globalStore.set(this.focusType, "waveai");
}

Expand All @@ -95,6 +100,7 @@ export class FocusManager {
refocusNode() {
const ftype = globalStore.get(this.focusType);
if (ftype == "waveai") {
termLog("[focus]", "refocusNode: waveai");
WaveAIModel.getInstance().focusInput();
return;
}
Expand All @@ -103,8 +109,9 @@ export class FocusManager {
if (lnode == null || lnode.data?.blockId == null) {
return;
}
layoutModel.focusNode(lnode.id);
const blockId = lnode.data.blockId;
termLog("[focus]", "refocusNode", blockId);
layoutModel.focusNode(lnode.id);
const bcm = getBlockComponentModel(blockId);
const ok = bcm?.viewModel?.giveFocus?.();
if (!ok) {
Expand Down
5 changes: 5 additions & 0 deletions frontend/app/store/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { getWebServerEndpoint } from "@/util/endpoints";
import { fetch } from "@/util/fetchutil";
import { setPlatform } from "@/util/platformutil";
import { termLog } from "@/util/termlog";
import {
base64ToString,
deepCompareReturnPrev,
Expand Down Expand Up @@ -511,6 +512,7 @@ async function fetchWaveFile(
}

function setNodeFocus(nodeId: string) {
termLog("[block]", "setNodeFocus", nodeId);
const layoutModel = getLayoutModelForStaticTab();
layoutModel.focusNode(nodeId);
}
Expand Down Expand Up @@ -613,6 +615,7 @@ function refocusNode(blockId: string) {
return;
}
}
termLog("[block]", "refocusNode", blockId);
const layoutModel = getLayoutModelForStaticTab();
const layoutNodeId = layoutModel.getNodeByBlockId(blockId);
if (layoutNodeId?.id == null) {
Expand Down Expand Up @@ -693,10 +696,12 @@ function getConnStatusAtom(conn: string): PrimitiveAtom<ConnStatus> {
}

function createTab() {
termLog("[tab]", "createTab");
getApi().createTab();
}

function setActiveTab(tabId: string) {
termLog("[tab]", "setActiveTab", tabId);
getApi().setActiveTab(tabId);
}

Expand Down
12 changes: 8 additions & 4 deletions frontend/app/store/keymodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
import { deleteLayoutModelForTab, getLayoutModelForStaticTab, NavigateDirection } from "@/layout/index";
import * as keyutil from "@/util/keyutil";
import { isWindows } from "@/util/platformutil";
import { termLog } from "@/util/termlog";
import { CHORD_TIMEOUT } from "@/util/sharedconst";
import { fireAndForget } from "@/util/util";
import * as jotai from "jotai";
Expand Down Expand Up @@ -141,7 +142,7 @@ function simpleCloseStaticTab() {
}
})
.catch((e) => {
console.log("error closing tab", e);
termLog("[tab]", "error closing tab", e);
});
}

Expand Down Expand Up @@ -230,6 +231,7 @@ function genericClose() {
}

function switchBlockByBlockNum(index: number) {
termLog("[block]", "switchBlockByBlockNum", index);
const layoutModel = getLayoutModelForStaticTab();
if (!layoutModel) {
return;
Expand All @@ -241,6 +243,7 @@ function switchBlockByBlockNum(index: number) {
}

function switchBlockInDirection(direction: NavigateDirection) {
termLog("[block]", "switchBlockInDirection", direction);
const layoutModel = getLayoutModelForStaticTab();
const focusType = FocusManager.getInstance().getFocusType();

Expand Down Expand Up @@ -282,7 +285,7 @@ function getAllTabs(ws: Workspace): string[] {
}

function switchTabAbs(index: number) {
console.log("switchTabAbs", index);
termLog("[tab]", "switchTabAbs", index);
const ws = globalStore.get(atoms.workspace);
const newTabIdx = index - 1;
const tabids = getAllTabs(ws);
Expand All @@ -294,7 +297,7 @@ function switchTabAbs(index: number) {
}

function switchTab(offset: number) {
console.log("switchTab", offset);
termLog("[tab]", "switchTab", offset);
const ws = globalStore.get(atoms.workspace);
const curTabId = globalStore.get(atoms.staticTabId);
let tabIdx = -1;
Expand Down Expand Up @@ -331,14 +334,15 @@ function globalRefocus() {
const layoutModel = getLayoutModelForStaticTab();
const focusedNode = globalStore.get(layoutModel.focusedNode);
if (focusedNode == null) {
// focus a node
termLog("[block]", "globalRefocus: no focused node, focus first");
layoutModel.focusFirstNode();
return;
}
const blockId = focusedNode?.data?.blockId;
if (blockId == null) {
return;
}
termLog("[block]", "globalRefocus", blockId);
refocusNode(blockId);
}

Expand Down
2 changes: 2 additions & 0 deletions frontend/app/store/tabrpcclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getApi, getBlockComponentModel, getConnStatusAtom, globalStore, WOS } f
import type { TermViewModel } from "@/app/view/term/term-model";
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
import { getLayoutModelForStaticTab } from "@/layout/index";
import { termLog } from "@/util/termlog";
import { base64ToArrayBuffer } from "@/util/util";
import { RpcResponseHelper, WshClient } from "./wshclient";
import { RpcApi } from "./wshclientapi";
Expand Down Expand Up @@ -93,6 +94,7 @@ export class TabClient extends WshClient {
}

async handle_setblockfocus(rh: RpcResponseHelper, blockId: string): Promise<void> {
termLog("[block]", "handle_setblockfocus (RPC)", blockId);
const layoutModel = getLayoutModelForStaticTab();
if (!layoutModel) {
throw new Error("Layout model not found");
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/tab/workspaceswitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ExpandableMenuItemRightElement,
} from "@/element/expandablemenu";
import { Popover, PopoverButton, PopoverContent } from "@/element/popover";
import { termLog } from "@/util/termlog";
import { fireAndForget, makeIconClass, useAtomValueSafe } from "@/util/util";
import clsx from "clsx";
import { atom, PrimitiveAtom, useAtomValue, useSetAtom } from "jotai";
Expand Down Expand Up @@ -224,6 +225,7 @@ const WorkspaceSwitcherItem = ({ entryAtom }: { entryAtom: PrimitiveAtom<Workspa
<ExpandableMenuItem
className="workspace-list-item"
onClick={() => {
termLog("[workspace]", "switchWorkspace", workspace.oid, workspace.name);
env.electron.switchWorkspace(workspace.oid);
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
}}
Expand Down
4 changes: 4 additions & 0 deletions frontend/app/view/term/term-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { waveEventSubscribeSingle } from "@/app/store/wps";
import { RpcApi } from "@/app/store/wshclientapi";
import { makeFeBlockRouteId } from "@/app/store/wshrouter";
import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil";
import { termLog } from "@/util/termlog";
import { TermClaudeIcon, TerminalView } from "@/app/view/term/term";
import { TermWshClient } from "@/app/view/term/term-wsh";
import { VDomModel } from "@/app/view/vdom/vdom-model";
Expand Down Expand Up @@ -508,6 +509,9 @@ export class TermViewModel implements ViewModel {

sendDataToController(data: string) {
const b64data = stringToBase64(data);
if (data.startsWith("\x1b[<") || data.startsWith("\x1b[?") || data === "\x1b[I" || data === "\x1b[O") {
termLog("[sendData]", this.blockId, data.slice(0, 60).replace(/\x1b/g, "\\e"));
}
RpcApi.ControllerInputCommand(TabRpcClient, { blockid: this.blockId, inputdata64: b64data });
}

Expand Down
23 changes: 18 additions & 5 deletions frontend/app/view/term/term.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import type { TermViewModel } from "@/app/view/term/term-model";
import { atoms, getOverrideConfigAtom, getSettingsPrefixAtom, WOS } from "@/store/global";
import { termLog } from "@/util/termlog";
import { fireAndForget, useAtomValueSafe } from "@/util/util";
import { computeBgStyleFromMeta } from "@/util/waveutil";
import { ISearchOptions } from "@xterm/addon-search";
Expand Down Expand Up @@ -51,19 +52,19 @@ const TermResyncHandler = React.memo(({ blockId, model }: TerminalViewProps) =>

React.useEffect(() => {
if (!model.termRef.current?.hasResized) {
console.log("[TermResyncHandler] hasResized=false, skipping resync", blockId);
termLog("[resync]", blockId, "TermResyncHandler skipped (hasResized=false)");
setLastConnStatus(connStatus);
return;
}
const isConnected = connStatus?.status == "connected";
const wasConnected = lastConnStatus?.status == "connected";
const curConnName = connStatus?.connection;
const lastConnName = lastConnStatus?.connection;
console.log("[TermResyncHandler] check", blockId, "cur:", connStatus?.status, "last:", lastConnStatus?.status, "conn:", curConnName);
termLog("[resync]", blockId, "check cur:", connStatus?.status, "last:", lastConnStatus?.status, "conn:", curConnName);
if (isConnected == wasConnected && curConnName == lastConnName) {
return;
}
console.log("[TermResyncHandler] triggering resync", blockId);
termLog("[resync]", blockId, "triggered (conn status changed)");
model.termRef.current?.resyncController("resync handler");
setLastConnStatus(connStatus);
}, [connStatus]);
Expand Down Expand Up @@ -299,10 +300,16 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
const termCursorStyle = normalizeCursorStyle(globalStore.get(getOverrideConfigAtom(blockId, "term:cursor")));
const termCursorBlink = globalStore.get(getOverrideConfigAtom(blockId, "term:cursorblink")) ?? false;
const wasFocused = model.termRef.current != null && globalStore.get(model.nodeModel.isFocused);
const connectElem = connectElemRef.current;
const createRect = connectElem?.getBoundingClientRect?.();
termLog("[init]", blockId, "creating TermWrap, containerRect:",
createRect ? `${createRect.width.toFixed(0)}x${createRect.height.toFixed(0)}` : "null",
"parentRect:", connectElem?.parentElement?.getBoundingClientRect?.() ?
`${connectElem.parentElement.getBoundingClientRect().width.toFixed(0)}x${connectElem.parentElement.getBoundingClientRect().height.toFixed(0)}` : "null");
const termWrap = new TermWrap(
tabModel.tabId,
blockId,
connectElemRef.current,
connectElem,
{
theme: termTheme,
fontSize: termFontSize,
Expand All @@ -329,7 +336,13 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
(window as any).term = termWrap;
model.termRef.current = termWrap;
setTermWrapInst(termWrap);
const rszObs = new ResizeObserver(() => {
const rszObs = new ResizeObserver((entries) => {
const entry = entries[0];
const cr = entry?.contentRect;
if (cr) {
termLog("[resize]", blockId, "ResizeObserver fired, contentRect:",
`${cr.width.toFixed(0)}x${cr.height.toFixed(0)}`);
}
termWrap.handleResize_debounced();
});
rszObs.observe(connectElemRef.current);
Expand Down
Loading
Loading