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
|
@@ -84,54 +84,6 @@ export function FieldRow({
|
|
|
84
84
|
);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export function PickerRow({
|
|
88
|
-
label,
|
|
89
|
-
value,
|
|
90
|
-
detail,
|
|
91
|
-
active,
|
|
92
|
-
width,
|
|
93
|
-
onFocus,
|
|
94
|
-
onOpen,
|
|
95
|
-
}: {
|
|
96
|
-
label: string;
|
|
97
|
-
value: string;
|
|
98
|
-
detail?: string;
|
|
99
|
-
active: boolean;
|
|
100
|
-
width: number;
|
|
101
|
-
onFocus: () => void;
|
|
102
|
-
onOpen: () => void;
|
|
103
|
-
}) {
|
|
104
|
-
const buttonWidth = Math.max(12, Math.min(32, width - 18));
|
|
105
|
-
const detailWidth = Math.max(0, width - buttonWidth - 4);
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<Box
|
|
109
|
-
flexDirection="column"
|
|
110
|
-
onMouseOver={onFocus}
|
|
111
|
-
onMouseDown={(event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
112
|
-
event?.stopPropagation?.();
|
|
113
|
-
event?.preventDefault?.();
|
|
114
|
-
onFocus();
|
|
115
|
-
onOpen();
|
|
116
|
-
}}
|
|
117
|
-
>
|
|
118
|
-
<Text fg={active ? colors.textBright : colors.textDim} attributes={active ? TextAttributes.BOLD : 0}>
|
|
119
|
-
{active ? `> ${label}` : ` ${label}`}
|
|
120
|
-
</Text>
|
|
121
|
-
<Box height={1} flexDirection="row" gap={1}>
|
|
122
|
-
<Box width={buttonWidth} backgroundColor={active ? colors.selected : colors.panel}>
|
|
123
|
-
<Text fg={active ? colors.selectedText : colors.text}>
|
|
124
|
-
{` ${truncate(value, Math.max(1, buttonWidth - 2))} `}
|
|
125
|
-
</Text>
|
|
126
|
-
</Box>
|
|
127
|
-
{detail ? (
|
|
128
|
-
<Text fg={colors.textMuted}>{truncate(detail, detailWidth)}</Text>
|
|
129
|
-
) : null}
|
|
130
|
-
</Box>
|
|
131
|
-
</Box>
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
87
|
export function CheckboxRow({
|
|
136
88
|
label,
|
|
137
89
|
checked,
|
|
@@ -207,78 +159,6 @@ function metricColor(tone: ProfileAnalyticsPreview["metrics"][number]["tone"]):
|
|
|
207
159
|
return colors.text;
|
|
208
160
|
}
|
|
209
161
|
|
|
210
|
-
export function AccountAnalyticsPreview({
|
|
211
|
-
preview,
|
|
212
|
-
width,
|
|
213
|
-
disclaimer,
|
|
214
|
-
surface = "panel",
|
|
215
|
-
}: {
|
|
216
|
-
preview: ProfileAnalyticsPreview;
|
|
217
|
-
width: number;
|
|
218
|
-
disclaimer?: string | null;
|
|
219
|
-
surface?: "panel" | "plain";
|
|
220
|
-
}) {
|
|
221
|
-
const horizontalPadding = surface === "panel" ? 1 : 0;
|
|
222
|
-
const contentWidth = Math.max(1, width - horizontalPadding * 2);
|
|
223
|
-
const metricWidth = Math.max(14, Math.min(28, Math.floor((contentWidth - 2) / 2)));
|
|
224
|
-
const subtitleWidth = Math.max(1, contentWidth - preview.title.length - 2);
|
|
225
|
-
const showHeader = surface === "panel";
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<Box
|
|
229
|
-
flexDirection="column"
|
|
230
|
-
width={width}
|
|
231
|
-
backgroundColor={surface === "panel" ? colors.panel : undefined}
|
|
232
|
-
paddingX={horizontalPadding}
|
|
233
|
-
>
|
|
234
|
-
{showHeader ? (
|
|
235
|
-
<Box height={1} flexDirection="row">
|
|
236
|
-
<Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
|
|
237
|
-
{truncate(preview.title, contentWidth)}
|
|
238
|
-
</Text>
|
|
239
|
-
{preview.subtitle ? (
|
|
240
|
-
<>
|
|
241
|
-
<Box flexGrow={1} />
|
|
242
|
-
<Text fg={colors.textMuted}>{truncate(preview.subtitle, subtitleWidth)}</Text>
|
|
243
|
-
</>
|
|
244
|
-
) : null}
|
|
245
|
-
</Box>
|
|
246
|
-
) : null}
|
|
247
|
-
{preview.metrics.length > 0 ? (
|
|
248
|
-
<Box flexDirection="row" flexWrap="wrap" gap={2}>
|
|
249
|
-
{preview.metrics.map((metric) => {
|
|
250
|
-
const labelWidth = Math.max(1, Math.min(metric.label.length, metricWidth - 2));
|
|
251
|
-
const valueWidth = Math.max(1, metricWidth - labelWidth - 1);
|
|
252
|
-
const detailWidth = Math.max(0, metricWidth - labelWidth - metric.value.length - 2);
|
|
253
|
-
return (
|
|
254
|
-
<Box key={metric.id} width={metricWidth} height={1} flexDirection="row" gap={1}>
|
|
255
|
-
<Text fg={colors.textDim}>{truncate(metric.label, labelWidth)}</Text>
|
|
256
|
-
<Text fg={metricColor(metric.tone)} attributes={TextAttributes.BOLD}>
|
|
257
|
-
{truncate(metric.value, valueWidth)}
|
|
258
|
-
</Text>
|
|
259
|
-
{metric.detail ? (
|
|
260
|
-
<Text fg={colors.textMuted}>
|
|
261
|
-
{truncate(metric.detail, detailWidth)}
|
|
262
|
-
</Text>
|
|
263
|
-
) : null}
|
|
264
|
-
</Box>
|
|
265
|
-
);
|
|
266
|
-
})}
|
|
267
|
-
</Box>
|
|
268
|
-
) : preview.subtitle ? (
|
|
269
|
-
<Text fg={colors.textMuted} wrapText width={contentWidth}>
|
|
270
|
-
{preview.subtitle}
|
|
271
|
-
</Text>
|
|
272
|
-
) : null}
|
|
273
|
-
{disclaimer ? (
|
|
274
|
-
<Text fg={colors.textMuted} wrapText width={contentWidth}>
|
|
275
|
-
{disclaimer}
|
|
276
|
-
</Text>
|
|
277
|
-
) : null}
|
|
278
|
-
</Box>
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
162
|
export function PublicAnalyticsGroup({
|
|
283
163
|
preview,
|
|
284
164
|
choices,
|
|
@@ -3,6 +3,8 @@ import type { AccountProfile, PublicPortfolioAnalytics } from "../../../api-clie
|
|
|
3
3
|
import type { Portfolio, TickerRecord } from "../../../types/ticker";
|
|
4
4
|
import { formatNumber } from "../../../utils/format";
|
|
5
5
|
|
|
6
|
+
export { truncateWithEllipsis as truncate } from "../../../utils/text-wrap";
|
|
7
|
+
|
|
6
8
|
export type AccountFieldKey =
|
|
7
9
|
| "username"
|
|
8
10
|
| "name"
|
|
@@ -36,7 +38,7 @@ export interface AccountDraft {
|
|
|
36
38
|
positionAlertsEnabled: boolean;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
interface ProfileAnalyticsPreviewMetric {
|
|
40
42
|
id: string;
|
|
41
43
|
label: string;
|
|
42
44
|
value: string;
|
|
@@ -52,25 +54,6 @@ export interface ProfileAnalyticsPreview {
|
|
|
52
54
|
publicAnalytics: PublicPortfolioAnalytics | null;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
export const BASE_FIELD_ORDER: AccountFieldKey[] = [
|
|
56
|
-
"username",
|
|
57
|
-
"name",
|
|
58
|
-
"company",
|
|
59
|
-
"title",
|
|
60
|
-
"bio",
|
|
61
|
-
"profilePublic",
|
|
62
|
-
"publicEmail",
|
|
63
|
-
"xAccount",
|
|
64
|
-
"acceptUnknownDms",
|
|
65
|
-
"sharedPortfolioId",
|
|
66
|
-
"weeklyRoundupEnabled",
|
|
67
|
-
"positionAlertsEnabled",
|
|
68
|
-
"emailAlertsOffAction",
|
|
69
|
-
"upgradeAction",
|
|
70
|
-
"passwordAction",
|
|
71
|
-
"deleteAccountAction",
|
|
72
|
-
];
|
|
73
|
-
|
|
74
57
|
export const NO_PORTFOLIO_VALUE = "__none__";
|
|
75
58
|
|
|
76
59
|
export function profileToDraft(profile: AccountProfile | null): AccountDraft {
|
|
@@ -95,13 +78,6 @@ export function emptyToNull(value: string): string | null {
|
|
|
95
78
|
return trimmed ? trimmed : null;
|
|
96
79
|
}
|
|
97
80
|
|
|
98
|
-
export function truncate(value: string, width: number): string {
|
|
99
|
-
if (width <= 0) return "";
|
|
100
|
-
if (value.length <= width) return value;
|
|
101
|
-
if (width <= 3) return value.slice(0, width);
|
|
102
|
-
return `${value.slice(0, width - 3)}...`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
81
|
export function formatPlan(plan: AccountProfile["plan"] | null | undefined): string {
|
|
106
82
|
return plan === "pro" ? "Pro" : "Free";
|
|
107
83
|
}
|
|
@@ -112,38 +112,6 @@ async function pressKey(key: string) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
describe("BrokersPane", () => {
|
|
115
|
-
test("shows empty state and opens add broker flow", async () => {
|
|
116
|
-
const calls: string[] = [];
|
|
117
|
-
testSetup = await testRender(<Harness calls={calls} />, { width: 92, height: 24 });
|
|
118
|
-
await act(async () => {
|
|
119
|
-
await testSetup!.renderOnce();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const frame = testSetup.captureCharFrame();
|
|
123
|
-
expect(frame).toContain("No broker profiles.");
|
|
124
|
-
expect(frame).not.toContain("Press a or click Add Broker.");
|
|
125
|
-
await act(async () => {
|
|
126
|
-
testSetup!.mockInput.pressKey("a");
|
|
127
|
-
await testSetup!.renderOnce();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
expect(calls).toEqual(["command:Add Broker Account"]);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("hides unavailable footer actions with no broker profile", async () => {
|
|
134
|
-
const calls: string[] = [];
|
|
135
|
-
testSetup = await testRender(<FooterHarness calls={calls} />, { width: 92, height: 25 });
|
|
136
|
-
await act(async () => {
|
|
137
|
-
await testSetup!.renderOnce();
|
|
138
|
-
await testSetup!.renderOnce();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const frame = testSetup.captureCharFrame();
|
|
142
|
-
expect(frame).toContain("[a]dd");
|
|
143
|
-
expect(frame).not.toContain("[c]onnect");
|
|
144
|
-
expect(frame).not.toContain("[d]isconnect");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
115
|
test("renders IBKR row and invokes broker actions", async () => {
|
|
148
116
|
const calls: string[] = [];
|
|
149
117
|
testSetup = await testRender(<FooterHarness calls={calls} instance={createGatewayInstance()} height={35} />, { width: 92, height: 35 });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TextAttributes } from "../../../ui";
|
|
2
2
|
import type { DataTableCell } from "../../../components";
|
|
3
3
|
import { colors } from "../../../theme/colors";
|
|
4
|
+
import { formatRelativeTime } from "../../../utils/datetime-format";
|
|
4
5
|
import {
|
|
5
6
|
CompanyCell,
|
|
6
7
|
FavoriteCell,
|
|
@@ -11,7 +12,6 @@ import {
|
|
|
11
12
|
activityColor,
|
|
12
13
|
activityLabel,
|
|
13
14
|
criticalityColor,
|
|
14
|
-
formatRelativeTime,
|
|
15
15
|
metricColor,
|
|
16
16
|
text,
|
|
17
17
|
tickerSymbol,
|
|
@@ -2,7 +2,7 @@ import { Box, Text } from "../../../../ui";
|
|
|
2
2
|
import { RemoteImage } from "../../../../components/ui";
|
|
3
3
|
import { colors } from "../../../../theme/colors";
|
|
4
4
|
import type { BuildoutObservation, BuildoutSite } from "../model/types";
|
|
5
|
-
import { dateShort
|
|
5
|
+
import { dateShort } from "../format";
|
|
6
6
|
|
|
7
7
|
function observationImageUrl(observation: BuildoutObservation) {
|
|
8
8
|
return observation.upscaledImageUrl
|
|
@@ -6,7 +6,6 @@ import { colors } from "../../../../theme/colors";
|
|
|
6
6
|
import type { InlineTickerCatalogEntry } from "../../../../state/hooks/inline-tickers";
|
|
7
7
|
import type { BuildoutCompany, BuildoutRelatedCompany, BuildoutSource } from "../model/types";
|
|
8
8
|
import { domainFromUrl, text, textOrNull, tickerSymbol, truncate } from "../format";
|
|
9
|
-
import { booleanText } from "./values";
|
|
10
9
|
|
|
11
10
|
export type InlineTickerCatalog = Record<string, InlineTickerCatalogEntry>;
|
|
12
11
|
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { colors, priceColor } from "../../../theme/colors";
|
|
2
|
-
import { formatDetailDate as sharedFormatDetailDate,
|
|
2
|
+
import { formatDetailDate as sharedFormatDetailDate, parseDisplayDate } from "../../../utils/datetime-format";
|
|
3
3
|
import type { BuildoutSource, BuildoutUpdate } from "./model/types";
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
if (width <= 0) return "";
|
|
7
|
-
if (text.length <= width) return text;
|
|
8
|
-
if (width <= 3) return text.slice(0, width);
|
|
9
|
-
return `${text.slice(0, width - 3)}...`;
|
|
10
|
-
}
|
|
5
|
+
export { truncateWithEllipsis as truncate } from "../../../utils/text-wrap";
|
|
11
6
|
|
|
12
7
|
export function text(value: unknown, fallback = "-") {
|
|
13
8
|
if (value == null) return fallback;
|
|
@@ -62,10 +57,6 @@ export function dateDetail(value: string | null | undefined) {
|
|
|
62
57
|
return formatted || null;
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
export function formatRelativeTime(value: string | null | undefined): string {
|
|
66
|
-
return sharedFormatRelativeTime(value);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
60
|
export function activityLabel(value: number | null | undefined) {
|
|
70
61
|
if (value == null) return "-";
|
|
71
62
|
if (value >= 2) return "High";
|
|
@@ -9,7 +9,8 @@ import type { ChatMessage } from "../../../api-client";
|
|
|
9
9
|
import { apiClient } from "../../../api-client";
|
|
10
10
|
import { PluginRenderProvider } from "../../runtime";
|
|
11
11
|
import { setSharedRegistryForTests } from "../../registry";
|
|
12
|
-
import { ChatContent
|
|
12
|
+
import { ChatContent } from "./content";
|
|
13
|
+
import { ChatStatusWidget } from "./status-widget";
|
|
13
14
|
import {
|
|
14
15
|
cleanupChatTest,
|
|
15
16
|
createChatTestControls,
|
|
@@ -311,11 +311,10 @@ export function ChatContent({
|
|
|
311
311
|
? await controller.openDirectChannel({ username: usernames[0] })
|
|
312
312
|
: await controller.openGroupChannel({ usernames });
|
|
313
313
|
setDirectExpanded(true);
|
|
314
|
-
channelIdRef.current = channel.id;
|
|
315
314
|
selectSidebarChannel(channel.id);
|
|
316
315
|
setSidebarFocused(false);
|
|
317
316
|
closeNewDmDialog();
|
|
318
|
-
}, [
|
|
317
|
+
}, [closeNewDmDialog, controller, selectSidebarChannel, setDirectExpanded, setSidebarFocused]);
|
|
319
318
|
|
|
320
319
|
useEffect(() => {
|
|
321
320
|
if (!focused && newDmOpen) {
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
disposeChatControllerRuntime,
|
|
31
31
|
resetChatControllerRuntime,
|
|
32
32
|
} from "./lifecycle";
|
|
33
|
-
import type { ChatSessionUser } from "./persistence";
|
|
34
33
|
import {
|
|
35
34
|
closeAllChannelConnections,
|
|
36
35
|
closeInactiveChannelConnections,
|
|
@@ -69,15 +68,6 @@ export class ChatController {
|
|
|
69
68
|
private notifyFn: (notification: AppNotificationRequest) => void = () => {};
|
|
70
69
|
private notifiedMessageIds = new Set<string>();
|
|
71
70
|
|
|
72
|
-
private get hydrated(): boolean { return this.session.hydrated; }
|
|
73
|
-
private set hydrated(value: boolean) { this.session.hydrated = value; }
|
|
74
|
-
private get sessionToken(): string | null { return this.session.sessionToken; }
|
|
75
|
-
private set sessionToken(value: string | null) { this.session.sessionToken = value; }
|
|
76
|
-
private get sessionChecked(): boolean { return this.session.sessionChecked; }
|
|
77
|
-
private set sessionChecked(value: boolean) { this.session.sessionChecked = value; }
|
|
78
|
-
private get user(): ChatSessionUser | null { return this.session.user; }
|
|
79
|
-
private set user(value: ChatSessionUser | null) { this.session.user = value; }
|
|
80
|
-
|
|
81
71
|
private readonly storage = new ChatControllerStorage({
|
|
82
72
|
emit: (channelId) => this.emit(channelId),
|
|
83
73
|
getSessionToken: () => this.session.sessionToken,
|
|
@@ -245,8 +245,8 @@ describe("ChatController", () => {
|
|
|
245
245
|
const controller = new ChatController();
|
|
246
246
|
|
|
247
247
|
apiClient.setSessionToken("token-123");
|
|
248
|
-
(controller as any).sessionToken = "token-123";
|
|
249
|
-
(controller as any).user = { id: "u1", username: "vince", emailVerified: false };
|
|
248
|
+
(controller as any).session.sessionToken = "token-123";
|
|
249
|
+
(controller as any).session.user = { id: "u1", username: "vince", emailVerified: false };
|
|
250
250
|
|
|
251
251
|
controller.setAppActive(false);
|
|
252
252
|
(controller as any).realtime.syncVerificationPolling();
|
|
@@ -263,12 +263,12 @@ describe("ChatController", () => {
|
|
|
263
263
|
let closed = false;
|
|
264
264
|
|
|
265
265
|
apiClient.setSessionToken("token-123");
|
|
266
|
-
(controller as any).sessionToken = "token-123";
|
|
267
|
-
(controller as any).user = { id: "u1", username: "vince", emailVerified: false };
|
|
266
|
+
(controller as any).session.sessionToken = "token-123";
|
|
267
|
+
(controller as any).session.user = { id: "u1", username: "vince", emailVerified: false };
|
|
268
268
|
(controller as any).realtime.syncVerificationPolling();
|
|
269
269
|
expect((controller as any).realtime.verificationPollTimer).not.toBeNull();
|
|
270
270
|
|
|
271
|
-
(controller as any).user = { id: "u1", username: "vince", emailVerified: true };
|
|
271
|
+
(controller as any).session.user = { id: "u1", username: "vince", emailVerified: true };
|
|
272
272
|
apiClient.connectChannel = () => ({
|
|
273
273
|
send: async () => {
|
|
274
274
|
throw new Error("not implemented");
|
|
@@ -8,7 +8,7 @@ import { TextAttributes } from "../../../ui";
|
|
|
8
8
|
import { apiClient } from "../../../api-client";
|
|
9
9
|
import { PluginRenderProvider } from "../../runtime";
|
|
10
10
|
import { gloomberbCloudPlugin } from "../cloud";
|
|
11
|
-
import { ChatContent } from "
|
|
11
|
+
import { ChatContent } from "./content";
|
|
12
12
|
import { chatController } from "./controller";
|
|
13
13
|
import { useChatChannelNavigation } from "./content/channel-navigation";
|
|
14
14
|
import {
|
|
@@ -40,7 +40,11 @@ afterEach(async () => {
|
|
|
40
40
|
testSetup = undefined;
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
function createChannelPane(
|
|
43
|
+
function createChannelPane(
|
|
44
|
+
controller: ReturnType<typeof createController>,
|
|
45
|
+
initialChannelId = "equities",
|
|
46
|
+
onChannelChange?: (channelId: string) => void,
|
|
47
|
+
) {
|
|
44
48
|
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-chat"));
|
|
45
49
|
|
|
46
50
|
return function ChannelPane() {
|
|
@@ -54,7 +58,10 @@ function createChannelPane(controller: ReturnType<typeof createController>, init
|
|
|
54
58
|
height={12}
|
|
55
59
|
focused
|
|
56
60
|
channelId={channelId}
|
|
57
|
-
onChannelChange={
|
|
61
|
+
onChannelChange={(nextChannelId) => {
|
|
62
|
+
onChannelChange?.(nextChannelId);
|
|
63
|
+
setChannelId(nextChannelId);
|
|
64
|
+
}}
|
|
58
65
|
/>
|
|
59
66
|
</PluginRenderProvider>
|
|
60
67
|
</AppContext>
|
|
@@ -240,7 +247,10 @@ describe("ChatContent channel sidebar", () => {
|
|
|
240
247
|
dmUser: { id: "u2", username: "bob", displayName: "Bob" },
|
|
241
248
|
};
|
|
242
249
|
};
|
|
243
|
-
const
|
|
250
|
+
const selectedChannels: string[] = [];
|
|
251
|
+
const ChannelPane = createChannelPane(controller, "everyone", (channelId) => {
|
|
252
|
+
selectedChannels.push(channelId);
|
|
253
|
+
});
|
|
244
254
|
|
|
245
255
|
try {
|
|
246
256
|
await act(async () => {
|
|
@@ -275,6 +285,7 @@ describe("ChatContent channel sidebar", () => {
|
|
|
275
285
|
await flushFrame();
|
|
276
286
|
|
|
277
287
|
expect(openedTargets).toEqual([{ username: "bob" }]);
|
|
288
|
+
expect(selectedChannels).toEqual(["dm:bob"]);
|
|
278
289
|
expect(setup().captureCharFrame()).toContain("@bob");
|
|
279
290
|
expect(setup().captureCharFrame()).not.toContain("New DM");
|
|
280
291
|
} finally {
|
|
@@ -355,7 +366,6 @@ describe("ChatContent channel sidebar", () => {
|
|
|
355
366
|
});
|
|
356
367
|
|
|
357
368
|
test("coalesces rapid sidebar navigation to the final channel", async () => {
|
|
358
|
-
const controller = createController({ sessionToken: "token-123" });
|
|
359
369
|
const channels = [
|
|
360
370
|
{ id: "everyone", name: "everyone", created_at: "2026-03-26T12:10:05.684Z" },
|
|
361
371
|
{ id: "equities", name: "equities", created_at: "2026-05-09T00:00:00.000Z" },
|
|
@@ -9,7 +9,7 @@ import { Box } from "../../../ui";
|
|
|
9
9
|
import { apiClient, type ChatChannel, type ChatMessage } from "../../../api-client";
|
|
10
10
|
import { PluginRenderProvider, type PluginRuntimeAccess } from "../../runtime";
|
|
11
11
|
import { setSharedMarketDataForTests, setSharedRegistryForTests } from "../../registry";
|
|
12
|
-
import { ChatContent } from "
|
|
12
|
+
import { ChatContent } from "./content";
|
|
13
13
|
import { ChatController } from "./controller";
|
|
14
14
|
|
|
15
15
|
const TRANSCRIPT_KIND = "channel-transcript";
|
|
@@ -201,7 +201,7 @@ export function createChatTestControls(getSetup: () => ChatTestSetup) {
|
|
|
201
201
|
...event,
|
|
202
202
|
};
|
|
203
203
|
await act(async () => {
|
|
204
|
-
getSetup().renderer.keyInput.emit("keypress", keyEvent
|
|
204
|
+
(getSetup().renderer.keyInput as any).emit("keypress", keyEvent);
|
|
205
205
|
await getSetup().renderOnce();
|
|
206
206
|
});
|
|
207
207
|
return keyEvent;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
CONGRESS_TRADES_PANE_ID,
|
|
11
11
|
CongressTradesPane,
|
|
12
12
|
} from "../congress-trades/pane";
|
|
13
|
-
import { registerTwitterFeedFeature } from "../cloud-tweets";
|
|
13
|
+
import { registerTwitterFeedFeature } from "../cloud-tweets/registration";
|
|
14
14
|
import {
|
|
15
15
|
buildDmCommandResults,
|
|
16
16
|
formatChatPaneTitle,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DataTableColumn } from "../../../components";
|
|
2
2
|
import { formatCompact } from "../../../utils/format";
|
|
3
|
+
|
|
4
|
+
export { truncateWithEllipsis as truncate } from "../../../utils/text-wrap";
|
|
3
5
|
import type {
|
|
4
6
|
CloudCongressMemberPayload,
|
|
5
7
|
CloudCongressTradePayload,
|
|
@@ -76,13 +78,6 @@ export function formatAmountRange(low: number | null, high: number | null, raw?:
|
|
|
76
78
|
return formatMoneyShort(low ?? high);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
export function truncate(value: string, width: number): string {
|
|
80
|
-
if (width <= 0) return "";
|
|
81
|
-
if (value.length <= width) return value;
|
|
82
|
-
if (width <= 3) return value.slice(0, width);
|
|
83
|
-
return `${value.slice(0, width - 3)}...`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
81
|
function compareText(left: string, right: string): number {
|
|
87
82
|
return left.localeCompare(right, "en-US", { sensitivity: "base" });
|
|
88
83
|
}
|
|
@@ -103,15 +103,6 @@ function EarningsCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
103
103
|
return false;
|
|
104
104
|
}, [reload]);
|
|
105
105
|
|
|
106
|
-
const selectDisplayRow = useCallback((row: EarningsDisplayRow) => {
|
|
107
|
-
if (row.kind !== "event") return;
|
|
108
|
-
if (row.eventIdx === activeEventIdx) {
|
|
109
|
-
openEvent(row.event);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
setSelectedIdx(row.eventIdx);
|
|
113
|
-
}, [activeEventIdx, openEvent, setSelectedIdx]);
|
|
114
|
-
|
|
115
106
|
const renderCell = useCallback((
|
|
116
107
|
row: EarningsDisplayRow,
|
|
117
108
|
column: EarningsColumn,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box
|
|
1
|
+
import { Box } from "../../../ui";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { TextAttributes, type ScrollBoxRenderable } from "../../../ui";
|
|
4
4
|
import { DataTableStackView, usePaneFooter, type DataTableCell } from "../../../components";
|
|
@@ -243,10 +243,6 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
243
243
|
}), [countryFilter, cycleCountryFilter, cycleImpactFilter, error, impactFilter, load, loading, nextCountdown, nextEvent?.event, staleness]);
|
|
244
244
|
|
|
245
245
|
const handleHeaderClick = useCallback(() => {}, []);
|
|
246
|
-
const selectDisplayRow = useCallback((row: DisplayRow) => {
|
|
247
|
-
if (row.kind !== "event") return;
|
|
248
|
-
setSelectedIdx(row.eventIdx);
|
|
249
|
-
}, []);
|
|
250
246
|
const openDisplayRow = useCallback((row: DisplayRow) => {
|
|
251
247
|
if (row.kind !== "event") return;
|
|
252
248
|
setDetailEvent(row.event);
|
|
@@ -31,7 +31,7 @@ const HELP_TABS = [
|
|
|
31
31
|
type HelpTabId = typeof HELP_TABS[number]["value"];
|
|
32
32
|
const GLOOMBERB_ISSUES_URL = "https://github.com/vincelwt/gloomberb/issues";
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
function HelpPane({ focused, width, height }: PaneProps) {
|
|
35
35
|
const registry = getSharedRegistry();
|
|
36
36
|
const { openCommandBar, showPane } = usePluginAppActions();
|
|
37
37
|
const [activeTabId, setActiveTabId] = useState<HelpTabId>("basics");
|
|
@@ -381,7 +381,7 @@ export function HelpPane({ focused, width, height }: PaneProps) {
|
|
|
381
381
|
<Box flexDirection="column" width={width} height={height}>
|
|
382
382
|
<Box width={width} height={1} flexShrink={0}>
|
|
383
383
|
<Tabs
|
|
384
|
-
tabs={HELP_TABS}
|
|
384
|
+
tabs={[...HELP_TABS]}
|
|
385
385
|
activeValue={activeTabId}
|
|
386
386
|
onSelect={(value) => setActiveTabId(value as HelpTabId)}
|
|
387
387
|
focused={focused}
|
|
@@ -127,11 +127,6 @@ export function HoldersView({ focused, width, height }: { focused: boolean; widt
|
|
|
127
127
|
};
|
|
128
128
|
}, [sortedRows]);
|
|
129
129
|
|
|
130
|
-
const selectIndex = useCallback((index: number) => {
|
|
131
|
-
const row = sortedRows[index];
|
|
132
|
-
if (row) setSelectedId(row.id);
|
|
133
|
-
}, [sortedRows]);
|
|
134
|
-
|
|
135
130
|
const selectedRow = activeIdx >= 0 ? sortedRows[activeIdx] ?? null : null;
|
|
136
131
|
const selectedFundMatch = selectedRow ? fundMatches.get(selectedRow.id) ?? null : null;
|
|
137
132
|
|
|
@@ -14,6 +14,7 @@ import { FeedDataTableStackView, Spinner, useExternalLinkFooter, type FeedDataTa
|
|
|
14
14
|
import { usePluginPaneState } from "../../runtime";
|
|
15
15
|
import { isUsEquityTicker } from "../../../utils/sec";
|
|
16
16
|
import { formatCompact, formatCurrency } from "../../../utils/format";
|
|
17
|
+
import { truncateWithEllipsis as truncateText } from "../../../utils/text-wrap";
|
|
17
18
|
import { parseForm4Xml, type InsiderTransaction } from "./insider-data";
|
|
18
19
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
19
20
|
import {
|
|
@@ -61,13 +62,6 @@ function buildSummary(parsed: ParsedFiling[]): string {
|
|
|
61
62
|
return parts.join(" | ");
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
function truncateText(text: string, width: number): string {
|
|
65
|
-
if (width <= 0) return "";
|
|
66
|
-
if (text.length <= width) return text;
|
|
67
|
-
if (width <= 3) return text.slice(0, width);
|
|
68
|
-
return `${text.slice(0, width - 3)}...`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
65
|
function toFeedItems(parsed: ParsedFiling[]): FeedDataTableItem[] {
|
|
72
66
|
return parsed.map(({ filing, transaction, isLoading }) => {
|
|
73
67
|
const filingMeta = [
|
|
@@ -95,7 +89,6 @@ function toFeedItems(parsed: ParsedFiling[]): FeedDataTableItem[] {
|
|
|
95
89
|
eyebrow: transaction.reportedName,
|
|
96
90
|
title: buildInsiderTransactionTitle(transaction),
|
|
97
91
|
timestamp: transaction.filingDate,
|
|
98
|
-
preview: transaction.title || undefined,
|
|
99
92
|
detailTitle: transaction.reportedName,
|
|
100
93
|
detailMeta: [
|
|
101
94
|
...(transaction.title ? [transaction.title] : []),
|
|
@@ -2,7 +2,6 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { act } from "react";
|
|
3
3
|
import { testRender } from "../../../renderers/opentui/test-utils";
|
|
4
4
|
import { setSharedMarketDataCoordinator } from "../../../market-data/coordinator";
|
|
5
|
-
import type { TickerRecord } from "../../../types/ticker";
|
|
6
5
|
import { KellySizerHarness, createFinancials, createSizerConfig, createTicker } from "./test-support";
|
|
7
6
|
|
|
8
7
|
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
@@ -36,31 +35,6 @@ afterEach(async () => {
|
|
|
36
35
|
});
|
|
37
36
|
|
|
38
37
|
describe("KellySizerPane", () => {
|
|
39
|
-
test("renders binary sizing context, result, and curve", async () => {
|
|
40
|
-
await renderPane();
|
|
41
|
-
await flushFrame();
|
|
42
|
-
|
|
43
|
-
const frame = testSetup!.captureCharFrame();
|
|
44
|
-
expect(frame).toContain("AAPL · Main Portfolio");
|
|
45
|
-
expect(frame).toContain("Bankroll");
|
|
46
|
-
expect(frame).toContain("100000");
|
|
47
|
-
expect(frame).toContain("Win p");
|
|
48
|
-
expect(frame).toContain("Full Kelly");
|
|
49
|
-
expect(frame).toContain("Clipped");
|
|
50
|
-
expect(frame).toContain("Target val");
|
|
51
|
-
expect(frame).toContain("Current %");
|
|
52
|
-
expect(frame).toContain("Kelly Curve");
|
|
53
|
-
expect(frame).toContain("4.0% -> 8.0%");
|
|
54
|
-
expect(frame).toContain("growth");
|
|
55
|
-
expect(frame).toContain("loss cap");
|
|
56
|
-
expect(frame).toContain("cap max loss, max name");
|
|
57
|
-
expect(frame).toContain("full");
|
|
58
|
-
expect(frame).toContain("159%");
|
|
59
|
-
expect(frame).toContain("636%");
|
|
60
|
-
expect(frame).not.toContain("Mode");
|
|
61
|
-
expect(frame).not.toContain("Sensitivity");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
38
|
test("prefills shared caps from plugin config", async () => {
|
|
65
39
|
const config = {
|
|
66
40
|
...createSizerConfig(),
|
|
@@ -51,8 +51,6 @@ function MarketMoversPane({ focused, width, height }: PaneProps) {
|
|
|
51
51
|
const selectedIdx = selectedSymbol
|
|
52
52
|
? rows.findIndex((row) => row.symbol === selectedSymbol)
|
|
53
53
|
: -1;
|
|
54
|
-
const activeIdx = selectedIdx >= 0 ? selectedIdx : (rows.length > 0 ? 0 : -1);
|
|
55
|
-
|
|
56
54
|
useEffect(() => {
|
|
57
55
|
if (selectedSymbol && selectedIdx >= 0) return;
|
|
58
56
|
const firstRow = rows[0];
|