gloomberb 0.4.1 → 0.5.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/README.md +2 -2
- package/package.json +1 -1
- package/src/app.tsx +168 -65
- package/src/cli/commands/ticker.ts +45 -15
- package/src/cli/helpers.ts +4 -2
- package/src/cli/registry.test.ts +42 -35
- package/src/cli.test.ts +4 -2
- package/src/components/chart/chart-control-hint.tsx +39 -0
- package/src/components/chart/chart-controller.test.ts +110 -0
- package/src/components/chart/chart-controller.ts +364 -2
- package/src/components/chart/chart-data.ts +177 -29
- package/src/components/chart/chart-indicator-selector.tsx +53 -0
- package/src/components/chart/chart-pane-settings.ts +20 -0
- package/src/components/chart/chart-renderer.ts +220 -30
- package/src/components/chart/chart-resolution.test.ts +67 -0
- package/src/components/chart/chart-resolution.ts +74 -4
- package/src/components/chart/chart-scroll.test.ts +69 -0
- package/src/components/chart/chart-scroll.ts +104 -0
- package/src/components/chart/chart-types.ts +12 -0
- package/src/components/chart/chart-viewport.test.ts +5 -1
- package/src/components/chart/chart-viewport.ts +1 -1
- package/src/components/chart/chart.test.ts +249 -10
- package/src/components/chart/comparison-chart-renderer.test.ts +71 -1
- package/src/components/chart/comparison-chart-renderer.ts +40 -3
- package/src/components/chart/comparison-stock-chart.tsx +218 -123
- package/src/components/chart/indicators/bands.test.ts +50 -0
- package/src/components/chart/indicators/bands.ts +29 -0
- package/src/components/chart/indicators/moving-averages.test.ts +54 -0
- package/src/components/chart/indicators/moving-averages.ts +42 -0
- package/src/components/chart/indicators/options.test.ts +32 -0
- package/src/components/chart/indicators/options.ts +107 -0
- package/src/components/chart/indicators/oscillators.test.ts +79 -0
- package/src/components/chart/indicators/oscillators.ts +92 -0
- package/src/components/chart/indicators/types.ts +36 -0
- package/src/components/chart/native/chart-rasterizer.ts +51 -0
- package/src/components/chart/native/kitty.test.ts +68 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +740 -9
- package/src/components/chart/stock-chart.test.ts +239 -0
- package/src/components/chart/stock-chart.tsx +2257 -372
- package/src/components/command-bar/command-bar.test.tsx +337 -5
- package/src/components/command-bar/command-bar.tsx +292 -120
- package/src/components/command-bar/helpers.ts +1 -1
- package/src/components/command-bar/root-shortcuts.ts +33 -4
- package/src/components/command-bar/view-model.test.ts +3 -3
- package/src/components/command-bar/view-model.ts +4 -4
- package/src/components/command-bar/workflow-ops.ts +10 -0
- package/src/components/layout/floating-pane.tsx +3 -2
- package/src/components/layout/header.tsx +2 -1
- package/src/components/layout/pane-sizing.ts +10 -0
- package/src/components/layout/pane.tsx +9 -2
- package/src/components/layout/shell.test.tsx +282 -28
- package/src/components/layout/shell.tsx +10 -7
- package/src/components/layout/status-bar.test.tsx +16 -7
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-editor.tsx +1 -0
- package/src/components/pane-settings-dialog.tsx +13 -152
- package/src/components/pane-template-wizard.tsx +1 -0
- package/src/components/price-selector-dialog.tsx +12 -7
- package/src/components/ticker-badge-text.test.tsx +50 -0
- package/src/components/ticker-badge-text.tsx +17 -2
- package/src/components/ticker-badge.tsx +2 -2
- package/src/components/ticker-list-table.tsx +54 -1
- package/src/components/toggle-list.tsx +15 -2
- package/src/components/ui/data-table.tsx +60 -3
- package/src/components/ui/external-link.tsx +48 -11
- package/src/components/ui/fields.tsx +2 -1
- package/src/components/ui/index.ts +8 -1
- package/src/components/ui/multi-select-chips.tsx +69 -0
- package/src/components/ui/multi-select-dialog.tsx +267 -0
- package/src/components/ui/multi-select.ts +78 -0
- package/src/components/ui/ui.test.tsx +343 -1
- package/src/data/plugin-state-store.ts +30 -19
- package/src/data/resource-store.ts +133 -103
- package/src/data/session-store.ts +19 -12
- package/src/data/sqlite-cache.test.ts +37 -2
- package/src/data/sqlite-database.ts +8 -2
- package/src/data/sqlite-retry.test.ts +63 -0
- package/src/data/sqlite-retry.ts +66 -0
- package/src/data/ticker-store.ts +29 -18
- package/src/index.tsx +6 -1
- package/src/market-data/coordinator.test.ts +114 -5
- package/src/market-data/coordinator.ts +70 -1
- package/src/market-data/hooks.test.tsx +73 -2
- package/src/market-data/hooks.tsx +16 -4
- package/src/notifications/app-notifier.test.ts +177 -0
- package/src/notifications/app-notifier.ts +241 -0
- package/src/pane-settings.test.ts +2 -0
- package/src/plugins/builtin/ai/index.test.tsx +1 -1
- package/src/plugins/builtin/ai/screener-pane.test.tsx +2 -0
- package/src/plugins/builtin/ai/screener-pane.tsx +1 -0
- package/src/plugins/builtin/ai/ticker-context.ts +3 -2
- package/src/plugins/builtin/alerts/alert-engine.test.ts +90 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +55 -0
- package/src/plugins/builtin/alerts/index.test.tsx +357 -0
- package/src/plugins/builtin/alerts/index.tsx +519 -0
- package/src/plugins/builtin/alerts/types.ts +14 -0
- package/src/plugins/builtin/analytics/index.test.tsx +300 -0
- package/src/plugins/builtin/analytics/index.tsx +775 -0
- package/src/plugins/builtin/analytics/metrics.test.ts +125 -0
- package/src/plugins/builtin/analytics/metrics.ts +155 -0
- package/src/plugins/builtin/ask-ai.test.tsx +2 -0
- package/src/plugins/builtin/chat-controller.test.ts +83 -18
- package/src/plugins/builtin/chat-controller.ts +26 -14
- package/src/plugins/builtin/chat.test.tsx +715 -4
- package/src/plugins/builtin/chat.tsx +424 -120
- package/src/plugins/builtin/comparison-chart.test.tsx +97 -57
- package/src/plugins/builtin/comparison-chart.tsx +1 -2
- package/src/plugins/builtin/correlation/compute.test.ts +147 -0
- package/src/plugins/builtin/correlation/compute.ts +115 -0
- package/src/plugins/builtin/correlation/index.tsx +385 -0
- package/src/plugins/builtin/correlation/plugin.test.ts +44 -0
- package/src/plugins/builtin/correlation/settings.test.ts +34 -0
- package/src/plugins/builtin/correlation/settings.ts +95 -0
- package/src/plugins/builtin/debug.tsx +36 -50
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +181 -0
- package/src/plugins/builtin/earnings/earnings-cache.ts +136 -0
- package/src/plugins/builtin/earnings/earnings-data.test.ts +59 -0
- package/src/plugins/builtin/earnings/earnings-data.ts +50 -0
- package/src/plugins/builtin/earnings/index.tsx +337 -0
- package/src/plugins/builtin/econ/calendar-source.test.ts +219 -0
- package/src/plugins/builtin/econ/calendar-source.ts +121 -0
- package/src/plugins/builtin/econ/fred-client.test.ts +63 -0
- package/src/plugins/builtin/econ/fred-client.ts +79 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +88 -0
- package/src/plugins/builtin/econ/index.tsx +999 -0
- package/src/plugins/builtin/econ/types.ts +13 -0
- package/src/plugins/builtin/fred-settings.ts +10 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +178 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +30 -0
- package/src/plugins/builtin/help.test.tsx +1 -5
- package/src/plugins/builtin/help.tsx +1 -16
- package/src/plugins/builtin/insider/index.tsx +417 -0
- package/src/plugins/builtin/insider/insider-data.test.ts +55 -0
- package/src/plugins/builtin/insider/insider-data.ts +49 -0
- package/src/plugins/builtin/layout-manager.tsx +24 -16
- package/src/plugins/builtin/market-movers/index.tsx +573 -0
- package/src/plugins/builtin/market-movers/screener.test.ts +143 -0
- package/src/plugins/builtin/market-movers/screener.ts +105 -0
- package/src/plugins/builtin/options.tsx +4 -3
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +11 -6
- package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -3
- package/src/plugins/builtin/portfolio-list/header.tsx +3 -2
- package/src/plugins/builtin/portfolio-list/metrics.ts +22 -11
- package/src/plugins/builtin/portfolio-list/pane.tsx +3 -9
- package/src/plugins/builtin/portfolio-list/settings.ts +9 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -0
- package/src/plugins/builtin/portfolio-list.test.tsx +3 -1
- package/src/plugins/builtin/portfolio-metrics.test.ts +13 -1
- package/src/plugins/builtin/sec.tsx +114 -7
- package/src/plugins/builtin/sectors/index.tsx +359 -0
- package/src/plugins/builtin/sectors/sector-data.ts +18 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +75 -13
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +316 -80
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +4 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +17 -12
- package/src/plugins/builtin/ticker-detail.test.tsx +3 -3
- package/src/plugins/builtin/world-indices/index.tsx +410 -0
- package/src/plugins/builtin/world-indices/indices.ts +52 -0
- package/src/plugins/builtin/yield-curve/index.tsx +242 -0
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +212 -0
- package/src/plugins/builtin/yield-curve/treasury-data.ts +107 -0
- package/src/plugins/catalog.ts +22 -0
- package/src/plugins/gridlock-notification.ts +15 -0
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/index.test.tsx +1 -1
- package/src/plugins/ibkr/index.tsx +1 -1
- package/src/plugins/ibkr/trade-tab.tsx +23 -5
- package/src/plugins/ibkr/trade-utils.test.ts +1 -1
- package/src/plugins/ibkr/trade-utils.ts +7 -6
- package/src/plugins/ibkr/trading-pane.tsx +8 -7
- package/src/plugins/plugin-runtime.test.tsx +2 -0
- package/src/plugins/plugin-runtime.tsx +10 -0
- package/src/plugins/prediction-markets/controller-data.ts +8 -0
- package/src/plugins/prediction-markets/detail/shared.tsx +7 -6
- package/src/plugins/prediction-markets/plugin.test.tsx +48 -1
- package/src/plugins/prediction-markets/services/fetch.ts +13 -7
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/services/polymarket-ws.ts +20 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +2 -0
- package/src/plugins/registry.ts +22 -13
- package/src/sources/provider-router.test.ts +3 -2
- package/src/sources/sec-edgar.ts +13 -1
- package/src/sources/yahoo-finance.ts +75 -1
- package/src/state/app-context-selector.test.tsx +17 -1
- package/src/state/app-context.test.tsx +24 -0
- package/src/state/app-context.tsx +29 -6
- package/src/theme/colors.ts +5 -0
- package/src/theme/themes.ts +25 -0
- package/src/types/config.test.ts +107 -0
- package/src/types/config.ts +30 -1
- package/src/types/data-provider.ts +13 -0
- package/src/types/plugin.ts +35 -1
- package/src/utils/api-client.test.ts +90 -0
- package/src/utils/api-client.ts +56 -16
- package/src/utils/format.test.ts +11 -1
- package/src/utils/format.ts +86 -8
- package/src/utils/inline-content-tokenizer.test.ts +22 -0
- package/src/utils/inline-content-tokenizer.ts +76 -0
- package/src/utils/link-tokenizer.test.ts +27 -0
- package/src/utils/link-tokenizer.ts +93 -0
- package/src/utils/market-format.test.ts +109 -0
- package/src/utils/market-format.ts +299 -0
- package/src/utils/market-status.ts +3 -2
- package/src/utils/selection-clipboard.test.ts +43 -0
- package/src/utils/selection-clipboard.ts +88 -0
- package/src/utils/terminal-input-reset.test.ts +50 -0
- package/src/utils/terminal-input-reset.ts +24 -0
- package/src/utils/throttled-fetch.test.ts +145 -0
- package/src/utils/throttled-fetch.ts +212 -0
- package/src/utils/ticker-tokenizer.test.ts +31 -0
- package/src/utils/ticker-tokenizer.ts +7 -42
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# 📉 Gloomberb
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Modern extensible financial terminal.**
|
|
6
6
|
|
|
7
|
-
>
|
|
7
|
+
> The Bloomberb Terminal for the rest of us.
|
|
8
8
|
|
|
9
9
|
<img src="https://gloomberb.com/landing-terminal.png" alt="Gloomberb terminal screenshot" width="720" />
|
|
10
10
|
|
package/package.json
CHANGED
package/src/app.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useRef, useMemo
|
|
1
|
+
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
2
2
|
import { useKeyboard, useRenderer } from "@opentui/react";
|
|
3
3
|
import type { CliRenderer } from "@opentui/core";
|
|
4
4
|
import {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useAppState,
|
|
11
11
|
} from "./state/app-context";
|
|
12
12
|
import { bindAppActivity, useAppActive } from "./state/app-activity";
|
|
13
|
+
import { copyActiveSelection, isCopyShortcut, isPasteShortcut, pasteSystemClipboard } from "./utils/selection-clipboard";
|
|
13
14
|
import { Header } from "./components/layout/header";
|
|
14
15
|
import { StatusBar } from "./components/layout/status-bar";
|
|
15
16
|
import { Shell } from "./components/layout/shell";
|
|
@@ -20,12 +21,15 @@ import { PluginRegistry } from "./plugins/registry";
|
|
|
20
21
|
import { AppPersistence } from "./data/app-persistence";
|
|
21
22
|
import { TickerRepository } from "./data/ticker-repository";
|
|
22
23
|
import { ProviderRouter } from "./sources/provider-router";
|
|
23
|
-
import { colors,
|
|
24
|
+
import { colors, syncTheme } from "./theme/colors";
|
|
24
25
|
import {
|
|
25
26
|
createPaneInstance,
|
|
26
27
|
findPaneInstance,
|
|
28
|
+
findPrimaryPaneInstance,
|
|
27
29
|
isTickerPaneId,
|
|
28
30
|
normalizePaneLayout,
|
|
31
|
+
resolveFollowBindingInstance,
|
|
32
|
+
resolvePaneInstance,
|
|
29
33
|
type AppConfig,
|
|
30
34
|
type BrokerInstanceConfig,
|
|
31
35
|
type LayoutConfig,
|
|
@@ -93,6 +97,7 @@ import { debugLog } from "./utils/debug-log";
|
|
|
93
97
|
import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "./market-data/coordinator";
|
|
94
98
|
import { instrumentFromTicker } from "./market-data/request-types";
|
|
95
99
|
import { syncBrokerInstance } from "./brokers/sync-broker-instance";
|
|
100
|
+
import { createAppNotifier } from "./notifications/app-notifier";
|
|
96
101
|
|
|
97
102
|
/** Global-level dedup: prevents concurrent refresh calls for the same symbol. */
|
|
98
103
|
const refreshInFlight: Set<string> = (globalThis as any).__refreshInFlight ??= new Set<string>();
|
|
@@ -100,6 +105,14 @@ const quoteRefreshInFlight: Set<string> = (globalThis as any).__quoteRefreshInFl
|
|
|
100
105
|
const PANEL_RESOLUTION_BOUNDS = { x: 0, y: 0, width: 120, height: 40 };
|
|
101
106
|
const appLog = debugLog.createLogger("app");
|
|
102
107
|
|
|
108
|
+
function isCollectionPaneInstance(instance: PaneInstanceConfig): boolean {
|
|
109
|
+
return instance.paneId === "portfolio-list";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isTickerContextPaneInstance(instance: PaneInstanceConfig): boolean {
|
|
113
|
+
return instance.paneId === "portfolio-list" || isTickerPaneId(instance.paneId);
|
|
114
|
+
}
|
|
115
|
+
|
|
103
116
|
function summarizeError(error: unknown): Record<string, string> {
|
|
104
117
|
if (error instanceof Error) {
|
|
105
118
|
return {
|
|
@@ -122,30 +135,45 @@ interface AppInnerProps {
|
|
|
122
135
|
function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData, sessionSnapshot = null }: AppInnerProps) {
|
|
123
136
|
const { state, dispatch } = useAppState();
|
|
124
137
|
const appActive = useAppActive();
|
|
138
|
+
const appActiveRef = useRef(appActive);
|
|
125
139
|
const renderer = useRenderer();
|
|
126
140
|
const dialog = useDialog();
|
|
127
141
|
const stateRef = useRef(state);
|
|
128
142
|
stateRef.current = state;
|
|
143
|
+
appActiveRef.current = appActive;
|
|
129
144
|
const focusedCollectionId = getFocusedCollectionId(state);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
const appNotifier = useMemo(() => createAppNotifier({
|
|
146
|
+
isAppActive: () => appActiveRef.current,
|
|
147
|
+
renderToast: (notification) => {
|
|
148
|
+
const type = notification.type ?? "info";
|
|
149
|
+
let toastId: string | number | undefined;
|
|
150
|
+
const options = {
|
|
151
|
+
duration: notification.duration,
|
|
152
|
+
action: notification.action
|
|
153
|
+
? {
|
|
154
|
+
label: notification.action.label,
|
|
155
|
+
onClick: () => {
|
|
156
|
+
try {
|
|
157
|
+
notification.action?.onClick();
|
|
158
|
+
} finally {
|
|
159
|
+
if (toastId != null) toast.dismiss(toastId);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
: undefined,
|
|
164
|
+
};
|
|
165
|
+
if (type === "success") toastId = toast.success(notification.body, options);
|
|
166
|
+
else if (type === "error") toastId = toast.error(notification.body, options);
|
|
167
|
+
else toastId = toast.info(notification.body, options);
|
|
168
|
+
},
|
|
169
|
+
}), []);
|
|
170
|
+
const notify = useCallback((body: string, options?: { type?: "info" | "success" | "error" }) => {
|
|
171
|
+
pluginRegistry.notify({ body, ...options });
|
|
172
|
+
}, [pluginRegistry]);
|
|
143
173
|
|
|
144
174
|
const resolvePaneTarget = useCallback((paneId: string, layout: LayoutConfig = state.config.layout): string | null => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return resolvePrimaryPaneInstanceId(paneId, layout);
|
|
148
|
-
}, [resolvePrimaryPaneInstanceId, state.config.layout]);
|
|
175
|
+
return resolvePaneInstance(layout, paneId)?.instanceId ?? null;
|
|
176
|
+
}, [state.config.layout]);
|
|
149
177
|
|
|
150
178
|
const getPreferredPortfolio = useCallback((ticker: TickerRecord | null) => {
|
|
151
179
|
const focusedPortfolio = state.config.portfolios.find((portfolio) => portfolio.id === focusedCollectionId);
|
|
@@ -197,39 +225,17 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
197
225
|
}, [dialog]);
|
|
198
226
|
|
|
199
227
|
const resolveCollectionSourcePaneId = useCallback((preferredPaneId?: string | null) => {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (!instance) return null;
|
|
204
|
-
if (instance.paneId === "portfolio-list") return instance.instanceId;
|
|
205
|
-
if (instance.binding?.kind === "follow") {
|
|
206
|
-
return tryResolve(instance.binding.sourceInstanceId);
|
|
207
|
-
}
|
|
208
|
-
return null;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
return tryResolve(preferredPaneId)
|
|
212
|
-
?? tryResolve(state.focusedPaneId)
|
|
213
|
-
?? state.config.layout.instances.find((instance) => instance.paneId === "portfolio-list")?.instanceId
|
|
228
|
+
return resolveFollowBindingInstance(state.config.layout, preferredPaneId, isCollectionPaneInstance)?.instanceId
|
|
229
|
+
?? resolveFollowBindingInstance(state.config.layout, state.focusedPaneId, isCollectionPaneInstance)?.instanceId
|
|
230
|
+
?? findPrimaryPaneInstance(state.config.layout, "portfolio-list")?.instanceId
|
|
214
231
|
?? null;
|
|
215
232
|
}, [state.config.layout, state.focusedPaneId]);
|
|
216
233
|
|
|
217
234
|
const resolveTickerContextSourcePaneId = useCallback((preferredPaneId?: string | null) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (instance.paneId === "portfolio-list" || isTickerPaneId(instance.paneId)) return instance.instanceId;
|
|
223
|
-
if (instance.binding?.kind === "follow") {
|
|
224
|
-
return tryResolve(instance.binding.sourceInstanceId);
|
|
225
|
-
}
|
|
226
|
-
return null;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
return tryResolve(preferredPaneId)
|
|
230
|
-
?? tryResolve(state.focusedPaneId)
|
|
231
|
-
?? state.config.layout.instances.find((instance) => instance.paneId === "ticker-detail" && instance.binding?.kind !== "fixed")?.instanceId
|
|
232
|
-
?? state.config.layout.instances.find((instance) => instance.paneId === "portfolio-list")?.instanceId
|
|
235
|
+
return resolveFollowBindingInstance(state.config.layout, preferredPaneId, isTickerContextPaneInstance)?.instanceId
|
|
236
|
+
?? resolveFollowBindingInstance(state.config.layout, state.focusedPaneId, isTickerContextPaneInstance)?.instanceId
|
|
237
|
+
?? findPrimaryPaneInstance(state.config.layout, "ticker-detail")?.instanceId
|
|
238
|
+
?? findPrimaryPaneInstance(state.config.layout, "portfolio-list")?.instanceId
|
|
233
239
|
?? null;
|
|
234
240
|
}, [state.config.layout, state.focusedPaneId]);
|
|
235
241
|
|
|
@@ -273,9 +279,9 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
273
279
|
|
|
274
280
|
const switchDetailTab = useCallback((tabId: string, preferredPaneId?: string | null) => {
|
|
275
281
|
const targetPaneId = (() => {
|
|
276
|
-
const target = preferredPaneId ?
|
|
282
|
+
const target = preferredPaneId ? resolvePaneInstance(state.config.layout, preferredPaneId) : null;
|
|
277
283
|
if (target?.paneId === "ticker-detail") return target.instanceId;
|
|
278
|
-
const focused = state.focusedPaneId ?
|
|
284
|
+
const focused = state.focusedPaneId ? resolvePaneInstance(state.config.layout, state.focusedPaneId) : null;
|
|
279
285
|
if (focused?.paneId === "ticker-detail") return focused.instanceId;
|
|
280
286
|
const sourcePaneId = resolveCollectionSourcePaneId(preferredPaneId);
|
|
281
287
|
if (!sourcePaneId) return null;
|
|
@@ -796,6 +802,14 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
796
802
|
pluginRegistry.switchPanelFn = (panel) => dispatch({ type: "SET_ACTIVE_PANEL", panel });
|
|
797
803
|
pluginRegistry.switchTabFn = (tabId, paneId) => switchDetailTab(tabId, paneId);
|
|
798
804
|
pluginRegistry.openCommandBarFn = (query) => dispatch({ type: "SET_COMMAND_BAR", open: true, query });
|
|
805
|
+
pluginRegistry.openPluginCommandWorkflowFn = (commandId) => {
|
|
806
|
+
dispatch({
|
|
807
|
+
type: "SET_COMMAND_BAR",
|
|
808
|
+
open: true,
|
|
809
|
+
query: "",
|
|
810
|
+
launch: { kind: "plugin-command", commandId },
|
|
811
|
+
});
|
|
812
|
+
};
|
|
799
813
|
const persistLayout = (layout: LayoutConfig, options?: { pushHistory?: boolean }) => {
|
|
800
814
|
const currentState = stateRef.current;
|
|
801
815
|
const normalizedLayout = normalizePaneLayout(layout);
|
|
@@ -894,7 +908,7 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
894
908
|
if (paneId === "ticker-detail") {
|
|
895
909
|
const sourcePaneId = resolveCollectionSourcePaneId();
|
|
896
910
|
if (!sourcePaneId) {
|
|
897
|
-
|
|
911
|
+
notify("Open a collection pane first to inspect a ticker.");
|
|
898
912
|
return;
|
|
899
913
|
}
|
|
900
914
|
const ensured = ensureInspectorPane(sourcePaneId);
|
|
@@ -917,7 +931,7 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
917
931
|
: buildPaneInstance(paneId);
|
|
918
932
|
if (!instance) {
|
|
919
933
|
if (isTickerPaneId(paneId)) {
|
|
920
|
-
|
|
934
|
+
notify("Open a ticker or collection context first.");
|
|
921
935
|
}
|
|
922
936
|
return;
|
|
923
937
|
}
|
|
@@ -960,7 +974,7 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
960
974
|
placePaneInstance,
|
|
961
975
|
});
|
|
962
976
|
} catch (error) {
|
|
963
|
-
|
|
977
|
+
notify(
|
|
964
978
|
error instanceof Error ? error.message : `Could not create ${getPaneTemplateDisplayLabel(template).toLowerCase()}.`,
|
|
965
979
|
{ type: "info" },
|
|
966
980
|
);
|
|
@@ -969,6 +983,7 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
969
983
|
buildPaneInstance,
|
|
970
984
|
dataProvider,
|
|
971
985
|
dispatch,
|
|
986
|
+
notify,
|
|
972
987
|
placePaneInstance,
|
|
973
988
|
pluginRegistry,
|
|
974
989
|
runPaneTemplateWizard,
|
|
@@ -1080,6 +1095,90 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
1080
1095
|
activatePane(instance.instanceId, nextLayout);
|
|
1081
1096
|
};
|
|
1082
1097
|
|
|
1098
|
+
pluginRegistry.navigateTickerFn = (rawSymbol) => {
|
|
1099
|
+
(async () => {
|
|
1100
|
+
try {
|
|
1101
|
+
// Resolve or create the ticker in the local database
|
|
1102
|
+
const resolved = await resolveTickerSearch({
|
|
1103
|
+
query: rawSymbol,
|
|
1104
|
+
activeTicker: null,
|
|
1105
|
+
tickers: stateRef.current.tickers,
|
|
1106
|
+
dataProvider,
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
let symbol = rawSymbol;
|
|
1110
|
+
if (resolved?.kind === "local") {
|
|
1111
|
+
symbol = resolved.symbol;
|
|
1112
|
+
} else if (resolved?.kind === "provider" && resolved.result) {
|
|
1113
|
+
const { ticker, created } = await upsertTickerFromSearchResult(tickerRepository, resolved.result);
|
|
1114
|
+
symbol = ticker.metadata.ticker;
|
|
1115
|
+
dispatch({ type: "UPDATE_TICKER", ticker });
|
|
1116
|
+
if (created) {
|
|
1117
|
+
pluginRegistry.events.emit("ticker:added", { symbol, ticker });
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Active panel resolution — navigate the focused or linked detail pane:
|
|
1122
|
+
// 1. If the focused pane IS a ticker-detail, retarget it directly
|
|
1123
|
+
// 2. If a ticker-detail follows the focused pane, retarget that
|
|
1124
|
+
// 3. Any follow-mode ticker-detail in the layout
|
|
1125
|
+
// 4. Any ticker-detail in the layout
|
|
1126
|
+
// 5. Fall back to pinning a new pane
|
|
1127
|
+
const currentState = stateRef.current;
|
|
1128
|
+
const currentLayout = currentState.config.layout;
|
|
1129
|
+
const focused = currentState.focusedPaneId;
|
|
1130
|
+
|
|
1131
|
+
const focusedInstance = focused
|
|
1132
|
+
? findPaneInstance(currentLayout, focused)
|
|
1133
|
+
: null;
|
|
1134
|
+
|
|
1135
|
+
const detailPane =
|
|
1136
|
+
(focusedInstance?.paneId === "ticker-detail" && isPaneInLayout(currentLayout, focusedInstance.instanceId)
|
|
1137
|
+
? focusedInstance
|
|
1138
|
+
: null)
|
|
1139
|
+
?? currentLayout.instances.find((inst) =>
|
|
1140
|
+
inst.paneId === "ticker-detail"
|
|
1141
|
+
&& inst.binding?.kind === "follow"
|
|
1142
|
+
&& inst.binding.sourceInstanceId === focused
|
|
1143
|
+
&& isPaneInLayout(currentLayout, inst.instanceId),
|
|
1144
|
+
)
|
|
1145
|
+
?? currentLayout.instances.find((inst) =>
|
|
1146
|
+
inst.paneId === "ticker-detail"
|
|
1147
|
+
&& inst.binding?.kind === "follow"
|
|
1148
|
+
&& isPaneInLayout(currentLayout, inst.instanceId),
|
|
1149
|
+
)
|
|
1150
|
+
?? currentLayout.instances.find((inst) =>
|
|
1151
|
+
inst.paneId === "ticker-detail"
|
|
1152
|
+
&& isPaneInLayout(currentLayout, inst.instanceId),
|
|
1153
|
+
);
|
|
1154
|
+
|
|
1155
|
+
if (detailPane) {
|
|
1156
|
+
if (detailPane.binding?.kind === "follow") {
|
|
1157
|
+
const sourceId = detailPane.binding.sourceInstanceId;
|
|
1158
|
+
dispatch({ type: "UPDATE_PANE_STATE", paneId: sourceId, patch: { cursorSymbol: symbol } });
|
|
1159
|
+
activatePane(detailPane.instanceId, currentLayout);
|
|
1160
|
+
} else {
|
|
1161
|
+
const nextLayout = {
|
|
1162
|
+
...currentLayout,
|
|
1163
|
+
instances: currentLayout.instances.map((instance) => (
|
|
1164
|
+
instance.instanceId === detailPane.instanceId
|
|
1165
|
+
? { ...instance, title: symbol, binding: { kind: "fixed" as const, symbol } }
|
|
1166
|
+
: instance
|
|
1167
|
+
)),
|
|
1168
|
+
};
|
|
1169
|
+
persistLayout(nextLayout);
|
|
1170
|
+
activatePane(detailPane.instanceId, nextLayout);
|
|
1171
|
+
}
|
|
1172
|
+
} else {
|
|
1173
|
+
pluginRegistry.pinTickerFn(symbol, { floating: false });
|
|
1174
|
+
}
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1177
|
+
pluginRegistry.notify({ body: `Failed to navigate to ${rawSymbol}: ${message}`, type: "error" });
|
|
1178
|
+
}
|
|
1179
|
+
})();
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1083
1182
|
setLayoutManagerDispatch(dispatch, () => ({
|
|
1084
1183
|
layout: state.config.layout,
|
|
1085
1184
|
termWidth: pluginRegistry.getTermSizeFn().width,
|
|
@@ -1087,14 +1186,8 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
1087
1186
|
focusedPaneId: state.focusedPaneId,
|
|
1088
1187
|
}));
|
|
1089
1188
|
|
|
1090
|
-
// Wire up
|
|
1091
|
-
pluginRegistry.
|
|
1092
|
-
const type = options?.type ?? "info";
|
|
1093
|
-
const duration = options?.duration;
|
|
1094
|
-
if (type === "success") toast.success(message, { duration });
|
|
1095
|
-
else if (type === "error") toast.error(message, { duration });
|
|
1096
|
-
else toast.info(message, { duration });
|
|
1097
|
-
};
|
|
1189
|
+
// Wire up app-level notifications.
|
|
1190
|
+
pluginRegistry.notifyFn = appNotifier.notify;
|
|
1098
1191
|
|
|
1099
1192
|
// Persist layout changes (switching, saving, deleting, renaming layouts)
|
|
1100
1193
|
const prevLayouts = useRef(state.config.layouts);
|
|
@@ -1124,6 +1217,17 @@ function AppInner({ pluginRegistry, tickerRepository, dataProvider, marketData,
|
|
|
1124
1217
|
|
|
1125
1218
|
// Global keyboard shortcuts
|
|
1126
1219
|
useKeyboard((event) => {
|
|
1220
|
+
if (isCopyShortcut(event) && copyActiveSelection(renderer)) {
|
|
1221
|
+
event.preventDefault();
|
|
1222
|
+
event.stopPropagation();
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
if (isPasteShortcut(event) && pasteSystemClipboard(renderer)) {
|
|
1226
|
+
event.preventDefault();
|
|
1227
|
+
event.stopPropagation();
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1127
1231
|
// Skip all global shortcuts when a dialog is open
|
|
1128
1232
|
if (dialogOpen) return;
|
|
1129
1233
|
|
|
@@ -1250,11 +1354,10 @@ export function App({
|
|
|
1250
1354
|
});
|
|
1251
1355
|
const [showOnboarding, setShowOnboarding] = useState(!initialConfig.onboardingComplete);
|
|
1252
1356
|
|
|
1253
|
-
|
|
1357
|
+
// Keep the shared palette aligned before this render builds any JSX that reads `colors`.
|
|
1358
|
+
syncTheme(config.theme);
|
|
1254
1359
|
|
|
1255
|
-
|
|
1256
|
-
if (config.theme) applyTheme(config.theme);
|
|
1257
|
-
}, [config.theme]);
|
|
1360
|
+
useEffect(() => bindAppActivity(renderer), [renderer]);
|
|
1258
1361
|
|
|
1259
1362
|
const services = useMemo(() => {
|
|
1260
1363
|
const dbPath = join(config.dataDir, ".gloomberb-cache.db");
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
formatNumber,
|
|
5
5
|
formatPercent,
|
|
6
6
|
} from "../../utils/format";
|
|
7
|
+
import { formatMarketCostWithCurrency, formatMarketPriceWithCurrency, formatMarketQuantity } from "../../utils/market-format";
|
|
7
8
|
import {
|
|
8
9
|
cliStyles,
|
|
9
10
|
colorBySign,
|
|
@@ -77,6 +78,30 @@ function appendTextSection(lines: string[], title: string, content: string | und
|
|
|
77
78
|
lines.push(text);
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function normalizeTimestamp(value: Date | string | number | undefined): number | null {
|
|
82
|
+
if (value instanceof Date) {
|
|
83
|
+
const timestamp = value.getTime();
|
|
84
|
+
return Number.isNaN(timestamp) ? null : timestamp;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
88
|
+
const timestamp = new Date(value).getTime();
|
|
89
|
+
return Number.isNaN(timestamp) ? null : timestamp;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatFeedDate(value: Date | string | number | undefined): string {
|
|
96
|
+
const timestamp = normalizeTimestamp(value);
|
|
97
|
+
if (timestamp == null) return "";
|
|
98
|
+
return new Date(timestamp).toLocaleDateString("en-US", {
|
|
99
|
+
month: "short",
|
|
100
|
+
day: "numeric",
|
|
101
|
+
year: "numeric",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
80
105
|
function appendFeedSection(
|
|
81
106
|
lines: string[],
|
|
82
107
|
title: string,
|
|
@@ -215,16 +240,16 @@ export async function buildTickerReport({
|
|
|
215
240
|
: "—";
|
|
216
241
|
|
|
217
242
|
appendMetricSection(lines, "Quote", [
|
|
218
|
-
["Last", colorBySign(
|
|
243
|
+
["Last", colorBySign(formatMarketPriceWithCurrency(quote.price, quote.currency, { assetCategory: tickerFile?.metadata.assetCategory }), quote.change)],
|
|
219
244
|
["Change", colorBySign(`${formatSignedCurrency(quote.change, quote.currency)} (${formatSignedPercentRaw(quote.changePercent)})`, quote.change)],
|
|
220
|
-
["Open", quote.open != null ?
|
|
245
|
+
["Open", quote.open != null ? formatMarketPriceWithCurrency(quote.open, quote.currency, { assetCategory: tickerFile?.metadata.assetCategory }) : "—"],
|
|
221
246
|
["Day Range", quote.low != null || quote.high != null
|
|
222
|
-
? `${quote.low != null ?
|
|
247
|
+
? `${quote.low != null ? formatMarketPriceWithCurrency(quote.low, quote.currency, { assetCategory: tickerFile?.metadata.assetCategory }) : "—"} - ${quote.high != null ? formatMarketPriceWithCurrency(quote.high, quote.currency, { assetCategory: tickerFile?.metadata.assetCategory }) : "—"}`
|
|
223
248
|
: "—"],
|
|
224
249
|
["52W Range", quote.low52w != null || quote.high52w != null
|
|
225
|
-
? `${quote.low52w != null ?
|
|
250
|
+
? `${quote.low52w != null ? formatMarketPriceWithCurrency(quote.low52w, quote.currency, { assetCategory: tickerFile?.metadata.assetCategory }) : "—"} - ${quote.high52w != null ? formatMarketPriceWithCurrency(quote.high52w, quote.currency, { assetCategory: tickerFile?.metadata.assetCategory }) : "—"}`
|
|
226
251
|
: "—"],
|
|
227
|
-
["Bid / Ask", formatBidAsk(quote.bid, quote.ask, quote.bidSize, quote.askSize, quote.currency)],
|
|
252
|
+
["Bid / Ask", formatBidAsk(quote.bid, quote.ask, quote.bidSize, quote.askSize, quote.currency, tickerFile?.metadata.assetCategory)],
|
|
228
253
|
["Volume", quote.volume != null ? formatNumber(quote.volume, 0) : "—"],
|
|
229
254
|
["Updated", formatTimestamp(quote.lastUpdated)],
|
|
230
255
|
]);
|
|
@@ -232,13 +257,13 @@ export async function buildTickerReport({
|
|
|
232
257
|
appendMetricSection(lines, "Extended Hours", [
|
|
233
258
|
["Pre-Market", quote.preMarketPrice != null
|
|
234
259
|
? colorBySign(
|
|
235
|
-
`${
|
|
260
|
+
`${formatMarketPriceWithCurrency(quote.preMarketPrice, quote.currency, { assetCategory: tickerFile?.metadata.assetCategory })} (${formatSignedPercentRaw(quote.preMarketChangePercent ?? 0)})`,
|
|
236
261
|
quote.preMarketChange ?? 0,
|
|
237
262
|
)
|
|
238
263
|
: "—"],
|
|
239
264
|
["After Hours", quote.postMarketPrice != null
|
|
240
265
|
? colorBySign(
|
|
241
|
-
`${
|
|
266
|
+
`${formatMarketPriceWithCurrency(quote.postMarketPrice, quote.currency, { assetCategory: tickerFile?.metadata.assetCategory })} (${formatSignedPercentRaw(quote.postMarketChangePercent ?? 0)})`,
|
|
242
267
|
quote.postMarketChange ?? 0,
|
|
243
268
|
)
|
|
244
269
|
: "—"],
|
|
@@ -288,18 +313,20 @@ export async function buildTickerReport({
|
|
|
288
313
|
title: item.title,
|
|
289
314
|
meta: [
|
|
290
315
|
item.source,
|
|
291
|
-
|
|
316
|
+
(() => {
|
|
317
|
+
const publishedAt = normalizeTimestamp(item.publishedAt as Date | string | number | undefined);
|
|
318
|
+
return publishedAt == null ? "" : formatTimestamp(publishedAt);
|
|
319
|
+
})(),
|
|
292
320
|
],
|
|
293
321
|
body: item.summary,
|
|
294
322
|
link: item.url,
|
|
295
323
|
})));
|
|
296
324
|
|
|
297
325
|
appendFeedSection(lines, "Recent SEC Filings", recentSecFilings.map((filing) => ({
|
|
298
|
-
title:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
})}`,
|
|
326
|
+
title: (() => {
|
|
327
|
+
const filingDate = formatFeedDate(filing.filingDate as Date | string | number | undefined);
|
|
328
|
+
return filingDate ? `${filing.form} | ${filingDate}` : filing.form;
|
|
329
|
+
})(),
|
|
303
330
|
meta: [
|
|
304
331
|
filing.items ? `Items ${filing.items}` : "",
|
|
305
332
|
getFilingDescription(filing) ?? "",
|
|
@@ -320,12 +347,15 @@ export async function buildTickerReport({
|
|
|
320
347
|
const pnl = marketValueBase - costBasisBase;
|
|
321
348
|
|
|
322
349
|
lines.push(cliStyles.bold(`${portfolioName} (${position.broker})`));
|
|
323
|
-
lines.push(renderStat(
|
|
350
|
+
lines.push(renderStat(
|
|
351
|
+
"Position",
|
|
352
|
+
`${formatMarketQuantity(position.shares, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier })} ${multiplier > 1 ? "contracts" : "shares"} @ ${formatMarketCostWithCurrency(position.avgCost, positionCurrency, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier })}`,
|
|
353
|
+
));
|
|
324
354
|
lines.push(renderStat("Cost Basis", formatCurrency(costBasisBase, config.baseCurrency)));
|
|
325
355
|
lines.push(renderStat("Market Value", formatCurrency(marketValueBase, config.baseCurrency)));
|
|
326
356
|
lines.push(renderStat("P&L", colorBySign(formatSignedCurrency(pnl, config.baseCurrency), pnl)));
|
|
327
357
|
if (position.markPrice != null) {
|
|
328
|
-
lines.push(renderStat("Mark",
|
|
358
|
+
lines.push(renderStat("Mark", formatMarketPriceWithCurrency(position.markPrice, positionCurrency, { assetCategory: tickerFile.metadata.assetCategory, multiplier: position.multiplier })));
|
|
329
359
|
}
|
|
330
360
|
if (index < tickerFile.metadata.positions.length - 1) {
|
|
331
361
|
lines.push(cliStyles.muted("-".repeat(24)));
|
package/src/cli/helpers.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
formatNumber,
|
|
5
5
|
formatPercentRaw,
|
|
6
6
|
} from "../utils/format";
|
|
7
|
+
import { formatMarketPriceWithCurrency } from "../utils/market-format";
|
|
7
8
|
export { slugifyName } from "../utils/slugify";
|
|
8
9
|
import type { AppConfig } from "../types/config";
|
|
9
10
|
import type { Watchlist, TickerRecord } from "../types/ticker";
|
|
@@ -42,13 +43,14 @@ export function formatBidAsk(
|
|
|
42
43
|
bidSize: number | undefined,
|
|
43
44
|
askSize: number | undefined,
|
|
44
45
|
currency: string,
|
|
46
|
+
assetCategory?: string,
|
|
45
47
|
): string {
|
|
46
48
|
if (bid == null && ask == null) return "—";
|
|
47
49
|
const bidText = bid != null
|
|
48
|
-
? `${
|
|
50
|
+
? `${formatMarketPriceWithCurrency(bid, currency, { assetCategory })}${bidSize != null ? ` x ${formatNumber(bidSize, 0)}` : ""}`
|
|
49
51
|
: "—";
|
|
50
52
|
const askText = ask != null
|
|
51
|
-
? `${
|
|
53
|
+
? `${formatMarketPriceWithCurrency(ask, currency, { assetCategory })}${askSize != null ? ` x ${formatNumber(askSize, 0)}` : ""}`
|
|
52
54
|
: "—";
|
|
53
55
|
return `${bidText} / ${askText}`;
|
|
54
56
|
}
|
package/src/cli/registry.test.ts
CHANGED
|
@@ -186,41 +186,48 @@ describe("CLI dispatch", () => {
|
|
|
186
186
|
test("routes prediction market commands and aliases through launch-ui requests", async () => {
|
|
187
187
|
process.env.HOME = await createTempHome("gloomberb-cli-prediction-home-");
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
189
|
+
const fixedNow = Date.parse("2026-04-08T10:30:00Z");
|
|
190
|
+
const realDateNow = Date.now;
|
|
191
|
+
Date.now = () => fixedNow;
|
|
192
|
+
try {
|
|
193
|
+
for (const rootCommand of ["predictions", "prediction-markets", "pm"]) {
|
|
194
|
+
const dispatchResult = await dispatchCli([rootCommand, "world"]);
|
|
195
|
+
expect(dispatchResult.kind).toBe("launch-ui");
|
|
196
|
+
if (dispatchResult.kind !== "launch-ui") continue;
|
|
197
|
+
|
|
198
|
+
const config = createDefaultConfig("/tmp/gloomberb-cli-prediction");
|
|
199
|
+
const intent = parsePredictionCommandArgs(["world"]);
|
|
200
|
+
const expectedConfigResult = applyPredictionLaunchIntentToConfig(config, intent, {
|
|
201
|
+
width: 132,
|
|
202
|
+
height: 40,
|
|
203
|
+
});
|
|
204
|
+
const actualConfigResult = dispatchResult.request.applyConfig(config, {
|
|
205
|
+
terminalWidth: 132,
|
|
206
|
+
terminalHeight: 40,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
expect(actualConfigResult.config).toEqual(expectedConfigResult.config);
|
|
210
|
+
expect(actualConfigResult.launchState).toEqual({
|
|
211
|
+
paneInstanceId: expectedConfigResult.paneInstanceId,
|
|
212
|
+
intent,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const expectedSession = applyPredictionLaunchIntentToSessionSnapshot(
|
|
216
|
+
actualConfigResult.config,
|
|
217
|
+
null,
|
|
218
|
+
expectedConfigResult.paneInstanceId,
|
|
219
|
+
intent,
|
|
220
|
+
);
|
|
221
|
+
const actualSession = dispatchResult.request.applySessionSnapshot?.(
|
|
222
|
+
actualConfigResult.config,
|
|
223
|
+
null,
|
|
224
|
+
actualConfigResult.launchState,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(actualSession).toEqual(expectedSession);
|
|
228
|
+
}
|
|
229
|
+
} finally {
|
|
230
|
+
Date.now = realDateNow;
|
|
224
231
|
}
|
|
225
232
|
});
|
|
226
233
|
});
|
package/src/cli.test.ts
CHANGED
|
@@ -53,6 +53,7 @@ async function createCliFixture({
|
|
|
53
53
|
if (baseCurrency) {
|
|
54
54
|
config.baseCurrency = baseCurrency;
|
|
55
55
|
}
|
|
56
|
+
config.disabledPlugins = [...new Set([...(config.disabledPlugins ?? []), "yahoo", "gloomberb-cloud"])];
|
|
56
57
|
if (portfolios) {
|
|
57
58
|
config.portfolios = portfolios;
|
|
58
59
|
}
|
|
@@ -482,14 +483,14 @@ describe("buildTickerReport", () => {
|
|
|
482
483
|
title: "NVIDIA unveils next platform",
|
|
483
484
|
source: "Example News",
|
|
484
485
|
url: "https://example.com/nvda-platform",
|
|
485
|
-
publishedAt:
|
|
486
|
+
publishedAt: "2026-04-01T15:45:00.000Z" as unknown as Date,
|
|
486
487
|
summary: "Analysts expect the launch to expand datacenter demand.",
|
|
487
488
|
}];
|
|
488
489
|
|
|
489
490
|
const recentSecFilings: SecFilingItem[] = [{
|
|
490
491
|
accessionNumber: "0000000000-26-000001",
|
|
491
492
|
form: "8-K",
|
|
492
|
-
filingDate:
|
|
493
|
+
filingDate: "2026-03-31T00:00:00.000Z" as unknown as Date,
|
|
493
494
|
cik: "0001045810",
|
|
494
495
|
filingUrl: "https://www.sec.gov/Archives/example-8k",
|
|
495
496
|
primaryDocument: "nvda-8k.htm",
|
|
@@ -521,6 +522,7 @@ describe("buildTickerReport", () => {
|
|
|
521
522
|
expect(report).toContain("Recent News");
|
|
522
523
|
expect(report).toContain("NVIDIA unveils next platform");
|
|
523
524
|
expect(report).toContain("Example News");
|
|
525
|
+
expect(report).toContain("Apr 1, 2026");
|
|
524
526
|
expect(report).toContain("Recent SEC Filings");
|
|
525
527
|
expect(report).toContain("8-K | Mar 31, 2026");
|
|
526
528
|
expect(report).toContain("Current report announcing a product launch");
|