Skip to content

Commit 3084e9d

Browse files
authored
introduce new models -- specifically tab model which has a react provider for prop drilling (#2694)
1 parent 3c3c665 commit 3084e9d

32 files changed

+328
-141
lines changed

frontend/app/aipanel/aipanel.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils";
66
import { ErrorBoundary } from "@/app/element/errorboundary";
77
import { atoms, getSettingsKeyAtom } from "@/app/store/global";
88
import { globalStore } from "@/app/store/jotaiStore";
9+
import { useTabModel } from "@/app/store/tab-model";
910
import { checkKeyPressed, keydownWrapper } from "@/util/keyutil";
1011
import { isMacOS, isWindows } from "@/util/platformutil";
1112
import { cn } from "@/util/util";
@@ -254,6 +255,7 @@ const AIPanelComponentInner = memo(() => {
254255
const isFocused = jotai.useAtomValue(model.isWaveAIFocusedAtom);
255256
const telemetryEnabled = jotai.useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false;
256257
const isPanelVisible = jotai.useAtomValue(model.getPanelVisibleAtom());
258+
const tabModel = useTabModel();
257259
const defaultMode = jotai.useAtomValue(getSettingsKeyAtom("waveai:defaultmode")) ?? "waveai@balanced";
258260
const aiModeConfigs = jotai.useAtomValue(model.aiModeConfigs);
259261

@@ -277,7 +279,7 @@ const AIPanelComponentInner = memo(() => {
277279
body.builderid = globalStore.get(atoms.builderId);
278280
body.builderappid = globalStore.get(atoms.builderAppId);
279281
} else {
280-
body.tabid = globalStore.get(atoms.staticTabId);
282+
body.tabid = tabModel.tabId;
281283
}
282284
return { body };
283285
},

frontend/app/app.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright 2025, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import { ClientModel } from "@/app/store/client-model";
5+
import { GlobalModel } from "@/app/store/global-model";
46
import { Workspace } from "@/app/workspace/workspace";
57
import { ContextMenuModel } from "@/store/contextmenu";
68
import { atoms, createBlock, getSettingsPrefixAtom, globalStore, isDev, removeFlashError } from "@/store/global";
@@ -273,8 +275,8 @@ const FlashError = () => {
273275

274276
const AppInner = () => {
275277
const prefersReducedMotion = useAtomValue(atoms.prefersReducedMotionAtom);
276-
const client = useAtomValue(atoms.client);
277-
const windowData = useAtomValue(atoms.waveWindow);
278+
const client = useAtomValue(ClientModel.getInstance().clientAtom);
279+
const windowData = useAtomValue(GlobalModel.getInstance().windowDataAtom);
278280
const isFullScreen = useAtomValue(atoms.isFullScreen);
279281

280282
if (client == null || windowData == null) {

frontend/app/block/block.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
registerBlockComponentModel,
2525
unregisterBlockComponentModel,
2626
} from "@/store/global";
27+
import type { TabModel } from "@/app/store/tab-model";
28+
import { useTabModel } from "@/app/store/tab-model";
2729
import { getWaveObjectAtom, makeORef, useWaveObjectValue } from "@/store/wos";
2830
import { focusedBlockId, getElemAsStr } from "@/util/focusutil";
2931
import { isBlank, useAtomValueSafe } from "@/util/util";
@@ -55,10 +57,10 @@ BlockRegistry.set("tsunami", TsunamiViewModel);
5557
BlockRegistry.set("aifilediff", AiFileDiffViewModel);
5658
BlockRegistry.set("waveconfig", WaveConfigViewModel);
5759

58-
function makeViewModel(blockId: string, blockView: string, nodeModel: BlockNodeModel): ViewModel {
60+
function makeViewModel(blockId: string, blockView: string, nodeModel: BlockNodeModel, tabModel: TabModel): ViewModel {
5961
const ctor = BlockRegistry.get(blockView);
6062
if (ctor != null) {
61-
return new ctor(blockId, nodeModel);
63+
return new ctor(blockId, nodeModel, tabModel);
6264
}
6365
return makeDefaultViewModel(blockId, blockView);
6466
}
@@ -261,11 +263,12 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => {
261263
const Block = memo((props: BlockProps) => {
262264
counterInc("render-Block");
263265
counterInc("render-Block-" + props.nodeModel?.blockId?.substring(0, 8));
266+
const tabModel = useTabModel();
264267
const [blockData, loading] = useWaveObjectValue<Block>(makeORef("block", props.nodeModel.blockId));
265268
const bcm = getBlockComponentModel(props.nodeModel.blockId);
266269
let viewModel = bcm?.viewModel;
267270
if (viewModel == null || viewModel.viewType != blockData?.meta?.view) {
268-
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel);
271+
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel, tabModel);
269272
registerBlockComponentModel(props.nodeModel.blockId, { viewModel });
270273
}
271274
useEffect(() => {
@@ -286,11 +289,12 @@ const Block = memo((props: BlockProps) => {
286289
const SubBlock = memo((props: SubBlockProps) => {
287290
counterInc("render-Block");
288291
counterInc("render-Block-" + props.nodeModel?.blockId?.substring(0, 8));
292+
const tabModel = useTabModel();
289293
const [blockData, loading] = useWaveObjectValue<Block>(makeORef("block", props.nodeModel.blockId));
290294
const bcm = getBlockComponentModel(props.nodeModel.blockId);
291295
let viewModel = bcm?.viewModel;
292296
if (viewModel == null || viewModel.viewType != blockData?.meta?.view) {
293-
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel);
297+
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel, tabModel);
294298
registerBlockComponentModel(props.nodeModel.blockId, { viewModel });
295299
}
296300
useEffect(() => {

frontend/app/block/blockframe.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
useBlockAtom,
1818
WOS,
1919
} from "@/app/store/global";
20+
import { useTabModel } from "@/app/store/tab-model";
2021
import { uxCloseBlock } from "@/app/store/keymodel";
2122
import { RpcApi } from "@/app/store/wshclientapi";
2223
import { TabRpcClient } from "@/app/store/wshrpcutil";
@@ -492,28 +493,27 @@ const ConnStatusOverlay = React.memo(
492493
);
493494

494495
const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => {
496+
const tabModel = useTabModel();
495497
const isFocused = jotai.useAtomValue(nodeModel.isFocused);
496498
const isEphemeral = jotai.useAtomValue(nodeModel.isEphemeral);
497499
const blockNum = jotai.useAtomValue(nodeModel.blockNum);
498500
const isLayoutMode = jotai.useAtomValue(atoms.controlShiftDelayAtom);
499501
const showOverlayBlockNums = jotai.useAtomValue(getSettingsKeyAtom("app:showoverlayblocknums")) ?? true;
500502
const blockHighlight = jotai.useAtomValue(BlockModel.getInstance().getBlockHighlightAtom(nodeModel.blockId));
501503
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
504+
const tabActiveBorderColor = jotai.useAtomValue(tabModel.getTabMetaAtom("bg:activebordercolor"));
505+
const tabBorderColor = jotai.useAtomValue(tabModel.getTabMetaAtom("bg:bordercolor"));
502506
const style: React.CSSProperties = {};
503507
let showBlockMask = false;
504508

505509
if (isFocused) {
506-
const tabData = jotai.useAtomValue(atoms.tabAtom);
507-
const tabActiveBorderColor = tabData?.meta?.["bg:activebordercolor"];
508510
if (tabActiveBorderColor) {
509511
style.borderColor = tabActiveBorderColor;
510512
}
511513
if (blockData?.meta?.["frame:activebordercolor"]) {
512514
style.borderColor = blockData.meta["frame:activebordercolor"];
513515
}
514516
} else {
515-
const tabData = jotai.useAtomValue(atoms.tabAtom);
516-
const tabBorderColor = tabData?.meta?.["bg:bordercolor"];
517517
if (tabBorderColor) {
518518
style.borderColor = tabBorderColor;
519519
}
@@ -674,13 +674,13 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
674674
const BlockFrame_Default = React.memo(BlockFrame_Default_Component) as typeof BlockFrame_Default_Component;
675675

676676
const BlockFrame = React.memo((props: BlockFrameProps) => {
677+
const tabModel = useTabModel();
677678
const blockId = props.nodeModel.blockId;
678679
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
679-
const tabData = jotai.useAtomValue(atoms.tabAtom);
680+
const numBlocks = jotai.useAtomValue(tabModel.tabNumBlocksAtom);
680681
if (!blockId || !blockData) {
681682
return null;
682683
}
683-
const numBlocks = tabData?.blockids?.length ?? 0;
684684
return <BlockFrame_Default {...props} numBlocksInTab={numBlocks} />;
685685
});
686686

frontend/app/modals/modalsrenderer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { NewInstallOnboardingModal } from "@/app/onboarding/onboarding";
55
import { CurrentOnboardingVersion } from "@/app/onboarding/onboarding-common";
66
import { UpgradeOnboardingModal } from "@/app/onboarding/onboarding-upgrade";
7+
import { ClientModel } from "@/app/store/client-model";
78
import { atoms, globalPrimaryTabStartup, globalStore } from "@/store/global";
89
import { modalsModel } from "@/store/modalmodel";
910
import * as jotai from "jotai";
@@ -12,7 +13,7 @@ import * as semver from "semver";
1213
import { getModalComponent } from "./modalregistry";
1314

1415
const ModalsRenderer = () => {
15-
const clientData = jotai.useAtomValue(atoms.client);
16+
const clientData = jotai.useAtomValue(ClientModel.getInstance().clientAtom);
1617
const [newInstallOnboardingOpen, setNewInstallOnboardingOpen] = jotai.useAtom(modalsModel.newInstallOnboardingOpen);
1718
const [upgradeOnboardingOpen, setUpgradeOnboardingOpen] = jotai.useAtom(modalsModel.upgradeOnboardingOpen);
1819
const [modals] = jotai.useAtom(modalsModel.modalsAtom);

frontend/app/onboarding/onboarding-features.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Logo from "@/app/asset/logo.svg";
55
import { Button } from "@/app/element/button";
66
import { EmojiButton } from "@/app/element/emojibutton";
77
import { MagnifyIcon } from "@/app/element/magnify";
8-
import { atoms, globalStore } from "@/app/store/global";
8+
import { ClientModel } from "@/app/store/client-model";
99
import * as WOS from "@/app/store/wos";
1010
import { RpcApi } from "@/app/store/wshclientapi";
1111
import { TabRpcClient } from "@/app/store/wshrpcutil";
@@ -314,7 +314,7 @@ export const OnboardingFeatures = ({ onComplete }: { onComplete: () => void }) =
314314
const [currentPage, setCurrentPage] = useState<FeaturePageName>("waveai");
315315

316316
useEffect(() => {
317-
const clientId = globalStore.get(atoms.clientId);
317+
const clientId = ClientModel.getInstance().clientId;
318318
RpcApi.SetMetaCommand(TabRpcClient, {
319319
oref: WOS.makeORef("client", clientId),
320320
meta: { "onboarding:lastversion": CurrentOnboardingVersion },

frontend/app/onboarding/onboarding-upgrade-minor.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { Button } from "@/app/element/button";
66
import { FlexiModal } from "@/app/modals/modal";
77
import { CurrentOnboardingVersion } from "@/app/onboarding/onboarding-common";
88
import { OnboardingFeatures } from "@/app/onboarding/onboarding-features";
9-
import { atoms, globalStore } from "@/app/store/global";
9+
import { ClientModel } from "@/app/store/client-model";
10+
import { globalStore } from "@/app/store/global";
1011
import { disableGlobalKeybindings, enableGlobalKeybindings, globalRefocus } from "@/app/store/keymodel";
1112
import { modalsModel } from "@/app/store/modalmodel";
1213
import * as WOS from "@/app/store/wos";
@@ -60,7 +61,7 @@ const UpgradeOnboardingMinor = () => {
6061
},
6162
{ noresponse: true }
6263
);
63-
const clientId = globalStore.get(atoms.clientId);
64+
const clientId = ClientModel.getInstance().clientId;
6465
await RpcApi.SetMetaCommand(TabRpcClient, {
6566
oref: WOS.makeORef("client", clientId),
6667
meta: { "onboarding:githubstar": true },
@@ -78,7 +79,7 @@ const UpgradeOnboardingMinor = () => {
7879
},
7980
{ noresponse: true }
8081
);
81-
const clientId = globalStore.get(atoms.clientId);
82+
const clientId = ClientModel.getInstance().clientId;
8283
await RpcApi.SetMetaCommand(TabRpcClient, {
8384
oref: WOS.makeORef("client", clientId),
8485
meta: { "onboarding:githubstar": true },
@@ -95,7 +96,7 @@ const UpgradeOnboardingMinor = () => {
9596
},
9697
{ noresponse: true }
9798
);
98-
const clientId = globalStore.get(atoms.clientId);
99+
const clientId = ClientModel.getInstance().clientId;
99100
await RpcApi.SetMetaCommand(TabRpcClient, {
100101
oref: WOS.makeORef("client", clientId),
101102
meta: { "onboarding:githubstar": false },
@@ -104,7 +105,7 @@ const UpgradeOnboardingMinor = () => {
104105
};
105106

106107
const handleFeaturesComplete = () => {
107-
const clientId = globalStore.get(atoms.clientId);
108+
const clientId = ClientModel.getInstance().clientId;
108109
RpcApi.SetMetaCommand(TabRpcClient, {
109110
oref: WOS.makeORef("client", clientId),
110111
meta: { "onboarding:lastversion": CurrentOnboardingVersion },

frontend/app/onboarding/onboarding-upgrade-patch.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import Logo from "@/app/asset/logo.svg";
55
import { Button } from "@/app/element/button";
66
import { FlexiModal } from "@/app/modals/modal";
77
import { CurrentOnboardingVersion } from "@/app/onboarding/onboarding-common";
8-
import { atoms, globalStore } from "@/app/store/global";
8+
import { ClientModel } from "@/app/store/client-model";
9+
import { globalStore } from "@/app/store/global";
910
import { disableGlobalKeybindings, enableGlobalKeybindings, globalRefocus } from "@/app/store/keymodel";
1011
import { modalsModel } from "@/app/store/modalmodel";
1112
import * as WOS from "@/app/store/wos";
@@ -98,7 +99,7 @@ const UpgradeOnboardingPatch = () => {
9899
}, []);
99100

100101
const handleClose = () => {
101-
const clientId = globalStore.get(atoms.clientId);
102+
const clientId = ClientModel.getInstance().clientId;
102103
RpcApi.SetMetaCommand(TabRpcClient, {
103104
oref: WOS.makeORef("client", clientId),
104105
meta: { "onboarding:lastversion": CurrentOnboardingVersion },

frontend/app/onboarding/onboarding-upgrade.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2025, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import { ClientModel } from "@/app/store/client-model";
45
import { atoms, globalStore } from "@/app/store/global";
56
import { modalsModel } from "@/app/store/modalmodel";
67
import { useAtomValue } from "jotai";
@@ -11,7 +12,7 @@ import { UpgradeOnboardingMinor } from "./onboarding-upgrade-minor";
1112
import { UpgradeOnboardingPatch } from "./onboarding-upgrade-patch";
1213

1314
const UpgradeOnboardingModal = () => {
14-
const clientData = useAtomValue(atoms.client);
15+
const clientData = useAtomValue(ClientModel.getInstance().clientAtom);
1516
const initialVersionRef = useRef<string | null>(null);
1617

1718
if (initialVersionRef.current == null) {

frontend/app/onboarding/onboarding.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@
44
import Logo from "@/app/asset/logo.svg";
55
import { Button } from "@/app/element/button";
66
import { FlexiModal } from "@/app/modals/modal";
7-
import { disableGlobalKeybindings, enableGlobalKeybindings, globalRefocus } from "@/app/store/keymodel";
8-
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
9-
import * as services from "@/store/services";
10-
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
11-
import { useEffect, useRef, useState } from "react";
12-
import { debounce } from "throttle-debounce";
13-
147
import { OnboardingFeatures } from "@/app/onboarding/onboarding-features";
15-
import { atoms, globalStore } from "@/app/store/global";
8+
import { ClientModel } from "@/app/store/client-model";
9+
import { atoms } from "@/app/store/global";
10+
import { disableGlobalKeybindings, enableGlobalKeybindings, globalRefocus } from "@/app/store/keymodel";
1611
import { modalsModel } from "@/app/store/modalmodel";
1712
import * as WOS from "@/app/store/wos";
1813
import { RpcApi } from "@/app/store/wshclientapi";
1914
import { TabRpcClient } from "@/app/store/wshrpcutil";
15+
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
16+
import * as services from "@/store/services";
2017
import { fireAndForget } from "@/util/util";
2118
import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai";
19+
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
20+
import { useEffect, useRef, useState } from "react";
21+
import { debounce } from "throttle-debounce";
2222

2323
// Page flow:
2424
// init -> (telemetry enabled) -> features
@@ -30,7 +30,7 @@ const pageNameAtom: PrimitiveAtom<PageName> = atom<PageName>("init");
3030

3131
const InitPage = ({ isCompact }: { isCompact: boolean }) => {
3232
const settings = useAtomValue(atoms.settingsAtom);
33-
const clientData = useAtomValue(atoms.client);
33+
const clientData = useAtomValue(ClientModel.getInstance().clientAtom);
3434
const [telemetryEnabled, setTelemetryEnabled] = useState<boolean>(!!settings["telemetry:enabled"]);
3535
const setPageName = useSetAtom(pageNameAtom);
3636

@@ -157,7 +157,7 @@ const NoTelemetryStarPage = ({ isCompact }: { isCompact: boolean }) => {
157157
const setPageName = useSetAtom(pageNameAtom);
158158

159159
const handleStarClick = async () => {
160-
const clientId = globalStore.get(atoms.clientId);
160+
const clientId = ClientModel.getInstance().clientId;
161161
await RpcApi.SetMetaCommand(TabRpcClient, {
162162
oref: WOS.makeORef("client", clientId),
163163
meta: { "onboarding:githubstar": true },
@@ -167,7 +167,7 @@ const NoTelemetryStarPage = ({ isCompact }: { isCompact: boolean }) => {
167167
};
168168

169169
const handleMaybeLater = async () => {
170-
const clientId = globalStore.get(atoms.clientId);
170+
const clientId = ClientModel.getInstance().clientId;
171171
await RpcApi.SetMetaCommand(TabRpcClient, {
172172
oref: WOS.makeORef("client", clientId),
173173
meta: { "onboarding:githubstar": false },
@@ -227,7 +227,7 @@ const FeaturesPage = () => {
227227
const NewInstallOnboardingModal = () => {
228228
const modalRef = useRef<HTMLDivElement | null>(null);
229229
const [pageName, setPageName] = useAtom(pageNameAtom);
230-
const clientData = useAtomValue(atoms.client);
230+
const clientData = useAtomValue(ClientModel.getInstance().clientAtom);
231231
const [isCompact, setIsCompact] = useState<boolean>(window.innerHeight < 800);
232232

233233
const updateModalHeight = () => {

0 commit comments

Comments
 (0)