gloomberb 0.8.1 → 0.9.0
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 +106 -54
- package/package.json +14 -13
- package/patches/{@opentui%2Fcore@0.1.90.patch → @opentui%2Fcore@0.3.2.patch} +175 -90
- package/src/api-client/chat.ts +8 -0
- package/src/api-client/index.test.ts +30 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/types.ts +1 -0
- package/src/app/global-shortcuts.test.tsx +54 -4
- package/src/app/global-shortcuts.ts +11 -5
- package/src/capabilities/factories.ts +4 -0
- package/src/components/chart/comparison/data.test.ts +54 -0
- package/src/components/chart/comparison/data.ts +47 -24
- package/src/components/chart/core/projection.ts +51 -27
- package/src/components/command-bar/action-runtime.ts +1 -0
- package/src/components/command-bar/layout-items/current-layout.test.ts +115 -0
- package/src/components/command-bar/layout-items/current-layout.ts +33 -0
- package/src/components/command-bar/list/view.tsx +3 -1
- package/src/components/command-bar/pane-templates/items.ts +4 -1
- package/src/components/command-bar/panel/body.tsx +1 -0
- package/src/components/command-bar/routes/root/results.ts +0 -30
- package/src/components/command-bar/routes/root/runtime.ts +2 -20
- package/src/components/command-bar/routes/ticker-search/actions.ts +42 -4
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -1
- package/src/components/command-bar/routing/launch-request.ts +14 -7
- package/src/components/command-bar/routing/list-state.ts +1 -3
- package/src/components/command-bar/surface/index.test.tsx +76 -4
- package/src/components/command-bar/surface/index.tsx +0 -2
- package/src/components/command-bar/surface/pane.test.tsx +1 -0
- package/src/components/command-bar/surface/test-harness.tsx +1 -0
- package/src/components/command-bar/view-model.ts +3 -3
- package/src/components/data-table/stack-view.test.tsx +14 -11
- package/src/components/data-table/stack-view.tsx +1 -2
- package/src/components/data-table/view.test.tsx +24 -18
- package/src/components/data-table/view.tsx +313 -83
- package/src/components/feed-data-table/stack-view.tsx +6 -8
- package/src/components/layout/pane/footer/index.test.tsx +6 -0
- package/src/components/layout/pane/footer/index.tsx +14 -2
- package/src/components/layout/pane/title.ts +5 -1
- package/src/components/layout/shell/index.test.tsx +120 -2
- package/src/components/layout/shell/index.tsx +2 -0
- package/src/components/layout/shell/menu.ts +1 -7
- package/src/components/layout/shell/pane/actions.ts +8 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +12 -2
- package/src/components/layout/shell/shortcuts.ts +14 -6
- package/src/components/layout/shell/window-mode/index.ts +8 -3
- package/src/components/layout/window-edit/presentation.ts +4 -4
- package/src/components/onboarding/onboarding-steps.tsx +3 -2
- package/src/components/pane-settings-dialog/field-dialogs.tsx +0 -2
- package/src/components/pane-settings-dialog/tui.tsx +1 -1
- package/src/components/ticker/list-table-view.tsx +17 -25
- package/src/components/ui/choice-dialog.tsx +12 -2
- package/src/components/ui/multi-select/dialog.tsx +17 -5
- package/src/components/ui/remote-image.tsx +1 -14
- package/src/components/ui/shortcut-hint.tsx +14 -2
- package/src/components/ui/table-layout.test.ts +2 -2
- package/src/components/ui/table-layout.ts +8 -2
- package/src/components/ui/tabs.tsx +115 -86
- package/src/components/ui/ui.test.tsx +83 -4
- package/src/core/state/app/types.ts +12 -4
- package/src/market-data/coordinator/auxiliary.ts +29 -1
- package/src/market-data/coordinator/index.ts +17 -1
- package/src/market-data/hooks.tsx +17 -1
- package/src/market-data/selectors.ts +4 -0
- package/src/plugins/builtin/account-management/form-components.tsx +0 -4
- package/src/plugins/builtin/account-management/model.test.ts +54 -0
- package/src/plugins/builtin/account-management/model.ts +13 -3
- package/src/plugins/builtin/account-management/pane.tsx +5 -11
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +5 -3
- package/src/plugins/builtin/analytics/index.tsx +0 -3
- package/src/plugins/builtin/analytics/view.tsx +6 -5
- package/src/plugins/builtin/broker-manager/index.tsx +6 -6
- package/src/plugins/builtin/buildout/pane/index.tsx +6 -6
- package/src/plugins/builtin/changelog/index.tsx +40 -27
- package/src/plugins/builtin/changelog/model.test.ts +77 -0
- package/src/plugins/builtin/changelog/model.ts +98 -0
- package/src/plugins/builtin/chat/channel-labels.ts +53 -0
- package/src/plugins/builtin/chat/channels.test.ts +28 -0
- package/src/plugins/builtin/chat/channels.ts +36 -19
- package/src/plugins/builtin/chat/chat.test.tsx +264 -5
- package/src/plugins/builtin/chat/content/channel-navigation.ts +50 -11
- package/src/plugins/builtin/chat/content/composer-runtime.test.tsx +144 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +151 -20
- package/src/plugins/builtin/chat/content/composer.tsx +23 -2
- package/src/plugins/builtin/chat/content/index.tsx +66 -4
- package/src/plugins/builtin/chat/content/layout-metrics.ts +3 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +27 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +8 -0
- package/src/plugins/builtin/chat/content/types.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +41 -0
- package/src/plugins/builtin/chat/controller/messages.ts +18 -0
- package/src/plugins/builtin/chat/controller/notifications.ts +10 -4
- package/src/plugins/builtin/chat/controller/utils.ts +6 -3
- package/src/plugins/builtin/chat/controller/view.ts +1 -1
- package/src/plugins/builtin/chat/controller.test.ts +189 -0
- package/src/plugins/builtin/chat/edit-window.ts +24 -0
- package/src/plugins/builtin/chat/layout.ts +94 -31
- package/src/plugins/builtin/chat/message/action-chip.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +53 -20
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +16 -10
- package/src/plugins/builtin/chat/message/render-state.ts +5 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +49 -20
- package/src/plugins/builtin/chat/message/types.ts +2 -0
- package/src/plugins/builtin/chat/pane.tsx +35 -7
- package/src/plugins/builtin/chat/sidebar.test.tsx +72 -0
- package/src/plugins/builtin/chat/status-widget.tsx +29 -11
- package/src/plugins/builtin/chat/test-harness.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +7 -11
- package/src/plugins/builtin/congress-trades/pane.tsx +14 -22
- package/src/plugins/builtin/earnings/index.tsx +8 -7
- package/src/plugins/builtin/econ/index.tsx +6 -8
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/help/index.test.tsx +105 -30
- package/src/plugins/builtin/help/index.tsx +326 -164
- package/src/plugins/builtin/help/shortcut-model.ts +5 -6
- package/src/plugins/builtin/holders/pane.tsx +7 -6
- package/src/plugins/builtin/market-movers/index.tsx +6 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +7 -6
- package/src/plugins/builtin/options/view.tsx +16 -8
- package/src/plugins/builtin/portfolio-list/column-values.ts +304 -1
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +93 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +7 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +25 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +281 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +27 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +10 -68
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +358 -7
- package/src/plugins/builtin/research/index.test.ts +30 -1
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +6 -8
- package/src/plugins/builtin/sec/filing-documents.ts +66 -0
- package/src/plugins/builtin/sec/index.tsx +132 -20
- package/src/plugins/builtin/sectors/index.tsx +13 -23
- package/src/plugins/builtin/shared/ticker-request.ts +1 -0
- package/src/plugins/builtin/substack/api/auth.ts +185 -0
- package/src/plugins/builtin/substack/api/cache.ts +78 -0
- package/src/plugins/builtin/substack/api/loaders.ts +146 -0
- package/src/plugins/builtin/substack/api/store.ts +186 -0
- package/src/plugins/builtin/substack/api/types.ts +40 -0
- package/src/plugins/builtin/substack/api.test.ts +298 -0
- package/src/plugins/builtin/substack/article-detail.tsx +359 -0
- package/src/plugins/builtin/substack/article-stack.tsx +129 -0
- package/src/plugins/builtin/substack/content.ts +478 -0
- package/src/plugins/builtin/substack/feed-tabs.tsx +41 -0
- package/src/plugins/builtin/substack/index.tsx +49 -0
- package/src/plugins/builtin/substack/login-view.tsx +126 -0
- package/src/plugins/builtin/substack/model.test.ts +155 -0
- package/src/plugins/builtin/substack/normalize.ts +343 -0
- package/src/plugins/builtin/substack/pane-footer.ts +71 -0
- package/src/plugins/builtin/substack/pane-state.ts +170 -0
- package/src/plugins/builtin/substack/pane.tsx +500 -0
- package/src/plugins/builtin/substack/table.ts +111 -0
- package/src/plugins/builtin/substack/types.ts +71 -0
- package/src/plugins/builtin/substack/utils.ts +131 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +26 -25
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +6 -5
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +10 -8
- package/src/plugins/builtin/ticker-detail/index.test.tsx +16 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/pane.tsx +13 -1
- package/src/plugins/builtin/world-indices/index.tsx +9 -6
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/pane-manager/layout-state.ts +11 -0
- package/src/plugins/pane-manager.ts +1 -1
- package/src/plugins/prediction-markets/pane.tsx +10 -9
- package/src/plugins/registry/index.ts +3 -0
- package/src/plugins/runtime/context.tsx +1 -0
- package/src/plugins/runtime/index.tsx +4 -0
- package/src/react/use-throttled-commit-value.ts +116 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +42 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +3 -2
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -2
- package/src/renderers/electrobun/view/host/input.tsx +23 -2
- package/src/renderers/electrobun/view/http-fetch.ts +10 -3
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -0
- package/src/sources/provider-router/cache.ts +1 -0
- package/src/sources/provider-router/document-routes.ts +45 -0
- package/src/sources/provider-router/index.ts +4 -0
- package/src/sources/sec-edgar/content.ts +9 -3
- package/src/sources/sec-edgar.test.ts +108 -1
- package/src/sources/sec-edgar.ts +105 -1
- package/src/sources/yahoo-finance.ts +4 -0
- package/src/state/app/context/index.test.tsx +16 -0
- package/src/test-support/plugin-runtime.ts +1 -0
- package/src/tickers/search/index.ts +1 -0
- package/src/types/data-provider.ts +11 -0
- package/src/ui/host.tsx +1 -0
- package/src/version.ts +1 -1
|
@@ -8,10 +8,6 @@ import type { TickerSearchCandidate } from "../../../../tickers/search";
|
|
|
8
8
|
import { matchPrefix, type Command } from "../../commands/registry";
|
|
9
9
|
import type { ResultItem } from "../../list/model";
|
|
10
10
|
import type { CommandBarRoute } from "../../workflow/types";
|
|
11
|
-
import {
|
|
12
|
-
isLikelyPlainTickerSearch,
|
|
13
|
-
normalizeCommandTickerSearchText,
|
|
14
|
-
} from "../ticker-search/results";
|
|
15
11
|
import { useTickerSearchRouteResults } from "../ticker-search/route";
|
|
16
12
|
import { buildRootResultModel, type RootResultModel } from "./results";
|
|
17
13
|
import { useRootProviderSearch } from "./provider-search";
|
|
@@ -47,7 +43,6 @@ interface UseCommandBarRootRuntimeOptions {
|
|
|
47
43
|
getTickers(): AppState["tickers"];
|
|
48
44
|
hasPaneSettings(paneId: string): boolean;
|
|
49
45
|
localTickerSearchResultItems(query?: string, options?: { category?: string; limit?: number }): ResultItem[];
|
|
50
|
-
mapTickerSearchCandidateToResultItem(candidate: TickerSearchCandidate): ResultItem;
|
|
51
46
|
nativeListScrollRef: RefObject<ScrollBoxRenderable | null>;
|
|
52
47
|
nonShortcutPaneTemplateItems(filterQuery?: string): ResultItem[];
|
|
53
48
|
openModeRoute(screen: "ticker-search" | "plugins" | "layout", initialQuery?: string): void;
|
|
@@ -101,7 +96,6 @@ export function useCommandBarRootRuntime({
|
|
|
101
96
|
getTickers,
|
|
102
97
|
hasPaneSettings,
|
|
103
98
|
localTickerSearchResultItems,
|
|
104
|
-
mapTickerSearchCandidateToResultItem,
|
|
105
99
|
nativeListScrollRef,
|
|
106
100
|
nonShortcutPaneTemplateItems,
|
|
107
101
|
openModeRoute,
|
|
@@ -178,7 +172,6 @@ export function useCommandBarRootRuntime({
|
|
|
178
172
|
getAvailablePaneShortcutTemplates,
|
|
179
173
|
hasPaneSettings,
|
|
180
174
|
localTickerSearchResultItems,
|
|
181
|
-
mapTickerSearchCandidateToResultItem,
|
|
182
175
|
nonShortcutPaneTemplateItems,
|
|
183
176
|
openModeRoute,
|
|
184
177
|
paneShortcutItems,
|
|
@@ -206,7 +199,6 @@ export function useCommandBarRootRuntime({
|
|
|
206
199
|
getAvailablePaneShortcutTemplates,
|
|
207
200
|
hasPaneSettings,
|
|
208
201
|
localTickerSearchResultItems,
|
|
209
|
-
mapTickerSearchCandidateToResultItem,
|
|
210
202
|
nonShortcutPaneTemplateItems,
|
|
211
203
|
openModeRoute,
|
|
212
204
|
paneShortcutItems,
|
|
@@ -220,20 +212,10 @@ export function useCommandBarRootRuntime({
|
|
|
220
212
|
tickerActionItems,
|
|
221
213
|
]);
|
|
222
214
|
|
|
223
|
-
const rootPlainTickerSearchArg = useMemo(() => {
|
|
224
|
-
if (activeMatch || rootShortcutIntent.kind !== "none" || !isLikelyPlainTickerSearch(rootQuery)) return null;
|
|
225
|
-
const normalizedQuery = normalizeCommandTickerSearchText(rootQuery);
|
|
226
|
-
const hasExactNonTickerResult = rootResultModel.items.some((item) => (
|
|
227
|
-
item.kind !== "ticker"
|
|
228
|
-
&& item.kind !== "search"
|
|
229
|
-
&& normalizeCommandTickerSearchText(item.label) === normalizedQuery
|
|
230
|
-
));
|
|
231
|
-
return hasExactNonTickerResult ? null : rootQuery.trim();
|
|
232
|
-
}, [activeMatch, rootQuery, rootResultModel.items, rootShortcutIntent.kind]);
|
|
233
215
|
const rootSecurityDescriptionArg = activeMatch?.command.id === "security-description" && activeMatch.arg.length >= 1
|
|
234
216
|
? activeMatch.arg
|
|
235
217
|
: null;
|
|
236
|
-
const rootTickerSearchArg = rootSecurityDescriptionArg
|
|
218
|
+
const rootTickerSearchArg = rootSecurityDescriptionArg;
|
|
237
219
|
|
|
238
220
|
useEffect(() => {
|
|
239
221
|
if (currentRoute) return;
|
|
@@ -272,7 +254,7 @@ export function useCommandBarRootRuntime({
|
|
|
272
254
|
localTickerSearchResultItems,
|
|
273
255
|
portfolios: state.config.portfolios,
|
|
274
256
|
readTickerSearchCache,
|
|
275
|
-
rootPlainTickerSearchArg,
|
|
257
|
+
rootPlainTickerSearchArg: null,
|
|
276
258
|
rootResultItems: rootResultModel.items,
|
|
277
259
|
rootTickerSearchArg,
|
|
278
260
|
tickers: state.tickers,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
rankTickerSearchItems,
|
|
7
7
|
upsertTickerFromSearchResult,
|
|
8
8
|
type TickerSearchCandidate,
|
|
9
|
+
type TickerSearchInstrumentClass,
|
|
9
10
|
} from "../../../../tickers/search";
|
|
10
11
|
import type { ResultItem } from "../../list/model";
|
|
11
12
|
import {
|
|
@@ -45,12 +46,15 @@ export function useCommandBarTickerSearchActions({
|
|
|
45
46
|
}, [tickerRepository, dispatch, pluginRegistry.events, focusTicker, closeAll]);
|
|
46
47
|
|
|
47
48
|
const mapTickerSearchCandidateToResultItem = useCallback((candidate: TickerSearchCandidate): ResultItem => {
|
|
49
|
+
const detail = formatTickerSearchDetail(candidate);
|
|
50
|
+
const right = formatTickerSearchRight(candidate);
|
|
51
|
+
|
|
48
52
|
if (candidate.kind === "ticker" && candidate.ticker) {
|
|
49
53
|
return {
|
|
50
54
|
id: candidate.id,
|
|
51
55
|
label: candidate.label,
|
|
52
|
-
detail
|
|
53
|
-
right
|
|
56
|
+
detail,
|
|
57
|
+
right,
|
|
54
58
|
category: candidate.category,
|
|
55
59
|
kind: "ticker",
|
|
56
60
|
secondaryAction: () => {
|
|
@@ -67,8 +71,8 @@ export function useCommandBarTickerSearchActions({
|
|
|
67
71
|
return {
|
|
68
72
|
id: candidate.id,
|
|
69
73
|
label: candidate.label,
|
|
70
|
-
detail
|
|
71
|
-
right
|
|
74
|
+
detail,
|
|
75
|
+
right,
|
|
72
76
|
category: candidate.category,
|
|
73
77
|
kind: "search",
|
|
74
78
|
secondaryAction: () => openTickerResearch(candidate.result!, { forceNewPane: true }),
|
|
@@ -140,3 +144,37 @@ export function useCommandBarTickerSearchActions({
|
|
|
140
144
|
writeTickerSearchCache,
|
|
141
145
|
};
|
|
142
146
|
}
|
|
147
|
+
|
|
148
|
+
function formatTickerSearchRight(candidate: TickerSearchCandidate): string | undefined {
|
|
149
|
+
const assetClass = formatInstrumentClass(candidate.instrumentClass);
|
|
150
|
+
const exchange = candidate.exchangeLabel || candidate.primaryExchangeLabel || candidate.right || "";
|
|
151
|
+
if (!exchange) return assetClass;
|
|
152
|
+
if (assetClass === "Other") return exchange;
|
|
153
|
+
return `${assetClass} ${exchange}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatTickerSearchDetail(candidate: TickerSearchCandidate): string {
|
|
157
|
+
const metadata = [
|
|
158
|
+
formatInstrumentClass(candidate.instrumentClass),
|
|
159
|
+
candidate.exchangeLabel,
|
|
160
|
+
candidate.primaryExchangeLabel && candidate.primaryExchangeLabel !== candidate.exchangeLabel
|
|
161
|
+
? candidate.primaryExchangeLabel
|
|
162
|
+
: null,
|
|
163
|
+
].filter((value): value is string => !!value);
|
|
164
|
+
return metadata.length > 0
|
|
165
|
+
? `${candidate.detail} | ${metadata.join(" | ")}`
|
|
166
|
+
: candidate.detail;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function formatInstrumentClass(instrumentClass: TickerSearchInstrumentClass): string {
|
|
170
|
+
switch (instrumentClass) {
|
|
171
|
+
case "equity":
|
|
172
|
+
return "Equity";
|
|
173
|
+
case "fund":
|
|
174
|
+
return "Fund";
|
|
175
|
+
case "derivative":
|
|
176
|
+
return "Derivative";
|
|
177
|
+
case "other":
|
|
178
|
+
return "Other";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -65,7 +65,10 @@ export function useTickerSearchRouteResults(options: {
|
|
|
65
65
|
|
|
66
66
|
const searchQuery = routeQuery.trim();
|
|
67
67
|
if (!searchQuery) {
|
|
68
|
-
|
|
68
|
+
searchRequestIdRef.current += 1;
|
|
69
|
+
setTickerSearchPending(false);
|
|
70
|
+
setTickerSearchResults(localTickerSearchResultItems(undefined, { limit: 8 }));
|
|
71
|
+
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
|
|
69
72
|
return;
|
|
70
73
|
}
|
|
71
74
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
2
|
import type { PluginRegistry } from "../../../plugins/registry";
|
|
3
|
+
import type { AppState } from "../../../state/app/context";
|
|
3
4
|
import type { CommandDef } from "../../../types/plugin";
|
|
4
5
|
|
|
5
|
-
interface CommandBarPluginLaunchRequest {
|
|
6
|
-
kind: "plugin-command";
|
|
7
|
-
commandId: string;
|
|
8
|
-
sequence: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
6
|
interface UseCommandBarLaunchRequestOptions {
|
|
12
7
|
activeTickerSymbol: string | null;
|
|
13
|
-
commandBarLaunchRequest:
|
|
8
|
+
commandBarLaunchRequest: AppState["commandBarLaunchRequest"];
|
|
14
9
|
commandBarOpen: boolean;
|
|
10
|
+
openModeRoute: (
|
|
11
|
+
screen: "ticker-search" | "plugins" | "layout",
|
|
12
|
+
initialQuery?: string,
|
|
13
|
+
payload?: Record<string, unknown>,
|
|
14
|
+
) => void;
|
|
15
15
|
openPluginCommandWorkflow: (
|
|
16
16
|
command: CommandDef,
|
|
17
17
|
options?: { values?: Record<string, string> },
|
|
@@ -23,6 +23,7 @@ export function useCommandBarLaunchRequest({
|
|
|
23
23
|
activeTickerSymbol,
|
|
24
24
|
commandBarLaunchRequest,
|
|
25
25
|
commandBarOpen,
|
|
26
|
+
openModeRoute,
|
|
26
27
|
openPluginCommandWorkflow,
|
|
27
28
|
pluginRegistry,
|
|
28
29
|
}: UseCommandBarLaunchRequestOptions) {
|
|
@@ -38,6 +39,11 @@ export function useCommandBarLaunchRequest({
|
|
|
38
39
|
if (processedLaunchSequenceRef.current === launch.sequence) return;
|
|
39
40
|
processedLaunchSequenceRef.current = launch.sequence;
|
|
40
41
|
|
|
42
|
+
if (launch.kind === "ticker-search") {
|
|
43
|
+
openModeRoute("ticker-search", launch.query ?? "");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
const command = pluginRegistry.commands.get(launch.commandId);
|
|
42
48
|
if (!command?.wizard || command.wizard.length === 0) return;
|
|
43
49
|
let values: Record<string, string> | undefined;
|
|
@@ -53,6 +59,7 @@ export function useCommandBarLaunchRequest({
|
|
|
53
59
|
activeTickerSymbol,
|
|
54
60
|
commandBarLaunchRequest,
|
|
55
61
|
commandBarOpen,
|
|
62
|
+
openModeRoute,
|
|
56
63
|
openPluginCommandWorkflow,
|
|
57
64
|
pluginRegistry,
|
|
58
65
|
]);
|
|
@@ -125,9 +125,7 @@ function buildRouteListState(options: BuildRouteListStateOptions): ListScreenSta
|
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
case "ticker-search": {
|
|
128
|
-
const results =
|
|
129
|
-
? tickerSearchResults.map((item) => adaptTickerSearchRouteResult(item, currentRoute.payload))
|
|
130
|
-
: [];
|
|
128
|
+
const results = tickerSearchResults.map((item) => adaptTickerSearchRouteResult(item, currentRoute.payload));
|
|
131
129
|
const emptyState = getEmptyState("search", currentRoute.query, currentRoute.query);
|
|
132
130
|
return {
|
|
133
131
|
kind: "mode",
|
|
@@ -81,13 +81,35 @@ describe("CommandBar", () => {
|
|
|
81
81
|
|
|
82
82
|
const frame = testSetup.captureCharFrame();
|
|
83
83
|
expect(frame).toContain("Commands");
|
|
84
|
-
expect(frame).toContain("Tickers");
|
|
85
|
-
expect(frame).toContain("AAPL");
|
|
84
|
+
expect(frame).not.toContain("Tickers");
|
|
86
85
|
expect(frame).toContain("Panes");
|
|
87
86
|
expect(frame).toContain("Portfolio");
|
|
88
87
|
expect(frame).toContain("Help");
|
|
89
88
|
});
|
|
90
89
|
|
|
90
|
+
test("keeps generic command filtering separate from ticker search", async () => {
|
|
91
|
+
const searchQueries: string[] = [];
|
|
92
|
+
testSetup = await testRender(<CommandBarHarness
|
|
93
|
+
query="MSFT"
|
|
94
|
+
dataProvider={makeDataProvider(async (query) => {
|
|
95
|
+
searchQueries.push(query);
|
|
96
|
+
return [];
|
|
97
|
+
})}
|
|
98
|
+
/>, {
|
|
99
|
+
width: 80,
|
|
100
|
+
height: 24,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await testSetup.renderOnce();
|
|
104
|
+
await Bun.sleep(260);
|
|
105
|
+
await testSetup.renderOnce();
|
|
106
|
+
|
|
107
|
+
const frame = testSetup.captureCharFrame();
|
|
108
|
+
expect(frame).not.toContain("Tickers");
|
|
109
|
+
expect(frame).not.toContain("Microsoft Corp.");
|
|
110
|
+
expect(searchQueries).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
|
|
91
113
|
test("runs check for updates from the command bar", async () => {
|
|
92
114
|
const calls: number[] = [];
|
|
93
115
|
|
|
@@ -416,6 +438,56 @@ describe("CommandBar", () => {
|
|
|
416
438
|
expect(frame).toContain("AMD");
|
|
417
439
|
});
|
|
418
440
|
|
|
441
|
+
test("opens ticker search from a launch request with saved ticker metadata", async () => {
|
|
442
|
+
testSetup = await testRender(<CommandBarHarness
|
|
443
|
+
query=""
|
|
444
|
+
extraTickers={[makeTicker("BRK.B", "Berkshire Hathaway Inc.", {
|
|
445
|
+
exchange: "NYSE",
|
|
446
|
+
assetCategory: "STK",
|
|
447
|
+
})]}
|
|
448
|
+
configureState={(state) => ({
|
|
449
|
+
...state,
|
|
450
|
+
commandBarLaunchRequest: {
|
|
451
|
+
kind: "ticker-search",
|
|
452
|
+
query: "",
|
|
453
|
+
sequence: 1,
|
|
454
|
+
},
|
|
455
|
+
})}
|
|
456
|
+
/>, {
|
|
457
|
+
width: 100,
|
|
458
|
+
height: 24,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const frame = await waitForFrameToContain("Security Description");
|
|
462
|
+
expectSingleBackControl(frame);
|
|
463
|
+
expect(frame).toContain("BRK.B");
|
|
464
|
+
expect(frame).toContain("Equity NYSE");
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test("opens ticker search when activating the Ticker Research pane item without a ticker", async () => {
|
|
468
|
+
testSetup = await testRender(<CommandBarHarness query="ticker research" />, {
|
|
469
|
+
width: 100,
|
|
470
|
+
height: 24,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
await testSetup.renderOnce();
|
|
474
|
+
const rootFrame = testSetup.captureCharFrame();
|
|
475
|
+
const tickerResearchRow = rootFrame
|
|
476
|
+
.split("\n")
|
|
477
|
+
.find((line) => line.includes("Ticker Research"));
|
|
478
|
+
expect(tickerResearchRow).toMatch(/\bT\s*$/);
|
|
479
|
+
|
|
480
|
+
await act(async () => {
|
|
481
|
+
testSetup!.mockInput.pressEnter();
|
|
482
|
+
await testSetup!.renderOnce();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const frame = testSetup.captureCharFrame();
|
|
486
|
+
expectSingleBackControl(frame);
|
|
487
|
+
expect(frame).toContain("Security Description");
|
|
488
|
+
expect(frame).toContain("Search tickers");
|
|
489
|
+
});
|
|
490
|
+
|
|
419
491
|
test("keeps typed prefixes in the root query until a result is activated", async () => {
|
|
420
492
|
testSetup = await testRender(<CommandBarHarness query="DES " />, {
|
|
421
493
|
width: 80,
|
|
@@ -763,8 +835,8 @@ describe("CommandBar", () => {
|
|
|
763
835
|
const rows = frame.split("\n");
|
|
764
836
|
const savedHeadings = frame.split("\n").filter((line) => line.trim() === "Saved");
|
|
765
837
|
const otherListingsHeadings = frame.split("\n").filter((line) => line.trim() === "Other Listings");
|
|
766
|
-
const aaplRow = rows.findIndex((line) => line.trimStart().startsWith("AAPL")
|
|
767
|
-
const appRow = rows.findIndex((line) => line.trimStart().startsWith("APP")
|
|
838
|
+
const aaplRow = rows.findIndex((line) => line.trimStart().startsWith("AAPL"));
|
|
839
|
+
const appRow = rows.findIndex((line) => line.trimStart().startsWith("APP"));
|
|
768
840
|
expect(savedHeadings).toHaveLength(1);
|
|
769
841
|
expect(otherListingsHeadings).toHaveLength(1);
|
|
770
842
|
expect(aaplRow).toBeGreaterThanOrEqual(0);
|
|
@@ -110,7 +110,6 @@ export function CommandBar({
|
|
|
110
110
|
getWorkflowFieldStringValue,
|
|
111
111
|
getWorkflowInputRef,
|
|
112
112
|
localTickerSearchResultItems,
|
|
113
|
-
mapTickerSearchCandidateToResultItem,
|
|
114
113
|
moveWorkflowFocus,
|
|
115
114
|
nonShortcutPaneTemplateItems,
|
|
116
115
|
openInlineConfirm,
|
|
@@ -193,7 +192,6 @@ export function CommandBar({
|
|
|
193
192
|
getTickers: getTickerSearchTickers,
|
|
194
193
|
hasPaneSettings,
|
|
195
194
|
localTickerSearchResultItems,
|
|
196
|
-
mapTickerSearchCandidateToResultItem,
|
|
197
195
|
nativeListScrollRef,
|
|
198
196
|
nonShortcutPaneTemplateItems,
|
|
199
197
|
openModeRoute,
|
|
@@ -158,6 +158,7 @@ describe("CommandBar pane and layout routes", () => {
|
|
|
158
158
|
expect(frame).toContain("Float Pane");
|
|
159
159
|
expect(frame).toContain("Undo Layout Change");
|
|
160
160
|
expect(frame).toContain("Current Layout");
|
|
161
|
+
expect(frame).toContain("Close All Floating Panes");
|
|
161
162
|
});
|
|
162
163
|
|
|
163
164
|
test("runs layout actions directly from root search", async () => {
|
|
@@ -174,6 +174,7 @@ function makePluginRegistry(hasPaneSettings: (paneId: string) => boolean = () =>
|
|
|
174
174
|
paneId: TICKER_RESEARCH_PANE_ID,
|
|
175
175
|
label: "Ticker Research",
|
|
176
176
|
description: "Open another research pane",
|
|
177
|
+
shortcut: { prefix: "T", argPlaceholder: "ticker", argKind: "ticker" },
|
|
177
178
|
}],
|
|
178
179
|
["new-chat-pane", {
|
|
179
180
|
id: "new-chat-pane",
|
|
@@ -40,7 +40,7 @@ export function resolveCommandBarMode(query: string, commandList?: Command[]): C
|
|
|
40
40
|
const match = matchPrefix(query, commandList);
|
|
41
41
|
|
|
42
42
|
if (!query.trim()) {
|
|
43
|
-
return { kind: "default", badge: "BROWSE", hint: "Type a command
|
|
43
|
+
return { kind: "default", badge: "BROWSE", hint: "Type a command or prefix" };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
if (!match) {
|
|
@@ -100,9 +100,9 @@ export function getEmptyState(mode: CommandBarMode, query: string, searchQuery?:
|
|
|
100
100
|
return { label: "No layout actions match", detail: query.trim() || "Focused-pane and layout actions will appear here" };
|
|
101
101
|
default:
|
|
102
102
|
if (query.trim()) {
|
|
103
|
-
return { label: `No matches for "${query.trim()}"`, detail: "Try a
|
|
103
|
+
return { label: `No matches for "${query.trim()}"`, detail: "Try a command name or prefix. Use T for ticker search" };
|
|
104
104
|
}
|
|
105
|
-
return { label: "No results yet", detail: "
|
|
105
|
+
return { label: "No results yet", detail: "Suggested commands will appear here" };
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -67,18 +67,18 @@ function Harness() {
|
|
|
67
67
|
)
|
|
68
68
|
}
|
|
69
69
|
detailTitle={openRow?.title}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
selection={{
|
|
71
|
+
kind: "index",
|
|
72
|
+
selectedIndex,
|
|
73
|
+
onChange: (index) => setSelectedIndex(index),
|
|
74
|
+
}}
|
|
75
|
+
onActivate={(row) => setOpenRow(row)}
|
|
73
76
|
columns={columns}
|
|
74
77
|
items={rows}
|
|
75
78
|
sortColumnId={null}
|
|
76
79
|
sortDirection="asc"
|
|
77
80
|
onHeaderClick={() => {}}
|
|
78
81
|
getItemKey={(row) => row.id}
|
|
79
|
-
isSelected={(_row, index) => index === selectedIndex}
|
|
80
|
-
onSelect={(_row, index) => setSelectedIndex(index)}
|
|
81
|
-
onActivate={(row) => setOpenRow(row)}
|
|
82
82
|
renderCell={(row): DataTableCell => ({ text: row.title })}
|
|
83
83
|
emptyStateTitle="No rows"
|
|
84
84
|
showHorizontalScrollbar={false}
|
|
@@ -90,13 +90,14 @@ function Harness() {
|
|
|
90
90
|
|
|
91
91
|
function GroupedHarness() {
|
|
92
92
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
93
|
+
const [cursorIndex, setCursorIndex] = useState(0);
|
|
93
94
|
const state = createInitialState(
|
|
94
95
|
createDefaultConfig("/tmp/gloomberb-data-table-stack-view-grouped-test"),
|
|
95
96
|
);
|
|
96
97
|
const columns: Column[] = [
|
|
97
98
|
{ id: "title", label: "Title", width: 20, align: "left" },
|
|
98
99
|
];
|
|
99
|
-
const selectedTitle = groupedRows[
|
|
100
|
+
const selectedTitle = groupedRows[cursorIndex]?.title ?? "none";
|
|
100
101
|
|
|
101
102
|
return (
|
|
102
103
|
<AppContext value={{ state, dispatch: () => {} }}>
|
|
@@ -106,8 +107,12 @@ function GroupedHarness() {
|
|
|
106
107
|
detailOpen={false}
|
|
107
108
|
onBack={() => {}}
|
|
108
109
|
detailContent={<Box flexGrow={1} />}
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
selection={{
|
|
111
|
+
kind: "index",
|
|
112
|
+
selectedIndex,
|
|
113
|
+
onChange: (index) => setSelectedIndex(index),
|
|
114
|
+
}}
|
|
115
|
+
onCursorChange={(_row, index) => setCursorIndex(index)}
|
|
111
116
|
isNavigable={(row) => row.type !== "section"}
|
|
112
117
|
rootAfter={
|
|
113
118
|
<Box height={1}>
|
|
@@ -120,8 +125,6 @@ function GroupedHarness() {
|
|
|
120
125
|
sortDirection="asc"
|
|
121
126
|
onHeaderClick={() => {}}
|
|
122
127
|
getItemKey={(row) => row.id}
|
|
123
|
-
isSelected={(_row, index) => index === selectedIndex}
|
|
124
|
-
onSelect={(_row, index) => setSelectedIndex(index)}
|
|
125
128
|
renderSectionHeader={(row) => row.type === "section"
|
|
126
129
|
? { text: row.title }
|
|
127
130
|
: null}
|
|
@@ -10,13 +10,12 @@ import { PageStackView, type DataTableColumn } from "../ui";
|
|
|
10
10
|
export interface DataTableStackViewProps<
|
|
11
11
|
T,
|
|
12
12
|
C extends DataTableColumn = DataTableColumn,
|
|
13
|
-
> extends Omit<DataTableViewProps<T, C>, "focused"
|
|
13
|
+
> extends Omit<DataTableViewProps<T, C>, "focused"> {
|
|
14
14
|
focused: boolean;
|
|
15
15
|
detailOpen: boolean;
|
|
16
16
|
onBack: () => void;
|
|
17
17
|
detailContent: ReactNode;
|
|
18
18
|
detailTitle?: string;
|
|
19
|
-
selectedIndex: number;
|
|
20
19
|
onDetailKeyDown?: (event: DataTableKeyEvent) => boolean | void;
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -45,21 +45,27 @@ afterEach(async () => {
|
|
|
45
45
|
|
|
46
46
|
function Harness() {
|
|
47
47
|
const [selectedIndex, setSelectedIndex] = useState(1);
|
|
48
|
+
const [cursorIndex, setCursorIndex] = useState(1);
|
|
48
49
|
const [activatedTitle, setActivatedTitle] = useState("");
|
|
49
50
|
const state = createInitialState(
|
|
50
51
|
createDefaultConfig("/tmp/gloomberb-data-table-view-test"),
|
|
51
52
|
);
|
|
52
53
|
const selectedTitle = rows[selectedIndex]?.title ?? "none";
|
|
54
|
+
const cursorTitle = rows[cursorIndex]?.title ?? "none";
|
|
53
55
|
|
|
54
56
|
return (
|
|
55
57
|
<AppContext value={{ state, dispatch: () => {} }}>
|
|
56
58
|
<PaneInstanceProvider paneId="data-table-view-test">
|
|
57
59
|
<DataTableView<Row, Column>
|
|
58
60
|
focused
|
|
59
|
-
selectedIndex={selectedIndex}
|
|
60
61
|
isNavigable={(row) => row.type === "row"}
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
selection={{
|
|
63
|
+
kind: "index",
|
|
64
|
+
selectedIndex,
|
|
65
|
+
onChange: (index) => setSelectedIndex(index),
|
|
66
|
+
}}
|
|
67
|
+
onCursorChange={(_row, index) => setCursorIndex(index)}
|
|
68
|
+
onActivate={(row) => {
|
|
63
69
|
if (row.type === "row") setActivatedTitle(row.title);
|
|
64
70
|
}}
|
|
65
71
|
columns={columns}
|
|
@@ -68,8 +74,6 @@ function Harness() {
|
|
|
68
74
|
sortDirection="asc"
|
|
69
75
|
onHeaderClick={() => {}}
|
|
70
76
|
getItemKey={(row) => row.id}
|
|
71
|
-
isSelected={(_row, index) => index === selectedIndex}
|
|
72
|
-
onSelect={(_row, index) => setSelectedIndex(index)}
|
|
73
77
|
renderSectionHeader={(row) => row.type === "section"
|
|
74
78
|
? { text: row.title }
|
|
75
79
|
: null}
|
|
@@ -79,7 +83,7 @@ function Harness() {
|
|
|
79
83
|
emptyStateTitle="No rows"
|
|
80
84
|
rootAfter={
|
|
81
85
|
<Box height={1}>
|
|
82
|
-
<Text>{`selected=${selectedTitle} activated=${activatedTitle}`}</Text>
|
|
86
|
+
<Text>{`cursor=${cursorTitle} selected=${selectedTitle} activated=${activatedTitle}`}</Text>
|
|
83
87
|
</Box>
|
|
84
88
|
}
|
|
85
89
|
/>
|
|
@@ -102,19 +106,21 @@ function LargeSelectionHarness({
|
|
|
102
106
|
<PaneInstanceProvider paneId="data-table-view-large-test">
|
|
103
107
|
<DataTableView<Row, Column>
|
|
104
108
|
focused
|
|
105
|
-
|
|
109
|
+
selection={{
|
|
110
|
+
kind: "index",
|
|
111
|
+
selectedIndex: 500,
|
|
112
|
+
onChange: () => {},
|
|
113
|
+
}}
|
|
106
114
|
columns={columns}
|
|
107
115
|
items={largeRows}
|
|
108
116
|
sortColumnId={null}
|
|
109
117
|
sortDirection="asc"
|
|
110
118
|
onHeaderClick={() => {}}
|
|
111
119
|
getItemKey={(row) => row.id}
|
|
112
|
-
|
|
120
|
+
renderCell={(row, _column, index): DataTableCell => {
|
|
113
121
|
onIsSelected();
|
|
114
|
-
return index === 500;
|
|
122
|
+
return { text: row.title + (index === 500 ? "" : "") };
|
|
115
123
|
}}
|
|
116
|
-
onSelect={() => {}}
|
|
117
|
-
renderCell={(row): DataTableCell => ({ text: row.title })}
|
|
118
124
|
emptyStateTitle="No rows"
|
|
119
125
|
/>
|
|
120
126
|
</PaneInstanceProvider>
|
|
@@ -190,24 +196,24 @@ describe("DataTableView", () => {
|
|
|
190
196
|
testSetup = await testRender(<Harness />, { width: 60, height: 12 });
|
|
191
197
|
|
|
192
198
|
await renderSettled();
|
|
193
|
-
expect(testSetup.captureCharFrame()).toContain("selected=First row");
|
|
199
|
+
expect(testSetup.captureCharFrame()).toContain("cursor=First row selected=First row");
|
|
194
200
|
|
|
195
201
|
await emitKeypress({ name: "down", sequence: "\u001B[B" });
|
|
196
202
|
await renderSettled();
|
|
197
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
203
|
+
expect(testSetup.captureCharFrame()).toContain("cursor=Second row selected=First row");
|
|
198
204
|
|
|
199
205
|
await emitKeypress({ name: "up", sequence: "\u001B[A", meta: true });
|
|
200
206
|
await renderSettled();
|
|
201
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
207
|
+
expect(testSetup.captureCharFrame()).toContain("cursor=Second row selected=First row");
|
|
202
208
|
|
|
203
209
|
await emitKeypress({ name: "up", sequence: "\u001B[A" });
|
|
204
210
|
await renderSettled();
|
|
205
|
-
expect(testSetup.captureCharFrame()).toContain("selected=First row");
|
|
211
|
+
expect(testSetup.captureCharFrame()).toContain("cursor=First row selected=First row");
|
|
206
212
|
|
|
207
213
|
await emitKeypress({ name: "j", sequence: "j" });
|
|
208
214
|
await emitKeypress({ name: "k", sequence: "k" });
|
|
209
215
|
await renderSettled();
|
|
210
|
-
expect(testSetup.captureCharFrame()).toContain("selected=First row");
|
|
216
|
+
expect(testSetup.captureCharFrame()).toContain("cursor=First row selected=First row");
|
|
211
217
|
|
|
212
218
|
await emitKeypress({ name: "enter", sequence: "\r" });
|
|
213
219
|
await renderSettled();
|
|
@@ -215,11 +221,11 @@ describe("DataTableView", () => {
|
|
|
215
221
|
|
|
216
222
|
await emitKeypress({ name: "j", sequence: "j" });
|
|
217
223
|
await renderSettled();
|
|
218
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
224
|
+
expect(testSetup.captureCharFrame()).toContain("cursor=Second row selected=First row activated=First row");
|
|
219
225
|
|
|
220
226
|
await emitKeypress({ name: "enter", sequence: "\r", defaultPrevented: true });
|
|
221
227
|
await renderSettled();
|
|
222
|
-
expect(testSetup.captureCharFrame()).toContain("
|
|
228
|
+
expect(testSetup.captureCharFrame()).toContain("cursor=Second row selected=First row activated=First row");
|
|
223
229
|
});
|
|
224
230
|
|
|
225
231
|
test("keeps selection current across repeated keypresses before the next render", async () => {
|