gloomberb 0.7.2 → 0.7.4

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 (167) hide show
  1. package/README.md +79 -132
  2. package/package.json +1 -1
  3. package/src/app.tsx +73 -67
  4. package/src/components/chart/chart-data.ts +7 -2
  5. package/src/components/chart/chart-pane-settings.ts +7 -4
  6. package/src/components/chart/chart-renderer.ts +158 -47
  7. package/src/components/chart/chart-types.ts +20 -2
  8. package/src/components/chart/chart.test.ts +179 -13
  9. package/src/components/chart/comparison-chart-renderer.test.ts +14 -30
  10. package/src/components/chart/comparison-chart-renderer.ts +50 -11
  11. package/src/components/chart/comparison-stock-chart.tsx +129 -129
  12. package/src/components/chart/market-session.ts +187 -0
  13. package/src/components/chart/native/chart-rasterizer.ts +108 -19
  14. package/src/components/chart/price-axis-labels.tsx +25 -9
  15. package/src/components/chart/stock-chart-renderer-switch.test.tsx +97 -1
  16. package/src/components/chart/stock-chart.tsx +95 -49
  17. package/src/components/chart/time-axis-label.tsx +56 -2
  18. package/src/components/command-bar/command-bar.test.tsx +446 -120
  19. package/src/components/command-bar/command-bar.tsx +535 -384
  20. package/src/components/command-bar/command-registry.ts +16 -6
  21. package/src/components/command-bar/list-model.ts +8 -4
  22. package/src/components/command-bar/root-shortcuts.ts +4 -5
  23. package/src/components/command-bar/view-model.test.ts +21 -0
  24. package/src/components/command-bar/view-model.ts +17 -4
  25. package/src/components/command-bar/workflow-ops.ts +11 -32
  26. package/src/components/data-table-view.test.tsx +24 -1
  27. package/src/components/data-table-view.tsx +3 -2
  28. package/src/components/feed-data-table-stack-view.tsx +3 -2
  29. package/src/components/layout/detached-pane-shell.tsx +2 -2
  30. package/src/components/layout/floating-pane.tsx +9 -2
  31. package/src/components/layout/pane-footer.tsx +1 -1
  32. package/src/components/layout/pane-header.tsx +4 -2
  33. package/src/components/layout/pane-sizing.ts +12 -0
  34. package/src/components/layout/pane.tsx +24 -2
  35. package/src/components/layout/shell.tsx +24 -10
  36. package/src/components/layout/status-bar.test.tsx +4 -1
  37. package/src/components/layout/status-bar.tsx +4 -24
  38. package/src/components/markdown-text.tsx +53 -8
  39. package/src/components/onboarding/onboarding-wizard.tsx +7 -6
  40. package/src/components/pane-settings-dialog.tsx +9 -6
  41. package/src/components/pane-template-wizard.tsx +3 -2
  42. package/src/components/price-selector-dialog.tsx +3 -2
  43. package/src/components/price-sparkline-view.tsx +463 -0
  44. package/src/components/price-sparkline.ts +46 -0
  45. package/src/components/table-view-shared.tsx +21 -3
  46. package/src/components/ticker-list-table-view.tsx +3 -2
  47. package/src/components/ticker-list-table.tsx +15 -66
  48. package/src/components/toggle-list.tsx +3 -3
  49. package/src/components/ui/choice-dialog.tsx +3 -2
  50. package/src/components/ui/multi-select-dialog.tsx +3 -2
  51. package/src/components/ui/native-select.tsx +3 -3
  52. package/src/core/state/app-state.ts +210 -80
  53. package/src/core/state/session-persistence.ts +25 -0
  54. package/src/data/config-store-node.ts +113 -25
  55. package/src/data/config-store.test.ts +65 -3
  56. package/src/data/plugin-state-store.ts +39 -13
  57. package/src/data/sqlite-cache.test.ts +13 -0
  58. package/src/market-data/coordinator-subscriptions.test.ts +102 -18
  59. package/src/market-data/coordinator.test.ts +122 -2
  60. package/src/market-data/coordinator.ts +200 -56
  61. package/src/market-data/hooks.test.tsx +31 -0
  62. package/src/market-data/hooks.tsx +1 -0
  63. package/src/news/aggregator.test.ts +41 -0
  64. package/src/news/aggregator.ts +61 -9
  65. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
  66. package/src/plugins/builtin/ai/screener-pane.tsx +3 -0
  67. package/src/plugins/builtin/ai/settings.ts +3 -0
  68. package/src/plugins/builtin/broker-manager/index.tsx +4 -3
  69. package/src/plugins/builtin/chat-controller.test.ts +398 -17
  70. package/src/plugins/builtin/chat-controller.ts +263 -48
  71. package/src/plugins/builtin/chat.test.tsx +312 -6
  72. package/src/plugins/builtin/chat.tsx +245 -66
  73. package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
  74. package/src/plugins/builtin/correlation/colors.test.ts +39 -0
  75. package/src/plugins/builtin/correlation/colors.ts +87 -0
  76. package/src/plugins/builtin/correlation/index.test.tsx +151 -0
  77. package/src/plugins/builtin/correlation/index.tsx +92 -21
  78. package/src/plugins/builtin/debug.tsx +4 -3
  79. package/src/plugins/builtin/econ/index.tsx +3 -2
  80. package/src/plugins/builtin/help.test.tsx +2 -2
  81. package/src/plugins/builtin/holders/index.tsx +25 -13
  82. package/src/plugins/builtin/layout-manager.tsx +1 -7
  83. package/src/plugins/builtin/market-movers/index.tsx +72 -40
  84. package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +156 -0
  85. package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -90
  86. package/src/plugins/builtin/news-wire/index.tsx +1 -3
  87. package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
  88. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
  89. package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
  90. package/src/plugins/builtin/options.tsx +9 -17
  91. package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
  92. package/src/plugins/builtin/portfolio-list/metrics.ts +13 -2
  93. package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
  94. package/src/plugins/builtin/portfolio-list/pane.tsx +139 -23
  95. package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
  96. package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
  97. package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
  98. package/src/plugins/builtin/portfolio-list.test.tsx +311 -9
  99. package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
  100. package/src/plugins/builtin/sectors/index.tsx +275 -43
  101. package/src/plugins/builtin/sectors/sector-data.ts +41 -1
  102. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
  103. package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
  104. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
  105. package/src/plugins/builtin/ticker-detail/pane.tsx +18 -7
  106. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +445 -63
  107. package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
  108. package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
  109. package/src/plugins/builtin/world-indices/index.tsx +58 -19
  110. package/src/plugins/ibkr/trading-pane.tsx +3 -2
  111. package/src/plugins/plugin-runtime.tsx +2 -2
  112. package/src/plugins/prediction-markets/settings.ts +4 -0
  113. package/src/plugins/registry.test.ts +55 -0
  114. package/src/plugins/registry.ts +11 -4
  115. package/src/plugins/ticker-navigation.test.ts +35 -5
  116. package/src/plugins/ticker-navigation.ts +16 -22
  117. package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
  118. package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
  119. package/src/renderers/electrobun/bun/application-menu.ts +6 -1
  120. package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
  121. package/src/renderers/electrobun/bun/index.ts +52 -13
  122. package/src/renderers/electrobun/view/app-services.ts +51 -9
  123. package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
  124. package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
  125. package/src/renderers/electrobun/view/main.tsx +0 -16
  126. package/src/renderers/electrobun/view/styles.css +23 -21
  127. package/src/renderers/electrobun/view/ui-host.tsx +6 -4
  128. package/src/renderers/opentui/host.tsx +2 -1
  129. package/src/renderers/opentui/start.tsx +1 -1
  130. package/src/renderers/opentui/test-utils.tsx +6 -5
  131. package/src/sources/gloomberb-cloud.test.ts +55 -2
  132. package/src/sources/gloomberb-cloud.ts +180 -29
  133. package/src/sources/provider-router.test.ts +567 -10
  134. package/src/sources/provider-router.ts +323 -93
  135. package/src/sources/yahoo-finance.test.ts +37 -0
  136. package/src/sources/yahoo-finance.ts +48 -2
  137. package/src/state/app-bootstrap.test.ts +50 -23
  138. package/src/state/app-bootstrap.ts +29 -13
  139. package/src/state/app-context.test.ts +113 -0
  140. package/src/state/app-context.tsx +8 -4
  141. package/src/state/session-persistence.test.ts +5 -3
  142. package/src/state/use-inline-tickers.ts +3 -0
  143. package/src/state/use-quote-streaming.ts +11 -0
  144. package/src/theme/colors.ts +13 -14
  145. package/src/types/config.ts +9 -4
  146. package/src/types/data-provider.ts +5 -1
  147. package/src/types/financials.ts +1 -0
  148. package/src/types/plugin.ts +8 -1
  149. package/src/ui/context-menu.tsx +4 -4
  150. package/src/utils/api-client.test.ts +248 -0
  151. package/src/utils/api-client.ts +178 -26
  152. package/src/utils/exchanges.test.ts +16 -0
  153. package/src/utils/exchanges.ts +33 -0
  154. package/src/utils/fuzzy-search.ts +12 -1
  155. package/src/utils/keyboard.ts +31 -0
  156. package/src/utils/options.ts +1 -1
  157. package/src/utils/price-history.test.ts +28 -1
  158. package/src/utils/price-history.ts +16 -0
  159. package/src/utils/quote-freshness.test.ts +150 -0
  160. package/src/utils/quote-freshness.ts +42 -1
  161. package/src/utils/quote-resolution.ts +2 -0
  162. package/src/utils/quote-time.test.ts +34 -0
  163. package/src/utils/quote-time.ts +18 -5
  164. package/src/utils/ticker-search.test.ts +2 -2
  165. package/src/utils/ticker-search.ts +48 -1
  166. package/src/version.ts +1 -1
  167. package/src/plugins/builtin/news-wire/digest-store.ts +0 -44
@@ -18,8 +18,7 @@ import type { ColumnConfig } from "../types/config";
18
18
  import type { TickerFinancials, PricePoint } from "../types/financials";
19
19
  import type { TickerRecord } from "../types/ticker";
20
20
  import { padTo } from "../utils/format";
21
- import { renderChart, resolveChartPalette, type StyledContent } from "./chart/chart-renderer";
22
- import type { ProjectedChartPoint } from "./chart/chart-data";
21
+ import { PRICE_SPARKLINE_COLUMN_ID, PriceSparkline } from "./price-sparkline-view";
23
22
  import { tableContentWidthProps, useMeasuredTableContentWidth } from "./ui/table-layout";
24
23
  import { useViewport } from "../react/input";
25
24
  import { measurePerf } from "../utils/perf-marks";
@@ -57,17 +56,6 @@ const FLASHABLE_QUOTE_COLUMN_IDS = new Set([
57
56
  "pnl_pct",
58
57
  ]);
59
58
 
60
- function resolveQuoteFlashColor(direction: QuoteFlashDirection, fallbackColor: string): string {
61
- switch (direction) {
62
- case "up":
63
- return colors.positive;
64
- case "down":
65
- return colors.negative;
66
- default:
67
- return fallbackColor === colors.textDim ? colors.text : colors.textBright;
68
- }
69
- }
70
-
71
59
  export interface TickerListTableProps {
72
60
  columns: ColumnConfig[];
73
61
  tickers: TickerRecord[];
@@ -88,7 +76,6 @@ export interface TickerListTableProps {
88
76
  onRowActivate?: (ticker: TickerRecord) => void;
89
77
  emptyTitle?: string;
90
78
  emptyHint?: string;
91
- showSparklines?: boolean;
92
79
  virtualize?: boolean;
93
80
  overscan?: number;
94
81
  }
@@ -155,38 +142,6 @@ const TickerListHeader = memo(function TickerListHeader({
155
142
  );
156
143
  });
157
144
 
158
- function renderSparkline(priceHistory: PricePoint[]): StyledContent | null {
159
- if (priceHistory.length < 5) return null;
160
-
161
- const last20 = priceHistory.slice(-20);
162
- const points: ProjectedChartPoint[] = last20.map((pt) => ({
163
- date: pt.date,
164
- open: pt.open ?? pt.close,
165
- high: pt.high ?? pt.close,
166
- low: pt.low ?? pt.close,
167
- close: pt.close,
168
- volume: pt.volume ?? 0,
169
- }));
170
-
171
- const first = last20[0]?.close ?? 0;
172
- const last = last20[last20.length - 1]?.close ?? 0;
173
- const trend = last >= first ? "positive" : "negative";
174
- const palette = resolveChartPalette(colors, trend);
175
-
176
- const result = renderChart(points, {
177
- width: 10,
178
- height: 1,
179
- showVolume: false,
180
- volumeHeight: 0,
181
- cursorX: null,
182
- cursorY: null,
183
- mode: "line",
184
- colors: palette,
185
- });
186
-
187
- return result.lines[0] ?? null;
188
- }
189
-
190
145
  const TickerListRow = memo(function TickerListRow({
191
146
  columns,
192
147
  ticker,
@@ -201,7 +156,6 @@ const TickerListRow = memo(function TickerListRow({
201
156
  onRowActivate,
202
157
  onRowContextMenu,
203
158
  openContextMenuOnMouseDown,
204
- showSparklines,
205
159
  priceHistory,
206
160
  contentWidth,
207
161
  }: {
@@ -218,7 +172,6 @@ const TickerListRow = memo(function TickerListRow({
218
172
  onRowActivate?: (ticker: TickerRecord) => void;
219
173
  onRowContextMenu: (ticker: TickerRecord, financials: TickerFinancials | undefined, event: unknown) => void;
220
174
  openContextMenuOnMouseDown: boolean;
221
- showSparklines?: boolean;
222
175
  priceHistory?: PricePoint[];
223
176
  contentWidth: number;
224
177
  }) {
@@ -226,10 +179,6 @@ const TickerListRow = memo(function TickerListRow({
226
179
  const lastActivatedAtRef = useRef<number | null>(null);
227
180
  const rowBg = isSelected ? colors.selected : isHovered ? hoverBg() : colors.bg;
228
181
 
229
- const sparkline = showSparklines && priceHistory && priceHistory.length >= 5
230
- ? renderSparkline(priceHistory)
231
- : null;
232
-
233
182
  return (
234
183
  <Box
235
184
  flexDirection="row"
@@ -268,26 +217,29 @@ const TickerListRow = memo(function TickerListRow({
268
217
  }}
269
218
  >
270
219
  {columns.map((column) => {
220
+ if (column.id === PRICE_SPARKLINE_COLUMN_ID) {
221
+ return (
222
+ <Box key={column.id} width={column.width + 1} overflow="hidden">
223
+ <PriceSparkline priceHistory={priceHistory} width={column.width} />
224
+ </Box>
225
+ );
226
+ }
227
+
271
228
  const { text, color } = resolveCell(column, ticker, financials);
272
229
  const baseFg = color || (isSelected ? colors.selectedText : colors.text);
273
230
  const shouldFlash = flashDirection != null && FLASHABLE_QUOTE_COLUMN_IDS.has(column.id);
274
- const cellFg = shouldFlash
275
- ? resolveQuoteFlashColor(flashDirection, baseFg)
276
- : baseFg;
277
231
 
278
232
  return (
279
233
  <Box key={column.id} width={column.width + 1}>
280
- <Text fg={cellFg}>
234
+ <Text
235
+ fg={baseFg}
236
+ attributes={shouldFlash ? TextAttributes.DIM : TextAttributes.NONE}
237
+ >
281
238
  {padTo(text, column.width, column.align)}
282
239
  </Text>
283
240
  </Box>
284
241
  );
285
242
  })}
286
- {sparkline && (
287
- <Box width={12}>
288
- <Text content={sparkline} />
289
- </Box>
290
- )}
291
243
  </Box>
292
244
  );
293
245
  }, (previous, next) => (
@@ -304,7 +256,6 @@ const TickerListRow = memo(function TickerListRow({
304
256
  && previous.onRowActivate === next.onRowActivate
305
257
  && previous.onRowContextMenu === next.onRowContextMenu
306
258
  && previous.openContextMenuOnMouseDown === next.openContextMenuOnMouseDown
307
- && previous.showSparklines === next.showSparklines
308
259
  && previous.priceHistory === next.priceHistory
309
260
  && previous.contentWidth === next.contentWidth
310
261
  ));
@@ -329,7 +280,6 @@ export function TickerListTable({
329
280
  onRowActivate,
330
281
  emptyTitle = "No tickers.",
331
282
  emptyHint = "Press Ctrl+P to add one.",
332
- showSparklines,
333
283
  virtualize = true,
334
284
  overscan = 4,
335
285
  }: TickerListTableProps) {
@@ -347,8 +297,8 @@ export function TickerListTable({
347
297
  hoveredIdxRef.current = hoveredIdx;
348
298
  const safeFlashSymbols = flashSymbols ?? EMPTY_FLASH_SYMBOLS;
349
299
  const tableWidth = useMemo(
350
- () => columns.reduce((sum, column) => sum + column.width + 1, 2) + (showSparklines ? 12 : 0),
351
- [columns, showSparklines],
300
+ () => columns.reduce((sum, column) => sum + column.width + 1, 2),
301
+ [columns],
352
302
  );
353
303
  const { contentWidth, measureContentWidth } = useMeasuredTableContentWidth(
354
304
  tableWidth,
@@ -492,7 +442,6 @@ export function TickerListTable({
492
442
  onRowActivate={onRowActivate}
493
443
  onRowContextMenu={handleRowContextMenu}
494
444
  openContextMenuOnMouseDown={nativeContextMenu !== true}
495
- showSparklines={showSparklines}
496
445
  priceHistory={financialsMap.get(ticker.metadata.ticker)?.priceHistory}
497
446
  contentWidth={contentWidth}
498
447
  />
@@ -1,6 +1,6 @@
1
1
  import { Box, Text, useUiHost } from "../ui";
2
2
  import { TextAttributes } from "../ui";
3
- import { colors } from "../theme/colors";
3
+ import { blendHex, colors } from "../theme/colors";
4
4
  import { ListView, type ListRowState, type ListViewItem } from "./ui/list-view";
5
5
 
6
6
  export interface ToggleListItem {
@@ -76,8 +76,8 @@ function DesktopToggleRow({
76
76
  border: `1px solid ${checkboxBorder}`,
77
77
  borderRadius: 4,
78
78
  boxShadow: enabled
79
- ? "inset 0 1px 0 rgba(255,255,255,0.28)"
80
- : "inset 0 1px 0 rgba(255,255,255,0.05)",
79
+ ? `inset 0 1px 0 ${blendHex(colors.borderFocused, colors.textBright, 0.28)}`
80
+ : `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.05)}`,
81
81
  }}
82
82
  >
83
83
  {enabled && (
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from "react";
2
2
  import { Box, Text } from "../../ui";
3
3
  import { type PromptContext, useDialogKeyboard } from "../../ui/dialog";
4
4
  import { colors } from "../../theme/colors";
5
+ import { isPlainKey } from "../../utils/keyboard";
5
6
  import { DialogFrame } from "./frame";
6
7
  import { ListView, type ListViewItem } from "./list-view";
7
8
 
@@ -58,9 +59,9 @@ export function ChoiceDialog({
58
59
 
59
60
  useDialogKeyboard((event) => {
60
61
  event.stopPropagation();
61
- if (event.name === "up" || event.name === "k") {
62
+ if (isPlainKey(event, "up", "k")) {
62
63
  setIndex((current) => clampChoiceIndex(current - 1, choices.length));
63
- } else if (event.name === "down" || event.name === "j") {
64
+ } else if (isPlainKey(event, "down", "j")) {
64
65
  setIndex((current) => clampChoiceIndex(current + 1, choices.length));
65
66
  } else if (event.name === "enter" || event.name === "return") {
66
67
  activateChoice(selectedChoice);
@@ -5,6 +5,7 @@ import { type AlertContext, useDialog, useDialogKeyboard } from "../../ui/dialog
5
5
  import { type ReactNode, useEffect, useMemo, useState } from "react";
6
6
  import { colors } from "../../theme/colors";
7
7
  import { isDetailBackNavigationKey } from "../../utils/back-navigation";
8
+ import { isPlainKey } from "../../utils/keyboard";
8
9
  import { ToggleList } from "../toggle-list";
9
10
  import { Button } from "./button";
10
11
  import { DialogFrame } from "./frame";
@@ -176,10 +177,10 @@ export function MultiSelectDialogContent({
176
177
 
177
178
  useDialogKeyboard((event) => {
178
179
  event.stopPropagation();
179
- if (event.name === "up" || event.name === "k") {
180
+ if (isPlainKey(event, "up", "k")) {
180
181
  const nextIndex = Math.max(0, selectedIndex - 1);
181
182
  setSelectedOptionId(displayOptions[nextIndex]?.value ?? selectedOptionId);
182
- } else if (event.name === "down" || event.name === "j") {
183
+ } else if (isPlainKey(event, "down", "j")) {
183
184
  const nextIndex = Math.min(displayOptions.length - 1, selectedIndex + 1);
184
185
  setSelectedOptionId(displayOptions[nextIndex]?.value ?? selectedOptionId);
185
186
  } else if (isSpaceKey(event)) {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { type CSSProperties } from "react";
4
4
  import { Box } from "../../ui";
5
- import { colors } from "../../theme/colors";
5
+ import { blendHex, colors } from "../../theme/colors";
6
6
 
7
7
  export type NativeSelectElement = HTMLSelectElement & { showPicker?: () => void };
8
8
 
@@ -51,11 +51,11 @@ export function NativeSelect({
51
51
  width,
52
52
  height: 28,
53
53
  color: colors.text,
54
- backgroundColor: "rgba(255, 255, 255, 0.06)",
54
+ backgroundColor: blendHex(colors.panel, colors.textBright, 0.06),
55
55
  border: `1px solid ${colors.border}`,
56
56
  borderRadius: 6,
57
57
  padding: "0 8px",
58
- boxShadow: "inset 0 1px 0 rgba(255,255,255,0.05)",
58
+ boxShadow: `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.05)}`,
59
59
  cursor: "pointer",
60
60
  font: "inherit",
61
61
  letterSpacing: 0,