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.
- package/package.json +1 -1
- package/patches/@opentui%2Fcore@0.1.90.patch +48 -24
- package/src/app.tsx +158 -57
- package/src/capabilities/factories.ts +3 -0
- package/src/cli/registry.test.ts +0 -1
- package/src/components/chart/chart-controller.ts +0 -1
- package/src/components/chart/chart-data.ts +1 -31
- package/src/components/chart/chart-renderer.ts +94 -12
- package/src/components/chart/chart-resolution.test.ts +0 -36
- package/src/components/chart/chart-resolution.ts +0 -27
- package/src/components/chart/chart.test.ts +60 -45
- package/src/components/chart/comparison-chart-data.test.ts +1 -5
- package/src/components/chart/comparison-chart-data.ts +1 -4
- package/src/components/chart/comparison-chart-renderer.test.ts +26 -0
- package/src/components/chart/comparison-chart-renderer.ts +13 -6
- package/src/components/chart/comparison-stock-chart.tsx +63 -39
- package/src/components/chart/price-axis-labels.tsx +114 -0
- package/src/components/chart/static-chart-surface.tsx +30 -4
- package/src/components/chart/stock-chart.test.ts +0 -16
- package/src/components/chart/stock-chart.tsx +69 -41
- package/src/components/chart/time-axis-label.tsx +120 -0
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +0 -28
- package/src/components/command-bar/command-bar.test.tsx +182 -36
- package/src/components/command-bar/command-bar.tsx +943 -1016
- package/src/components/command-bar/command-registry.ts +0 -10
- package/src/components/command-bar/confirm-body.tsx +51 -0
- package/src/components/command-bar/helpers.ts +2 -5
- package/src/components/command-bar/list-model.ts +76 -0
- package/src/components/command-bar/theme-picker.tsx +254 -0
- package/src/components/command-bar/view-model.test.ts +4 -4
- package/src/components/command-bar/view-model.ts +1 -20
- package/src/components/command-bar/workflow-ops.ts +4 -0
- package/src/components/command-bar/workflow-types.ts +1 -2
- package/src/components/data-table-stack-view.test.tsx +39 -4
- package/src/components/data-table-stack-view.tsx +11 -104
- package/src/components/data-table-view.tsx +43 -79
- package/src/components/feed-data-table-stack-view.tsx +10 -17
- package/src/components/feed-stack-controller.ts +39 -0
- package/src/components/index.ts +5 -1
- package/src/components/layout/detached-pane-shell.tsx +29 -10
- package/src/components/layout/floating-pane.tsx +35 -4
- package/src/components/layout/header.tsx +12 -7
- package/src/components/layout/pane-content.test.tsx +64 -0
- package/src/components/layout/pane-content.tsx +2 -0
- package/src/components/layout/pane-cycle-order.test.ts +30 -0
- package/src/components/layout/pane-cycle-order.ts +32 -0
- package/src/components/layout/pane-footer.tsx +11 -4
- package/src/components/layout/pane-header.tsx +5 -5
- package/src/components/layout/pane-sizing.ts +9 -2
- package/src/components/layout/pane-title.ts +4 -1
- package/src/components/layout/pane.tsx +11 -5
- package/src/components/layout/shell.test.tsx +264 -22
- package/src/components/layout/shell.tsx +46 -8
- package/src/components/layout/status-bar.tsx +2 -0
- package/src/components/onboarding/onboarding-wizard.tsx +1 -2
- package/src/components/pane-settings-dialog.tsx +12 -4
- package/src/components/pane-template-wizard.test.tsx +3 -4
- package/src/components/speedometer-gauge.test.tsx +45 -0
- package/src/components/speedometer-gauge.tsx +562 -0
- package/src/components/table-view-shared.tsx +218 -0
- package/src/components/ticker-list-table-view.tsx +57 -73
- package/src/components/ticker-list-table.test.tsx +82 -1
- package/src/components/ticker-list-table.tsx +22 -9
- package/src/components/ui/confirm-dialog.tsx +66 -0
- package/src/components/ui/data-table.tsx +30 -19
- package/src/components/ui/frame.tsx +2 -0
- package/src/components/ui/index.ts +0 -1
- package/src/components/ui/page-stack-view.tsx +39 -10
- package/src/components/ui/table-layout.test.ts +42 -0
- package/src/components/ui/table-layout.ts +49 -4
- package/src/components/ui/tabs.tsx +34 -1
- package/src/components/ui/ui.test.tsx +168 -21
- package/src/core/state/app-state.ts +10 -17
- package/src/market-data/coordinator.test.ts +76 -1
- package/src/market-data/coordinator.ts +218 -49
- package/src/news/aggregator.test.ts +49 -1
- package/src/news/aggregator.ts +48 -3
- package/src/news/hooks.ts +6 -2
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +46 -16
- package/src/plugins/builtin/ai/providers.ts +0 -17
- package/src/plugins/builtin/ai/screener-pane.test.tsx +3 -4
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -5
- package/src/plugins/builtin/analytics/index.tsx +1 -18
- package/src/plugins/builtin/ask-ai.test.tsx +89 -2
- package/src/plugins/builtin/broker-manager/index.test.tsx +2 -2
- package/src/plugins/builtin/broker-manager/index.tsx +19 -36
- package/src/plugins/builtin/chat-controller.test.ts +188 -1
- package/src/plugins/builtin/chat-controller.ts +537 -220
- package/src/plugins/builtin/chat.test.tsx +431 -13
- package/src/plugins/builtin/chat.tsx +1134 -227
- package/src/plugins/builtin/cloud-tweets.tsx +1 -1
- package/src/plugins/builtin/comparison-chart.test.tsx +53 -2
- package/src/plugins/builtin/comparison-chart.tsx +11 -5
- package/src/plugins/builtin/correlation/index.tsx +1 -6
- package/src/plugins/builtin/debug.tsx +1 -3
- package/src/plugins/builtin/econ/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +72 -0
- package/src/plugins/builtin/fear-greed/fear-greed-data.ts +366 -0
- package/src/plugins/builtin/fear-greed/index.tsx +587 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/help.tsx +2 -2
- package/src/plugins/builtin/holders/index.test.ts +13 -1
- package/src/plugins/builtin/holders/index.tsx +64 -29
- package/src/plugins/builtin/insider/index.tsx +22 -89
- package/src/plugins/builtin/market-movers/index.tsx +13 -19
- package/src/plugins/builtin/market-movers/screener.test.ts +39 -1
- package/src/plugins/builtin/market-movers/screener.ts +70 -17
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +3 -2
- package/src/plugins/builtin/news-wire/index.tsx +41 -2
- package/src/plugins/builtin/news-wire/industry-pane.tsx +4 -18
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +46 -4
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-table.tsx +11 -11
- package/src/plugins/builtin/news.tsx +4 -16
- package/src/plugins/builtin/notes.tsx +19 -55
- package/src/plugins/builtin/options.test.tsx +58 -2
- package/src/plugins/builtin/options.tsx +110 -65
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane.tsx +16 -39
- package/src/plugins/builtin/research/index.test.ts +94 -0
- package/src/plugins/builtin/research/index.tsx +164 -33
- package/src/plugins/builtin/sec-filing-display.tsx +84 -0
- package/src/plugins/builtin/sec.tsx +13 -66
- package/src/plugins/builtin/sectors/index.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +28 -22
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +3 -4
- package/src/plugins/builtin/ticker-detail.test.tsx +38 -0
- package/src/plugins/builtin/world-indices/index.tsx +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/ibkr/broker-adapter.ts +1 -3
- package/src/plugins/ibkr/gateway-service-native.ts +1 -5
- package/src/plugins/ibkr/gateway-service.ts +1 -1
- package/src/plugins/ibkr/index.test.tsx +0 -2
- package/src/plugins/ibkr/trade-tab.test.tsx +3 -4
- package/src/plugins/pane-manager.ts +0 -13
- package/src/plugins/plugin-runtime.test.tsx +40 -6
- package/src/plugins/plugin-runtime.tsx +20 -10
- package/src/plugins/prediction-markets/cli.ts +0 -1
- package/src/plugins/prediction-markets/controller-data.ts +0 -2
- package/src/plugins/prediction-markets/controller.ts +0 -15
- package/src/plugins/prediction-markets/detail/pane.tsx +1 -0
- package/src/plugins/prediction-markets/pane.test.tsx +3 -11
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -1
- package/src/plugins/registry.ts +13 -42
- package/src/plugins/ticker-navigation.test.ts +66 -0
- package/src/plugins/ticker-navigation.ts +44 -0
- package/src/react/input.ts +2 -1
- package/src/renderers/electrobun/bun/index.ts +32 -11
- package/src/renderers/electrobun/bun/window-focus.ts +37 -0
- package/src/renderers/electrobun/shared/protocol.ts +7 -1
- package/src/renderers/electrobun/view/app-services.ts +88 -31
- package/src/renderers/electrobun/view/backend-rpc.ts +15 -0
- package/src/renderers/electrobun/view/data-table.tsx +138 -101
- package/src/renderers/electrobun/view/desktop-controls.tsx +42 -20
- package/src/renderers/electrobun/view/desktop-window-bridge.ts +12 -1
- package/src/renderers/electrobun/view/dialog-host.tsx +13 -1
- package/src/renderers/electrobun/view/input-host.tsx +74 -10
- package/src/renderers/electrobun/view/key-event.ts +1 -1
- package/src/renderers/electrobun/view/main.tsx +18 -1
- package/src/renderers/electrobun/view/styles.css +329 -0
- package/src/renderers/electrobun/view/toast-host.tsx +29 -3
- package/src/renderers/electrobun/view/ui-host.tsx +42 -0
- package/src/renderers/opentui/chart-surface.tsx +179 -0
- package/src/renderers/opentui/input-host.tsx +1 -1
- package/src/renderers/opentui/start.tsx +48 -30
- package/src/renderers/opentui/test-utils.tsx +17 -0
- package/src/renderers/opentui/ui-host.tsx +5 -2
- package/src/sources/gloomberb-cloud.test.ts +86 -1
- package/src/sources/gloomberb-cloud.ts +117 -21
- package/src/sources/provider-router.test.ts +122 -0
- package/src/sources/provider-router.ts +198 -15
- package/src/sources/yahoo-finance.test.ts +44 -0
- package/src/sources/yahoo-finance.ts +6 -0
- package/src/state/app-bootstrap.ts +20 -7
- package/src/state/app-context-selector.test.tsx +189 -4
- package/src/state/app-context.test.ts +65 -0
- package/src/state/app-context.tsx +48 -16
- package/src/state/use-inline-tickers.test.tsx +73 -0
- package/src/state/use-inline-tickers.ts +19 -10
- package/src/test-support/plugin-runtime.ts +3 -2
- package/src/theme/colors.test.ts +75 -0
- package/src/theme/colors.ts +76 -6
- package/src/theme/theme-context.tsx +26 -0
- package/src/theme/themes.test.ts +23 -1
- package/src/theme/themes.ts +115 -0
- package/src/types/broker.ts +1 -1
- package/src/types/capability-route-source.ts +1 -0
- package/src/types/data-provider.ts +15 -2
- package/src/types/desktop-window.ts +6 -0
- package/src/types/financials.ts +2 -0
- package/src/types/plugin.ts +1 -3
- package/src/ui/host.tsx +7 -0
- package/src/utils/api-client.test.ts +32 -0
- package/src/utils/api-client.ts +119 -18
- package/src/utils/back-navigation.ts +24 -1
- package/src/utils/double-escape-close.test.ts +33 -0
- package/src/utils/double-escape-close.ts +40 -0
- package/src/utils/throttled-fetch.test.ts +22 -0
- package/src/utils/throttled-fetch.ts +3 -1
- package/src/utils/ticker-search.ts +53 -9
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/feed-pane.tsx +0 -21
- package/src/plugins/builtin/news-wire/top-pane.tsx +0 -21
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
applyTheme,
|
|
4
|
+
clearTransientThemePreview,
|
|
5
|
+
getCurrentThemeId,
|
|
6
|
+
previewTheme,
|
|
7
|
+
syncTheme,
|
|
8
|
+
} from "./colors";
|
|
9
|
+
import { DEFAULT_THEME, getTheme } from "./themes";
|
|
10
|
+
|
|
11
|
+
const originalDocument = (globalThis as Record<string, unknown>).document;
|
|
12
|
+
|
|
13
|
+
function setDocument(value: unknown): void {
|
|
14
|
+
Object.defineProperty(globalThis, "document", {
|
|
15
|
+
configurable: true,
|
|
16
|
+
writable: true,
|
|
17
|
+
value,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function installDocumentStyleMock() {
|
|
22
|
+
const values = new Map<string, string>();
|
|
23
|
+
setDocument({
|
|
24
|
+
documentElement: {
|
|
25
|
+
style: {
|
|
26
|
+
setProperty(name: string, value: string) {
|
|
27
|
+
values.set(name, value);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
return values;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
if (originalDocument === undefined) {
|
|
37
|
+
Reflect.deleteProperty(globalThis, "document");
|
|
38
|
+
} else {
|
|
39
|
+
setDocument(originalDocument);
|
|
40
|
+
}
|
|
41
|
+
clearTransientThemePreview();
|
|
42
|
+
syncTheme(DEFAULT_THEME);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("theme colors", () => {
|
|
46
|
+
test("syncs CSS variables when a theme is applied", () => {
|
|
47
|
+
const values = installDocumentStyleMock();
|
|
48
|
+
const theme = getTheme("midnight");
|
|
49
|
+
|
|
50
|
+
applyTheme("midnight");
|
|
51
|
+
|
|
52
|
+
expect(values.get("--gloom-bg")).toBe(theme.bg);
|
|
53
|
+
expect(values.get("--gloom-panel")).toBe(theme.panel);
|
|
54
|
+
expect(values.get("--gloom-text-dim")).toBe(theme.textDim);
|
|
55
|
+
expect(values.get("--gloom-selected")).toBe(theme.selected);
|
|
56
|
+
expect(values.get("--gloom-hover-bg")).toBeString();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("does not let provider sync clobber a pending preview", () => {
|
|
60
|
+
const values = installDocumentStyleMock();
|
|
61
|
+
const preview = getTheme("midnight");
|
|
62
|
+
|
|
63
|
+
previewTheme("midnight");
|
|
64
|
+
syncTheme(DEFAULT_THEME);
|
|
65
|
+
|
|
66
|
+
expect(getCurrentThemeId()).toBe("midnight");
|
|
67
|
+
expect(values.get("--gloom-bg")).toBe(preview.bg);
|
|
68
|
+
|
|
69
|
+
clearTransientThemePreview();
|
|
70
|
+
syncTheme(DEFAULT_THEME);
|
|
71
|
+
|
|
72
|
+
expect(getCurrentThemeId()).toBe(DEFAULT_THEME);
|
|
73
|
+
expect(values.get("--gloom-bg")).toBe(getTheme(DEFAULT_THEME).bg);
|
|
74
|
+
});
|
|
75
|
+
});
|
package/src/theme/colors.ts
CHANGED
|
@@ -5,7 +5,33 @@ import { getTheme, DEFAULT_THEME, type Theme } from "./themes";
|
|
|
5
5
|
// React components re-read these on each render triggered by the SET_THEME action.
|
|
6
6
|
export const colors: Omit<Theme, "name" | "description"> = { ...getTheme(DEFAULT_THEME) };
|
|
7
7
|
|
|
8
|
+
export type ColorKey = keyof typeof colors;
|
|
9
|
+
|
|
8
10
|
let currentThemeId = DEFAULT_THEME;
|
|
11
|
+
let transientPreviewThemeId: string | null = null;
|
|
12
|
+
let cssThemeId: string | null = null;
|
|
13
|
+
let cssThemeDocument: unknown = null;
|
|
14
|
+
|
|
15
|
+
const THEME_CSS_VARIABLES: Array<[ColorKey, string]> = [
|
|
16
|
+
["bg", "--gloom-bg"],
|
|
17
|
+
["panel", "--gloom-panel"],
|
|
18
|
+
["border", "--gloom-border"],
|
|
19
|
+
["borderFocused", "--gloom-border-focused"],
|
|
20
|
+
["text", "--gloom-text"],
|
|
21
|
+
["textDim", "--gloom-text-dim"],
|
|
22
|
+
["textBright", "--gloom-text-bright"],
|
|
23
|
+
["textMuted", "--gloom-text-muted"],
|
|
24
|
+
["positive", "--gloom-positive"],
|
|
25
|
+
["negative", "--gloom-negative"],
|
|
26
|
+
["neutral", "--gloom-neutral"],
|
|
27
|
+
["warning", "--gloom-warning"],
|
|
28
|
+
["header", "--gloom-header"],
|
|
29
|
+
["headerText", "--gloom-header-text"],
|
|
30
|
+
["selected", "--gloom-selected"],
|
|
31
|
+
["selectedText", "--gloom-selected-text"],
|
|
32
|
+
["commandBg", "--gloom-command-bg"],
|
|
33
|
+
["commandBorder", "--gloom-command-border"],
|
|
34
|
+
];
|
|
9
35
|
|
|
10
36
|
export function getCurrentThemeId(): string {
|
|
11
37
|
return currentThemeId;
|
|
@@ -16,14 +42,34 @@ export function applyTheme(id: string): void {
|
|
|
16
42
|
currentThemeId = id;
|
|
17
43
|
// Mutate in-place so every existing import sees the new values
|
|
18
44
|
Object.assign(colors, theme);
|
|
45
|
+
syncThemeCssVariables();
|
|
19
46
|
}
|
|
20
47
|
|
|
21
|
-
export function
|
|
22
|
-
|
|
48
|
+
export function previewTheme(id: string): void {
|
|
49
|
+
transientPreviewThemeId = id;
|
|
23
50
|
applyTheme(id);
|
|
24
51
|
}
|
|
25
52
|
|
|
26
|
-
export
|
|
53
|
+
export function clearTransientThemePreview(): void {
|
|
54
|
+
transientPreviewThemeId = null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function syncTheme(id: string): void {
|
|
58
|
+
if (
|
|
59
|
+
transientPreviewThemeId
|
|
60
|
+
&& currentThemeId === transientPreviewThemeId
|
|
61
|
+
&& id !== transientPreviewThemeId
|
|
62
|
+
) {
|
|
63
|
+
syncThemeCssVariables();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
transientPreviewThemeId = null;
|
|
67
|
+
if (currentThemeId === id) {
|
|
68
|
+
syncThemeCssVariables();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
applyTheme(id);
|
|
72
|
+
}
|
|
27
73
|
|
|
28
74
|
export { blendHex } from "./color-utils";
|
|
29
75
|
|
|
@@ -49,6 +95,21 @@ export function hoverBg(): string {
|
|
|
49
95
|
return blendHex(colors.bg, colors.selected, 0.5);
|
|
50
96
|
}
|
|
51
97
|
|
|
98
|
+
function syncThemeCssVariables(): void {
|
|
99
|
+
const documentLike = (globalThis as {
|
|
100
|
+
document?: { documentElement?: { style?: { setProperty: (name: string, value: string) => void } } };
|
|
101
|
+
}).document;
|
|
102
|
+
const style = documentLike?.documentElement?.style;
|
|
103
|
+
if (!style) return;
|
|
104
|
+
if (cssThemeId === currentThemeId && cssThemeDocument === documentLike) return;
|
|
105
|
+
cssThemeId = currentThemeId;
|
|
106
|
+
cssThemeDocument = documentLike;
|
|
107
|
+
for (const [key, name] of THEME_CSS_VARIABLES) {
|
|
108
|
+
style.setProperty(name, colors[key]);
|
|
109
|
+
}
|
|
110
|
+
style.setProperty("--gloom-hover-bg", hoverBg());
|
|
111
|
+
}
|
|
112
|
+
|
|
52
113
|
export function commandBarBg(): string {
|
|
53
114
|
const base = higherContrast(colors.commandBg, colors.panel, colors.bg);
|
|
54
115
|
const accent = higherContrast(colors.textBright, colors.borderFocused, base);
|
|
@@ -74,7 +135,7 @@ export function commandBarOverlayBg(): string {
|
|
|
74
135
|
export function commandBarSelectedBg(): string {
|
|
75
136
|
const base = commandBarBg();
|
|
76
137
|
const accent = higherContrast(colors.selectedText, colors.textBright, colors.selected);
|
|
77
|
-
return blendForContrast(colors.selected, base, accent, 1.
|
|
138
|
+
return blendForContrast(colors.selected, base, accent, 1.45);
|
|
78
139
|
}
|
|
79
140
|
|
|
80
141
|
export function commandBarHoverBg(): string {
|
|
@@ -89,7 +150,12 @@ export function commandBarText(): string {
|
|
|
89
150
|
|
|
90
151
|
export function commandBarHeadingText(): string {
|
|
91
152
|
const base = commandBarBg();
|
|
92
|
-
|
|
153
|
+
const fallback = higherContrast(
|
|
154
|
+
higherContrast(colors.textDim, colors.text, base),
|
|
155
|
+
higherContrast(colors.textBright, colors.selectedText, base),
|
|
156
|
+
base,
|
|
157
|
+
);
|
|
158
|
+
return blendForContrast(colors.textMuted, base, fallback, 3.6);
|
|
93
159
|
}
|
|
94
160
|
|
|
95
161
|
export function commandBarSubtleText(): string {
|
|
@@ -101,7 +167,11 @@ export function commandBarSubtleText(): string {
|
|
|
101
167
|
export function commandBarSelectedText(): string {
|
|
102
168
|
const base = commandBarSelectedBg();
|
|
103
169
|
const preferred = higherContrast(colors.selectedText, colors.text, base);
|
|
104
|
-
const fallback = higherContrast(
|
|
170
|
+
const fallback = higherContrast(
|
|
171
|
+
higherContrast(colors.textBright, colors.text, base),
|
|
172
|
+
higherContrast("#ffffff", "#000000", base),
|
|
173
|
+
base,
|
|
174
|
+
);
|
|
105
175
|
return blendForContrast(preferred, base, fallback, 4.5);
|
|
106
176
|
}
|
|
107
177
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
|
+
import { colors, getCurrentThemeId, syncTheme } from "./colors";
|
|
3
|
+
import type { Theme } from "./themes";
|
|
4
|
+
|
|
5
|
+
type ThemeColors = Omit<Theme, "name" | "description">;
|
|
6
|
+
|
|
7
|
+
interface ThemeContextValue {
|
|
8
|
+
themeId: string;
|
|
9
|
+
colors: ThemeColors;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
13
|
+
|
|
14
|
+
export function ThemeProvider({ themeId, children }: { themeId: string; children: ReactNode }) {
|
|
15
|
+
syncTheme(themeId);
|
|
16
|
+
const value = useMemo(() => ({ themeId, colors }), [themeId]);
|
|
17
|
+
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useThemeId(): string {
|
|
21
|
+
return useContext(ThemeContext)?.themeId ?? getCurrentThemeId();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useThemeColors(): ThemeColors {
|
|
25
|
+
return useContext(ThemeContext)?.colors ?? colors;
|
|
26
|
+
}
|
package/src/theme/themes.test.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { afterEach, describe, test } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
applyTheme,
|
|
4
|
+
commandBarBg,
|
|
5
|
+
commandBarHeadingText,
|
|
6
|
+
commandBarPanelBg,
|
|
7
|
+
commandBarSelectedBg,
|
|
8
|
+
commandBarSelectedText,
|
|
9
|
+
commandBarSubtleText,
|
|
10
|
+
commandBarText,
|
|
11
|
+
floatingPaneTitleBg,
|
|
12
|
+
paneTitleBg,
|
|
13
|
+
paneTitleText,
|
|
14
|
+
} from "./colors";
|
|
3
15
|
import { contrastRatio } from "./color-utils";
|
|
4
16
|
import { DEFAULT_THEME, themes } from "./themes";
|
|
5
17
|
|
|
@@ -44,4 +56,14 @@ describe("theme contrast", () => {
|
|
|
44
56
|
assertMinContrast(id, "floatingPaneTitleText/unfocused", paneTitleText(false, true), floatingPaneTitleBg(false), CHROME_TEXT_MIN);
|
|
45
57
|
}
|
|
46
58
|
});
|
|
59
|
+
|
|
60
|
+
test("keeps command bar text readable", () => {
|
|
61
|
+
for (const id of Object.keys(themes)) {
|
|
62
|
+
applyTheme(id);
|
|
63
|
+
assertMinContrast(id, "commandBarText/bg", commandBarText(), commandBarBg(), BODY_TEXT_MIN);
|
|
64
|
+
assertMinContrast(id, "commandBarHeadingText/bg", commandBarHeadingText(), commandBarBg(), SUBTLE_TEXT_MIN);
|
|
65
|
+
assertMinContrast(id, "commandBarSubtleText/panel", commandBarSubtleText(), commandBarPanelBg(), SUBTLE_TEXT_MIN);
|
|
66
|
+
assertMinContrast(id, "commandBarSelectedText/selected", commandBarSelectedText(), commandBarSelectedBg(), BODY_TEXT_MIN);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
47
69
|
});
|
package/src/theme/themes.ts
CHANGED
|
@@ -305,6 +305,121 @@ const rawThemes: Record<string, Theme> = {
|
|
|
305
305
|
commandBorder: "#cccccc",
|
|
306
306
|
},
|
|
307
307
|
|
|
308
|
+
paper: {
|
|
309
|
+
name: "Paper",
|
|
310
|
+
description: "Warm light terminal palette",
|
|
311
|
+
bg: "#f7f3e8",
|
|
312
|
+
panel: "#fffaf0",
|
|
313
|
+
border: "#d8cdb7",
|
|
314
|
+
borderFocused: "#a05a2c",
|
|
315
|
+
text: "#2f2a23",
|
|
316
|
+
textDim: "#5f5548",
|
|
317
|
+
textBright: "#17130f",
|
|
318
|
+
textMuted: "#776b5d",
|
|
319
|
+
positive: "#2f7d4a",
|
|
320
|
+
negative: "#b33630",
|
|
321
|
+
neutral: "#6e6256",
|
|
322
|
+
warning: "#946200",
|
|
323
|
+
header: "#eadcc4",
|
|
324
|
+
headerText: "#7a3f1f",
|
|
325
|
+
selected: "#ead1ab",
|
|
326
|
+
selectedText: "#211810",
|
|
327
|
+
commandBg: "#fff7e8",
|
|
328
|
+
commandBorder: "#a05a2c",
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
"github-light": {
|
|
332
|
+
name: "GitHub Light",
|
|
333
|
+
description: "Clean light code palette",
|
|
334
|
+
bg: "#ffffff",
|
|
335
|
+
panel: "#f6f8fa",
|
|
336
|
+
border: "#d0d7de",
|
|
337
|
+
borderFocused: "#0969da",
|
|
338
|
+
text: "#24292f",
|
|
339
|
+
textDim: "#57606a",
|
|
340
|
+
textBright: "#0b1220",
|
|
341
|
+
textMuted: "#6e7781",
|
|
342
|
+
positive: "#1a7f37",
|
|
343
|
+
negative: "#cf222e",
|
|
344
|
+
neutral: "#57606a",
|
|
345
|
+
warning: "#9a6700",
|
|
346
|
+
header: "#eaeef2",
|
|
347
|
+
headerText: "#0969da",
|
|
348
|
+
selected: "#ddf4ff",
|
|
349
|
+
selectedText: "#0b1220",
|
|
350
|
+
commandBg: "#f6f8fa",
|
|
351
|
+
commandBorder: "#0969da",
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
"solarized-light": {
|
|
355
|
+
name: "Solarized Light",
|
|
356
|
+
description: "Ethan Schoonover's light palette",
|
|
357
|
+
bg: "#fdf6e3",
|
|
358
|
+
panel: "#eee8d5",
|
|
359
|
+
border: "#93a1a1",
|
|
360
|
+
borderFocused: "#b58900",
|
|
361
|
+
text: "#586e75",
|
|
362
|
+
textDim: "#657b83",
|
|
363
|
+
textBright: "#073642",
|
|
364
|
+
textMuted: "#7c8a8a",
|
|
365
|
+
positive: "#859900",
|
|
366
|
+
negative: "#dc322f",
|
|
367
|
+
neutral: "#657b83",
|
|
368
|
+
warning: "#b58900",
|
|
369
|
+
header: "#e6dfc9",
|
|
370
|
+
headerText: "#268bd2",
|
|
371
|
+
selected: "#dce6d6",
|
|
372
|
+
selectedText: "#073642",
|
|
373
|
+
commandBg: "#eee8d5",
|
|
374
|
+
commandBorder: "#b58900",
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
"gruvbox-light": {
|
|
378
|
+
name: "Gruvbox Light",
|
|
379
|
+
description: "Retro earthy light palette",
|
|
380
|
+
bg: "#fbf1c7",
|
|
381
|
+
panel: "#f2e5bc",
|
|
382
|
+
border: "#d5c4a1",
|
|
383
|
+
borderFocused: "#af3a03",
|
|
384
|
+
text: "#3c3836",
|
|
385
|
+
textDim: "#665c54",
|
|
386
|
+
textBright: "#282828",
|
|
387
|
+
textMuted: "#7c6f64",
|
|
388
|
+
positive: "#79740e",
|
|
389
|
+
negative: "#9d0006",
|
|
390
|
+
neutral: "#665c54",
|
|
391
|
+
warning: "#b57614",
|
|
392
|
+
header: "#ebdbb2",
|
|
393
|
+
headerText: "#af3a03",
|
|
394
|
+
selected: "#d5c4a1",
|
|
395
|
+
selectedText: "#282828",
|
|
396
|
+
commandBg: "#f2e5bc",
|
|
397
|
+
commandBorder: "#af3a03",
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
"nord-light": {
|
|
401
|
+
name: "Nord Light",
|
|
402
|
+
description: "Soft arctic light palette",
|
|
403
|
+
bg: "#eceff4",
|
|
404
|
+
panel: "#e5e9f0",
|
|
405
|
+
border: "#d8dee9",
|
|
406
|
+
borderFocused: "#5e81ac",
|
|
407
|
+
text: "#2e3440",
|
|
408
|
+
textDim: "#4c566a",
|
|
409
|
+
textBright: "#1f2530",
|
|
410
|
+
textMuted: "#687286",
|
|
411
|
+
positive: "#4b7f36",
|
|
412
|
+
negative: "#bf616a",
|
|
413
|
+
neutral: "#4c566a",
|
|
414
|
+
warning: "#9a7500",
|
|
415
|
+
header: "#d8dee9",
|
|
416
|
+
headerText: "#5e81ac",
|
|
417
|
+
selected: "#c8d5e8",
|
|
418
|
+
selectedText: "#1f2530",
|
|
419
|
+
commandBg: "#e5e9f0",
|
|
420
|
+
commandBorder: "#5e81ac",
|
|
421
|
+
},
|
|
422
|
+
|
|
308
423
|
tokyo: {
|
|
309
424
|
name: "Tokyo Night",
|
|
310
425
|
description: "Cool blue-purple palette",
|
package/src/types/broker.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface NewsDataProvider {
|
|
|
6
6
|
supports?(query: NewsQuery): boolean;
|
|
7
7
|
getCachedNews?(query: NewsQuery): NewsArticle[];
|
|
8
8
|
fetchNews(query: NewsQuery): Promise<NewsArticle[]>;
|
|
9
|
+
fetchNewsStory?(storyId: string): Promise<NewsArticle | null>;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export interface CapabilityRouteSource {
|
|
@@ -2,7 +2,6 @@ import type {
|
|
|
2
2
|
AnalystResearchData,
|
|
3
3
|
CorporateActionsData,
|
|
4
4
|
Quote,
|
|
5
|
-
Fundamentals,
|
|
6
5
|
TickerFinancials,
|
|
7
6
|
PricePoint,
|
|
8
7
|
OptionsChain,
|
|
@@ -93,6 +92,18 @@ export interface QuoteSubscriptionTarget {
|
|
|
93
92
|
route?: "auto" | "provider" | "broker";
|
|
94
93
|
}
|
|
95
94
|
|
|
95
|
+
export interface QuoteBatchResult {
|
|
96
|
+
target: QuoteSubscriptionTarget;
|
|
97
|
+
quote: Quote | null;
|
|
98
|
+
error?: unknown;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface TickerFinancialsBatchResult {
|
|
102
|
+
target: CachedFinancialsTarget;
|
|
103
|
+
financials: TickerFinancials | null;
|
|
104
|
+
error?: unknown;
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
export interface AssetDataProvider {
|
|
97
108
|
readonly id: string;
|
|
98
109
|
readonly name: string;
|
|
@@ -100,7 +111,9 @@ export interface AssetDataProvider {
|
|
|
100
111
|
readonly cachePolicy?: CachePolicyMap;
|
|
101
112
|
|
|
102
113
|
canProvide?(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<boolean> | boolean;
|
|
103
|
-
getCachedFinancialsForTargets?(targets: CachedFinancialsTarget[], options?: { allowExpired?: boolean }): Map<string, TickerFinancials
|
|
114
|
+
getCachedFinancialsForTargets?(targets: CachedFinancialsTarget[], options?: { allowExpired?: boolean }): Map<string, TickerFinancials> | Promise<Map<string, TickerFinancials>>;
|
|
115
|
+
getQuotesBatch?(targets: QuoteSubscriptionTarget[], options?: { forceRefresh?: boolean }): Promise<QuoteBatchResult[]>;
|
|
116
|
+
getTickerFinancialsBatch?(targets: CachedFinancialsTarget[], options?: { forceRefresh?: boolean }): Promise<TickerFinancialsBatchResult[]>;
|
|
104
117
|
getTickerFinancials(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<TickerFinancials>;
|
|
105
118
|
getQuote(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<Quote>;
|
|
106
119
|
getExchangeRate(fromCurrency: string): Promise<number>;
|
|
@@ -15,10 +15,15 @@ export interface DesktopDockPreviewState {
|
|
|
15
15
|
edge: "left" | "right" | "top" | "bottom" | null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export interface DesktopThemePreviewState {
|
|
19
|
+
theme: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
export interface DesktopWindowBridge {
|
|
19
23
|
kind: "main" | "detached";
|
|
20
24
|
paneId?: string;
|
|
21
25
|
syncMainState?(snapshot: DesktopSharedStateSnapshot): Promise<void>;
|
|
26
|
+
syncThemePreview?(preview: DesktopThemePreviewState): Promise<void>;
|
|
22
27
|
replaceDetachedPaneState?(paneId: string, paneState: PaneRuntimeState): Promise<void>;
|
|
23
28
|
popOutPane?(paneId: string): Promise<void>;
|
|
24
29
|
dockDetachedPane?(paneId: string): Promise<void>;
|
|
@@ -26,4 +31,5 @@ export interface DesktopWindowBridge {
|
|
|
26
31
|
focusDetachedPane?(paneId: string): Promise<void>;
|
|
27
32
|
subscribeState(listener: (snapshot: DesktopSharedStateSnapshot) => void): () => void;
|
|
28
33
|
subscribeDockPreview?(listener: (preview: DesktopDockPreviewState) => void): () => void;
|
|
34
|
+
subscribeThemePreview?(listener: (preview: DesktopThemePreviewState) => void): () => void;
|
|
29
35
|
}
|
package/src/types/financials.ts
CHANGED
package/src/types/plugin.ts
CHANGED
|
@@ -449,14 +449,12 @@ export interface GloomPluginContext {
|
|
|
449
449
|
hidePane(paneId: string): void;
|
|
450
450
|
focusPane(paneId: string): void;
|
|
451
451
|
pinTicker(symbol: string, options?: { floating?: boolean; paneType?: string }): void;
|
|
452
|
-
navigateTicker(symbol: string): void;
|
|
452
|
+
navigateTicker(symbol: string, options?: { sourcePaneId?: string | null }): void;
|
|
453
453
|
openPaneSettings(paneId?: string): void;
|
|
454
454
|
|
|
455
455
|
on<K extends keyof PluginEvents>(event: K, handler: (payload: PluginEvents[K]) => void): () => void;
|
|
456
456
|
emit<K extends keyof PluginEvents>(event: K, payload: PluginEvents[K]): void;
|
|
457
457
|
|
|
458
|
-
showWidget(widgetId: string): void;
|
|
459
|
-
hideWidget(widgetId: string): void;
|
|
460
458
|
notify(notification: AppNotificationRequest): void;
|
|
461
459
|
}
|
|
462
460
|
|
package/src/ui/host.tsx
CHANGED
|
@@ -98,13 +98,19 @@ export interface ScrollBoxRenderable {
|
|
|
98
98
|
scrollTop: number;
|
|
99
99
|
scrollLeft?: number;
|
|
100
100
|
scrollHeight: number;
|
|
101
|
+
scrollTopPx?: number;
|
|
102
|
+
scrollLeftPx?: number;
|
|
103
|
+
scrollHeightPx?: number;
|
|
104
|
+
scrollWidthPx?: number;
|
|
101
105
|
viewport?: { width: number; height: number };
|
|
106
|
+
viewportPx?: { width: number; height: number };
|
|
102
107
|
visible?: boolean;
|
|
103
108
|
parent?: unknown;
|
|
104
109
|
getBoundingClientRect?: () => { x: number; y: number; width: number; height: number };
|
|
105
110
|
horizontalScrollBar?: { visible: boolean };
|
|
106
111
|
verticalScrollBar?: { visible: boolean };
|
|
107
112
|
scrollTo(target: number | { x?: number; y?: number }, y?: number): void;
|
|
113
|
+
scrollToPixels?(target: number | { x?: number; y?: number }, y?: number): void;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
export interface InputRenderable {
|
|
@@ -245,6 +251,7 @@ export interface UiHost {
|
|
|
245
251
|
cellHeightPx?: number;
|
|
246
252
|
pixelRatio?: number;
|
|
247
253
|
canvasCharts?: boolean;
|
|
254
|
+
nativeCharts?: boolean;
|
|
248
255
|
nativeContextMenu?: boolean;
|
|
249
256
|
};
|
|
250
257
|
Box: ComponentType<BoxProps>;
|
|
@@ -320,4 +320,36 @@ describe("apiClient cloud news", () => {
|
|
|
320
320
|
expect(url.searchParams.get("cursor")).toBe("cursor-1");
|
|
321
321
|
expect(result).toEqual({ items: [], nextCursor: null });
|
|
322
322
|
});
|
|
323
|
+
|
|
324
|
+
test("fetches news story details from the story route", async () => {
|
|
325
|
+
let seenUrl = "";
|
|
326
|
+
globalThis.fetch = mockFetch(async (input: Request | string | URL) => {
|
|
327
|
+
seenUrl = String(input);
|
|
328
|
+
return createResponse({
|
|
329
|
+
id: "story-1",
|
|
330
|
+
headline: "Story headline",
|
|
331
|
+
summary: "Story summary",
|
|
332
|
+
category: "general",
|
|
333
|
+
sentiment: "neutral",
|
|
334
|
+
sectors: [],
|
|
335
|
+
firstPublishedAt: "2026-04-01T10:00:00.000Z",
|
|
336
|
+
lastPublishedAt: "2026-04-01T10:05:00.000Z",
|
|
337
|
+
firstSeenAt: "2026-04-01T10:00:10.000Z",
|
|
338
|
+
lastSeenAt: "2026-04-01T10:05:10.000Z",
|
|
339
|
+
primaryUrl: "https://example.com/story",
|
|
340
|
+
primarySource: "example-wire",
|
|
341
|
+
variantCount: 2,
|
|
342
|
+
sourceCount: 2,
|
|
343
|
+
sources: ["example-wire"],
|
|
344
|
+
entities: [],
|
|
345
|
+
tickerLinks: [],
|
|
346
|
+
items: [],
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const story = await apiClient.getCloudNewsStory("story-1");
|
|
351
|
+
|
|
352
|
+
expect(new URL(seenUrl).pathname).toBe("/news/story-1");
|
|
353
|
+
expect(story.id).toBe("story-1");
|
|
354
|
+
});
|
|
323
355
|
});
|