Skip to content

Commit 1360492

Browse files
committed
better way to mock singleton or static keyed models
1 parent f87b8c1 commit 1360492

6 files changed

Lines changed: 53 additions & 34 deletions

File tree

.kilocode/skills/waveenv/SKILL.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ export type MyEnv = WaveEnvSubset<{
8080
}>;
8181
```
8282

83+
### Automatically Included Fields
84+
85+
Every `WaveEnvSubset<T>` automatically includes the mock fields — you never need to declare them:
86+
87+
- `isMock: boolean`
88+
- `mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void`
89+
- `mockModels?: Map<any, any>`
90+
8391
### Rules for Each Section
8492

8593
| Section | Pattern | Notes |

frontend/app/store/keymodel.ts

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

44
import { WaveAIModel } from "@/app/aipanel/waveai-model";
@@ -490,7 +490,7 @@ function tryReinjectKey(event: WaveKeyboardEvent): boolean {
490490
function countTermBlocks(): number {
491491
const allBCMs = getAllBlockComponentModels();
492492
let count = 0;
493-
let gsGetBound = globalStore.get.bind(globalStore);
493+
const gsGetBound = globalStore.get.bind(globalStore);
494494
for (const bcm of allBCMs) {
495495
const viewModel = bcm.viewModel;
496496
if (viewModel.viewType == "term" && viewModel.isBasicTerm?.(gsGetBound)) {

frontend/app/store/tab-model.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
// Copyright 2026, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { useWaveEnv, WaveEnv } from "@/app/waveenv/waveenv";
4+
import { WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
55
import { atom, Atom, PrimitiveAtom } from "jotai";
66
import { createContext, useContext } from "react";
77
import { globalStore } from "./jotaiStore";
88
import * as WOS from "./wos";
99

10+
export type TabModelEnv = WaveEnvSubset<{
11+
wos: WaveEnv["wos"];
12+
}>;
13+
1014
const tabModelCache = new Map<string, TabModel>();
1115
export const activeTabIdAtom = atom<string>(null) as PrimitiveAtom<string>;
1216

1317
export class TabModel {
1418
tabId: string;
15-
waveEnv: WaveEnv;
19+
waveEnv: TabModelEnv;
1620
tabAtom: Atom<Tab>;
1721
tabNumBlocksAtom: Atom<number>;
1822
isTermMultiInput = atom(false) as PrimitiveAtom<boolean>;
1923
metaCache: Map<string, Atom<any>> = new Map();
2024

21-
constructor(tabId: string, waveEnv?: WaveEnv) {
25+
constructor(tabId: string, waveEnv?: TabModelEnv) {
2226
this.tabId = tabId;
2327
this.waveEnv = waveEnv;
2428
this.tabAtom = atom((get) => {
@@ -46,16 +50,25 @@ export class TabModel {
4650
}
4751
}
4852

49-
export function getTabModelByTabId(tabId: string, waveEnv?: WaveEnv): TabModel {
50-
let model = tabModelCache.get(tabId);
53+
export function getTabModelByTabId(tabId: string, waveEnv?: TabModelEnv): TabModel {
54+
if (!waveEnv?.isMock) {
55+
let model = tabModelCache.get(tabId);
56+
if (model == null) {
57+
model = new TabModel(tabId, waveEnv);
58+
tabModelCache.set(tabId, model);
59+
}
60+
return model;
61+
}
62+
const key = `TabModel:${tabId}`;
63+
let model = waveEnv.mockModels.get(key);
5164
if (model == null) {
5265
model = new TabModel(tabId, waveEnv);
53-
tabModelCache.set(tabId, model);
66+
waveEnv.mockModels.set(key, model);
5467
}
5568
return model;
5669
}
5770

58-
export function getActiveTabModel(waveEnv?: WaveEnv): TabModel | null {
71+
export function getActiveTabModel(waveEnv?: TabModelEnv): TabModel | null {
5972
const activeTabId = globalStore.get(activeTabIdAtom);
6073
if (activeTabId == null) {
6174
return null;
@@ -66,22 +79,13 @@ export function getActiveTabModel(waveEnv?: WaveEnv): TabModel | null {
6679
export const TabModelContext = createContext<TabModel | undefined>(undefined);
6780

6881
export function useTabModel(): TabModel {
69-
const waveEnv = useWaveEnv();
7082
const ctxModel = useContext(TabModelContext);
71-
if (waveEnv?.mockTabModel != null) {
72-
return waveEnv.mockTabModel;
73-
}
7483
if (ctxModel == null) {
7584
throw new Error("useTabModel must be used within a TabModelProvider");
7685
}
7786
return ctxModel;
7887
}
7988

8089
export function useTabModelMaybe(): TabModel {
81-
const waveEnv = useWaveEnv();
82-
const ctxModel = useContext(TabModelContext);
83-
if (waveEnv?.mockTabModel != null) {
84-
return waveEnv.mockTabModel;
85-
}
86-
return ctxModel;
90+
return useContext(TabModelContext);
8791
}

frontend/app/waveenv/waveenv.ts

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

4-
import type { TabModel } from "@/app/store/tab-model";
54
import { RpcApiType } from "@/app/store/wshclientapi";
65
import { Atom, PrimitiveAtom } from "jotai";
76
import React from "react";
@@ -35,16 +34,24 @@ type ComplexWaveEnvKeys = {
3534
wos: WaveEnv["wos"];
3635
};
3736

38-
export type WaveEnvSubset<T> = OmitNever<{
39-
[K in keyof T]: K extends keyof ComplexWaveEnvKeys
40-
? Subset<T[K], ComplexWaveEnvKeys[K]>
41-
: K extends keyof WaveEnv
42-
? T[K]
43-
: never;
44-
}>;
37+
type WaveEnvMockFields = {
38+
isMock: WaveEnv["isMock"];
39+
mockSetWaveObj: WaveEnv["mockSetWaveObj"];
40+
mockModels: WaveEnv["mockModels"];
41+
};
42+
43+
export type WaveEnvSubset<T> = WaveEnvMockFields &
44+
OmitNever<{
45+
[K in keyof T]: K extends keyof ComplexWaveEnvKeys
46+
? Subset<T[K], ComplexWaveEnvKeys[K]>
47+
: K extends keyof WaveEnv
48+
? T[K]
49+
: never;
50+
}>;
4551

4652
// default implementation for production is in ./waveenvimpl.ts
4753
export type WaveEnv = {
54+
isMock: boolean;
4855
electron: ElectronApi;
4956
rpc: RpcApiType;
5057
platform: NodeJS.Platform;
@@ -68,7 +75,7 @@ export type WaveEnv = {
6875

6976
// the mock fields are only usable in the preview server (may be be null or throw errors in production)
7077
mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void;
71-
mockTabModel?: TabModel;
78+
mockModels?: Map<any, any>;
7279
};
7380

7481
export const WaveEnvContext = React.createContext<WaveEnv>(null);

frontend/app/waveenv/waveenvimpl.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { isMacOS, isWindows, PLATFORM } from "@/util/platformutil";
1919

2020
export function makeWaveEnvImpl(): WaveEnv {
2121
return {
22+
isMock: false,
2223
electron: (window as any).api,
2324
rpc: RpcApi,
2425
getSettingsKeyAtom,
@@ -41,8 +42,10 @@ export function makeWaveEnvImpl(): WaveEnv {
4142
},
4243
getBlockMetaKeyAtom,
4344
getConnConfigKeyAtom,
45+
4446
mockSetWaveObj: <T extends WaveObj>(_oref: string, _obj: T) => {
4547
throw new Error("mockSetWaveObj is only available in the preview server");
4648
},
49+
mockModels: new Map<any, any>(),
4750
};
4851
}

frontend/preview/mock/mockwaveenv.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import { makeDefaultConnStatus } from "@/app/store/global";
55
import { globalStore } from "@/app/store/jotaiStore";
6-
import { TabModel } from "@/app/store/tab-model";
76
import { handleWaveEvent } from "@/app/store/wps";
87
import { RpcApiType } from "@/app/store/wshclientapi";
98
import { WaveEnv } from "@/app/waveenv/waveenv";
@@ -260,6 +259,7 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv {
260259
},
261260
};
262261
const env = {
262+
isMock: true,
263263
mockEnv: overrides,
264264
electron: {
265265
...previewElectronApi,
@@ -318,7 +318,6 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv {
318318
return [useAtomValue(objAtom), false];
319319
},
320320
},
321-
mockSetWaveObj: mockWosFns.mockSetWaveObj,
322321
getBlockMetaKeyAtom: <T extends keyof MetaType>(blockId: string, key: T) => {
323322
const cacheKey = blockId + "#meta-" + key;
324323
if (!blockMetaKeyAtomCache.has(cacheKey)) {
@@ -343,10 +342,8 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv {
343342
}
344343
return connConfigKeyAtomCache.get(cacheKey) as Atom<ConnKeywords[T]>;
345344
},
346-
mockTabModel: null as TabModel,
345+
mockSetWaveObj: mockWosFns.mockSetWaveObj,
346+
mockModels: new Map<any, any>(),
347347
} as MockWaveEnv;
348-
if (overrides.tabId != null) {
349-
env.mockTabModel = new TabModel(overrides.tabId, env);
350-
}
351348
return env;
352349
}

0 commit comments

Comments
 (0)