gloomberb 0.6.0 → 0.7.0

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 (156) hide show
  1. package/package.json +2 -1
  2. package/patches/@opentui%2Fcore@0.1.90.patch +48 -24
  3. package/src/app.tsx +9 -6
  4. package/src/cli/registry.test.ts +0 -1
  5. package/src/components/chart/chart-controller.ts +0 -1
  6. package/src/components/chart/chart-data.ts +1 -31
  7. package/src/components/chart/chart-renderer.ts +70 -0
  8. package/src/components/chart/chart-resolution.test.ts +0 -36
  9. package/src/components/chart/chart-resolution.ts +0 -27
  10. package/src/components/chart/chart.test.ts +17 -45
  11. package/src/components/chart/comparison-chart-data.test.ts +1 -5
  12. package/src/components/chart/comparison-chart-data.ts +1 -4
  13. package/src/components/chart/comparison-stock-chart.tsx +62 -77
  14. package/src/components/chart/native/kitty.test.ts +23 -0
  15. package/src/components/chart/native/surface-visibility.ts +48 -0
  16. package/src/components/chart/static-chart-surface.tsx +28 -4
  17. package/src/components/chart/stock-chart.test.ts +0 -16
  18. package/src/components/chart/stock-chart.tsx +52 -71
  19. package/src/components/chart/time-axis-label.tsx +43 -0
  20. package/src/components/command-bar/command-bar.test.tsx +71 -8
  21. package/src/components/command-bar/command-bar.tsx +388 -713
  22. package/src/components/command-bar/confirm-body.tsx +51 -0
  23. package/src/components/command-bar/list-model.ts +77 -0
  24. package/src/components/command-bar/view-model.ts +0 -19
  25. package/src/components/command-bar/workflow-ops.ts +4 -0
  26. package/src/components/data-table-stack-view.test.tsx +39 -4
  27. package/src/components/data-table-stack-view.tsx +11 -104
  28. package/src/components/data-table-view.tsx +43 -79
  29. package/src/components/feed-data-table-stack-view.tsx +10 -17
  30. package/src/components/feed-stack-controller.ts +39 -0
  31. package/src/components/index.ts +6 -2
  32. package/src/components/layout/floating-pane.tsx +30 -2
  33. package/src/components/layout/header.tsx +10 -7
  34. package/src/components/layout/pane-content.tsx +2 -0
  35. package/src/components/layout/pane-cycle-order.test.ts +30 -0
  36. package/src/components/layout/pane-cycle-order.ts +32 -0
  37. package/src/components/layout/pane-footer.tsx +26 -4
  38. package/src/components/layout/pane-header.tsx +5 -5
  39. package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
  40. package/src/components/layout/shell.test.tsx +66 -22
  41. package/src/components/layout/shell.tsx +13 -6
  42. package/src/components/onboarding/onboarding-wizard.tsx +1 -2
  43. package/src/components/pane-settings-dialog.tsx +12 -4
  44. package/src/components/pane-template-wizard.test.tsx +3 -4
  45. package/src/components/speedometer-gauge.test.tsx +45 -0
  46. package/src/components/speedometer-gauge.tsx +558 -0
  47. package/src/components/table-view-shared.tsx +158 -0
  48. package/src/components/ticker-list-table-view.tsx +42 -72
  49. package/src/components/ui/confirm-dialog.tsx +66 -0
  50. package/src/components/ui/data-table.tsx +18 -10
  51. package/src/components/ui/index.ts +0 -1
  52. package/src/components/ui/page-stack-view.tsx +39 -10
  53. package/src/components/ui/remote-image.tsx +15 -40
  54. package/src/components/ui/table-layout.test.ts +16 -0
  55. package/src/components/ui/table-layout.ts +10 -3
  56. package/src/components/ui/ui.test.tsx +100 -20
  57. package/src/core/state/app-state.ts +6 -3
  58. package/src/market-data/coordinator.test.ts +1 -1
  59. package/src/market-data/coordinator.ts +0 -1
  60. package/src/news/aggregator.ts +0 -3
  61. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +46 -16
  62. package/src/plugins/builtin/ai/providers.ts +0 -17
  63. package/src/plugins/builtin/ai/screener-pane.test.tsx +3 -4
  64. package/src/plugins/builtin/ai/screener-pane.tsx +0 -3
  65. package/src/plugins/builtin/ask-ai.test.tsx +89 -2
  66. package/src/plugins/builtin/broker-manager/index.test.tsx +2 -2
  67. package/src/plugins/builtin/broker-manager/index.tsx +19 -36
  68. package/src/plugins/builtin/chat-controller.test.ts +105 -0
  69. package/src/plugins/builtin/chat-controller.ts +75 -6
  70. package/src/plugins/builtin/chat.test.tsx +6 -6
  71. package/src/plugins/builtin/chat.tsx +29 -20
  72. package/src/plugins/builtin/cloud-tweets.tsx +0 -1
  73. package/src/plugins/builtin/comparison-chart.test.tsx +53 -2
  74. package/src/plugins/builtin/comparison-chart.tsx +11 -5
  75. package/src/plugins/builtin/correlation/index.tsx +1 -6
  76. package/src/plugins/builtin/debug.tsx +1 -3
  77. package/src/plugins/builtin/econ/index.tsx +1 -2
  78. package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +72 -0
  79. package/src/plugins/builtin/fear-greed/fear-greed-data.ts +366 -0
  80. package/src/plugins/builtin/fear-greed/index.tsx +478 -0
  81. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  82. package/src/plugins/builtin/help.test.tsx +2 -2
  83. package/src/plugins/builtin/help.tsx +2 -2
  84. package/src/plugins/builtin/holders/index.tsx +10 -15
  85. package/src/plugins/builtin/insider/index.tsx +22 -89
  86. package/src/plugins/builtin/market-movers/index.tsx +1 -1
  87. package/src/plugins/builtin/news-wire/index.tsx +41 -2
  88. package/src/plugins/builtin/news-wire/news-table.tsx +11 -11
  89. package/src/plugins/builtin/news.tsx +4 -16
  90. package/src/plugins/builtin/notes.tsx +18 -55
  91. package/src/plugins/builtin/options.test.tsx +58 -2
  92. package/src/plugins/builtin/options.tsx +108 -49
  93. package/src/plugins/builtin/plugin-groups.ts +2 -0
  94. package/src/plugins/builtin/portfolio-list/cli/commands.ts +1 -1
  95. package/src/plugins/builtin/research/index.tsx +5 -21
  96. package/src/plugins/builtin/sec-filing-display.tsx +84 -0
  97. package/src/plugins/builtin/sec.tsx +13 -66
  98. package/src/plugins/builtin/sectors/index.tsx +1 -1
  99. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -1
  100. package/src/plugins/builtin/ticker-detail/index.tsx +0 -1
  101. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -1
  102. package/src/plugins/builtin/ticker-detail/pane.tsx +36 -28
  103. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +69 -4
  104. package/src/plugins/builtin/ticker-detail.test.tsx +102 -0
  105. package/src/plugins/builtin/world-indices/index.tsx +1 -1
  106. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  107. package/src/plugins/ibkr/broker-adapter.ts +1 -3
  108. package/src/plugins/ibkr/gateway-service-native.ts +1 -5
  109. package/src/plugins/ibkr/gateway-service.ts +1 -1
  110. package/src/plugins/ibkr/index.test.tsx +0 -2
  111. package/src/plugins/ibkr/trade-tab.test.tsx +56 -5
  112. package/src/plugins/ibkr/trade-tab.tsx +13 -11
  113. package/src/plugins/pane-manager.ts +0 -13
  114. package/src/plugins/plugin-runtime.test.tsx +6 -6
  115. package/src/plugins/plugin-runtime.tsx +13 -8
  116. package/src/plugins/prediction-markets/cli.ts +0 -1
  117. package/src/plugins/prediction-markets/controller-data.ts +0 -2
  118. package/src/plugins/prediction-markets/pane.test.tsx +3 -11
  119. package/src/plugins/prediction-markets/test-helpers.tsx +1 -1
  120. package/src/plugins/registry.ts +10 -39
  121. package/src/react/input.ts +11 -2
  122. package/src/renderers/electrobun/view/data-table.tsx +28 -6
  123. package/src/renderers/electrobun/view/desktop-controls.tsx +34 -20
  124. package/src/renderers/electrobun/view/dialog-host.tsx +11 -1
  125. package/src/renderers/electrobun/view/input-host.tsx +123 -12
  126. package/src/renderers/electrobun/view/key-event.ts +1 -1
  127. package/src/renderers/electrobun/view/styles.css +331 -0
  128. package/src/renderers/electrobun/view/ui-host.tsx +22 -3
  129. package/src/renderers/opentui/chart-surface.tsx +179 -0
  130. package/src/renderers/opentui/host.tsx +6 -0
  131. package/src/renderers/opentui/image-loader.test.ts +25 -0
  132. package/src/renderers/opentui/image-loader.ts +105 -0
  133. package/src/renderers/opentui/image-surface.tsx +266 -0
  134. package/src/renderers/opentui/input-host.tsx +45 -6
  135. package/src/renderers/opentui/start.tsx +48 -30
  136. package/src/renderers/opentui/test-utils.tsx +17 -0
  137. package/src/renderers/opentui/ui-host.tsx +7 -3
  138. package/src/sources/provider-router.ts +0 -4
  139. package/src/state/app-context.test.ts +45 -0
  140. package/src/state/app-context.tsx +4 -0
  141. package/src/state/pane-scroll-registry.tsx +254 -0
  142. package/src/state/use-inline-tickers.test.tsx +73 -0
  143. package/src/state/use-inline-tickers.ts +7 -1
  144. package/src/test-support/plugin-runtime.ts +3 -2
  145. package/src/theme/colors.ts +12 -3
  146. package/src/theme/themes.test.ts +23 -1
  147. package/src/theme/themes.ts +115 -0
  148. package/src/types/broker.ts +1 -1
  149. package/src/types/data-provider.ts +0 -1
  150. package/src/types/plugin.ts +0 -2
  151. package/src/ui/host.tsx +4 -0
  152. package/src/ui/index.tsx +9 -1
  153. package/src/utils/back-navigation.ts +24 -1
  154. package/src/version.ts +1 -1
  155. package/src/plugins/builtin/news-wire/feed-pane.tsx +0 -21
  156. package/src/plugins/builtin/news-wire/top-pane.tsx +0 -21
@@ -1,7 +1,6 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { TextAttributes } from "@opentui/core";
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 type { ReactNode } from "react";
6
5
  import { act, createElement, useReducer } from "react";
7
6
  import { AppContext, appReducer, createInitialState } from "../../state/app-context";
@@ -76,7 +75,7 @@ function createShellPluginRegistry(options?: {
76
75
  openPaneSettingsFn: () => {},
77
76
  openCommandBar: () => {},
78
77
  updateLayoutFn: () => {},
79
- hideWidget: () => {},
78
+ hidePane: () => {},
80
79
  } as unknown as PluginRegistry;
81
80
  }
82
81
 
@@ -114,7 +113,7 @@ function createBrokerPortfolioRegistry(): PluginRegistry {
114
113
  openPaneSettingsFn: () => {},
115
114
  openCommandBar: () => {},
116
115
  updateLayoutFn: () => {},
117
- hideWidget: () => {},
116
+ hidePane: () => {},
118
117
  focusPaneFn: () => {},
119
118
  pinTicker: () => {},
120
119
  navigateTicker: () => {},
@@ -337,9 +336,9 @@ function ChartShellHarness({
337
336
 
338
337
  return (
339
338
  <AppContext value={{ state, dispatch }}>
340
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
339
+ <TestDialogProvider>
341
340
  <Shell pluginRegistry={pluginRegistry} />
342
- </DialogProvider>
341
+ </TestDialogProvider>
343
342
  </AppContext>
344
343
  );
345
344
  }
@@ -613,9 +612,9 @@ describe("Shell", () => {
613
612
 
614
613
  testSetup = await testRender(
615
614
  <AppContext value={{ state, dispatch: () => {} }}>
616
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
615
+ <TestDialogProvider>
617
616
  <Shell pluginRegistry={pluginRegistry} />
618
- </DialogProvider>
617
+ </TestDialogProvider>
619
618
  </AppContext>,
620
619
  { width: 40, height: 10 },
621
620
  );
@@ -655,7 +654,7 @@ describe("Shell", () => {
655
654
 
656
655
  testSetup = await testRender(
657
656
  <AppContext value={{ state, dispatch: () => {} }}>
658
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
657
+ <TestDialogProvider>
659
658
  <Shell
660
659
  pluginRegistry={pluginRegistry}
661
660
  desktopWindowBridge={{
@@ -665,7 +664,7 @@ describe("Shell", () => {
665
664
  subscribeDockPreview: () => () => {},
666
665
  }}
667
666
  />
668
- </DialogProvider>
667
+ </TestDialogProvider>
669
668
  </AppContext>,
670
669
  { width: 40, height: 10 },
671
670
  );
@@ -704,9 +703,9 @@ describe("Shell", () => {
704
703
 
705
704
  testSetup = await testRender(
706
705
  <AppContext value={{ state, dispatch: () => {} }}>
707
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
706
+ <TestDialogProvider>
708
707
  <Shell pluginRegistry={pluginRegistry} />
709
- </DialogProvider>
708
+ </TestDialogProvider>
710
709
  </AppContext>,
711
710
  { width: 40, height: 10 },
712
711
  );
@@ -823,9 +822,9 @@ describe("Shell", () => {
823
822
  setSharedRegistryForTests(pluginRegistry);
824
823
 
825
824
  testSetup = await testRender(
826
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
825
+ <TestDialogProvider>
827
826
  <BrokerShellHarness pluginRegistry={pluginRegistry} />
828
- </DialogProvider>,
827
+ </TestDialogProvider>,
829
828
  { width: 100, height: 24 },
830
829
  );
831
830
 
@@ -916,9 +915,9 @@ describe("Shell", () => {
916
915
 
917
916
  testSetup = await testRender(
918
917
  <AppContext value={{ state, dispatch: (action) => actions.push(action) }}>
919
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
918
+ <TestDialogProvider>
920
919
  <Shell pluginRegistry={createShellPluginRegistry()} />
921
- </DialogProvider>
920
+ </TestDialogProvider>
922
921
  </AppContext>,
923
922
  { width: 40, height: 10 },
924
923
  );
@@ -946,6 +945,7 @@ describe("Shell", () => {
946
945
  dockRoot: { kind: "pane" as const, instanceId: "portfolio-list:main" },
947
946
  instances: [{ ...mainPane }, { ...detailPane }],
948
947
  floating: [{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 30, height: 8 }],
948
+ detached: [],
949
949
  };
950
950
  const state = {
951
951
  ...createInitialState({
@@ -959,9 +959,9 @@ describe("Shell", () => {
959
959
 
960
960
  testSetup = await testRender(
961
961
  <AppContext value={{ state, dispatch: (action) => actions.push(action) }}>
962
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
962
+ <TestDialogProvider>
963
963
  <Shell pluginRegistry={createShellPluginRegistry()} />
964
- </DialogProvider>
964
+ </TestDialogProvider>
965
965
  </AppContext>,
966
966
  { width: 40, height: 12 },
967
967
  );
@@ -989,6 +989,7 @@ describe("Shell", () => {
989
989
  dockRoot: { kind: "pane" as const, instanceId: "portfolio-list:main" },
990
990
  instances: [{ ...mainPane }, { ...detailPane }],
991
991
  floating: [{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 30, height: 8 }],
992
+ detached: [],
992
993
  };
993
994
  const state = {
994
995
  ...createInitialState({
@@ -1002,9 +1003,9 @@ describe("Shell", () => {
1002
1003
 
1003
1004
  testSetup = await testRender(
1004
1005
  <AppContext value={{ state, dispatch: (action) => actions.push(action) }}>
1005
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
1006
+ <TestDialogProvider>
1006
1007
  <Shell pluginRegistry={createShellPluginRegistry()} />
1007
- </DialogProvider>
1008
+ </TestDialogProvider>
1008
1009
  </AppContext>,
1009
1010
  { width: 40, height: 12 },
1010
1011
  );
@@ -1020,6 +1021,49 @@ describe("Shell", () => {
1020
1021
  expect(updateLayout?.layout.floating).toEqual([]);
1021
1022
  });
1022
1023
 
1024
+ test("closes the focused floating pane with Ctrl+W while plugin input is captured", async () => {
1025
+ const config = createDefaultConfig("/tmp/gloomberb-shell-test");
1026
+ const mainPane = config.layout.instances.find((instance) => instance.instanceId === "portfolio-list:main");
1027
+ const detailPane = config.layout.instances.find((instance) => instance.instanceId === "ticker-detail:main");
1028
+ if (!mainPane || !detailPane) throw new Error("missing default panes");
1029
+
1030
+ const mixedLayout = {
1031
+ dockRoot: { kind: "pane" as const, instanceId: "portfolio-list:main" },
1032
+ instances: [{ ...mainPane }, { ...detailPane }],
1033
+ floating: [{ instanceId: "ticker-detail:main", x: 4, y: 2, width: 30, height: 8 }],
1034
+ detached: [],
1035
+ };
1036
+ const state = {
1037
+ ...createInitialState({
1038
+ ...config,
1039
+ layout: cloneLayout(mixedLayout),
1040
+ layouts: [{ name: "Default", layout: cloneLayout(mixedLayout) }],
1041
+ }),
1042
+ focusedPaneId: "ticker-detail:main",
1043
+ inputCaptured: true,
1044
+ };
1045
+ const actions: Array<any> = [];
1046
+
1047
+ testSetup = await testRender(
1048
+ <AppContext value={{ state, dispatch: (action) => actions.push(action) }}>
1049
+ <TestDialogProvider>
1050
+ <Shell pluginRegistry={createShellPluginRegistry()} />
1051
+ </TestDialogProvider>
1052
+ </AppContext>,
1053
+ { width: 40, height: 12 },
1054
+ );
1055
+
1056
+ await testSetup.renderOnce();
1057
+ await act(async () => {
1058
+ testSetup!.mockInput.pressKey("w", { ctrl: true });
1059
+ await testSetup!.renderOnce();
1060
+ });
1061
+
1062
+ const updateLayout = actions.find((action) => action.type === "UPDATE_LAYOUT");
1063
+ expect(updateLayout?.layout.instances.map((instance: { instanceId: string }) => instance.instanceId)).toEqual(["portfolio-list:main"]);
1064
+ expect(updateLayout?.layout.floating).toEqual([]);
1065
+ });
1066
+
1023
1067
  test("keeps a floating pane at the preview rect after a free drag release", () => {
1024
1068
  const config = createDefaultConfig("/tmp/gloomberb-shell-drag-test");
1025
1069
  const detailPane = config.layout.instances.find((instance) => instance.instanceId === "ticker-detail:main");
@@ -1153,7 +1197,7 @@ describe("Shell", () => {
1153
1197
 
1154
1198
  testSetup = await testRender(
1155
1199
  <AppContext value={{ state, dispatch: () => {} }}>
1156
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
1200
+ <TestDialogProvider>
1157
1201
  <Shell pluginRegistry={createShellPluginRegistry({
1158
1202
  tickerDetailComponent: ({ width, height }) => (
1159
1203
  <box flexDirection="column" width={width} height={height}>
@@ -1164,7 +1208,7 @@ describe("Shell", () => {
1164
1208
  </box>
1165
1209
  ),
1166
1210
  })} />
1167
- </DialogProvider>
1211
+ </TestDialogProvider>
1168
1212
  </AppContext>,
1169
1213
  { width: 40, height: 12 },
1170
1214
  );
@@ -9,7 +9,6 @@ import {
9
9
  MIN_FLOAT_HEIGHT,
10
10
  MIN_FLOAT_WIDTH,
11
11
  applyDrop,
12
- findDockLeaf,
13
12
  floatAtRect,
14
13
  floatPane,
15
14
  getDockDividerLayouts,
@@ -22,7 +21,6 @@ import {
22
21
  resolveDocked,
23
22
  resolveFloating,
24
23
  simulateDrop,
25
- swapPanes,
26
24
  type DockDividerLayout,
27
25
  type DockLeafLayout,
28
26
  type DropTarget,
@@ -218,6 +216,14 @@ export function resolvePaneManagementShortcut(
218
216
  return null;
219
217
  }
220
218
 
219
+ function inputCaptureAllowsPaneManagementShortcut(
220
+ shortcut: PaneManagementShortcut,
221
+ event: Pick<KeyEventLike, "meta" | "super" | "targetEditable">,
222
+ ): boolean {
223
+ if (shortcut !== "close") return false;
224
+ return event.meta || event.super || event.targetEditable !== true;
225
+ }
226
+
221
227
  export function resolveAppHeaderHeightCells(options: { titleBarOverlay?: boolean; cellHeightPx?: number }): number {
222
228
  if (!options.titleBarOverlay || !options.cellHeightPx || options.cellHeightPx <= 0) return DEFAULT_HEADER_HEIGHT;
223
229
  return TITLEBAR_OVERLAY_HEIGHT_PX / options.cellHeightPx;
@@ -588,8 +594,8 @@ function resolveHeaderHitAreas(
588
594
  actionStart: number | null;
589
595
  closeStart: number | null;
590
596
  } {
591
- // When focused, the header renders ┌─...─┐ adding 2 chars on each side
592
- let rightEdge = options.focused ? width - 2 : width;
597
+ // Focused panes and all floating panes render ┌─...─┐, adding 2 chars on each side.
598
+ let rightEdge = options.focused || options.floating ? width - 2 : width;
593
599
  let closeStart: number | null = null;
594
600
  let actionStart: number | null = null;
595
601
 
@@ -966,7 +972,8 @@ export function Shell({ pluginRegistry, desktopWindowBridge, desktopDockPreview
966
972
  }
967
973
 
968
974
  const shortcut = resolvePaneManagementShortcut(event);
969
- if (!shortcut || dragRef.current || overlayOpen || inputCaptured) return;
975
+ if (!shortcut || dragRef.current || overlayOpen) return;
976
+ if (inputCaptured && !inputCaptureAllowsPaneManagementShortcut(shortcut, event)) return;
970
977
 
971
978
  let handled = false;
972
979
  switch (shortcut) {
@@ -1701,7 +1708,7 @@ export function Shell({ pluginRegistry, desktopWindowBridge, desktopDockPreview
1701
1708
  "data-active": active ? "true" : "false",
1702
1709
  style: { "--divider-color": active ? colors.borderFocused : colors.border } as any,
1703
1710
  } : {})}
1704
- onMouseDown={nativePaneChrome ? (event) => startNativeDividerDrag(divider, event) : undefined}
1711
+ onMouseDown={nativePaneChrome ? (event: ShellMouseEvent) => startNativeDividerDrag(divider, event) : undefined}
1705
1712
  onMouseDrag={nativePaneChrome ? handleNativeDrag : undefined}
1706
1713
  onMouseDragEnd={nativePaneChrome ? handleNativeDrag : undefined}
1707
1714
  />
@@ -1,4 +1,4 @@
1
- import { AsciiText, Box, Input, Span, Strong, Text, Underline, useUiHost } from "../../ui";
1
+ import { AsciiText, Box, Span, Strong, Text, Underline, useUiHost } from "../../ui";
2
2
  import { useState, useCallback, useEffect, useRef, useMemo, type RefObject } from "react";
3
3
  import { useShortcut, useViewport } from "../../react/input";
4
4
  import { TextAttributes } from "../../ui";
@@ -963,7 +963,6 @@ function PortfolioStep({
963
963
 
964
964
  // broker-fields: dynamically render fields from the broker's configSchema
965
965
  if (!selectedBrokerId) return null;
966
- const currentField = brokerFields[brokerFieldIdx];
967
966
  const values = brokerValues[selectedBrokerId] ?? {};
968
967
 
969
968
  return (
@@ -9,7 +9,7 @@ import type {
9
9
  PaneSettingTextField,
10
10
  } from "../types/plugin";
11
11
  import type { PluginRegistry } from "../plugins/registry";
12
- import { colors } from "../theme/colors";
12
+ import { blendHex, colors } from "../theme/colors";
13
13
  import { Button, DialogFrame, ListView, MultiSelectDialogContent, TextField } from "./ui";
14
14
  import { NativeSelect, openNativeSelect, type NativeSelectElement } from "./ui/native-select";
15
15
 
@@ -80,6 +80,14 @@ function desktopText(weight?: number) {
80
80
  return weight ? { ...DESKTOP_TEXT_STYLE, fontWeight: weight } : DESKTOP_TEXT_STYLE;
81
81
  }
82
82
 
83
+ function desktopSubtleSurface(): string {
84
+ return blendHex(colors.panel, colors.bg, 0.22);
85
+ }
86
+
87
+ function desktopHoverSurface(): string {
88
+ return blendHex(colors.bg, colors.borderFocused, 0.08);
89
+ }
90
+
83
91
  function DesktopDialogSurface({
84
92
  title,
85
93
  subtitle,
@@ -149,7 +157,7 @@ function DesktopSwitch({
149
157
  flexDirection="row"
150
158
  alignItems="center"
151
159
  justifyContent={checked ? "flex-end" : "flex-start"}
152
- backgroundColor={checked ? colors.selected : "rgba(255, 255, 255, 0.07)"}
160
+ backgroundColor={checked ? colors.selected : desktopSubtleSurface()}
153
161
  onMouseDown={(event: any) => {
154
162
  event.stopPropagation?.();
155
163
  event.preventDefault?.();
@@ -182,7 +190,7 @@ function DesktopValuePill({ value }: { value: string }) {
182
190
  <Box
183
191
  flexDirection="row"
184
192
  alignItems="center"
185
- backgroundColor="rgba(255, 255, 255, 0.06)"
193
+ backgroundColor={desktopSubtleSurface()}
186
194
  style={{
187
195
  border: `1px solid ${colors.border}`,
188
196
  borderRadius: 6,
@@ -236,7 +244,7 @@ function DesktopSettingsRow({
236
244
  <Box
237
245
  flexDirection="column"
238
246
  minHeight={field.description ? undefined : 2}
239
- backgroundColor={hovered || selected ? "rgba(255, 255, 255, 0.045)" : "transparent"}
247
+ backgroundColor={hovered || selected ? desktopHoverSurface() : "transparent"}
240
248
  onMouseMove={onHover}
241
249
  onMouseDown={field.type === "select" ? undefined : (event: any) => {
242
250
  event.stopPropagation?.();
@@ -1,7 +1,6 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { act } 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 { PaneTemplateTextareaStep } from "./pane-template-wizard";
6
5
 
7
6
  let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
@@ -18,7 +17,7 @@ describe("PaneTemplateTextareaStep", () => {
18
17
  let resolved = "";
19
18
 
20
19
  testSetup = await testRender(
21
- <DialogProvider dialogOptions={{ style: { backgroundColor: "#000000", borderColor: "#ffffff", borderStyle: "single" } }}>
20
+ <TestDialogProvider>
22
21
  <PaneTemplateTextareaStep
23
22
  resolve={(value) => { resolved = value; }}
24
23
  dialogId="textarea-test"
@@ -30,7 +29,7 @@ describe("PaneTemplateTextareaStep", () => {
30
29
  defaultValue: "Find cash-generative semiconductor suppliers",
31
30
  }}
32
31
  />
33
- </DialogProvider>,
32
+ </TestDialogProvider>,
34
33
  { width: 80, height: 16 },
35
34
  );
36
35
 
@@ -0,0 +1,45 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { act } from "react";
3
+ import { testRender } from "../renderers/opentui/test-utils";
4
+ import { colors } from "../theme/colors";
5
+ import { SpeedometerGauge, type SpeedometerSegment } from "./speedometer-gauge";
6
+
7
+ let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
8
+
9
+ afterEach(async () => {
10
+ if (!testSetup) return;
11
+ await act(async () => {
12
+ testSetup!.renderer.destroy();
13
+ });
14
+ testSetup = undefined;
15
+ });
16
+
17
+ const segments: SpeedometerSegment[] = [
18
+ { from: 0, to: 24.999, label: "EXTREME FEAR", color: colors.negative },
19
+ { from: 25, to: 44.999, label: "FEAR", color: colors.warning },
20
+ { from: 45, to: 55, label: "NEUTRAL", color: colors.neutral },
21
+ { from: 55.001, to: 75, label: "GREED", color: colors.positive },
22
+ { from: 75.001, to: 100, label: "EXTREME GREED", color: colors.positive },
23
+ ];
24
+
25
+ describe("SpeedometerGauge", () => {
26
+ test("keeps the terminal fallback compact in wide panes", async () => {
27
+ testSetup = await testRender(
28
+ <SpeedometerGauge value={67} valueLabel="GREED" width={120} segments={segments} />,
29
+ { width: 124, height: 14 },
30
+ );
31
+
32
+ await act(async () => {
33
+ await testSetup!.renderOnce();
34
+ await testSetup!.renderOnce();
35
+ });
36
+
37
+ const frame = testSetup.captureCharFrame();
38
+ expect(frame).toContain("Current reading GREED");
39
+ expect(frame).toContain("67");
40
+
41
+ const labelLine = frame.split("\n").find((line) => line.includes("EXT FEAR") && line.includes("EXT GREED"));
42
+ expect(labelLine).toBeTruthy();
43
+ expect(labelLine!.indexOf("EXT GREED") - labelLine!.indexOf("EXT FEAR")).toBeLessThanOrEqual(58);
44
+ });
45
+ });