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
@@ -9,6 +9,7 @@ import { ShortcutHint } from "../ui";
9
9
  import { saveConfig } from "../../data/config-store";
10
10
  import { getSharedMarketData } from "../../plugins/registry";
11
11
  import { colors } from "../../theme/colors";
12
+ import { useThemeColors } from "../../theme/theme-context";
12
13
  import { formatCompact } from "../../utils/format";
13
14
  import { formatMarketPriceWithCurrency } from "../../utils/market-format";
14
15
  import { useChartQueries, useChartQuery } from "../../market-data/hooks";
@@ -27,7 +28,7 @@ import {
27
28
  getKeyboardPanCellCount,
28
29
  resolveDragPanOffset,
29
30
  } from "./chart-scroll";
30
- import { getVisibleWindow, projectChartData, resolveBarSize, resolveStableOhlcProjectionOptions, type ProjectedChartPoint } from "./chart-data";
31
+ import { projectChartData, resolveBarSize, resolveStableOhlcProjectionOptions, type ProjectedChartPoint } from "./chart-data";
31
32
  import {
32
33
  buildPresetDateWindow,
33
34
  buildVisibleDateWindow,
@@ -82,7 +83,6 @@ import {
82
83
  } from "./chart-viewport";
83
84
  import {
84
85
  buildChartScene,
85
- formatAxisCell,
86
86
  formatCursorAxisValue,
87
87
  getActivePointIndex,
88
88
  getPointTerminalColumn,
@@ -127,6 +127,8 @@ import {
127
127
  } from "./native/surface-visibility";
128
128
  import type { PricePoint, TickerFinancials } from "../../types/financials";
129
129
  import type { TickerRecord } from "../../types/ticker";
130
+ import { TimeAxisLabel } from "./time-axis-label";
131
+ import { PriceAxisLabels } from "./price-axis-labels";
130
132
 
131
133
  const MODE_CHIPS: Record<ChartRenderMode, string> = {
132
134
  area: "A",
@@ -173,7 +175,6 @@ interface StockChartProps {
173
175
  }
174
176
 
175
177
  interface ResolvedStockChartProps extends StockChartProps {
176
- symbol: string | null;
177
178
  ticker: TickerRecord | null;
178
179
  financials: TickerFinancials | null;
179
180
  }
@@ -726,14 +727,6 @@ function isSeriesAcceptedForRequest(
726
727
  return windowPoints.length >= minimumPointCount;
727
728
  }
728
729
 
729
- function canRenderAutoViewForWindow(
730
- render: AutoRenderedView | null,
731
- requestedWindow: DateWindowRange | null | undefined,
732
- ): render is AutoRenderedView {
733
- if (!render || !requestedWindow?.start || !requestedWindow.end) return false;
734
- return getWindowPoints(render.data, requestedWindow).length > 0;
735
- }
736
-
737
730
  export function resolveAutoDisplayState(options: {
738
731
  shouldUseRenderedAutoView: boolean;
739
732
  renderedAutoView: AutoRenderedView | null;
@@ -772,13 +765,6 @@ export function resolveAutoDisplayState(options: {
772
765
  };
773
766
  }
774
767
 
775
- export function resolveVisibleChartDateWindow(
776
- points: readonly Pick<PricePoint, "date">[],
777
- preferredWindow: DateWindowRange | null | undefined,
778
- ): DateWindowRange | null {
779
- return getDateWindowBounds(getPointDates(points)) ?? preferredWindow ?? null;
780
- }
781
-
782
768
  function buildDateWindowFromIndices(dates: readonly Date[], startIdx: number, endIdx: number): DateWindowRange | null {
783
769
  if (dates.length === 0 || endIdx <= startIdx) return null;
784
770
  return {
@@ -1430,8 +1416,8 @@ function buildIndicatorProjectionKey(options: {
1430
1416
  }
1431
1417
 
1432
1418
  export function StockChart(props: StockChartProps) {
1433
- const { symbol, ticker, financials } = usePaneTicker();
1434
- return <ResolvedStockChart {...props} symbol={symbol} ticker={ticker} financials={financials} />;
1419
+ const { ticker, financials } = usePaneTicker();
1420
+ return <ResolvedStockChart {...props} ticker={ticker} financials={financials} />;
1435
1421
  }
1436
1422
 
1437
1423
  export const ResolvedStockChart = memo(function ResolvedStockChart({
@@ -1448,10 +1434,10 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
1448
1434
  showVolume: showVolumeOverride,
1449
1435
  footerControls,
1450
1436
  footerHints,
1451
- symbol,
1452
1437
  ticker,
1453
1438
  financials,
1454
1439
  }: ResolvedStockChartProps) {
1440
+ const themeColors = useThemeColors();
1455
1441
  const renderer = useNativeRenderer();
1456
1442
  const { canvasCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
1457
1443
  const dispatch = useAppDispatch();
@@ -1514,6 +1500,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
1514
1500
  const lastCanvasBaseBitmapRef = useRef<{ key: string; bitmap: NativeChartBitmap } | null>(null);
1515
1501
  const displayCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
1516
1502
  const targetCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
1503
+ const mouseCrosshairDisabledRef = useRef(false);
1517
1504
  const cursorMotionKindRef = useRef<ChartCursorMotionKind>("discrete");
1518
1505
  const animationFrameRef = useRef<number | null>(null);
1519
1506
  const pendingCanonicalResetRef = useRef(1);
@@ -2906,6 +2893,11 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
2906
2893
  if (!interactive) return;
2907
2894
 
2908
2895
  switch (key) {
2896
+ case "escape":
2897
+ mouseCrosshairDisabledRef.current = true;
2898
+ updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
2899
+ commitSelectionCursor({ cursorX: null, cursorY: null });
2900
+ return;
2909
2901
  case "left":
2910
2902
  if (event.shift) {
2911
2903
  if (effectiveResolution === "auto") {
@@ -2923,6 +2915,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
2923
2915
  return { ...cleared, panOffset: current.panOffset + panStep };
2924
2916
  });
2925
2917
  } else {
2918
+ mouseCrosshairDisabledRef.current = false;
2926
2919
  cursorMotionKindRef.current = "discrete";
2927
2920
  const pointCount = projection.points.length;
2928
2921
  const currentIndex = pointCount <= 0
@@ -2974,6 +2967,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
2974
2967
  return cleared.panOffset === nextPanOffset ? cleared : { ...cleared, panOffset: nextPanOffset };
2975
2968
  });
2976
2969
  } else {
2970
+ mouseCrosshairDisabledRef.current = false;
2977
2971
  cursorMotionKindRef.current = "discrete";
2978
2972
  const pointCount = projection.points.length;
2979
2973
  const currentIndex = pointCount <= 0
@@ -3021,15 +3015,24 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3021
3015
  : 0;
3022
3016
  const trend = rawChange < 0 ? "negative" : rawChange > 0 ? "positive" : "neutral";
3023
3017
  return resolveChartPalette({
3024
- bg: colors.bg,
3025
- border: colors.border,
3026
- borderFocused: colors.borderFocused,
3027
- text: colors.text,
3028
- textDim: colors.textDim,
3029
- positive: colors.positive,
3030
- negative: colors.negative,
3018
+ bg: themeColors.bg,
3019
+ border: themeColors.border,
3020
+ borderFocused: themeColors.borderFocused,
3021
+ text: themeColors.text,
3022
+ textDim: themeColors.textDim,
3023
+ positive: themeColors.positive,
3024
+ negative: themeColors.negative,
3031
3025
  }, trend);
3032
- }, [chartWindow.points]);
3026
+ }, [
3027
+ chartWindow.points,
3028
+ themeColors.bg,
3029
+ themeColors.border,
3030
+ themeColors.borderFocused,
3031
+ themeColors.negative,
3032
+ themeColors.positive,
3033
+ themeColors.text,
3034
+ themeColors.textDim,
3035
+ ]);
3033
3036
 
3034
3037
  const cursorX = viewState.cursorX !== null ? clamp(viewState.cursorX, 0, chartWidth - 1) : null;
3035
3038
  const cursorY = viewState.cursorY !== null ? clamp(viewState.cursorY, 0, chartHeight - 1) : null;
@@ -3058,6 +3061,19 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3058
3061
  colors: chartColors,
3059
3062
  timeAxisDates,
3060
3063
  }), [axisMode, chartColors, chartHeight, chartWidth, compact, projection.effectiveMode, projection.points, selectionCursorX, selectionCursorY, showVolume, timeAxisDates, volumeHeight]);
3064
+ const displayScene = useMemo(() => buildChartScene(projection.points, {
3065
+ width: chartWidth,
3066
+ height: chartHeight,
3067
+ showVolume: showVolume && !compact,
3068
+ volumeHeight,
3069
+ cursorX: displayCursorX,
3070
+ cursorY: displayCursorY,
3071
+ mode: projection.effectiveMode,
3072
+ axisMode,
3073
+ colors: chartColors,
3074
+ timeAxisDates,
3075
+ indicators,
3076
+ }), [axisMode, chartColors, chartHeight, chartWidth, compact, displayCursorX, displayCursorY, indicators, projection.effectiveMode, projection.points, showVolume, timeAxisDates, volumeHeight]);
3061
3077
  const snappedSelectionCursorX = selectionScene
3062
3078
  ? getPointTerminalColumn(selectionScene.activeIdx, projection.points.length, chartWidth, projection.effectiveMode)
3063
3079
  : null;
@@ -3419,6 +3435,8 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3419
3435
  : null;
3420
3436
  const isUpdating = !compact && (renderBodyState.updating || boundsBodyState.updating);
3421
3437
  const timeAxisLabel = selectionScene?.timeLabels ?? staticResult.timeLabels;
3438
+ const cursorTimeAxisColumn = hasDisplayCursor ? displayScene?.cursorColumn ?? null : null;
3439
+ const cursorTimeAxisDate = hasDisplayCursor ? displayScene?.dateAtCursor ?? null : null;
3422
3440
  const timeAxisBox = (
3423
3441
  <Box
3424
3442
  height={1}
@@ -3426,7 +3444,15 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3426
3444
  overflow="hidden"
3427
3445
  data-gloom-role="chart-time-axis"
3428
3446
  >
3429
- <Text fg={colors.textDim} style={{ whiteSpace: "pre" }}>{timeAxisLabel}</Text>
3447
+ <TimeAxisLabel
3448
+ timeLabels={timeAxisLabel}
3449
+ width={chartWidth}
3450
+ cursorColumn={cursorTimeAxisColumn}
3451
+ cursorPixelX={hasDisplayCursor ? displayCursor.pixelX : null}
3452
+ cursorDate={cursorTimeAxisDate}
3453
+ dates={timeAxisDates}
3454
+ cursorColor={chartColors.crosshairColor}
3455
+ />
3430
3456
  </Box>
3431
3457
  );
3432
3458
  const chartFooterHints = useMemo<PaneHint[]>(() => {
@@ -3573,6 +3599,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3573
3599
 
3574
3600
  const handlePlotMove = (event: ChartMouseEvent) => {
3575
3601
  if (!interactive || compact) return;
3602
+ if (mouseCrosshairDisabledRef.current) return;
3576
3603
  const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
3577
3604
  if (!localPointer) return;
3578
3605
  const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
@@ -3594,6 +3621,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3594
3621
  if (compact) return;
3595
3622
  if (!interactive) onActivate?.();
3596
3623
  focusPaneForMouseInteraction(event);
3624
+ mouseCrosshairDisabledRef.current = false;
3597
3625
  const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
3598
3626
  if (!localPointer) return;
3599
3627
  const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
@@ -3618,6 +3646,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3618
3646
 
3619
3647
  const handlePlotDrag = (event: ChartMouseEvent) => {
3620
3648
  if (compact || (!interactive && !dragRef.current)) return;
3649
+ if (mouseCrosshairDisabledRef.current && !dragRef.current) return;
3621
3650
  const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
3622
3651
  if (localPointer) {
3623
3652
  const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
@@ -3938,17 +3967,16 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3938
3967
  );
3939
3968
 
3940
3969
  const axisBox = (
3941
- <Box width={axisSectionWidth} height={chartHeight} flexDirection="column">
3942
- {Array.from({ length: chartHeight }, (_, row) => {
3943
- const isCursorRow = cursorAxisLabel !== null && cursorRow === row;
3944
- const label = isCursorRow ? cursorAxisLabel : (axisLabels.get(row) ?? null);
3945
- return (
3946
- <Text key={row} fg={isCursorRow ? chartColors.crosshairColor : colors.textDim}>
3947
- {formatAxisCell(label, axisWidth).padEnd(axisSectionWidth)}
3948
- </Text>
3949
- );
3950
- })}
3951
- </Box>
3970
+ <PriceAxisLabels
3971
+ axisLabels={axisLabels}
3972
+ axisWidth={axisWidth}
3973
+ axisSectionWidth={axisSectionWidth}
3974
+ height={chartHeight}
3975
+ cursorRow={cursorRow}
3976
+ cursorPixelY={hasDisplayCursor ? displayCursor.pixelY : null}
3977
+ cursorLabel={cursorAxisLabel}
3978
+ cursorColor={chartColors.crosshairColor}
3979
+ />
3952
3980
  );
3953
3981
 
3954
3982
  if (compact) {
@@ -0,0 +1,120 @@
1
+ import { useMemo } from "react";
2
+ import { Box, Span, Text, useUiCapabilities } from "../../ui";
3
+ import { colors } from "../../theme/colors";
4
+ import { buildCursorTimeAxisSegments } from "./chart-renderer";
5
+
6
+ interface TimeAxisLabelProps {
7
+ timeLabels: string;
8
+ width: number;
9
+ cursorColumn: number | null;
10
+ cursorPixelX?: number | null;
11
+ cursorDate: Date | string | number | null;
12
+ dates: Array<Date | string | number>;
13
+ cursorColor: string;
14
+ }
15
+
16
+ interface CursorTimeAxisOverlay {
17
+ baseText: string;
18
+ label: string | null;
19
+ leftPercent: number | null;
20
+ }
21
+
22
+ function clamp(value: number, min: number, max: number): number {
23
+ return Math.max(min, Math.min(max, value));
24
+ }
25
+
26
+ export function buildCursorTimeAxisOverlay({
27
+ segments,
28
+ width,
29
+ cursorPixelX,
30
+ cellWidthPx,
31
+ }: {
32
+ segments: ReturnType<typeof buildCursorTimeAxisSegments>;
33
+ width: number;
34
+ cursorPixelX: number | null | undefined;
35
+ cellWidthPx: number;
36
+ }): CursorTimeAxisOverlay {
37
+ const baseText = segments
38
+ .map((segment) => (segment.highlighted ? " ".repeat(segment.text.length) : segment.text))
39
+ .join("")
40
+ .padEnd(width)
41
+ .slice(0, width);
42
+ const label = segments.find((segment) => segment.highlighted)?.text ?? null;
43
+ const pixelWidth = Math.max(width * cellWidthPx, 1);
44
+ let leftPercent: number | null = null;
45
+ if (label && cursorPixelX !== null && cursorPixelX !== undefined && Number.isFinite(cursorPixelX) && pixelWidth > 1) {
46
+ const halfLabelWidth = Math.min((label.length * cellWidthPx) / 2, (pixelWidth - 1) / 2);
47
+ const leftPx = clamp(cursorPixelX, halfLabelWidth, Math.max(pixelWidth - halfLabelWidth, halfLabelWidth));
48
+ leftPercent = (leftPx / Math.max(pixelWidth - 1, 1)) * 100;
49
+ }
50
+
51
+ return { baseText, label, leftPercent };
52
+ }
53
+
54
+ export function TimeAxisLabel({
55
+ timeLabels,
56
+ width,
57
+ cursorColumn,
58
+ cursorPixelX = null,
59
+ cursorDate,
60
+ dates,
61
+ cursorColor,
62
+ }: TimeAxisLabelProps) {
63
+ const { cellWidthPx = 8, fractionalViewport = false } = useUiCapabilities();
64
+ const segments = useMemo(() => buildCursorTimeAxisSegments({
65
+ timeLabels,
66
+ width,
67
+ cursorColumn,
68
+ cursorDate,
69
+ dates,
70
+ }), [cursorColumn, cursorDate, dates, timeLabels, width]);
71
+ const overlay = useMemo(() => buildCursorTimeAxisOverlay({
72
+ segments,
73
+ width,
74
+ cursorPixelX,
75
+ cellWidthPx,
76
+ }), [cellWidthPx, cursorPixelX, segments, width]);
77
+
78
+ if (fractionalViewport && overlay.label && overlay.leftPercent !== null) {
79
+ return (
80
+ <Box
81
+ width={width}
82
+ height={1}
83
+ overflow="hidden"
84
+ style={{ position: "relative" }}
85
+ >
86
+ <Text fg={colors.textDim} style={{ whiteSpace: "pre" }}>
87
+ {overlay.baseText}
88
+ </Text>
89
+ <Text
90
+ fg={cursorColor}
91
+ bg={colors.bg}
92
+ style={{
93
+ position: "absolute",
94
+ left: `${overlay.leftPercent}%`,
95
+ top: 0,
96
+ transform: "translateX(-50%)",
97
+ whiteSpace: "pre",
98
+ pointerEvents: "none",
99
+ zIndex: 1,
100
+ }}
101
+ >
102
+ {overlay.label}
103
+ </Text>
104
+ </Box>
105
+ );
106
+ }
107
+
108
+ return (
109
+ <Text style={{ whiteSpace: "pre" }}>
110
+ {segments.map((segment, index) => (
111
+ <Span
112
+ key={index}
113
+ fg={segment.highlighted ? cursorColor : colors.textDim}
114
+ >
115
+ {segment.text}
116
+ </Span>
117
+ ))}
118
+ </Text>
119
+ );
120
+ }
@@ -25,34 +25,6 @@ exports[`CommandBar renders the default layout with opencode-style chrome 1`] =
25
25
 
26
26
 
27
27
 
28
- "
29
- `;
30
-
31
- exports[`CommandBar renders theme prefix results in the root query 1`] = `
32
- "
33
-
34
- Commands
35
- TH
36
-
37
- Themes
38
- Amber current
39
- Green Phosphor
40
- Cyan
41
- Red Phosphor
42
- Blue Phosphor
43
- Purple Phosphor
44
- Hot Pink
45
- White Phosphor
46
- Tokyo Night
47
- Solarized Dark
48
- Dracula
49
- Nord
50
- Monokai
51
- Catppuccin Mocha
52
-
53
-
54
-
55
-
56
28
  "
57
29
  `;
58
30