gloomberb 0.9.4 → 0.9.6

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 (138) hide show
  1. package/README.md +49 -8
  2. package/package.json +4 -3
  3. package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
  4. package/patches/react-reconciler@0.33.0.patch +10 -0
  5. package/src/app/global-shortcuts.test.tsx +20 -1
  6. package/src/app/global-shortcuts.ts +2 -0
  7. package/src/app/pane-runtime/index.tsx +11 -4
  8. package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -1
  9. package/src/app.tsx +41 -17
  10. package/src/cli/commands/remote.ts +206 -0
  11. package/src/cli/entry.ts +8 -1
  12. package/src/cli/index.ts +2 -0
  13. package/src/cli/native-smoke.ts +5 -0
  14. package/src/components/chart/core/keyboard.ts +10 -1
  15. package/src/components/chart/native/bitmap-support.test.ts +27 -0
  16. package/src/components/chart/native/bitmap-support.ts +55 -0
  17. package/src/components/chart/native/kitty/index.test.ts +47 -0
  18. package/src/components/chart/native/surface/visibility.ts +8 -1
  19. package/src/components/chart/static/bar-chart-surface.test.tsx +4 -0
  20. package/src/components/chart/static/chart/bitmap.ts +16 -9
  21. package/src/components/chart/static/chart/surface.test.tsx +59 -4
  22. package/src/components/chart/static/chart/surface.tsx +4 -0
  23. package/src/components/chart/stock-chart/index.test.ts +7 -0
  24. package/src/components/command-bar/list/view.tsx +48 -1
  25. package/src/components/command-bar/multi-select-picker.tsx +7 -0
  26. package/src/components/command-bar/routing/navigation-state.ts +10 -1
  27. package/src/components/command-bar/surface/index.test.tsx +14 -0
  28. package/src/components/command-bar/theme-picker.tsx +91 -79
  29. package/src/components/command-bar/workflow/field-row.tsx +30 -5
  30. package/src/components/data-table/view.tsx +0 -12
  31. package/src/components/index.ts +7 -0
  32. package/src/components/layout/floating-pane.tsx +3 -3
  33. package/src/components/layout/pane/footer/index.tsx +23 -0
  34. package/src/components/layout/pane/header.tsx +5 -0
  35. package/src/components/layout/pane/index.tsx +3 -3
  36. package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
  37. package/src/components/layout/shell/cursor-occlusion.ts +9 -1
  38. package/src/components/layout/shell/index.test.tsx +2 -0
  39. package/src/components/layout/shell/index.tsx +16 -4
  40. package/src/components/layout/shell/pane/actions.ts +21 -7
  41. package/src/components/layout/shell/pane/layers.tsx +2 -2
  42. package/src/components/layout/status-bar.tsx +5 -5
  43. package/src/components/metric-treemap/index.tsx +317 -0
  44. package/src/components/metric-treemap/layout.test.ts +77 -0
  45. package/src/components/metric-treemap/layout.ts +476 -0
  46. package/src/components/pane-settings-dialog/desktop.tsx +1 -1
  47. package/src/components/price-performance.tsx +1 -1
  48. package/src/components/price-sparkline/view.tsx +15 -14
  49. package/src/components/speedometer-gauge/terminal.tsx +18 -15
  50. package/src/components/table-view-shared.tsx +0 -10
  51. package/src/components/ticker/list-table-view.tsx +1 -7
  52. package/src/components/ticker/list-table.test.tsx +0 -4
  53. package/src/components/toggle-list.tsx +10 -0
  54. package/src/components/ui/button.tsx +12 -0
  55. package/src/components/ui/data-table/index.tsx +51 -0
  56. package/src/components/ui/data-table/opentui/index.tsx +4 -10
  57. package/src/components/ui/data-table/types.ts +6 -4
  58. package/src/components/ui/fields.tsx +18 -0
  59. package/src/components/ui/list-view.tsx +84 -4
  60. package/src/components/ui/message-composer.tsx +22 -0
  61. package/src/components/ui/tabs.tsx +46 -2
  62. package/src/components/ui/ui.test.tsx +0 -9
  63. package/src/core/state/app/layout-reducer.ts +7 -1
  64. package/src/core/state/app/layout.ts +6 -0
  65. package/src/core/state/app/state.ts +23 -1
  66. package/src/core/state/app/types.ts +4 -2
  67. package/src/plugins/builtin/account-management/form-components.tsx +2 -2
  68. package/src/plugins/builtin/alerts/pane.tsx +1 -1
  69. package/src/plugins/builtin/buildout/cells.tsx +1 -1
  70. package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
  71. package/src/plugins/builtin/chat/channels.ts +5 -18
  72. package/src/plugins/builtin/chat/chat.test.tsx +1 -7
  73. package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
  74. package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
  75. package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
  76. package/src/plugins/builtin/chat/sidebar.tsx +1 -1
  77. package/src/plugins/builtin/chat/status-widget.tsx +2 -8
  78. package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
  79. package/src/plugins/builtin/cloud/plugin.tsx +4 -13
  80. package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
  81. package/src/plugins/builtin/debug/index.tsx +3 -4
  82. package/src/plugins/builtin/help/components.tsx +1 -1
  83. package/src/plugins/builtin/holders/treemap.tsx +28 -297
  84. package/src/plugins/builtin/holders/types.ts +0 -34
  85. package/src/plugins/builtin/market-heatmap/data.test.ts +121 -0
  86. package/src/plugins/builtin/market-heatmap/data.ts +296 -0
  87. package/src/plugins/builtin/market-heatmap/index.tsx +320 -0
  88. package/src/plugins/builtin/options/table.ts +5 -5
  89. package/src/plugins/builtin/plugin-groups.ts +2 -0
  90. package/src/plugins/builtin/portfolio-list/grid.tsx +197 -0
  91. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +41 -0
  92. package/src/plugins/builtin/portfolio-list/pane/index.tsx +78 -22
  93. package/src/plugins/ibkr/gateway/service/index.test.ts +22 -0
  94. package/src/plugins/ibkr/gateway/service/index.ts +6 -9
  95. package/src/plugins/ibkr/gateway/service/native-loader.ts +12 -0
  96. package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
  97. package/src/remote/app-host.tsx +77 -0
  98. package/src/remote/client.ts +34 -0
  99. package/src/remote/command-bar.ts +98 -0
  100. package/src/remote/controller-utils.ts +111 -0
  101. package/src/remote/controller.test.ts +365 -0
  102. package/src/remote/controller.ts +434 -0
  103. package/src/remote/endpoint.ts +41 -0
  104. package/src/remote/json-patch.test.ts +31 -0
  105. package/src/remote/json-patch.ts +89 -0
  106. package/src/remote/layout-helpers.ts +62 -0
  107. package/src/remote/resources.ts +186 -0
  108. package/src/remote/revision.ts +18 -0
  109. package/src/remote/schema.ts +143 -0
  110. package/src/remote/semantic-helpers.ts +161 -0
  111. package/src/remote/semantic-tree.tsx +112 -0
  112. package/src/remote/server.test.ts +50 -0
  113. package/src/remote/server.ts +121 -0
  114. package/src/remote/types.ts +119 -0
  115. package/src/renderers/electrobun/bun/index.ts +55 -1
  116. package/src/renderers/electrobun/shared/protocol.ts +11 -1
  117. package/src/renderers/electrobun/view/backend-rpc.ts +28 -1
  118. package/src/renderers/electrobun/view/build-assets.ts +1 -0
  119. package/src/renderers/electrobun/view/data-table/index.tsx +0 -8
  120. package/src/renderers/electrobun/view/data-table/row.tsx +2 -12
  121. package/src/renderers/electrobun/view/host/box.tsx +11 -1
  122. package/src/renderers/electrobun/view/host/mouse.test.ts +37 -0
  123. package/src/renderers/electrobun/view/host/mouse.ts +2 -0
  124. package/src/renderers/electrobun/view/host/style.ts +1 -0
  125. package/src/renderers/electrobun/view/main.tsx +5 -1
  126. package/src/renderers/electrobun/view/native-stubs/ibkr-native-loader.ts +3 -0
  127. package/src/renderers/electrobun/view/styles.css +7 -0
  128. package/src/renderers/opentui/chart-surface.test.tsx +101 -0
  129. package/src/renderers/opentui/chart-surface.tsx +15 -20
  130. package/src/renderers/opentui/native-smoke.ts +1 -0
  131. package/src/renderers/opentui/start.tsx +32 -0
  132. package/src/sources/yahoo-finance/http.ts +31 -4
  133. package/src/state/app/context/index.test.tsx +47 -0
  134. package/src/state/app/context/index.tsx +48 -18
  135. package/src/ui/host.tsx +1 -0
  136. package/src/ui/index.tsx +183 -7
  137. package/src/version.ts +1 -1
  138. package/src/plugins/builtin/holders/treemap-layout.ts +0 -231
@@ -351,6 +351,9 @@ export function focusPaneState(state: AppState, paneId: string): AppState {
351
351
  ...state,
352
352
  config: syncConfigActiveLayoutState(config, state.paneState, paneId, state.activePanel),
353
353
  focusedPaneId: paneId,
354
+ previousFocusedPaneId: state.focusedPaneId && state.focusedPaneId !== paneId
355
+ ? state.focusedPaneId
356
+ : state.previousFocusedPaneId,
354
357
  recentTickers,
355
358
  };
356
359
  }
@@ -386,6 +389,9 @@ export function withFocusedPane(
386
389
  paneState: nextPaneState,
387
390
  brokerAccounts: reconcileBrokerAccounts(syncedConfig, state.brokerAccounts),
388
391
  focusedPaneId,
392
+ previousFocusedPaneId: state.focusedPaneId && state.focusedPaneId !== focusedPaneId
393
+ ? state.focusedPaneId
394
+ : state.previousFocusedPaneId,
389
395
  activePanel,
390
396
  };
391
397
  }
@@ -108,7 +108,9 @@ export function appReducer(state: AppState, action: AppAction): AppState {
108
108
  return { ...state, recentTickers: nextRecentTickers(state.recentTickers, action.symbol) };
109
109
 
110
110
  case "SET_ACTIVE_PANEL": {
111
- const firstPane = getPanelFocusTarget(state.config.layout, action.panel);
111
+ const firstPane = action.preserveFocus
112
+ ? state.focusedPaneId
113
+ : getPanelFocusTarget(state.config.layout, action.panel);
112
114
  return withFocusedPane(state, state.config, {
113
115
  activePanel: action.panel,
114
116
  focusedPaneId: firstPane ?? state.focusedPaneId,
@@ -260,6 +262,25 @@ export function appReducer(state: AppState, action: AppAction): AppState {
260
262
  };
261
263
  }
262
264
 
265
+ case "REPLACE_PANE_STATE": {
266
+ const current = state.paneState[action.paneId] ?? {};
267
+ const nextState = action.state;
268
+ if (Object.is(current, nextState)) return state;
269
+ const paneState = {
270
+ ...state.paneState,
271
+ [action.paneId]: nextState,
272
+ };
273
+ const recentTickers = current.cursorSymbol !== nextState.cursorSymbol
274
+ ? nextRecentTickers(state.recentTickers, typeof nextState.cursorSymbol === "string" ? nextState.cursorSymbol : null)
275
+ : state.recentTickers;
276
+ return {
277
+ ...state,
278
+ config: syncConfigActiveLayoutState(state.config, paneState, state.focusedPaneId, state.activePanel),
279
+ paneState,
280
+ recentTickers,
281
+ };
282
+ }
283
+
263
284
  case "UPDATE_PLUGIN_PANE_STATE": {
264
285
  const current = state.paneState[action.paneId] ?? {};
265
286
  const currentPluginState = current.pluginState ?? {};
@@ -319,6 +340,7 @@ export function createInitialState(config: AppConfig, sessionSnapshot: AppSessio
319
340
  brokerAccounts: {},
320
341
  activePanel,
321
342
  focusedPaneId,
343
+ previousFocusedPaneId: null,
322
344
  paneState,
323
345
  recentTickers: config.recentTickers,
324
346
  commandBarOpen: false,
@@ -48,6 +48,7 @@ export interface AppState {
48
48
  brokerAccounts: Record<string, BrokerAccount[]>;
49
49
  activePanel: "left" | "right";
50
50
  focusedPaneId: string | null;
51
+ previousFocusedPaneId: string | null;
51
52
  paneState: Record<string, PaneRuntimeState>;
52
53
  recentTickers: string[];
53
54
  commandBarOpen: boolean;
@@ -76,7 +77,7 @@ export type AppAction =
76
77
  | { type: "MERGE_QUOTE"; symbol: string; quote: Quote }
77
78
  | { type: "HYDRATE_FINANCIALS"; financials: Map<string, TickerFinancials> }
78
79
  | { type: "TRACK_TICKER"; symbol: string | null }
79
- | { type: "SET_ACTIVE_PANEL"; panel: "left" | "right" }
80
+ | { type: "SET_ACTIVE_PANEL"; panel: "left" | "right"; preserveFocus?: boolean }
80
81
  | { type: "TOGGLE_COMMAND_BAR" }
81
82
  | {
82
83
  type: "SET_COMMAND_BAR";
@@ -104,7 +105,7 @@ export type AppAction =
104
105
  | { type: "PUSH_LAYOUT_HISTORY" }
105
106
  | { type: "UNDO_LAYOUT" }
106
107
  | { type: "REDO_LAYOUT" }
107
- | { type: "UPDATE_LAYOUT"; layout: LayoutConfig }
108
+ | { type: "UPDATE_LAYOUT"; layout: LayoutConfig; focusedPaneId?: string | null }
108
109
  | { type: "SWITCH_LAYOUT"; index: number }
109
110
  | { type: "NEW_LAYOUT"; name: string }
110
111
  | { type: "DELETE_LAYOUT"; index: number }
@@ -121,4 +122,5 @@ export type AppAction =
121
122
  key: string;
122
123
  value: unknown;
123
124
  }
125
+ | { type: "REPLACE_PANE_STATE"; paneId: string; state: PaneRuntimeState }
124
126
  | { type: "UPDATE_PANE_STATE"; paneId: string; patch: Partial<PaneRuntimeState> };
@@ -85,7 +85,7 @@ export function PickerRow({
85
85
  return (
86
86
  <Box
87
87
  flexDirection="column"
88
- onMouseMove={onFocus}
88
+ onMouseOver={onFocus}
89
89
  onMouseDown={(event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
90
90
  event?.stopPropagation?.();
91
91
  event?.preventDefault?.();
@@ -135,7 +135,7 @@ export function CheckboxRow({
135
135
  <Box
136
136
  flexDirection="column"
137
137
  backgroundColor={hovered ? hoverBg() : undefined}
138
- onMouseMove={() => {
138
+ onMouseOver={() => {
139
139
  setHovered(true);
140
140
  onFocus();
141
141
  }}
@@ -214,7 +214,7 @@ export function AlertsPane({ focused, width, height, close }: PaneProps) {
214
214
  alert: AlertRule,
215
215
  column: AlertColumn,
216
216
  _index: number,
217
- rowState: { selected: boolean; hovered: boolean },
217
+ rowState: { selected: boolean },
218
218
  ): DataTableCell => {
219
219
  const selectedColor = rowState.selected ? colors.selectedText : undefined;
220
220
  const actionMouseDown = (handler: () => void) => (
@@ -26,7 +26,7 @@ interface BuildoutCellContext {
26
26
  export function renderBuildoutCell(
27
27
  row: BuildoutRow,
28
28
  column: BuildoutColumn,
29
- rowState: { selected: boolean; hovered: boolean },
29
+ rowState: { selected: boolean },
30
30
  context: BuildoutCellContext,
31
31
  ): DataTableCell {
32
32
  const selectedColor = rowState.selected ? colors.selectedText : undefined;
@@ -314,7 +314,7 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
314
314
  row: BuildoutRow,
315
315
  column: BuildoutColumn,
316
316
  _index: number,
317
- rowState: { selected: boolean; hovered: boolean },
317
+ rowState: { selected: boolean },
318
318
  ) => renderBuildoutCell(row, column, rowState, {
319
319
  favoriteBusyKey,
320
320
  toggleFavorite,
@@ -1,6 +1,6 @@
1
1
  import type { CommandResultDef, GloomPluginContext } from "../../../types/plugin";
2
2
  import type { ChatChannel, ChatChannelState } from "../../../api-client";
3
- import type { AppConfig, PaneInstanceConfig } from "../../../types/config";
3
+ import type { AppConfig } from "../../../types/config";
4
4
  import { chatController } from "./controller";
5
5
  import { formatChannelLabel } from "./channel-labels";
6
6
 
@@ -25,14 +25,6 @@ export function getLastVisitedChatChannelId(config: { pluginConfig: Record<strin
25
25
  return normalizeChannelId(config.pluginConfig["gloomberb-cloud"]?.[LAST_VISITED_CHAT_CHANNEL_KEY] as string | undefined);
26
26
  }
27
27
 
28
- export function findChatPaneInstanceForChannel(config: AppConfig, channelId: string): PaneInstanceConfig | undefined {
29
- const normalizedChannelId = normalizeChannelId(channelId);
30
- return config.layout.instances.find((instance) => (
31
- instance.paneId === "chat"
32
- && normalizeChannelId(typeof instance.settings?.channelId === "string" ? instance.settings.channelId : null) === normalizedChannelId
33
- ));
34
- }
35
-
36
28
  export function getPreferredChatOpenChannelId(
37
29
  config: AppConfig,
38
30
  snapshot?: { channels: ChatChannel[]; channelStates: ChatChannelState[] },
@@ -80,17 +72,12 @@ export function hasOnlyDmUsernameArgs(value: string): boolean {
80
72
  return parts.length > 0 && parts.every((part) => CHAT_USERNAME_ARG.test(part));
81
73
  }
82
74
 
83
- function openChatChannelFromCommand(ctx: Pick<GloomPluginContext, "createPaneFromTemplate" | "focusPane" | "getConfig">, channelId: string): void {
84
- const existingInstance = findChatPaneInstanceForChannel(ctx.getConfig(), channelId);
85
- if (existingInstance) {
86
- ctx.focusPane(existingInstance.instanceId);
87
- return;
88
- }
75
+ function openChatChannelFromCommand(ctx: Pick<GloomPluginContext, "createPaneFromTemplate">, channelId: string): void {
89
76
  ctx.createPaneFromTemplate("new-chat-pane", { arg: channelId });
90
77
  }
91
78
 
92
- export function openDefaultChatFromCommand(
93
- ctx: Pick<GloomPluginContext, "createPaneFromTemplate" | "focusPane" | "getConfig">,
79
+ function openDefaultChatPane(
80
+ ctx: Pick<GloomPluginContext, "createPaneFromTemplate" | "getConfig">,
94
81
  ): void {
95
82
  const config = ctx.getConfig();
96
83
  openChatChannelFromCommand(ctx, getPreferredChatOpenChannelId(config, chatController.getSnapshot()));
@@ -98,7 +85,7 @@ export function openDefaultChatFromCommand(
98
85
 
99
86
  export async function openDmTargetFromCommand(ctx: GloomPluginContext, usernames: string[]): Promise<void> {
100
87
  if (usernames.length === 0) {
101
- openDefaultChatFromCommand(ctx);
88
+ openDefaultChatPane(ctx);
102
89
  return;
103
90
  }
104
91
  const channel = usernames.length === 1
@@ -1178,7 +1178,6 @@ describe("ChatContent", () => {
1178
1178
  user: { id: "u1", username: "vince", emailVerified: true },
1179
1179
  });
1180
1180
  const openedTemplates: Array<{ templateId: string; options?: { arg?: string } }> = [];
1181
- const focusedPanes: string[] = [];
1182
1181
  const state = createInitialState(createDefaultConfig("/tmp/gloomberb-chat"));
1183
1182
  state.config.disabledPlugins = [];
1184
1183
 
@@ -1186,9 +1185,6 @@ describe("ChatContent", () => {
1186
1185
  createPaneFromTemplate(templateId: string, options?: { arg?: string }) {
1187
1186
  openedTemplates.push({ templateId, options });
1188
1187
  },
1189
- focusPane(paneId: string) {
1190
- focusedPanes.push(paneId);
1191
- },
1192
1188
  });
1193
1189
 
1194
1190
  await act(async () => {
@@ -1230,9 +1226,7 @@ describe("ChatContent", () => {
1230
1226
  await setup().renderOnce();
1231
1227
  });
1232
1228
 
1233
- expect(openedTemplates).toEqual([]);
1234
- expect(focusedPanes).toHaveLength(1);
1235
- expect(focusedPanes[0]?.startsWith("chat:")).toBe(true);
1229
+ expect(openedTemplates).toEqual([{ templateId: "new-chat-pane", options: { arg: "everyone" } }]);
1236
1230
  });
1237
1231
 
1238
1232
  test("opens an unread direct-message channel from the status widget", async () => {
@@ -97,7 +97,7 @@ export const DesktopChatMessage = memo(function DesktopChatMessage({
97
97
  <Text
98
98
  fg={state.authorColor}
99
99
  attributes={state.authorAttributes}
100
- onMouseMove={() => onUserHover(msg.user)}
100
+ onMouseOver={() => onUserHover(msg.user)}
101
101
  onMouseOut={onUserHoverEnd}
102
102
  style={{ cursor: "default" }}
103
103
  >
@@ -58,7 +58,7 @@ export function ResponsiveTickerBadgeText({
58
58
  height={1}
59
59
  flexDirection="row"
60
60
  backgroundColor={blendHex(colors.panel, colors.positive, 0.24)}
61
- onMouseMove={() => {
61
+ onMouseOver={() => {
62
62
  if (user) onUserHover?.(user);
63
63
  }}
64
64
  onMouseOut={() => {
@@ -44,7 +44,7 @@ export function TerminalChatMessage({
44
44
  const messageRowProps = {
45
45
  width: contentWidth,
46
46
  backgroundColor: state.bgColor,
47
- onMouseMove: setHovered,
47
+ onMouseOver: setHovered,
48
48
  onMouseOut: clearHovered,
49
49
  };
50
50
 
@@ -80,7 +80,7 @@ export function TerminalChatMessage({
80
80
  <Text
81
81
  fg={state.authorColor}
82
82
  attributes={state.authorAttributes}
83
- onMouseMove={() => onUserHover(msg.user)}
83
+ onMouseOver={() => onUserHover(msg.user)}
84
84
  onMouseOut={onUserHoverEnd}
85
85
  >
86
86
  {msg.user.username ?? "anon"}
@@ -239,7 +239,7 @@ export function ChannelSidebar({
239
239
  flexDirection="row"
240
240
  backgroundColor={bg}
241
241
  onMouseDown={selectChannel}
242
- onMouseMove={() => setHoveredChannelId((current) => (current === channel.id ? current : channel.id))}
242
+ onMouseOver={() => setHoveredChannelId((current) => (current === channel.id ? current : channel.id))}
243
243
  onMouseOut={() => setHoveredChannelId((current) => (current === channel.id ? null : current))}
244
244
  style={{ cursor: "pointer" }}
245
245
  >
@@ -5,7 +5,6 @@ import { Box, Span, Text, TextAttributes, useUiCapabilities } from "../../../ui"
5
5
  import { usePluginAppActions } from "../../runtime";
6
6
  import { InlineAuthActions } from "../cloud/auth-actions";
7
7
  import {
8
- findChatPaneInstanceForChannel,
9
8
  getPreferredChatOpenChannelId,
10
9
  } from "./channels";
11
10
  import { chatController, type ChatController } from "./controller";
@@ -53,7 +52,7 @@ function CloudStatusIcon() {
53
52
  }
54
53
 
55
54
  export function ChatStatusWidget({ controller = chatController }: ChatStatusWidgetProps) {
56
- const { createPaneFromTemplate, focusPane } = usePluginAppActions();
55
+ const { createPaneFromTemplate } = usePluginAppActions();
57
56
  const config = useAppSelector((state) => state.config);
58
57
  const cloudPluginDisabled = config.disabledPlugins.includes("gloomberb-cloud");
59
58
  const initialSnapshot = controller.getSnapshot();
@@ -67,11 +66,6 @@ export function ChatStatusWidget({ controller = chatController }: ChatStatusWidg
67
66
  event?.preventDefault?.();
68
67
  event?.stopPropagation?.();
69
68
  const channelId = getPreferredChatOpenChannelId(config, snapshot);
70
- const existingInstance = findChatPaneInstanceForChannel(config, channelId);
71
- if (existingInstance) {
72
- focusPane(existingInstance.instanceId);
73
- return;
74
- }
75
69
  createPaneFromTemplate("new-chat-pane", { arg: channelId });
76
70
  };
77
71
 
@@ -98,7 +92,7 @@ export function ChatStatusWidget({ controller = chatController }: ChatStatusWidg
98
92
  <Box
99
93
  flexDirection="row"
100
94
  backgroundColor={hovered ? hoverBg() : undefined}
101
- onMouseMove={() => setHovered((current) => (current ? current : true))}
95
+ onMouseOver={() => setHovered((current) => (current ? current : true))}
102
96
  onMouseOut={() => setHovered((current) => (current ? false : current))}
103
97
  onMouseDown={openChat}
104
98
  >
@@ -22,7 +22,7 @@ export function InlineAuthActions({ showSignup = true }: { showSignup?: boolean
22
22
  <Box flexDirection="row">
23
23
  <Box
24
24
  backgroundColor={hoveredAction === "login" ? hoverBg() : undefined}
25
- onMouseMove={() => setHoveredAction((current) => (current === "login" ? current : "login"))}
25
+ onMouseOver={() => setHoveredAction((current) => (current === "login" ? current : "login"))}
26
26
  onMouseOut={() => setHoveredAction((current) => (current === "login" ? null : current))}
27
27
  onMouseDown={(event: any) => openAuthCommand(openCommandBar, "Log In", event)}
28
28
  >
@@ -33,7 +33,7 @@ export function InlineAuthActions({ showSignup = true }: { showSignup?: boolean
33
33
  <Text fg={colors.textDim}>/</Text>
34
34
  <Box
35
35
  backgroundColor={hoveredAction === "signup" ? hoverBg() : undefined}
36
- onMouseMove={() => setHoveredAction((current) => (current === "signup" ? current : "signup"))}
36
+ onMouseOver={() => setHoveredAction((current) => (current === "signup" ? current : "signup"))}
37
37
  onMouseOut={() => setHoveredAction((current) => (current === "signup" ? null : current))}
38
38
  onMouseDown={(event: any) => openAuthCommand(openCommandBar, "Sign Up", event)}
39
39
  >
@@ -14,10 +14,9 @@ import { registerTwitterFeedFeature } from "../cloud-tweets";
14
14
  import {
15
15
  buildDmCommandResults,
16
16
  formatChatPaneTitle,
17
- getLastVisitedChatChannelId,
17
+ getPreferredChatOpenChannelId,
18
18
  hasOnlyDmUsernameArgs,
19
19
  normalizeShortcutChannelId,
20
- openDefaultChatFromCommand,
21
20
  openDmTargetFromCommand,
22
21
  parseDmUsernames,
23
22
  } from "../chat/channels";
@@ -50,7 +49,9 @@ export function createGloomberbCloudPlugin({
50
49
  createInstance: async (context, options) => {
51
50
  const channelId = options?.arg
52
51
  ? await chatController.resolveRequiredChannelId(normalizeShortcutChannelId(options.arg))
53
- : await chatController.resolvePreferredChannelId(getLastVisitedChatChannelId(context.config));
52
+ : await chatController.resolvePreferredChannelId(
53
+ getPreferredChatOpenChannelId(context.config, chatController.getSnapshot()),
54
+ );
54
55
  const channel = chatController.getChannels().find((entry) => entry.id === channelId);
55
56
  return {
56
57
  placement: "floating",
@@ -144,16 +145,6 @@ export function createGloomberbCloudPlugin({
144
145
 
145
146
  registerTwitterFeedFeature(ctx);
146
147
 
147
- ctx.registerCommand({
148
- id: "open-chat",
149
- label: "Chat",
150
- description: "Open chat",
151
- keywords: ["chat", "message", "messages"],
152
- category: "navigation",
153
- shortcut: "CHAT",
154
- execute: () => openDefaultChatFromCommand(ctx),
155
- });
156
-
157
148
  ctx.registerCommand({
158
149
  id: "direct-message",
159
150
  label: "DM",
@@ -41,7 +41,7 @@ export function SymbolLabelCell({
41
41
  overflow="hidden"
42
42
  backgroundColor={hovered ? hoverBg() : undefined}
43
43
  style={{ cursor: "pointer" }}
44
- onMouseMove={() => onHover(symbol)}
44
+ onMouseOver={() => onHover(symbol)}
45
45
  onMouseOut={() => onLeave(symbol)}
46
46
  onMouseDown={(event: any) => handleSymbolMouseDown(event, openSymbol)}
47
47
  >
@@ -64,7 +64,6 @@ function DebugPane({ focused, width, height }: PaneProps) {
64
64
  const [filterSource, setFilterSource] = useState<string | null>(null);
65
65
  const [autoScroll, setAutoScroll] = useState(true);
66
66
  const [selectedIdx, setSelectedIdx] = useState(-1);
67
- const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
68
67
  const [showDetail, setShowDetail] = useState(false);
69
68
  const sourcesRef = useRef<string[]>(debugLog.getSources());
70
69
 
@@ -261,8 +260,8 @@ function DebugPane({ focused, width, height }: PaneProps) {
261
260
  {visibleEntries.map((entry, i) => {
262
261
  const globalIdx = viewStart + i;
263
262
  const isSelected = globalIdx === selectedIdx;
264
- const isHovered = globalIdx === hoveredIdx && !isSelected;
265
- const bgColor = isSelected ? colors.selected : isHovered ? hoverBg() : undefined;
263
+ const bgColor = isSelected ? colors.selected : undefined;
264
+ const rowHoverBg = isSelected ? undefined : hoverBg();
266
265
  const ts = formatTimestamp(entry.timestamp);
267
266
  const lvl = LEVEL_LABELS[entry.level];
268
267
  const sourceTag = entry.source;
@@ -278,7 +277,7 @@ function DebugPane({ focused, width, height }: PaneProps) {
278
277
  width={contentWidth}
279
278
  flexDirection="row"
280
279
  backgroundColor={bgColor}
281
- onMouseMove={() => setHoveredIdx((current) => (current === globalIdx ? current : globalIdx))}
280
+ hoverBackgroundColor={rowHoverBg}
282
281
  onMouseDown={() => { setSelectedIdx(globalIdx); setAutoScroll(false); }}
283
282
  >
284
283
  <Text fg={colors.textMuted}> {ts} </Text>
@@ -63,7 +63,7 @@ export function ActionButton({
63
63
  return (
64
64
  <Box
65
65
  backgroundColor={hovered ? hoverBg() : colors.panel}
66
- onMouseMove={() => onHover(id)}
66
+ onMouseOver={() => onHover(id)}
67
67
  onMouseOut={() => onHover(null)}
68
68
  onMouseDown={(event: any) => {
69
69
  event.stopPropagation?.();