gloomberb 0.12.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -2
- package/src/api-client/auth.ts +2 -1
- package/src/api-client/index.test.ts +34 -0
- package/src/api-client/index.ts +10 -2
- package/src/api-client/request.ts +4 -1
- package/src/api-client/research-activity.ts +61 -0
- package/src/api-client/types.ts +2 -1
- package/src/cli/commands/plugins.ts +4 -3
- package/src/cli/desktop-pane-shot.ts +79 -10
- package/src/cli/pane-functions/capabilities.ts +76 -23
- package/src/cli/pane-functions/cloud-session.test.ts +54 -0
- package/src/cli/pane-functions/cloud-session.ts +38 -0
- package/src/cli/pane-functions/dom.test.ts +41 -0
- package/src/cli/pane-functions/dom.ts +199 -0
- package/src/cli/pane-functions/index.test.ts +13 -2
- package/src/cli/pane-functions/index.ts +9 -5
- package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
- package/src/cli/pane-functions/intraday-shot.ts +334 -0
- package/src/cli/pane-functions/report.test.ts +29 -1
- package/src/cli/pane-functions/report.ts +68 -7
- package/src/cli/pane-functions/screenshot.ts +195 -42
- package/src/cli/restore-plugins.ts +10 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -1
- package/src/components/chart/composite/composite-chart.tsx +87 -21
- package/src/components/chart/composite/rasterizer.ts +33 -0
- package/src/components/chart/composite/renderers.test.ts +51 -4
- package/src/components/chart/composite/scene.test.ts +128 -2
- package/src/components/chart/composite/scene.ts +92 -16
- package/src/components/chart/composite/text-renderer.ts +13 -0
- package/src/components/chart/composite/time-scale.ts +30 -10
- package/src/components/chart/composite/types.ts +26 -0
- package/src/components/command-bar/assist/runtime.ts +38 -30
- package/src/components/command-bar/surface/assist.test.tsx +63 -9
- package/src/components/command-bar/surface/test-harness.tsx +23 -4
- package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
- package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
- package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
- package/src/components/onboarding/onboarding-wizard.tsx +49 -14
- package/src/data/config/store/index.test.ts +23 -0
- package/src/data/config/store/node.ts +7 -1
- package/src/data/config/store/normalize.ts +4 -0
- package/src/news/types.ts +1 -0
- package/src/plugins/builtin/cds/client.test.ts +20 -0
- package/src/plugins/builtin/cds/client.ts +13 -3
- package/src/plugins/builtin/cds/headless.test.ts +30 -0
- package/src/plugins/builtin/cds/headless.ts +113 -0
- package/src/plugins/builtin/cds/index.tsx +4 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chat/controller/channels.ts +2 -1
- package/src/plugins/builtin/chat/controller/index.ts +13 -5
- package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
- package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
- package/src/plugins/builtin/chat/controller/state.ts +1 -0
- package/src/plugins/builtin/chat/controller.test.ts +260 -4
- package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
- package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
- package/src/plugins/builtin/cloud/askg/protocol.test.ts +190 -0
- package/src/plugins/builtin/cloud/askg/protocol.ts +286 -0
- package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
- package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
- package/src/plugins/builtin/cloud/auth-dialog.tsx +7 -1
- package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
- package/src/plugins/builtin/cloud/plugin.tsx +2 -0
- package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
- package/src/plugins/builtin/congress-trades/client.ts +11 -0
- package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
- package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
- package/src/plugins/builtin/congress-trades/headless.ts +145 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
- package/src/plugins/builtin/credit-conditions/client.ts +1 -1
- package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +4 -0
- package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
- package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
- package/src/plugins/builtin/dividend-yield/model.ts +4 -22
- package/src/plugins/builtin/dividend-yield/view.ts +17 -0
- package/src/plugins/builtin/earnings/headless.test.ts +82 -0
- package/src/plugins/builtin/earnings/headless.ts +162 -0
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/data.ts +29 -8
- package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
- package/src/plugins/builtin/earnings-calls/model.ts +238 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +4 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +13 -11
- package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
- package/src/plugins/builtin/fear-greed/headless.ts +84 -0
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/holders/client.ts +29 -0
- package/src/plugins/builtin/holders/headless.test.ts +58 -0
- package/src/plugins/builtin/holders/headless.ts +162 -0
- package/src/plugins/builtin/holders/index.tsx +14 -8
- package/src/plugins/builtin/holders/pane.tsx +7 -1
- package/src/plugins/builtin/insider/client.ts +50 -0
- package/src/plugins/builtin/insider/headless.test.ts +122 -0
- package/src/plugins/builtin/insider/headless.ts +117 -0
- package/src/plugins/builtin/insider/index.tsx +21 -50
- package/src/plugins/builtin/insider/insider-data.ts +1 -1
- package/src/plugins/builtin/insider/model.ts +72 -0
- package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
- package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
- package/src/plugins/builtin/market-movers/client.ts +76 -0
- package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
- package/src/plugins/builtin/market-movers/headless.ts +73 -0
- package/src/plugins/builtin/market-movers/index.tsx +11 -51
- package/src/plugins/builtin/news/headless.test.ts +138 -0
- package/src/plugins/builtin/news/headless.ts +190 -0
- package/src/plugins/builtin/news/index.tsx +13 -9
- package/src/plugins/builtin/news/wire/index.tsx +2 -1
- package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
- package/src/plugins/builtin/research/analyst-headless.ts +172 -0
- package/src/plugins/builtin/research/analyst-model.ts +219 -0
- package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
- package/src/plugins/builtin/research/client.ts +12 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
- package/src/plugins/builtin/research/event-model.ts +245 -0
- package/src/plugins/builtin/research/events-headless.test.ts +32 -0
- package/src/plugins/builtin/research/events-headless.ts +87 -0
- package/src/plugins/builtin/research/headless.test.ts +93 -0
- package/src/plugins/builtin/research/headless.ts +161 -0
- package/src/plugins/builtin/research/index.tsx +32 -18
- package/src/plugins/builtin/research-search/pane.tsx +4 -0
- package/src/plugins/builtin/scanner/headless.test.ts +59 -0
- package/src/plugins/builtin/scanner/headless.ts +125 -0
- package/src/plugins/builtin/scanner/index.ts +4 -0
- package/src/plugins/builtin/sec/client.ts +16 -0
- package/src/plugins/builtin/sec/headless.test.ts +72 -0
- package/src/plugins/builtin/sec/headless.ts +69 -0
- package/src/plugins/builtin/sec/index.tsx +20 -75
- package/src/plugins/builtin/sec/model.ts +89 -0
- package/src/plugins/builtin/sectors/client.ts +54 -0
- package/src/plugins/builtin/sectors/headless.test.ts +28 -0
- package/src/plugins/builtin/sectors/headless.ts +80 -0
- package/src/plugins/builtin/sectors/index.tsx +6 -51
- package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
- package/src/plugins/builtin/short-interest/client.ts +5 -2
- package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
- package/src/plugins/builtin/short-interest/headless.ts +111 -0
- package/src/plugins/builtin/short-interest/index.tsx +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +13 -11
- package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
- package/src/plugins/builtin/thirteenf/headless.ts +286 -0
- package/src/plugins/builtin/thirteenf/index.tsx +4 -0
- package/src/plugins/builtin/thirteenf/model.ts +13 -0
- package/src/plugins/builtin/thirteenf/table.ts +3 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
- package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
- package/src/plugins/builtin/volatility/client.ts +16 -3
- package/src/plugins/builtin/volatility/headless.test.ts +35 -0
- package/src/plugins/builtin/volatility/headless.ts +75 -0
- package/src/plugins/builtin/volatility/index.tsx +4 -0
- package/src/plugins/builtin/world-indices/client.ts +43 -0
- package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
- package/src/plugins/builtin/world-indices/headless.ts +109 -0
- package/src/plugins/builtin/world-indices/index.tsx +4 -0
- package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
- package/src/plugins/builtin/yield-curve/headless.ts +67 -0
- package/src/plugins/builtin/yield-curve/index.tsx +6 -8
- package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
- package/src/plugins/bundle.test.ts +58 -1
- package/src/plugins/bundle.ts +4 -2
- package/src/plugins/catalog-backend.test.ts +26 -0
- package/src/plugins/catalog-backend.ts +9 -2
- package/src/plugins/catalog-browser.test.ts +0 -13
- package/src/plugins/catalog-browser.ts +4 -0
- package/src/plugins/catalog-manifest.test.ts +15 -0
- package/src/plugins/catalog-manifest.ts +12 -5
- package/src/plugins/catalog-ui.ts +0 -6
- package/src/plugins/host-contract.ts +9 -0
- package/src/plugins/host-modules.ts +12 -0
- package/src/plugins/loader.ts +63 -15
- package/src/plugins/prediction-markets/headless.test.ts +119 -0
- package/src/plugins/prediction-markets/headless.ts +255 -0
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/seed.ts +3 -0
- package/src/public/broker.ts +6 -0
- package/src/remote/schema.test.ts +65 -0
- package/src/remote/schema.ts +322 -39
- package/src/remote/types.ts +49 -2
- package/src/renderers/browser/app-services.ts +3 -0
- package/src/renderers/browser/config-host.ts +24 -8
- package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
- package/src/renderers/browser/deeplink-bridge.ts +15 -3
- package/src/renderers/browser/main.tsx +3 -1
- package/src/renderers/browser/research-entry.test.ts +8 -0
- package/src/renderers/browser/research-entry.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
- package/src/renderers/electrobun/bun/external-plugins.ts +20 -4
- package/src/renderers/electrobun/view/build-assets.ts +0 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +63 -10
- package/src/sources/gloomberb-cloud/news.ts +1 -0
- package/src/sync/core-contributors.test.ts +111 -0
- package/src/sync/core-contributors.ts +155 -1
- package/src/time-series/resolve.test.ts +118 -1
- package/src/time-series/resolve.ts +14 -16
- package/src/types/config.ts +2 -0
- package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
- package/src/plugins/broker-sync/native-loader.ts +0 -10
- package/src/plugins/broker-sync/normalize.ts +0 -308
- package/src/plugins/broker-sync/public.ts +0 -122
- package/src/plugins/broker-sync/robinhood-native.ts +0 -367
- package/src/plugins/broker-sync/robinhood.ts +0 -114
- package/src/plugins/broker-sync/simplefin-native.ts +0 -201
- package/src/plugins/broker-sync/simplefin.ts +0 -70
- package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createAppServices } from "../../../../core/app-services";
|
|
5
|
+
import {
|
|
6
|
+
appReducer,
|
|
7
|
+
createInitialState,
|
|
8
|
+
type AppAction,
|
|
9
|
+
} from "../../../../core/state/app/state";
|
|
10
|
+
import { getLoadablePlugins } from "../../../catalog";
|
|
11
|
+
import { createAppRemoteController } from "../../../../remote/controller";
|
|
12
|
+
import type { RemoteUiRegistry } from "../../../../remote/semantic-tree";
|
|
13
|
+
import { createDefaultConfig } from "../../../../types/config";
|
|
14
|
+
import type { ASKGToolCallEvent, ClientToolManifest } from "./protocol";
|
|
15
|
+
import { createASKGToolExecutor } from "./executor";
|
|
16
|
+
import { buildASKGClientManifest, REMOTE_RESOURCE_TOOL_NAME } from "./manifest";
|
|
17
|
+
|
|
18
|
+
function toolCall(tool: ClientToolManifest, args: ASKGToolCallEvent["args"]): ASKGToolCallEvent {
|
|
19
|
+
return {
|
|
20
|
+
seq: 1,
|
|
21
|
+
type: "tool-call",
|
|
22
|
+
turnId: "verify-turn",
|
|
23
|
+
toolCallId: `verify-${tool.name}`,
|
|
24
|
+
name: tool.name,
|
|
25
|
+
args,
|
|
26
|
+
writeTier: tool.writeTier,
|
|
27
|
+
requiresConfirmation: false,
|
|
28
|
+
preview: null,
|
|
29
|
+
timeoutMs: tool.timeoutMs,
|
|
30
|
+
expiresAt: new Date(Date.now() + tool.timeoutMs).toISOString(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resultShape(value: unknown): string {
|
|
35
|
+
if (Array.isArray(value)) return "array";
|
|
36
|
+
if (value == null) return String(value);
|
|
37
|
+
if (typeof value !== "object") return typeof value;
|
|
38
|
+
return Object.keys(value as Record<string, unknown>).sort().join(",") || "object";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function verify() {
|
|
42
|
+
const dataDir = mkdtempSync(join(tmpdir(), "gloomberb-askg-client-"));
|
|
43
|
+
const config = { ...createDefaultConfig(dataDir), onboardingComplete: true };
|
|
44
|
+
const services = createAppServices({ config, plugins: getLoadablePlugins() });
|
|
45
|
+
try {
|
|
46
|
+
await services.ready;
|
|
47
|
+
let state = createInitialState(config);
|
|
48
|
+
const dispatch = (action: AppAction) => {
|
|
49
|
+
state = appReducer(state, action);
|
|
50
|
+
};
|
|
51
|
+
services.pluginRegistry.getConfigFn = () => state.config;
|
|
52
|
+
services.pluginRegistry.getLayoutFn = () => state.config.layout;
|
|
53
|
+
services.pluginRegistry.updateLayoutFn = (layout) => dispatch({ type: "UPDATE_LAYOUT", layout });
|
|
54
|
+
|
|
55
|
+
const uiRegistry: RemoteUiRegistry = {
|
|
56
|
+
register() {},
|
|
57
|
+
unregister() {},
|
|
58
|
+
snapshot: () => [],
|
|
59
|
+
invoke: async () => null,
|
|
60
|
+
};
|
|
61
|
+
const remote = createAppRemoteController({
|
|
62
|
+
dispatch,
|
|
63
|
+
getState: () => state,
|
|
64
|
+
pluginRegistry: services.pluginRegistry,
|
|
65
|
+
uiRegistry,
|
|
66
|
+
});
|
|
67
|
+
const built = await buildASKGClientManifest(services.pluginRegistry);
|
|
68
|
+
const sources = { headless: 0, "remote-op": 0 };
|
|
69
|
+
const tiers = { read: 0, "ui-write": 0, "user-data": 0, broker: 0 };
|
|
70
|
+
for (const tool of built.tools) {
|
|
71
|
+
sources[tool.source] += 1;
|
|
72
|
+
tiers[tool.writeTier] += 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(JSON.stringify({
|
|
76
|
+
totalTools: built.tools.length,
|
|
77
|
+
sources,
|
|
78
|
+
tiers,
|
|
79
|
+
headlessNames: built.tools.filter(({ source }) => source === "headless").map(({ name }) => name),
|
|
80
|
+
manifestHash: built.manifestHash,
|
|
81
|
+
skipped: built.skipped,
|
|
82
|
+
}, null, 2));
|
|
83
|
+
|
|
84
|
+
const executor = createASKGToolExecutor({
|
|
85
|
+
manifests: built.tools,
|
|
86
|
+
registry: services.pluginRegistry,
|
|
87
|
+
context: {
|
|
88
|
+
config,
|
|
89
|
+
dataDir,
|
|
90
|
+
persistence: services.persistence,
|
|
91
|
+
store: services.tickerRepository,
|
|
92
|
+
dataProvider: services.providerRouter,
|
|
93
|
+
},
|
|
94
|
+
remoteHandler: remote.handle,
|
|
95
|
+
});
|
|
96
|
+
const headless = built.tools.find(({ source, name, argument }) => (
|
|
97
|
+
source === "headless" && name === "val" && (argument?.kind === "none" || argument?.optional)
|
|
98
|
+
)) ?? built.tools.find(({ source, argument }) => (
|
|
99
|
+
source === "headless" && (argument?.kind === "none" || argument?.optional)
|
|
100
|
+
));
|
|
101
|
+
const resource = built.tools.find(({ name }) => name === REMOTE_RESOURCE_TOOL_NAME);
|
|
102
|
+
if (!headless || !resource) throw new Error("Verification tools are missing from the manifest.");
|
|
103
|
+
|
|
104
|
+
const headlessResult = await executor.execute(toolCall(headless, {}));
|
|
105
|
+
const remoteResult = await executor.execute(toolCall(resource, { resource: "app://snapshot" }));
|
|
106
|
+
console.log(JSON.stringify({
|
|
107
|
+
headless: {
|
|
108
|
+
name: headless.name,
|
|
109
|
+
status: headlessResult.status,
|
|
110
|
+
resultShape: resultShape(headlessResult.result),
|
|
111
|
+
rowCount: headlessResult.rowCount,
|
|
112
|
+
elapsedMs: headlessResult.elapsedMs,
|
|
113
|
+
},
|
|
114
|
+
remote: {
|
|
115
|
+
name: resource.name,
|
|
116
|
+
status: remoteResult.status,
|
|
117
|
+
resultShape: resultShape(remoteResult.result),
|
|
118
|
+
rowCount: remoteResult.rowCount,
|
|
119
|
+
elapsedMs: remoteResult.elapsedMs,
|
|
120
|
+
},
|
|
121
|
+
}, null, 2));
|
|
122
|
+
} finally {
|
|
123
|
+
services.destroy();
|
|
124
|
+
rmSync(dataDir, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (import.meta.main) {
|
|
129
|
+
await verify();
|
|
130
|
+
}
|
|
@@ -12,6 +12,7 @@ import { useAppLanguage } from "../../../i18n/react";
|
|
|
12
12
|
import { useDialog, type PromptContext } from "../../../ui/dialog";
|
|
13
13
|
import { AuthForm, authFormTitle } from "./auth-form";
|
|
14
14
|
import type { AccountMode } from "./auth-model";
|
|
15
|
+
import { CloudVerificationPanel } from "./verification-panel";
|
|
15
16
|
|
|
16
17
|
export function AuthDialog({
|
|
17
18
|
initialMode,
|
|
@@ -20,13 +21,18 @@ export function AuthDialog({
|
|
|
20
21
|
}: PromptContext<AuthUser | undefined> & { initialMode: AccountMode }) {
|
|
21
22
|
useAppLanguage();
|
|
22
23
|
const [mode, setMode] = useState<AccountMode>(initialMode);
|
|
24
|
+
const [verifyingUser, setVerifyingUser] = useState<AuthUser | null>(null);
|
|
25
|
+
|
|
26
|
+
if (verifyingUser) return <DialogFrame title={`Confirm ${verifyingUser.email}`}>
|
|
27
|
+
<CloudVerificationPanel onVerified={resolve} onContinueFree={() => resolve(verifyingUser)} />
|
|
28
|
+
</DialogFrame>;
|
|
23
29
|
|
|
24
30
|
return (
|
|
25
31
|
<DialogFrame title={authFormTitle(mode)}>
|
|
26
32
|
<AuthForm
|
|
27
33
|
initialMode={initialMode}
|
|
28
34
|
onModeChange={setMode}
|
|
29
|
-
onSignedIn={resolve}
|
|
35
|
+
onSignedIn={(user) => user.emailVerified ? resolve(user) : setVerifyingUser(user)}
|
|
30
36
|
onEscape={dismiss}
|
|
31
37
|
/>
|
|
32
38
|
</DialogFrame>
|
|
@@ -8,9 +8,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
8
8
|
import type { AuthUser } from "../../../api-client";
|
|
9
9
|
import { t, tf } from "../../../i18n";
|
|
10
10
|
import { useAppLanguage } from "../../../i18n/react";
|
|
11
|
-
import { useViewport } from "../../../react/input";
|
|
11
|
+
import { useShortcut, useViewport } from "../../../react/input";
|
|
12
12
|
import { colors } from "../../../theme/colors";
|
|
13
|
-
import { Box, Text, TextAttributes, useUiHost } from "../../../ui";
|
|
13
|
+
import { Box, Text, TextAttributes, useUiHost, useRendererHost } from "../../../ui";
|
|
14
|
+
import { Button } from "../../../components/ui/button";
|
|
14
15
|
import { renderAsciiText } from "../../../ui/ascii-font";
|
|
15
16
|
import { useDialog, useDialogKeyboard, type PromptContext } from "../../../ui/dialog";
|
|
16
17
|
import { renderQrLines, renderQrSvgDataUri } from "../../../ui/qr";
|
|
@@ -55,6 +56,7 @@ export function DeviceSignInPanel({
|
|
|
55
56
|
height: number;
|
|
56
57
|
}) {
|
|
57
58
|
useAppLanguage();
|
|
59
|
+
const renderer = useRendererHost();
|
|
58
60
|
const desktop = useUiHost().kind === "desktop-web";
|
|
59
61
|
// Cell lines still drive layout on desktop: they give the code its row/column
|
|
60
62
|
// footprint, but the pixels come from the SVG below.
|
|
@@ -67,14 +69,25 @@ export function DeviceSignInPanel({
|
|
|
67
69
|
[desktop, snapshot.verificationUri],
|
|
68
70
|
);
|
|
69
71
|
const status = deviceSignInStatus(snapshot);
|
|
72
|
+
useShortcut((event) => {
|
|
73
|
+
if (!isPlainKey(event, "b") || !snapshot.verificationUri) return;
|
|
74
|
+
event.preventDefault(); event.stopPropagation();
|
|
75
|
+
void renderer.openExternal(snapshot.verificationUri).catch(() => {});
|
|
76
|
+
}, { scope: "device-signin:browser", phase: "before" });
|
|
70
77
|
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
78
|
+
// Reserve the browser button before fitting the QR, code, and status.
|
|
79
|
+
const contentHeight = height - (snapshot.verificationUri ? 2 : 0);
|
|
80
|
+
const showQr = qrLines.length > 0 && contentHeight >= qrLines.length + 2;
|
|
81
|
+
const spacious = showQr && contentHeight >= qrLines.length + 8;
|
|
82
|
+
const showUrl = !!snapshot.verificationUri && (!showQr || contentHeight >= qrLines.length + 4);
|
|
75
83
|
|
|
76
84
|
return (
|
|
77
85
|
<Box flexDirection="column" alignItems="center">
|
|
86
|
+
{snapshot.verificationUri && <Box height={2}>
|
|
87
|
+
<Button label="Continue in browser" shortcut="b" variant="primary" onPress={() => {
|
|
88
|
+
void renderer.openExternal(snapshot.verificationUri!).catch(() => {});
|
|
89
|
+
}} />
|
|
90
|
+
</Box>}
|
|
78
91
|
{showQr && (qrImage
|
|
79
92
|
? (
|
|
80
93
|
<Box
|
|
@@ -128,7 +141,7 @@ export function DeviceSignInPanel({
|
|
|
128
141
|
{spacious && <Box height={1} />}
|
|
129
142
|
<Box height={1}>
|
|
130
143
|
<Text fg={colors.textMuted}>
|
|
131
|
-
{
|
|
144
|
+
{snapshot.verificationUri ?? ""}
|
|
132
145
|
</Text>
|
|
133
146
|
</Box>
|
|
134
147
|
</>
|
|
@@ -184,7 +197,7 @@ export function DeviceSignInDialog({ resolve, dismiss }: PromptContext<AuthUser
|
|
|
184
197
|
<Box flexDirection="column" alignItems="center">
|
|
185
198
|
<Box height={1}>
|
|
186
199
|
<Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
|
|
187
|
-
{t("
|
|
200
|
+
{t("Sign in through your browser or scan the code")}
|
|
188
201
|
</Text>
|
|
189
202
|
</Box>
|
|
190
203
|
<Box height={1} />
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
CONGRESS_TRADES_PANE_ID,
|
|
18
18
|
CongressTradesPane,
|
|
19
19
|
} from "../congress-trades/pane";
|
|
20
|
+
import { congressHeadless } from "../congress-trades/headless";
|
|
20
21
|
import { registerTwitterFeedFeature } from "../cloud-tweets/registration";
|
|
21
22
|
import { composeBuiltinPlugin, type PluginModule } from "../plugin-module";
|
|
22
23
|
import { registerCloudAuthCommands } from "./auth-commands";
|
|
@@ -181,6 +182,7 @@ const congressTradesModule: PluginModule = {
|
|
|
181
182
|
description: "Track newly disclosed House periodic transaction reports.",
|
|
182
183
|
keywords: ["congress", "house", "trades", "ptr", "stock", "disclosures"],
|
|
183
184
|
shortcut: { prefix: "CG" },
|
|
185
|
+
headless: congressHeadless,
|
|
184
186
|
createInstance: () => ({ placement: "floating" }),
|
|
185
187
|
publicShare: createPublicPaneShare("Congress Trades"),
|
|
186
188
|
}],
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { apiClient, type AuthUser } from "../../../api-client";
|
|
3
|
+
import { Button } from "../../../components/ui/button";
|
|
4
|
+
import { useShortcut } from "../../../react/input";
|
|
5
|
+
import { Box, Text } from "../../../ui";
|
|
6
|
+
import { colors } from "../../../theme/colors";
|
|
7
|
+
|
|
8
|
+
/** Shared continuation after email signup in the terminal and browser. */
|
|
9
|
+
export function CloudVerificationPanel({ onVerified, onContinueFree }: {
|
|
10
|
+
onVerified: (user: AuthUser) => void;
|
|
11
|
+
onContinueFree: () => void;
|
|
12
|
+
}) {
|
|
13
|
+
const [status, setStatus] = useState("Check your inbox and spam folder. This page continues automatically.");
|
|
14
|
+
const [sending, setSending] = useState(false);
|
|
15
|
+
const [cooldown, setCooldown] = useState(false);
|
|
16
|
+
const completed = useRef(false);
|
|
17
|
+
const callback = useRef(onVerified);
|
|
18
|
+
callback.current = onVerified;
|
|
19
|
+
const check = useCallback(async () => {
|
|
20
|
+
try {
|
|
21
|
+
const user = await apiClient.getSession();
|
|
22
|
+
if (user?.emailVerified && !completed.current) {
|
|
23
|
+
completed.current = true;
|
|
24
|
+
callback.current(user);
|
|
25
|
+
}
|
|
26
|
+
} catch { setStatus("Connection interrupted. Check again when you're online."); }
|
|
27
|
+
}, []);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
void check();
|
|
30
|
+
const timer = setInterval(() => { void check(); }, 5000);
|
|
31
|
+
return () => clearInterval(timer);
|
|
32
|
+
}, [check]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!cooldown) return;
|
|
35
|
+
const timer = setTimeout(() => setCooldown(false), 60_000);
|
|
36
|
+
return () => clearTimeout(timer);
|
|
37
|
+
}, [cooldown]);
|
|
38
|
+
useShortcut((event) => {
|
|
39
|
+
if (event.name !== "enter" && event.name !== "return") return;
|
|
40
|
+
event.preventDefault(); event.stopPropagation(); void check();
|
|
41
|
+
}, { scope: "cloud-email-verification" });
|
|
42
|
+
return <Box flexDirection="column" gap={1}>
|
|
43
|
+
<Text fg={colors.textDim} wrapText>{status}</Text>
|
|
44
|
+
<Box flexDirection="row" gap={1}>
|
|
45
|
+
<Button label="I've confirmed" variant="primary" onPress={() => { void check(); }} />
|
|
46
|
+
<Button label={cooldown ? "Email sent" : "Resend email"} disabled={sending || cooldown} onPress={() => {
|
|
47
|
+
setSending(true);
|
|
48
|
+
void apiClient.sendVerification().then(() => {
|
|
49
|
+
setCooldown(true); setStatus("Confirmation link sent. Check your inbox and spam folder.");
|
|
50
|
+
}).catch((error) => setStatus(error instanceof Error ? error.message : "Couldn't send email. Try again."))
|
|
51
|
+
.finally(() => setSending(false));
|
|
52
|
+
}} />
|
|
53
|
+
</Box>
|
|
54
|
+
<Button label="Keep using the terminal" variant="ghost" onPress={onContinueFree} />
|
|
55
|
+
</Box>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { apiClient } from "../../../api-client";
|
|
2
|
+
import type { CloudCongressHousePayload } from "../../../api-client";
|
|
3
|
+
import type { CloudCongressHouseParams } from "../../../api-client/paths";
|
|
4
|
+
import type { HeadlessPaneApiClient } from "../../../types/plugin";
|
|
5
|
+
|
|
6
|
+
export function loadCongressHouse(
|
|
7
|
+
params: CloudCongressHouseParams = {},
|
|
8
|
+
client: Pick<HeadlessPaneApiClient, "getCloudCongressHouse"> = apiClient,
|
|
9
|
+
): Promise<CloudCongressHousePayload> {
|
|
10
|
+
return client.getCloudCongressHouse(params);
|
|
11
|
+
}
|
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
CloudCongressMemberPayload,
|
|
16
16
|
CloudCongressTradePayload,
|
|
17
17
|
} from "../../../api-client";
|
|
18
|
-
import { apiClient } from "../../../api-client";
|
|
19
18
|
import {
|
|
20
19
|
CONGRESS_MEMBER_FILING_LIMIT,
|
|
21
20
|
CONGRESS_MEMBER_TRADE_LIMIT,
|
|
@@ -34,6 +33,7 @@ import {
|
|
|
34
33
|
type TradeColumnId,
|
|
35
34
|
} from "./model";
|
|
36
35
|
import { renderCongressTradeCell } from "./table";
|
|
36
|
+
import { loadCongressHouse } from "./client";
|
|
37
37
|
|
|
38
38
|
function DetailLine({
|
|
39
39
|
label,
|
|
@@ -133,7 +133,7 @@ export function MemberTradesDetail({
|
|
|
133
133
|
const gen = fetchGenRef.current;
|
|
134
134
|
setStatus("loading");
|
|
135
135
|
setError(null);
|
|
136
|
-
|
|
136
|
+
loadCongressHouse({
|
|
137
137
|
member: member.memberName,
|
|
138
138
|
limit: CONGRESS_MEMBER_TRADE_LIMIT,
|
|
139
139
|
filingLimit: Math.max(CONGRESS_MEMBER_FILING_LIMIT, filingLimit),
|
|
@@ -162,7 +162,7 @@ export function MemberTradesDetail({
|
|
|
162
162
|
if (!nextRequest) return;
|
|
163
163
|
const gen = fetchGenRef.current;
|
|
164
164
|
setLoadingMore(true);
|
|
165
|
-
|
|
165
|
+
loadCongressHouse({
|
|
166
166
|
...nextRequest,
|
|
167
167
|
member: member.memberName,
|
|
168
168
|
limit: CONGRESS_MEMBER_TRADE_LIMIT,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { CloudCongressHousePayload } from "../../../api-client";
|
|
3
|
+
import { createTestDataProvider } from "../../../test-support/data-provider";
|
|
4
|
+
import { createDefaultConfig } from "../../../types/config";
|
|
5
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
6
|
+
import { createCongressHeadless } from "./headless";
|
|
7
|
+
|
|
8
|
+
const payload: CloudCongressHousePayload = {
|
|
9
|
+
asOf: "2026-09-04T12:00:00.000Z",
|
|
10
|
+
chamber: "house",
|
|
11
|
+
source: "house-clerk",
|
|
12
|
+
year: 2026,
|
|
13
|
+
indexUpdatedAt: "2026-09-04T11:00:00.000Z",
|
|
14
|
+
filingsScanned: 2,
|
|
15
|
+
filingCount: 2,
|
|
16
|
+
trades: [
|
|
17
|
+
{
|
|
18
|
+
id: "trade-1",
|
|
19
|
+
chamber: "house",
|
|
20
|
+
filingId: "filing-1",
|
|
21
|
+
docId: "doc-1",
|
|
22
|
+
memberName: "Nancy Pelosi",
|
|
23
|
+
stateDistrict: "CA-11",
|
|
24
|
+
filingDate: "2026-09-03",
|
|
25
|
+
transactionDate: "2026-08-20",
|
|
26
|
+
notificationDate: null,
|
|
27
|
+
lagDays: 14,
|
|
28
|
+
side: "BUY",
|
|
29
|
+
transactionType: "Purchase",
|
|
30
|
+
ticker: "NVDA",
|
|
31
|
+
assetName: "NVIDIA Corporation",
|
|
32
|
+
assetType: "Stock",
|
|
33
|
+
owner: "SP",
|
|
34
|
+
rawOwner: "Spouse",
|
|
35
|
+
amount: "$1,000,001 - $5,000,000",
|
|
36
|
+
amountLow: 1_000_001,
|
|
37
|
+
amountHigh: 5_000_000,
|
|
38
|
+
capGainsOver200: null,
|
|
39
|
+
filingStatus: null,
|
|
40
|
+
subholdingOf: null,
|
|
41
|
+
description: null,
|
|
42
|
+
sourceUrl: "https://disclosures-clerk.house.gov/filing-1",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
members: [
|
|
46
|
+
{
|
|
47
|
+
id: "member-1",
|
|
48
|
+
memberName: "Nancy Pelosi",
|
|
49
|
+
stateDistrict: "CA-11",
|
|
50
|
+
tradeCount: 12,
|
|
51
|
+
buyCount: 8,
|
|
52
|
+
sellCount: 4,
|
|
53
|
+
exchangeCount: 0,
|
|
54
|
+
otherCount: 0,
|
|
55
|
+
estimatedLow: 2_000_000,
|
|
56
|
+
estimatedHigh: 8_000_000,
|
|
57
|
+
lastFilingDate: "2026-09-03",
|
|
58
|
+
avgLagDays: 18,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function context(): HeadlessPaneContext {
|
|
64
|
+
return {
|
|
65
|
+
marketData: createTestDataProvider(),
|
|
66
|
+
apiClient: {} as HeadlessPaneContext["apiClient"],
|
|
67
|
+
config: createDefaultConfig("/tmp/gloomberb-headless-congress"),
|
|
68
|
+
signal: new AbortController().signal,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function args(tab: "trades" | "members", limit = 50): HeadlessPaneLoadArgs {
|
|
73
|
+
return { rawArgument: "", argument: null, symbols: [], options: { tab, year: 2026, limit } };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("congress headless model", () => {
|
|
77
|
+
test("projects the active tab and applies its row limit", async () => {
|
|
78
|
+
const headless = createCongressHeadless({ loadHouse: async () => payload });
|
|
79
|
+
|
|
80
|
+
const trades = await headless.load(args("trades", 1), context());
|
|
81
|
+
expect(trades.rows).toMatchObject([{
|
|
82
|
+
memberName: "Nancy Pelosi",
|
|
83
|
+
side: "BUY",
|
|
84
|
+
ticker: "NVDA",
|
|
85
|
+
amountLow: 1_000_001,
|
|
86
|
+
amountHigh: 5_000_000,
|
|
87
|
+
}]);
|
|
88
|
+
expect(trades.columns?.map((column) => column.key)).toContain("ticker");
|
|
89
|
+
|
|
90
|
+
const members = await headless.load(args("members", 1), context());
|
|
91
|
+
expect(members.rows).toMatchObject([{
|
|
92
|
+
memberName: "Nancy Pelosi",
|
|
93
|
+
tradeCount: 12,
|
|
94
|
+
buyCount: 8,
|
|
95
|
+
}]);
|
|
96
|
+
expect(members.columns?.map((column) => column.key)).toContain("tradeCount");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HeadlessPaneColumn,
|
|
3
|
+
HeadlessPaneContext,
|
|
4
|
+
HeadlessPaneDefinition,
|
|
5
|
+
HeadlessPaneLoadArgs,
|
|
6
|
+
} from "../../../types/plugin";
|
|
7
|
+
import type { CloudCongressHousePayload } from "../../../api-client";
|
|
8
|
+
import { loadCongressHouse } from "./client";
|
|
9
|
+
import {
|
|
10
|
+
CONGRESS_FILING_LIMIT,
|
|
11
|
+
CONGRESS_TRADE_LIMIT,
|
|
12
|
+
formatAmountRange,
|
|
13
|
+
formatLag,
|
|
14
|
+
formatShortDate,
|
|
15
|
+
sortedMembers,
|
|
16
|
+
sortedTrades,
|
|
17
|
+
} from "./model";
|
|
18
|
+
|
|
19
|
+
const TRADE_COLUMNS: HeadlessPaneColumn[] = [
|
|
20
|
+
{ key: "filingDate", header: "Filed", format: (value) => formatShortDate(typeof value === "string" ? value : null) },
|
|
21
|
+
{ key: "transactionDate", header: "Tx", format: (value) => formatShortDate(typeof value === "string" ? value : null) },
|
|
22
|
+
{ key: "lagDays", header: "Lag", align: "right", format: (value) => formatLag(typeof value === "number" ? value : null) },
|
|
23
|
+
{ key: "memberName", header: "Member" },
|
|
24
|
+
{ key: "side", header: "Side" },
|
|
25
|
+
{ key: "ticker", header: "Ticker" },
|
|
26
|
+
{
|
|
27
|
+
key: "amountLow",
|
|
28
|
+
header: "Amount",
|
|
29
|
+
align: "right",
|
|
30
|
+
format: (value, row) => formatAmountRange(
|
|
31
|
+
typeof value === "number" ? value : null,
|
|
32
|
+
typeof row.amountHigh === "number" ? row.amountHigh : null,
|
|
33
|
+
typeof row.amount === "string" ? row.amount : undefined,
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
{ key: "owner", header: "Owner" },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const MEMBER_COLUMNS: HeadlessPaneColumn[] = [
|
|
40
|
+
{ key: "memberName", header: "Member" },
|
|
41
|
+
{ key: "stateDistrict", header: "Dist" },
|
|
42
|
+
{ key: "tradeCount", header: "Trades", align: "right" },
|
|
43
|
+
{ key: "buyCount", header: "Buy", align: "right" },
|
|
44
|
+
{ key: "sellCount", header: "Sell", align: "right" },
|
|
45
|
+
{
|
|
46
|
+
key: "estimatedLow",
|
|
47
|
+
header: "Est Range",
|
|
48
|
+
align: "right",
|
|
49
|
+
format: (value, row) => formatAmountRange(
|
|
50
|
+
typeof value === "number" ? value : null,
|
|
51
|
+
typeof row.estimatedHigh === "number" ? row.estimatedHigh : null,
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
{ key: "lastFilingDate", header: "Last", format: (value) => formatShortDate(typeof value === "string" ? value : null) },
|
|
55
|
+
{ key: "avgLagDays", header: "Avg", align: "right", format: (value) => formatLag(typeof value === "number" ? value : null) },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
export interface CongressHeadlessDependencies {
|
|
59
|
+
loadHouse(
|
|
60
|
+
args: HeadlessPaneLoadArgs,
|
|
61
|
+
ctx: HeadlessPaneContext,
|
|
62
|
+
): Promise<CloudCongressHousePayload>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const defaultDependencies: CongressHeadlessDependencies = {
|
|
66
|
+
loadHouse: (args, ctx) => loadCongressHouse({
|
|
67
|
+
year: Number(args.options.year),
|
|
68
|
+
limit: CONGRESS_TRADE_LIMIT,
|
|
69
|
+
filingLimit: CONGRESS_FILING_LIMIT,
|
|
70
|
+
}, ctx.apiClient),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export function createCongressHeadless(
|
|
74
|
+
dependencies: CongressHeadlessDependencies = defaultDependencies,
|
|
75
|
+
): HeadlessPaneDefinition<"rows"> {
|
|
76
|
+
return {
|
|
77
|
+
shape: "rows",
|
|
78
|
+
argument: {
|
|
79
|
+
kind: "none",
|
|
80
|
+
description: "The House PTR feed does not require an argument.",
|
|
81
|
+
},
|
|
82
|
+
options: [
|
|
83
|
+
{
|
|
84
|
+
key: "tab",
|
|
85
|
+
description: "Congress pane tab.",
|
|
86
|
+
type: "enum",
|
|
87
|
+
values: [{ value: "trades" }, { value: "members" }],
|
|
88
|
+
defaultValue: "trades",
|
|
89
|
+
pluginState: { pluginId: "gloomberb-cloud", key: "activeTab" },
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: "year",
|
|
93
|
+
description: "House disclosure year.",
|
|
94
|
+
type: "integer",
|
|
95
|
+
defaultValue: new Date().getUTCFullYear(),
|
|
96
|
+
minimum: 2008,
|
|
97
|
+
maximum: 2100,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: "limit",
|
|
101
|
+
aliases: ["count", "rows"],
|
|
102
|
+
description: "Maximum rows.",
|
|
103
|
+
type: "integer",
|
|
104
|
+
defaultValue: 50,
|
|
105
|
+
minimum: 1,
|
|
106
|
+
maximum: 200,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
describe: (args) => `Congress Trades | ${args.options.tab === "members" ? "Members" : "Trades"}`,
|
|
110
|
+
async load(args, ctx) {
|
|
111
|
+
const payload = await dependencies.loadHouse(args, ctx);
|
|
112
|
+
const limit = Number(args.options.limit);
|
|
113
|
+
if (args.options.tab === "members") {
|
|
114
|
+
const rows = sortedMembers(payload.members, { columnId: "trades", direction: "desc" })
|
|
115
|
+
.slice(0, limit)
|
|
116
|
+
.map((member) => ({ ...member }));
|
|
117
|
+
return {
|
|
118
|
+
columns: MEMBER_COLUMNS,
|
|
119
|
+
rows,
|
|
120
|
+
metadata: {
|
|
121
|
+
asOf: payload.asOf,
|
|
122
|
+
chamber: payload.chamber,
|
|
123
|
+
source: payload.source,
|
|
124
|
+
year: payload.year,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const rows = sortedTrades(payload.trades, { columnId: "filed", direction: "desc" })
|
|
129
|
+
.slice(0, limit)
|
|
130
|
+
.map((trade) => ({ ...trade }));
|
|
131
|
+
return {
|
|
132
|
+
columns: TRADE_COLUMNS,
|
|
133
|
+
rows,
|
|
134
|
+
metadata: {
|
|
135
|
+
asOf: payload.asOf,
|
|
136
|
+
chamber: payload.chamber,
|
|
137
|
+
source: payload.source,
|
|
138
|
+
year: payload.year,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const congressHeadless = createCongressHeadless();
|
|
@@ -10,7 +10,6 @@ import { useDebouncedPluginPaneState, usePluginPaneState } from "../../runtime";
|
|
|
10
10
|
import { useAutoRefresh } from "../shared/auto-refresh";
|
|
11
11
|
import { useInlineTickerOpener } from "../../../state/hooks/inline-tickers";
|
|
12
12
|
import {
|
|
13
|
-
apiClient,
|
|
14
13
|
type CloudCongressHousePayload,
|
|
15
14
|
type CloudCongressMemberPayload,
|
|
16
15
|
type CloudCongressTradePayload,
|
|
@@ -46,6 +45,7 @@ import {
|
|
|
46
45
|
renderCongressMemberCell,
|
|
47
46
|
renderCongressTradeCell,
|
|
48
47
|
} from "./table";
|
|
48
|
+
import { loadCongressHouse } from "./client";
|
|
49
49
|
|
|
50
50
|
export { CONGRESS_TRADES_PANE_ID } from "./model";
|
|
51
51
|
|
|
@@ -77,7 +77,7 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
77
77
|
setStatus((current) => (current === "loaded" && !refresh ? "loaded" : "loading"));
|
|
78
78
|
setError(null);
|
|
79
79
|
setLoadingMore(false);
|
|
80
|
-
|
|
80
|
+
loadCongressHouse({
|
|
81
81
|
limit: CONGRESS_TRADE_LIMIT,
|
|
82
82
|
filingLimit: CONGRESS_FILING_LIMIT,
|
|
83
83
|
refresh,
|
|
@@ -103,7 +103,7 @@ export function CongressTradesPane({ focused, width, height }: PaneProps) {
|
|
|
103
103
|
if (!nextRequest) return;
|
|
104
104
|
const gen = fetchGenRef.current;
|
|
105
105
|
setLoadingMore(true);
|
|
106
|
-
|
|
106
|
+
loadCongressHouse({
|
|
107
107
|
...nextRequest,
|
|
108
108
|
limit: CONGRESS_TRADE_LIMIT,
|
|
109
109
|
filingLimit: CONGRESS_FILING_LIMIT,
|
|
@@ -19,7 +19,7 @@ export interface CreditConditionsLoadResult {
|
|
|
19
19
|
|
|
20
20
|
const HISTORY_LIMIT = 45;
|
|
21
21
|
|
|
22
|
-
type CreditSeriesLoader = (
|
|
22
|
+
export type CreditSeriesLoader = (
|
|
23
23
|
seriesId: CreditSeriesId,
|
|
24
24
|
options: { limit: number; sortOrder: "desc" },
|
|
25
25
|
) => Promise<CloudFredSeriesPayload>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
3
|
+
import { createCreditConditionsHeadless } from "./headless";
|
|
4
|
+
|
|
5
|
+
const args: HeadlessPaneLoadArgs = { rawArgument: "", argument: null, symbols: [], options: {} };
|
|
6
|
+
|
|
7
|
+
describe("credit conditions headless model", () => {
|
|
8
|
+
test("returns normalized spread rows and partial errors", async () => {
|
|
9
|
+
const headless = createCreditConditionsHeadless({
|
|
10
|
+
load: async () => ({
|
|
11
|
+
rows: [{
|
|
12
|
+
seriesId: "BAMLC0A0CM",
|
|
13
|
+
label: "US IG",
|
|
14
|
+
title: "US Corporate Option-Adjusted Spread",
|
|
15
|
+
units: "Percent",
|
|
16
|
+
frequency: "Daily",
|
|
17
|
+
oasBp: 82.4,
|
|
18
|
+
dailyChangeBp: 1.2,
|
|
19
|
+
date: "2026-09-03",
|
|
20
|
+
stale: false,
|
|
21
|
+
}],
|
|
22
|
+
stale: false,
|
|
23
|
+
errors: ["one series unavailable"],
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
const result = await headless.load(args, {} as HeadlessPaneContext);
|
|
27
|
+
|
|
28
|
+
expect(result.rows).toEqual([expect.objectContaining({ label: "US IG", oasBp: 82.4 })]);
|
|
29
|
+
expect(result.errors).toEqual(["one series unavailable"]);
|
|
30
|
+
});
|
|
31
|
+
});
|