gloomberb 0.6.1 → 0.7.1

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 (208) hide show
  1. package/package.json +1 -1
  2. package/patches/@opentui%2Fcore@0.1.90.patch +48 -24
  3. package/src/app.tsx +158 -57
  4. package/src/capabilities/factories.ts +3 -0
  5. package/src/cli/registry.test.ts +0 -1
  6. package/src/components/chart/chart-controller.ts +0 -1
  7. package/src/components/chart/chart-data.ts +1 -31
  8. package/src/components/chart/chart-renderer.ts +94 -12
  9. package/src/components/chart/chart-resolution.test.ts +0 -36
  10. package/src/components/chart/chart-resolution.ts +0 -27
  11. package/src/components/chart/chart.test.ts +60 -45
  12. package/src/components/chart/comparison-chart-data.test.ts +1 -5
  13. package/src/components/chart/comparison-chart-data.ts +1 -4
  14. package/src/components/chart/comparison-chart-renderer.test.ts +26 -0
  15. package/src/components/chart/comparison-chart-renderer.ts +13 -6
  16. package/src/components/chart/comparison-stock-chart.tsx +63 -39
  17. package/src/components/chart/price-axis-labels.tsx +114 -0
  18. package/src/components/chart/static-chart-surface.tsx +30 -4
  19. package/src/components/chart/stock-chart.test.ts +0 -16
  20. package/src/components/chart/stock-chart.tsx +69 -41
  21. package/src/components/chart/time-axis-label.tsx +120 -0
  22. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -28
  23. package/src/components/command-bar/command-bar.test.tsx +182 -36
  24. package/src/components/command-bar/command-bar.tsx +943 -1016
  25. package/src/components/command-bar/command-registry.ts +0 -10
  26. package/src/components/command-bar/confirm-body.tsx +51 -0
  27. package/src/components/command-bar/helpers.ts +2 -5
  28. package/src/components/command-bar/list-model.ts +76 -0
  29. package/src/components/command-bar/theme-picker.tsx +254 -0
  30. package/src/components/command-bar/view-model.test.ts +4 -4
  31. package/src/components/command-bar/view-model.ts +1 -20
  32. package/src/components/command-bar/workflow-ops.ts +4 -0
  33. package/src/components/command-bar/workflow-types.ts +1 -2
  34. package/src/components/data-table-stack-view.test.tsx +39 -4
  35. package/src/components/data-table-stack-view.tsx +11 -104
  36. package/src/components/data-table-view.tsx +43 -79
  37. package/src/components/feed-data-table-stack-view.tsx +10 -17
  38. package/src/components/feed-stack-controller.ts +39 -0
  39. package/src/components/index.ts +5 -1
  40. package/src/components/layout/detached-pane-shell.tsx +29 -10
  41. package/src/components/layout/floating-pane.tsx +35 -4
  42. package/src/components/layout/header.tsx +12 -7
  43. package/src/components/layout/pane-content.test.tsx +64 -0
  44. package/src/components/layout/pane-content.tsx +2 -0
  45. package/src/components/layout/pane-cycle-order.test.ts +30 -0
  46. package/src/components/layout/pane-cycle-order.ts +32 -0
  47. package/src/components/layout/pane-footer.tsx +11 -4
  48. package/src/components/layout/pane-header.tsx +5 -5
  49. package/src/components/layout/pane-sizing.ts +9 -2
  50. package/src/components/layout/pane-title.ts +4 -1
  51. package/src/components/layout/pane.tsx +11 -5
  52. package/src/components/layout/shell.test.tsx +264 -22
  53. package/src/components/layout/shell.tsx +46 -8
  54. package/src/components/layout/status-bar.tsx +2 -0
  55. package/src/components/onboarding/onboarding-wizard.tsx +1 -2
  56. package/src/components/pane-settings-dialog.tsx +12 -4
  57. package/src/components/pane-template-wizard.test.tsx +3 -4
  58. package/src/components/speedometer-gauge.test.tsx +45 -0
  59. package/src/components/speedometer-gauge.tsx +562 -0
  60. package/src/components/table-view-shared.tsx +218 -0
  61. package/src/components/ticker-list-table-view.tsx +57 -73
  62. package/src/components/ticker-list-table.test.tsx +82 -1
  63. package/src/components/ticker-list-table.tsx +22 -9
  64. package/src/components/ui/confirm-dialog.tsx +66 -0
  65. package/src/components/ui/data-table.tsx +30 -19
  66. package/src/components/ui/frame.tsx +2 -0
  67. package/src/components/ui/index.ts +0 -1
  68. package/src/components/ui/page-stack-view.tsx +39 -10
  69. package/src/components/ui/table-layout.test.ts +42 -0
  70. package/src/components/ui/table-layout.ts +49 -4
  71. package/src/components/ui/tabs.tsx +34 -1
  72. package/src/components/ui/ui.test.tsx +168 -21
  73. package/src/core/state/app-state.ts +10 -17
  74. package/src/market-data/coordinator.test.ts +76 -1
  75. package/src/market-data/coordinator.ts +218 -49
  76. package/src/news/aggregator.test.ts +49 -1
  77. package/src/news/aggregator.ts +48 -3
  78. package/src/news/hooks.ts +6 -2
  79. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +46 -16
  80. package/src/plugins/builtin/ai/providers.ts +0 -17
  81. package/src/plugins/builtin/ai/screener-pane.test.tsx +3 -4
  82. package/src/plugins/builtin/ai/screener-pane.tsx +3 -5
  83. package/src/plugins/builtin/analytics/index.tsx +1 -18
  84. package/src/plugins/builtin/ask-ai.test.tsx +89 -2
  85. package/src/plugins/builtin/broker-manager/index.test.tsx +2 -2
  86. package/src/plugins/builtin/broker-manager/index.tsx +19 -36
  87. package/src/plugins/builtin/chat-controller.test.ts +188 -1
  88. package/src/plugins/builtin/chat-controller.ts +537 -220
  89. package/src/plugins/builtin/chat.test.tsx +431 -13
  90. package/src/plugins/builtin/chat.tsx +1134 -227
  91. package/src/plugins/builtin/cloud-tweets.tsx +1 -1
  92. package/src/plugins/builtin/comparison-chart.test.tsx +53 -2
  93. package/src/plugins/builtin/comparison-chart.tsx +11 -5
  94. package/src/plugins/builtin/correlation/index.tsx +1 -6
  95. package/src/plugins/builtin/debug.tsx +1 -3
  96. package/src/plugins/builtin/econ/index.tsx +1 -2
  97. package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +72 -0
  98. package/src/plugins/builtin/fear-greed/fear-greed-data.ts +366 -0
  99. package/src/plugins/builtin/fear-greed/index.tsx +587 -0
  100. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  101. package/src/plugins/builtin/help.test.tsx +2 -2
  102. package/src/plugins/builtin/help.tsx +2 -2
  103. package/src/plugins/builtin/holders/index.test.ts +13 -1
  104. package/src/plugins/builtin/holders/index.tsx +64 -29
  105. package/src/plugins/builtin/insider/index.tsx +22 -89
  106. package/src/plugins/builtin/market-movers/index.tsx +13 -19
  107. package/src/plugins/builtin/market-movers/screener.test.ts +39 -1
  108. package/src/plugins/builtin/market-movers/screener.ts +70 -17
  109. package/src/plugins/builtin/news-wire/breaking-pane.tsx +3 -2
  110. package/src/plugins/builtin/news-wire/index.tsx +41 -2
  111. package/src/plugins/builtin/news-wire/industry-pane.tsx +4 -18
  112. package/src/plugins/builtin/news-wire/news-detail-view.tsx +46 -4
  113. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +3 -2
  114. package/src/plugins/builtin/news-wire/news-table.tsx +11 -11
  115. package/src/plugins/builtin/news.tsx +4 -16
  116. package/src/plugins/builtin/notes.tsx +19 -55
  117. package/src/plugins/builtin/options.test.tsx +58 -2
  118. package/src/plugins/builtin/options.tsx +110 -65
  119. package/src/plugins/builtin/plugin-groups.ts +2 -0
  120. package/src/plugins/builtin/portfolio-list/cli/commands.ts +1 -1
  121. package/src/plugins/builtin/portfolio-list/pane.tsx +16 -39
  122. package/src/plugins/builtin/research/index.test.ts +94 -0
  123. package/src/plugins/builtin/research/index.tsx +164 -33
  124. package/src/plugins/builtin/sec-filing-display.tsx +84 -0
  125. package/src/plugins/builtin/sec.tsx +13 -66
  126. package/src/plugins/builtin/sectors/index.tsx +1 -1
  127. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -1
  128. package/src/plugins/builtin/ticker-detail/index.tsx +0 -1
  129. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -1
  130. package/src/plugins/builtin/ticker-detail/pane.tsx +28 -22
  131. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +3 -4
  132. package/src/plugins/builtin/ticker-detail.test.tsx +38 -0
  133. package/src/plugins/builtin/world-indices/index.tsx +1 -1
  134. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  135. package/src/plugins/ibkr/broker-adapter.ts +1 -3
  136. package/src/plugins/ibkr/gateway-service-native.ts +1 -5
  137. package/src/plugins/ibkr/gateway-service.ts +1 -1
  138. package/src/plugins/ibkr/index.test.tsx +0 -2
  139. package/src/plugins/ibkr/trade-tab.test.tsx +3 -4
  140. package/src/plugins/pane-manager.ts +0 -13
  141. package/src/plugins/plugin-runtime.test.tsx +40 -6
  142. package/src/plugins/plugin-runtime.tsx +20 -10
  143. package/src/plugins/prediction-markets/cli.ts +0 -1
  144. package/src/plugins/prediction-markets/controller-data.ts +0 -2
  145. package/src/plugins/prediction-markets/controller.ts +0 -15
  146. package/src/plugins/prediction-markets/detail/pane.tsx +1 -0
  147. package/src/plugins/prediction-markets/pane.test.tsx +3 -11
  148. package/src/plugins/prediction-markets/test-helpers.tsx +1 -1
  149. package/src/plugins/registry.ts +13 -42
  150. package/src/plugins/ticker-navigation.test.ts +66 -0
  151. package/src/plugins/ticker-navigation.ts +44 -0
  152. package/src/react/input.ts +2 -1
  153. package/src/renderers/electrobun/bun/index.ts +32 -11
  154. package/src/renderers/electrobun/bun/window-focus.ts +37 -0
  155. package/src/renderers/electrobun/shared/protocol.ts +7 -1
  156. package/src/renderers/electrobun/view/app-services.ts +88 -31
  157. package/src/renderers/electrobun/view/backend-rpc.ts +15 -0
  158. package/src/renderers/electrobun/view/data-table.tsx +138 -101
  159. package/src/renderers/electrobun/view/desktop-controls.tsx +42 -20
  160. package/src/renderers/electrobun/view/desktop-window-bridge.ts +12 -1
  161. package/src/renderers/electrobun/view/dialog-host.tsx +13 -1
  162. package/src/renderers/electrobun/view/input-host.tsx +74 -10
  163. package/src/renderers/electrobun/view/key-event.ts +1 -1
  164. package/src/renderers/electrobun/view/main.tsx +18 -1
  165. package/src/renderers/electrobun/view/styles.css +329 -0
  166. package/src/renderers/electrobun/view/toast-host.tsx +29 -3
  167. package/src/renderers/electrobun/view/ui-host.tsx +42 -0
  168. package/src/renderers/opentui/chart-surface.tsx +179 -0
  169. package/src/renderers/opentui/input-host.tsx +1 -1
  170. package/src/renderers/opentui/start.tsx +48 -30
  171. package/src/renderers/opentui/test-utils.tsx +17 -0
  172. package/src/renderers/opentui/ui-host.tsx +5 -2
  173. package/src/sources/gloomberb-cloud.test.ts +86 -1
  174. package/src/sources/gloomberb-cloud.ts +117 -21
  175. package/src/sources/provider-router.test.ts +122 -0
  176. package/src/sources/provider-router.ts +198 -15
  177. package/src/sources/yahoo-finance.test.ts +44 -0
  178. package/src/sources/yahoo-finance.ts +6 -0
  179. package/src/state/app-bootstrap.ts +20 -7
  180. package/src/state/app-context-selector.test.tsx +189 -4
  181. package/src/state/app-context.test.ts +65 -0
  182. package/src/state/app-context.tsx +48 -16
  183. package/src/state/use-inline-tickers.test.tsx +73 -0
  184. package/src/state/use-inline-tickers.ts +19 -10
  185. package/src/test-support/plugin-runtime.ts +3 -2
  186. package/src/theme/colors.test.ts +75 -0
  187. package/src/theme/colors.ts +76 -6
  188. package/src/theme/theme-context.tsx +26 -0
  189. package/src/theme/themes.test.ts +23 -1
  190. package/src/theme/themes.ts +115 -0
  191. package/src/types/broker.ts +1 -1
  192. package/src/types/capability-route-source.ts +1 -0
  193. package/src/types/data-provider.ts +15 -2
  194. package/src/types/desktop-window.ts +6 -0
  195. package/src/types/financials.ts +2 -0
  196. package/src/types/plugin.ts +1 -3
  197. package/src/ui/host.tsx +7 -0
  198. package/src/utils/api-client.test.ts +32 -0
  199. package/src/utils/api-client.ts +119 -18
  200. package/src/utils/back-navigation.ts +24 -1
  201. package/src/utils/double-escape-close.test.ts +33 -0
  202. package/src/utils/double-escape-close.ts +40 -0
  203. package/src/utils/throttled-fetch.test.ts +22 -0
  204. package/src/utils/throttled-fetch.ts +3 -1
  205. package/src/utils/ticker-search.ts +53 -9
  206. package/src/version.ts +1 -1
  207. package/src/plugins/builtin/news-wire/feed-pane.tsx +0 -21
  208. package/src/plugins/builtin/news-wire/top-pane.tsx +0 -21
@@ -0,0 +1,64 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { act, type Dispatch } from "react";
3
+ import { testRender } from "../../renderers/opentui/test-utils";
4
+ import { AppProvider, useAppDispatch, type AppAction } from "../../state/app-context";
5
+ import { applyTheme, colors } from "../../theme/colors";
6
+ import { getTheme } from "../../theme/themes";
7
+ import { createDefaultConfig } from "../../types/config";
8
+ import { PaneContent } from "./pane-content";
9
+
10
+ let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
11
+ let capturedDispatch: Dispatch<AppAction> | null = null;
12
+
13
+ function DispatchCapture() {
14
+ capturedDispatch = useAppDispatch();
15
+ return null;
16
+ }
17
+
18
+ function ThemeColorProbe() {
19
+ return <text>{colors.textBright}</text>;
20
+ }
21
+
22
+ describe("PaneContent", () => {
23
+ afterEach(() => {
24
+ testSetup?.renderer.destroy();
25
+ testSetup = undefined;
26
+ capturedDispatch = null;
27
+ applyTheme("amber");
28
+ });
29
+
30
+ test("rerenders memoized pane bodies when the theme preview changes", async () => {
31
+ const config = {
32
+ ...createDefaultConfig("/tmp/gloomberb-test"),
33
+ theme: "amber",
34
+ };
35
+
36
+ testSetup = await testRender(
37
+ <AppProvider config={config}>
38
+ <DispatchCapture />
39
+ <PaneContent
40
+ component={ThemeColorProbe}
41
+ paneId="theme-preview:test"
42
+ paneType="test"
43
+ focused
44
+ width={24}
45
+ height={4}
46
+ />
47
+ </AppProvider>,
48
+ { width: 32, height: 6 },
49
+ );
50
+
51
+ await testSetup.renderOnce();
52
+ expect(testSetup.captureCharFrame()).toContain(getTheme("amber").textBright);
53
+
54
+ await act(async () => {
55
+ capturedDispatch?.({ type: "PREVIEW_THEME", theme: "green" });
56
+ await testSetup!.renderOnce();
57
+ });
58
+ await testSetup.renderOnce();
59
+
60
+ const frame = testSetup.captureCharFrame();
61
+ expect(frame).toContain(getTheme("green").textBright);
62
+ expect(frame).not.toContain(getTheme("amber").textBright);
63
+ });
64
+ });
@@ -1,6 +1,7 @@
1
1
  import { memo, useCallback } from "react";
2
2
  import { PaneInstanceProvider } from "../../state/app-context";
3
3
  import { PaneKeyboardScrollController } from "../../state/pane-scroll-registry";
4
+ import { useThemeColors } from "../../theme/theme-context";
4
5
  import type { PaneDef } from "../../types/plugin";
5
6
 
6
7
  interface PaneContentProps {
@@ -22,6 +23,7 @@ export const PaneContent = memo(function PaneContent({
22
23
  height,
23
24
  onClose,
24
25
  }: PaneContentProps) {
26
+ useThemeColors();
25
27
  const close = useCallback(() => {
26
28
  onClose?.(paneId);
27
29
  }, [onClose, paneId]);
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { PluginRegistry } from "../../plugins/registry";
3
+ import { createDefaultConfig } from "../../types/config";
4
+ import { getVisiblePaneCycleOrder } from "./pane-cycle-order";
5
+
6
+ function createRegistry(options: {
7
+ paneIds: readonly string[];
8
+ disabledPaneIds?: Record<string, readonly string[]>;
9
+ }): PluginRegistry {
10
+ return {
11
+ panes: new Map(options.paneIds.map((paneId) => [paneId, {}])),
12
+ getPluginPaneIds: (pluginId: string) => [...(options.disabledPaneIds?.[pluginId] ?? [])],
13
+ } as unknown as PluginRegistry;
14
+ }
15
+
16
+ describe("getVisiblePaneCycleOrder", () => {
17
+ test("skips disabled and unregistered panes when cycling focus", () => {
18
+ const config = createDefaultConfig("/tmp/gloomberb-pane-cycle-order");
19
+ const registry = createRegistry({
20
+ paneIds: ["portfolio-list", "chat", "ticker-detail"],
21
+ disabledPaneIds: { chat: ["chat"] },
22
+ });
23
+
24
+ expect(getVisiblePaneCycleOrder(config.layout, registry, ["chat"])).toEqual([
25
+ "portfolio-list:main",
26
+ "ticker-detail:main",
27
+ "ticker-detail:nvda",
28
+ ]);
29
+ });
30
+ });
@@ -0,0 +1,32 @@
1
+ import { getDockedPaneIds } from "../../plugins/pane-manager";
2
+ import type { PluginRegistry } from "../../plugins/registry";
3
+ import { findPaneInstance, type LayoutConfig } from "../../types/config";
4
+
5
+ function disabledPaneTypes(pluginRegistry: PluginRegistry, disabledPlugins: readonly string[]): Set<string> {
6
+ const paneTypes = new Set<string>();
7
+ for (const pluginId of disabledPlugins) {
8
+ for (const paneId of pluginRegistry.getPluginPaneIds(pluginId)) {
9
+ paneTypes.add(paneId);
10
+ }
11
+ }
12
+ return paneTypes;
13
+ }
14
+
15
+ export function getVisiblePaneCycleOrder(
16
+ layout: LayoutConfig,
17
+ pluginRegistry: PluginRegistry,
18
+ disabledPlugins: readonly string[],
19
+ ): string[] {
20
+ const disabledTypes = disabledPaneTypes(pluginRegistry, disabledPlugins);
21
+ const isVisiblePane = (instanceId: string) => {
22
+ const instance = findPaneInstance(layout, instanceId);
23
+ return !!instance
24
+ && !disabledTypes.has(instance.paneId)
25
+ && pluginRegistry.panes.has(instance.paneId);
26
+ };
27
+
28
+ return [
29
+ ...getDockedPaneIds(layout),
30
+ ...layout.floating.map((entry) => entry.instanceId),
31
+ ].filter(isVisiblePane);
32
+ }
@@ -357,17 +357,24 @@ export function PaneFooterBar({
357
357
  focused,
358
358
  width = 0,
359
359
  reserveRight = 0,
360
+ showBorder = false,
360
361
  }: {
361
362
  footer?: CombinedPaneFooter | null;
362
363
  focused: boolean;
363
364
  width?: number;
364
365
  reserveRight?: number;
366
+ showBorder?: boolean;
365
367
  }) {
366
368
  const { nativePaneChrome } = useUiCapabilities();
367
369
  const resolvedFooter = footer ?? EMPTY_FOOTER;
368
370
  const empty = !hasPaneFooterContent(resolvedFooter);
369
371
  const borderColor = focused ? colors.borderFocused : colors.border;
370
372
  const topBorderColor = colors.border;
373
+ const nativeBackgroundColor = empty
374
+ ? "transparent"
375
+ : focused
376
+ ? blendHex(colors.bg, colors.borderFocused, 0.06)
377
+ : blendHex(colors.panel, colors.border, 0.12);
371
378
  const reservedRight = Math.max(0, reserveRight);
372
379
  const rightPadding = reservedRight > 0 ? reservedRight : 1;
373
380
 
@@ -385,7 +392,7 @@ export function PaneFooterBar({
385
392
  style={{
386
393
  "--pane-footer-border-color": empty ? "transparent" : topBorderColor,
387
394
  borderTop: `1px solid ${empty ? "transparent" : topBorderColor}`,
388
- backgroundColor: empty ? "transparent" : focused ? "rgba(84, 201, 159, 0.05)" : "rgba(20, 25, 30, 0.55)",
395
+ backgroundColor: nativeBackgroundColor,
389
396
  boxShadow: empty ? "none" : "inset 0 1px 0 rgba(255,255,255,0.03)",
390
397
  }}
391
398
  >
@@ -399,15 +406,15 @@ export function PaneFooterBar({
399
406
  );
400
407
  }
401
408
 
402
- if (focused) {
409
+ if (focused || showBorder) {
403
410
  const contentWidth = Math.max(0, Math.floor(width) - 1 - reservedRight - (reservedRight > 0 ? 0 : 1));
404
411
  return (
405
- <Box height={1} width={width} flexDirection="row" data-gloom-role="pane-footer" data-focused="true" data-empty={empty ? "true" : "false"}>
412
+ <Box height={1} width={width} flexDirection="row" data-gloom-role="pane-footer" data-focused={focused ? "true" : "false"} data-empty={empty ? "true" : "false"}>
406
413
  <Text fg={borderColor} selectable={false}>└</Text>
407
414
  <Box width={contentWidth} height={1} overflow="hidden">
408
415
  {empty
409
416
  ? <Text fg={borderColor} selectable={false}>{"─".repeat(contentWidth)}</Text>
410
- : <FooterContent footer={resolvedFooter} focused width={contentWidth} />}
417
+ : <FooterContent footer={resolvedFooter} focused={focused} width={contentWidth} />}
411
418
  </Box>
412
419
  {reservedRight === 0 && <Text fg={borderColor} selectable={false}>┘</Text>}
413
420
  </Box>
@@ -108,7 +108,6 @@ export function PaneHeader({
108
108
  const backgroundColor = floating ? floatingPaneTitleBg(focused) : paneTitleBg(focused);
109
109
  const actionText = showActions ? PANE_HEADER_ACTION : " ";
110
110
  const closeText = floating ? PANE_HEADER_CLOSE : "";
111
- const bc = colors.borderFocused;
112
111
  const textColor = paneTitleText(focused, floating);
113
112
 
114
113
  if (nativePaneChrome) {
@@ -184,9 +183,10 @@ export function PaneHeader({
184
183
  );
185
184
  }
186
185
 
187
- if (focused) {
186
+ if (focused || floating) {
188
187
  // Build: ┌─:: Title ─────────── ... x─┐
189
188
  // Reserve 2 for corners, 1 for ─ after ┌, 1 for ─ before ┐
189
+ const borderColor = focused ? colors.borderFocused : colors.border;
190
190
  const innerWidth = Math.max(0, width - 4);
191
191
  const contentWidth = PANE_HEADER_GRIP.length + closeText.length + actionText.length;
192
192
  const titleWidth = Math.max(0, innerWidth - contentWidth);
@@ -196,9 +196,9 @@ export function PaneHeader({
196
196
 
197
197
  return (
198
198
  <Box height={PANE_HEADER_HEIGHT} width={width} backgroundColor={backgroundColor} flexDirection="row">
199
- <Text fg={bc} selectable={false}>{"┌─"}</Text>
199
+ <Text fg={borderColor} selectable={false}>{"┌─"}</Text>
200
200
  <Text fg={textColor} selectable={false}>{`${PANE_HEADER_GRIP}${clippedTitle}`}</Text>
201
- <Text fg={bc} selectable={false}>{fill}</Text>
201
+ <Text fg={borderColor} selectable={false}>{fill}</Text>
202
202
  <TerminalPaneButton
203
203
  text={actionText}
204
204
  fg={textColor}
@@ -213,7 +213,7 @@ export function PaneHeader({
213
213
  onMouseDown={onCloseMouseDown}
214
214
  />
215
215
  )}
216
- <Text fg={bc} selectable={false}>{"─┐"}</Text>
216
+ <Text fg={borderColor} selectable={false}>{"─┐"}</Text>
217
217
  </Box>
218
218
  );
219
219
  }
@@ -2,14 +2,21 @@
2
2
  const PANE_HEADER_ROWS = 1;
3
3
  const PANE_FOOTER_ROWS = 1;
4
4
 
5
+ export const NATIVE_PANE_BODY_LAYOUT_PROPS = {
6
+ flexGrow: 1,
7
+ flexBasis: 0,
8
+ minHeight: 0,
9
+ } as const;
10
+
5
11
  export function shouldReservePaneFooter(nativePaneChrome: boolean | undefined, showFooter: boolean): boolean {
6
12
  return !nativePaneChrome || showFooter;
7
13
  }
8
14
 
9
15
  export function getPaneBodyHeight(height: number, reserveFooter = true): number {
10
- return Math.max(1, height - PANE_HEADER_ROWS - (reserveFooter ? PANE_FOOTER_ROWS : 0));
16
+ const cellHeight = Math.max(1, Math.floor(height));
17
+ return Math.max(1, cellHeight - PANE_HEADER_ROWS - (reserveFooter ? PANE_FOOTER_ROWS : 0));
11
18
  }
12
19
 
13
20
  export function getPaneBodyWidth(width: number): number {
14
- return Math.max(1, width);
21
+ return Math.max(1, Math.floor(width));
15
22
  }
@@ -8,7 +8,10 @@ export function getPaneDisplayTitle(
8
8
  paneDef: PaneDef,
9
9
  ): string {
10
10
  if (instance.paneId === "chat") {
11
- return instance.title ?? "#everyone";
11
+ const channelId = typeof instance.settings?.channelId === "string" && instance.settings.channelId.trim()
12
+ ? instance.settings.channelId.trim()
13
+ : "everyone";
14
+ return instance.title ?? `#${channelId}`;
12
15
  }
13
16
 
14
17
  if (instance.paneId === "ticker-detail") {
@@ -3,7 +3,7 @@ import type { ReactNode } from "react";
3
3
  import { colors, paneBg } from "../../theme/colors";
4
4
  import { PaneHeader } from "./pane-header";
5
5
  import { hasPaneFooterContent, PaneFooterBar, type CombinedPaneFooter } from "./pane-footer";
6
- import { getPaneBodyHeight, shouldReservePaneFooter } from "./pane-sizing";
6
+ import { getPaneBodyHeight, NATIVE_PANE_BODY_LAYOUT_PROPS, shouldReservePaneFooter } from "./pane-sizing";
7
7
 
8
8
  interface PaneWrapperProps {
9
9
  paneId?: string;
@@ -47,8 +47,15 @@ export function PaneWrapper({
47
47
  const showFooter = hasPaneFooterContent(footer);
48
48
  const reserveFooter = shouldReservePaneFooter(nativePaneChrome, showFooter);
49
49
  const bodyHeight = typeof height === "number"
50
- ? title ? getPaneBodyHeight(height, reserveFooter) : height
50
+ ? title ? getPaneBodyHeight(height, reserveFooter) : Math.max(1, Math.floor(height))
51
51
  : undefined;
52
+ const bodyLayoutProps = nativePaneChrome
53
+ ? NATIVE_PANE_BODY_LAYOUT_PROPS
54
+ : {
55
+ height: bodyHeight,
56
+ flexGrow: bodyHeight == null ? 1 : 0,
57
+ flexBasis: bodyHeight == null ? 0 : undefined,
58
+ };
52
59
 
53
60
  return (
54
61
  <Box
@@ -82,10 +89,9 @@ export function PaneWrapper({
82
89
  />
83
90
  )}
84
91
  <Box
85
- height={bodyHeight}
86
- flexGrow={bodyHeight == null ? 1 : 0}
87
- flexBasis={bodyHeight == null ? 0 : undefined}
92
+ {...bodyLayoutProps}
88
93
  overflow="hidden"
94
+ backgroundColor={bg}
89
95
  >
90
96
  {children}
91
97
  </Box>