waveterm/frontend/wave.ts
Copilot 200863bbbc
Remove unused frontend notification system and dev-only updater notification UI (#2929)
This PR removes the legacy notification pipeline from the frontend
(`pushNotification`, removal helpers, notification atoms/types, and
notification UI components). The updater status notification path was
only wired through this system and dev-only rendering in `App`, so the
entire path is deleted.

- **Store/API cleanup**
  - Removed notification functions from `frontend/app/store/global.ts`:
    - `pushNotification`
    - `removeNotificationById`
    - `removeNotification`
    - internal add/update helper
- Removed notification API exposure from `frontend/wave.ts`
(`window.pushNotification`, `window.removeNotificationById`).

- **Global atom/type cleanup**
- Removed notification atoms from `frontend/app/store/global-atoms.ts`:
    - `notifications`
    - `notificationPopoverMode`
- Removed notification-related fields/types from
`frontend/types/custom.d.ts`:
    - `GlobalAtomsType.notifications`
    - `GlobalAtomsType.notificationPopoverMode`
    - `NotificationType`
    - `NotificationActionType`

- **UI removal**
- Removed notification rendering from `frontend/app/app.tsx` by deleting
the `NotificationBubbles` import and dev-guarded render branch.
- Deleted the entire notification UI module under
`frontend/app/notification/`:
    - `notificationbubbles.tsx/.scss`
    - `notificationitem.tsx/.scss`
    - `notificationpopover.tsx`
    - `updatenotifier.tsx`
    - `usenotification.tsx`

- **Representative diff**
  ```tsx
  // before (app.tsx)
import { NotificationBubbles } from
"./notification/notificationbubbles";
  ...
  {isDev() ? <NotificationBubbles></NotificationBubbles> : null}

  // after
  // notification import and render removed
  ```

<screenshot>

UI reference provided for this change set:  

https://github.com/user-attachments/assets/6f56b16f-95c9-4a5e-ada8-18ecdc9a1ff8

</screenshot>

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
2026-02-24 12:49:13 -08:00

277 lines
11 KiB
TypeScript

// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { App } from "@/app/app";
import { loadMonaco } from "@/app/monaco/monaco-env";
import { GlobalModel } from "@/app/store/global-model";
import {
globalRefocus,
registerBuilderGlobalKeys,
registerControlShiftStateUpdateHandler,
registerElectronReinjectKeyHandler,
registerGlobalKeys,
} from "@/app/store/keymodel";
import { modalsModel } from "@/app/store/modalmodel";
import { RpcApi } from "@/app/store/wshclientapi";
import { makeBuilderRouteId, makeTabRouteId } from "@/app/store/wshrouter";
import { initWshrpc, TabRpcClient } from "@/app/store/wshrpcutil";
import { BuilderApp } from "@/builder/builder-app";
import { getLayoutModelForStaticTab } from "@/layout/index";
import { countersClear, countersPrint } from "@/store/counters";
import {
atoms,
getApi,
globalStore,
initGlobal,
initGlobalWaveEventSubs,
loadConnStatus,
loadTabIndicators,
subscribeToConnEvents,
} from "@/store/global";
import { activeTabIdAtom } from "@/store/tab-model";
import * as WOS from "@/store/wos";
import { loadFonts } from "@/util/fontutil";
import { setKeyUtilPlatform } from "@/util/keyutil";
import { createElement } from "react";
import { createRoot } from "react-dom/client";
const platform = getApi().getPlatform();
document.title = `Wave Terminal`;
let savedInitOpts: WaveInitOpts = null;
(window as any).WOS = WOS;
(window as any).globalStore = globalStore;
(window as any).globalAtoms = atoms;
(window as any).RpcApi = RpcApi;
(window as any).isFullScreen = false;
(window as any).countersPrint = countersPrint;
(window as any).countersClear = countersClear;
(window as any).getLayoutModelForStaticTab = getLayoutModelForStaticTab;
(window as any).modalsModel = modalsModel;
function updateZoomFactor(zoomFactor: number) {
console.log("update zoomfactor", zoomFactor);
document.documentElement.style.setProperty("--zoomfactor", String(zoomFactor));
document.documentElement.style.setProperty("--zoomfactor-inv", String(1 / zoomFactor));
}
async function initBare() {
getApi().sendLog("Init Bare");
document.body.style.visibility = "hidden";
document.body.style.opacity = "0";
document.body.classList.add("is-transparent");
getApi().onWaveInit(initWaveWrap);
getApi().onBuilderInit(initBuilderWrap);
setKeyUtilPlatform(platform);
loadFonts();
updateZoomFactor(getApi().getZoomFactor());
getApi().onZoomFactorChange((zoomFactor) => {
updateZoomFactor(zoomFactor);
});
document.fonts.ready.then(() => {
console.log("Init Bare Done");
getApi().setWindowInitStatus("ready");
});
}
document.addEventListener("DOMContentLoaded", initBare);
async function initWaveWrap(initOpts: WaveInitOpts) {
try {
if (savedInitOpts) {
await reinitWave();
return;
}
savedInitOpts = initOpts;
await initWave(initOpts);
} catch (e) {
getApi().sendLog("Error in initWave " + e.message + "\n" + e.stack);
console.error("Error in initWave", e);
} finally {
document.body.style.visibility = null;
document.body.style.opacity = null;
document.body.classList.remove("is-transparent");
}
}
async function reinitWave() {
console.log("Reinit Wave");
getApi().sendLog("Reinit Wave");
// We use this hack to prevent a flicker of the previously-hovered tab when this view was last active.
document.body.classList.add("nohover");
requestAnimationFrame(() =>
setTimeout(() => {
document.body.classList.remove("nohover");
}, 100)
);
await WOS.reloadWaveObject<Client>(WOS.makeORef("client", savedInitOpts.clientId));
const waveWindow = await WOS.reloadWaveObject<WaveWindow>(WOS.makeORef("window", savedInitOpts.windowId));
const ws = await WOS.reloadWaveObject<Workspace>(WOS.makeORef("workspace", waveWindow.workspaceid));
const initialTab = await WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", savedInitOpts.tabId));
await WOS.reloadWaveObject<LayoutState>(WOS.makeORef("layout", initialTab.layoutstate));
reloadAllWorkspaceTabs(ws);
document.title = `Wave Terminal - ${initialTab.name}`; // TODO update with tab name change
getApi().setWindowInitStatus("wave-ready");
globalStore.set(atoms.reinitVersion, globalStore.get(atoms.reinitVersion) + 1);
globalStore.set(atoms.updaterStatusAtom, getApi().getUpdaterStatus());
setTimeout(() => {
globalRefocus();
}, 50);
}
function reloadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || !ws.tabids?.length) {
return;
}
ws.tabids?.forEach((tabid) => {
WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid));
});
}
function loadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || !ws.tabids?.length) {
return;
}
ws.tabids?.forEach((tabid) => {
WOS.getObjectValue<Tab>(WOS.makeORef("tab", tabid));
});
}
async function initWave(initOpts: WaveInitOpts) {
getApi().sendLog("Init Wave " + JSON.stringify(initOpts));
const globalInitOpts: GlobalInitOptions = {
tabId: initOpts.tabId,
clientId: initOpts.clientId,
windowId: initOpts.windowId,
platform,
environment: "renderer",
primaryTabStartup: initOpts.primaryTabStartup,
};
console.log("Wave Init", globalInitOpts);
globalStore.set(activeTabIdAtom, initOpts.tabId);
await GlobalModel.getInstance().initialize(globalInitOpts);
initGlobal(globalInitOpts);
(window as any).globalAtoms = atoms;
// Init WPS event handlers
const globalWS = initWshrpc(makeTabRouteId(initOpts.tabId));
(window as any).globalWS = globalWS;
(window as any).TabRpcClient = TabRpcClient;
await loadConnStatus();
await loadTabIndicators();
initGlobalWaveEventSubs(initOpts);
subscribeToConnEvents();
// ensures client/window/workspace are loaded into the cache before rendering
try {
const [client, waveWindow, initialTab] = await Promise.all([
WOS.loadAndPinWaveObject<Client>(WOS.makeORef("client", initOpts.clientId)),
WOS.loadAndPinWaveObject<WaveWindow>(WOS.makeORef("window", initOpts.windowId)),
WOS.loadAndPinWaveObject<Tab>(WOS.makeORef("tab", initOpts.tabId)),
]);
const [ws, layoutState] = await Promise.all([
WOS.loadAndPinWaveObject<Workspace>(WOS.makeORef("workspace", waveWindow.workspaceid)),
WOS.reloadWaveObject<LayoutState>(WOS.makeORef("layout", initialTab.layoutstate)),
]);
loadAllWorkspaceTabs(ws);
WOS.wpsSubscribeToObject(WOS.makeORef("workspace", waveWindow.workspaceid));
document.title = `Wave Terminal - ${initialTab.name}`; // TODO update with tab name change
} catch (e) {
console.error("Failed initialization error", e);
getApi().sendLog("Error in initialization (wave.ts, loading required objects) " + e.message + "\n" + e.stack);
}
registerGlobalKeys();
registerElectronReinjectKeyHandler();
registerControlShiftStateUpdateHandler();
await loadMonaco();
const fullConfig = await RpcApi.GetFullConfigCommand(TabRpcClient);
console.log("fullconfig", fullConfig);
globalStore.set(atoms.fullConfigAtom, fullConfig);
const waveaiModeConfig = await RpcApi.GetWaveAIModeConfigCommand(TabRpcClient);
globalStore.set(atoms.waveaiModeConfigAtom, waveaiModeConfig.configs);
console.log("Wave First Render");
let firstRenderResolveFn: () => void = null;
let firstRenderPromise = new Promise<void>((resolve) => {
firstRenderResolveFn = resolve;
});
const reactElem = createElement(App, { onFirstRender: firstRenderResolveFn }, null);
const elem = document.getElementById("main");
const root = createRoot(elem);
root.render(reactElem);
await firstRenderPromise;
console.log("Wave First Render Done");
getApi().setWindowInitStatus("wave-ready");
}
async function initBuilderWrap(initOpts: BuilderInitOpts) {
try {
await initBuilder(initOpts);
} catch (e) {
getApi().sendLog("Error in initBuilder " + e.message + "\n" + e.stack);
console.error("Error in initBuilder", e);
} finally {
document.body.style.visibility = null;
document.body.style.opacity = null;
document.body.classList.remove("is-transparent");
}
}
async function initBuilder(initOpts: BuilderInitOpts) {
getApi().sendLog("Init Builder " + JSON.stringify(initOpts));
const globalInitOpts: GlobalInitOptions = {
clientId: initOpts.clientId,
windowId: initOpts.windowId,
platform,
environment: "renderer",
builderId: initOpts.builderId,
};
console.log("Tsunami Builder Init", globalInitOpts);
await GlobalModel.getInstance().initialize(globalInitOpts);
initGlobal(globalInitOpts);
(window as any).globalAtoms = atoms;
const globalWS = initWshrpc(makeBuilderRouteId(initOpts.builderId));
(window as any).globalWS = globalWS;
(window as any).TabRpcClient = TabRpcClient;
await loadConnStatus();
let appIdToUse: string = null;
try {
const oref = WOS.makeORef("builder", initOpts.builderId);
const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, { oref });
if (rtInfo && rtInfo["builder:appid"]) {
appIdToUse = rtInfo["builder:appid"];
}
} catch (e) {
console.log("Could not load saved builder appId from rtinfo:", e);
}
document.title = appIdToUse ? `WaveApp Builder (${appIdToUse})` : "WaveApp Builder";
globalStore.set(atoms.builderAppId, appIdToUse);
const client = await WOS.loadAndPinWaveObject<Client>(WOS.makeORef("client", initOpts.clientId));
registerBuilderGlobalKeys();
registerElectronReinjectKeyHandler();
await loadMonaco();
const fullConfig = await RpcApi.GetFullConfigCommand(TabRpcClient);
console.log("fullconfig", fullConfig);
globalStore.set(atoms.fullConfigAtom, fullConfig);
const waveaiModeConfig = await RpcApi.GetWaveAIModeConfigCommand(TabRpcClient);
globalStore.set(atoms.waveaiModeConfigAtom, waveaiModeConfig.configs);
console.log("Tsunami Builder First Render");
let firstRenderResolveFn: () => void = null;
let firstRenderPromise = new Promise<void>((resolve) => {
firstRenderResolveFn = resolve;
});
const reactElem = createElement(BuilderApp, { initOpts, onFirstRender: firstRenderResolveFn }, null);
const elem = document.getElementById("main");
const root = createRoot(elem);
root.render(reactElem);
await firstRenderPromise;
console.log("Tsunami Builder First Render Done");
}