gloomberb 0.7.2 → 0.7.4
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 +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +158 -47
- package/src/components/chart/chart-types.ts +20 -2
- package/src/components/chart/chart.test.ts +179 -13
- package/src/components/chart/comparison-chart-renderer.test.ts +14 -30
- package/src/components/chart/comparison-chart-renderer.ts +50 -11
- package/src/components/chart/comparison-stock-chart.tsx +129 -129
- package/src/components/chart/market-session.ts +187 -0
- package/src/components/chart/native/chart-rasterizer.ts +108 -19
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +97 -1
- package/src/components/chart/stock-chart.tsx +95 -49
- package/src/components/chart/time-axis-label.tsx +56 -2
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +535 -384
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/list-model.ts +8 -4
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +21 -0
- package/src/components/command-bar/view-model.ts +17 -4
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +21 -3
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +15 -66
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/core/state/session-persistence.ts +25 -0
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator-subscriptions.test.ts +102 -18
- package/src/market-data/coordinator.test.ts +122 -2
- package/src/market-data/coordinator.ts +200 -56
- package/src/market-data/hooks.test.tsx +31 -0
- package/src/market-data/hooks.tsx +1 -0
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -0
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +263 -48
- package/src/plugins/builtin/chat.test.tsx +312 -6
- package/src/plugins/builtin/chat.tsx +245 -66
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/market-movers/index.tsx +72 -40
- package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +156 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -90
- package/src/plugins/builtin/news-wire/index.tsx +1 -3
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +13 -2
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +139 -23
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +311 -9
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +275 -43
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +18 -7
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +445 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/builtin/world-indices/index.tsx +58 -19
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +6 -4
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/sources/gloomberb-cloud.test.ts +55 -2
- package/src/sources/gloomberb-cloud.ts +180 -29
- package/src/sources/provider-router.test.ts +567 -10
- package/src/sources/provider-router.ts +323 -93
- package/src/sources/yahoo-finance.test.ts +37 -0
- package/src/sources/yahoo-finance.ts +48 -2
- package/src/state/app-bootstrap.test.ts +50 -23
- package/src/state/app-bootstrap.ts +29 -13
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/state/session-persistence.test.ts +5 -3
- package/src/state/use-inline-tickers.ts +3 -0
- package/src/state/use-quote-streaming.ts +11 -0
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +1 -0
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +248 -0
- package/src/utils/api-client.ts +178 -26
- package/src/utils/exchanges.test.ts +16 -0
- package/src/utils/exchanges.ts +33 -0
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/options.ts +1 -1
- package/src/utils/price-history.test.ts +28 -1
- package/src/utils/price-history.ts +16 -0
- package/src/utils/quote-freshness.test.ts +150 -0
- package/src/utils/quote-freshness.ts +42 -1
- package/src/utils/quote-resolution.ts +2 -0
- package/src/utils/quote-time.test.ts +34 -0
- package/src/utils/quote-time.ts +18 -5
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/digest-store.ts +0 -44
package/src/news/aggregator.ts
CHANGED
|
@@ -12,6 +12,9 @@ const MAX_ARTICLES = 500;
|
|
|
12
12
|
const DEFAULT_POLL_INTERVAL_MS = 2 * 60 * 1000;
|
|
13
13
|
const DEFAULT_GLOBAL_QUERY: NewsQuery = { feed: "latest", limit: MAX_ARTICLES };
|
|
14
14
|
const FEEDS = new Set<NewsFeed>(["latest", "top", "breaking", "ticker", "sector", "topic"]);
|
|
15
|
+
const DETAIL_CAPABLE_ARTICLE = Symbol("detail-capable-news-article");
|
|
16
|
+
|
|
17
|
+
type DetailCapableArticle = NewsArticle & { [DETAIL_CAPABLE_ARTICLE]?: true };
|
|
15
18
|
|
|
16
19
|
function normalizeTicker(ticker: string | undefined): string {
|
|
17
20
|
return ticker ? normalizeSymbol(ticker) : "";
|
|
@@ -101,6 +104,57 @@ function sortByPublishedAt(items: NewsArticle[]): NewsArticle[] {
|
|
|
101
104
|
return [...items].sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime());
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
function hasStoryItems(item: NewsArticle | null | undefined): boolean {
|
|
108
|
+
return (item?.items?.length ?? 0) > 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function markDetailCapableArticle(source: NewsCapability, item: NewsArticle): NewsArticle {
|
|
112
|
+
if (!source.provider.fetchNewsStory) return item;
|
|
113
|
+
Object.defineProperty(item, DETAIL_CAPABLE_ARTICLE, {
|
|
114
|
+
value: true,
|
|
115
|
+
configurable: true,
|
|
116
|
+
});
|
|
117
|
+
return item;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isDetailCapableArticle(item: NewsArticle): boolean {
|
|
121
|
+
return (item as DetailCapableArticle)[DETAIL_CAPABLE_ARTICLE] === true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function shouldReplaceDuplicate(existing: NewsArticle, item: NewsArticle): boolean {
|
|
125
|
+
return (
|
|
126
|
+
item.importance > existing.importance ||
|
|
127
|
+
(item.importance === existing.importance && item.publishedAt > existing.publishedAt)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function selectDetailArticle(
|
|
132
|
+
existing: NewsArticle,
|
|
133
|
+
item: NewsArticle,
|
|
134
|
+
winner: NewsArticle,
|
|
135
|
+
): NewsArticle | null {
|
|
136
|
+
if (isDetailCapableArticle(winner)) return winner;
|
|
137
|
+
if (isDetailCapableArticle(item)) return item;
|
|
138
|
+
if (isDetailCapableArticle(existing)) return existing;
|
|
139
|
+
if (hasStoryItems(winner)) return winner;
|
|
140
|
+
if (hasStoryItems(item)) return item;
|
|
141
|
+
if (hasStoryItems(existing)) return existing;
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function mergeDuplicateArticle(existing: NewsArticle, item: NewsArticle): NewsArticle {
|
|
146
|
+
const winner = shouldReplaceDuplicate(existing, item)
|
|
147
|
+
? { ...existing, ...item }
|
|
148
|
+
: existing;
|
|
149
|
+
const detailArticle = selectDetailArticle(existing, item, winner);
|
|
150
|
+
if (!detailArticle || detailArticle.id === winner.id) return winner;
|
|
151
|
+
return {
|
|
152
|
+
...winner,
|
|
153
|
+
id: detailArticle.id,
|
|
154
|
+
items: hasStoryItems(detailArticle) ? detailArticle.items : winner.items,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
104
158
|
function dedupeArticles(items: NewsArticle[]): NewsArticle[] {
|
|
105
159
|
const byKey = new Map<string, NewsArticle>();
|
|
106
160
|
for (const item of items) {
|
|
@@ -110,12 +164,7 @@ function dedupeArticles(items: NewsArticle[]): NewsArticle[] {
|
|
|
110
164
|
byKey.set(key, item);
|
|
111
165
|
continue;
|
|
112
166
|
}
|
|
113
|
-
|
|
114
|
-
item.importance > existing.importance ||
|
|
115
|
-
(item.importance === existing.importance && item.publishedAt > existing.publishedAt)
|
|
116
|
-
) {
|
|
117
|
-
byKey.set(key, { ...existing, ...item });
|
|
118
|
-
}
|
|
167
|
+
byKey.set(key, mergeDuplicateArticle(existing, item));
|
|
119
168
|
}
|
|
120
169
|
return sortByPublishedAt([...byKey.values()]).slice(0, MAX_ARTICLES);
|
|
121
170
|
}
|
|
@@ -382,7 +431,8 @@ export class NewsService {
|
|
|
382
431
|
let firstEmpty: SourceFetchResult | null = null;
|
|
383
432
|
for (const source of sources) {
|
|
384
433
|
try {
|
|
385
|
-
const articles = await source.provider.fetchNews(query)
|
|
434
|
+
const articles = (await source.provider.fetchNews(query))
|
|
435
|
+
.map((article) => markDetailCapableArticle(source, article));
|
|
386
436
|
const result = { articles, sourceIds: [newsCapabilitySourceId(source)] };
|
|
387
437
|
if (articles.length > 0) return result;
|
|
388
438
|
firstEmpty ??= result;
|
|
@@ -397,7 +447,8 @@ export class NewsService {
|
|
|
397
447
|
const settled = await Promise.allSettled(
|
|
398
448
|
sources.map(async (source) => ({
|
|
399
449
|
source,
|
|
400
|
-
articles: await source.provider.fetchNews(query)
|
|
450
|
+
articles: (await source.provider.fetchNews(query))
|
|
451
|
+
.map((article) => markDetailCapableArticle(source, article)),
|
|
401
452
|
})),
|
|
402
453
|
);
|
|
403
454
|
const articles: NewsArticle[] = [];
|
|
@@ -418,7 +469,8 @@ export class NewsService {
|
|
|
418
469
|
let changed = false;
|
|
419
470
|
for (const query of queries) {
|
|
420
471
|
if (source.isEnabled?.() === false || news.supports?.(query) === false) continue;
|
|
421
|
-
const cached = news.getCachedNews?.(query) ?? []
|
|
472
|
+
const cached = (news.getCachedNews?.(query) ?? [])
|
|
473
|
+
.map((article) => markDetailCapableArticle(source, article));
|
|
422
474
|
if (cached.length === 0) continue;
|
|
423
475
|
const key = buildNewsQueryKey(query);
|
|
424
476
|
const current = this.queryStates.get(key) ?? createIdleState();
|
|
@@ -300,13 +300,32 @@ export function AskAiDetailTab({ width, height, focused, onCapture }: DetailTabP
|
|
|
300
300
|
nativePaneChrome,
|
|
301
301
|
terminalBottomInset: terminalFooterClearance,
|
|
302
302
|
});
|
|
303
|
-
const chatHeight = Math.max(height - composerBlockHeight, 0);
|
|
303
|
+
const chatHeight = nativePaneChrome ? undefined : Math.max(height - composerBlockHeight, 0);
|
|
304
|
+
const layoutHeight = nativePaneChrome ? "100%" : height;
|
|
305
|
+
const nativeFillStyle = nativePaneChrome ? { minHeight: 0 } : undefined;
|
|
304
306
|
|
|
305
307
|
return (
|
|
306
|
-
<Box
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
308
|
+
<Box
|
|
309
|
+
flexDirection="column"
|
|
310
|
+
paddingX={nativePaneChrome ? 0 : 1}
|
|
311
|
+
height={layoutHeight}
|
|
312
|
+
flexGrow={nativePaneChrome ? 1 : undefined}
|
|
313
|
+
overflow="hidden"
|
|
314
|
+
style={nativeFillStyle}
|
|
315
|
+
>
|
|
316
|
+
{(nativePaneChrome || (chatHeight ?? 0) > 0) && (
|
|
317
|
+
<ScrollBox
|
|
318
|
+
ref={scrollRef}
|
|
319
|
+
height={nativePaneChrome ? undefined : chatHeight}
|
|
320
|
+
flexGrow={nativePaneChrome ? 1 : undefined}
|
|
321
|
+
scrollY
|
|
322
|
+
style={nativeFillStyle}
|
|
323
|
+
>
|
|
324
|
+
<Box
|
|
325
|
+
flexDirection="column"
|
|
326
|
+
paddingX={nativePaneChrome ? 1 : 0}
|
|
327
|
+
style={nativeFillStyle}
|
|
328
|
+
>
|
|
310
329
|
{messages.length === 0 ? (
|
|
311
330
|
<Box paddingTop={1}>
|
|
312
331
|
<Text fg={colors.textDim}>
|
|
@@ -354,7 +373,7 @@ export function AskAiDetailTab({ width, height, focused, onCapture }: DetailTabP
|
|
|
354
373
|
placeholder="Ask a question..."
|
|
355
374
|
terminalPrefix=" > "
|
|
356
375
|
terminalBottomInset={terminalFooterClearance}
|
|
357
|
-
width={nativePaneChrome ?
|
|
376
|
+
width={nativePaneChrome ? "100%" : contentWidth}
|
|
358
377
|
height={composerHeight}
|
|
359
378
|
onFocusRequest={focusInput}
|
|
360
379
|
onInput={(value) => setInputValue(value)}
|
|
@@ -432,6 +432,9 @@ export function AiScreenerPane({ focused, width, height }: PaneProps) {
|
|
|
432
432
|
const quoteTargets = useMemo(() => (
|
|
433
433
|
sortedTickers
|
|
434
434
|
.map((ticker) => quoteSubscriptionTargetFromTicker(ticker, ticker.metadata.ticker))
|
|
435
|
+
.map((target) => target
|
|
436
|
+
? { ...target, surface: "screener" as const, visible: true, weight: 70 }
|
|
437
|
+
: null)
|
|
435
438
|
.filter((target): target is NonNullable<ReturnType<typeof quoteSubscriptionTargetFromTicker>> => target != null)
|
|
436
439
|
), [sortedTickers]);
|
|
437
440
|
|
|
@@ -60,6 +60,9 @@ export function resolveVisibleAiScreenerColumns(columnIds: string[]): ColumnConf
|
|
|
60
60
|
export function buildAiScreenerPaneSettingsDef(settings: AiScreenerPaneSettings): PaneSettingsDef {
|
|
61
61
|
return {
|
|
62
62
|
title: "AI Screener Settings",
|
|
63
|
+
values: {
|
|
64
|
+
columnIds: [...settings.columnIds],
|
|
65
|
+
},
|
|
63
66
|
fields: [
|
|
64
67
|
{
|
|
65
68
|
key: "columnIds",
|
|
@@ -32,6 +32,7 @@ import type { GloomPlugin, PaneProps } from "../../../types/plugin";
|
|
|
32
32
|
import type { BrokerAccount } from "../../../types/trading";
|
|
33
33
|
import { useDialog, type PromptContext } from "../../../ui/dialog";
|
|
34
34
|
import { formatCurrency } from "../../../utils/format";
|
|
35
|
+
import { isPlainKey } from "../../../utils/keyboard";
|
|
35
36
|
import { usePluginAppActions, usePluginBrokerActions } from "../../plugin-runtime";
|
|
36
37
|
import {
|
|
37
38
|
buildBrokerProfileRows,
|
|
@@ -47,7 +48,7 @@ type BrokerColumn = DataTableColumn & { id: BrokerColumnId };
|
|
|
47
48
|
function stateColor(state: BrokerDisplayState): string {
|
|
48
49
|
switch (state) {
|
|
49
50
|
case "connected": return colors.positive;
|
|
50
|
-
case "connecting": return
|
|
51
|
+
case "connecting": return colors.warning;
|
|
51
52
|
case "error": return colors.negative;
|
|
52
53
|
case "disabled": return colors.textMuted;
|
|
53
54
|
case "unavailable": return colors.negative;
|
|
@@ -442,13 +443,13 @@ export function BrokersPane({ focused, width, height }: PaneProps) {
|
|
|
442
443
|
saveEdit().catch(() => {});
|
|
443
444
|
return;
|
|
444
445
|
}
|
|
445
|
-
if (event
|
|
446
|
+
if (isPlainKey(event, "up", "k")) {
|
|
446
447
|
event.stopPropagation();
|
|
447
448
|
const index = editKeys.indexOf(activeEditKey);
|
|
448
449
|
setActiveEditKey(editKeys[Math.max(0, index - 1)] ?? "label");
|
|
449
450
|
return;
|
|
450
451
|
}
|
|
451
|
-
if (event
|
|
452
|
+
if (isPlainKey(event, "down", "j", "tab")) {
|
|
452
453
|
event.stopPropagation();
|
|
453
454
|
const index = editKeys.indexOf(activeEditKey);
|
|
454
455
|
setActiveEditKey(editKeys[Math.min(editKeys.length - 1, index + 1)] ?? "label");
|