gloomberb 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/@opentui%2Fcore@0.1.90.patch +48 -24
- package/src/app.tsx +158 -57
- package/src/capabilities/factories.ts +3 -0
- package/src/cli/registry.test.ts +0 -1
- package/src/components/chart/chart-controller.ts +0 -1
- package/src/components/chart/chart-data.ts +1 -31
- package/src/components/chart/chart-renderer.ts +94 -12
- package/src/components/chart/chart-resolution.test.ts +0 -36
- package/src/components/chart/chart-resolution.ts +0 -27
- package/src/components/chart/chart.test.ts +60 -45
- package/src/components/chart/comparison-chart-data.test.ts +1 -5
- package/src/components/chart/comparison-chart-data.ts +1 -4
- package/src/components/chart/comparison-chart-renderer.test.ts +26 -0
- package/src/components/chart/comparison-chart-renderer.ts +13 -6
- package/src/components/chart/comparison-stock-chart.tsx +63 -39
- package/src/components/chart/price-axis-labels.tsx +114 -0
- package/src/components/chart/static-chart-surface.tsx +30 -4
- package/src/components/chart/stock-chart.test.ts +0 -16
- package/src/components/chart/stock-chart.tsx +69 -41
- package/src/components/chart/time-axis-label.tsx +120 -0
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -28
- package/src/components/command-bar/command-bar.test.tsx +182 -36
- package/src/components/command-bar/command-bar.tsx +943 -1016
- package/src/components/command-bar/command-registry.ts +0 -10
- package/src/components/command-bar/confirm-body.tsx +51 -0
- package/src/components/command-bar/helpers.ts +2 -5
- package/src/components/command-bar/list-model.ts +76 -0
- package/src/components/command-bar/theme-picker.tsx +254 -0
- package/src/components/command-bar/view-model.test.ts +4 -4
- package/src/components/command-bar/view-model.ts +1 -20
- package/src/components/command-bar/workflow-ops.ts +4 -0
- package/src/components/command-bar/workflow-types.ts +1 -2
- package/src/components/data-table-stack-view.test.tsx +39 -4
- package/src/components/data-table-stack-view.tsx +11 -104
- package/src/components/data-table-view.tsx +43 -79
- package/src/components/feed-data-table-stack-view.tsx +10 -17
- package/src/components/feed-stack-controller.ts +39 -0
- package/src/components/index.ts +5 -1
- package/src/components/layout/detached-pane-shell.tsx +29 -10
- package/src/components/layout/floating-pane.tsx +35 -4
- package/src/components/layout/header.tsx +12 -7
- package/src/components/layout/pane-content.test.tsx +64 -0
- package/src/components/layout/pane-content.tsx +2 -0
- package/src/components/layout/pane-cycle-order.test.ts +30 -0
- package/src/components/layout/pane-cycle-order.ts +32 -0
- package/src/components/layout/pane-footer.tsx +11 -4
- package/src/components/layout/pane-header.tsx +5 -5
- package/src/components/layout/pane-sizing.ts +9 -2
- package/src/components/layout/pane-title.ts +4 -1
- package/src/components/layout/pane.tsx +11 -5
- package/src/components/layout/shell.test.tsx +264 -22
- package/src/components/layout/shell.tsx +46 -8
- package/src/components/layout/status-bar.tsx +2 -0
- package/src/components/onboarding/onboarding-wizard.tsx +1 -2
- package/src/components/pane-settings-dialog.tsx +12 -4
- package/src/components/pane-template-wizard.test.tsx +3 -4
- package/src/components/speedometer-gauge.test.tsx +45 -0
- package/src/components/speedometer-gauge.tsx +562 -0
- package/src/components/table-view-shared.tsx +218 -0
- package/src/components/ticker-list-table-view.tsx +57 -73
- package/src/components/ticker-list-table.test.tsx +82 -1
- package/src/components/ticker-list-table.tsx +22 -9
- package/src/components/ui/confirm-dialog.tsx +66 -0
- package/src/components/ui/data-table.tsx +30 -19
- package/src/components/ui/frame.tsx +2 -0
- package/src/components/ui/index.ts +0 -1
- package/src/components/ui/page-stack-view.tsx +39 -10
- package/src/components/ui/table-layout.test.ts +42 -0
- package/src/components/ui/table-layout.ts +49 -4
- package/src/components/ui/tabs.tsx +34 -1
- package/src/components/ui/ui.test.tsx +168 -21
- package/src/core/state/app-state.ts +10 -17
- package/src/market-data/coordinator.test.ts +76 -1
- package/src/market-data/coordinator.ts +218 -49
- package/src/news/aggregator.test.ts +49 -1
- package/src/news/aggregator.ts +48 -3
- package/src/news/hooks.ts +6 -2
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +46 -16
- package/src/plugins/builtin/ai/providers.ts +0 -17
- package/src/plugins/builtin/ai/screener-pane.test.tsx +3 -4
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -5
- package/src/plugins/builtin/analytics/index.tsx +1 -18
- package/src/plugins/builtin/ask-ai.test.tsx +89 -2
- package/src/plugins/builtin/broker-manager/index.test.tsx +2 -2
- package/src/plugins/builtin/broker-manager/index.tsx +19 -36
- package/src/plugins/builtin/chat-controller.test.ts +188 -1
- package/src/plugins/builtin/chat-controller.ts +537 -220
- package/src/plugins/builtin/chat.test.tsx +431 -13
- package/src/plugins/builtin/chat.tsx +1134 -227
- package/src/plugins/builtin/cloud-tweets.tsx +1 -1
- package/src/plugins/builtin/comparison-chart.test.tsx +53 -2
- package/src/plugins/builtin/comparison-chart.tsx +11 -5
- package/src/plugins/builtin/correlation/index.tsx +1 -6
- package/src/plugins/builtin/debug.tsx +1 -3
- package/src/plugins/builtin/econ/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +72 -0
- package/src/plugins/builtin/fear-greed/fear-greed-data.ts +366 -0
- package/src/plugins/builtin/fear-greed/index.tsx +587 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/help.tsx +2 -2
- package/src/plugins/builtin/holders/index.test.ts +13 -1
- package/src/plugins/builtin/holders/index.tsx +64 -29
- package/src/plugins/builtin/insider/index.tsx +22 -89
- package/src/plugins/builtin/market-movers/index.tsx +13 -19
- package/src/plugins/builtin/market-movers/screener.test.ts +39 -1
- package/src/plugins/builtin/market-movers/screener.ts +70 -17
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +3 -2
- package/src/plugins/builtin/news-wire/index.tsx +41 -2
- package/src/plugins/builtin/news-wire/industry-pane.tsx +4 -18
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +46 -4
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-table.tsx +11 -11
- package/src/plugins/builtin/news.tsx +4 -16
- package/src/plugins/builtin/notes.tsx +19 -55
- package/src/plugins/builtin/options.test.tsx +58 -2
- package/src/plugins/builtin/options.tsx +110 -65
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane.tsx +16 -39
- package/src/plugins/builtin/research/index.test.ts +94 -0
- package/src/plugins/builtin/research/index.tsx +164 -33
- package/src/plugins/builtin/sec-filing-display.tsx +84 -0
- package/src/plugins/builtin/sec.tsx +13 -66
- package/src/plugins/builtin/sectors/index.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +28 -22
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +3 -4
- package/src/plugins/builtin/ticker-detail.test.tsx +38 -0
- package/src/plugins/builtin/world-indices/index.tsx +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/ibkr/broker-adapter.ts +1 -3
- package/src/plugins/ibkr/gateway-service-native.ts +1 -5
- package/src/plugins/ibkr/gateway-service.ts +1 -1
- package/src/plugins/ibkr/index.test.tsx +0 -2
- package/src/plugins/ibkr/trade-tab.test.tsx +3 -4
- package/src/plugins/pane-manager.ts +0 -13
- package/src/plugins/plugin-runtime.test.tsx +40 -6
- package/src/plugins/plugin-runtime.tsx +20 -10
- package/src/plugins/prediction-markets/cli.ts +0 -1
- package/src/plugins/prediction-markets/controller-data.ts +0 -2
- package/src/plugins/prediction-markets/controller.ts +0 -15
- package/src/plugins/prediction-markets/detail/pane.tsx +1 -0
- package/src/plugins/prediction-markets/pane.test.tsx +3 -11
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -1
- package/src/plugins/registry.ts +13 -42
- package/src/plugins/ticker-navigation.test.ts +66 -0
- package/src/plugins/ticker-navigation.ts +44 -0
- package/src/react/input.ts +2 -1
- package/src/renderers/electrobun/bun/index.ts +32 -11
- package/src/renderers/electrobun/bun/window-focus.ts +37 -0
- package/src/renderers/electrobun/shared/protocol.ts +7 -1
- package/src/renderers/electrobun/view/app-services.ts +88 -31
- package/src/renderers/electrobun/view/backend-rpc.ts +15 -0
- package/src/renderers/electrobun/view/data-table.tsx +138 -101
- package/src/renderers/electrobun/view/desktop-controls.tsx +42 -20
- package/src/renderers/electrobun/view/desktop-window-bridge.ts +12 -1
- package/src/renderers/electrobun/view/dialog-host.tsx +13 -1
- package/src/renderers/electrobun/view/input-host.tsx +74 -10
- package/src/renderers/electrobun/view/key-event.ts +1 -1
- package/src/renderers/electrobun/view/main.tsx +18 -1
- package/src/renderers/electrobun/view/styles.css +329 -0
- package/src/renderers/electrobun/view/toast-host.tsx +29 -3
- package/src/renderers/electrobun/view/ui-host.tsx +42 -0
- package/src/renderers/opentui/chart-surface.tsx +179 -0
- package/src/renderers/opentui/input-host.tsx +1 -1
- package/src/renderers/opentui/start.tsx +48 -30
- package/src/renderers/opentui/test-utils.tsx +17 -0
- package/src/renderers/opentui/ui-host.tsx +5 -2
- package/src/sources/gloomberb-cloud.test.ts +86 -1
- package/src/sources/gloomberb-cloud.ts +117 -21
- package/src/sources/provider-router.test.ts +122 -0
- package/src/sources/provider-router.ts +198 -15
- package/src/sources/yahoo-finance.test.ts +44 -0
- package/src/sources/yahoo-finance.ts +6 -0
- package/src/state/app-bootstrap.ts +20 -7
- package/src/state/app-context-selector.test.tsx +189 -4
- package/src/state/app-context.test.ts +65 -0
- package/src/state/app-context.tsx +48 -16
- package/src/state/use-inline-tickers.test.tsx +73 -0
- package/src/state/use-inline-tickers.ts +19 -10
- package/src/test-support/plugin-runtime.ts +3 -2
- package/src/theme/colors.test.ts +75 -0
- package/src/theme/colors.ts +76 -6
- package/src/theme/theme-context.tsx +26 -0
- package/src/theme/themes.test.ts +23 -1
- package/src/theme/themes.ts +115 -0
- package/src/types/broker.ts +1 -1
- package/src/types/capability-route-source.ts +1 -0
- package/src/types/data-provider.ts +15 -2
- package/src/types/desktop-window.ts +6 -0
- package/src/types/financials.ts +2 -0
- package/src/types/plugin.ts +1 -3
- package/src/ui/host.tsx +7 -0
- package/src/utils/api-client.test.ts +32 -0
- package/src/utils/api-client.ts +119 -18
- package/src/utils/back-navigation.ts +24 -1
- package/src/utils/double-escape-close.test.ts +33 -0
- package/src/utils/double-escape-close.ts +40 -0
- package/src/utils/throttled-fetch.test.ts +22 -0
- package/src/utils/throttled-fetch.ts +3 -1
- package/src/utils/ticker-search.ts +53 -9
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/feed-pane.tsx +0 -21
- package/src/plugins/builtin/news-wire/top-pane.tsx +0 -21
|
@@ -3,7 +3,9 @@ import { act, useRef, type Dispatch } from "react";
|
|
|
3
3
|
import { testRender } from "../renderers/opentui/test-utils";
|
|
4
4
|
import { AppProvider, PaneInstanceProvider, useAppDispatch, useAppSelector, usePaneTicker, type AppAction } from "./app-context";
|
|
5
5
|
import { cloneLayout, createDefaultConfig, type AppConfig } from "../types/config";
|
|
6
|
-
import { applyTheme
|
|
6
|
+
import { applyTheme } from "../theme/colors";
|
|
7
|
+
import { useThemeId } from "../theme/theme-context";
|
|
8
|
+
import type { DesktopSharedStateSnapshot, DesktopThemePreviewState, DesktopWindowBridge } from "../types/desktop-window";
|
|
7
9
|
|
|
8
10
|
const TEST_PANE_ID = "ticker-detail:test";
|
|
9
11
|
|
|
@@ -20,6 +22,7 @@ function createTickerDetailConfig(symbol: string): AppConfig {
|
|
|
20
22
|
binding: { kind: "fixed" as const, symbol },
|
|
21
23
|
}],
|
|
22
24
|
floating: [],
|
|
25
|
+
detached: [],
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
return {
|
|
@@ -43,7 +46,46 @@ function PaneTickerHarness() {
|
|
|
43
46
|
|
|
44
47
|
function ThemeSelectorHarness() {
|
|
45
48
|
const focusedPaneId = useAppSelector((state) => state.focusedPaneId);
|
|
46
|
-
|
|
49
|
+
const themeId = useThemeId();
|
|
50
|
+
return <text>{`${focusedPaneId ?? "none"}:${themeId}`}</text>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function FocusSelectorHarness() {
|
|
54
|
+
const renderCountRef = useRef(0);
|
|
55
|
+
renderCountRef.current += 1;
|
|
56
|
+
const focusedPaneId = useAppSelector((state) => state.focusedPaneId);
|
|
57
|
+
return <text>{`${focusedPaneId ?? "none"}:${renderCountRef.current}`}</text>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createDesktopBridge(
|
|
61
|
+
kind: "main" | "detached",
|
|
62
|
+
calls: {
|
|
63
|
+
mainSnapshots?: DesktopSharedStateSnapshot[];
|
|
64
|
+
themePreviews?: DesktopThemePreviewState[];
|
|
65
|
+
onThemePreviewSubscribe?: (listener: (preview: DesktopThemePreviewState) => void) => void;
|
|
66
|
+
} = {},
|
|
67
|
+
): DesktopWindowBridge {
|
|
68
|
+
return {
|
|
69
|
+
kind,
|
|
70
|
+
paneId: kind === "detached" ? TEST_PANE_ID : undefined,
|
|
71
|
+
syncMainState: kind === "main"
|
|
72
|
+
? async (snapshot) => {
|
|
73
|
+
calls.mainSnapshots?.push(snapshot);
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
syncThemePreview: kind === "main"
|
|
77
|
+
? async (preview) => {
|
|
78
|
+
calls.themePreviews?.push(preview);
|
|
79
|
+
}
|
|
80
|
+
: undefined,
|
|
81
|
+
subscribeState: () => () => {},
|
|
82
|
+
subscribeThemePreview: kind === "detached"
|
|
83
|
+
? (listener) => {
|
|
84
|
+
calls.onThemePreviewSubscribe?.(listener);
|
|
85
|
+
return () => {};
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
88
|
+
};
|
|
47
89
|
}
|
|
48
90
|
|
|
49
91
|
describe("pane selectors", () => {
|
|
@@ -100,7 +142,28 @@ describe("pane selectors", () => {
|
|
|
100
142
|
expect(testSetup.captureCharFrame()).toContain("AAPL:1");
|
|
101
143
|
});
|
|
102
144
|
|
|
103
|
-
test("
|
|
145
|
+
test("does not rerender non-theme selector consumers when the theme preview changes", async () => {
|
|
146
|
+
testSetup = await testRender(
|
|
147
|
+
<AppProvider config={createTickerDetailConfig("AAPL")}>
|
|
148
|
+
<DispatchCapture />
|
|
149
|
+
<FocusSelectorHarness />
|
|
150
|
+
</AppProvider>,
|
|
151
|
+
{ width: 32, height: 4 },
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await testSetup.renderOnce();
|
|
155
|
+
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:1`);
|
|
156
|
+
|
|
157
|
+
await act(() => {
|
|
158
|
+
capturedDispatch?.({ type: "PREVIEW_THEME", theme: "green" });
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await testSetup.renderOnce();
|
|
162
|
+
await testSetup.renderOnce();
|
|
163
|
+
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:1`);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("rerenders theme hook consumers when the theme changes", async () => {
|
|
104
167
|
testSetup = await testRender(
|
|
105
168
|
<AppProvider config={createTickerDetailConfig("AAPL")}>
|
|
106
169
|
<DispatchCapture />
|
|
@@ -121,7 +184,7 @@ describe("pane selectors", () => {
|
|
|
121
184
|
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:green`);
|
|
122
185
|
});
|
|
123
186
|
|
|
124
|
-
test("rerenders
|
|
187
|
+
test("rerenders theme hook consumers when the theme preview changes", async () => {
|
|
125
188
|
testSetup = await testRender(
|
|
126
189
|
<AppProvider config={createTickerDetailConfig("AAPL")}>
|
|
127
190
|
<DispatchCapture />
|
|
@@ -142,6 +205,33 @@ describe("pane selectors", () => {
|
|
|
142
205
|
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:green`);
|
|
143
206
|
});
|
|
144
207
|
|
|
208
|
+
test("set theme updates the theme hook and clears the active preview", async () => {
|
|
209
|
+
testSetup = await testRender(
|
|
210
|
+
<AppProvider config={createTickerDetailConfig("AAPL")}>
|
|
211
|
+
<DispatchCapture />
|
|
212
|
+
<ThemeSelectorHarness />
|
|
213
|
+
</AppProvider>,
|
|
214
|
+
{ width: 32, height: 4 },
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
await testSetup.renderOnce();
|
|
218
|
+
await act(() => {
|
|
219
|
+
capturedDispatch?.({ type: "PREVIEW_THEME", theme: "green" });
|
|
220
|
+
});
|
|
221
|
+
await testSetup.renderOnce();
|
|
222
|
+
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:green`);
|
|
223
|
+
|
|
224
|
+
await act(() => {
|
|
225
|
+
capturedDispatch?.({ type: "SET_THEME", theme: "red" });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await testSetup.renderOnce();
|
|
229
|
+
await testSetup.renderOnce();
|
|
230
|
+
const frame = testSetup.captureCharFrame();
|
|
231
|
+
expect(frame).toContain(`${TEST_PANE_ID}:red`);
|
|
232
|
+
expect(frame).not.toContain(`${TEST_PANE_ID}:green`);
|
|
233
|
+
});
|
|
234
|
+
|
|
145
235
|
test("falls back to the committed theme when theme preview clears", async () => {
|
|
146
236
|
testSetup = await testRender(
|
|
147
237
|
<AppProvider config={createTickerDetailConfig("AAPL")}>
|
|
@@ -181,4 +271,99 @@ describe("pane selectors", () => {
|
|
|
181
271
|
await testSetup.renderOnce();
|
|
182
272
|
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:green`);
|
|
183
273
|
});
|
|
274
|
+
|
|
275
|
+
test("uses the initial desktop theme preview on the first provider render", async () => {
|
|
276
|
+
testSetup = await testRender(
|
|
277
|
+
<AppProvider config={createTickerDetailConfig("AAPL")} initialThemePreview={{ theme: "green" }}>
|
|
278
|
+
<ThemeSelectorHarness />
|
|
279
|
+
</AppProvider>,
|
|
280
|
+
{ width: 32, height: 4 },
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
await testSetup.renderOnce();
|
|
284
|
+
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:green`);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("desktop main preview sync does not publish the full state snapshot", async () => {
|
|
288
|
+
const mainSnapshots: DesktopSharedStateSnapshot[] = [];
|
|
289
|
+
const themePreviews: DesktopThemePreviewState[] = [];
|
|
290
|
+
const bridge = createDesktopBridge("main", { mainSnapshots, themePreviews });
|
|
291
|
+
|
|
292
|
+
testSetup = await testRender(
|
|
293
|
+
<AppProvider config={createTickerDetailConfig("AAPL")} desktopBridge={bridge}>
|
|
294
|
+
<DispatchCapture />
|
|
295
|
+
<ThemeSelectorHarness />
|
|
296
|
+
</AppProvider>,
|
|
297
|
+
{ width: 32, height: 4 },
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
await testSetup.renderOnce();
|
|
301
|
+
mainSnapshots.length = 0;
|
|
302
|
+
themePreviews.length = 0;
|
|
303
|
+
|
|
304
|
+
await act(() => {
|
|
305
|
+
capturedDispatch?.({ type: "PREVIEW_THEME", theme: "green" });
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
await testSetup.renderOnce();
|
|
309
|
+
await testSetup.renderOnce();
|
|
310
|
+
expect(themePreviews).toEqual([{ theme: "green" }]);
|
|
311
|
+
expect(mainSnapshots).toHaveLength(0);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("desktop committed theme changes still sync through the config snapshot", async () => {
|
|
315
|
+
const mainSnapshots: DesktopSharedStateSnapshot[] = [];
|
|
316
|
+
const themePreviews: DesktopThemePreviewState[] = [];
|
|
317
|
+
const bridge = createDesktopBridge("main", { mainSnapshots, themePreviews });
|
|
318
|
+
|
|
319
|
+
testSetup = await testRender(
|
|
320
|
+
<AppProvider config={createTickerDetailConfig("AAPL")} desktopBridge={bridge}>
|
|
321
|
+
<DispatchCapture />
|
|
322
|
+
<ThemeSelectorHarness />
|
|
323
|
+
</AppProvider>,
|
|
324
|
+
{ width: 32, height: 4 },
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
await testSetup.renderOnce();
|
|
328
|
+
mainSnapshots.length = 0;
|
|
329
|
+
themePreviews.length = 0;
|
|
330
|
+
|
|
331
|
+
await act(() => {
|
|
332
|
+
capturedDispatch?.({ type: "SET_THEME", theme: "green" });
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
await testSetup.renderOnce();
|
|
336
|
+
await testSetup.renderOnce();
|
|
337
|
+
expect(mainSnapshots).toHaveLength(1);
|
|
338
|
+
expect(mainSnapshots[0]?.config.theme).toBe("green");
|
|
339
|
+
expect(themePreviews).toHaveLength(0);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("desktop detached windows apply theme preview messages locally", async () => {
|
|
343
|
+
let previewListener: ((preview: DesktopThemePreviewState) => void) | null = null;
|
|
344
|
+
const bridge = createDesktopBridge("detached", {
|
|
345
|
+
onThemePreviewSubscribe: (listener) => {
|
|
346
|
+
previewListener = listener;
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
testSetup = await testRender(
|
|
351
|
+
<AppProvider config={createTickerDetailConfig("AAPL")} desktopBridge={bridge}>
|
|
352
|
+
<ThemeSelectorHarness />
|
|
353
|
+
</AppProvider>,
|
|
354
|
+
{ width: 32, height: 4 },
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
await testSetup.renderOnce();
|
|
358
|
+
expect(previewListener).not.toBeNull();
|
|
359
|
+
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:amber`);
|
|
360
|
+
|
|
361
|
+
await act(() => {
|
|
362
|
+
previewListener?.({ theme: "green" });
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
await testSetup.renderOnce();
|
|
366
|
+
await testSetup.renderOnce();
|
|
367
|
+
expect(testSetup.captureCharFrame()).toContain(`${TEST_PANE_ID}:green`);
|
|
368
|
+
});
|
|
184
369
|
});
|
|
@@ -111,6 +111,38 @@ describe("resolveTickerForPane", () => {
|
|
|
111
111
|
)?.zIndex).toBe(21);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
test("raises the session-focused floating pane above tied saved z-order", () => {
|
|
115
|
+
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
116
|
+
config.layout = {
|
|
117
|
+
...config.layout,
|
|
118
|
+
dockRoot: null,
|
|
119
|
+
floating: [
|
|
120
|
+
{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 60, height: 22, zIndex: 50 },
|
|
121
|
+
{ instanceId: "chat:main", x: 8, y: 4, width: 48, height: 18, zIndex: 50 },
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
config.layouts = config.layouts.map((entry, index) => (
|
|
125
|
+
index === config.activeLayoutIndex ? { ...entry, layout: cloneLayout(config.layout) } : entry
|
|
126
|
+
));
|
|
127
|
+
const sessionSnapshot: AppSessionSnapshot = {
|
|
128
|
+
paneState: {},
|
|
129
|
+
focusedPaneId: "ticker-detail:main",
|
|
130
|
+
activePanel: "right",
|
|
131
|
+
statusBarVisible: true,
|
|
132
|
+
openPaneIds: ["ticker-detail:main", "chat:main"],
|
|
133
|
+
hydrationTargets: [],
|
|
134
|
+
exchangeCurrencies: ["USD"],
|
|
135
|
+
savedAt: Date.now(),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const state = createInitialState(config, sessionSnapshot);
|
|
139
|
+
const tickerEntry = state.config.layout.floating.find((entry) => entry.instanceId === "ticker-detail:main");
|
|
140
|
+
const chatEntry = state.config.layout.floating.find((entry) => entry.instanceId === "chat:main");
|
|
141
|
+
|
|
142
|
+
expect(tickerEntry?.zIndex).toBe(51);
|
|
143
|
+
expect((tickerEntry?.zIndex ?? 0) > (chatEntry?.zIndex ?? 0)).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
114
146
|
test("raises the desktop-snapshot focused floating pane during hydration", () => {
|
|
115
147
|
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
116
148
|
config.layout = {
|
|
@@ -455,6 +487,39 @@ describe("quote merging", () => {
|
|
|
455
487
|
});
|
|
456
488
|
|
|
457
489
|
describe("layout focus fallback", () => {
|
|
490
|
+
test("starts with the top floating pane focused when no session pane is active", () => {
|
|
491
|
+
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
492
|
+
const layout = {
|
|
493
|
+
...cloneLayout(config.layout),
|
|
494
|
+
dockRoot: { kind: "pane" as const, instanceId: "portfolio-list:main" },
|
|
495
|
+
floating: [
|
|
496
|
+
{ instanceId: "chat:main", x: 4, y: 2, width: 36, height: 10, zIndex: 70 },
|
|
497
|
+
{ instanceId: "ticker-detail:main", x: 12, y: 4, width: 36, height: 10, zIndex: 95 },
|
|
498
|
+
],
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const state = createInitialState({
|
|
502
|
+
...config,
|
|
503
|
+
layout,
|
|
504
|
+
layouts: [{ name: "Default", layout: cloneLayout(layout) }],
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
expect(state.focusedPaneId).toBe("ticker-detail:main");
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("cycles backward from a stale focused pane to the last visible pane", () => {
|
|
511
|
+
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
512
|
+
const state = createInitialState(config);
|
|
513
|
+
state.focusedPaneId = "hidden:stale";
|
|
514
|
+
|
|
515
|
+
const next = appReducer(state, {
|
|
516
|
+
type: "FOCUS_PREV",
|
|
517
|
+
paneOrder: ["portfolio-list:main", "ticker-detail:main", "ticker-detail:nvda"],
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
expect(next.focusedPaneId).toBe("ticker-detail:nvda");
|
|
521
|
+
});
|
|
522
|
+
|
|
458
523
|
test("focuses the highest remaining floating pane when the focused floating pane closes", () => {
|
|
459
524
|
const config = createDefaultConfig("/tmp/gloomberb-test");
|
|
460
525
|
const backgroundPane = createPaneInstance("chat", {
|
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
type ReactNode,
|
|
14
14
|
} from "react";
|
|
15
15
|
import type { SessionStore } from "../data/session-store";
|
|
16
|
-
import {
|
|
16
|
+
import { ThemeProvider } from "../theme/theme-context";
|
|
17
17
|
import {
|
|
18
18
|
findPaneInstance,
|
|
19
19
|
materializeDetachedPanesAsFloating,
|
|
20
20
|
type AppConfig,
|
|
21
21
|
type PaneInstanceConfig,
|
|
22
22
|
} from "../types/config";
|
|
23
|
-
import type { DesktopSharedStateSnapshot, DesktopWindowBridge } from "../types/desktop-window";
|
|
23
|
+
import type { DesktopSharedStateSnapshot, DesktopThemePreviewState, DesktopWindowBridge } from "../types/desktop-window";
|
|
24
24
|
import { setPaneSetting } from "../pane-settings";
|
|
25
25
|
import { useTickerFinancials } from "../market-data/hooks";
|
|
26
26
|
import {
|
|
@@ -142,7 +142,7 @@ export function useAppSelector<T>(selector: (state: AppState) => T): T {
|
|
|
142
142
|
const context = useRequiredAppContext();
|
|
143
143
|
const selectorRef = useRef(selector);
|
|
144
144
|
selectorRef.current = selector;
|
|
145
|
-
const lastSnapshotRef = useRef<{ selection: T
|
|
145
|
+
const lastSnapshotRef = useRef<{ selection: T } | undefined>(undefined);
|
|
146
146
|
|
|
147
147
|
if (!isAppStoreContextValue(context)) {
|
|
148
148
|
return selector(context.state);
|
|
@@ -153,12 +153,11 @@ export function useAppSelector<T>(selector: (state: AppState) => T): T {
|
|
|
153
153
|
() => {
|
|
154
154
|
const state = context.getState();
|
|
155
155
|
const selection = selectorRef.current(state);
|
|
156
|
-
const themeId = getEffectiveThemeId(state);
|
|
157
156
|
const previous = lastSnapshotRef.current;
|
|
158
|
-
if (previous && Object.is(previous.selection, selection)
|
|
157
|
+
if (previous && Object.is(previous.selection, selection)) {
|
|
159
158
|
return previous;
|
|
160
159
|
}
|
|
161
|
-
const nextSnapshot = { selection
|
|
160
|
+
const nextSnapshot = { selection };
|
|
162
161
|
lastSnapshotRef.current = nextSnapshot;
|
|
163
162
|
return nextSnapshot;
|
|
164
163
|
},
|
|
@@ -166,7 +165,6 @@ export function useAppSelector<T>(selector: (state: AppState) => T): T {
|
|
|
166
165
|
const state = context.getState();
|
|
167
166
|
return {
|
|
168
167
|
selection: selectorRef.current(state),
|
|
169
|
-
themeId: getEffectiveThemeId(state),
|
|
170
168
|
};
|
|
171
169
|
},
|
|
172
170
|
);
|
|
@@ -303,6 +301,7 @@ export function AppProvider({
|
|
|
303
301
|
sessionSnapshot = null,
|
|
304
302
|
desktopBridge,
|
|
305
303
|
desktopSnapshot = null,
|
|
304
|
+
initialThemePreview = null,
|
|
306
305
|
}: {
|
|
307
306
|
config: AppConfig;
|
|
308
307
|
children: ReactNode;
|
|
@@ -310,34 +309,40 @@ export function AppProvider({
|
|
|
310
309
|
sessionSnapshot?: AppSessionSnapshot | null;
|
|
311
310
|
desktopBridge?: DesktopWindowBridge;
|
|
312
311
|
desktopSnapshot?: DesktopSharedStateSnapshot | null;
|
|
312
|
+
initialThemePreview?: DesktopThemePreviewState | null;
|
|
313
313
|
}) {
|
|
314
314
|
const [state, rawDispatch] = useReducer(
|
|
315
315
|
appReducer,
|
|
316
|
-
{ config, sessionSnapshot, desktopSnapshot },
|
|
316
|
+
{ config, sessionSnapshot, desktopSnapshot, initialThemePreview },
|
|
317
317
|
(initialState: {
|
|
318
318
|
config: AppConfig;
|
|
319
319
|
sessionSnapshot?: AppSessionSnapshot | null;
|
|
320
320
|
desktopSnapshot?: DesktopSharedStateSnapshot | null;
|
|
321
|
+
initialThemePreview?: DesktopThemePreviewState | null;
|
|
321
322
|
}) => {
|
|
322
323
|
const {
|
|
323
324
|
config: initialConfig,
|
|
324
325
|
sessionSnapshot: initialSessionSnapshot,
|
|
325
326
|
desktopSnapshot: initialDesktopSnapshot,
|
|
327
|
+
initialThemePreview: initialDesktopThemePreview,
|
|
326
328
|
} = initialState;
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
? appReducer(
|
|
329
|
+
const baseState = createInitialState(initialConfig, initialSessionSnapshot);
|
|
330
|
+
const nextState = initialDesktopSnapshot
|
|
331
|
+
? appReducer(baseState, { type: "HYDRATE_DESKTOP_SNAPSHOT", snapshot: initialDesktopSnapshot })
|
|
332
|
+
: baseState;
|
|
333
|
+
return initialDesktopThemePreview
|
|
334
|
+
? appReducer(nextState, { type: "PREVIEW_THEME", theme: initialDesktopThemePreview.theme })
|
|
330
335
|
: nextState;
|
|
331
336
|
},
|
|
332
337
|
);
|
|
333
|
-
|
|
334
|
-
syncTheme(getEffectiveThemeId(state));
|
|
338
|
+
const effectiveThemeId = getEffectiveThemeId(state);
|
|
335
339
|
const previousRecentTickers = useRef(state.recentTickers);
|
|
336
340
|
const stateRef = useRef(state);
|
|
337
341
|
const listenersRef = useRef(new Set<() => void>());
|
|
338
342
|
const storeRef = useRef<AppContextStoreValue | null>(null);
|
|
339
343
|
const lastMainSyncRef = useRef<string | null>(null);
|
|
340
344
|
const lastDetachedPaneSyncRef = useRef<string | null>(null);
|
|
345
|
+
const lastThemePreviewSyncRef = useRef<string | null | undefined>(undefined);
|
|
341
346
|
|
|
342
347
|
stateRef.current = state;
|
|
343
348
|
|
|
@@ -421,12 +426,35 @@ export function AppProvider({
|
|
|
421
426
|
|
|
422
427
|
useEffect(() => {
|
|
423
428
|
if (desktopBridge?.kind !== "main" || !desktopBridge.syncMainState) return;
|
|
424
|
-
const snapshot = buildDesktopSnapshot(
|
|
429
|
+
const snapshot = buildDesktopSnapshot(stateRef.current);
|
|
425
430
|
const signature = serializeDesktopSnapshot(snapshot);
|
|
426
431
|
if (signature === lastMainSyncRef.current) return;
|
|
427
432
|
lastMainSyncRef.current = signature;
|
|
428
433
|
void desktopBridge.syncMainState(snapshot);
|
|
429
|
-
}, [
|
|
434
|
+
}, [
|
|
435
|
+
buildDesktopSnapshot,
|
|
436
|
+
desktopBridge,
|
|
437
|
+
serializeDesktopSnapshot,
|
|
438
|
+
state.activePanel,
|
|
439
|
+
state.config,
|
|
440
|
+
state.focusedPaneId,
|
|
441
|
+
state.paneState,
|
|
442
|
+
state.statusBarVisible,
|
|
443
|
+
]);
|
|
444
|
+
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
if (desktopBridge?.kind !== "main" || !desktopBridge.syncThemePreview) return;
|
|
447
|
+
if (lastThemePreviewSyncRef.current === state.themePreview) return;
|
|
448
|
+
lastThemePreviewSyncRef.current = state.themePreview;
|
|
449
|
+
void desktopBridge.syncThemePreview({ theme: state.themePreview });
|
|
450
|
+
}, [desktopBridge, state.themePreview]);
|
|
451
|
+
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
if (desktopBridge?.kind !== "detached" || !desktopBridge.subscribeThemePreview) return;
|
|
454
|
+
return desktopBridge.subscribeThemePreview((preview) => {
|
|
455
|
+
dispatch({ type: "PREVIEW_THEME", theme: preview.theme });
|
|
456
|
+
});
|
|
457
|
+
}, [desktopBridge, dispatch]);
|
|
430
458
|
|
|
431
459
|
useEffect(() => {
|
|
432
460
|
if (desktopBridge?.kind !== "detached" || !desktopBridge.replaceDetachedPaneState || !desktopBridge.paneId) return;
|
|
@@ -445,5 +473,9 @@ export function AppProvider({
|
|
|
445
473
|
|
|
446
474
|
usePersistSessionSnapshot(sessionStore, state, APP_SESSION_ID, APP_SESSION_SCHEMA_VERSION);
|
|
447
475
|
|
|
448
|
-
return
|
|
476
|
+
return (
|
|
477
|
+
<ThemeProvider themeId={effectiveThemeId}>
|
|
478
|
+
<AppContext.Provider value={storeRef.current}>{children}</AppContext.Provider>
|
|
479
|
+
</ThemeProvider>
|
|
480
|
+
);
|
|
449
481
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { act } from "react";
|
|
3
|
+
import { testRender } from "../renderers/opentui/test-utils";
|
|
4
|
+
import { setSharedMarketDataCoordinator } from "../market-data/coordinator";
|
|
5
|
+
import { setSharedRegistryForTests, type PluginRegistry } from "../plugins/registry";
|
|
6
|
+
import { createDefaultConfig } from "../types/config";
|
|
7
|
+
import { AppContext, createInitialState } from "./app-context";
|
|
8
|
+
import { useInlineTickers } from "./use-inline-tickers";
|
|
9
|
+
|
|
10
|
+
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
11
|
+
|
|
12
|
+
function InlineTickerHarness() {
|
|
13
|
+
const { catalog } = useInlineTickers(["$LGD1L"]);
|
|
14
|
+
return <text>{catalog.LGD1L?.status ?? "none"}</text>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
testSetup?.renderer.destroy();
|
|
19
|
+
testSetup = undefined;
|
|
20
|
+
setSharedMarketDataCoordinator(null);
|
|
21
|
+
setSharedRegistryForTests(undefined);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("useInlineTickers", () => {
|
|
25
|
+
test("does not leak rejected quote lookups for existing inline tickers", async () => {
|
|
26
|
+
const config = createDefaultConfig("/tmp/gloomberb-inline-tickers-test");
|
|
27
|
+
const state = createInitialState(config);
|
|
28
|
+
state.tickers.set("LGD1L", {
|
|
29
|
+
metadata: {
|
|
30
|
+
ticker: "LGD1L",
|
|
31
|
+
exchange: "NASDAQ",
|
|
32
|
+
currency: "USD",
|
|
33
|
+
name: "Unsupported quote",
|
|
34
|
+
portfolios: [],
|
|
35
|
+
watchlists: [],
|
|
36
|
+
positions: [],
|
|
37
|
+
broker_contracts: [],
|
|
38
|
+
custom: {},
|
|
39
|
+
tags: [],
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const actions: unknown[] = [];
|
|
43
|
+
setSharedRegistryForTests({
|
|
44
|
+
marketData: {
|
|
45
|
+
getQuote: async () => {
|
|
46
|
+
throw new Error("No quote provider available for LGD1L");
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
pinTicker: () => {},
|
|
50
|
+
} as unknown as PluginRegistry);
|
|
51
|
+
|
|
52
|
+
await act(async () => {
|
|
53
|
+
testSetup = await testRender(
|
|
54
|
+
<AppContext value={{ state, dispatch: (action) => actions.push(action) }}>
|
|
55
|
+
<InlineTickerHarness />
|
|
56
|
+
</AppContext>,
|
|
57
|
+
{ width: 20, height: 1 },
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await act(async () => {
|
|
62
|
+
await testSetup!.renderOnce();
|
|
63
|
+
await Promise.resolve();
|
|
64
|
+
await Promise.resolve();
|
|
65
|
+
});
|
|
66
|
+
await act(async () => {
|
|
67
|
+
await testSetup!.renderOnce();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(testSetup.captureCharFrame()).toContain("missing");
|
|
71
|
+
expect(actions).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -18,6 +18,7 @@ export interface InlineTickerCatalogEntry {
|
|
|
18
18
|
const resolutionInFlight = new Map<string, Promise<void>>();
|
|
19
19
|
const quoteInFlight = new Map<string, Promise<void>>();
|
|
20
20
|
const missingSymbols = new Set<string>();
|
|
21
|
+
const quoteMissingSymbols = new Set<string>();
|
|
21
22
|
const initialQuoteRequested = new Set<string>();
|
|
22
23
|
|
|
23
24
|
function normalizeSymbols(texts: readonly string[]): string[] {
|
|
@@ -32,7 +33,7 @@ export function useInlineTickers(texts: readonly string[]): {
|
|
|
32
33
|
const tickers = useAppSelector((state) => state.tickers);
|
|
33
34
|
const financials = useAppSelector((state) => state.financials);
|
|
34
35
|
const registry = getSharedRegistry();
|
|
35
|
-
const [, setRefreshVersion] = useState(0);
|
|
36
|
+
const [refreshVersion, setRefreshVersion] = useState(0);
|
|
36
37
|
const textsKey = texts.join("\u0000");
|
|
37
38
|
const symbols = useMemo(() => normalizeSymbols(texts), [textsKey]);
|
|
38
39
|
const symbolsKey = symbols.join("|");
|
|
@@ -54,6 +55,7 @@ export function useInlineTickers(texts: readonly string[]): {
|
|
|
54
55
|
|
|
55
56
|
if (currentTicker && currentQuote) {
|
|
56
57
|
missingSymbols.delete(symbol);
|
|
58
|
+
quoteMissingSymbols.delete(symbol);
|
|
57
59
|
initialQuoteRequested.delete(symbol);
|
|
58
60
|
continue;
|
|
59
61
|
}
|
|
@@ -123,8 +125,12 @@ export function useInlineTickers(texts: readonly string[]): {
|
|
|
123
125
|
}
|
|
124
126
|
: undefined,
|
|
125
127
|
);
|
|
128
|
+
quoteMissingSymbols.delete(symbol);
|
|
126
129
|
current.dispatch({ type: "MERGE_QUOTE", symbol, quote });
|
|
127
130
|
})()
|
|
131
|
+
.catch(() => {
|
|
132
|
+
quoteMissingSymbols.add(symbol);
|
|
133
|
+
})
|
|
128
134
|
.finally(() => {
|
|
129
135
|
quoteInFlight.delete(symbol);
|
|
130
136
|
});
|
|
@@ -162,15 +168,18 @@ export function useInlineTickers(texts: readonly string[]): {
|
|
|
162
168
|
registry?.pinTicker(symbol, { floating: true, paneType: "ticker-detail" });
|
|
163
169
|
}, [registry]);
|
|
164
170
|
|
|
165
|
-
const catalog
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
const catalog = useMemo(() => {
|
|
172
|
+
const entries: Record<string, InlineTickerCatalogEntry> = {};
|
|
173
|
+
for (const symbol of symbols) {
|
|
174
|
+
const ticker = tickers.get(symbol) ?? null;
|
|
175
|
+
const quote = financials.get(symbol)?.quote ?? null;
|
|
176
|
+
const status: InlineTickerStatus = ticker
|
|
177
|
+
? (quote ? "ready" : quoteMissingSymbols.has(symbol) ? "missing" : "loading")
|
|
178
|
+
: (missingSymbols.has(symbol) ? "missing" : "loading");
|
|
179
|
+
entries[symbol] = { status, ticker, quote };
|
|
180
|
+
}
|
|
181
|
+
return entries;
|
|
182
|
+
}, [financials, refreshVersion, symbols, tickers]);
|
|
174
183
|
|
|
175
184
|
return { catalog, openTicker };
|
|
176
185
|
}
|
|
@@ -23,8 +23,9 @@ export function createTestPluginRuntime(
|
|
|
23
23
|
switchTab() {},
|
|
24
24
|
switchPanel() {},
|
|
25
25
|
openCommandBar() {},
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
showPane() {},
|
|
27
|
+
hidePane() {},
|
|
28
|
+
openPaneSettings() {},
|
|
28
29
|
openPluginCommandWorkflow() {},
|
|
29
30
|
notify() {},
|
|
30
31
|
subscribeResumeState: () => () => {},
|