gloomberb 0.9.2 → 0.9.3

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 (111) hide show
  1. package/README.md +23 -2
  2. package/bin/gloomberb +1 -1
  3. package/package.json +1 -1
  4. package/src/app.tsx +10 -7
  5. package/src/capabilities/registry.ts +1 -0
  6. package/src/capabilities/types.ts +23 -0
  7. package/src/cli/commands/api.ts +119 -0
  8. package/src/cli/commands/automation.ts +173 -0
  9. package/src/cli/commands/command-utils.ts +53 -0
  10. package/src/cli/commands/market.ts +527 -0
  11. package/src/cli/commands/overview.ts +222 -0
  12. package/src/cli/commands/search.ts +39 -0
  13. package/src/cli/commands/system.ts +553 -0
  14. package/src/cli/commands/ticker.ts +104 -12
  15. package/src/cli/context.ts +35 -2
  16. package/src/cli/entry.ts +51 -0
  17. package/src/cli/errors.ts +56 -5
  18. package/src/cli/index.ts +73 -11
  19. package/src/cli/options.test.ts +38 -0
  20. package/src/cli/options.ts +95 -0
  21. package/src/cli/pane-functions/index.ts +10 -1
  22. package/src/cli/registry.test.ts +40 -0
  23. package/src/cli/registry.ts +46 -5
  24. package/src/cli/result.test.ts +82 -0
  25. package/src/cli/result.ts +149 -0
  26. package/src/cli/types.ts +11 -0
  27. package/src/cli.test.ts +11 -11
  28. package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
  29. package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
  30. package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
  31. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
  32. package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
  33. package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
  34. package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
  35. package/src/components/chart/core/pane-settings.test.ts +27 -0
  36. package/src/components/chart/core/pane-settings.ts +60 -1
  37. package/src/components/chart/core/pointer.ts +2 -0
  38. package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
  39. package/src/components/chart/stock-chart/settings.ts +7 -3
  40. package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
  41. package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
  42. package/src/components/command-bar/surface/pane.test.tsx +76 -0
  43. package/src/components/data-table/view.tsx +13 -2
  44. package/src/components/feed-data-table/stack-view.tsx +9 -1
  45. package/src/components/index.ts +1 -1
  46. package/src/components/input-search-bar.test.tsx +103 -0
  47. package/src/components/input-search-bar.tsx +16 -0
  48. package/src/components/layout/shell/active-drag.ts +5 -1
  49. package/src/components/layout/shell/drag/runtime.ts +21 -3
  50. package/src/components/layout/shell/fullscreen.ts +10 -0
  51. package/src/components/layout/shell/index.test.tsx +395 -2
  52. package/src/components/layout/shell/index.tsx +128 -2
  53. package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
  54. package/src/components/layout/shell/pane/layers.tsx +26 -14
  55. package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
  56. package/src/components/layout/shell/shortcuts.ts +4 -0
  57. package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
  58. package/src/components/layout/shell/window-mode/index.ts +11 -3
  59. package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
  60. package/src/components/layout/status-bar.test.tsx +105 -0
  61. package/src/components/layout/status-bar.tsx +32 -6
  62. package/src/components/layout/transient-layout.tsx +57 -0
  63. package/src/components/layout/window-edit/mode.test.ts +76 -5
  64. package/src/components/layout/window-edit/mode.ts +47 -2
  65. package/src/components/layout/window-edit/presentation.ts +2 -1
  66. package/src/components/price-performance.tsx +9 -1
  67. package/src/core/app-services.ts +7 -4
  68. package/src/data/config/store/index.ts +1 -2
  69. package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
  70. package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
  71. package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
  72. package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
  73. package/src/plugins/builtin/news/wire/read-state.ts +9 -32
  74. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
  75. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
  76. package/src/plugins/builtin/portfolio-list/pane/index.tsx +1 -1
  77. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
  78. package/src/plugins/builtin/shared/read-state.ts +43 -0
  79. package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
  80. package/src/plugins/builtin/substack/article-detail.tsx +39 -15
  81. package/src/plugins/builtin/substack/article-stack.tsx +6 -2
  82. package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
  83. package/src/plugins/builtin/substack/pane.tsx +8 -3
  84. package/src/plugins/builtin/substack/read-state.ts +55 -0
  85. package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
  86. package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
  87. package/src/plugins/pane-manager/docking.ts +9 -2
  88. package/src/plugins/pane-manager/queries.ts +8 -2
  89. package/src/plugins/prediction-markets/controller/index.ts +2 -0
  90. package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
  91. package/src/plugins/prediction-markets/pane.test.tsx +32 -0
  92. package/src/plugins/prediction-markets/pane.tsx +21 -1
  93. package/src/plugins/prediction-markets/test-helpers.tsx +1 -0
  94. package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
  95. package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
  96. package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
  97. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  98. package/src/renderers/electrobun/bun/index.ts +13 -0
  99. package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
  100. package/src/renderers/electrobun/bun/window/focus.ts +5 -0
  101. package/src/renderers/electrobun/view/app-services.ts +6 -4
  102. package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
  103. package/src/renderers/electrobun/view/host/text.test.ts +22 -0
  104. package/src/renderers/electrobun/view/host/text.tsx +24 -20
  105. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  106. package/src/renderers/opentui/start.tsx +13 -5
  107. package/src/state/app/input-capture.ts +12 -0
  108. package/src/types/plugin.ts +28 -0
  109. package/src/utils/cli-output.ts +6 -0
  110. package/src/utils/search-focus-navigation.ts +24 -0
  111. package/src/version.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import { AsciiText, Box, Text, compactContextMenuItems, useContextMenu, useUiHost } from "../../../ui";
2
- import { useCallback, useMemo, useState } from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { useRendererHost, useUiCapabilities } from "../../../ui";
4
4
  import { useViewport } from "../../../react/input";
5
5
  import { useDialogState } from "../../../ui/dialog";
@@ -8,6 +8,7 @@ import type { DesktopDockPreviewState, DesktopWindowBridge } from "../../../type
8
8
  import {
9
9
  getDockDividerLayouts,
10
10
  getDockLeafLayouts,
11
+ isPaneInLayout,
11
12
  type DockGeometryOptions,
12
13
  type LayoutBounds,
13
14
  type ResolvedPane,
@@ -58,6 +59,8 @@ import {
58
59
  useShellVisibleLayout,
59
60
  } from "./layout-state";
60
61
  import { useShellPaneActions } from "./pane/actions";
62
+ import { resolvePaneFocusSourceLayout } from "./fullscreen";
63
+ import { useTransientLayout } from "../transient-layout";
61
64
 
62
65
  export { resolveAppHeaderHeightCells } from "./chrome";
63
66
  export { buildNativeWindowState } from "./native/window-state";
@@ -70,6 +73,13 @@ interface ShellProps {
70
73
  commandBarNativeOccluder?: LayoutBounds | null;
71
74
  }
72
75
 
76
+ interface TransientFocusLayoutState {
77
+ paneId: string;
78
+ layout: LayoutConfig;
79
+ sourceLayoutIndex: number;
80
+ active: boolean;
81
+ }
82
+
73
83
  export function Shell({
74
84
  pluginRegistry,
75
85
  desktopWindowBridge,
@@ -86,6 +96,7 @@ export function Shell({
86
96
  const inputCaptured = useAppSelector((state) => state.inputCaptured);
87
97
  const statusBarVisible = useAppSelector(selectStatusBarVisible);
88
98
  const rendererHost = useRendererHost();
99
+ const { setTransientLayout } = useTransientLayout();
89
100
  const uiKind = useUiHost().kind;
90
101
  const shortcutDisplayMode = getShortcutDisplayMode(uiKind);
91
102
  const { nativePaneChrome = false, nativeContextMenu, precisePointer, titleBarOverlay, cellHeightPx } = useUiCapabilities();
@@ -103,6 +114,9 @@ export function Shell({
103
114
  setHoveredPaneId((current) => (current === paneId ? current : paneId));
104
115
  }, []);
105
116
  const [menuState, setMenuState] = useState<ActionMenuState | null>(null);
117
+ const [transientFocusLayoutState, setTransientFocusLayoutState] = useState<TransientFocusLayoutState | null>(null);
118
+ const transientFocusLayoutStateRef = useRef<TransientFocusLayoutState | null>(null);
119
+ transientFocusLayoutStateRef.current = transientFocusLayoutState;
106
120
  const [hoveredMenuItemId, setHoveredMenuItemId] = useState<string | null>(null);
107
121
  const closePaneMenu = useCallback(() => {
108
122
  setMenuState(null);
@@ -149,7 +163,7 @@ export function Shell({
149
163
  }, [dispatch]);
150
164
 
151
165
  const {
152
- activeLayout,
166
+ activeLayout: windowModeLayout,
153
167
  nativeWindowModePanelRect,
154
168
  selectWindowModePane,
155
169
  startWindowMode,
@@ -171,6 +185,17 @@ export function Shell({
171
185
  visibleLayout,
172
186
  width,
173
187
  });
188
+ const transientFocusActive = !windowMode && transientFocusLayoutState?.active === true;
189
+ const activeLayout = transientFocusActive && transientFocusLayoutState
190
+ ? transientFocusLayoutState.layout
191
+ : windowModeLayout;
192
+ const transientFocusPaneId = transientFocusActive ? transientFocusLayoutState?.paneId ?? null : null;
193
+
194
+ useEffect(() => {
195
+ if (!windowMode || !transientFocusLayoutState) return;
196
+ transientFocusLayoutStateRef.current = null;
197
+ setTransientFocusLayoutState(null);
198
+ }, [transientFocusLayoutState, windowMode]);
174
199
 
175
200
  const {
176
201
  dockedPanes,
@@ -211,6 +236,102 @@ export function Shell({
211
236
  visibleLayout,
212
237
  width,
213
238
  });
239
+ const setTransientFocusLayout = useCallback((next: TransientFocusLayoutState | null) => {
240
+ transientFocusLayoutStateRef.current = next;
241
+ setTransientFocusLayoutState(next);
242
+ }, []);
243
+ const activateTransientFocusState = useCallback((current: TransientFocusLayoutState) => {
244
+ closePaneMenu();
245
+ setTransientFocusLayout({ ...current, active: true });
246
+ const sourceLayout = config.layouts[current.sourceLayoutIndex]?.layout;
247
+ if (
248
+ current.sourceLayoutIndex !== config.activeLayoutIndex
249
+ && sourceLayout
250
+ && isPaneInLayout(sourceLayout, current.paneId)
251
+ ) {
252
+ dispatch({ type: "SWITCH_LAYOUT", index: current.sourceLayoutIndex });
253
+ }
254
+ focusPane(current.paneId);
255
+ }, [closePaneMenu, config.activeLayoutIndex, config.layouts, dispatch, focusPane, setTransientFocusLayout]);
256
+ const toggleFocusedPaneFullscreen = useCallback(() => {
257
+ const current = transientFocusLayoutStateRef.current;
258
+ if (current?.active) {
259
+ setTransientFocusLayout(null);
260
+ return true;
261
+ }
262
+
263
+ if (
264
+ current
265
+ && current.paneId === focusedPaneId
266
+ && current.sourceLayoutIndex === config.activeLayoutIndex
267
+ ) {
268
+ activateTransientFocusState(current);
269
+ return true;
270
+ }
271
+
272
+ const nextLayout = resolvePaneFocusSourceLayout(visibleLayout, focusedPaneId);
273
+ if (!focusedPaneId || !nextLayout) {
274
+ pluginRegistry.notify({ body: "Focus a pane to make it fullscreen", type: "info" });
275
+ return false;
276
+ }
277
+
278
+ closePaneMenu();
279
+ setTransientFocusLayout({
280
+ paneId: focusedPaneId,
281
+ layout: nextLayout,
282
+ sourceLayoutIndex: config.activeLayoutIndex,
283
+ active: true,
284
+ });
285
+ focusPane(focusedPaneId);
286
+ return true;
287
+ }, [
288
+ activateTransientFocusState,
289
+ closePaneMenu,
290
+ config.activeLayoutIndex,
291
+ focusedPaneId,
292
+ focusPane,
293
+ pluginRegistry,
294
+ setTransientFocusLayout,
295
+ visibleLayout,
296
+ ]);
297
+ const activateTransientFocusLayout = useCallback(() => {
298
+ const current = transientFocusLayoutStateRef.current;
299
+ if (!current) return;
300
+ activateTransientFocusState(current);
301
+ }, [activateTransientFocusState]);
302
+ const deactivateTransientFocusLayout = useCallback(() => {
303
+ const current = transientFocusLayoutStateRef.current;
304
+ if (!current || !current.active) return;
305
+ closePaneMenu();
306
+ setTransientFocusLayout({ ...current, active: false });
307
+ }, [closePaneMenu, setTransientFocusLayout]);
308
+ const exitTransientFocusLayout = useCallback(() => {
309
+ closePaneMenu();
310
+ setTransientFocusLayout(null);
311
+ }, [closePaneMenu, setTransientFocusLayout]);
312
+
313
+ useEffect(() => {
314
+ setTransientLayout(
315
+ transientFocusLayoutState
316
+ ? {
317
+ id: "pane-focus",
318
+ label: "^F Focus",
319
+ active: transientFocusActive,
320
+ onActivate: activateTransientFocusLayout,
321
+ onDeactivate: deactivateTransientFocusLayout,
322
+ onExit: exitTransientFocusLayout,
323
+ }
324
+ : null,
325
+ );
326
+ return () => setTransientLayout(null);
327
+ }, [
328
+ activateTransientFocusLayout,
329
+ deactivateTransientFocusLayout,
330
+ exitTransientFocusLayout,
331
+ setTransientLayout,
332
+ transientFocusActive,
333
+ transientFocusLayoutState,
334
+ ]);
214
335
 
215
336
  useShellPaneManagementShortcuts({
216
337
  cancelActiveDrag,
@@ -226,6 +347,7 @@ export function Shell({
226
347
  overlayOpen,
227
348
  popOutFocusedPane,
228
349
  startWindowMode,
350
+ toggleFocusedPaneFullscreen,
229
351
  toggleFocusedPaneFloating,
230
352
  });
231
353
 
@@ -333,6 +455,7 @@ export function Shell({
333
455
  closePaneMenu,
334
456
  contentHeight,
335
457
  dispatch,
458
+ dockGeometryOptions,
336
459
  dockDividerLayouts,
337
460
  dockLeafLayouts,
338
461
  dragRuntime,
@@ -349,6 +472,7 @@ export function Shell({
349
472
  setHoveredMenuItemId,
350
473
  setMenuState,
351
474
  snapGuides,
475
+ transientFocusActive,
352
476
  updateWindowModePreviewLayout,
353
477
  visibleFloatingPanes,
354
478
  visibleLayout,
@@ -409,6 +533,8 @@ export function Shell({
409
533
  startNativeDockedDrag={startNativeDockedDrag}
410
534
  startNativeFloatingDrag={startNativeFloatingDrag}
411
535
  startNativeFloatResize={startNativeFloatResize}
536
+ transientFocusActive={transientFocusActive}
537
+ transientFocusPaneId={transientFocusPaneId}
412
538
  visibleFloatingPanes={visibleFloatingPanes}
413
539
  width={width}
414
540
  windowModeDockResizePathKey={windowModeDockResizePathKey}
@@ -20,6 +20,7 @@ interface UseShellNativePointerRuntimeOptions {
20
20
  selectWindowModePane: (paneId: string) => void;
21
21
  setHoveredMenuItemId: Dispatch<SetStateAction<string | null>>;
22
22
  setMenuState: Dispatch<SetStateAction<ActionMenuState | null>>;
23
+ transientFocusActive: boolean;
23
24
  windowMode: WindowEditState | null;
24
25
  }
25
26
 
@@ -35,6 +36,7 @@ export function useShellNativePointerRuntime({
35
36
  selectWindowModePane,
36
37
  setHoveredMenuItemId,
37
38
  setMenuState,
39
+ transientFocusActive,
38
40
  windowMode,
39
41
  }: UseShellNativePointerRuntimeOptions) {
40
42
  const {
@@ -69,6 +71,7 @@ export function useShellNativePointerRuntime({
69
71
  const startNativeFloatingDrag = useCallback((paneId: string, rect: FloatingRect, event: ShellMouseEvent) => {
70
72
  if (!nativePaneChrome) return;
71
73
  if (windowMode) return;
74
+ if (transientFocusActive) return;
72
75
  if (event.button === 2) return;
73
76
 
74
77
  const pointer = getShellPointer(event);
@@ -83,11 +86,12 @@ export function useShellNativePointerRuntime({
83
86
  };
84
87
  updateDragFloatingRect({ paneId, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
85
88
  event.preventDefault();
86
- }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, updateDragFloatingRect, windowMode]);
89
+ }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, updateDragFloatingRect, windowMode]);
87
90
 
88
91
  const startNativeDockedDrag = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
89
92
  if (!nativePaneChrome) return;
90
93
  if (windowMode) return;
94
+ if (transientFocusActive) return;
91
95
  if (event.button === 2) return;
92
96
 
93
97
  const pointer = getShellPointer(event);
@@ -101,10 +105,11 @@ export function useShellNativePointerRuntime({
101
105
  origRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
102
106
  };
103
107
  event.preventDefault();
104
- }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, windowMode]);
108
+ }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, transientFocusActive, windowMode]);
105
109
 
106
110
  const startNativeFloatResize = useCallback((paneId: string, rect: FloatingRect, event: ShellMouseEvent) => {
107
111
  if (!nativePaneChrome) return;
112
+ if (transientFocusActive) return;
108
113
 
109
114
  const pointer = getShellPointer(event);
110
115
  if (windowMode) {
@@ -122,10 +127,11 @@ export function useShellNativePointerRuntime({
122
127
  updateDragFloatingRect({ paneId, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } });
123
128
  event.stopPropagation();
124
129
  event.preventDefault();
125
- }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, selectWindowModePane, updateDragFloatingRect, windowMode]);
130
+ }, [dragRef, focusNativePane, getShellPointer, nativePaneChrome, selectWindowModePane, transientFocusActive, updateDragFloatingRect, windowMode]);
126
131
 
127
132
  const startNativeDividerDrag = useCallback((divider: DockDividerLayout, event: ShellMouseEvent) => {
128
133
  if (!nativePaneChrome) return;
134
+ if (transientFocusActive) return;
129
135
 
130
136
  const pointer = getShellPointer(event);
131
137
  if (menuState) {
@@ -148,7 +154,7 @@ export function useShellNativePointerRuntime({
148
154
  });
149
155
  event.stopPropagation();
150
156
  event.preventDefault();
151
- }, [dragRef, getShellPointer, menuState, nativePaneChrome, setHoveredMenuItemId, setMenuState, updateDividerPreview]);
157
+ }, [dragRef, getShellPointer, menuState, nativePaneChrome, setHoveredMenuItemId, setMenuState, transientFocusActive, updateDividerPreview]);
152
158
 
153
159
  const handlePaneAction = useCallback((paneId: string, rect: LayoutBounds, event: ShellMouseEvent) => {
154
160
  if (windowMode) return;
@@ -48,6 +48,8 @@ interface ShellPaneLayersProps {
48
48
  startNativeDockedDrag: (paneId: string, rect: LayoutBounds, event: any) => void;
49
49
  startNativeFloatingDrag: (paneId: string, rect: FloatingRect, event: any) => void;
50
50
  startNativeFloatResize: (paneId: string, rect: FloatingRect, event: any) => void;
51
+ transientFocusActive: boolean;
52
+ transientFocusPaneId: string | null;
51
53
  visibleFloatingPanes: VisibleFloatingPane[];
52
54
  width: number;
53
55
  windowModeDockResizePathKey: string | null;
@@ -79,6 +81,8 @@ export function ShellPaneLayers({
79
81
  startNativeDockedDrag,
80
82
  startNativeFloatingDrag,
81
83
  startNativeFloatResize,
84
+ transientFocusActive,
85
+ transientFocusPaneId,
82
86
  visibleFloatingPanes,
83
87
  width,
84
88
  windowModeDockResizePathKey,
@@ -87,8 +91,12 @@ export function ShellPaneLayers({
87
91
  return (
88
92
  <>
89
93
  {dockLeafLayouts.map((leaf) => {
94
+ if (transientFocusActive && leaf.instanceId !== transientFocusPaneId) return null;
90
95
  const pane = paneMap.get(leaf.instanceId);
91
96
  if (!pane) return null;
97
+ const rect = transientFocusActive
98
+ ? { x: 0, y: 0, width, height: contentHeight }
99
+ : leaf.rect;
92
100
  const focused = focusedPaneId === leaf.instanceId && (!overlayOpen || menuPaneId === leaf.instanceId);
93
101
  const windowModeSelected = windowModePaneId === leaf.instanceId;
94
102
  const showActions = focused || hoveredPaneId === leaf.instanceId || menuPaneId === leaf.instanceId;
@@ -96,17 +104,17 @@ export function ShellPaneLayers({
96
104
  <Box
97
105
  key={`dock:${leaf.instanceId}`}
98
106
  position="absolute"
99
- left={leaf.rect.x}
100
- top={leaf.rect.y}
101
- width={leaf.rect.width}
102
- height={leaf.rect.height}
107
+ left={rect.x}
108
+ top={rect.y}
109
+ width={rect.width}
110
+ height={rect.height}
103
111
  >
104
112
  <PaneFooterProvider>
105
113
  {(footer) => {
106
114
  const reserveFooter = shouldReservePaneFooter(nativePaneChrome, hasPaneFooterContent(footer));
107
115
  const bodyFrame = resolvePaneBodyFrame({
108
- width: leaf.rect.width,
109
- height: leaf.rect.height,
116
+ width: rect.width,
117
+ height: rect.height,
110
118
  nativePaneChrome,
111
119
  reserveFooter,
112
120
  });
@@ -115,18 +123,18 @@ export function ShellPaneLayers({
115
123
  paneId={leaf.instanceId}
116
124
  title={getPaneTitle(pane)}
117
125
  focused={focused}
118
- width={leaf.rect.width}
119
- height={leaf.rect.height}
126
+ width={rect.width}
127
+ height={rect.height}
120
128
  showActions={showActions}
121
129
  windowModeSelected={windowModeSelected}
122
130
  footer={footer}
123
131
  onMouseDown={nativePaneChrome ? (event) => handleNativePaneMouseDown(leaf.instanceId, event) : undefined}
124
132
  onMouseMove={() => setHoveredPaneIfChanged(leaf.instanceId)}
125
- onHeaderMouseDown={nativePaneChrome ? (event) => startNativeDockedDrag(leaf.instanceId, leaf.rect, event) : undefined}
126
- onHeaderMouseDrag={nativePaneChrome ? handleNativeDrag : undefined}
127
- onHeaderMouseDragEnd={nativePaneChrome ? handleNativeDrag : undefined}
128
- onHeaderContextMenu={nativePaneChrome && nativeContextMenu === true ? (event) => handleNativePaneContextMenu(leaf.instanceId, leaf.rect, event) : undefined}
129
- onActionMouseDown={(event) => handlePaneAction(leaf.instanceId, leaf.rect, event)}
133
+ onHeaderMouseDown={nativePaneChrome && !transientFocusActive ? (event) => startNativeDockedDrag(leaf.instanceId, rect, event) : undefined}
134
+ onHeaderMouseDrag={nativePaneChrome && !transientFocusActive ? handleNativeDrag : undefined}
135
+ onHeaderMouseDragEnd={nativePaneChrome && !transientFocusActive ? handleNativeDrag : undefined}
136
+ onHeaderContextMenu={nativePaneChrome && nativeContextMenu === true ? (event) => handleNativePaneContextMenu(leaf.instanceId, rect, event) : undefined}
137
+ onActionMouseDown={(event) => handlePaneAction(leaf.instanceId, rect, event)}
130
138
  >
131
139
  <PaneContent
132
140
  component={pane.def.component}
@@ -145,7 +153,10 @@ export function ShellPaneLayers({
145
153
  })}
146
154
 
147
155
  {visibleFloatingPanes.map(({ pane, rect }) => {
148
- const preview = dragFloatingRect?.paneId === pane.instance.instanceId
156
+ if (transientFocusActive && pane.instance.instanceId !== transientFocusPaneId) return null;
157
+ const preview = transientFocusActive
158
+ ? { x: 0, y: 0, width, height: contentHeight }
159
+ : dragFloatingRect?.paneId === pane.instance.instanceId
149
160
  ? constrainFloatingRectToBounds(dragFloatingRect.rect, width, contentHeight)
150
161
  : rect;
151
162
  const focused = focusedPaneId === pane.instance.instanceId && (!overlayOpen || menuPaneId === pane.instance.instanceId);
@@ -203,6 +214,7 @@ export function ShellPaneLayers({
203
214
  })}
204
215
 
205
216
  {dockDividerLayouts.map((divider) => {
217
+ if (transientFocusActive) return null;
206
218
  const dividerPathKey = pathKey(divider.path);
207
219
  const previewActive = dividerPreview?.pathKey === dividerPathKey;
208
220
  const active = previewActive || windowModeDockResizePathKey === dividerPathKey;
@@ -25,6 +25,7 @@ interface ShellPaneManagementShortcutOptions {
25
25
  overlayOpen: boolean;
26
26
  popOutFocusedPane(): boolean;
27
27
  startWindowMode(paneId?: string, mode?: WindowEditMode): void;
28
+ toggleFocusedPaneFullscreen(): boolean;
28
29
  toggleFocusedPaneFloating(): boolean;
29
30
  }
30
31
 
@@ -42,6 +43,7 @@ export function useShellPaneManagementShortcuts({
42
43
  overlayOpen,
43
44
  popOutFocusedPane,
44
45
  startWindowMode,
46
+ toggleFocusedPaneFullscreen,
45
47
  toggleFocusedPaneFloating,
46
48
  }: ShellPaneManagementShortcutOptions): void {
47
49
  const doubleEscapeCloseRef = useRef(createDoubleEscapeCloseState());
@@ -53,6 +55,20 @@ export function useShellPaneManagementShortcuts({
53
55
  }, [overlayOpen]);
54
56
 
55
57
  useShortcut((event) => {
58
+ const shortcut = resolvePaneManagementShortcut(event);
59
+ if (shortcut === "toggle-fullscreen" && !overlayOpen) {
60
+ if (!inputCaptured || inputCaptureAllowsPaneManagementShortcut(shortcut, event)) {
61
+ if (hasActiveDrag()) {
62
+ cancelActiveDrag();
63
+ }
64
+ if (toggleFocusedPaneFullscreen()) {
65
+ event.preventDefault();
66
+ event.stopPropagation();
67
+ return;
68
+ }
69
+ }
70
+ }
71
+
56
72
  const isEscape = event.name === "escape" || event.name === "esc";
57
73
  if (isEscape) {
58
74
  const doubleEscapeState = doubleEscapeCloseRef.current;
@@ -91,6 +107,9 @@ export function useShellPaneManagementShortcuts({
91
107
  case "settings":
92
108
  handled = openFocusedPaneSettings();
93
109
  break;
110
+ case "toggle-fullscreen":
111
+ handled = toggleFocusedPaneFullscreen();
112
+ break;
94
113
  case "toggle-floating":
95
114
  handled = toggleFocusedPaneFloating();
96
115
  break;
@@ -2,6 +2,7 @@ import type { KeyEventLike } from "../../../react/input";
2
2
 
3
3
  export const PANE_MANAGEMENT_ACCELERATORS = {
4
4
  settings: "CmdOrCtrl+,",
5
+ fullscreen: "CmdOrCtrl+Shift+F",
5
6
  toggleFloating: "CmdOrCtrl+Shift+D",
6
7
  popOut: "CmdOrCtrl+Shift+O",
7
8
  copyScreenshot: "CmdOrCtrl+Shift+C",
@@ -15,6 +16,7 @@ export const PANE_MANAGEMENT_ACCELERATORS = {
15
16
 
16
17
  export type PaneManagementShortcut =
17
18
  | "settings"
19
+ | "toggle-fullscreen"
18
20
  | "toggle-floating"
19
21
  | "pop-out"
20
22
  | "copy-screenshot"
@@ -38,6 +40,7 @@ export function resolvePaneManagementShortcut(
38
40
  if (!shifted || event.alt) return null;
39
41
  if (name === "c") return "copy-screenshot";
40
42
  if (name === "d") return "toggle-floating";
43
+ if (name === "f") return "toggle-fullscreen";
41
44
  if (name === "o") return "pop-out";
42
45
  if (name === "l") return "layout-actions";
43
46
  if (name === "g") return "gridlock-all";
@@ -50,6 +53,7 @@ export function inputCaptureAllowsPaneManagementShortcut(
50
53
  shortcut: PaneManagementShortcut,
51
54
  event: Pick<KeyEventLike, "meta" | "super" | "targetEditable">,
52
55
  ): boolean {
56
+ if (shortcut === "toggle-fullscreen") return event.meta || event.super === true;
53
57
  if (shortcut !== "close" && shortcut !== "close-all-floating") return false;
54
58
  return event.meta || event.super || event.targetEditable !== true;
55
59
  }
@@ -41,6 +41,7 @@ interface UseShellTerminalPointerRuntimeOptions {
41
41
  selectWindowModePane: (paneId: string) => void;
42
42
  setHoveredMenuItemId: Dispatch<SetStateAction<string | null>>;
43
43
  setMenuState: Dispatch<SetStateAction<ActionMenuState | null>>;
44
+ transientFocusActive: boolean;
44
45
  visibleFloatingPanes: VisibleFloatingPane[];
45
46
  width: number;
46
47
  windowMode: WindowEditState | null;
@@ -79,6 +80,7 @@ export function useShellTerminalPointerRuntime({
79
80
  selectWindowModePane,
80
81
  setHoveredMenuItemId,
81
82
  setMenuState,
83
+ transientFocusActive,
82
84
  visibleFloatingPanes,
83
85
  width,
84
86
  windowMode,
@@ -132,25 +134,27 @@ export function useShellTerminalPointerRuntime({
132
134
  return;
133
135
  }
134
136
 
135
- for (const divider of dockDividerLayouts) {
136
- if (!pointInRect(divider.rect, event.x, shellY)) continue;
137
- dragRef.current = {
138
- type: "divider",
139
- path: divider.path,
140
- axis: divider.axis,
141
- startX: preciseX,
142
- startY: preciseShellY,
143
- startRatio: divider.ratio,
144
- bounds: divider.bounds,
145
- };
146
- updateDividerPreview({
147
- pathKey: divider.path.join("."),
148
- rect: divider.rect,
149
- ratio: divider.ratio,
150
- });
151
- event.stopPropagation();
152
- event.preventDefault();
153
- return;
137
+ if (!transientFocusActive) {
138
+ for (const divider of dockDividerLayouts) {
139
+ if (!pointInRect(divider.rect, event.x, shellY)) continue;
140
+ dragRef.current = {
141
+ type: "divider",
142
+ path: divider.path,
143
+ axis: divider.axis,
144
+ startX: preciseX,
145
+ startY: preciseShellY,
146
+ startRatio: divider.ratio,
147
+ bounds: divider.bounds,
148
+ };
149
+ updateDividerPreview({
150
+ pathKey: divider.path.join("."),
151
+ rect: divider.rect,
152
+ ratio: divider.ratio,
153
+ });
154
+ event.stopPropagation();
155
+ event.preventDefault();
156
+ return;
157
+ }
154
158
  }
155
159
 
156
160
  for (const leaf of dockLeafLayouts) {
@@ -279,6 +283,11 @@ export function useShellTerminalPointerRuntime({
279
283
  event.preventDefault();
280
284
  return;
281
285
  }
286
+ if (relativeY === 0 && transientFocusActive) {
287
+ event.stopPropagation();
288
+ event.preventDefault();
289
+ return;
290
+ }
282
291
  if (relativeY === 0) {
283
292
  dragRef.current = {
284
293
  type: "pane-drag",
@@ -317,6 +326,7 @@ export function useShellTerminalPointerRuntime({
317
326
  selectWindowModePane,
318
327
  setHoveredMenuItemId,
319
328
  setMenuState,
329
+ transientFocusActive,
320
330
  updateDividerPreview,
321
331
  updateDragFloatingRect,
322
332
  visibleFloatingPanes,
@@ -13,6 +13,7 @@ import {
13
13
  applyWindowEditDirection,
14
14
  cycleWindowEditFocus,
15
15
  cycleWindowEditPane,
16
+ cycleWindowEditTarget,
16
17
  directionFromWindowEditKey,
17
18
  getWindowEditPaneIds,
18
19
  normalizeWindowEditFocus,
@@ -72,7 +73,10 @@ export function useShellWindowMode({
72
73
  );
73
74
  const windowModePaneId = windowMode?.paneId;
74
75
  const activeLayout = windowMode?.previewLayout ?? visibleLayout;
75
- const windowModePaneIds = useMemo(() => getWindowEditPaneIds(activeLayout), [activeLayout]);
76
+ const windowModePaneIds = useMemo(
77
+ () => getWindowEditPaneIds(activeLayout, bounds, dockGeometryOptions),
78
+ [activeLayout, bounds, dockGeometryOptions],
79
+ );
76
80
  const windowModeDockMovePreview = useMemo(
77
81
  () => resolveWindowEditDockMovePreview(windowMode, bounds, dockGeometryOptions),
78
82
  [bounds, dockGeometryOptions, windowMode],
@@ -210,12 +214,16 @@ export function useShellWindowMode({
210
214
  }
211
215
  } else if (name === "w") {
212
216
  setWindowMode((current) => current
213
- ? cycleWindowEditPane(current, windowModePaneIds, bounds, dockGeometryOptions, event.shift ? -1 : 1)
217
+ ? current.mode === "move" && current.focus.kind === "dock-move"
218
+ ? cycleWindowEditTarget(current, bounds, dockGeometryOptions, event.shift ? -1 : 1)
219
+ : cycleWindowEditPane(current, windowModePaneIds, bounds, dockGeometryOptions, event.shift ? -1 : 1)
214
220
  : current);
215
221
  } else if (name === "tab") {
216
222
  setWindowMode((current) => current
217
223
  ? current.mode === "move"
218
- ? cycleWindowEditPane(current, windowModePaneIds, bounds, dockGeometryOptions, event.shift ? -1 : 1)
224
+ ? current.focus.kind === "dock-move"
225
+ ? cycleWindowEditTarget(current, bounds, dockGeometryOptions, event.shift ? -1 : 1)
226
+ : cycleWindowEditPane(current, windowModePaneIds, bounds, dockGeometryOptions, event.shift ? -1 : 1)
219
227
  : {
220
228
  ...current,
221
229
  focus: cycleWindowEditFocus(
@@ -40,6 +40,13 @@ interface ShellWindowModeOverlaysProps {
40
40
  windowModeDockMovePreview: WindowEditDockMovePreview | null;
41
41
  }
42
42
 
43
+ function getHighlightedPaneId(windowMode: WindowEditState | null, focusedPaneId: string | null): string | null {
44
+ if (windowMode?.mode === "move" && windowMode.focus.kind === "dock-move") {
45
+ return windowMode.focus.targetId;
46
+ }
47
+ return windowMode?.paneId ?? focusedPaneId;
48
+ }
49
+
43
50
  function windowModeBannerKey(windowMode: WindowEditState): string {
44
51
  const focusKey = windowMode.focus.kind === "dock-move"
45
52
  ? `${windowMode.focus.targetId}:${windowMode.focus.position}`
@@ -69,7 +76,7 @@ export function ShellWindowModeOverlays({
69
76
  windowMode,
70
77
  windowModeDockMovePreview,
71
78
  }: ShellWindowModeOverlaysProps) {
72
- const highlightedPaneId = windowMode?.paneId ?? focusedPaneId;
79
+ const highlightedPaneId = getHighlightedPaneId(windowMode, focusedPaneId);
73
80
  const highlightedFloatingPane = highlightedPaneId
74
81
  ? visibleFloatingPanes.find((entry) => entry.pane.instance.instanceId === highlightedPaneId)
75
82
  : null;