gloomberb 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/README.md +24 -2
  2. package/bin/gloomberb +1 -1
  3. package/package.json +1 -1
  4. package/src/app/runtime/desktop-menu.ts +5 -1
  5. package/src/app/runtime/ticker-refresh.ts +4 -1
  6. package/src/app.tsx +20 -8
  7. package/src/brokers/account-cache.ts +1 -1
  8. package/src/brokers/sync-broker-instance.test.ts +107 -0
  9. package/src/brokers/sync-broker-instance.ts +22 -10
  10. package/src/capabilities/registry.ts +1 -0
  11. package/src/capabilities/types.ts +23 -0
  12. package/src/cli/commands/api.ts +119 -0
  13. package/src/cli/commands/automation.ts +173 -0
  14. package/src/cli/commands/command-utils.ts +53 -0
  15. package/src/cli/commands/market.ts +527 -0
  16. package/src/cli/commands/overview.ts +222 -0
  17. package/src/cli/commands/search.ts +39 -0
  18. package/src/cli/commands/system.ts +553 -0
  19. package/src/cli/commands/ticker.ts +104 -12
  20. package/src/cli/context.ts +35 -2
  21. package/src/cli/desktop-pane-shot.ts +2 -1
  22. package/src/cli/entry.ts +51 -0
  23. package/src/cli/errors.ts +56 -5
  24. package/src/cli/index.ts +73 -11
  25. package/src/cli/options.test.ts +38 -0
  26. package/src/cli/options.ts +95 -0
  27. package/src/cli/pane-functions/index.ts +10 -1
  28. package/src/cli/pane-functions/screenshot.ts +13 -1
  29. package/src/cli/registry.test.ts +40 -0
  30. package/src/cli/registry.ts +46 -5
  31. package/src/cli/result.test.ts +82 -0
  32. package/src/cli/result.ts +149 -0
  33. package/src/cli/types.ts +11 -0
  34. package/src/cli.test.ts +11 -11
  35. package/src/components/chart/bar-chart-renderer.test.ts +108 -3
  36. package/src/components/chart/bar-chart-renderer.ts +226 -27
  37. package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
  38. package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
  39. package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
  40. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
  41. package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
  42. package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
  43. package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
  44. package/src/components/chart/core/pane-settings.test.ts +27 -0
  45. package/src/components/chart/core/pane-settings.ts +60 -1
  46. package/src/components/chart/core/pointer.ts +34 -6
  47. package/src/components/chart/price-axis-labels.tsx +3 -1
  48. package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
  49. package/src/components/chart/static/bar-chart-surface.tsx +151 -28
  50. package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
  51. package/src/components/chart/static/chart/surface.test.tsx +92 -0
  52. package/src/components/chart/static/chart/surface.tsx +167 -24
  53. package/src/components/chart/stock-chart/index.test.ts +25 -0
  54. package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
  55. package/src/components/chart/stock-chart/settings.ts +7 -3
  56. package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
  57. package/src/components/command-bar/layout-items/current-layout.ts +5 -1
  58. package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
  59. package/src/components/command-bar/surface/pane.test.tsx +76 -0
  60. package/src/components/data-table/view.tsx +13 -2
  61. package/src/components/feed-data-table/stack-view.tsx +9 -1
  62. package/src/components/index.ts +1 -1
  63. package/src/components/input-search-bar.test.tsx +103 -0
  64. package/src/components/input-search-bar.tsx +16 -0
  65. package/src/components/layout/detached-pane-shell.tsx +6 -3
  66. package/src/components/layout/floating-pane.tsx +3 -2
  67. package/src/components/layout/pane/content.tsx +20 -8
  68. package/src/components/layout/pane/footer/registration.tsx +6 -2
  69. package/src/components/layout/pane/frame.tsx +4 -4
  70. package/src/components/layout/pane/header.tsx +40 -4
  71. package/src/components/layout/pane/index.tsx +3 -1
  72. package/src/components/layout/pane/sizing.test.ts +43 -0
  73. package/src/components/layout/pane/sizing.ts +13 -4
  74. package/src/components/layout/shell/active-drag.ts +5 -1
  75. package/src/components/layout/shell/cursor-occlusion.ts +196 -0
  76. package/src/components/layout/shell/drag/runtime.ts +28 -3
  77. package/src/components/layout/shell/fullscreen.ts +10 -0
  78. package/src/components/layout/shell/index.test.tsx +487 -4
  79. package/src/components/layout/shell/index.tsx +165 -4
  80. package/src/components/layout/shell/layout-state.test.ts +33 -0
  81. package/src/components/layout/shell/layout-state.ts +4 -9
  82. package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
  83. package/src/components/layout/shell/pane/layers.tsx +34 -16
  84. package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
  85. package/src/components/layout/shell/shortcuts.ts +4 -0
  86. package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
  87. package/src/components/layout/shell/visible-layout.ts +17 -0
  88. package/src/components/layout/shell/window-mode/index.ts +11 -3
  89. package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
  90. package/src/components/layout/status-bar.test.tsx +111 -1
  91. package/src/components/layout/status-bar.tsx +37 -7
  92. package/src/components/layout/transient-layout.tsx +57 -0
  93. package/src/components/layout/window-edit/mode.test.ts +76 -5
  94. package/src/components/layout/window-edit/mode.ts +47 -2
  95. package/src/components/layout/window-edit/presentation.ts +2 -1
  96. package/src/components/onboarding/onboarding-steps.tsx +8 -28
  97. package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
  98. package/src/components/onboarding/onboarding-wizard.tsx +2 -4
  99. package/src/components/onboarding/portfolio-step/index.tsx +0 -12
  100. package/src/components/onboarding/portfolio-step/types.ts +1 -3
  101. package/src/components/onboarding/wizard-keyboard.ts +2 -5
  102. package/src/components/price-performance.tsx +9 -1
  103. package/src/components/table-view-shared.tsx +12 -4
  104. package/src/components/ui/data-table/opentui/index.tsx +25 -12
  105. package/src/components/ui/data-table/types.ts +4 -0
  106. package/src/components/ui/fields.tsx +10 -3
  107. package/src/components/ui/page-stack-view.tsx +6 -2
  108. package/src/components/ui/table-layout.test.ts +25 -0
  109. package/src/components/ui/table-layout.ts +22 -6
  110. package/src/components/ui/tabs.tsx +26 -10
  111. package/src/components/ui/ui.test.tsx +87 -0
  112. package/src/core/app-services.ts +7 -4
  113. package/src/core/state/app/layout-reducer.ts +2 -2
  114. package/src/data/config/store/index.ts +1 -2
  115. package/src/plugins/builtin/analytics/index.test.tsx +6 -2
  116. package/src/plugins/builtin/analytics/pane-model.ts +43 -8
  117. package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
  118. package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
  119. package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
  120. package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
  121. package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
  122. package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
  123. package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
  124. package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
  125. package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
  126. package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
  127. package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
  128. package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
  129. package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
  130. package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
  131. package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
  132. package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
  133. package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
  134. package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
  135. package/src/plugins/builtin/layout-manager/index.tsx +5 -1
  136. package/src/plugins/builtin/news/wire/read-state.ts +9 -32
  137. package/src/plugins/builtin/options/view.test.tsx +141 -4
  138. package/src/plugins/builtin/options/view.tsx +76 -15
  139. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
  140. package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
  141. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
  142. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
  143. package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
  144. package/src/plugins/builtin/portfolio-list/pane/index.tsx +3 -1
  145. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
  146. package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
  147. package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
  148. package/src/plugins/builtin/shared/read-state.ts +43 -0
  149. package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
  150. package/src/plugins/builtin/substack/article-detail.tsx +39 -15
  151. package/src/plugins/builtin/substack/article-stack.tsx +6 -2
  152. package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
  153. package/src/plugins/builtin/substack/pane.tsx +8 -3
  154. package/src/plugins/builtin/substack/read-state.ts +55 -0
  155. package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
  156. package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
  157. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
  158. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
  159. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
  160. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
  161. package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
  162. package/src/plugins/catalog-ui.ts +2 -0
  163. package/src/plugins/ibkr/broker-adapter.ts +19 -0
  164. package/src/plugins/ibkr/config.test.ts +31 -2
  165. package/src/plugins/ibkr/config.ts +9 -2
  166. package/src/plugins/ibkr/flex/index.test.ts +18 -9
  167. package/src/plugins/ibkr/flex/index.ts +30 -0
  168. package/src/plugins/ibkr/flex/statement-client.ts +68 -14
  169. package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
  170. package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
  171. package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
  172. package/src/plugins/pane-manager/docking.ts +9 -2
  173. package/src/plugins/pane-manager/gridlock.ts +13 -5
  174. package/src/plugins/pane-manager/layout-state.ts +25 -0
  175. package/src/plugins/pane-manager/queries.ts +8 -2
  176. package/src/plugins/pane-manager.test.ts +33 -0
  177. package/src/plugins/pane-manager.ts +6 -1
  178. package/src/plugins/prediction-markets/chart.tsx +10 -4
  179. package/src/plugins/prediction-markets/controller/index.ts +2 -0
  180. package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
  181. package/src/plugins/prediction-markets/detail/book.tsx +141 -96
  182. package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
  183. package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
  184. package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
  185. package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
  186. package/src/plugins/prediction-markets/detail.test.tsx +50 -0
  187. package/src/plugins/prediction-markets/pane.test.tsx +75 -0
  188. package/src/plugins/prediction-markets/pane.tsx +71 -23
  189. package/src/plugins/prediction-markets/test-helpers.tsx +42 -23
  190. package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
  191. package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
  192. package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
  193. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  194. package/src/renderers/electrobun/bun/index.ts +13 -0
  195. package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
  196. package/src/renderers/electrobun/bun/window/focus.ts +5 -0
  197. package/src/renderers/electrobun/view/app-services.ts +6 -4
  198. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
  199. package/src/renderers/electrobun/view/data-table/index.tsx +19 -4
  200. package/src/renderers/electrobun/view/data-table/row.tsx +30 -6
  201. package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
  202. package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
  203. package/src/renderers/electrobun/view/host/input.tsx +115 -18
  204. package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
  205. package/src/renderers/electrobun/view/host/text.test.ts +22 -0
  206. package/src/renderers/electrobun/view/host/text.tsx +24 -20
  207. package/src/renderers/electrobun/view/styles.css +11 -0
  208. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  209. package/src/renderers/opentui/host.tsx +17 -1
  210. package/src/renderers/opentui/start.tsx +13 -5
  211. package/src/renderers/opentui/ui-host.tsx +57 -25
  212. package/src/sources/provider-router/batches.ts +16 -2
  213. package/src/sources/provider-router/financial-routes.ts +5 -0
  214. package/src/sources/provider-router/financials.ts +11 -23
  215. package/src/sources/provider-router/index.test.ts +189 -0
  216. package/src/sources/provider-router/primary.ts +3 -2
  217. package/src/sources/sec-edgar.test.ts +83 -0
  218. package/src/sources/sec-edgar.ts +231 -0
  219. package/src/sources/yahoo-finance.test.ts +51 -0
  220. package/src/sources/yahoo-finance.ts +49 -1
  221. package/src/state/app/context/index.test.tsx +5 -8
  222. package/src/state/app/input-capture.ts +12 -0
  223. package/src/types/broker.ts +1 -0
  224. package/src/types/config.ts +10 -0
  225. package/src/types/plugin.ts +28 -0
  226. package/src/types/trading.ts +2 -0
  227. package/src/ui/host.tsx +16 -0
  228. package/src/ui/index.tsx +2 -0
  229. package/src/utils/cli-output.ts +6 -0
  230. package/src/utils/financial-statements.ts +23 -0
  231. package/src/utils/search-focus-navigation.ts +24 -0
  232. package/src/version.ts +1 -1
  233. package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
@@ -0,0 +1,43 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { resolvePaneBodyFrame, shouldReservePaneFooter } from "./sizing";
3
+
4
+ describe("pane sizing", () => {
5
+ test("lets native pane chrome lay out footer bars in normal flex flow", () => {
6
+ expect(shouldReservePaneFooter(true, true)).toBe(false);
7
+
8
+ const bodyFrame = resolvePaneBodyFrame({
9
+ width: 80,
10
+ height: 30,
11
+ nativePaneChrome: true,
12
+ footerVisible: true,
13
+ reserveFooter: false,
14
+ });
15
+
16
+ expect(bodyFrame.height).toBe(28);
17
+ expect(bodyFrame.layoutProps).toEqual({
18
+ flexGrow: 1,
19
+ flexShrink: 1,
20
+ flexBasis: 0,
21
+ minWidth: 0,
22
+ minHeight: 0,
23
+ });
24
+ });
25
+
26
+ test("keeps terminal pane footer rows reserved for border rendering", () => {
27
+ expect(shouldReservePaneFooter(false, false)).toBe(true);
28
+
29
+ const bodyFrame = resolvePaneBodyFrame({
30
+ width: 80,
31
+ height: 30,
32
+ nativePaneChrome: false,
33
+ reserveFooter: true,
34
+ });
35
+
36
+ expect(bodyFrame.height).toBe(28);
37
+ expect(bodyFrame.layoutProps).toEqual({
38
+ height: 28,
39
+ flexGrow: 0,
40
+ flexBasis: undefined,
41
+ });
42
+ });
43
+ });
@@ -4,28 +4,35 @@ const PANE_FOOTER_ROWS = 1;
4
4
 
5
5
  const NATIVE_PANE_BODY_LAYOUT_PROPS = {
6
6
  flexGrow: 1,
7
+ flexShrink: 1,
7
8
  flexBasis: 0,
9
+ minWidth: 0,
8
10
  minHeight: 0,
9
11
  } as const;
10
12
 
11
- export function shouldReservePaneFooter(nativePaneChrome: boolean | undefined, showFooter: boolean): boolean {
12
- return !nativePaneChrome || showFooter;
13
+ export function shouldReservePaneFooter(nativePaneChrome: boolean | undefined, _showFooter: boolean): boolean {
14
+ return !nativePaneChrome;
13
15
  }
14
16
 
15
17
  function resolvePaneBodyHeight({
16
18
  height,
17
19
  nativePaneChrome,
20
+ footerVisible,
18
21
  reserveFooter = true,
19
22
  headerRows = PANE_HEADER_ROWS,
20
23
  }: {
21
24
  height: number;
22
25
  nativePaneChrome?: boolean;
26
+ footerVisible?: boolean;
23
27
  reserveFooter?: boolean;
24
28
  headerRows?: number;
25
29
  }): number {
26
30
  const finiteHeight = Number.isFinite(height) ? height : 1;
27
31
  const normalizedHeight = nativePaneChrome ? finiteHeight : Math.max(1, Math.floor(finiteHeight));
28
- return Math.max(1, normalizedHeight - headerRows - (reserveFooter ? PANE_FOOTER_ROWS : 0));
32
+ const footerRows = nativePaneChrome
33
+ ? footerVisible ? PANE_FOOTER_ROWS : 0
34
+ : reserveFooter ? PANE_FOOTER_ROWS : 0;
35
+ return Math.max(1, normalizedHeight - headerRows - footerRows);
29
36
  }
30
37
 
31
38
  function getPaneBodyLayoutProps(nativePaneChrome: boolean | undefined, bodyHeight: number | undefined) {
@@ -41,17 +48,19 @@ export function resolvePaneBodyFrame({
41
48
  width,
42
49
  height,
43
50
  nativePaneChrome,
51
+ footerVisible,
44
52
  reserveFooter = true,
45
53
  headerRows = PANE_HEADER_ROWS,
46
54
  }: {
47
55
  width?: number;
48
56
  height?: number;
49
57
  nativePaneChrome?: boolean;
58
+ footerVisible?: boolean;
50
59
  reserveFooter?: boolean;
51
60
  headerRows?: number;
52
61
  }) {
53
62
  const bodyHeight = typeof height === "number"
54
- ? resolvePaneBodyHeight({ height, nativePaneChrome, reserveFooter, headerRows })
63
+ ? resolvePaneBodyHeight({ height, nativePaneChrome, footerVisible, reserveFooter, headerRows })
55
64
  : undefined;
56
65
  return {
57
66
  width: typeof width === "number" ? resolvePaneBodyWidth(width, nativePaneChrome) : undefined,
@@ -4,6 +4,7 @@ import {
4
4
  getRememberedFloatingRect,
5
5
  resizeSplitAtPath,
6
6
  simulateDrop,
7
+ type DockGeometryOptions,
7
8
  type DockLeafLayout,
8
9
  type DropTarget,
9
10
  type LayoutBounds,
@@ -32,6 +33,7 @@ interface UseShellActiveDragOptions {
32
33
  bounds: LayoutBounds;
33
34
  contentHeight: number;
34
35
  dispatch: Dispatch<AppAction>;
36
+ dockGeometryOptions: DockGeometryOptions;
35
37
  dockLeafLayouts: DockLeafLayout[];
36
38
  dragRuntime: ShellDragRuntimeState;
37
39
  focusPane: (paneId: string) => void;
@@ -51,6 +53,7 @@ export function useShellActiveDrag({
51
53
  bounds,
52
54
  contentHeight,
53
55
  dispatch,
56
+ dockGeometryOptions,
54
57
  dockLeafLayouts,
55
58
  dragRuntime,
56
59
  focusPane,
@@ -117,7 +120,7 @@ export function useShellActiveDrag({
117
120
  const hoveredCell = hoveredOverlay.cells.find((cell) => pointInRect(cell.rect, hitX, hitShellY));
118
121
  if (hoveredCell) {
119
122
  const target: DropTarget = { kind: "leaf", targetId: hoveredOverlay.targetId, position: hoveredCell.position };
120
- const simulation = simulateDrop(baseLayout, drag.paneId, target, bounds);
123
+ const simulation = simulateDrop(baseLayout, drag.paneId, target, bounds, dockGeometryOptions);
121
124
  if (simulation.previewRect) {
122
125
  updateDockPreview({ kind: "dock", target, rect: simulation.previewRect });
123
126
  } else {
@@ -199,6 +202,7 @@ export function useShellActiveDrag({
199
202
  bounds,
200
203
  contentHeight,
201
204
  dispatch,
205
+ dockGeometryOptions,
202
206
  dividerPreviewRef,
203
207
  dockLeafLayouts,
204
208
  dockPreviewRef,
@@ -0,0 +1,196 @@
1
+ import { useEffect, useRef, type RefObject } from "react";
2
+ import { useNativeRenderer, type NativeCursorState, type NativePostProcessFn } from "../../../ui";
3
+ import type { FloatingRect, LayoutBounds, ResolvedPane } from "../../../plugins/pane-manager";
4
+ import { constrainFloatingRectToBounds } from "./drag";
5
+
6
+ interface VisibleFloatingPane {
7
+ pane: ResolvedPane;
8
+ rect: FloatingRect;
9
+ }
10
+
11
+ export interface ShellCursorOcclusionRect extends LayoutBounds {
12
+ paneId: string;
13
+ zIndex: number;
14
+ }
15
+
16
+ interface ResolveShellCursorOcclusionRectsOptions {
17
+ contentHeight: number;
18
+ dragFloatingRect: { paneId: string; rect: FloatingRect } | null;
19
+ nativePaneChrome: boolean;
20
+ overlayOpen: boolean;
21
+ transientFocusActive: boolean;
22
+ visibleFloatingPanes: readonly VisibleFloatingPane[];
23
+ width: number;
24
+ }
25
+
26
+ export function resolveShellCursorOcclusionRects({
27
+ contentHeight,
28
+ dragFloatingRect,
29
+ nativePaneChrome,
30
+ overlayOpen,
31
+ transientFocusActive,
32
+ visibleFloatingPanes,
33
+ width,
34
+ }: ResolveShellCursorOcclusionRectsOptions): ShellCursorOcclusionRect[] {
35
+ if (nativePaneChrome || overlayOpen || transientFocusActive || visibleFloatingPanes.length === 0) {
36
+ return [];
37
+ }
38
+
39
+ return visibleFloatingPanes.map(({ pane, rect }) => {
40
+ const paneId = pane.instance.instanceId;
41
+ const visibleRect = dragFloatingRect?.paneId === paneId
42
+ ? constrainFloatingRectToBounds(dragFloatingRect.rect, width, contentHeight)
43
+ : rect;
44
+ return {
45
+ paneId,
46
+ zIndex: pane.floating?.zIndex ?? 50,
47
+ x: Math.floor(visibleRect.x),
48
+ y: Math.floor(visibleRect.y),
49
+ width: Math.max(0, Math.ceil(visibleRect.width)),
50
+ height: Math.max(0, Math.ceil(visibleRect.height)),
51
+ };
52
+ });
53
+ }
54
+
55
+ function readRenderableBounds(renderable: unknown): LayoutBounds | null {
56
+ if (!renderable || typeof renderable !== "object") return null;
57
+ const record = renderable as Record<string, unknown>;
58
+ const getRect = record.getBoundingClientRect;
59
+ if (typeof getRect === "function") {
60
+ const rect = getRect.call(renderable) as Partial<LayoutBounds> | null | undefined;
61
+ if (
62
+ rect
63
+ && typeof rect.x === "number"
64
+ && typeof rect.y === "number"
65
+ && typeof rect.width === "number"
66
+ && typeof rect.height === "number"
67
+ ) {
68
+ return rect as LayoutBounds;
69
+ }
70
+ }
71
+
72
+ const absoluteBounds = record.absoluteBounds as Partial<LayoutBounds> | null | undefined;
73
+ if (
74
+ absoluteBounds
75
+ && typeof absoluteBounds.x === "number"
76
+ && typeof absoluteBounds.y === "number"
77
+ && typeof absoluteBounds.width === "number"
78
+ && typeof absoluteBounds.height === "number"
79
+ ) {
80
+ return absoluteBounds as LayoutBounds;
81
+ }
82
+
83
+ const x = typeof record.screenX === "number"
84
+ ? record.screenX
85
+ : typeof record.absoluteX === "number"
86
+ ? record.absoluteX
87
+ : typeof record.x === "number"
88
+ ? record.x
89
+ : null;
90
+ const y = typeof record.screenY === "number"
91
+ ? record.screenY
92
+ : typeof record.absoluteY === "number"
93
+ ? record.absoluteY
94
+ : typeof record.y === "number"
95
+ ? record.y
96
+ : null;
97
+ const width = typeof record.width === "number" ? record.width : null;
98
+ const height = typeof record.height === "number" ? record.height : null;
99
+ if (x == null || y == null || width == null || height == null) return null;
100
+ return { x, y, width, height };
101
+ }
102
+
103
+ function readRenderablePaneId(renderable: unknown): string | null {
104
+ let current = renderable;
105
+ while (current && typeof current === "object") {
106
+ const record = current as Record<string, unknown>;
107
+ const paneId = record["data-gloom-pane-id"];
108
+ if (typeof paneId === "string") return paneId;
109
+ current = record.parent;
110
+ }
111
+ return null;
112
+ }
113
+
114
+ function rectContainsCursor(rect: LayoutBounds, cursor: NativeCursorState): boolean {
115
+ return cursor.x >= rect.x
116
+ && cursor.x < rect.x + rect.width
117
+ && cursor.y >= rect.y
118
+ && cursor.y < rect.y + rect.height;
119
+ }
120
+
121
+ function toTerminalRect(shellBounds: LayoutBounds, rect: ShellCursorOcclusionRect): ShellCursorOcclusionRect {
122
+ return {
123
+ ...rect,
124
+ x: Math.floor(shellBounds.x + rect.x + 1),
125
+ y: Math.floor(shellBounds.y + rect.y + 1),
126
+ };
127
+ }
128
+
129
+ function resolveCursorOwnerZIndex(
130
+ cursorOwnerPaneId: string | null,
131
+ rects: readonly ShellCursorOcclusionRect[],
132
+ ): number {
133
+ if (!cursorOwnerPaneId) return Number.NEGATIVE_INFINITY;
134
+ return rects.find((rect) => rect.paneId === cursorOwnerPaneId)?.zIndex ?? 0;
135
+ }
136
+
137
+ function shouldHideCursor({
138
+ cursor,
139
+ cursorOwnerPaneId,
140
+ terminalRects,
141
+ }: {
142
+ cursor: NativeCursorState;
143
+ cursorOwnerPaneId: string | null;
144
+ terminalRects: readonly ShellCursorOcclusionRect[];
145
+ }): boolean {
146
+ const ownerZIndex = resolveCursorOwnerZIndex(cursorOwnerPaneId, terminalRects);
147
+ return terminalRects.some((rect) => {
148
+ if (rect.paneId === cursorOwnerPaneId) return false;
149
+ if (rect.zIndex <= ownerZIndex) return false;
150
+ return rectContainsCursor(rect, cursor);
151
+ });
152
+ }
153
+
154
+ export function useShellCursorOcclusionGuard({
155
+ occlusionRects,
156
+ shellRef,
157
+ }: {
158
+ occlusionRects: readonly ShellCursorOcclusionRect[];
159
+ shellRef: RefObject<unknown>;
160
+ }) {
161
+ const nativeRenderer = useNativeRenderer();
162
+ const stateRef = useRef({ occlusionRects, shellRef });
163
+ stateRef.current = { occlusionRects, shellRef };
164
+
165
+ useEffect(() => {
166
+ if (
167
+ !nativeRenderer.addPostProcessFn
168
+ || !nativeRenderer.removePostProcessFn
169
+ || !nativeRenderer.getCursorState
170
+ || !nativeRenderer.setCursorPosition
171
+ ) {
172
+ return;
173
+ }
174
+
175
+ const guardCursor: NativePostProcessFn = () => {
176
+ const cursor = nativeRenderer.getCursorState?.();
177
+ if (!cursor?.visible) return;
178
+ const shellBounds = readRenderableBounds(stateRef.current.shellRef.current);
179
+ if (!shellBounds) return;
180
+
181
+ const terminalRects = stateRef.current.occlusionRects.map((rect) => toTerminalRect(shellBounds, rect));
182
+ if (terminalRects.length === 0) return;
183
+
184
+ const cursorOwnerPaneId = readRenderablePaneId(nativeRenderer.currentFocusedEditor);
185
+ if (!shouldHideCursor({ cursor, cursorOwnerPaneId, terminalRects })) return;
186
+ nativeRenderer.setCursorPosition?.(0, 0, false);
187
+ };
188
+
189
+ nativeRenderer.addPostProcessFn(guardCursor);
190
+ nativeRenderer.requestRender();
191
+ return () => {
192
+ nativeRenderer.removePostProcessFn?.(guardCursor);
193
+ nativeRenderer.requestRender();
194
+ };
195
+ }, [nativeRenderer]);
196
+ }
@@ -6,8 +6,10 @@ import {
6
6
  type MutableRefObject,
7
7
  type SetStateAction,
8
8
  } from "react";
9
+ import { useRafCallback } from "../../../../react/use-raf-callback";
9
10
  import {
10
11
  type DockDividerLayout,
12
+ type DockGeometryOptions,
11
13
  type DockLeafLayout,
12
14
  type FloatingRect,
13
15
  type LayoutBounds,
@@ -84,24 +86,40 @@ export interface ShellDragRuntimeState {
84
86
 
85
87
  export function useShellDragRuntimeState({
86
88
  contentHeight,
89
+ throttleFloatingPreview = false,
87
90
  width,
88
91
  }: {
89
92
  contentHeight: number;
93
+ throttleFloatingPreview?: boolean;
90
94
  width: number;
91
95
  }): ShellDragRuntimeState {
92
96
  const dragRef = useRef<DragMode | null>(null);
93
97
  const [dragFloatingRect, setDragFloatingRect] = useState<{ paneId: string; rect: FloatingRect } | null>(null);
98
+ const pendingDragFloatingRectRef = useRef<{ paneId: string; rect: FloatingRect } | null>(null);
94
99
  const [dragCursor, setDragCursor] = useState<{ x: number; y: number } | null>(null);
95
100
  const [dividerPreview, setDividerPreview] = useState<DividerPreviewState | null>(null);
96
101
  const [dockPreview, setDockPreview] = useState<DragPreview | null>(null);
97
102
  const dividerPreviewRef = useRef<DividerPreviewState | null>(null);
98
103
  const dockPreviewRef = useRef<DragPreview | null>(null);
104
+ const flushDragFloatingRect = useRafCallback(() => {
105
+ setDragFloatingRect(pendingDragFloatingRectRef.current);
106
+ });
99
107
 
100
108
  const updateDragFloatingRect = useCallback((next: { paneId: string; rect: FloatingRect } | null) => {
101
- setDragFloatingRect(next
109
+ const constrainedNext = next
102
110
  ? { paneId: next.paneId, rect: constrainFloatingRectToBounds(next.rect, width, contentHeight) }
103
- : null);
104
- }, [contentHeight, width]);
111
+ : null;
112
+ pendingDragFloatingRectRef.current = constrainedNext;
113
+ if (!next) {
114
+ setDragFloatingRect(null);
115
+ return;
116
+ }
117
+ if (!throttleFloatingPreview) {
118
+ setDragFloatingRect(constrainedNext);
119
+ return;
120
+ }
121
+ flushDragFloatingRect();
122
+ }, [contentHeight, flushDragFloatingRect, throttleFloatingPreview, width]);
105
123
 
106
124
  const updateDividerPreview = useCallback((next: DividerPreviewState | null) => {
107
125
  dividerPreviewRef.current = next;
@@ -146,6 +164,7 @@ interface UseShellPointerRuntimeOptions {
146
164
  closePaneMenu: () => void;
147
165
  contentHeight: number;
148
166
  dispatch: Dispatch<AppAction>;
167
+ dockGeometryOptions: DockGeometryOptions;
149
168
  dockDividerLayouts: DockDividerLayout[];
150
169
  dockLeafLayouts: DockLeafLayout[];
151
170
  dragRuntime: ShellDragRuntimeState;
@@ -166,6 +185,7 @@ interface UseShellPointerRuntimeOptions {
166
185
  setHoveredMenuItemId: Dispatch<SetStateAction<string | null>>;
167
186
  setMenuState: Dispatch<SetStateAction<ActionMenuState | null>>;
168
187
  snapGuides: ReturnType<typeof makeSnapGuides>;
188
+ transientFocusActive: boolean;
169
189
  updateWindowModePreviewLayout: (nextLayout: LayoutConfig, paneId?: string) => void;
170
190
  visibleFloatingPanes: VisibleFloatingPane[];
171
191
  visibleLayout: LayoutConfig;
@@ -179,6 +199,7 @@ export function useShellPointerRuntime({
179
199
  closePaneMenu,
180
200
  contentHeight,
181
201
  dispatch,
202
+ dockGeometryOptions,
182
203
  dockDividerLayouts,
183
204
  dockLeafLayouts,
184
205
  dragRuntime,
@@ -195,6 +216,7 @@ export function useShellPointerRuntime({
195
216
  setHoveredMenuItemId,
196
217
  setMenuState,
197
218
  snapGuides,
219
+ transientFocusActive,
198
220
  updateWindowModePreviewLayout,
199
221
  visibleFloatingPanes,
200
222
  visibleLayout,
@@ -206,6 +228,7 @@ export function useShellPointerRuntime({
206
228
  bounds,
207
229
  contentHeight,
208
230
  dispatch,
231
+ dockGeometryOptions,
209
232
  dockLeafLayouts,
210
233
  focusPane,
211
234
  dragRuntime,
@@ -237,6 +260,7 @@ export function useShellPointerRuntime({
237
260
  selectWindowModePane,
238
261
  setHoveredMenuItemId,
239
262
  setMenuState,
263
+ transientFocusActive,
240
264
  visibleFloatingPanes,
241
265
  width,
242
266
  windowMode,
@@ -254,6 +278,7 @@ export function useShellPointerRuntime({
254
278
  selectWindowModePane,
255
279
  setHoveredMenuItemId,
256
280
  setMenuState,
281
+ transientFocusActive,
257
282
  windowMode,
258
283
  });
259
284
 
@@ -0,0 +1,10 @@
1
+ import { isPaneInLayout } from "../../../plugins/pane-manager";
2
+ import { cloneLayout, type LayoutConfig } from "../../../types/config";
3
+
4
+ export function resolvePaneFocusSourceLayout(
5
+ layout: LayoutConfig,
6
+ paneId: string | null,
7
+ ): LayoutConfig | null {
8
+ if (!paneId || !isPaneInLayout(layout, paneId)) return null;
9
+ return cloneLayout(layout);
10
+ }