gloomberb 0.9.8 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-client/index.test.ts +3 -3
- package/src/api-client/index.ts +0 -6
- package/src/api-client/request.test.ts +63 -0
- package/src/api-client/request.ts +53 -5
- package/src/api-client/types.ts +0 -2
- package/src/app/pane-runtime/layout-placement.ts +0 -1
- package/src/brokers/account-cache.ts +2 -10
- package/src/brokers/broker-portfolio-sync.ts +0 -1
- package/src/brokers/broker-ticker-sync.ts +20 -16
- package/src/brokers/remote-broker-adapter.ts +3 -0
- package/src/capabilities/registry.test.ts +25 -0
- package/src/capabilities/registry.ts +22 -5
- package/src/cli/commands/automation.ts +1 -3
- package/src/cli/commands/command-utils.ts +0 -7
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/commands/plugins.ts +34 -17
- package/src/cli/index.ts +2 -2
- package/src/cli.test.ts +0 -157
- package/src/components/chart/comparison/renderer.ts +0 -1
- package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
- package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
- package/src/components/chart/core/index.test.ts +0 -24
- package/src/components/chart/core/renderer.ts +0 -4
- package/src/components/chart/core/resolution.test.ts +0 -17
- package/src/components/chart/core/resolution.ts +0 -26
- package/src/components/chart/core/scroll.test.ts +0 -6
- package/src/components/chart/core/scroll.ts +0 -22
- package/src/components/chart/core/viewport.test.ts +2 -23
- package/src/components/chart/core/viewport.ts +0 -27
- package/src/components/chart/market-session.ts +1 -1
- package/src/components/chart/native/surface/hooks.ts +10 -3
- package/src/components/chart/stock-chart/data-runtime.ts +0 -1
- package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
- package/src/components/command-bar/helpers.test.ts +1 -80
- package/src/components/command-bar/helpers.ts +1 -36
- package/src/components/command-bar/pane-settings.ts +1 -1
- package/src/components/command-bar/panel/runtime.ts +0 -2
- package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
- package/src/components/command-bar/surface/index.test.tsx +0 -16
- package/src/components/command-bar/surface/index.tsx +0 -1
- package/src/components/feed-data-table/stack-view.test.tsx +36 -130
- package/src/components/feed-data-table/stack-view.tsx +0 -1
- package/src/components/layout/shell/index.test.tsx +2 -113
- package/src/components/layout/shell/index.tsx +0 -1
- package/src/components/layout/shell/menu.ts +0 -1
- package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
- package/src/components/onboarding/onboarding-wizard.tsx +3 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -23
- package/src/components/price-selector-dialog.tsx +3 -3
- package/src/components/ticker/badge/text.tsx +0 -1
- package/src/components/ui/page-stack-view.tsx +1 -1
- package/src/components/ui/ui.test.tsx +0 -78
- package/src/core/state/app/layout-reducer.ts +0 -1
- package/src/data/config/layout.ts +3 -3
- package/src/data/resource-store.ts +6 -0
- package/src/data/sqlite/cache.test.ts +5 -0
- package/src/data/ticker-store.ts +0 -7
- package/src/market-data/coordinator/auxiliary.ts +1 -44
- package/src/market-data/coordinator/entries.ts +0 -1
- package/src/market-data/coordinator/index.test.ts +0 -10
- package/src/market-data/coordinator/index.ts +1 -18
- package/src/market-data/coordinator/quotes.ts +0 -6
- package/src/market-data/coordinator/subscriptions.test.ts +1 -0
- package/src/market-data/market/status.ts +19 -1
- package/src/market-data/query-store.ts +0 -4
- package/src/market-data/quotes/resolution.ts +2 -2
- package/src/market-data/request-types.ts +0 -5
- package/src/market-data/result-types.ts +0 -11
- package/src/market-data/selectors.ts +1 -5
- package/src/news/aggregator.ts +5 -1
- package/src/pane-settings.test.ts +0 -18
- package/src/plugins/builtin/account-management/form-components.tsx +0 -120
- package/src/plugins/builtin/account-management/model.ts +3 -27
- package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
- package/src/plugins/builtin/buildout/format.ts +2 -11
- package/src/plugins/builtin/chat/chat.test.tsx +2 -1
- package/src/plugins/builtin/chat/content/index.tsx +1 -2
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -1
- package/src/plugins/builtin/chat/controller/index.ts +0 -10
- package/src/plugins/builtin/chat/controller.test.ts +5 -5
- package/src/plugins/builtin/chat/sidebar.test.tsx +15 -5
- package/src/plugins/builtin/chat/test-harness.tsx +2 -2
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
- package/src/plugins/builtin/congress-trades/model.ts +2 -7
- package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
- package/src/plugins/builtin/earnings/index.tsx +0 -9
- package/src/plugins/builtin/econ/index.tsx +1 -5
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/pane.tsx +0 -5
- package/src/plugins/builtin/insider/index.tsx +1 -8
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
- package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -2
- package/src/plugins/builtin/news/index.tsx +0 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
- package/src/plugins/builtin/options/view.test.tsx +1 -38
- package/src/plugins/builtin/plugin-groups.ts +17 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
- package/src/plugins/builtin/portfolio-list/index.test.ts +1 -24
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
- package/src/plugins/builtin/sec/filing-documents.ts +0 -11
- package/src/plugins/builtin/sec/index.tsx +0 -2
- package/src/plugins/builtin/substack/article-detail.tsx +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +0 -15
- package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
- package/src/plugins/builtin/world-indices/index.tsx +0 -4
- package/src/plugins/builtin/world-indices/model.ts +0 -7
- package/src/plugins/ibkr/account-cache.ts +1 -1
- package/src/plugins/ibkr/portfolio-performance.ts +1 -1
- package/src/plugins/ibkr/trade/utils.ts +1 -7
- package/src/plugins/loader.ts +2 -1
- package/src/plugins/prediction-markets/detail.test.tsx +0 -34
- package/src/plugins/registry/contributions.ts +22 -11
- package/src/plugins/registry/index.test.ts +44 -0
- package/src/plugins/registry/index.ts +44 -26
- package/src/remote/controller.ts +1 -7
- package/src/remote/json-patch.ts +15 -1
- package/src/remote/server.ts +16 -6
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
- package/src/renderers/electrobun/view/host/mouse.ts +0 -11
- package/src/renderers/electrobun/view/http-fetch.ts +15 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
- package/src/renderers/opentui/host.tsx +11 -3
- package/src/renderers/opentui/input-host.tsx +5 -1
- package/src/sources/gloomberb-cloud/index.test.ts +0 -72
- package/src/sources/gloomberb-cloud/index.ts +1 -30
- package/src/sources/gloomberb-cloud/news.ts +0 -12
- package/src/sources/provider-router/financial-routes.ts +25 -3
- package/src/sources/provider-router/index.test.ts +167 -0
- package/src/sources/provider-router/search.ts +5 -0
- package/src/sources/provider-router/supplemental.ts +4 -3
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/sources/yahoo-finance/options.ts +59 -14
- package/src/sources/yahoo-finance/quote-summary.ts +4 -1
- package/src/sources/yahoo-finance.test.ts +40 -21
- package/src/sources/yahoo-finance.ts +13 -8
- package/src/state/app/context/index.test.tsx +0 -81
- package/src/state/app/context/selector.test.tsx +6 -2
- package/src/sync/controller.test.ts +31 -0
- package/src/sync/controller.ts +21 -17
- package/src/test-support/data-provider.ts +0 -1
- package/src/ui/host.tsx +2 -1
- package/src/ui/index.tsx +1 -0
- package/src/updater.test.ts +0 -21
- package/src/updater.ts +20 -16
- package/src/utils/async-deadline.ts +26 -0
- package/src/utils/selection-clipboard.ts +2 -2
- package/src/version.ts +1 -1
- package/src/components/layout/pane/header.test.tsx +0 -39
- package/src/plugins/builtin/chat/channels.test.ts +0 -28
- package/src/plugins/builtin/chat/index.tsx +0 -4
- package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
- package/src/plugins/builtin/earnings/index.test.ts +0 -12
- package/src/plugins/builtin/help/index.test.tsx +0 -242
- package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
- package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
- package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
- package/src/renderers/electrobun/view/host/text.test.ts +0 -22
- package/src/renderers/opentui/dialog-host.test.tsx +0 -44
- /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { PaneTemplateDef, WizardStep } from "../../types/plugin";
|
|
3
|
-
import type {
|
|
4
|
-
CommandBarRoute,
|
|
5
|
-
CommandBarWorkflowField,
|
|
6
|
-
} from "./workflow/types";
|
|
3
|
+
import type { CommandBarWorkflowField } from "./workflow/types";
|
|
7
4
|
import {
|
|
8
5
|
buildGeneratedTemplateField,
|
|
9
6
|
getCollectionCommandAction,
|
|
10
7
|
getCollectionCommandKind,
|
|
11
8
|
getCollectionCommandVerb,
|
|
12
9
|
getFirstVisibleFieldId,
|
|
13
|
-
getScreenFooterLeft,
|
|
14
|
-
getScreenFooterRight,
|
|
15
10
|
getVisibleWorkflowFields,
|
|
16
11
|
isCollectionCommand,
|
|
17
|
-
isRootParsedCommand,
|
|
18
12
|
isRouteCommandId,
|
|
19
13
|
isWorkflowTextField,
|
|
20
14
|
looksDestructiveCommand,
|
|
@@ -22,7 +16,6 @@ import {
|
|
|
22
16
|
routeCommandIdToScreen,
|
|
23
17
|
slugifyName,
|
|
24
18
|
summarizeError,
|
|
25
|
-
summarizePaneSettingValue,
|
|
26
19
|
summarizeWorkflowFieldValue,
|
|
27
20
|
} from "./helpers";
|
|
28
21
|
|
|
@@ -79,26 +72,6 @@ describe("command-bar helpers", () => {
|
|
|
79
72
|
}, ["symbol", "price", "pnl", "volume"])).toBe("Symbol, Price, P&L +1");
|
|
80
73
|
});
|
|
81
74
|
|
|
82
|
-
test("summarizes pane setting values consistently", () => {
|
|
83
|
-
expect(summarizePaneSettingValue({
|
|
84
|
-
key: "theme",
|
|
85
|
-
label: "Theme",
|
|
86
|
-
type: "select",
|
|
87
|
-
options: [{ label: "Nord", value: "nord" }],
|
|
88
|
-
}, "nord")).toBe("Nord");
|
|
89
|
-
expect(summarizePaneSettingValue({
|
|
90
|
-
key: "columns",
|
|
91
|
-
label: "Columns",
|
|
92
|
-
type: "ordered-multi-select",
|
|
93
|
-
options: [
|
|
94
|
-
{ label: "Symbol", value: "symbol" },
|
|
95
|
-
{ label: "Price", value: "price" },
|
|
96
|
-
{ label: "P&L", value: "pnl" },
|
|
97
|
-
{ label: "Volume", value: "volume" },
|
|
98
|
-
],
|
|
99
|
-
}, ["symbol", "price", "pnl", "volume"])).toBe("Symbol, Price, P&L +1");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
75
|
test("normalizes wizard fields and derives defaults from steps", () => {
|
|
103
76
|
const steps: WizardStep[] = [
|
|
104
77
|
{ key: "intro", label: "Intro", type: "info", body: ["Line one", "Line two"] },
|
|
@@ -157,61 +130,9 @@ describe("command-bar helpers", () => {
|
|
|
157
130
|
});
|
|
158
131
|
});
|
|
159
132
|
|
|
160
|
-
test("formats screen footers based on route context", () => {
|
|
161
|
-
const orderedPickerRoute: CommandBarRoute = {
|
|
162
|
-
kind: "picker",
|
|
163
|
-
pickerId: "field-multi-select",
|
|
164
|
-
title: "Columns",
|
|
165
|
-
query: "",
|
|
166
|
-
selectedIdx: 0,
|
|
167
|
-
hoveredIdx: null,
|
|
168
|
-
options: [],
|
|
169
|
-
payload: { fieldType: "ordered-multi-select" },
|
|
170
|
-
};
|
|
171
|
-
const filteredPickerRoute: CommandBarRoute = {
|
|
172
|
-
...orderedPickerRoute,
|
|
173
|
-
query: "vol",
|
|
174
|
-
};
|
|
175
|
-
const workflowRoute: CommandBarRoute = {
|
|
176
|
-
kind: "workflow",
|
|
177
|
-
workflowId: "create-pane",
|
|
178
|
-
title: "Create Pane",
|
|
179
|
-
fields: [],
|
|
180
|
-
values: {},
|
|
181
|
-
activeFieldId: null,
|
|
182
|
-
submitLabel: "Create",
|
|
183
|
-
pending: false,
|
|
184
|
-
error: null,
|
|
185
|
-
payload: {
|
|
186
|
-
kind: "builtin",
|
|
187
|
-
actionId: "create-pane",
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
const confirmRoute: CommandBarRoute = {
|
|
191
|
-
kind: "confirm",
|
|
192
|
-
confirmId: "delete",
|
|
193
|
-
title: "Delete",
|
|
194
|
-
body: ["Delete item?"],
|
|
195
|
-
confirmLabel: "Delete",
|
|
196
|
-
cancelLabel: "Back",
|
|
197
|
-
tone: "danger",
|
|
198
|
-
onConfirm: () => {},
|
|
199
|
-
pending: false,
|
|
200
|
-
error: null,
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
expect(getScreenFooterLeft(null)).toBe("up/down move enter select");
|
|
204
|
-
expect(getScreenFooterLeft(orderedPickerRoute)).toBe("up/down move space toggle [ ] reorder enter done");
|
|
205
|
-
expect(getScreenFooterRight(orderedPickerRoute)).toBe("backspace/esc back");
|
|
206
|
-
expect(getScreenFooterRight(filteredPickerRoute)).toBe("backspace delete esc back");
|
|
207
|
-
expect(getScreenFooterRight(workflowRoute)).toBe("backspace/esc back");
|
|
208
|
-
expect(getScreenFooterRight(confirmRoute)).toBe("backspace/esc back");
|
|
209
|
-
});
|
|
210
|
-
|
|
211
133
|
test("maps route and collection command ids", () => {
|
|
212
134
|
expect(isRouteCommandId("plugins")).toBe(true);
|
|
213
135
|
expect(routeCommandIdToScreen("plugins")).toBe("plugins");
|
|
214
|
-
expect(isRootParsedCommand("remove-portfolio")).toBe(true);
|
|
215
136
|
expect(isCollectionCommand("remove-portfolio")).toBe(true);
|
|
216
137
|
expect(getCollectionCommandKind("remove-portfolio")).toBe("portfolio");
|
|
217
138
|
expect(getCollectionCommandAction("remove-portfolio")).toBe("remove");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CommandDef
|
|
1
|
+
import type { CommandDef } from "../../types/plugin";
|
|
2
2
|
import type { CommandBarRoute } from "./workflow/types";
|
|
3
3
|
import type { CollectionKind, CollectionMembershipAction } from "./workflow/ops";
|
|
4
4
|
|
|
@@ -37,14 +37,6 @@ export function routeCommandIdToScreen(commandId: RouteCommandId): "ticker-searc
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function isRootParsedCommand(commandId: string): boolean {
|
|
41
|
-
return commandId === "security-description"
|
|
42
|
-
|| commandId === "add-watchlist"
|
|
43
|
-
|| commandId === "add-portfolio"
|
|
44
|
-
|| commandId === "remove-watchlist"
|
|
45
|
-
|| commandId === "remove-portfolio";
|
|
46
|
-
}
|
|
47
|
-
|
|
48
40
|
export function isCollectionCommand(commandId: string): commandId is CollectionCommandId {
|
|
49
41
|
return commandId === "add-watchlist"
|
|
50
42
|
|| commandId === "add-portfolio"
|
|
@@ -64,33 +56,6 @@ export function getCollectionCommandVerb(action: CollectionMembershipAction): st
|
|
|
64
56
|
return action === "add" ? "Add" : "Remove";
|
|
65
57
|
}
|
|
66
58
|
|
|
67
|
-
export function summarizePaneSettingValue(field: PaneSettingField, value: unknown): string {
|
|
68
|
-
switch (field.type) {
|
|
69
|
-
case "toggle":
|
|
70
|
-
return value === true ? "On" : "Off";
|
|
71
|
-
case "text":
|
|
72
|
-
return typeof value === "string" && value.trim().length > 0 ? value : "Unset";
|
|
73
|
-
case "select": {
|
|
74
|
-
const option = field.options.find((entry) => entry.value === value);
|
|
75
|
-
return option?.label ?? "Unset";
|
|
76
|
-
}
|
|
77
|
-
case "multi-select":
|
|
78
|
-
case "ordered-multi-select": {
|
|
79
|
-
const selected = Array.isArray(value)
|
|
80
|
-
? value.filter((entry): entry is string => typeof entry === "string")
|
|
81
|
-
: [];
|
|
82
|
-
if (selected.length === 0) return "None";
|
|
83
|
-
const labels = selected
|
|
84
|
-
.map((entry) => field.options.find((option) => option.value === entry)?.label ?? entry)
|
|
85
|
-
.slice(0, 3);
|
|
86
|
-
const suffix = selected.length > 3 ? ` +${selected.length - 3}` : "";
|
|
87
|
-
return `${labels.join(", ")}${suffix}`;
|
|
88
|
-
}
|
|
89
|
-
default:
|
|
90
|
-
return "";
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
59
|
export function getScreenFooterLeft(route: CommandBarRoute | null): string {
|
|
95
60
|
if (!route) return "up/down move enter select";
|
|
96
61
|
switch (route.kind) {
|
|
@@ -3,7 +3,7 @@ import type { PluginRegistry } from "../../plugins/registry";
|
|
|
3
3
|
import { fuzzyFilter } from "../../utils/fuzzy-search";
|
|
4
4
|
import type { ResultItem } from "./list/model";
|
|
5
5
|
import type { CommandBarRoute, CommandBarWorkflowRoute } from "./workflow/types";
|
|
6
|
-
import { summarizePaneSettingValue } from "
|
|
6
|
+
import { summarizePaneSettingValue } from "../pane-settings-dialog/value";
|
|
7
7
|
|
|
8
8
|
type NotifyFn = (body: string, options?: { type?: "info" | "success" | "error" }) => void;
|
|
9
9
|
|
|
@@ -37,7 +37,6 @@ interface CommandBarPanelRuntimeOptions {
|
|
|
37
37
|
applyThemePreview: (themeId: string | null) => void;
|
|
38
38
|
cellHeightPx: number;
|
|
39
39
|
cellWidthPx: number;
|
|
40
|
-
clearThemePreview: (themeId: string | null | undefined) => void;
|
|
41
40
|
closeAll: (options?: { revertThemePreview?: boolean }) => void;
|
|
42
41
|
commitTheme: (themeId: string) => void;
|
|
43
42
|
committedThemeId: string;
|
|
@@ -92,7 +91,6 @@ export function useCommandBarPanelRuntime({
|
|
|
92
91
|
applyThemePreview,
|
|
93
92
|
cellHeightPx,
|
|
94
93
|
cellWidthPx,
|
|
95
|
-
clearThemePreview,
|
|
96
94
|
closeAll,
|
|
97
95
|
commitTheme,
|
|
98
96
|
committedThemeId,
|
|
@@ -24,17 +24,6 @@ export function normalizeCommandTickerSearchText(value: string): string {
|
|
|
24
24
|
return value.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export function isLikelyPlainTickerSearch(query: string): boolean {
|
|
28
|
-
const trimmed = query.trim();
|
|
29
|
-
if (!trimmed || /\s/.test(trimmed)) return false;
|
|
30
|
-
if (!/^[A-Za-z0-9.^=\-/]+$/.test(trimmed)) return false;
|
|
31
|
-
|
|
32
|
-
const compact = normalizeCommandTickerSearchText(trimmed);
|
|
33
|
-
if (compact.length < 2) return false;
|
|
34
|
-
if (compact.length <= 6) return true;
|
|
35
|
-
return /[.^=\-/]/.test(trimmed) && compact.length <= 10;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
27
|
function isExactTickerResultMatch(item: ResultItem, query: string): boolean {
|
|
39
28
|
if (item.kind !== "ticker" && item.kind !== "search") return false;
|
|
40
29
|
const normalizedQuery = normalizeCommandTickerSearchText(query);
|
|
@@ -81,22 +81,6 @@ function mutablePaneRegistryMap(map: ReadonlyMap<string, unknown>): Map<string,
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
describe("CommandBar", () => {
|
|
84
|
-
test("renders the default layout with opencode-style chrome", async () => {
|
|
85
|
-
testSetup = await testRender(<CommandBarHarness query="" selectedTicker="AAPL" />, {
|
|
86
|
-
width: 80,
|
|
87
|
-
height: 24,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await testSetup.renderOnce();
|
|
91
|
-
|
|
92
|
-
const frame = testSetup.captureCharFrame();
|
|
93
|
-
expect(frame).toContain("Commands");
|
|
94
|
-
expect(frame).not.toContain("Tickers");
|
|
95
|
-
expect(frame).toContain("Panes");
|
|
96
|
-
expect(frame).toContain("Portfolio");
|
|
97
|
-
expect(frame).toContain("Help");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
84
|
test("keeps generic command filtering separate from ticker search", async () => {
|
|
101
85
|
const searchQueries: string[] = [];
|
|
102
86
|
testSetup = await testRender(<CommandBarHarness
|
|
@@ -1,148 +1,54 @@
|
|
|
1
|
-
import { afterEach,
|
|
2
|
-
import { act
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import { act } from "react";
|
|
3
3
|
import { testRender } from "../../renderers/opentui/test-utils";
|
|
4
4
|
import { AppContext, PaneInstanceProvider, createInitialState } from "../../state/app/context";
|
|
5
5
|
import { createDefaultConfig } from "../../types/config";
|
|
6
|
-
import {
|
|
7
|
-
FeedDataTableStackView,
|
|
8
|
-
type FeedDataTableItem,
|
|
9
|
-
} from "./stack-view";
|
|
10
6
|
import { Box, Text } from "../../ui";
|
|
7
|
+
import { FeedDataTableStackView } from "./stack-view";
|
|
11
8
|
|
|
12
9
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
13
|
-
let rootKeyHits = 0;
|
|
14
|
-
|
|
15
|
-
const items: FeedDataTableItem[] = [
|
|
16
|
-
{
|
|
17
|
-
id: "1",
|
|
18
|
-
eyebrow: "Reuters",
|
|
19
|
-
title:
|
|
20
|
-
"Long headline with enough words to prove the table truncates cleanly",
|
|
21
|
-
timestamp: new Date("2024-08-01T12:00:00Z"),
|
|
22
|
-
preview: "Preview text with a little more color.",
|
|
23
|
-
detailTitle: "Long headline with enough words",
|
|
24
|
-
detailMeta: ["Reuters", "Published Aug 1, 2024"],
|
|
25
|
-
detailBody:
|
|
26
|
-
"This body should stay readable when the pane opens the detail page.",
|
|
27
|
-
detailNote: "https://example.com/story",
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "2",
|
|
31
|
-
eyebrow: "10-Q",
|
|
32
|
-
title: "10-Q filing",
|
|
33
|
-
timestamp: new Date("2024-08-02T12:00:00Z"),
|
|
34
|
-
preview: "Quarterly report",
|
|
35
|
-
detailTitle: "10-Q filing",
|
|
36
|
-
detailMeta: ["Filed Aug 2, 2024"],
|
|
37
|
-
detailBody: "Quarterly report details.",
|
|
38
|
-
},
|
|
39
|
-
];
|
|
40
10
|
|
|
41
11
|
afterEach(async () => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
testSetup!.renderer.destroy();
|
|
45
|
-
});
|
|
46
|
-
testSetup = undefined;
|
|
47
|
-
}
|
|
48
|
-
rootKeyHits = 0;
|
|
12
|
+
await act(async () => testSetup?.renderer.destroy());
|
|
13
|
+
testSetup = undefined;
|
|
49
14
|
});
|
|
50
15
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
withRootControls = false,
|
|
55
|
-
}: {
|
|
56
|
-
width: number;
|
|
57
|
-
height: number;
|
|
58
|
-
withRootControls?: boolean;
|
|
59
|
-
}) {
|
|
60
|
-
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
61
|
-
|
|
62
|
-
const state = createInitialState(
|
|
63
|
-
createDefaultConfig("/tmp/gloomberb-detail-data-table-test"),
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<AppContext value={{ state, dispatch: () => {} }}>
|
|
68
|
-
<PaneInstanceProvider paneId="portfolio-list:main">
|
|
69
|
-
<FeedDataTableStackView
|
|
70
|
-
width={width}
|
|
71
|
-
height={height}
|
|
72
|
-
focused
|
|
73
|
-
items={items}
|
|
74
|
-
selectedIdx={selectedIdx}
|
|
75
|
-
onSelect={setSelectedIdx}
|
|
76
|
-
rootBefore={withRootControls ? (
|
|
77
|
-
<Box height={1}>
|
|
78
|
-
<Text>Feed controls</Text>
|
|
79
|
-
</Box>
|
|
80
|
-
) : undefined}
|
|
81
|
-
onRootKeyDown={withRootControls ? (event) => {
|
|
82
|
-
if (event.name !== "f") return false;
|
|
83
|
-
rootKeyHits += 1;
|
|
84
|
-
return true;
|
|
85
|
-
} : undefined}
|
|
86
|
-
sourceLabel="Source"
|
|
87
|
-
titleLabel="Headline"
|
|
88
|
-
/>
|
|
89
|
-
</PaneInstanceProvider>
|
|
90
|
-
</AppContext>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function renderSettled() {
|
|
16
|
+
test("delegates root key handling", async () => {
|
|
17
|
+
let keyHits = 0;
|
|
18
|
+
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-feed-table-test"));
|
|
95
19
|
await act(async () => {
|
|
96
|
-
await
|
|
97
|
-
|
|
20
|
+
testSetup = await testRender(
|
|
21
|
+
<AppContext value={{ state, dispatch: () => {} }}>
|
|
22
|
+
<PaneInstanceProvider paneId="portfolio-list:main">
|
|
23
|
+
<FeedDataTableStackView
|
|
24
|
+
width={80}
|
|
25
|
+
height={12}
|
|
26
|
+
focused
|
|
27
|
+
items={[]}
|
|
28
|
+
selectedIdx={0}
|
|
29
|
+
onSelect={() => {}}
|
|
30
|
+
rootBefore={<Box height={1}><Text>Feed controls</Text></Box>}
|
|
31
|
+
onRootKeyDown={(event) => {
|
|
32
|
+
if (event.name !== "f") return false;
|
|
33
|
+
keyHits += 1;
|
|
34
|
+
return true;
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
</PaneInstanceProvider>
|
|
38
|
+
</AppContext>,
|
|
39
|
+
{ width: 80, height: 12 },
|
|
40
|
+
);
|
|
41
|
+
await testSetup.renderOnce();
|
|
98
42
|
});
|
|
99
|
-
}
|
|
100
43
|
|
|
101
|
-
async function emitKeypress(event: { name?: string; sequence?: string }) {
|
|
102
44
|
await act(async () => {
|
|
103
|
-
testSetup!.renderer.keyInput.emit("keypress", {
|
|
104
|
-
|
|
105
|
-
meta: false,
|
|
106
|
-
|
|
107
|
-
shift: false,
|
|
108
|
-
eventType: "press",
|
|
109
|
-
repeated: false,
|
|
110
|
-
preventDefault: () => {},
|
|
111
|
-
stopPropagation: () => {},
|
|
112
|
-
...event,
|
|
113
|
-
} as any);
|
|
114
|
-
await testSetup!.renderOnce();
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
describe("FeedDataTableStackView", () => {
|
|
119
|
-
test("renders a DataTable list by default", async () => {
|
|
120
|
-
testSetup = await testRender(<Harness width={90} height={16} />, {
|
|
121
|
-
width: 90,
|
|
122
|
-
height: 16,
|
|
45
|
+
(testSetup!.renderer.keyInput as any).emit("keypress", {
|
|
46
|
+
name: "f", sequence: "f", eventType: "press",
|
|
47
|
+
ctrl: false, meta: false, option: false, shift: false, repeated: false,
|
|
48
|
+
preventDefault() {}, stopPropagation() {},
|
|
123
49
|
});
|
|
124
|
-
|
|
125
|
-
await renderSettled();
|
|
126
|
-
const frame = testSetup.captureCharFrame();
|
|
127
|
-
|
|
128
|
-
expect(frame).toContain("Time");
|
|
129
|
-
expect(frame).toContain("Source");
|
|
130
|
-
expect(frame).toContain("Headline");
|
|
131
|
-
expect(frame).toContain("10-Q filing");
|
|
132
|
-
expect(frame).not.toContain("j/k move");
|
|
133
|
-
expect(frame).not.toContain("Quarterly report details.");
|
|
50
|
+
await testSetup!.renderOnce();
|
|
134
51
|
});
|
|
135
52
|
|
|
136
|
-
|
|
137
|
-
testSetup = await testRender(<Harness width={90} height={16} withRootControls />, {
|
|
138
|
-
width: 90,
|
|
139
|
-
height: 16,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
await renderSettled();
|
|
143
|
-
expect(testSetup.captureCharFrame()).toContain("Feed controls");
|
|
144
|
-
|
|
145
|
-
await emitKeypress({ name: "f", sequence: "f" });
|
|
146
|
-
expect(rootKeyHits).toBe(1);
|
|
147
|
-
});
|
|
53
|
+
expect(keyHits).toBe(1);
|
|
148
54
|
});
|
|
@@ -14,7 +14,6 @@ import { cloneLayout, createDefaultConfig, TICKER_RESEARCH_PANE_ID, type LayoutC
|
|
|
14
14
|
import type { PluginRegistry } from "../../../plugins/registry";
|
|
15
15
|
import type { PaneProps } from "../../../types/plugin";
|
|
16
16
|
import { Textarea } from "../../../ui";
|
|
17
|
-
import { Header } from "../header";
|
|
18
17
|
import {
|
|
19
18
|
buildNativeWindowState,
|
|
20
19
|
resolvePaneManagementShortcut,
|
|
@@ -70,7 +69,7 @@ function createShellPluginRegistry(options?: {
|
|
|
70
69
|
} as unknown as PluginRegistry;
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
async function emitKeypress(event: { name?: string; sequence?: string; ctrl?: boolean; meta?: boolean; shift?: boolean; alt?: boolean }) {
|
|
72
|
+
async function emitKeypress(event: { name?: string; sequence?: string; ctrl?: boolean; meta?: boolean; super?: boolean; shift?: boolean; alt?: boolean }) {
|
|
74
73
|
await act(async () => {
|
|
75
74
|
const keyEvent = {
|
|
76
75
|
ctrl: false,
|
|
@@ -184,112 +183,6 @@ function findUpdateLayout(actions: ShellTestAction[]) {
|
|
|
184
183
|
return actions.find((action) => action.type === "UPDATE_LAYOUT");
|
|
185
184
|
}
|
|
186
185
|
|
|
187
|
-
function HeaderHarness({
|
|
188
|
-
updateAvailable = null,
|
|
189
|
-
updateProgress = null,
|
|
190
|
-
updateCheckInProgress = false,
|
|
191
|
-
updateNotice = null,
|
|
192
|
-
}: {
|
|
193
|
-
updateAvailable?: ReturnType<typeof createInitialState>["updateAvailable"];
|
|
194
|
-
updateProgress?: ReturnType<typeof createInitialState>["updateProgress"];
|
|
195
|
-
updateCheckInProgress?: boolean;
|
|
196
|
-
updateNotice?: string | null;
|
|
197
|
-
}) {
|
|
198
|
-
const initialState = createInitialState(createDefaultConfig("/tmp/gloomberb-header-test"));
|
|
199
|
-
initialState.updateAvailable = updateAvailable;
|
|
200
|
-
initialState.updateProgress = updateProgress;
|
|
201
|
-
initialState.updateCheckInProgress = updateCheckInProgress;
|
|
202
|
-
initialState.updateNotice = updateNotice;
|
|
203
|
-
const [state, dispatch] = useReducer(appReducer, initialState);
|
|
204
|
-
|
|
205
|
-
return (
|
|
206
|
-
<AppContext value={{ state, dispatch }}>
|
|
207
|
-
<Header />
|
|
208
|
-
</AppContext>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
describe("Header", () => {
|
|
213
|
-
test("shows automatic self-update status for standalone binaries", async () => {
|
|
214
|
-
testSetup = await testRender(
|
|
215
|
-
<HeaderHarness updateAvailable={{
|
|
216
|
-
version: "0.3.0",
|
|
217
|
-
tagName: "v0.3.0",
|
|
218
|
-
downloadUrl: "https://example.com/gloomberb",
|
|
219
|
-
publishedAt: "2026-04-01T00:00:00.000Z",
|
|
220
|
-
updateAction: { kind: "self" },
|
|
221
|
-
}} />,
|
|
222
|
-
{ width: 120, height: 2 },
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
await testSetup.renderOnce();
|
|
226
|
-
const frame = testSetup!.captureCharFrame();
|
|
227
|
-
|
|
228
|
-
expect(frame).toContain("v0.3.0 available");
|
|
229
|
-
expect(frame).toContain("starting download");
|
|
230
|
-
expect(frame).not.toContain("press u to update");
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("shows the manual npm command when self-update is disabled", async () => {
|
|
234
|
-
testSetup = await testRender(
|
|
235
|
-
<HeaderHarness updateAvailable={{
|
|
236
|
-
version: "0.3.0",
|
|
237
|
-
tagName: "v0.3.0",
|
|
238
|
-
downloadUrl: "https://example.com/gloomberb",
|
|
239
|
-
publishedAt: "2026-04-01T00:00:00.000Z",
|
|
240
|
-
updateAction: { kind: "manual", command: "npm install -g gloomberb@latest" },
|
|
241
|
-
}} />,
|
|
242
|
-
{ width: 120, height: 2 },
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
await testSetup.renderOnce();
|
|
246
|
-
const frame = testSetup!.captureCharFrame();
|
|
247
|
-
|
|
248
|
-
expect(frame).toContain("v0.3.0 available");
|
|
249
|
-
expect(frame).toContain("run npm install -g gloomberb@latest");
|
|
250
|
-
expect(frame).not.toContain("press u to update");
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test("shows update download progress and notices", async () => {
|
|
254
|
-
testSetup = await testRender(
|
|
255
|
-
<HeaderHarness
|
|
256
|
-
updateAvailable={{
|
|
257
|
-
version: "0.3.0",
|
|
258
|
-
tagName: "v0.3.0",
|
|
259
|
-
downloadUrl: "https://example.com/gloomberb",
|
|
260
|
-
publishedAt: "2026-04-01T00:00:00.000Z",
|
|
261
|
-
updateAction: { kind: "self" },
|
|
262
|
-
}}
|
|
263
|
-
updateProgress={{ phase: "downloading", percent: 42 }}
|
|
264
|
-
/>,
|
|
265
|
-
{ width: 120, height: 2 },
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
await testSetup.renderOnce();
|
|
269
|
-
expect(testSetup.captureCharFrame()).toContain("Downloading v0.3.0: 42%");
|
|
270
|
-
|
|
271
|
-
testSetup.renderer.destroy();
|
|
272
|
-
|
|
273
|
-
testSetup = await testRender(
|
|
274
|
-
<HeaderHarness updateCheckInProgress />,
|
|
275
|
-
{ width: 120, height: 2 },
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
await testSetup.renderOnce();
|
|
279
|
-
expect(testSetup.captureCharFrame()).toContain("Checking for updates...");
|
|
280
|
-
|
|
281
|
-
testSetup.renderer.destroy();
|
|
282
|
-
|
|
283
|
-
testSetup = await testRender(
|
|
284
|
-
<HeaderHarness updateNotice="Already on v0.3.1" />,
|
|
285
|
-
{ width: 120, height: 2 },
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
await testSetup.renderOnce();
|
|
289
|
-
expect(testSetup.captureCharFrame()).toContain("Already on v0.3.1");
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
186
|
describe("Shell", () => {
|
|
294
187
|
test("uses the desktop titlebar overlay height for shell chrome math", () => {
|
|
295
188
|
expect(resolveAppHeaderHeightCells({ titleBarOverlay: true, cellHeightPx: 18 })).toBe(28 / 18);
|
|
@@ -716,7 +609,6 @@ describe("Shell", () => {
|
|
|
716
609
|
await emitKeypress({
|
|
717
610
|
name: "f",
|
|
718
611
|
key: "f",
|
|
719
|
-
meta: true,
|
|
720
612
|
super: true,
|
|
721
613
|
shift: true,
|
|
722
614
|
targetEditable: true,
|
|
@@ -1330,10 +1222,7 @@ describe("Shell", () => {
|
|
|
1330
1222
|
);
|
|
1331
1223
|
|
|
1332
1224
|
await testSetup.renderOnce();
|
|
1333
|
-
await
|
|
1334
|
-
testSetup!.mockInput.pressKey("w", { meta: true });
|
|
1335
|
-
await testSetup!.renderOnce();
|
|
1336
|
-
});
|
|
1225
|
+
await emitKeypress({ name: "w", super: true });
|
|
1337
1226
|
|
|
1338
1227
|
const updateLayout = actions.find((action) => action.type === "UPDATE_LAYOUT");
|
|
1339
1228
|
expect(updateLayout?.layout.instances.map((instance: { instanceId: string }) => instance.instanceId)).toEqual(["portfolio-list:main"]);
|
|
@@ -81,9 +81,9 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
useEffect(() => {
|
|
84
|
-
if (editingField)
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
if (!editingField) return;
|
|
85
|
+
const focusTimer = setTimeout(() => inputRef.current?.focus?.(), 10);
|
|
86
|
+
return () => clearTimeout(focusTimer);
|
|
87
87
|
}, [editingField, portfolioSub, brokerFieldIdx]);
|
|
88
88
|
|
|
89
89
|
useEffect(() => {
|
|
@@ -72,7 +72,7 @@ export function useOnboardingKeyboard({
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
if (editingField) {
|
|
75
|
-
if (event.name === "return") {
|
|
75
|
+
if (event.name === "enter" || event.name === "return") {
|
|
76
76
|
setEditingField(false);
|
|
77
77
|
if (portfolioSub === "broker-fields" && selectedBrokerId) {
|
|
78
78
|
const currentField = activeBrokerFields[brokerFieldIdx];
|
|
@@ -188,28 +188,7 @@ export function useOnboardingKeyboard({
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
nextStep();
|
|
191
|
-
} else if (isBackNavigationKey(event)) {
|
|
192
|
-
if (step === "portfolio" && portfolioSub === "broker-sync") {
|
|
193
|
-
resetBrokerSync();
|
|
194
|
-
setPortfolioSub("broker-fields");
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (step === "portfolio" && portfolioSub === "broker-fields") {
|
|
198
|
-
setPortfolioSub("broker-setup");
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (step === "portfolio" && portfolioSub === "broker-setup") {
|
|
202
|
-
setPortfolioSub("broker-fields");
|
|
203
|
-
setBrokerFieldIdx(0);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
if (step === "portfolio" && portfolioSub !== "choose") {
|
|
207
|
-
setPortfolioSub("choose");
|
|
208
|
-
setBrokerFieldIdx(0);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
prevStep();
|
|
212
|
-
} else if (event.name === "left") {
|
|
191
|
+
} else if (isBackNavigationKey(event) || event.name === "left") {
|
|
213
192
|
if (step === "portfolio" && portfolioSub === "broker-sync") {
|
|
214
193
|
resetBrokerSync();
|
|
215
194
|
setPortfolioSub("broker-fields");
|