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.
Files changed (208) hide show
  1. package/package.json +1 -1
  2. package/patches/@opentui%2Fcore@0.1.90.patch +48 -24
  3. package/src/app.tsx +158 -57
  4. package/src/capabilities/factories.ts +3 -0
  5. package/src/cli/registry.test.ts +0 -1
  6. package/src/components/chart/chart-controller.ts +0 -1
  7. package/src/components/chart/chart-data.ts +1 -31
  8. package/src/components/chart/chart-renderer.ts +94 -12
  9. package/src/components/chart/chart-resolution.test.ts +0 -36
  10. package/src/components/chart/chart-resolution.ts +0 -27
  11. package/src/components/chart/chart.test.ts +60 -45
  12. package/src/components/chart/comparison-chart-data.test.ts +1 -5
  13. package/src/components/chart/comparison-chart-data.ts +1 -4
  14. package/src/components/chart/comparison-chart-renderer.test.ts +26 -0
  15. package/src/components/chart/comparison-chart-renderer.ts +13 -6
  16. package/src/components/chart/comparison-stock-chart.tsx +63 -39
  17. package/src/components/chart/price-axis-labels.tsx +114 -0
  18. package/src/components/chart/static-chart-surface.tsx +30 -4
  19. package/src/components/chart/stock-chart.test.ts +0 -16
  20. package/src/components/chart/stock-chart.tsx +69 -41
  21. package/src/components/chart/time-axis-label.tsx +120 -0
  22. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -28
  23. package/src/components/command-bar/command-bar.test.tsx +182 -36
  24. package/src/components/command-bar/command-bar.tsx +943 -1016
  25. package/src/components/command-bar/command-registry.ts +0 -10
  26. package/src/components/command-bar/confirm-body.tsx +51 -0
  27. package/src/components/command-bar/helpers.ts +2 -5
  28. package/src/components/command-bar/list-model.ts +76 -0
  29. package/src/components/command-bar/theme-picker.tsx +254 -0
  30. package/src/components/command-bar/view-model.test.ts +4 -4
  31. package/src/components/command-bar/view-model.ts +1 -20
  32. package/src/components/command-bar/workflow-ops.ts +4 -0
  33. package/src/components/command-bar/workflow-types.ts +1 -2
  34. package/src/components/data-table-stack-view.test.tsx +39 -4
  35. package/src/components/data-table-stack-view.tsx +11 -104
  36. package/src/components/data-table-view.tsx +43 -79
  37. package/src/components/feed-data-table-stack-view.tsx +10 -17
  38. package/src/components/feed-stack-controller.ts +39 -0
  39. package/src/components/index.ts +5 -1
  40. package/src/components/layout/detached-pane-shell.tsx +29 -10
  41. package/src/components/layout/floating-pane.tsx +35 -4
  42. package/src/components/layout/header.tsx +12 -7
  43. package/src/components/layout/pane-content.test.tsx +64 -0
  44. package/src/components/layout/pane-content.tsx +2 -0
  45. package/src/components/layout/pane-cycle-order.test.ts +30 -0
  46. package/src/components/layout/pane-cycle-order.ts +32 -0
  47. package/src/components/layout/pane-footer.tsx +11 -4
  48. package/src/components/layout/pane-header.tsx +5 -5
  49. package/src/components/layout/pane-sizing.ts +9 -2
  50. package/src/components/layout/pane-title.ts +4 -1
  51. package/src/components/layout/pane.tsx +11 -5
  52. package/src/components/layout/shell.test.tsx +264 -22
  53. package/src/components/layout/shell.tsx +46 -8
  54. package/src/components/layout/status-bar.tsx +2 -0
  55. package/src/components/onboarding/onboarding-wizard.tsx +1 -2
  56. package/src/components/pane-settings-dialog.tsx +12 -4
  57. package/src/components/pane-template-wizard.test.tsx +3 -4
  58. package/src/components/speedometer-gauge.test.tsx +45 -0
  59. package/src/components/speedometer-gauge.tsx +562 -0
  60. package/src/components/table-view-shared.tsx +218 -0
  61. package/src/components/ticker-list-table-view.tsx +57 -73
  62. package/src/components/ticker-list-table.test.tsx +82 -1
  63. package/src/components/ticker-list-table.tsx +22 -9
  64. package/src/components/ui/confirm-dialog.tsx +66 -0
  65. package/src/components/ui/data-table.tsx +30 -19
  66. package/src/components/ui/frame.tsx +2 -0
  67. package/src/components/ui/index.ts +0 -1
  68. package/src/components/ui/page-stack-view.tsx +39 -10
  69. package/src/components/ui/table-layout.test.ts +42 -0
  70. package/src/components/ui/table-layout.ts +49 -4
  71. package/src/components/ui/tabs.tsx +34 -1
  72. package/src/components/ui/ui.test.tsx +168 -21
  73. package/src/core/state/app-state.ts +10 -17
  74. package/src/market-data/coordinator.test.ts +76 -1
  75. package/src/market-data/coordinator.ts +218 -49
  76. package/src/news/aggregator.test.ts +49 -1
  77. package/src/news/aggregator.ts +48 -3
  78. package/src/news/hooks.ts +6 -2
  79. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +46 -16
  80. package/src/plugins/builtin/ai/providers.ts +0 -17
  81. package/src/plugins/builtin/ai/screener-pane.test.tsx +3 -4
  82. package/src/plugins/builtin/ai/screener-pane.tsx +3 -5
  83. package/src/plugins/builtin/analytics/index.tsx +1 -18
  84. package/src/plugins/builtin/ask-ai.test.tsx +89 -2
  85. package/src/plugins/builtin/broker-manager/index.test.tsx +2 -2
  86. package/src/plugins/builtin/broker-manager/index.tsx +19 -36
  87. package/src/plugins/builtin/chat-controller.test.ts +188 -1
  88. package/src/plugins/builtin/chat-controller.ts +537 -220
  89. package/src/plugins/builtin/chat.test.tsx +431 -13
  90. package/src/plugins/builtin/chat.tsx +1134 -227
  91. package/src/plugins/builtin/cloud-tweets.tsx +1 -1
  92. package/src/plugins/builtin/comparison-chart.test.tsx +53 -2
  93. package/src/plugins/builtin/comparison-chart.tsx +11 -5
  94. package/src/plugins/builtin/correlation/index.tsx +1 -6
  95. package/src/plugins/builtin/debug.tsx +1 -3
  96. package/src/plugins/builtin/econ/index.tsx +1 -2
  97. package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +72 -0
  98. package/src/plugins/builtin/fear-greed/fear-greed-data.ts +366 -0
  99. package/src/plugins/builtin/fear-greed/index.tsx +587 -0
  100. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  101. package/src/plugins/builtin/help.test.tsx +2 -2
  102. package/src/plugins/builtin/help.tsx +2 -2
  103. package/src/plugins/builtin/holders/index.test.ts +13 -1
  104. package/src/plugins/builtin/holders/index.tsx +64 -29
  105. package/src/plugins/builtin/insider/index.tsx +22 -89
  106. package/src/plugins/builtin/market-movers/index.tsx +13 -19
  107. package/src/plugins/builtin/market-movers/screener.test.ts +39 -1
  108. package/src/plugins/builtin/market-movers/screener.ts +70 -17
  109. package/src/plugins/builtin/news-wire/breaking-pane.tsx +3 -2
  110. package/src/plugins/builtin/news-wire/index.tsx +41 -2
  111. package/src/plugins/builtin/news-wire/industry-pane.tsx +4 -18
  112. package/src/plugins/builtin/news-wire/news-detail-view.tsx +46 -4
  113. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +3 -2
  114. package/src/plugins/builtin/news-wire/news-table.tsx +11 -11
  115. package/src/plugins/builtin/news.tsx +4 -16
  116. package/src/plugins/builtin/notes.tsx +19 -55
  117. package/src/plugins/builtin/options.test.tsx +58 -2
  118. package/src/plugins/builtin/options.tsx +110 -65
  119. package/src/plugins/builtin/plugin-groups.ts +2 -0
  120. package/src/plugins/builtin/portfolio-list/cli/commands.ts +1 -1
  121. package/src/plugins/builtin/portfolio-list/pane.tsx +16 -39
  122. package/src/plugins/builtin/research/index.test.ts +94 -0
  123. package/src/plugins/builtin/research/index.tsx +164 -33
  124. package/src/plugins/builtin/sec-filing-display.tsx +84 -0
  125. package/src/plugins/builtin/sec.tsx +13 -66
  126. package/src/plugins/builtin/sectors/index.tsx +1 -1
  127. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -1
  128. package/src/plugins/builtin/ticker-detail/index.tsx +0 -1
  129. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -1
  130. package/src/plugins/builtin/ticker-detail/pane.tsx +28 -22
  131. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +3 -4
  132. package/src/plugins/builtin/ticker-detail.test.tsx +38 -0
  133. package/src/plugins/builtin/world-indices/index.tsx +1 -1
  134. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  135. package/src/plugins/ibkr/broker-adapter.ts +1 -3
  136. package/src/plugins/ibkr/gateway-service-native.ts +1 -5
  137. package/src/plugins/ibkr/gateway-service.ts +1 -1
  138. package/src/plugins/ibkr/index.test.tsx +0 -2
  139. package/src/plugins/ibkr/trade-tab.test.tsx +3 -4
  140. package/src/plugins/pane-manager.ts +0 -13
  141. package/src/plugins/plugin-runtime.test.tsx +40 -6
  142. package/src/plugins/plugin-runtime.tsx +20 -10
  143. package/src/plugins/prediction-markets/cli.ts +0 -1
  144. package/src/plugins/prediction-markets/controller-data.ts +0 -2
  145. package/src/plugins/prediction-markets/controller.ts +0 -15
  146. package/src/plugins/prediction-markets/detail/pane.tsx +1 -0
  147. package/src/plugins/prediction-markets/pane.test.tsx +3 -11
  148. package/src/plugins/prediction-markets/test-helpers.tsx +1 -1
  149. package/src/plugins/registry.ts +13 -42
  150. package/src/plugins/ticker-navigation.test.ts +66 -0
  151. package/src/plugins/ticker-navigation.ts +44 -0
  152. package/src/react/input.ts +2 -1
  153. package/src/renderers/electrobun/bun/index.ts +32 -11
  154. package/src/renderers/electrobun/bun/window-focus.ts +37 -0
  155. package/src/renderers/electrobun/shared/protocol.ts +7 -1
  156. package/src/renderers/electrobun/view/app-services.ts +88 -31
  157. package/src/renderers/electrobun/view/backend-rpc.ts +15 -0
  158. package/src/renderers/electrobun/view/data-table.tsx +138 -101
  159. package/src/renderers/electrobun/view/desktop-controls.tsx +42 -20
  160. package/src/renderers/electrobun/view/desktop-window-bridge.ts +12 -1
  161. package/src/renderers/electrobun/view/dialog-host.tsx +13 -1
  162. package/src/renderers/electrobun/view/input-host.tsx +74 -10
  163. package/src/renderers/electrobun/view/key-event.ts +1 -1
  164. package/src/renderers/electrobun/view/main.tsx +18 -1
  165. package/src/renderers/electrobun/view/styles.css +329 -0
  166. package/src/renderers/electrobun/view/toast-host.tsx +29 -3
  167. package/src/renderers/electrobun/view/ui-host.tsx +42 -0
  168. package/src/renderers/opentui/chart-surface.tsx +179 -0
  169. package/src/renderers/opentui/input-host.tsx +1 -1
  170. package/src/renderers/opentui/start.tsx +48 -30
  171. package/src/renderers/opentui/test-utils.tsx +17 -0
  172. package/src/renderers/opentui/ui-host.tsx +5 -2
  173. package/src/sources/gloomberb-cloud.test.ts +86 -1
  174. package/src/sources/gloomberb-cloud.ts +117 -21
  175. package/src/sources/provider-router.test.ts +122 -0
  176. package/src/sources/provider-router.ts +198 -15
  177. package/src/sources/yahoo-finance.test.ts +44 -0
  178. package/src/sources/yahoo-finance.ts +6 -0
  179. package/src/state/app-bootstrap.ts +20 -7
  180. package/src/state/app-context-selector.test.tsx +189 -4
  181. package/src/state/app-context.test.ts +65 -0
  182. package/src/state/app-context.tsx +48 -16
  183. package/src/state/use-inline-tickers.test.tsx +73 -0
  184. package/src/state/use-inline-tickers.ts +19 -10
  185. package/src/test-support/plugin-runtime.ts +3 -2
  186. package/src/theme/colors.test.ts +75 -0
  187. package/src/theme/colors.ts +76 -6
  188. package/src/theme/theme-context.tsx +26 -0
  189. package/src/theme/themes.test.ts +23 -1
  190. package/src/theme/themes.ts +115 -0
  191. package/src/types/broker.ts +1 -1
  192. package/src/types/capability-route-source.ts +1 -0
  193. package/src/types/data-provider.ts +15 -2
  194. package/src/types/desktop-window.ts +6 -0
  195. package/src/types/financials.ts +2 -0
  196. package/src/types/plugin.ts +1 -3
  197. package/src/ui/host.tsx +7 -0
  198. package/src/utils/api-client.test.ts +32 -0
  199. package/src/utils/api-client.ts +119 -18
  200. package/src/utils/back-navigation.ts +24 -1
  201. package/src/utils/double-escape-close.test.ts +33 -0
  202. package/src/utils/double-escape-close.ts +40 -0
  203. package/src/utils/throttled-fetch.test.ts +22 -0
  204. package/src/utils/throttled-fetch.ts +3 -1
  205. package/src/utils/ticker-search.ts +53 -9
  206. package/src/version.ts +1 -1
  207. package/src/plugins/builtin/news-wire/feed-pane.tsx +0 -21
  208. package/src/plugins/builtin/news-wire/top-pane.tsx +0 -21
@@ -1,14 +1,15 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { act, useReducer } from "react";
3
- import { testRender } from "../../renderers/opentui/test-utils";
4
- import { DialogProvider } from "@opentui-ui/dialog/react";
3
+ import { TestDialogProvider, testRender } from "../../renderers/opentui/test-utils";
5
4
  import { CommandBar } from "./command-bar";
6
- import { AppContext, type AppState, appReducer, createInitialState, getEffectiveThemeId } from "../../state/app-context";
5
+ import { AppContext, type AppAction, type AppState, appReducer, createInitialState, getEffectiveThemeId } from "../../state/app-context";
7
6
  import { createTestDataProvider } from "../../test-support/data-provider";
7
+ import { ThemeProvider, useThemeId } from "../../theme/theme-context";
8
8
  import { cloneLayout, createDefaultConfig, type AppConfig } from "../../types/config";
9
9
  import type { DataProvider } from "../../types/data-provider";
10
10
  import type { TickerRecord } from "../../types/ticker";
11
11
  import type { PluginRegistry } from "../../plugins/registry";
12
+ import type { PaneTemplateCreateOptions } from "../../types/plugin";
12
13
 
13
14
  let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
14
15
 
@@ -162,7 +163,7 @@ function makePluginRegistry(hasPaneSettings: (paneId: string) => boolean = () =>
162
163
  paneId: "chat",
163
164
  label: "New Chat Pane",
164
165
  description: "Open another floating chat window",
165
- shortcut: { prefix: "CHAT" },
166
+ shortcut: { prefix: "CHAT", argPlaceholder: "channel", argKind: "text" },
166
167
  }],
167
168
  ["quote-monitor-pane", {
168
169
  id: "quote-monitor-pane",
@@ -213,9 +214,9 @@ function makePluginRegistry(hasPaneSettings: (paneId: string) => boolean = () =>
213
214
  getPluginPaneTemplateIds: () => [],
214
215
  hasPaneSettings,
215
216
  events: { emit: () => {} },
216
- hideWidget: () => {},
217
+ hidePane: () => {},
217
218
  pinTicker: () => {},
218
- showWidget: () => {},
219
+ showPane: () => {},
219
220
  updateLayoutFn: () => {},
220
221
  getTermSizeFn: () => ({ width: 80, height: 24 }),
221
222
  notify: () => {},
@@ -231,6 +232,10 @@ function makePluginRegistry(hasPaneSettings: (paneId: string) => boolean = () =>
231
232
  } as unknown as PluginRegistry;
232
233
  }
233
234
 
235
+ function ThemeProbe() {
236
+ return <text>{`theme:${useThemeId()}`}</text>;
237
+ }
238
+
234
239
  function CommandBarHarness({
235
240
  query,
236
241
  disabledPlugins = [],
@@ -245,6 +250,7 @@ function CommandBarHarness({
245
250
  dataProvider = makeDataProvider(),
246
251
  hasPaneSettings,
247
252
  onCheckForUpdates,
253
+ onAction,
248
254
  }: {
249
255
  query: string;
250
256
  disabledPlugins?: string[];
@@ -259,6 +265,7 @@ function CommandBarHarness({
259
265
  dataProvider?: DataProvider;
260
266
  hasPaneSettings?: (paneId: string) => boolean;
261
267
  onCheckForUpdates?: () => void | Promise<void>;
268
+ onAction?: (action: AppAction) => void;
262
269
  }) {
263
270
  let config = {
264
271
  ...createDefaultConfig("/tmp/gloomberb-test"),
@@ -273,6 +280,7 @@ function CommandBarHarness({
273
280
  ...createInitialState(config),
274
281
  commandBarOpen: true,
275
282
  commandBarQuery: query,
283
+ focusedPaneId: "portfolio-list:main",
276
284
  tickers: new Map(tickers.map((ticker) => [ticker.metadata.ticker, ticker])),
277
285
  config: { ...config, disabledPlugins },
278
286
  paneState: selectedTicker
@@ -300,24 +308,31 @@ function CommandBarHarness({
300
308
  configurePluginRegistry?.(pluginRegistry);
301
309
  const [liveState, dispatch] = useReducer(appReducer, state);
302
310
  const currentState = live ? liveState : state;
303
- const currentDispatch = live ? dispatch : () => {};
311
+ const currentDispatch = live
312
+ ? (action: AppAction) => {
313
+ onAction?.(action);
314
+ dispatch(action);
315
+ }
316
+ : (_action: AppAction) => {};
304
317
 
305
318
  return (
306
- <AppContext value={{ state: currentState, dispatch: currentDispatch }}>
307
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
308
- {live && <text>{`theme:${getEffectiveThemeId(currentState)}`}</text>}
309
- {showQueryState && <text>{`query:${currentState.commandBarQuery}`}</text>}
310
- {currentState.commandBarOpen && (
311
- <CommandBar
312
- dataProvider={dataProvider}
313
- tickerRepository={tickerRepository as any}
314
- pluginRegistry={pluginRegistry}
315
- quitApp={() => {}}
316
- onCheckForUpdates={onCheckForUpdates}
317
- />
318
- )}
319
- </DialogProvider>
320
- </AppContext>
319
+ <ThemeProvider themeId={getEffectiveThemeId(currentState)}>
320
+ <AppContext value={{ state: currentState, dispatch: currentDispatch }}>
321
+ <TestDialogProvider>
322
+ {live && <ThemeProbe />}
323
+ {showQueryState && <text>{`query:${currentState.commandBarQuery}`}</text>}
324
+ {currentState.commandBarOpen && (
325
+ <CommandBar
326
+ dataProvider={dataProvider}
327
+ tickerRepository={tickerRepository as any}
328
+ pluginRegistry={pluginRegistry}
329
+ quitApp={() => {}}
330
+ onCheckForUpdates={onCheckForUpdates}
331
+ />
332
+ )}
333
+ </TestDialogProvider>
334
+ </AppContext>
335
+ </ThemeProvider>
321
336
  );
322
337
  }
323
338
 
@@ -334,7 +349,7 @@ describe("CommandBar", () => {
334
349
  });
335
350
 
336
351
  test("runs check for updates from the command bar", async () => {
337
- const calls = [];
352
+ const calls: number[] = [];
338
353
 
339
354
  testSetup = await testRender(<CommandBarHarness query="check for updates" live onCheckForUpdates={() => { calls.push(Date.now()); }} />, {
340
355
  width: 80,
@@ -739,8 +754,12 @@ describe("CommandBar", () => {
739
754
  const frame = testSetup.captureCharFrame();
740
755
  expect(frame).toContain("Commands");
741
756
  expect(frame).toContain("TH");
742
- expect(frame).toContain("Themes");
743
- expect(frame).toMatchSnapshot();
757
+ expect(frame).toContain("Amber");
758
+ expect(frame).toContain("Paper");
759
+ expect(frame).toContain("GitHub Light");
760
+ expect(frame).toContain("Solarized Light");
761
+ expect(frame).toContain("Gruvbox Light");
762
+ expect(frame).toContain("Nord Light");
744
763
  });
745
764
 
746
765
  test("renders plugin toggle state", async () => {
@@ -793,6 +812,7 @@ describe("CommandBar", () => {
793
812
  await testSetup.renderOnce();
794
813
  await act(async () => {
795
814
  testSetup!.mockInput.pressArrow("down");
815
+ await Bun.sleep(220);
796
816
  await testSetup!.renderOnce();
797
817
  });
798
818
  await testSetup.renderOnce();
@@ -801,6 +821,38 @@ describe("CommandBar", () => {
801
821
  expect(frame).toContain("theme:green");
802
822
  });
803
823
 
824
+ test("debounces rapid keyboard theme previews", async () => {
825
+ const actions: AppAction[] = [];
826
+ testSetup = await testRender(
827
+ <CommandBarHarness query="TH " live onAction={(action) => actions.push(action)} />,
828
+ { width: 80, height: 24 },
829
+ );
830
+
831
+ await testSetup.renderOnce();
832
+ await act(async () => {
833
+ for (let index = 0; index < 6; index++) {
834
+ testSetup!.mockInput.pressArrow("down");
835
+ }
836
+ await testSetup!.renderOnce();
837
+ });
838
+
839
+ expect(actions.filter((action) => action.type === "PREVIEW_THEME")).toHaveLength(0);
840
+
841
+ await act(async () => {
842
+ await Bun.sleep(100);
843
+ await testSetup!.renderOnce();
844
+ });
845
+
846
+ expect(actions.filter((action) => action.type === "PREVIEW_THEME")).toHaveLength(0);
847
+
848
+ await act(async () => {
849
+ await Bun.sleep(150);
850
+ await testSetup!.renderOnce();
851
+ });
852
+
853
+ expect(actions.filter((action) => action.type === "PREVIEW_THEME")).toHaveLength(1);
854
+ });
855
+
804
856
  test("does not preview a theme from mouse hover alone", async () => {
805
857
  testSetup = await testRender(<CommandBarHarness query="TH " live />, {
806
858
  width: 80,
@@ -836,6 +888,7 @@ describe("CommandBar", () => {
836
888
  await testSetup.renderOnce();
837
889
  await act(async () => {
838
890
  testSetup!.mockInput.pressArrow("down");
891
+ await Bun.sleep(220);
839
892
  await testSetup!.renderOnce();
840
893
  });
841
894
  await testSetup.renderOnce();
@@ -883,8 +936,10 @@ describe("CommandBar", () => {
883
936
  await testSetup.renderOnce();
884
937
  await act(async () => {
885
938
  testSetup!.mockInput.pressArrow("down");
939
+ await Bun.sleep(220);
886
940
  await testSetup!.renderOnce();
887
941
  });
942
+ await testSetup.renderOnce();
888
943
  expect(testSetup.captureCharFrame()).toContain("theme:green");
889
944
 
890
945
  await emitKeypress(testSetup, { name: "u", ctrl: true });
@@ -952,7 +1007,7 @@ describe("CommandBar", () => {
952
1007
  });
953
1008
 
954
1009
  test("typing a shorthand and pressing enter executes the inferred quote monitor shortcut", async () => {
955
- const created: Array<{ templateId: string; options?: Record<string, unknown> }> = [];
1010
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
956
1011
 
957
1012
  testSetup = await testRender(<CommandBarHarness
958
1013
  query=""
@@ -987,6 +1042,39 @@ describe("CommandBar", () => {
987
1042
  }]);
988
1043
  });
989
1044
 
1045
+ test("typing a chat channel shortcut opens that channel directly", async () => {
1046
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
1047
+
1048
+ testSetup = await testRender(<CommandBarHarness
1049
+ query=""
1050
+ live
1051
+ configurePluginRegistry={(pluginRegistry) => {
1052
+ pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
1053
+ created.push({ templateId, options });
1054
+ };
1055
+ }}
1056
+ />, {
1057
+ width: 100,
1058
+ height: 20,
1059
+ });
1060
+
1061
+ await testSetup.renderOnce();
1062
+
1063
+ await act(async () => {
1064
+ await testSetup!.mockInput.typeText("CHAT help");
1065
+ testSetup!.mockInput.pressEnter();
1066
+ await Bun.sleep(0);
1067
+ await testSetup!.renderOnce();
1068
+ });
1069
+
1070
+ expect(created).toEqual([{
1071
+ templateId: "new-chat-pane",
1072
+ options: {
1073
+ arg: "help",
1074
+ },
1075
+ }]);
1076
+ });
1077
+
990
1078
  test("clears the root query with cmd-backspace", async () => {
991
1079
  testSetup = await testRender(<CommandBarHarness query="DES AMD" />, {
992
1080
  width: 80,
@@ -1649,7 +1737,6 @@ describe("CommandBar", () => {
1649
1737
  await testSetup.renderOnce();
1650
1738
  const initialFrame = testSetup.captureCharFrame();
1651
1739
  expect(initialFrame).toContain("Delete Layout");
1652
- expect(initialFrame).toContain("Danger");
1653
1740
 
1654
1741
  await act(async () => {
1655
1742
  testSetup!.mockInput.pressEnter();
@@ -1900,7 +1987,7 @@ describe("CommandBar", () => {
1900
1987
  });
1901
1988
 
1902
1989
  test("does not treat no-argument shortcut prefixes as typed pane names", async () => {
1903
- const created: Array<{ templateId: string; options?: Record<string, unknown> }> = [];
1990
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
1904
1991
 
1905
1992
  testSetup = await testRender(<CommandBarHarness
1906
1993
  query="Top News"
@@ -2086,7 +2173,7 @@ describe("CommandBar", () => {
2086
2173
  });
2087
2174
 
2088
2175
  test("QQ MSFT executes directly without opening a secondary workflow", async () => {
2089
- const created: Array<{ templateId: string; options?: Record<string, unknown> }> = [];
2176
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
2090
2177
 
2091
2178
  testSetup = await testRender(<CommandBarHarness
2092
2179
  query="QQ MSFT"
@@ -2119,7 +2206,7 @@ describe("CommandBar", () => {
2119
2206
  });
2120
2207
 
2121
2208
  test("CMP AAPL,MSFT creates the comparison chart directly", async () => {
2122
- const created: Array<{ templateId: string; options?: Record<string, unknown> }> = [];
2209
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
2123
2210
 
2124
2211
  testSetup = await testRender(<CommandBarHarness
2125
2212
  query="CMP AAPL,MSFT"
@@ -2197,8 +2284,52 @@ describe("CommandBar", () => {
2197
2284
  expect(frame).toContain("AAPL,");
2198
2285
  });
2199
2286
 
2287
+ test("CMP with one resolved ticker opens inline completion instead of creating a one-symbol chart", async () => {
2288
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
2289
+
2290
+ testSetup = await testRender(<CommandBarHarness
2291
+ query="CMP AMD"
2292
+ extraTickers={[makeTicker("AMD", "Advanced Micro Devices")]}
2293
+ configurePluginRegistry={(pluginRegistry) => {
2294
+ (pluginRegistry.panes as Map<string, any>).set("comparison-chart", {
2295
+ id: "comparison-chart",
2296
+ name: "Comparison Chart",
2297
+ component: () => null,
2298
+ defaultPosition: "right",
2299
+ });
2300
+ (pluginRegistry.paneTemplates as Map<string, any>).set("comparison-chart-pane", {
2301
+ id: "comparison-chart-pane",
2302
+ paneId: "comparison-chart",
2303
+ label: "Comparison Chart",
2304
+ description: "Compare multiple symbols in one pane",
2305
+ shortcut: { prefix: "CMP", argPlaceholder: "tickers", argKind: "ticker-list" },
2306
+ wizard: [{ key: "tickers", label: "Tickers", type: "text" }],
2307
+ canCreate: (_context: unknown, options?: { symbols?: string[] | null }) => !options?.symbols || options.symbols.length >= 2,
2308
+ });
2309
+ pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
2310
+ created.push({ templateId, options });
2311
+ };
2312
+ }}
2313
+ />, {
2314
+ width: 100,
2315
+ height: 20,
2316
+ });
2317
+
2318
+ await testSetup.renderOnce();
2319
+ await act(async () => {
2320
+ testSetup!.mockInput.pressEnter();
2321
+ await Bun.sleep(0);
2322
+ await testSetup!.renderOnce();
2323
+ });
2324
+
2325
+ const frame = await waitForFrameToContain("Tickers");
2326
+ expect(created).toEqual([]);
2327
+ expect(frame).toContain("Comparison Chart");
2328
+ expect(frame).toContain("AMD");
2329
+ });
2330
+
2200
2331
  test("AI <prompt> opens the inline workflow and prefills the textarea prompt", async () => {
2201
- const created: Array<{ templateId: string; options?: Record<string, unknown> }> = [];
2332
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
2202
2333
 
2203
2334
  testSetup = await testRender(<CommandBarHarness
2204
2335
  query="AI quality compounders"
@@ -2254,7 +2385,7 @@ describe("CommandBar", () => {
2254
2385
  });
2255
2386
 
2256
2387
  test("submits typed AI screener prompts from the textarea field", async () => {
2257
- const created: Array<{ templateId: string; options?: Record<string, unknown> }> = [];
2388
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
2258
2389
 
2259
2390
  testSetup = await testRender(<CommandBarHarness
2260
2391
  query="AI"
@@ -2608,7 +2739,15 @@ describe("CommandBar", () => {
2608
2739
  });
2609
2740
 
2610
2741
  test("matches the direct help command", async () => {
2611
- testSetup = await testRender(<CommandBarHarness query="help" />, {
2742
+ const openedPanes: string[] = [];
2743
+ testSetup = await testRender(<CommandBarHarness
2744
+ query="help"
2745
+ configurePluginRegistry={(pluginRegistry) => {
2746
+ pluginRegistry.showPane = (paneId: string) => {
2747
+ openedPanes.push(paneId);
2748
+ };
2749
+ }}
2750
+ />, {
2612
2751
  width: 80,
2613
2752
  height: 18,
2614
2753
  });
@@ -2618,6 +2757,13 @@ describe("CommandBar", () => {
2618
2757
  const frame = testSetup.captureCharFrame();
2619
2758
  expect(frame).toContain("Help");
2620
2759
  expect(frame).toContain("Navigation");
2760
+
2761
+ await act(async () => {
2762
+ testSetup!.mockInput.pressEnter();
2763
+ await testSetup!.renderOnce();
2764
+ });
2765
+
2766
+ expect(openedPanes).toEqual(["help"]);
2621
2767
  });
2622
2768
 
2623
2769
  test("skips pane templates whose canCreate throws", async () => {
@@ -2686,7 +2832,7 @@ describe("CommandBar", () => {
2686
2832
  query="auth login"
2687
2833
  live
2688
2834
  configurePluginRegistry={(pluginRegistry) => {
2689
- pluginRegistry.commands.set("auth-login", {
2835
+ (pluginRegistry.commands as Map<string, any>).set("auth-login", {
2690
2836
  id: "auth-login",
2691
2837
  label: "Auth Login",
2692
2838
  description: "Log in to your account",
@@ -2726,7 +2872,7 @@ describe("CommandBar", () => {
2726
2872
  query="workspace"
2727
2873
  live
2728
2874
  configurePluginRegistry={(pluginRegistry) => {
2729
- pluginRegistry.commands.set("new-workspace", {
2875
+ (pluginRegistry.commands as Map<string, any>).set("new-workspace", {
2730
2876
  id: "new-workspace",
2731
2877
  label: "Workspace",
2732
2878
  description: "Create a workspace",
@@ -2736,7 +2882,7 @@ describe("CommandBar", () => {
2736
2882
  wizard: [
2737
2883
  { key: "name", label: "Name", type: "text", placeholder: "Research" },
2738
2884
  ],
2739
- execute: async (values) => {
2885
+ execute: async (values?: Record<string, string>) => {
2740
2886
  submitted.push(values);
2741
2887
  },
2742
2888
  } as any);