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
package/README.md CHANGED
@@ -60,6 +60,7 @@ Open command mode with `Ctrl+P`, then type a command. Press `` ` `` to open tick
60
60
  | `TOP` | Ranked market stories |
61
61
  | `MOST` | Market movers |
62
62
  | `PF` | Portfolios and watchlists |
63
+ | `KELLY AAPL` | Position sizing |
63
64
  | `HELP` | Full in-app shortcut list |
64
65
 
65
66
  ## What It Does
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Market research and portfolio tracker for the terminal",
5
5
  "module": "src/index.tsx",
6
6
  "type": "module",
@@ -63,6 +63,7 @@
63
63
  },
64
64
  "patchedDependencies": {
65
65
  "@opentui/core@0.3.2": "patches/@opentui%2Fcore@0.3.2.patch",
66
- "electrobun@1.18.1": "patches/electrobun@1.18.1.patch"
66
+ "electrobun@1.18.1": "patches/electrobun@1.18.1.patch",
67
+ "react-reconciler@0.33.0": "patches/react-reconciler@0.33.0.patch"
67
68
  }
68
69
  }
@@ -1,5 +1,5 @@
1
1
  diff --git a/index-218h9p3f.js b/index-218h9p3f.js
2
- index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346939bb068 100644
2
+ index b9445d2cca9477dfe06026da451b6d19268fcbdd..da790c31da185325405227329dad2ac12f5329e7 100644
3
3
  --- a/index-218h9p3f.js
4
4
  +++ b/index-218h9p3f.js
5
5
  @@ -6487,17 +6487,27 @@ class MouseParser {
@@ -210,7 +210,101 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
210
210
  }
211
211
  throw new Error(`opentui is not supported on the current platform: ${process.platform}-${process.arch}`);
212
212
  }
213
- @@ -22288,6 +22323,8 @@ class MouseEvent {
213
+ @@ -17996,5 +18031,7 @@ function isGapType(value) {
214
+ class BoxRenderable extends Renderable {
215
+ _backgroundColor;
216
+ + _hoverBackgroundColor;
217
+ + _isHovered = false;
218
+ _border;
219
+ _borderStyle;
220
+ _borderColor;
221
+ @@ -18023,6 +18060,7 @@ class BoxRenderable extends Renderable {
222
+ this._focusable = true;
223
+ }
224
+ this._backgroundColor = parseColor(options.backgroundColor || this._defaultOptions.backgroundColor);
225
+ + this._hoverBackgroundColor = options.hoverBackgroundColor === undefined ? undefined : parseColor(options.hoverBackgroundColor);
226
+ this._border = options.border ?? this._defaultOptions.border;
227
+ if (!options.border && (options.borderStyle || options.borderColor || options.focusedBorderColor || options.customBorderChars)) {
228
+ this._border = true;
229
+ @@ -18064,11 +18102,26 @@ class BoxRenderable extends Renderable {
230
+ }
231
+ set backgroundColor(value) {
232
+ const newColor = parseColor(value ?? this._defaultOptions.backgroundColor);
233
+ - if (this._backgroundColor !== newColor) {
234
+ + if (!this._backgroundColor.equals(newColor)) {
235
+ this._backgroundColor = newColor;
236
+ this.requestRender();
237
+ }
238
+ }
239
+ + get hoverBackgroundColor() {
240
+ + return this._hoverBackgroundColor;
241
+ + }
242
+ + set hoverBackgroundColor(value) {
243
+ + const newColor = value === undefined || value === null ? undefined : parseColor(value);
244
+ + const changed = newColor === undefined
245
+ + ? this._hoverBackgroundColor !== undefined
246
+ + : !this._hoverBackgroundColor?.equals(newColor);
247
+ + if (changed) {
248
+ + this._hoverBackgroundColor = newColor;
249
+ + if (this._isHovered) {
250
+ + this.requestRender();
251
+ + }
252
+ + }
253
+ + }
254
+ get border() {
255
+ return this._border;
256
+ }
257
+ @@ -18152,9 +18205,39 @@ class BoxRenderable extends Renderable {
258
+ this.requestRender();
259
+ }
260
+ }
261
+ + onMouseEvent(event) {
262
+ + if (!this._hoverBackgroundColor) {
263
+ + if (this._isHovered) {
264
+ + this._isHovered = false;
265
+ + this.requestRender();
266
+ + }
267
+ + return;
268
+ + }
269
+ + if (event.type === "over" && !this._isHovered) {
270
+ + this._isHovered = true;
271
+ + this.requestRender();
272
+ + } else if (event.type === "out" && this._isHovered) {
273
+ + this._isHovered = false;
274
+ + this.requestRender();
275
+ + }
276
+ + }
277
+ + getActiveHoverBackgroundColor() {
278
+ + if (this._isHovered && this._hoverBackgroundColor) {
279
+ + return this._hoverBackgroundColor;
280
+ + }
281
+ + let parent = this.parent;
282
+ + while (parent) {
283
+ + if (parent instanceof BoxRenderable && parent._isHovered && parent._hoverBackgroundColor) {
284
+ + return parent._hoverBackgroundColor;
285
+ + }
286
+ + parent = parent.parent;
287
+ + }
288
+ + return undefined;
289
+ + }
290
+ renderSelf(buffer) {
291
+ const hasBorder = this.borderSides.top || this.borderSides.right || this.borderSides.bottom || this.borderSides.left;
292
+ - const hasVisibleFill = this.shouldFill && this._backgroundColor.a > 0;
293
+ + const backgroundColor = this.getActiveHoverBackgroundColor() ?? this._backgroundColor;
294
+ + const hasVisibleFill = this.shouldFill && backgroundColor.a > 0;
295
+ if (!hasBorder && !hasVisibleFill) {
296
+ return;
297
+ }
298
+ @@ -18171,7 +18254,7 @@ class BoxRenderable extends Renderable {
299
+ customBorderChars: this._customBorderChars,
300
+ border: this._border,
301
+ borderColor: currentBorderColor,
302
+ - backgroundColor: this._backgroundColor,
303
+ + backgroundColor,
304
+ shouldFill: this.shouldFill,
305
+ title: this._title,
306
+ titleAlignment: this._titleAlignment,
307
+ @@ -22288,6 +22371,8 @@ class MouseEvent {
214
308
  button;
215
309
  x;
216
310
  y;
@@ -219,7 +313,7 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
219
313
  source;
220
314
  modifiers;
221
315
  scroll;
222
- @@ -22307,6 +22344,8 @@ class MouseEvent {
316
+ @@ -22307,6 +22392,8 @@ class MouseEvent {
223
317
  this.button = attributes.button;
224
318
  this.x = attributes.x;
225
319
  this.y = attributes.y;
@@ -228,16 +322,18 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
228
322
  this.modifiers = attributes.modifiers;
229
323
  this.scroll = attributes.scroll;
230
324
  this.source = attributes.source;
231
- @@ -22457,6 +22496,8 @@ class CliRenderer extends EventEmitter9 {
325
+ @@ -22457,6 +22544,10 @@ class CliRenderer extends EventEmitter9 {
232
326
  resizeDebounceDelay = 100;
233
327
  enableMouseMovement = false;
234
328
  _useMouse = true;
235
329
  + _mouseProtocolSignature = "disabled";
236
330
  + _pixelMouseEnabled = false;
331
+ + _pendingMouseMoveEvent = null;
332
+ + _pendingMouseMoveTimer = null;
237
333
  autoFocus = true;
238
334
  _screenMode = "alternate-screen";
239
335
  _footerHeight = DEFAULT_FOOTER_HEIGHT;
240
- @@ -22491,7 +22532,7 @@ class CliRenderer extends EventEmitter9 {
336
+ @@ -22491,7 +22582,7 @@ class CliRenderer extends EventEmitter9 {
241
337
  this.handleResize(width, height);
242
338
  }).bind(this);
243
339
  _capabilities = null;
@@ -246,7 +342,7 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
246
342
  _hasPointer = false;
247
343
  _lastPointerModifiers = {
248
344
  shift: false,
249
- @@ -22738,7 +22779,13 @@ Captured external output:
345
+ @@ -22738,7 +22829,13 @@ Captured external output:
250
346
  privateCapabilityRepliesActive: false,
251
347
  pixelResolutionQueryActive: false,
252
348
  explicitWidthCprActive: false,
@@ -261,7 +357,7 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
261
357
  },
262
358
  clock: this.clock
263
359
  });
264
- @@ -24021,12 +24068,14 @@ Captured external output:
360
+ @@ -24021,12 +24118,14 @@ Captured external output:
265
361
  enableMouse() {
266
362
  this._useMouse = true;
267
363
  this.lib.enableMouse(this.rendererPtr, this.enableMouseMovement);
@@ -276,7 +372,7 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
276
372
  }
277
373
  enableKittyKeyboard(flags = 3) {
278
374
  this.lib.enableKittyKeyboard(this.rendererPtr, flags);
279
- @@ -24036,6 +24085,52 @@ Captured external output:
375
+ @@ -24036,6 +24135,52 @@ Captured external output:
280
376
  this.lib.disableKittyKeyboard(this.rendererPtr);
281
377
  this.updateStdinParserProtocolContext({ kittyKeyboardEnabled: false }, true);
282
378
  }
@@ -329,7 +425,7 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
329
425
  set useThread(useThread) {
330
426
  this._useThread = useThread;
331
427
  this.lib.setUseThread(this.rendererPtr, useThread);
332
- @@ -24141,6 +24236,7 @@ Captured external output:
428
+ @@ -24141,6 +24286,7 @@ Captured external output:
333
429
  }
334
430
  this.lib.processCapabilityResponse(this.rendererPtr, sequence);
335
431
  this._capabilities = this.lib.getTerminalCapabilities(this.rendererPtr);
@@ -337,7 +433,7 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
337
433
  if (this._capabilities?.terminal?.from_xtversion) {
338
434
  this.resolveXtVersionWaiters();
339
435
  }
340
- @@ -24170,6 +24266,7 @@ Captured external output:
436
+ @@ -24170,6 +24316,7 @@ Captured external output:
341
437
  if (sequence === "\x1B[I") {
342
438
  if (this.shouldRestoreModesOnNextFocus) {
343
439
  this.lib.restoreTerminalModes(this.rendererPtr);
@@ -345,7 +441,16 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
345
441
  this.shouldRestoreModesOnNextFocus = false;
346
442
  }
347
443
  if (this._terminalFocusState !== true) {
348
- @@ -24264,6 +24361,7 @@ Captured external output:
444
+ @@ -24218,7 +24365,7 @@ Captured external output:
445
+ this._keyHandler.processParsedKey(event.key);
446
+ return;
447
+ case "mouse":
448
+ - if (this._useMouse && this.processSingleMouseEvent(event.event)) {
449
+ + if (this._useMouse && this.processMouseEvent(event.event)) {
450
+ return;
451
+ }
452
+ this.dispatchSequenceHandlers(event.raw);
453
+ @@ -24264,6 +24411,7 @@ Captured external output:
349
454
  }
350
455
  this.waitingForPixelResolution = false;
351
456
  this.updateStdinParserProtocolContext({ pixelResolutionQueryActive: false }, true);
@@ -353,7 +458,54 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
353
458
  return true;
354
459
  }
355
460
  return false;
356
- @@ -24298,9 +24396,15 @@ Captured external output:
461
+ @@ -24292,15 +24440,62 @@ Captured external output:
462
+ }
463
+ return event;
464
+ }
465
+ + cloneMouseEventAttributes(mouseEvent) {
466
+ + return {
467
+ + ...mouseEvent,
468
+ + modifiers: { ...mouseEvent.modifiers }
469
+ + };
470
+ + }
471
+ + processMouseEvent(mouseEvent) {
472
+ + if (mouseEvent.type !== "move") {
473
+ + this.flushPendingMouseMove();
474
+ + return this.processSingleMouseEvent(mouseEvent);
475
+ + }
476
+ + if (this._pendingMouseMoveTimer === null) {
477
+ + const handled = this.processSingleMouseEvent(mouseEvent);
478
+ + this.schedulePendingMouseMove();
479
+ + return handled;
480
+ + }
481
+ + this._pendingMouseMoveEvent = this.cloneMouseEventAttributes(mouseEvent);
482
+ + return true;
483
+ + }
484
+ + schedulePendingMouseMove() {
485
+ + if (this._pendingMouseMoveTimer !== null || this._isDestroyed) {
486
+ + return;
487
+ + }
488
+ + const delay = Math.max(100, Math.floor(this.targetFrameTime));
489
+ + this._pendingMouseMoveTimer = this.clock.setTimeout(() => {
490
+ + this._pendingMouseMoveTimer = null;
491
+ + this.flushPendingMouseMove();
492
+ + }, delay);
493
+ + }
494
+ + flushPendingMouseMove() {
495
+ + if (this._pendingMouseMoveTimer !== null) {
496
+ + this.clock.clearTimeout(this._pendingMouseMoveTimer);
497
+ + this._pendingMouseMoveTimer = null;
498
+ + }
499
+ + const pending = this._pendingMouseMoveEvent;
500
+ + this._pendingMouseMoveEvent = null;
501
+ + if (!pending || this._isDestroyed) {
502
+ + return false;
503
+ + }
504
+ + return this.processSingleMouseEvent(pending);
505
+ + }
506
+ processSingleMouseEvent(mouseEvent) {
507
+ if (this._splitHeight > 0) {
508
+ if (mouseEvent.y < this.renderOffset) {
357
509
  return false;
358
510
  }
359
511
  mouseEvent.y -= this.renderOffset;
@@ -369,31 +521,43 @@ index b9445d2cca9477dfe06026da451b6d19268fcbdd..3efd05fa41eb713ac6e539764b15c346
369
521
  this._hasPointer = true;
370
522
  this._lastPointerModifiers = mouseEvent.modifiers;
371
523
  if (this._console.visible) {
372
- @@ -24441,6 +24545,8 @@ Captured external output:
373
- button: 0,
374
- x: this._latestPointer.x,
375
- y: this._latestPointer.y,
524
+ @@ -24437,6 +24632,8 @@ Captured external output:
525
+ return;
526
+ }
527
+ const baseEvent = {
376
528
  + pixelX: this._latestPointer.pixelX,
377
529
  + pixelY: this._latestPointer.pixelY,
378
- modifiers: this._lastPointerModifiers
379
- };
380
- if (lastOver && !lastOver.isDestroyed) {
381
- @@ -24531,6 +24637,7 @@ Captured external output:
530
+ type: "move",
531
+ button: 0,
532
+ x: this._latestPointer.x,
533
+ @@ -24527,6 +24724,7 @@ Captured external output:
534
+ const pendingSplitFooterTransition = this.pendingSplitFooterTransition;
535
+ const previousGeometry = calculateRenderGeometry(this._screenMode, this._terminalWidth, this._terminalHeight, this._footerHeight);
536
+ const prevWidth = this._terminalWidth;
537
+ + this.syncPixelMouseMode();
538
+ const previousTerminalHeight = this._terminalHeight;
382
539
  const visiblePreviousSplitHeight = pendingSplitFooterTransition?.sourceHeight ?? previousGeometry.effectiveFooterHeight;
383
540
  this._terminalWidth = width;
384
- this._terminalHeight = height;
385
- + this.syncPixelMouseMode();
386
- this.queryPixelResolution();
387
- this.setCapturedRenderable(undefined);
388
- this.stdinParser?.resetMouseState();
389
- @@ -24848,6 +24955,7 @@ Captured external output:
541
+ @@ -24832,6 +25030,11 @@ Captured external output:
542
+ this.clock.clearTimeout(this.resizeTimeoutId);
543
+ this.resizeTimeoutId = null;
544
+ }
545
+ + if (this._pendingMouseMoveTimer !== null) {
546
+ + this.clock.clearTimeout(this._pendingMouseMoveTimer);
547
+ + this._pendingMouseMoveTimer = null;
548
+ + }
549
+ + this._pendingMouseMoveEvent = null;
550
+ if (this.capabilityTimeoutId !== null) {
551
+ this.clock.clearTimeout(this.capabilityTimeoutId);
552
+ this.capabilityTimeoutId = null;
553
+ @@ -24847,6 +25050,7 @@ Captured external output:
554
+ this.renderTimeout = null;
555
+ }
390
556
  this.themeModeState.cancelRefresh();
557
+ + this.disablePixelMouseMode();
391
558
  this._isRunning = false;
392
559
  this.waitingForPixelResolution = false;
393
- + this.disablePixelMouseMode();
394
560
  this.updateStdinParserProtocolContext({
395
- privateCapabilityRepliesActive: false,
396
- pixelResolutionQueryActive: false,
397
561
  diff --git a/lib/parse.mouse.d.ts b/lib/parse.mouse.d.ts
398
562
  index c29f953fde0b78f09d2ec7a830e5a72452c1fe7e..04613b921bd9b4a9a7a1a4fbd0234d4012df67a1 100644
399
563
  --- a/lib/parse.mouse.d.ts
@@ -450,6 +614,44 @@ index 6b733d534d8e9b77c5c2773f93c7a22063c18d84..a5c318d591e9f3775e276e555c15052c
450
614
  }
451
615
  export interface StdinParserOptions {
452
616
  timeoutMs?: number;
617
+ diff --git a/renderables/Box.d.ts b/renderables/Box.d.ts
618
+ index 7ecb70d38ca41d1afdc078780985827e8f10e3a7..0845141e20ebd71a163dba67880d494a05b27d9a 100644
619
+ --- a/renderables/Box.d.ts
620
+ +++ b/renderables/Box.d.ts
621
+ @@ -5,6 +5,7 @@ import { type ColorInput, RGBA } from "../lib/RGBA.js";
622
+ import type { RenderContext } from "../types.js";
623
+ export interface BoxOptions<TRenderable extends Renderable = BoxRenderable> extends RenderableOptions<TRenderable> {
624
+ backgroundColor?: string | RGBA;
625
+ + hoverBackgroundColor?: string | RGBA;
626
+ borderStyle?: BorderStyle;
627
+ border?: boolean | BorderSides[];
628
+ borderColor?: string | RGBA;
629
+ @@ -22,6 +23,8 @@ export interface BoxOptions<TRenderable extends Renderable = BoxRenderable> exte
630
+ }
631
+ export declare class BoxRenderable extends Renderable {
632
+ protected _backgroundColor: RGBA;
633
+ + protected _hoverBackgroundColor?: RGBA;
634
+ + protected _isHovered: boolean;
635
+ protected _border: boolean | BorderSides[];
636
+ protected _borderStyle: BorderStyle;
637
+ protected _borderColor: RGBA;
638
+ @@ -50,6 +53,8 @@ export declare class BoxRenderable extends Renderable {
639
+ set customBorderChars(value: BorderCharacters | undefined);
640
+ get backgroundColor(): RGBA;
641
+ set backgroundColor(value: RGBA | string | undefined);
642
+ + get hoverBackgroundColor(): RGBA | undefined;
643
+ + set hoverBackgroundColor(value: RGBA | string | undefined);
644
+ get border(): boolean | BorderSides[];
645
+ set border(value: boolean | BorderSides[]);
646
+ get borderStyle(): BorderStyle;
647
+ @@ -66,6 +71,7 @@ export declare class BoxRenderable extends Renderable {
648
+ set bottomTitle(value: string | undefined);
649
+ get bottomTitleAlignment(): "left" | "center" | "right";
650
+ set bottomTitleAlignment(value: "left" | "center" | "right");
651
+ + protected getActiveHoverBackgroundColor(): RGBA | undefined;
652
+ protected renderSelf(buffer: OptimizedBuffer): void;
653
+ protected getScissorRect(): {
654
+ x: number;
453
655
  diff --git a/renderer.d.ts b/renderer.d.ts
454
656
  index 96189246f423809fc52d2ad182d57ed2e80ac098..7dd0baf1697041cf8ad556b115cd13ad2e18bc65 100644
455
657
  --- a/renderer.d.ts
@@ -0,0 +1,10 @@
1
+ diff --git a/cjs/react-reconciler.development.js b/cjs/react-reconciler.development.js
2
+ --- a/cjs/react-reconciler.development.js
3
+ +++ b/cjs/react-reconciler.development.js
4
+ @@ -17213,5 +17213,6 @@
5
+ hasOwnProperty = Object.prototype.hasOwnProperty,
6
+ supportsUserTiming =
7
+ + "undefined" !== typeof window &&
8
+ "undefined" !== typeof console &&
9
+ "function" === typeof console.timeStamp &&
10
+ "undefined" !== typeof performance &&
@@ -54,7 +54,11 @@ export function useDesktopApplicationMenuRuntime({
54
54
  break;
55
55
  case "layout-gridlock": {
56
56
  const { width, height } = pluginRegistry.getTermSizeFn();
57
- pluginRegistry.updateLayoutFn(gridlockAllPanes(stateRef.current.config.layout, { x: 0, y: 0, width, height }));
57
+ pluginRegistry.updateLayoutFn(gridlockAllPanes(
58
+ stateRef.current.config.layout,
59
+ { x: 0, y: 0, width, height },
60
+ pluginRegistry.panes,
61
+ ));
58
62
  notifyGridlockComplete(pluginRegistry.notify.bind(pluginRegistry), () => {
59
63
  dispatch({ type: "UNDO_LAYOUT" });
60
64
  });
@@ -164,7 +164,10 @@ export function useTickerRefreshRuntime({
164
164
  dispatch({ type: "SET_REFRESHING", symbol, refreshing: true });
165
165
  }
166
166
  try {
167
- const entries = await marketData.loadSnapshotsBatch(instrumentEntries.map((entry) => entry.instrument));
167
+ const entries = await marketData.loadSnapshotsBatch(
168
+ instrumentEntries.map((entry) => entry.instrument),
169
+ { forceRefresh: true },
170
+ );
168
171
  entries.forEach((entry, index) => {
169
172
  const ticker = instrumentEntries[index]?.ticker;
170
173
  const data = entry.data ?? entry.lastGoodData;
package/src/app.tsx CHANGED
@@ -60,7 +60,16 @@ interface AppInnerProps {
60
60
  function ThemedAppRoot({ children }: { children: ReactNode }) {
61
61
  const themeColors = useThemeColors();
62
62
  return (
63
- <Box flexDirection="column" flexGrow={1} backgroundColor={themeColors.bg}>
63
+ <Box
64
+ flexDirection="column"
65
+ flexGrow={1}
66
+ flexShrink={1}
67
+ flexBasis={0}
68
+ minWidth={0}
69
+ minHeight={0}
70
+ overflow="hidden"
71
+ backgroundColor={themeColors.bg}
72
+ >
64
73
  {children}
65
74
  </Box>
66
75
  );
@@ -124,7 +124,7 @@ export function loadPersistedBrokerAccountMap(
124
124
 
125
125
  for (const instance of brokerInstances) {
126
126
  const broker = brokers.get(instance.brokerType);
127
- if (!broker?.listAccounts) continue;
127
+ if (!broker?.listAccounts && !broker?.importPortfolioSnapshot) continue;
128
128
  const accounts = loadPersistedBrokerAccounts(resources, instance, broker);
129
129
  if (!accounts || accounts.length === 0) continue;
130
130
  accountMap[instance.id] = accounts;
@@ -2,6 +2,8 @@ import { describe, expect, test } from "bun:test";
2
2
  import { createDefaultConfig, type BrokerInstanceConfig } from "../types/config";
3
3
  import type { BrokerAdapter } from "../types/broker";
4
4
  import type { TickerRecord } from "../types/ticker";
5
+ import { AppPersistence } from "../data/app-persistence";
6
+ import { loadPersistedBrokerAccounts, persistBrokerAccounts } from "./account-cache";
5
7
  import {
6
8
  restoreBrokerPortfoliosFromTickerPositions,
7
9
  syncBrokerInstance,
@@ -164,6 +166,111 @@ describe("syncBrokerInstance", () => {
164
166
  ]);
165
167
  });
166
168
 
169
+ test("imports account and position data from one broker portfolio snapshot", async () => {
170
+ const instance = createBrokerInstance();
171
+ const config = {
172
+ ...createDefaultConfig("/tmp/gloomberb-sync-broker-snapshot"),
173
+ portfolios: [],
174
+ brokerInstances: [instance],
175
+ };
176
+ const tickerRepository = createTickerRepository();
177
+ const persistence = new AppPersistence(":memory:");
178
+ let listAccountsCalled = false;
179
+ let importPositionsCalled = false;
180
+ const broker: BrokerAdapter = {
181
+ ...createDemoBroker(),
182
+ listAccounts: async () => {
183
+ listAccountsCalled = true;
184
+ return [];
185
+ },
186
+ importPositions: async () => {
187
+ importPositionsCalled = true;
188
+ return [];
189
+ },
190
+ importPortfolioSnapshot: async () => ({
191
+ accounts: [{
192
+ accountId: "ACC-1",
193
+ name: "Snapshot Account",
194
+ currency: "USD",
195
+ netLiquidation: 125_000,
196
+ grossPositionValue: 175_000,
197
+ totalCashValue: -50_000,
198
+ }],
199
+ positions: [{
200
+ ticker: "AAPL",
201
+ exchange: "NASDAQ",
202
+ shares: 12,
203
+ avgCost: 180,
204
+ currency: "USD",
205
+ accountId: "ACC-1",
206
+ name: "Apple Inc.",
207
+ assetCategory: "STK",
208
+ }],
209
+ }),
210
+ };
211
+
212
+ try {
213
+ const result = await syncBrokerInstance({
214
+ config,
215
+ instanceId: "demo-broker",
216
+ brokers: new Map([["demo", broker]]),
217
+ tickerRepository: tickerRepository as any,
218
+ resources: persistence.resources,
219
+ });
220
+
221
+ expect(listAccountsCalled).toBe(false);
222
+ expect(importPositionsCalled).toBe(false);
223
+ expect(result.brokerAccounts[0]?.netLiquidation).toBe(125_000);
224
+ expect(result.positions).toHaveLength(1);
225
+ expect(loadPersistedBrokerAccounts(persistence.resources, instance, broker)).toEqual(result.brokerAccounts);
226
+ } finally {
227
+ persistence.close();
228
+ }
229
+ });
230
+
231
+ test("preserves the last account snapshot when a broker portfolio snapshot fails", async () => {
232
+ const instance = createBrokerInstance();
233
+ const config = {
234
+ ...createDefaultConfig("/tmp/gloomberb-sync-broker-snapshot-failure"),
235
+ portfolios: [],
236
+ brokerInstances: [instance],
237
+ };
238
+ const tickerRepository = createTickerRepository();
239
+ const persistence = new AppPersistence(":memory:");
240
+ const broker: BrokerAdapter = {
241
+ ...createDemoBroker(),
242
+ importPortfolioSnapshot: async () => {
243
+ throw new Error("account snapshot unavailable");
244
+ },
245
+ };
246
+
247
+ try {
248
+ persistBrokerAccounts(persistence.resources, instance, broker, [{
249
+ accountId: "ACC-1",
250
+ name: "Stale Account",
251
+ currency: "USD",
252
+ netLiquidation: 99_000,
253
+ }]);
254
+
255
+ await expect(syncBrokerInstance({
256
+ config,
257
+ instanceId: "demo-broker",
258
+ brokers: new Map([["demo", broker]]),
259
+ tickerRepository: tickerRepository as any,
260
+ resources: persistence.resources,
261
+ })).rejects.toThrow("account snapshot unavailable");
262
+
263
+ expect(loadPersistedBrokerAccounts(persistence.resources, instance, broker)).toEqual([{
264
+ accountId: "ACC-1",
265
+ name: "Stale Account",
266
+ currency: "USD",
267
+ netLiquidation: 99_000,
268
+ }]);
269
+ } finally {
270
+ persistence.close();
271
+ }
272
+ });
273
+
167
274
  test("preserves broker portfolios across sequential profile syncs", async () => {
168
275
  const config = {
169
276
  ...createDefaultConfig("/tmp/gloomberb-sync-broker-instances"),
@@ -131,17 +131,30 @@ export async function syncBrokerInstance({
131
131
  const tickers = await loadTickerMap(tickerRepository, existingTickers);
132
132
 
133
133
  let brokerAccounts: BrokerAccount[] = [];
134
- if (broker.listAccounts) {
135
- try {
136
- brokerAccounts = await broker.listAccounts(instance);
137
- if (resources) {
138
- try {
139
- persistBrokerAccounts(resources, instance, broker, brokerAccounts);
140
- } catch {}
134
+ let positions: BrokerPosition[];
135
+ if (broker.importPortfolioSnapshot) {
136
+ const snapshot = await broker.importPortfolioSnapshot(instance);
137
+ brokerAccounts = snapshot.accounts;
138
+ positions = snapshot.positions;
139
+ if (resources) {
140
+ try {
141
+ persistBrokerAccounts(resources, instance, broker, brokerAccounts);
142
+ } catch {}
143
+ }
144
+ } else {
145
+ if (broker.listAccounts) {
146
+ try {
147
+ brokerAccounts = await broker.listAccounts(instance);
148
+ if (resources) {
149
+ try {
150
+ persistBrokerAccounts(resources, instance, broker, brokerAccounts);
151
+ } catch {}
152
+ }
153
+ } catch (error) {
154
+ throw error;
141
155
  }
142
- } catch {
143
- brokerAccounts = [];
144
156
  }
157
+ positions = await broker.importPositions(instance);
145
158
  }
146
159
 
147
160
  const accountMetadata = new Map(
@@ -154,7 +167,6 @@ export async function syncBrokerInstance({
154
167
  ]),
155
168
  );
156
169
 
157
- const positions = await broker.importPositions(instance);
158
170
  const syncedAt = Date.now();
159
171
 
160
172
  let nextConfig = config;
@@ -3,7 +3,7 @@ import { tmpdir } from "os";
3
3
  import { join } from "path";
4
4
  import { pathToFileURL } from "url";
5
5
  import type { AppConfig } from "../types/config";
6
- import type { TickerFinancials } from "../types/financials";
6
+ import type { OptionsChain, TickerFinancials } from "../types/financials";
7
7
  import type { TickerRecord } from "../types/ticker";
8
8
  import type { PaneRuntimeState } from "../core/state/app/state";
9
9
  import {
@@ -20,6 +20,7 @@ export interface DesktopPaneShotPayload {
20
20
  heightPx: number;
21
21
  tickers: TickerRecord[];
22
22
  financials: Array<[string, TickerFinancials]>;
23
+ optionsChains: Array<[string, OptionsChain]>;
23
24
  paneState: Record<string, PaneRuntimeState>;
24
25
  }
25
26
 
package/src/cli/entry.ts CHANGED
@@ -2,6 +2,7 @@ import { dispatchCli } from "./index";
2
2
  import { fail, inferCliErrorOptions, printCliError } from "./errors";
3
3
  import { loadExternalPlugins } from "../plugins/loader";
4
4
  import type { CliLaunchRequest } from "../types/plugin";
5
+ import { OPEN_TUI_NATIVE_SMOKE_COMMAND, smokeOpenTuiNative } from "./native-smoke";
5
6
 
6
7
  async function launchOpenTuiApp(options: {
7
8
  externalPlugins: Awaited<ReturnType<typeof loadExternalPlugins>>;
@@ -18,9 +19,15 @@ async function launchOpenTuiApp(options: {
18
19
  }
19
20
 
20
21
  export async function runCliEntrypoint(rawArgs = process.argv.slice(2)): Promise<void> {
21
- const externalPlugins = await loadExternalPlugins();
22
22
  const command = rawArgs[0];
23
23
 
24
+ if (command === OPEN_TUI_NATIVE_SMOKE_COMMAND) {
25
+ await smokeOpenTuiNative();
26
+ return;
27
+ }
28
+
29
+ const externalPlugins = await loadExternalPlugins();
30
+
24
31
  if (!command) {
25
32
  await launchOpenTuiApp({ externalPlugins });
26
33
  return;
@@ -0,0 +1,5 @@
1
+ export const OPEN_TUI_NATIVE_SMOKE_COMMAND = "__gloomberb-smoke-opentui-native";
2
+
3
+ export async function smokeOpenTuiNative(): Promise<void> {
4
+ await import("../renderers/opentui/native-smoke");
5
+ }