gloomberb 0.7.0 → 0.7.2

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 (126) hide show
  1. package/package.json +1 -1
  2. package/src/app.tsx +149 -51
  3. package/src/capabilities/factories.ts +3 -0
  4. package/src/components/chart/chart-renderer.ts +24 -12
  5. package/src/components/chart/chart.test.ts +43 -0
  6. package/src/components/chart/comparison-chart-renderer.test.ts +26 -0
  7. package/src/components/chart/comparison-chart-renderer.ts +13 -6
  8. package/src/components/chart/comparison-stock-chart.tsx +15 -12
  9. package/src/components/chart/price-axis-labels.tsx +114 -0
  10. package/src/components/chart/static-chart-surface.tsx +2 -0
  11. package/src/components/chart/stock-chart.tsx +31 -20
  12. package/src/components/chart/time-axis-label.tsx +78 -1
  13. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -28
  14. package/src/components/command-bar/command-bar.test.tsx +114 -31
  15. package/src/components/command-bar/command-bar.tsx +587 -335
  16. package/src/components/command-bar/command-registry.ts +0 -10
  17. package/src/components/command-bar/helpers.ts +2 -5
  18. package/src/components/command-bar/list-model.ts +1 -2
  19. package/src/components/command-bar/theme-picker.tsx +254 -0
  20. package/src/components/command-bar/view-model.test.ts +4 -4
  21. package/src/components/command-bar/view-model.ts +1 -1
  22. package/src/components/command-bar/workflow-types.ts +1 -2
  23. package/src/components/layout/detached-pane-shell.tsx +29 -10
  24. package/src/components/layout/floating-pane.tsx +5 -2
  25. package/src/components/layout/header.tsx +2 -0
  26. package/src/components/layout/pane-content.test.tsx +64 -0
  27. package/src/components/layout/pane-content.tsx +2 -0
  28. package/src/components/layout/pane-sizing.ts +9 -2
  29. package/src/components/layout/pane-title.ts +4 -1
  30. package/src/components/layout/pane.tsx +11 -5
  31. package/src/components/layout/shell.test.tsx +198 -0
  32. package/src/components/layout/shell.tsx +33 -2
  33. package/src/components/layout/status-bar.tsx +2 -0
  34. package/src/components/speedometer-gauge.tsx +29 -25
  35. package/src/components/table-view-shared.tsx +85 -3
  36. package/src/components/ticker-list-table-view.tsx +16 -2
  37. package/src/components/ticker-list-table.test.tsx +82 -1
  38. package/src/components/ticker-list-table.tsx +22 -9
  39. package/src/components/ui/data-table.tsx +12 -9
  40. package/src/components/ui/frame.tsx +2 -0
  41. package/src/components/ui/table-layout.test.ts +27 -1
  42. package/src/components/ui/table-layout.ts +39 -1
  43. package/src/components/ui/tabs.tsx +34 -1
  44. package/src/components/ui/ui.test.tsx +68 -1
  45. package/src/core/state/app-state.ts +4 -14
  46. package/src/market-data/coordinator.test.ts +75 -0
  47. package/src/market-data/coordinator.ts +218 -48
  48. package/src/news/aggregator.test.ts +49 -1
  49. package/src/news/aggregator.ts +48 -0
  50. package/src/news/hooks.ts +6 -2
  51. package/src/plugins/builtin/ai/screener-pane.tsx +3 -2
  52. package/src/plugins/builtin/analytics/index.tsx +1 -18
  53. package/src/plugins/builtin/chat-controller.test.ts +83 -1
  54. package/src/plugins/builtin/chat-controller.ts +491 -243
  55. package/src/plugins/builtin/chat.test.tsx +425 -7
  56. package/src/plugins/builtin/chat.tsx +1109 -211
  57. package/src/plugins/builtin/cloud-tweets.tsx +1 -0
  58. package/src/plugins/builtin/fear-greed/index.tsx +148 -39
  59. package/src/plugins/builtin/holders/index.test.ts +13 -1
  60. package/src/plugins/builtin/holders/index.tsx +54 -14
  61. package/src/plugins/builtin/market-movers/index.tsx +12 -18
  62. package/src/plugins/builtin/market-movers/screener.test.ts +39 -1
  63. package/src/plugins/builtin/market-movers/screener.ts +70 -17
  64. package/src/plugins/builtin/news-wire/breaking-pane.tsx +3 -2
  65. package/src/plugins/builtin/news-wire/industry-pane.tsx +4 -18
  66. package/src/plugins/builtin/news-wire/news-detail-view.tsx +46 -4
  67. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +3 -2
  68. package/src/plugins/builtin/notes.tsx +1 -0
  69. package/src/plugins/builtin/options.tsx +2 -16
  70. package/src/plugins/builtin/portfolio-list/pane.tsx +16 -39
  71. package/src/plugins/builtin/research/index.test.ts +94 -0
  72. package/src/plugins/builtin/research/index.tsx +159 -12
  73. package/src/plugins/builtin/ticker-detail/pane.tsx +28 -22
  74. package/src/plugins/builtin/ticker-detail.test.tsx +38 -0
  75. package/src/plugins/plugin-runtime.test.tsx +34 -0
  76. package/src/plugins/plugin-runtime.tsx +7 -2
  77. package/src/plugins/prediction-markets/controller.ts +0 -15
  78. package/src/plugins/prediction-markets/detail/pane.tsx +1 -0
  79. package/src/plugins/registry.ts +3 -3
  80. package/src/plugins/ticker-navigation.test.ts +66 -0
  81. package/src/plugins/ticker-navigation.ts +44 -0
  82. package/src/react/input.ts +1 -1
  83. package/src/renderers/electrobun/bun/index.ts +32 -11
  84. package/src/renderers/electrobun/bun/window-focus.ts +37 -0
  85. package/src/renderers/electrobun/shared/protocol.ts +7 -1
  86. package/src/renderers/electrobun/view/app-services.ts +88 -31
  87. package/src/renderers/electrobun/view/backend-rpc.ts +15 -0
  88. package/src/renderers/electrobun/view/data-table.tsx +111 -96
  89. package/src/renderers/electrobun/view/desktop-controls.tsx +8 -0
  90. package/src/renderers/electrobun/view/desktop-window-bridge.ts +12 -1
  91. package/src/renderers/electrobun/view/dialog-host.tsx +2 -0
  92. package/src/renderers/electrobun/view/input-host.tsx +1 -1
  93. package/src/renderers/electrobun/view/main.tsx +18 -1
  94. package/src/renderers/electrobun/view/styles.css +6 -8
  95. package/src/renderers/electrobun/view/toast-host.tsx +29 -3
  96. package/src/renderers/electrobun/view/ui-host.tsx +101 -5
  97. package/src/renderers/opentui/input-host.tsx +1 -1
  98. package/src/sources/gloomberb-cloud.test.ts +86 -1
  99. package/src/sources/gloomberb-cloud.ts +117 -21
  100. package/src/sources/provider-router.test.ts +122 -0
  101. package/src/sources/provider-router.ts +198 -11
  102. package/src/sources/yahoo-finance.test.ts +44 -0
  103. package/src/sources/yahoo-finance.ts +6 -0
  104. package/src/state/app-bootstrap.ts +20 -7
  105. package/src/state/app-context-selector.test.tsx +189 -4
  106. package/src/state/app-context.test.ts +20 -0
  107. package/src/state/app-context.tsx +48 -16
  108. package/src/state/use-inline-tickers.ts +13 -10
  109. package/src/theme/colors.test.ts +75 -0
  110. package/src/theme/colors.ts +64 -3
  111. package/src/theme/theme-context.tsx +26 -0
  112. package/src/types/capability-route-source.ts +1 -0
  113. package/src/types/data-provider.ts +15 -1
  114. package/src/types/desktop-window.ts +6 -0
  115. package/src/types/financials.ts +2 -0
  116. package/src/types/plugin.ts +1 -1
  117. package/src/ui/host.tsx +14 -2
  118. package/src/ui/index.tsx +1 -0
  119. package/src/utils/api-client.test.ts +32 -0
  120. package/src/utils/api-client.ts +119 -18
  121. package/src/utils/double-escape-close.test.ts +33 -0
  122. package/src/utils/double-escape-close.ts +40 -0
  123. package/src/utils/throttled-fetch.test.ts +22 -0
  124. package/src/utils/throttled-fetch.ts +3 -1
  125. package/src/utils/ticker-search.ts +53 -9
  126. package/src/version.ts +1 -1
@@ -0,0 +1,114 @@
1
+ import { useMemo } from "react";
2
+ import { Box, Text, useUiCapabilities } from "../../ui";
3
+ import { colors } from "../../theme/colors";
4
+ import { formatAxisCell } from "./chart-renderer";
5
+
6
+ interface PriceAxisLabelsProps {
7
+ axisLabels: ReadonlyMap<number, string>;
8
+ axisWidth: number;
9
+ axisSectionWidth: number;
10
+ height: number;
11
+ cursorRow: number | null;
12
+ cursorPixelY?: number | null;
13
+ cursorLabel: string | null;
14
+ cursorColor: string;
15
+ }
16
+
17
+ interface CursorPriceAxisOverlay {
18
+ labelText: string | null;
19
+ topPercent: 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 buildCursorPriceAxisOverlay({
27
+ axisWidth,
28
+ axisSectionWidth,
29
+ height,
30
+ cursorPixelY,
31
+ cursorLabel,
32
+ cellHeightPx,
33
+ }: {
34
+ axisWidth: number;
35
+ axisSectionWidth: number;
36
+ height: number;
37
+ cursorPixelY: number | null | undefined;
38
+ cursorLabel: string | null;
39
+ cellHeightPx: number;
40
+ }): CursorPriceAxisOverlay {
41
+ const labelText = cursorLabel === null
42
+ ? null
43
+ : formatAxisCell(cursorLabel, axisWidth).padEnd(axisSectionWidth);
44
+ const pixelHeight = Math.max(height * cellHeightPx, 1);
45
+ let topPercent: number | null = null;
46
+
47
+ if (labelText && cursorPixelY !== null && cursorPixelY !== undefined && Number.isFinite(cursorPixelY) && pixelHeight > 1) {
48
+ const halfLabelHeight = Math.min(cellHeightPx / 2, (pixelHeight - 1) / 2);
49
+ const topPx = clamp(cursorPixelY, halfLabelHeight, Math.max(pixelHeight - halfLabelHeight, halfLabelHeight));
50
+ topPercent = (topPx / Math.max(pixelHeight - 1, 1)) * 100;
51
+ }
52
+
53
+ return { labelText, topPercent };
54
+ }
55
+
56
+ export function PriceAxisLabels({
57
+ axisLabels,
58
+ axisWidth,
59
+ axisSectionWidth,
60
+ height,
61
+ cursorRow,
62
+ cursorPixelY = null,
63
+ cursorLabel,
64
+ cursorColor,
65
+ }: PriceAxisLabelsProps) {
66
+ const { cellHeightPx = 18, fractionalViewport = false } = useUiCapabilities();
67
+ const overlay = useMemo(() => buildCursorPriceAxisOverlay({
68
+ axisWidth,
69
+ axisSectionWidth,
70
+ height,
71
+ cursorPixelY,
72
+ cursorLabel,
73
+ cellHeightPx,
74
+ }), [axisSectionWidth, axisWidth, cellHeightPx, cursorLabel, cursorPixelY, height]);
75
+ const usePixelOverlay = fractionalViewport && overlay.labelText !== null && overlay.topPercent !== null;
76
+
77
+ return (
78
+ <Box
79
+ width={axisSectionWidth}
80
+ height={height}
81
+ flexDirection="column"
82
+ overflow="hidden"
83
+ style={usePixelOverlay ? { position: "relative" } : undefined}
84
+ >
85
+ {Array.from({ length: height }, (_, row) => {
86
+ const isCursorRow = !usePixelOverlay && cursorLabel !== null && cursorRow === row;
87
+ const label = isCursorRow ? cursorLabel : (axisLabels.get(row) ?? null);
88
+ return (
89
+ <Text key={row} fg={isCursorRow ? cursorColor : colors.textDim}>
90
+ {formatAxisCell(label, axisWidth).padEnd(axisSectionWidth)}
91
+ </Text>
92
+ );
93
+ })}
94
+ {usePixelOverlay ? (
95
+ <Text
96
+ width={axisSectionWidth}
97
+ fg={cursorColor}
98
+ bg={colors.bg}
99
+ style={{
100
+ position: "absolute",
101
+ left: 0,
102
+ top: `${overlay.topPercent}%`,
103
+ transform: "translateY(-50%)",
104
+ whiteSpace: "pre",
105
+ pointerEvents: "none",
106
+ zIndex: 1,
107
+ }}
108
+ >
109
+ {overlay.labelText}
110
+ </Text>
111
+ ) : null}
112
+ </Box>
113
+ );
114
+ }
@@ -1,5 +1,6 @@
1
1
  import { useMemo } from "react";
2
2
  import { Box, ChartSurface, Text, useNativeRenderer, useUiCapabilities } from "../../ui";
3
+ import { useThemeColors } from "../../theme/theme-context";
3
4
  import type { ProjectedChartPoint } from "./chart-data";
4
5
  import {
5
6
  buildChartScene,
@@ -45,6 +46,7 @@ export function StaticChartSurface({
45
46
  yAxisColor,
46
47
  formatYAxisValue,
47
48
  }: StaticChartSurfaceProps) {
49
+ useThemeColors();
48
50
  const {
49
51
  canvasCharts,
50
52
  nativeCharts,
@@ -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";
@@ -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,
@@ -128,6 +128,7 @@ import {
128
128
  import type { PricePoint, TickerFinancials } from "../../types/financials";
129
129
  import type { TickerRecord } from "../../types/ticker";
130
130
  import { TimeAxisLabel } from "./time-axis-label";
131
+ import { PriceAxisLabels } from "./price-axis-labels";
131
132
 
132
133
  const MODE_CHIPS: Record<ChartRenderMode, string> = {
133
134
  area: "A",
@@ -1436,6 +1437,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
1436
1437
  ticker,
1437
1438
  financials,
1438
1439
  }: ResolvedStockChartProps) {
1440
+ const themeColors = useThemeColors();
1439
1441
  const renderer = useNativeRenderer();
1440
1442
  const { canvasCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
1441
1443
  const dispatch = useAppDispatch();
@@ -3013,15 +3015,24 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3013
3015
  : 0;
3014
3016
  const trend = rawChange < 0 ? "negative" : rawChange > 0 ? "positive" : "neutral";
3015
3017
  return resolveChartPalette({
3016
- bg: colors.bg,
3017
- border: colors.border,
3018
- borderFocused: colors.borderFocused,
3019
- text: colors.text,
3020
- textDim: colors.textDim,
3021
- positive: colors.positive,
3022
- 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,
3023
3025
  }, trend);
3024
- }, [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
+ ]);
3025
3036
 
3026
3037
  const cursorX = viewState.cursorX !== null ? clamp(viewState.cursorX, 0, chartWidth - 1) : null;
3027
3038
  const cursorY = viewState.cursorY !== null ? clamp(viewState.cursorY, 0, chartHeight - 1) : null;
@@ -3437,6 +3448,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3437
3448
  timeLabels={timeAxisLabel}
3438
3449
  width={chartWidth}
3439
3450
  cursorColumn={cursorTimeAxisColumn}
3451
+ cursorPixelX={hasDisplayCursor ? displayCursor.pixelX : null}
3440
3452
  cursorDate={cursorTimeAxisDate}
3441
3453
  dates={timeAxisDates}
3442
3454
  cursorColor={chartColors.crosshairColor}
@@ -3955,17 +3967,16 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
3955
3967
  );
3956
3968
 
3957
3969
  const axisBox = (
3958
- <Box width={axisSectionWidth} height={chartHeight} flexDirection="column">
3959
- {Array.from({ length: chartHeight }, (_, row) => {
3960
- const isCursorRow = cursorAxisLabel !== null && cursorRow === row;
3961
- const label = isCursorRow ? cursorAxisLabel : (axisLabels.get(row) ?? null);
3962
- return (
3963
- <Text key={row} fg={isCursorRow ? chartColors.crosshairColor : colors.textDim}>
3964
- {formatAxisCell(label, axisWidth).padEnd(axisSectionWidth)}
3965
- </Text>
3966
- );
3967
- })}
3968
- </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
+ />
3969
3980
  );
3970
3981
 
3971
3982
  if (compact) {
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from "react";
2
- import { Span, Text } from "../../ui";
2
+ import { Box, Span, Text, useUiCapabilities } from "../../ui";
3
3
  import { colors } from "../../theme/colors";
4
4
  import { buildCursorTimeAxisSegments } from "./chart-renderer";
5
5
 
@@ -7,19 +7,60 @@ interface TimeAxisLabelProps {
7
7
  timeLabels: string;
8
8
  width: number;
9
9
  cursorColumn: number | null;
10
+ cursorPixelX?: number | null;
10
11
  cursorDate: Date | string | number | null;
11
12
  dates: Array<Date | string | number>;
12
13
  cursorColor: string;
13
14
  }
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
+
15
54
  export function TimeAxisLabel({
16
55
  timeLabels,
17
56
  width,
18
57
  cursorColumn,
58
+ cursorPixelX = null,
19
59
  cursorDate,
20
60
  dates,
21
61
  cursorColor,
22
62
  }: TimeAxisLabelProps) {
63
+ const { cellWidthPx = 8, fractionalViewport = false } = useUiCapabilities();
23
64
  const segments = useMemo(() => buildCursorTimeAxisSegments({
24
65
  timeLabels,
25
66
  width,
@@ -27,6 +68,42 @@ export function TimeAxisLabel({
27
68
  cursorDate,
28
69
  dates,
29
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
+ }
30
107
 
31
108
  return (
32
109
  <Text style={{ whiteSpace: "pre" }}>
@@ -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