gloomberb 0.9.3 → 0.9.5

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 (176) hide show
  1. package/README.md +1 -0
  2. package/package.json +3 -2
  3. package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
  4. package/patches/react-reconciler@0.33.0.patch +10 -0
  5. package/src/app/runtime/desktop-menu.ts +5 -1
  6. package/src/app/runtime/ticker-refresh.ts +4 -1
  7. package/src/app.tsx +10 -1
  8. package/src/brokers/account-cache.ts +1 -1
  9. package/src/brokers/sync-broker-instance.test.ts +107 -0
  10. package/src/brokers/sync-broker-instance.ts +22 -10
  11. package/src/cli/desktop-pane-shot.ts +2 -1
  12. package/src/cli/entry.ts +8 -1
  13. package/src/cli/native-smoke.ts +5 -0
  14. package/src/cli/pane-functions/screenshot.ts +13 -1
  15. package/src/components/chart/bar-chart-renderer.test.ts +108 -3
  16. package/src/components/chart/bar-chart-renderer.ts +226 -27
  17. package/src/components/chart/core/pointer.ts +32 -6
  18. package/src/components/chart/native/bitmap-support.test.ts +27 -0
  19. package/src/components/chart/native/bitmap-support.ts +55 -0
  20. package/src/components/chart/native/kitty/index.test.ts +47 -0
  21. package/src/components/chart/native/surface/visibility.ts +8 -1
  22. package/src/components/chart/price-axis-labels.tsx +3 -1
  23. package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
  24. package/src/components/chart/static/bar-chart-surface.tsx +151 -28
  25. package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
  26. package/src/components/chart/static/chart/bitmap.ts +16 -9
  27. package/src/components/chart/static/chart/surface.test.tsx +92 -0
  28. package/src/components/chart/static/chart/surface.tsx +167 -24
  29. package/src/components/chart/stock-chart/index.test.ts +25 -0
  30. package/src/components/command-bar/layout-items/current-layout.ts +5 -1
  31. package/src/components/command-bar/list/view.tsx +1 -1
  32. package/src/components/command-bar/theme-picker.tsx +1 -1
  33. package/src/components/data-table/view.tsx +0 -12
  34. package/src/components/layout/detached-pane-shell.tsx +6 -3
  35. package/src/components/layout/floating-pane.tsx +6 -5
  36. package/src/components/layout/pane/content.tsx +20 -8
  37. package/src/components/layout/pane/footer/registration.tsx +6 -2
  38. package/src/components/layout/pane/frame.tsx +4 -4
  39. package/src/components/layout/pane/header.tsx +45 -4
  40. package/src/components/layout/pane/index.tsx +6 -4
  41. package/src/components/layout/pane/sizing.test.ts +43 -0
  42. package/src/components/layout/pane/sizing.ts +13 -4
  43. package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
  44. package/src/components/layout/shell/cursor-occlusion.ts +204 -0
  45. package/src/components/layout/shell/drag/runtime.ts +9 -2
  46. package/src/components/layout/shell/index.test.tsx +92 -2
  47. package/src/components/layout/shell/index.tsx +45 -3
  48. package/src/components/layout/shell/layout-state.test.ts +33 -0
  49. package/src/components/layout/shell/layout-state.ts +4 -9
  50. package/src/components/layout/shell/pane/layers.tsx +10 -4
  51. package/src/components/layout/shell/visible-layout.ts +17 -0
  52. package/src/components/layout/status-bar.test.tsx +6 -1
  53. package/src/components/layout/status-bar.tsx +10 -6
  54. package/src/components/onboarding/onboarding-steps.tsx +8 -28
  55. package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
  56. package/src/components/onboarding/onboarding-wizard.tsx +2 -4
  57. package/src/components/onboarding/portfolio-step/index.tsx +0 -12
  58. package/src/components/onboarding/portfolio-step/types.ts +1 -3
  59. package/src/components/onboarding/wizard-keyboard.ts +2 -5
  60. package/src/components/pane-settings-dialog/desktop.tsx +1 -1
  61. package/src/components/price-performance.tsx +1 -1
  62. package/src/components/price-sparkline/view.tsx +15 -14
  63. package/src/components/speedometer-gauge/terminal.tsx +18 -15
  64. package/src/components/table-view-shared.tsx +12 -14
  65. package/src/components/ticker/list-table-view.tsx +1 -7
  66. package/src/components/ticker/list-table.test.tsx +0 -4
  67. package/src/components/ui/data-table/opentui/index.tsx +29 -22
  68. package/src/components/ui/data-table/types.ts +10 -4
  69. package/src/components/ui/fields.tsx +10 -3
  70. package/src/components/ui/list-view.tsx +1 -1
  71. package/src/components/ui/page-stack-view.tsx +6 -2
  72. package/src/components/ui/table-layout.test.ts +25 -0
  73. package/src/components/ui/table-layout.ts +22 -6
  74. package/src/components/ui/tabs.tsx +27 -12
  75. package/src/components/ui/ui.test.tsx +87 -9
  76. package/src/core/state/app/layout-reducer.ts +2 -2
  77. package/src/plugins/builtin/account-management/form-components.tsx +2 -2
  78. package/src/plugins/builtin/alerts/pane.tsx +1 -1
  79. package/src/plugins/builtin/analytics/index.test.tsx +6 -2
  80. package/src/plugins/builtin/analytics/pane-model.ts +43 -8
  81. package/src/plugins/builtin/buildout/cells.tsx +1 -1
  82. package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
  83. package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
  84. package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
  85. package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
  86. package/src/plugins/builtin/chat/sidebar.tsx +1 -1
  87. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  88. package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
  89. package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
  90. package/src/plugins/builtin/debug/index.tsx +3 -4
  91. package/src/plugins/builtin/help/components.tsx +1 -1
  92. package/src/plugins/builtin/holders/treemap.tsx +1 -1
  93. package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
  94. package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
  95. package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
  96. package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
  97. package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
  98. package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
  99. package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
  100. package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
  101. package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
  102. package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
  103. package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
  104. package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
  105. package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
  106. package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
  107. package/src/plugins/builtin/layout-manager/index.tsx +5 -1
  108. package/src/plugins/builtin/options/table.ts +5 -5
  109. package/src/plugins/builtin/options/view.test.tsx +141 -4
  110. package/src/plugins/builtin/options/view.tsx +76 -15
  111. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
  112. package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
  113. package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
  114. package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -0
  115. package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
  116. package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
  117. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
  118. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
  119. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
  120. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
  121. package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
  122. package/src/plugins/catalog-ui.ts +2 -0
  123. package/src/plugins/ibkr/broker-adapter.ts +19 -0
  124. package/src/plugins/ibkr/config.test.ts +31 -2
  125. package/src/plugins/ibkr/config.ts +9 -2
  126. package/src/plugins/ibkr/flex/index.test.ts +18 -9
  127. package/src/plugins/ibkr/flex/index.ts +30 -0
  128. package/src/plugins/ibkr/flex/statement-client.ts +68 -14
  129. package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
  130. package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
  131. package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
  132. package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
  133. package/src/plugins/pane-manager/gridlock.ts +13 -5
  134. package/src/plugins/pane-manager/layout-state.ts +25 -0
  135. package/src/plugins/pane-manager.test.ts +33 -0
  136. package/src/plugins/pane-manager.ts +6 -1
  137. package/src/plugins/prediction-markets/chart.tsx +10 -4
  138. package/src/plugins/prediction-markets/detail/book.tsx +141 -96
  139. package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
  140. package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
  141. package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
  142. package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
  143. package/src/plugins/prediction-markets/detail.test.tsx +50 -0
  144. package/src/plugins/prediction-markets/pane.test.tsx +43 -0
  145. package/src/plugins/prediction-markets/pane.tsx +50 -22
  146. package/src/plugins/prediction-markets/test-helpers.tsx +41 -23
  147. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
  148. package/src/renderers/electrobun/view/data-table/index.tsx +19 -12
  149. package/src/renderers/electrobun/view/data-table/row.tsx +32 -18
  150. package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
  151. package/src/renderers/electrobun/view/host/box.tsx +6 -1
  152. package/src/renderers/electrobun/view/host/input.tsx +115 -18
  153. package/src/renderers/electrobun/view/host/style.ts +1 -0
  154. package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
  155. package/src/renderers/electrobun/view/styles.css +18 -0
  156. package/src/renderers/opentui/host.tsx +17 -1
  157. package/src/renderers/opentui/native-smoke.ts +1 -0
  158. package/src/renderers/opentui/ui-host.tsx +57 -25
  159. package/src/sources/provider-router/batches.ts +16 -2
  160. package/src/sources/provider-router/financial-routes.ts +5 -0
  161. package/src/sources/provider-router/financials.ts +11 -23
  162. package/src/sources/provider-router/index.test.ts +189 -0
  163. package/src/sources/provider-router/primary.ts +3 -2
  164. package/src/sources/sec-edgar.test.ts +83 -0
  165. package/src/sources/sec-edgar.ts +231 -0
  166. package/src/sources/yahoo-finance.test.ts +51 -0
  167. package/src/sources/yahoo-finance.ts +49 -1
  168. package/src/state/app/context/index.test.tsx +5 -8
  169. package/src/types/broker.ts +1 -0
  170. package/src/types/config.ts +10 -0
  171. package/src/types/trading.ts +2 -0
  172. package/src/ui/host.tsx +16 -0
  173. package/src/ui/index.tsx +2 -0
  174. package/src/utils/financial-statements.ts +23 -0
  175. package/src/version.ts +1 -1
  176. package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
@@ -1,8 +1,6 @@
1
- import { useMemo } from "react";
2
1
  import { AsciiText, Box, Span, Strong, Text, TextAttributes, useUiHost } from "../../ui";
3
2
  import { colors } from "../../theme/colors";
4
3
  import { themes } from "../../theme/themes";
5
- import type { PluginRegistry } from "../../plugins/registry";
6
4
  import { detectShortcutPlatform, formatPrimaryShortcut, getShortcutDisplayMode } from "../../utils/shortcut-labels";
7
5
  export { PortfolioStep, type PortfolioSub } from "./portfolio-step";
8
6
 
@@ -95,7 +93,7 @@ export function ThemeStep({ themeIds, selectedIdx, height }: { themeIds: string[
95
93
  );
96
94
  }
97
95
 
98
- export function ShortcutsStep({ pluginRegistry }: { pluginRegistry: PluginRegistry }) {
96
+ export function ShortcutsStep() {
99
97
  const uiHost = useUiHost();
100
98
  const shortcutPlatform = detectShortcutPlatform();
101
99
  const shortcutDisplayMode = getShortcutDisplayMode(uiHost.kind);
@@ -109,30 +107,12 @@ export function ShortcutsStep({ pluginRegistry }: { pluginRegistry: PluginRegist
109
107
  { key: "q", desc: "Quit" },
110
108
  ];
111
109
 
112
- const commandPrefixes = useMemo(() => {
113
- const builtIn = [
114
- { key: "DES AAPL", desc: "Open security details" },
115
- { key: "TH", desc: "Switch theme" },
116
- { key: "PL", desc: "Toggle plugins" },
117
- { key: "PS", desc: "Edit the current window settings" },
118
- { key: "HELP", desc: "Open the help window" },
119
- ];
120
-
121
- const builtInKeys = new Set(builtIn.map((entry) => entry.key.split(" ")[0]));
122
- const pluginPrefixes: { key: string; desc: string }[] = [];
123
-
124
- for (const [, template] of pluginRegistry.paneTemplates) {
125
- if (!template.shortcut) continue;
126
- if (builtInKeys.has(template.shortcut.prefix)) continue;
127
- const label = template.shortcut.argPlaceholder
128
- ? `${template.shortcut.prefix} <${template.shortcut.argPlaceholder}>`
129
- : template.shortcut.prefix;
130
- pluginPrefixes.push({ key: label, desc: template.label });
131
- }
132
-
133
- pluginPrefixes.sort((a, b) => a.key.localeCompare(b.key));
134
- return [...builtIn, ...pluginPrefixes];
135
- }, [pluginRegistry]);
110
+ const commandPrefixes = [
111
+ { key: "DES AAPL", desc: "Open security details" },
112
+ { key: "TH", desc: "Switch theme" },
113
+ { key: "PL", desc: "Toggle plugins" },
114
+ { key: "HELP", desc: "Open the help window" },
115
+ ];
136
116
 
137
117
  const COL = 20;
138
118
 
@@ -152,7 +132,7 @@ export function ShortcutsStep({ pluginRegistry }: { pluginRegistry: PluginRegist
152
132
 
153
133
  <Box height={2} />
154
134
  <Box height={1}>
155
- <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Command-bar prefixes"}</Text>
135
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Basic command prefixes"}</Text>
156
136
  </Box>
157
137
  <Box height={1} />
158
138
  <Box height={1}>
@@ -93,11 +93,14 @@ afterEach(async () => {
93
93
  });
94
94
 
95
95
  describe("OnboardingWizard", () => {
96
- test("uses backspace to go back when not editing and keeps backspace for text deletion while editing", async () => {
96
+ test("skips manual portfolio naming and shows compact starter shortcuts", async () => {
97
97
  const pluginRegistry = {
98
98
  allPlugins: new Map(),
99
99
  brokers: new Map(),
100
- paneTemplates: new Map(),
100
+ paneTemplates: new Map([[
101
+ "ai",
102
+ { label: "AI Screener", shortcut: { prefix: "AI", argPlaceholder: "prompt" } },
103
+ ]]),
101
104
  getPaneTemplatePluginId: () => undefined,
102
105
  tickerRepository: createTickerRepository(),
103
106
  persistence: { resources: undefined },
@@ -132,16 +135,14 @@ describe("OnboardingWizard", () => {
132
135
  expect(frame).toContain("Create Manual Portfolio");
133
136
 
134
137
  await emitKeypress(testSetup, { name: "return", sequence: "\r" });
135
- frame = await waitForFrameToContain("Main Portfolio");
136
- expect(frame).toContain("Main Portfolio");
137
-
138
- await act(async () => {
139
- testSetup!.mockInput.pressBackspace();
140
- await testSetup!.renderOnce();
141
- });
142
- frame = testSetup.captureCharFrame();
143
- expect(frame).toContain("Main Portfoli");
144
- expect(frame).not.toContain("Create Manual Portfolio");
138
+ frame = await waitForFrameToContain("After launch shortcuts");
139
+ expect(frame).toContain("Basic command prefixes");
140
+ expect(frame).toContain("DES AAPL");
141
+ expect(frame).toContain("HELP");
142
+ expect(frame).not.toContain("Name your portfolio");
143
+ expect(frame).not.toContain("Command-bar prefixes");
144
+ expect(frame).not.toContain("AI <prompt>");
145
+ expect(frame).not.toContain("AI Screener");
145
146
  });
146
147
 
147
148
  test("waits for broker positions to import before completing onboarding", async () => {
@@ -36,7 +36,7 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
36
36
  const [themeIdx, setThemeIdx] = useState(0);
37
37
  const [portfolioSub, setPortfolioSub] = useState<PortfolioSub>("choose");
38
38
  const [portfolioOptionIdx, setPortfolioOptionIdx] = useState(0);
39
- const [portfolioName, setPortfolioName] = useState("Main Portfolio");
39
+ const portfolioName = "Main Portfolio";
40
40
  const [brokerValues, setBrokerValues] = useState<Record<string, Record<string, string>>>({});
41
41
  const [selectedBrokerId, setSelectedBrokerId] = useState<string | null>(null);
42
42
  const [brokerFieldIdx, setBrokerFieldIdx] = useState(0);
@@ -255,8 +255,6 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
255
255
  choices={portfolioChoices}
256
256
  optionIdx={portfolioOptionIdx}
257
257
  onOptionSelect={setPortfolioOptionIdx}
258
- portfolioName={portfolioName}
259
- onNameChange={setPortfolioName}
260
258
  selectedBrokerId={selectedBrokerId}
261
259
  brokerFields={activeBrokerFields}
262
260
  brokerFieldIdx={brokerFieldIdx}
@@ -270,7 +268,7 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
270
268
  />
271
269
  )}
272
270
  {step === "shortcuts" && (
273
- <ShortcutsStep pluginRegistry={pluginRegistry} />
271
+ <ShortcutsStep />
274
272
  )}
275
273
  {step === "ready" && (
276
274
  <ReadyStep
@@ -2,7 +2,6 @@ import { BrokerFieldsPanel } from "./broker-fields-panel";
2
2
  import { BrokerSetupPanel } from "./broker-setup-panel";
3
3
  import { BrokerSyncPanel } from "./broker-sync-panel";
4
4
  import { PortfolioChoicePanel } from "./choose-panel";
5
- import { ManualPortfolioNamePanel } from "./manual-name-panel";
6
5
  import type { PortfolioStepProps, PortfolioSub } from "./types";
7
6
 
8
7
  export type { PortfolioSub };
@@ -18,17 +17,6 @@ export function PortfolioStep(props: PortfolioStepProps) {
18
17
  );
19
18
  }
20
19
 
21
- if (props.sub === "manual-name") {
22
- return (
23
- <ManualPortfolioNamePanel
24
- portfolioName={props.portfolioName}
25
- onNameChange={props.onNameChange}
26
- editing={props.editing}
27
- inputRef={props.inputRef}
28
- />
29
- );
30
- }
31
-
32
20
  if (props.sub === "broker-setup" && props.selectedBrokerId) {
33
21
  return (
34
22
  <BrokerSetupPanel
@@ -3,15 +3,13 @@ import type { InputRenderable } from "../../../ui";
3
3
  import type { BrokerConfigField } from "../../../types/broker";
4
4
  import type { ListViewItem } from "../../ui";
5
5
 
6
- export type PortfolioSub = "choose" | "manual-name" | "broker-setup" | "broker-fields" | "broker-sync";
6
+ export type PortfolioSub = "choose" | "broker-setup" | "broker-fields" | "broker-sync";
7
7
 
8
8
  export interface PortfolioStepProps {
9
9
  sub: PortfolioSub;
10
10
  choices: ListViewItem[];
11
11
  optionIdx: number;
12
12
  onOptionSelect: (idx: number) => void;
13
- portfolioName: string;
14
- onNameChange: (name: string) => void;
15
13
  selectedBrokerId: string | null;
16
14
  brokerFields: BrokerConfigField[];
17
15
  brokerFieldIdx: number;
@@ -74,9 +74,7 @@ export function useOnboardingKeyboard({
74
74
  if (editingField) {
75
75
  if (event.name === "return") {
76
76
  setEditingField(false);
77
- if (portfolioSub === "manual-name") {
78
- nextStep();
79
- } else if (portfolioSub === "broker-fields" && selectedBrokerId) {
77
+ if (portfolioSub === "broker-fields" && selectedBrokerId) {
80
78
  const currentField = activeBrokerFields[brokerFieldIdx];
81
79
  if (!currentField) {
82
80
  void syncSelectedBroker();
@@ -125,8 +123,7 @@ export function useOnboardingKeyboard({
125
123
  if (choice.id === "manual") {
126
124
  resetBrokerSync();
127
125
  setSelectedBrokerId(null);
128
- setPortfolioSub("manual-name");
129
- setEditingField(true);
126
+ nextStep();
130
127
  } else {
131
128
  resetBrokerSync();
132
129
  setSelectedBrokerId(choice.id);
@@ -183,7 +183,7 @@ function DesktopSettingsRow({
183
183
  flexDirection="column"
184
184
  minHeight={field.description ? undefined : 2}
185
185
  backgroundColor={hovered || selected ? desktopHoverSurface() : "transparent"}
186
- onMouseMove={onHover}
186
+ onMouseOver={onHover}
187
187
  onMouseDown={field.type === "select" ? undefined : (event: any) => {
188
188
  event.stopPropagation?.();
189
189
  event.preventDefault?.();
@@ -157,7 +157,7 @@ export function PriceReturnTable({
157
157
  height={1}
158
158
  width={width}
159
159
  backgroundColor={row.selected ? blendHex(colors.panel, colors.borderFocused, 0.18) : colors.panel}
160
- onMouseMove={() => onSelectSymbol(row.symbol)}
160
+ onMouseOver={() => onSelectSymbol(row.symbol)}
161
161
  onMouseDown={(event: any) => {
162
162
  onFocusInteraction?.(event);
163
163
  onSelectSymbol(row.symbol);
@@ -10,7 +10,7 @@ import {
10
10
  import { colors } from "../../theme/colors";
11
11
  import { useThemeColors } from "../../theme/theme-context";
12
12
  import type { PricePoint } from "../../types/financials";
13
- import { computeBitmapSize } from "../chart/native/chart-rasterizer";
13
+ import { resolveNativeBitmapSize, shouldRenderNativeBitmap } from "../chart/native/bitmap-support";
14
14
  import { renderSparklineBitmap } from "./bitmap";
15
15
  import {
16
16
  buildSamples,
@@ -90,22 +90,21 @@ function TerminalPriceSparkline({
90
90
  const { nativeCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
91
91
  const nativeRenderer = useNativeRenderer();
92
92
  const rendererResolution = nativeRenderer.resolution;
93
+ const rendererCapabilities = nativeRenderer.capabilities;
93
94
  const rendererTerminalWidth = nativeRenderer.terminalWidth;
94
95
  const rendererTerminalHeight = nativeRenderer.terminalHeight;
95
96
  const bitmap = useMemo(() => {
96
- if (!nativeCharts || values.length < 2) return null;
97
- const scale = Math.max(1, pixelRatio);
98
- const bitmapSize = rendererResolution && rendererTerminalWidth > 0 && rendererTerminalHeight > 0
99
- ? computeBitmapSize(
100
- { x: 0, y: 0, width, height },
101
- rendererResolution,
102
- rendererTerminalWidth,
103
- rendererTerminalHeight,
104
- )
105
- : {
106
- pixelWidth: Math.max(1, Math.round(width * cellWidthPx * scale)),
107
- pixelHeight: Math.max(1, Math.round(height * cellHeightPx * scale)),
108
- };
97
+ if (values.length < 2 || !shouldRenderNativeBitmap(nativeRenderer, nativeCharts === true)) return null;
98
+ const bitmapSize = resolveNativeBitmapSize({
99
+ width,
100
+ height,
101
+ resolution: rendererResolution,
102
+ terminalWidth: rendererTerminalWidth,
103
+ terminalHeight: rendererTerminalHeight,
104
+ cellWidthPx,
105
+ cellHeightPx,
106
+ pixelRatio,
107
+ });
109
108
  return renderSparklineBitmap(values, bitmapSize.pixelWidth, bitmapSize.pixelHeight, color, { area, compact: height <= 1 });
110
109
  }, [
111
110
  area,
@@ -114,7 +113,9 @@ function TerminalPriceSparkline({
114
113
  color,
115
114
  height,
116
115
  nativeCharts,
116
+ nativeRenderer,
117
117
  pixelRatio,
118
+ rendererCapabilities,
118
119
  rendererResolution,
119
120
  rendererTerminalHeight,
120
121
  rendererTerminalWidth,
@@ -1,6 +1,7 @@
1
1
  import { useMemo } from "react";
2
2
  import { Box, ChartSurface, Span, Text, TextAttributes, useNativeRenderer, useUiCapabilities } from "../../ui";
3
- import { computeBitmapSize, type NativeChartBitmap } from "../chart/native/chart-rasterizer";
3
+ import type { NativeChartBitmap } from "../chart/native/chart-rasterizer";
4
+ import { resolveNativeBitmapSize, shouldRenderNativeBitmap } from "../chart/native/bitmap-support";
4
5
  import { drawCircle, drawLine, parseHex } from "../chart/native/raster/primitives";
5
6
  import { colors } from "../../theme/colors";
6
7
  import {
@@ -203,29 +204,29 @@ export function TerminalSpeedometerGauge({
203
204
  const labelRow = useMemo(() => renderLabelRow(gaugeWidth, min, max, segments), [gaugeWidth, max, min, segments]);
204
205
  const tickRow = useMemo(() => renderTickRow(gaugeWidth, min, max), [gaugeWidth, max, min]);
205
206
  const arcRows = useMemo(() => renderArcRows(value, min, max, gaugeWidth, segments), [gaugeWidth, max, min, segments, value]);
207
+ const rendererCapabilities = nativeRenderer.capabilities;
206
208
  const rendererResolution = nativeRenderer.resolution;
207
209
  const rendererTerminalWidth = nativeRenderer.terminalWidth;
208
210
  const rendererTerminalHeight = nativeRenderer.terminalHeight;
209
211
  const bitmap = useMemo<NativeChartBitmap | null>(() => {
210
- if (!nativeCharts) return null;
211
- const bitmapSize = rendererResolution && rendererTerminalWidth > 0 && rendererTerminalHeight > 0
212
- ? computeBitmapSize(
213
- { x: 0, y: 0, width: gaugeWidth, height: arcRows.length },
214
- rendererResolution,
215
- rendererTerminalWidth,
216
- rendererTerminalHeight,
217
- )
218
- : null;
219
- const scale = Math.max(1, pixelRatio);
220
- const pixelWidth = bitmapSize?.pixelWidth ?? Math.max(1, Math.round(gaugeWidth * cellWidthPx * scale));
221
- const pixelHeight = bitmapSize?.pixelHeight ?? Math.max(1, Math.round(arcRows.length * cellHeightPx * scale));
212
+ if (!shouldRenderNativeBitmap(nativeRenderer, nativeCharts === true)) return null;
213
+ const bitmapSize = resolveNativeBitmapSize({
214
+ width: gaugeWidth,
215
+ height: arcRows.length,
216
+ resolution: rendererResolution,
217
+ terminalWidth: rendererTerminalWidth,
218
+ terminalHeight: rendererTerminalHeight,
219
+ cellWidthPx,
220
+ cellHeightPx,
221
+ pixelRatio,
222
+ });
222
223
  return renderGaugeBitmap(
223
224
  value,
224
225
  min,
225
226
  max,
226
227
  segments,
227
- pixelWidth,
228
- pixelHeight,
228
+ bitmapSize.pixelWidth,
229
+ bitmapSize.pixelHeight,
229
230
  );
230
231
  }, [
231
232
  arcRows.length,
@@ -235,7 +236,9 @@ export function TerminalSpeedometerGauge({
235
236
  max,
236
237
  min,
237
238
  nativeCharts,
239
+ nativeRenderer,
238
240
  pixelRatio,
241
+ rendererCapabilities,
239
242
  rendererResolution,
240
243
  rendererTerminalHeight,
241
244
  rendererTerminalWidth,
@@ -2,11 +2,10 @@ import {
2
2
  useCallback,
3
3
  useEffect,
4
4
  useRef,
5
- useState,
6
5
  type ReactNode,
7
6
  type RefObject,
8
7
  } from "react";
9
- import { Box, type ScrollBoxRenderable } from "../ui";
8
+ import { Box, useUiCapabilities, type ScrollBoxRenderable } from "../ui";
10
9
  import { isPlainKeyboardEvent } from "../utils/keyboard";
11
10
 
12
11
  function listenToScrollBarChange(
@@ -56,15 +55,23 @@ export function TableViewFrame({
56
55
  after,
57
56
  children,
58
57
  }: TableViewFrameProps) {
58
+ const { nativePaneChrome } = useUiCapabilities();
59
+ const nativeFlexibleFrame = nativePaneChrome === true;
60
+
59
61
  return (
60
62
  <Box
61
63
  flexDirection="column"
62
- flexGrow={height == null ? 1 : undefined}
63
- flexShrink={height == null ? undefined : 0}
64
+ flexGrow={nativeFlexibleFrame || height == null ? 1 : undefined}
65
+ flexShrink={nativeFlexibleFrame ? 1 : height == null ? undefined : 0}
66
+ flexBasis={nativeFlexibleFrame ? 0 : undefined}
64
67
  width={width}
65
- height={height}
68
+ height={nativeFlexibleFrame ? undefined : height}
69
+ minWidth={nativeFlexibleFrame ? 0 : undefined}
70
+ minHeight={nativeFlexibleFrame ? 0 : undefined}
71
+ maxHeight={nativeFlexibleFrame ? "100%" : undefined}
66
72
  backgroundColor={backgroundColor}
67
73
  overflow="hidden"
74
+ data-gloom-role="table-view-frame"
68
75
  >
69
76
  {before}
70
77
  {children}
@@ -106,23 +113,16 @@ export function useTableViewState({
106
113
  headerScrollRef,
107
114
  scrollRef,
108
115
  syncHeaderScroll,
109
- hoveredIdx,
110
- setHoveredIdx,
111
116
  }: {
112
117
  headerScrollRef?: RefObject<ScrollBoxRenderable | null>;
113
118
  scrollRef?: RefObject<ScrollBoxRenderable | null>;
114
119
  syncHeaderScroll?: () => void;
115
- hoveredIdx?: number | null;
116
- setHoveredIdx?: (index: number | null) => void;
117
120
  }) {
118
121
  const internalHeaderScrollRef = useRef<ScrollBoxRenderable>(null);
119
122
  const internalScrollRef = useRef<ScrollBoxRenderable>(null);
120
- const [internalHoveredIdx, setInternalHoveredIdx] = useState<number | null>(null);
121
123
 
122
124
  const effectiveHeaderScrollRef = headerScrollRef ?? internalHeaderScrollRef;
123
125
  const effectiveScrollRef = scrollRef ?? internalScrollRef;
124
- const effectiveHoveredIdx = hoveredIdx !== undefined ? hoveredIdx : internalHoveredIdx;
125
- const effectiveSetHoveredIdx = setHoveredIdx ?? setInternalHoveredIdx;
126
126
 
127
127
  const defaultSyncHeaderScroll = useCallback(() => {
128
128
  const body = effectiveScrollRef.current;
@@ -136,8 +136,6 @@ export function useTableViewState({
136
136
  return {
137
137
  effectiveHeaderScrollRef,
138
138
  effectiveScrollRef,
139
- effectiveHoveredIdx,
140
- effectiveSetHoveredIdx,
141
139
  effectiveSyncHeaderScroll: syncHeaderScroll ?? defaultSyncHeaderScroll,
142
140
  };
143
141
  }
@@ -56,8 +56,6 @@ export interface TickerListTableViewProps {
56
56
  scrollRef?: RefObject<ScrollBoxRenderable | null>;
57
57
  syncHeaderScroll?: () => void;
58
58
  onBodyScrollActivity?: () => void;
59
- hoveredIdx?: number | null;
60
- setHoveredIdx?: (index: number | null) => void;
61
59
  keyboardNavigation?: boolean;
62
60
  onRootKeyDown?: (event: DataTableKeyEvent) => boolean | void;
63
61
  onVisibleRangeChange?: (range: TickerListVisibleRange) => void;
@@ -119,8 +117,6 @@ export function TickerListTableView({
119
117
  scrollRef,
120
118
  syncHeaderScroll,
121
119
  onBodyScrollActivity,
122
- hoveredIdx,
123
- setHoveredIdx,
124
120
  keyboardNavigation = true,
125
121
  onRootKeyDown,
126
122
  onVisibleRangeChange,
@@ -187,7 +183,7 @@ export function TickerListTableView({
187
183
  ticker: TickerRecord,
188
184
  column: ColumnConfig,
189
185
  _index: number,
190
- rowState: { selected: boolean; hovered: boolean },
186
+ rowState: { selected: boolean },
191
187
  ) => {
192
188
  const financials = financialsMap.get(ticker.metadata.ticker);
193
189
  if (column.id === PRICE_SPARKLINE_COLUMN_ID) {
@@ -279,8 +275,6 @@ export function TickerListTableView({
279
275
  scrollRef={effectiveScrollRef}
280
276
  syncHeaderScroll={syncHeaderScroll}
281
277
  onBodyScrollActivity={handleBodyScrollActivity}
282
- hoveredIdx={hoveredIdx}
283
- setHoveredIdx={setHoveredIdx}
284
278
  keyboardNavigation={keyboardNavigation}
285
279
  onRootKeyDown={onRootKeyDown}
286
280
  resetScrollKey={resetScrollKey}
@@ -32,8 +32,6 @@ const manyTickers: TickerRecord[] = Array.from({ length: 1000 }, (_, index) => (
32
32
  },
33
33
  }));
34
34
 
35
- function noop(_index: number | null): void {}
36
-
37
35
  function resolveCell(_column: ColumnConfig, ticker: TickerRecord, _financials: TickerFinancials | undefined): TickerTableCell {
38
36
  resolveCellCallCount += 1;
39
37
  return { text: ticker.metadata.ticker };
@@ -69,8 +67,6 @@ function LargeTickerListTableHarness() {
69
67
  columns={columns}
70
68
  tickers={manyTickers}
71
69
  cursorSymbol={cursorSymbol}
72
- hoveredIdx={null}
73
- setHoveredIdx={noop}
74
70
  setCursorSymbol={setCursorSymbol}
75
71
  resolveCell={resolveCell}
76
72
  financialsMap={financialsMap}
@@ -40,14 +40,13 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
40
40
  scrollRef,
41
41
  syncHeaderScroll,
42
42
  onBodyScrollActivity,
43
- hoveredIdx,
44
- setHoveredIdx,
45
43
  headerScrollId,
46
44
  bodyScrollId,
47
45
  getItemKey,
48
46
  isSelected,
49
47
  onSelect,
50
48
  onActivate,
49
+ onTableMouseDown,
51
50
  onRowMouseDown,
52
51
  onRowContextMenu,
53
52
  rowContextMenuSurface = false,
@@ -60,6 +59,9 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
60
59
  emptyStateHint,
61
60
  virtualize = true,
62
61
  overscan = 3,
62
+ columnGap = 1,
63
+ horizontalPadding = 1,
64
+ fillAvailableWidth = true,
63
65
  showHorizontalScrollbar = true,
64
66
  scrollToIndex,
65
67
  scrollToIndexAlign = "nearest",
@@ -105,17 +107,21 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
105
107
  ],
106
108
  );
107
109
  const { endIndex, startIndex, viewportHeight, visibleItems } = tableWindow;
108
- const tableWidth = useMemo(() => getTableWidth(columns), [columns]);
110
+ const tableWidth = useMemo(
111
+ () => getTableWidth(columns, columnGap, horizontalPadding),
112
+ [columnGap, columns, horizontalPadding],
113
+ );
109
114
  const {
110
115
  contentWidth: measuredContentWidth,
111
116
  viewportWidth: measuredViewportWidth,
112
117
  measureContentWidth,
113
118
  } = useMeasuredTableContentWidth(tableWidth, headerScrollRef, scrollRef);
114
119
  const horizontalScrollbarVisible = showHorizontalScrollbar
115
- && hasMeaningfulTableHorizontalOverflow(tableWidth, measuredViewportWidth);
120
+ && hasMeaningfulTableHorizontalOverflow(tableWidth, measuredViewportWidth, columnGap);
121
+ const contentWidth = fillAvailableWidth ? measuredContentWidth : tableWidth;
116
122
  const displayColumns = useMemo(
117
- () => expandTableColumns(columns, measuredContentWidth),
118
- [columns, measuredContentWidth],
123
+ () => expandTableColumns(columns, contentWidth, columnGap, horizontalPadding),
124
+ [columnGap, columns, contentWidth, horizontalPadding],
119
125
  );
120
126
  const handleRowMouseDown =
121
127
  useDoubleClickActivation<DataTableRowPointerTarget<T>>({
@@ -264,8 +270,8 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
264
270
  <Box
265
271
  flexDirection="row"
266
272
  height={1}
267
- {...tableContentWidthProps(measuredContentWidth)}
268
- paddingX={1}
273
+ {...tableContentWidthProps(contentWidth)}
274
+ paddingX={horizontalPadding}
269
275
  backgroundColor={colors.panel}
270
276
  >
271
277
  {displayColumns.map((column) => {
@@ -283,10 +289,11 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
283
289
  return (
284
290
  <Box
285
291
  key={column.id}
286
- width={column.width + 1}
292
+ width={column.width + columnGap}
287
293
  backgroundColor={column.headerBackgroundColor ?? colors.panel}
288
294
  onMouseDown={(event: any) => {
289
295
  focusPane();
296
+ onTableMouseDown?.(event);
290
297
  event.preventDefault();
291
298
  onHeaderClick(column.id);
292
299
  }}
@@ -315,6 +322,7 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
315
322
  focusable={false}
316
323
  onMouseDown={() => {
317
324
  focusPane();
325
+ onTableMouseDown?.({});
318
326
  }}
319
327
  onSizeChange={measureContentWidth}
320
328
  >
@@ -339,11 +347,12 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
339
347
  key={getItemKey(item, index)}
340
348
  flexDirection="row"
341
349
  height={1}
342
- {...tableContentWidthProps(measuredContentWidth)}
343
- paddingX={1}
350
+ {...tableContentWidthProps(contentWidth)}
351
+ paddingX={horizontalPadding}
344
352
  backgroundColor={sectionHeader.backgroundColor ?? colors.bg}
345
353
  onMouseDown={(event: any) => {
346
354
  focusPane();
355
+ onTableMouseDown?.(event);
347
356
  event.preventDefault();
348
357
  }}
349
358
  >
@@ -358,8 +367,7 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
358
367
  }
359
368
 
360
369
  const selected = isSelected(item, index);
361
- const hovered = hoveredIdx === index && !selected;
362
- const rowState = { selected, hovered };
370
+ const rowState = { selected };
363
371
  const rowBackgroundColor = getRowBackgroundColor?.(
364
372
  item,
365
373
  index,
@@ -367,24 +375,22 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
367
375
  );
368
376
  const rowBg = selected
369
377
  ? colors.selected
370
- : hovered
371
- ? hoverBg()
372
- : rowBackgroundColor ?? colors.bg;
378
+ : rowBackgroundColor ?? colors.bg;
379
+ const rowHoverBg = selected ? undefined : hoverBg();
373
380
 
374
381
  return (
375
382
  <Box
376
383
  key={getItemKey(item, index)}
377
384
  flexDirection="row"
378
385
  height={1}
379
- {...tableContentWidthProps(measuredContentWidth)}
380
- paddingX={1}
386
+ {...tableContentWidthProps(contentWidth)}
387
+ paddingX={horizontalPadding}
381
388
  backgroundColor={rowBg}
389
+ hoverBackgroundColor={rowHoverBg}
382
390
  data-gloom-context-menu-surface={rowContextMenuSurface ? "true" : undefined}
383
- onMouseMove={() => {
384
- if (hoveredIdx !== index) setHoveredIdx(index);
385
- }}
386
391
  onMouseDown={(event: any) => {
387
392
  focusPane();
393
+ onTableMouseDown?.(event);
388
394
  if (onRowMouseDown?.(item, index, event) === true) {
389
395
  return;
390
396
  }
@@ -404,10 +410,11 @@ export function OpenTuiDataTable<T, C extends DataTableColumn = DataTableColumn>
404
410
  return (
405
411
  <Box
406
412
  key={column.id}
407
- width={column.width + 1}
413
+ width={column.width + columnGap}
408
414
  backgroundColor={cell.backgroundColor ?? rowBg}
409
415
  onMouseDown={(event: any) => {
410
416
  focusPane();
417
+ onTableMouseDown?.(event);
411
418
  if (cell.onMouseDown) {
412
419
  cell.onMouseDown(event);
413
420
  return;