gloomberb 0.9.4 → 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 (56) hide show
  1. package/package.json +3 -2
  2. package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
  3. package/patches/react-reconciler@0.33.0.patch +10 -0
  4. package/src/cli/entry.ts +8 -1
  5. package/src/cli/native-smoke.ts +5 -0
  6. package/src/components/chart/native/bitmap-support.test.ts +27 -0
  7. package/src/components/chart/native/bitmap-support.ts +55 -0
  8. package/src/components/chart/native/kitty/index.test.ts +47 -0
  9. package/src/components/chart/native/surface/visibility.ts +8 -1
  10. package/src/components/chart/static/chart/bitmap.ts +16 -9
  11. package/src/components/command-bar/list/view.tsx +1 -1
  12. package/src/components/command-bar/theme-picker.tsx +1 -1
  13. package/src/components/data-table/view.tsx +0 -12
  14. package/src/components/layout/floating-pane.tsx +3 -3
  15. package/src/components/layout/pane/header.tsx +5 -0
  16. package/src/components/layout/pane/index.tsx +3 -3
  17. package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
  18. package/src/components/layout/shell/cursor-occlusion.ts +9 -1
  19. package/src/components/layout/shell/index.tsx +8 -1
  20. package/src/components/layout/shell/pane/layers.tsx +2 -2
  21. package/src/components/layout/status-bar.tsx +5 -5
  22. package/src/components/pane-settings-dialog/desktop.tsx +1 -1
  23. package/src/components/price-performance.tsx +1 -1
  24. package/src/components/price-sparkline/view.tsx +15 -14
  25. package/src/components/speedometer-gauge/terminal.tsx +18 -15
  26. package/src/components/table-view-shared.tsx +0 -10
  27. package/src/components/ticker/list-table-view.tsx +1 -7
  28. package/src/components/ticker/list-table.test.tsx +0 -4
  29. package/src/components/ui/data-table/opentui/index.tsx +4 -10
  30. package/src/components/ui/data-table/types.ts +6 -4
  31. package/src/components/ui/list-view.tsx +1 -1
  32. package/src/components/ui/tabs.tsx +1 -2
  33. package/src/components/ui/ui.test.tsx +0 -9
  34. package/src/plugins/builtin/account-management/form-components.tsx +2 -2
  35. package/src/plugins/builtin/alerts/pane.tsx +1 -1
  36. package/src/plugins/builtin/buildout/cells.tsx +1 -1
  37. package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
  38. package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
  39. package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
  40. package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
  41. package/src/plugins/builtin/chat/sidebar.tsx +1 -1
  42. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  43. package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
  44. package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
  45. package/src/plugins/builtin/debug/index.tsx +3 -4
  46. package/src/plugins/builtin/help/components.tsx +1 -1
  47. package/src/plugins/builtin/holders/treemap.tsx +1 -1
  48. package/src/plugins/builtin/options/table.ts +5 -5
  49. package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
  50. package/src/renderers/electrobun/view/data-table/index.tsx +0 -8
  51. package/src/renderers/electrobun/view/data-table/row.tsx +2 -12
  52. package/src/renderers/electrobun/view/host/box.tsx +6 -1
  53. package/src/renderers/electrobun/view/host/style.ts +1 -0
  54. package/src/renderers/electrobun/view/styles.css +7 -0
  55. package/src/renderers/opentui/native-smoke.ts +1 -0
  56. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.9.4",
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 &&
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
+ }
@@ -0,0 +1,27 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { NativeRendererHost } from "../../../ui";
3
+ import { shouldRenderNativeBitmap } from "./bitmap-support";
4
+
5
+ function rendererWithCapabilities(capabilities: unknown): NativeRendererHost {
6
+ return {
7
+ terminalWidth: 80,
8
+ terminalHeight: 24,
9
+ resolution: null,
10
+ capabilities,
11
+ on() {},
12
+ off() {},
13
+ requestRender() {},
14
+ registerLifecyclePass() {},
15
+ unregisterLifecyclePass() {},
16
+ };
17
+ }
18
+
19
+ describe("shouldRenderNativeBitmap", () => {
20
+ test("skips native bitmaps only when kitty graphics are known unsupported", () => {
21
+ expect(shouldRenderNativeBitmap(rendererWithCapabilities({ kitty_graphics: false }), true)).toBe(false);
22
+ expect(shouldRenderNativeBitmap(rendererWithCapabilities({ kitty_graphics: true }), true)).toBe(true);
23
+ expect(shouldRenderNativeBitmap(rendererWithCapabilities(null), true)).toBe(true);
24
+ expect(shouldRenderNativeBitmap(rendererWithCapabilities({}), true)).toBe(true);
25
+ expect(shouldRenderNativeBitmap(rendererWithCapabilities({ kitty_graphics: true }), false)).toBe(false);
26
+ });
27
+ });
@@ -0,0 +1,55 @@
1
+ import type { NativeRendererHost, PixelResolution } from "../../../ui";
2
+ import { computeBitmapSize } from "./chart-rasterizer";
3
+
4
+ interface NativeBitmapRendererCapabilities {
5
+ kitty_graphics?: boolean;
6
+ }
7
+
8
+ export interface NativeBitmapSize {
9
+ pixelWidth: number;
10
+ pixelHeight: number;
11
+ }
12
+
13
+ function hasKnownUnsupportedKittyGraphics(renderer: NativeRendererHost): boolean {
14
+ const capabilities = renderer.capabilities as NativeBitmapRendererCapabilities | null | undefined;
15
+ return capabilities?.kitty_graphics === false;
16
+ }
17
+
18
+ export function shouldRenderNativeBitmap(renderer: NativeRendererHost, nativeCharts: boolean): boolean {
19
+ return nativeCharts && !hasKnownUnsupportedKittyGraphics(renderer);
20
+ }
21
+
22
+ export function resolveNativeBitmapSize({
23
+ width,
24
+ height,
25
+ resolution,
26
+ terminalWidth,
27
+ terminalHeight,
28
+ cellWidthPx,
29
+ cellHeightPx,
30
+ pixelRatio,
31
+ }: {
32
+ width: number;
33
+ height: number;
34
+ resolution: PixelResolution | null;
35
+ terminalWidth: number;
36
+ terminalHeight: number;
37
+ cellWidthPx: number;
38
+ cellHeightPx: number;
39
+ pixelRatio: number;
40
+ }): NativeBitmapSize {
41
+ if (resolution && terminalWidth > 0 && terminalHeight > 0) {
42
+ return computeBitmapSize(
43
+ { x: 0, y: 0, width, height },
44
+ resolution,
45
+ terminalWidth,
46
+ terminalHeight,
47
+ );
48
+ }
49
+
50
+ const scale = Math.max(1, pixelRatio);
51
+ return {
52
+ pixelWidth: Math.max(1, Math.round(width * cellWidthPx * scale)),
53
+ pixelHeight: Math.max(1, Math.round(height * cellHeightPx * scale)),
54
+ };
55
+ }
@@ -143,6 +143,53 @@ describe("resolveNativeSurfaceVisibleRect", () => {
143
143
 
144
144
  expect(resolveNativeSurfaceVisibleRect(child, 100, 30)).toBeNull();
145
145
  });
146
+
147
+ test("stops at repeated ancestors in cyclic native renderable trees", () => {
148
+ const root: NativeSurfaceRenderableNode = {
149
+ x: 0,
150
+ y: 0,
151
+ width: 100,
152
+ height: 30,
153
+ parent: null,
154
+ };
155
+ const child: NativeSurfaceRenderableNode = {
156
+ x: 2,
157
+ y: 3,
158
+ width: 20,
159
+ height: 8,
160
+ parent: root,
161
+ };
162
+ root.parent = child;
163
+
164
+ expect(resolveNativeSurfaceVisibleRect(child, 100, 30)).toEqual({
165
+ x: 2,
166
+ y: 3,
167
+ width: 20,
168
+ height: 8,
169
+ });
170
+ });
171
+
172
+ test("fails closed when native renderable ancestors exceed a sane depth", () => {
173
+ let node: NativeSurfaceRenderableNode = {
174
+ x: 0,
175
+ y: 0,
176
+ width: 100,
177
+ height: 30,
178
+ parent: null,
179
+ };
180
+
181
+ for (let index = 0; index < 300; index += 1) {
182
+ node = {
183
+ x: 0,
184
+ y: 0,
185
+ width: 100,
186
+ height: 30,
187
+ parent: node,
188
+ };
189
+ }
190
+
191
+ expect(resolveNativeSurfaceVisibleRect(node, 100, 30)).toBeNull();
192
+ });
146
193
  });
147
194
 
148
195
  describe("renderNativeChartBase", () => {
@@ -9,6 +9,8 @@ export interface NativeSurfaceRenderableNode {
9
9
  parent: NativeSurfaceRenderableNode | null;
10
10
  }
11
11
 
12
+ const MAX_RENDERABLE_ANCESTOR_DEPTH = 256;
13
+
12
14
  export function getRenderableCellRect(
13
15
  renderable: Pick<NativeSurfaceRenderableNode, "x" | "y" | "width" | "height">,
14
16
  ): CellRect {
@@ -34,8 +36,13 @@ export function resolveNativeSurfaceVisibleRect(
34
36
  height: terminalHeight,
35
37
  };
36
38
  let current: NativeSurfaceRenderableNode | null = renderable;
39
+ const seen = new Set<NativeSurfaceRenderableNode>();
40
+ let depth = 0;
37
41
 
38
- while (current) {
42
+ while (current && !seen.has(current)) {
43
+ if (depth >= MAX_RENDERABLE_ANCESTOR_DEPTH) return null;
44
+ seen.add(current);
45
+ depth += 1;
39
46
  if (current.visible === false) return null;
40
47
 
41
48
  const nextVisible = intersectCellRects(visible, getRenderableCellRect(current));
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from "react";
2
2
  import { useNativeRenderer, useUiCapabilities } from "../../../../ui";
3
- import { computeBitmapSize } from "../../native/chart-rasterizer";
3
+ import { resolveNativeBitmapSize, shouldRenderNativeBitmap } from "../../native/bitmap-support";
4
4
 
5
5
  export interface StaticChartBitmapSize {
6
6
  pixelWidth: number;
@@ -16,20 +16,25 @@ export function useStaticChartBitmapSize(width: number, height: number): StaticC
16
16
  pixelRatio = 1,
17
17
  } = useUiCapabilities();
18
18
  const nativeRenderer = useNativeRenderer();
19
+ const rendererCapabilities = nativeRenderer.capabilities;
19
20
  const rendererResolution = nativeRenderer.resolution;
20
21
  const rendererTerminalWidth = nativeRenderer.terminalWidth;
21
22
  const rendererTerminalHeight = nativeRenderer.terminalHeight;
22
23
 
23
24
  return useMemo(() => {
24
- if (nativeCharts && rendererResolution && rendererTerminalWidth > 0 && rendererTerminalHeight > 0) {
25
- return computeBitmapSize(
26
- { x: 0, y: 0, width, height },
27
- rendererResolution,
28
- rendererTerminalWidth,
29
- rendererTerminalHeight,
30
- );
25
+ if (shouldRenderNativeBitmap(nativeRenderer, nativeCharts === true)) {
26
+ return resolveNativeBitmapSize({
27
+ width,
28
+ height,
29
+ resolution: rendererResolution,
30
+ terminalWidth: rendererTerminalWidth,
31
+ terminalHeight: rendererTerminalHeight,
32
+ cellWidthPx,
33
+ cellHeightPx,
34
+ pixelRatio,
35
+ });
31
36
  }
32
- if (!canvasCharts && !nativeCharts) return null;
37
+ if (!canvasCharts) return null;
33
38
  const scale = Math.max(1, pixelRatio);
34
39
  return {
35
40
  pixelWidth: Math.max(1, Math.round(width * cellWidthPx * scale)),
@@ -41,7 +46,9 @@ export function useStaticChartBitmapSize(width: number, height: number): StaticC
41
46
  cellWidthPx,
42
47
  height,
43
48
  nativeCharts,
49
+ nativeRenderer,
44
50
  pixelRatio,
51
+ rendererCapabilities,
45
52
  rendererResolution,
46
53
  rendererTerminalHeight,
47
54
  rendererTerminalWidth,
@@ -165,7 +165,7 @@ const CommandBarListItemRow = memo(function CommandBarListItemRow({
165
165
  : isHovered
166
166
  ? paletteHoverBg
167
167
  : (nativePaneChrome ? panelBg : paletteBg)}
168
- onMouseMove={() => onHoverIndex(globalIdx)}
168
+ onMouseOver={() => onHoverIndex(globalIdx)}
169
169
  onMouseOut={() => onHoverIndex(null)}
170
170
  {...(!nativePaneChrome ? { onMouseScroll: onListScroll } : {})}
171
171
  onMouseDown={(event: any) => onRowMouseDown(event, item, globalIdx)}
@@ -219,7 +219,7 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
219
219
  : hovered
220
220
  ? paletteHoverBg
221
221
  : (nativePaneChrome ? panelBg : paletteBg)}
222
- onMouseMove={() => setHoveredIndex(index)}
222
+ onMouseOver={() => setHoveredIndex(index)}
223
223
  onMouseOut={() => setHoveredIndex(null)}
224
224
  onMouseDown={(event: any) => {
225
225
  event.stopPropagation?.();