gloomberb 0.12.0 → 0.13.1
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.ts +10 -2
- 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/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 +4 -4
- 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 +193 -23
- 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/surface/assist.test.tsx +20 -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/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/cloud/askg/protocol.test.ts +189 -0
- package/src/plugins/builtin/cloud/askg/protocol.ts +274 -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/verification-panel.tsx +56 -0
- 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/earnings-calls/pane.tsx +4 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +5 -2
- 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/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/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/index.tsx +4 -0
- 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/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/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/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/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
|
@@ -11,6 +11,7 @@ const DEFAULT_SHOT_DEVICE_SCALE_FACTOR = 2;
|
|
|
11
11
|
import {
|
|
12
12
|
renderDesktopPaneScreenshot,
|
|
13
13
|
type DesktopPaneShotApiProxy,
|
|
14
|
+
type DesktopPaneShotIntradayHistory,
|
|
14
15
|
type DesktopPaneShotPayload,
|
|
15
16
|
type DesktopPaneShotRenderResult,
|
|
16
17
|
} from "../desktop-pane-shot";
|
|
@@ -65,6 +66,10 @@ import {
|
|
|
65
66
|
isFinancialAnalysisFunction,
|
|
66
67
|
withShotPriceHistory,
|
|
67
68
|
} from "./data";
|
|
69
|
+
import {
|
|
70
|
+
loadShotIntradayWindow,
|
|
71
|
+
resolveShotIntradayRequest,
|
|
72
|
+
} from "./intraday-shot";
|
|
68
73
|
|
|
69
74
|
const DESKTOP_CELL_WIDTH_PX = 8;
|
|
70
75
|
const DESKTOP_CELL_HEIGHT_PX = 18;
|
|
@@ -289,6 +294,8 @@ export interface PaneScreenshotPriceSeriesEvidence {
|
|
|
289
294
|
pointCount: number;
|
|
290
295
|
first: PaneScreenshotChartPointEvidence;
|
|
291
296
|
last: PaneScreenshotChartPointEvidence;
|
|
297
|
+
resolution?: string;
|
|
298
|
+
sessionDates?: string[];
|
|
292
299
|
}
|
|
293
300
|
|
|
294
301
|
export interface PaneScreenshotFundamentalSeriesEvidence {
|
|
@@ -362,6 +369,7 @@ export interface PaneScreenshotResult {
|
|
|
362
369
|
unavailableSymbols: string[];
|
|
363
370
|
semanticMismatch: boolean;
|
|
364
371
|
usable: boolean;
|
|
372
|
+
unusableReason: string | null;
|
|
365
373
|
dataEvidence: PaneScreenshotDataEvidence | null;
|
|
366
374
|
outputPath: string;
|
|
367
375
|
render: DesktopPaneShotRenderResult & {
|
|
@@ -379,7 +387,7 @@ export function defaultScreenshotPath(resolved: ResolvedPaneFunction, rawArg: st
|
|
|
379
387
|
return resolve(process.cwd(), `gloomberb-${suffix}.png`);
|
|
380
388
|
}
|
|
381
389
|
|
|
382
|
-
async function buildDesktopShotPayload(
|
|
390
|
+
export async function buildDesktopShotPayload(
|
|
383
391
|
resolved: ResolvedPaneFunction,
|
|
384
392
|
context: MarketContext,
|
|
385
393
|
rawArg: string,
|
|
@@ -414,9 +422,10 @@ async function buildDesktopShotPayload(
|
|
|
414
422
|
const paneState = stripDesktopShotCredentials<Record<string, PaneRuntimeState>>({
|
|
415
423
|
[resolved.instance.instanceId]: initialPaneState,
|
|
416
424
|
});
|
|
425
|
+
let shotInstance = stripDesktopShotCredentials<typeof resolved.instance>(resolved.instance);
|
|
417
426
|
const layout = {
|
|
418
427
|
dockRoot: null,
|
|
419
|
-
instances: [
|
|
428
|
+
instances: [shotInstance],
|
|
420
429
|
floating: [{
|
|
421
430
|
instanceId: resolved.instance.instanceId,
|
|
422
431
|
x: 0,
|
|
@@ -444,6 +453,7 @@ async function buildDesktopShotPayload(
|
|
|
444
453
|
|
|
445
454
|
const tickers: TickerRecord[] = [];
|
|
446
455
|
const financials: Array<[string, TickerFinancials]> = [];
|
|
456
|
+
const intradayHistories: DesktopPaneShotIntradayHistory[] = [];
|
|
447
457
|
const optionsChains: Array<[string, OptionsChain]> = [];
|
|
448
458
|
const [fredSeries, capabilitySeries, valuationSeries, statSeries] = await Promise.all([
|
|
449
459
|
collectShotFredSeries(resolved),
|
|
@@ -452,16 +462,64 @@ async function buildDesktopShotPayload(
|
|
|
452
462
|
collectShotStatSeries(resolved),
|
|
453
463
|
]);
|
|
454
464
|
const includeOptionsChains = resolved.pane.id === OPTIONS_PANE_ID || resolved.template?.paneId === OPTIONS_PANE_ID;
|
|
465
|
+
const shotNow = new Date();
|
|
455
466
|
for (const symbol of collectShotSymbols(resolved, rawArg)) {
|
|
456
467
|
const entry = await fetchTickerFinancials(context, symbol);
|
|
457
468
|
const requestedRange = shotPriceHistoryRange(resolved);
|
|
458
469
|
let data = entry.financials;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
470
|
+
const exchange = entry.instrument.exchange
|
|
471
|
+
?? entry.tickerFile?.metadata.exchange
|
|
472
|
+
?? data.quote?.listingExchangeName
|
|
473
|
+
?? data.quote?.exchangeName
|
|
474
|
+
?? "";
|
|
475
|
+
if (resolved.capability.id === "intraday-price-chart") {
|
|
476
|
+
const request = resolveShotIntradayRequest(resolved.options);
|
|
477
|
+
const intraday = await loadShotIntradayWindow({
|
|
478
|
+
provider: context.dataProvider,
|
|
479
|
+
symbol: entry.instrument.symbol,
|
|
480
|
+
exchange,
|
|
481
|
+
request,
|
|
482
|
+
now: shotNow,
|
|
483
|
+
});
|
|
484
|
+
data = { ...data, priceHistory: intraday.points };
|
|
485
|
+
intradayHistories.push({
|
|
486
|
+
symbol: entry.instrument.symbol,
|
|
487
|
+
exchange,
|
|
488
|
+
rangePreset: request.rangePreset,
|
|
489
|
+
resolution: request.resolution,
|
|
490
|
+
requestedSession: request.session,
|
|
491
|
+
sessionDates: intraday.sessionDates,
|
|
492
|
+
points: intraday.points,
|
|
493
|
+
start: intraday.start?.toISOString() ?? null,
|
|
494
|
+
end: intraday.end?.toISOString() ?? null,
|
|
495
|
+
unavailableReason: intraday.unavailableReason,
|
|
496
|
+
});
|
|
497
|
+
const presetStart = subtractTimeRange(shotNow, request.rangePreset);
|
|
498
|
+
const needsExplicitWindow = request.session !== null
|
|
499
|
+
|| (intraday.start !== null && intraday.start.getTime() < presetStart.getTime())
|
|
500
|
+
|| (intraday.end !== null && intraday.end.getTime() > shotNow.getTime());
|
|
501
|
+
if (needsExplicitWindow && intraday.start && intraday.end) {
|
|
502
|
+
const chartSpec = parseChartSpec(shotInstance.settings?.chartSpec);
|
|
503
|
+
if (chartSpec) {
|
|
504
|
+
shotInstance = {
|
|
505
|
+
...shotInstance,
|
|
506
|
+
settings: {
|
|
507
|
+
...shotInstance.settings,
|
|
508
|
+
chartSpec: {
|
|
509
|
+
...chartSpec,
|
|
510
|
+
viewport: {
|
|
511
|
+
...chartSpec.viewport,
|
|
512
|
+
dateWindow: {
|
|
513
|
+
start: intraday.start.toISOString(),
|
|
514
|
+
end: intraday.end.toISOString(),
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
} else if (requestedRange) {
|
|
465
523
|
try {
|
|
466
524
|
const priceHistory = await context.dataProvider.getPriceHistory(entry.instrument.symbol, exchange, requestedRange);
|
|
467
525
|
data = { ...data, priceHistory: clipPriceHistoryToRange(priceHistory, requestedRange) };
|
|
@@ -472,15 +530,13 @@ async function buildDesktopShotPayload(
|
|
|
472
530
|
tickers.push(entry.tickerFile ?? createFallbackTicker(symbol, data, context));
|
|
473
531
|
financials.push([symbol, data]);
|
|
474
532
|
if (includeOptionsChains && context.dataProvider.getOptionsChain) {
|
|
475
|
-
const exchange = entry.instrument.exchange
|
|
476
|
-
?? entry.tickerFile?.metadata.exchange
|
|
477
|
-
?? data.quote?.listingExchangeName
|
|
478
|
-
?? data.quote?.exchangeName
|
|
479
|
-
?? "";
|
|
480
533
|
const chain = await context.dataProvider.getOptionsChain(entry.instrument.symbol, exchange);
|
|
481
534
|
optionsChains.push([symbol, chain]);
|
|
482
535
|
}
|
|
483
536
|
}
|
|
537
|
+
layout.instances[0] = shotInstance;
|
|
538
|
+
config.layout.instances[0] = shotInstance;
|
|
539
|
+
if (config.layouts[0]) config.layouts[0].layout.instances[0] = shotInstance;
|
|
484
540
|
|
|
485
541
|
return {
|
|
486
542
|
config,
|
|
@@ -493,6 +549,7 @@ async function buildDesktopShotPayload(
|
|
|
493
549
|
watermark,
|
|
494
550
|
tickers,
|
|
495
551
|
financials,
|
|
552
|
+
intradayHistories,
|
|
496
553
|
optionsChains,
|
|
497
554
|
fredSeries,
|
|
498
555
|
valuationSeries,
|
|
@@ -525,7 +582,7 @@ export function shotPriceHistoryRange(resolved: ResolvedPaneFunction): TimeRange
|
|
|
525
582
|
case "return-correlation":
|
|
526
583
|
return (resolved.options.rangePreset ?? "1Y") as TimeRange;
|
|
527
584
|
case "intraday-price-chart":
|
|
528
|
-
return "1D";
|
|
585
|
+
return (resolved.options.rangePreset ?? "1D") as TimeRange;
|
|
529
586
|
case "historical-prices":
|
|
530
587
|
case "security-relationship":
|
|
531
588
|
return (resolved.options.range ?? "1Y") as TimeRange;
|
|
@@ -545,6 +602,7 @@ export async function renderDesktopShot({
|
|
|
545
602
|
scale,
|
|
546
603
|
watermark,
|
|
547
604
|
options,
|
|
605
|
+
captureImage = true,
|
|
548
606
|
}: {
|
|
549
607
|
resolved: ResolvedPaneFunction;
|
|
550
608
|
context: MarketContext;
|
|
@@ -556,8 +614,9 @@ export async function renderDesktopShot({
|
|
|
556
614
|
scale?: number;
|
|
557
615
|
watermark?: string | null;
|
|
558
616
|
options: Record<string, string | true>;
|
|
617
|
+
captureImage?: boolean;
|
|
559
618
|
}): Promise<PaneScreenshotResult> {
|
|
560
|
-
await mkdir(dirname(outputPath), { recursive: true });
|
|
619
|
+
if (captureImage) await mkdir(dirname(outputPath), { recursive: true });
|
|
561
620
|
const apiProxy = resolveDesktopShotApiProxy(context);
|
|
562
621
|
const previousSessionToken = apiClient.getSessionToken();
|
|
563
622
|
let payload: DesktopPaneShotPayload;
|
|
@@ -575,7 +634,7 @@ export async function renderDesktopShot({
|
|
|
575
634
|
scale ?? 1,
|
|
576
635
|
watermark ?? null,
|
|
577
636
|
);
|
|
578
|
-
render = await renderDesktopPaneScreenshot(payload, outputPath, apiProxy);
|
|
637
|
+
render = await renderDesktopPaneScreenshot(payload, outputPath, apiProxy, { captureImage });
|
|
579
638
|
} finally {
|
|
580
639
|
apiClient.setSessionToken(previousSessionToken);
|
|
581
640
|
}
|
|
@@ -598,9 +657,10 @@ export async function renderDesktopShot({
|
|
|
598
657
|
);
|
|
599
658
|
const expectedChart = shotExpectedChart(resolved, payload);
|
|
600
659
|
const dataEvidence = shotDataEvidenceFor(resolved, payload);
|
|
601
|
-
const chartEvidenceMismatches =
|
|
602
|
-
? chartEvidenceMismatchesFor(render.semanticUi, expectedChart)
|
|
603
|
-
|
|
660
|
+
const chartEvidenceMismatches = [
|
|
661
|
+
...(expectedChart ? chartEvidenceMismatchesFor(render.semanticUi, expectedChart) : []),
|
|
662
|
+
...intradayChartEvidenceMismatchesFor(resolved, payload, render.semanticUi),
|
|
663
|
+
];
|
|
604
664
|
const semanticMismatch = missingExpectedText.length > 0
|
|
605
665
|
|| missingExpectedSelections.length > 0
|
|
606
666
|
|| chartEvidenceMismatches.length > 0;
|
|
@@ -619,6 +679,9 @@ export async function renderDesktopShot({
|
|
|
619
679
|
requiresStructuredDataEvidence: requiresStructuredDataEvidence(resolved),
|
|
620
680
|
hasStructuredDataEvidence: dataEvidence !== null,
|
|
621
681
|
});
|
|
682
|
+
const unusableReason = usable
|
|
683
|
+
? null
|
|
684
|
+
: shotUnusableReasonFor(resolved, payload, render, unavailableSymbols, semanticMismatch);
|
|
622
685
|
return {
|
|
623
686
|
kind: "pane-screenshot",
|
|
624
687
|
target: resolved.token,
|
|
@@ -637,6 +700,7 @@ export async function renderDesktopShot({
|
|
|
637
700
|
unavailableSymbols,
|
|
638
701
|
semanticMismatch,
|
|
639
702
|
usable,
|
|
703
|
+
unusableReason,
|
|
640
704
|
dataEvidence,
|
|
641
705
|
outputPath,
|
|
642
706
|
render: {
|
|
@@ -652,8 +716,37 @@ export async function renderDesktopShot({
|
|
|
652
716
|
}
|
|
653
717
|
|
|
654
718
|
function requiresStructuredDataEvidence(resolved: ResolvedPaneFunction): boolean {
|
|
655
|
-
return [
|
|
656
|
-
|
|
719
|
+
return [
|
|
720
|
+
"price-chart",
|
|
721
|
+
"intraday-price-chart",
|
|
722
|
+
"price-comparison",
|
|
723
|
+
"fundamental-series",
|
|
724
|
+
"financial-statements",
|
|
725
|
+
].includes(resolved.capability.id);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export function shotUnusableReasonFor(
|
|
729
|
+
resolved: ResolvedPaneFunction,
|
|
730
|
+
payload: DesktopPaneShotPayload,
|
|
731
|
+
render: Pick<DesktopPaneShotRenderResult, "loadingStateDetected" | "errorStateDetected" | "emptyStateDetected">,
|
|
732
|
+
unavailableSymbols: string[],
|
|
733
|
+
semanticMismatch: boolean,
|
|
734
|
+
): string {
|
|
735
|
+
if (resolved.capability.id === "intraday-price-chart") {
|
|
736
|
+
const intraday = payload.intradayHistories[0];
|
|
737
|
+
if (intraday?.unavailableReason) return intraday.unavailableReason;
|
|
738
|
+
if (intraday && intraday.points.length > 0) {
|
|
739
|
+
return `Intraday bars were loaded for ${intraday.symbol}, but the chart did not render them.`;
|
|
740
|
+
}
|
|
741
|
+
const symbol = payload.financials[0]?.[0] ?? resolved.createOptions?.symbol ?? "the ticker";
|
|
742
|
+
return `No intraday price history is available for ${symbol} for the requested session window.`;
|
|
743
|
+
}
|
|
744
|
+
if (render.loadingStateDetected) return "The pane was still loading when the screenshot was captured.";
|
|
745
|
+
if (render.errorStateDetected) return "The pane rendered an error state.";
|
|
746
|
+
if (render.emptyStateDetected) return "The pane rendered an empty state.";
|
|
747
|
+
if (unavailableSymbols.length > 0) return `Data is unavailable for ${unavailableSymbols.join(", ")}.`;
|
|
748
|
+
if (semanticMismatch) return "The rendered content did not match the requested capability.";
|
|
749
|
+
return "The pane did not produce verifiable screenshot evidence.";
|
|
657
750
|
}
|
|
658
751
|
|
|
659
752
|
const STATEMENT_EVIDENCE_KEYS: Record<string, ReadonlySet<string>> = {
|
|
@@ -685,6 +778,23 @@ export function shotDataEvidenceFor(
|
|
|
685
778
|
resolved: ResolvedPaneFunction,
|
|
686
779
|
payload: DesktopPaneShotPayload,
|
|
687
780
|
): PaneScreenshotDataEvidence | null {
|
|
781
|
+
if (resolved.capability.id === "intraday-price-chart") {
|
|
782
|
+
const intraday = payload.intradayHistories[0];
|
|
783
|
+
if (!intraday || intraday.points.length === 0) return null;
|
|
784
|
+
const evidence = chartSeriesEvidence(intraday.symbol, intraday.points);
|
|
785
|
+
if (!evidence.first || !evidence.last) return null;
|
|
786
|
+
return {
|
|
787
|
+
kind: "price-series",
|
|
788
|
+
symbol: intraday.symbol,
|
|
789
|
+
range: intraday.rangePreset,
|
|
790
|
+
pointCount: evidence.pointCount,
|
|
791
|
+
first: evidence.first,
|
|
792
|
+
last: evidence.last,
|
|
793
|
+
resolution: intraday.resolution,
|
|
794
|
+
sessionDates: intraday.sessionDates,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
688
798
|
if (resolved.capability.id === "price-chart") {
|
|
689
799
|
const range = String(resolved.options.rangePreset ?? "5Y") as TimeRange;
|
|
690
800
|
const [symbol, financials] = payload.financials[0] ?? [];
|
|
@@ -1140,6 +1250,48 @@ export function chartEvidenceMismatchesFor(
|
|
|
1140
1250
|
return mismatches;
|
|
1141
1251
|
}
|
|
1142
1252
|
|
|
1253
|
+
export function intradayChartEvidenceMismatchesFor(
|
|
1254
|
+
resolved: ResolvedPaneFunction,
|
|
1255
|
+
payload: DesktopPaneShotPayload,
|
|
1256
|
+
semanticUi: RemoteUiNodeSnapshot[],
|
|
1257
|
+
): string[] {
|
|
1258
|
+
if (resolved.capability.id !== "intraday-price-chart") return [];
|
|
1259
|
+
const intraday = payload.intradayHistories[0];
|
|
1260
|
+
if (!intraday || intraday.points.length === 0) return [];
|
|
1261
|
+
const metadata = semanticUi.find((node) => (
|
|
1262
|
+
node.role === "chart-data" && node.metadata?.kind === "chart-composer"
|
|
1263
|
+
))?.metadata;
|
|
1264
|
+
if (!metadata) return ["rendered intraday chart-data semantic evidence is missing"];
|
|
1265
|
+
const baseSeries = Array.isArray(metadata.baseSeries) ? metadata.baseSeries : [];
|
|
1266
|
+
const actual = baseSeries[0];
|
|
1267
|
+
if (!isRecord(actual)) return ["rendered intraday base series is missing"];
|
|
1268
|
+
|
|
1269
|
+
const expected = chartSeriesEvidence(intraday.symbol, intraday.points);
|
|
1270
|
+
const mismatches: string[] = [];
|
|
1271
|
+
if (actual.pointCount !== expected.pointCount) {
|
|
1272
|
+
mismatches.push("rendered intraday point count does not match");
|
|
1273
|
+
}
|
|
1274
|
+
if (metadata.projectedPointCount !== expected.pointCount) {
|
|
1275
|
+
mismatches.push("rendered intraday projection point count does not match");
|
|
1276
|
+
}
|
|
1277
|
+
const pointMatches = (
|
|
1278
|
+
actualPoint: unknown,
|
|
1279
|
+
expectedPoint: PaneScreenshotChartPointEvidence | null,
|
|
1280
|
+
): boolean => {
|
|
1281
|
+
if (!isRecord(actualPoint) || !expectedPoint) return false;
|
|
1282
|
+
return actualPoint.date === expectedPoint.date
|
|
1283
|
+
&& typeof actualPoint.value === "number"
|
|
1284
|
+
&& closeEnough(actualPoint.value, expectedPoint.close);
|
|
1285
|
+
};
|
|
1286
|
+
if (!pointMatches(actual.first, expected.first)) {
|
|
1287
|
+
mismatches.push("rendered intraday first bar does not match");
|
|
1288
|
+
}
|
|
1289
|
+
if (!pointMatches(actual.last, expected.last)) {
|
|
1290
|
+
mismatches.push("rendered intraday last bar does not match");
|
|
1291
|
+
}
|
|
1292
|
+
return mismatches;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1143
1295
|
export function shotUnavailableSymbols(
|
|
1144
1296
|
resolved: ResolvedPaneFunction,
|
|
1145
1297
|
payload: DesktopPaneShotPayload,
|
|
@@ -1162,6 +1314,16 @@ export function shotUnavailableSymbols(
|
|
|
1162
1314
|
return [];
|
|
1163
1315
|
});
|
|
1164
1316
|
}
|
|
1317
|
+
if (resolved.capability.id === "intraday-price-chart") {
|
|
1318
|
+
const symbol = payload.intradayHistories[0]?.symbol ?? payload.financials[0]?.[0];
|
|
1319
|
+
const metadata = semanticUi.find((node) => (
|
|
1320
|
+
node.role === "chart-data" && node.metadata?.kind === "chart-composer"
|
|
1321
|
+
))?.metadata;
|
|
1322
|
+
const renderedPoints = typeof metadata?.projectedPointCount === "number"
|
|
1323
|
+
? metadata.projectedPointCount
|
|
1324
|
+
: 0;
|
|
1325
|
+
return symbol && renderedPoints <= 0 ? [symbol] : [];
|
|
1326
|
+
}
|
|
1165
1327
|
const graphKind = shotGraphKind(resolved);
|
|
1166
1328
|
if (graphKind) {
|
|
1167
1329
|
const metric = resolved.options.metric as GraphMetricKey;
|
|
@@ -1174,7 +1336,7 @@ export function shotUnavailableSymbols(
|
|
|
1174
1336
|
).length === 0 ? [symbol] : []
|
|
1175
1337
|
));
|
|
1176
1338
|
}
|
|
1177
|
-
if (["price-chart", "
|
|
1339
|
+
if (["price-chart", "historical-prices"].includes(resolved.capability.id)) {
|
|
1178
1340
|
return payload.financials.flatMap(([symbol, financials]) => financials.priceHistory.length > 0 ? [] : [symbol]);
|
|
1179
1341
|
}
|
|
1180
1342
|
if (["price-comparison", "return-correlation", "security-relationship"].includes(resolved.capability.id)) {
|
|
@@ -1211,6 +1373,14 @@ export function shotSemanticRowCount(
|
|
|
1211
1373
|
count + (isRecord(entry) && typeof entry.pointCount === "number" ? Math.max(0, entry.pointCount) : 0)
|
|
1212
1374
|
), 0);
|
|
1213
1375
|
}
|
|
1376
|
+
if (resolved.capability.id === "intraday-price-chart") {
|
|
1377
|
+
const metadata = semanticUi.find((node) => (
|
|
1378
|
+
node.role === "chart-data" && node.metadata?.kind === "chart-composer"
|
|
1379
|
+
))?.metadata;
|
|
1380
|
+
return typeof metadata?.projectedPointCount === "number"
|
|
1381
|
+
? Math.max(0, metadata.projectedPointCount)
|
|
1382
|
+
: 0;
|
|
1383
|
+
}
|
|
1214
1384
|
const graphKind = shotGraphKind(resolved);
|
|
1215
1385
|
if (graphKind) {
|
|
1216
1386
|
const metric = resolved.options.metric as GraphMetricKey;
|
|
@@ -1223,7 +1393,7 @@ export function shotSemanticRowCount(
|
|
|
1223
1393
|
).length
|
|
1224
1394
|
), 0);
|
|
1225
1395
|
}
|
|
1226
|
-
if (["price-chart", "
|
|
1396
|
+
if (["price-chart", "historical-prices"].includes(resolved.capability.id)) {
|
|
1227
1397
|
return payload.financials[0]?.[1].priceHistory.length ?? 0;
|
|
1228
1398
|
}
|
|
1229
1399
|
if (["price-comparison", "return-correlation", "security-relationship"].includes(resolved.capability.id)) {
|
|
@@ -11,24 +11,31 @@ const log = debugLog.createLogger("plugin-seed");
|
|
|
11
11
|
* Runs before the plugin catalog is read so a restored plugin is available in
|
|
12
12
|
* the same session, and stays quiet on failure: an offline launch should start
|
|
13
13
|
* normally and try again next time, not block on a network request.
|
|
14
|
+
*
|
|
15
|
+
* Returns the seeded ids so a caller that already holds the config in memory
|
|
16
|
+
* can adopt them. The desktop loads its config before restoring, and without
|
|
17
|
+
* this it would keep an object whose `seededPlugins` is one launch stale and
|
|
18
|
+
* overwrite the record on the next save.
|
|
14
19
|
*/
|
|
15
|
-
export async function restoreExtractedPlugins(): Promise<
|
|
20
|
+
export async function restoreExtractedPlugins(): Promise<string[] | null> {
|
|
16
21
|
try {
|
|
17
22
|
const config = await loadCliConfigIfAvailable();
|
|
18
23
|
// No data directory yet means a first run: there is nothing to restore.
|
|
19
|
-
if (!config) return;
|
|
24
|
+
if (!config) return null;
|
|
20
25
|
const { installPlugin } = await import("./commands/plugins");
|
|
21
26
|
const result = await seedExtractedPlugins(config, installPlugin);
|
|
22
27
|
|
|
23
28
|
const seeded = [...new Set(result.seeded)].sort();
|
|
24
29
|
const existing = [...new Set(config.seededPlugins ?? [])].sort();
|
|
25
|
-
if (seeded.join() === existing.join()) return;
|
|
30
|
+
if (seeded.join() === existing.join()) return seeded;
|
|
26
31
|
|
|
27
32
|
await saveConfig({ ...config, seededPlugins: seeded });
|
|
28
33
|
if (result.installed.length > 0) {
|
|
29
34
|
log.info(`Restored ${result.installed.join(", ")} into ~/.gloomberb/plugins`);
|
|
30
35
|
}
|
|
36
|
+
return seeded;
|
|
31
37
|
} catch (error) {
|
|
32
38
|
log.error(`Plugin restore skipped: ${error}`);
|
|
39
|
+
return null;
|
|
33
40
|
}
|
|
34
41
|
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
buildCompositeNavigationFrame,
|
|
32
32
|
zoomCompositeViewport,
|
|
33
33
|
} from "./interactions";
|
|
34
|
+
import { COMPOSITE_RIGHT_OFFSET_RATIO } from "./time-scale";
|
|
34
35
|
import { getThemeColors, syncTheme } from "../../../theme/colors";
|
|
35
36
|
import { ThemeProvider } from "../../../theme/theme-context";
|
|
36
37
|
import { DEFAULT_THEME } from "../../../theme/themes";
|
|
@@ -304,6 +305,70 @@ describe("CompositeChart", () => {
|
|
|
304
305
|
expect(testSetup.captureCharFrame()).toContain("ACME Price $110 +10.65%");
|
|
305
306
|
});
|
|
306
307
|
|
|
308
|
+
test("holds a lower panel's rows while its series has no observations in view", async () => {
|
|
309
|
+
const price: ResolvedSeries = {
|
|
310
|
+
...series("price", "main", "left", "USD", []),
|
|
311
|
+
points: [
|
|
312
|
+
point("2025-01-01", 100),
|
|
313
|
+
point("2025-01-02", 103),
|
|
314
|
+
point("2025-01-03", 101),
|
|
315
|
+
point("2025-01-04", 104),
|
|
316
|
+
point("2025-01-05", 102),
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
const volume: ResolvedSeries = {
|
|
320
|
+
...series("volume", "volume", "left", "shares", []),
|
|
321
|
+
label: "Volume",
|
|
322
|
+
unitGroup: "volume",
|
|
323
|
+
style: "columns",
|
|
324
|
+
points: [point("2025-01-01", 4_000), point("2025-01-02", 6_000)],
|
|
325
|
+
};
|
|
326
|
+
const panels = [{ id: "main", height: 3 }, { id: "volume", height: 1 }];
|
|
327
|
+
const renderChart = async (
|
|
328
|
+
volumeSeries: ResolvedSeries,
|
|
329
|
+
viewport?: { start: Date; end: Date },
|
|
330
|
+
) => {
|
|
331
|
+
testSetup = await testRender(
|
|
332
|
+
<CompositeChart
|
|
333
|
+
width={78}
|
|
334
|
+
height={18}
|
|
335
|
+
series={[price, volumeSeries]}
|
|
336
|
+
panels={panels}
|
|
337
|
+
viewport={viewport}
|
|
338
|
+
/>,
|
|
339
|
+
{ width: 80, height: 20 },
|
|
340
|
+
);
|
|
341
|
+
await act(async () => {
|
|
342
|
+
await testSetup!.renderOnce();
|
|
343
|
+
await testSetup!.renderOnce();
|
|
344
|
+
});
|
|
345
|
+
const frame = testSetup!.captureCharFrame();
|
|
346
|
+
await act(async () => testSetup!.renderer.destroy());
|
|
347
|
+
testSetup = undefined;
|
|
348
|
+
return frame;
|
|
349
|
+
};
|
|
350
|
+
// The lowest price-axis label sits on the boundary between the two panels,
|
|
351
|
+
// so it moves down the moment the lower panel gives up its rows.
|
|
352
|
+
const priceAxisFloor = (frame: string) => frame
|
|
353
|
+
.split("\n")
|
|
354
|
+
.reduce((row, line, index) => line.includes("$") ? index : row, -1);
|
|
355
|
+
|
|
356
|
+
const loaded = await renderChart(volume);
|
|
357
|
+
// Panned to a window the upper panel covers and the lower panel has no bars in.
|
|
358
|
+
const panned = await renderChart(volume, {
|
|
359
|
+
start: new Date("2025-01-03T00:00:00.000Z"),
|
|
360
|
+
end: new Date("2025-01-05T00:00:00.000Z"),
|
|
361
|
+
});
|
|
362
|
+
// The same chart before the lower panel's data has loaded at all.
|
|
363
|
+
const loading = await renderChart({ ...volume, points: [] });
|
|
364
|
+
|
|
365
|
+
expect(priceAxisFloor(loaded)).toBeGreaterThan(0);
|
|
366
|
+
expect(priceAxisFloor(panned)).toBe(priceAxisFloor(loaded));
|
|
367
|
+
expect(priceAxisFloor(loading)).toBe(priceAxisFloor(loaded));
|
|
368
|
+
// A panel with loaded history keeps its axis while the window holds no bars.
|
|
369
|
+
expect(panned).toMatch(/\dK/);
|
|
370
|
+
});
|
|
371
|
+
|
|
307
372
|
test("lays out mixed panels with one shared legend and time axis", async () => {
|
|
308
373
|
testSetup = await testRender(
|
|
309
374
|
<CompositeChart
|
|
@@ -1429,7 +1494,9 @@ describe("CompositeChart", () => {
|
|
|
1429
1494
|
frame,
|
|
1430
1495
|
viewport,
|
|
1431
1496
|
1 + 4 * 0.04,
|
|
1432
|
-
|
|
1497
|
+
// The pointer reads in plot space, which is wider than the viewport by
|
|
1498
|
+
// the reserved right offset.
|
|
1499
|
+
pointerX / Math.max(plotWidth - 1, 1) * (1 + COMPOSITE_RIGHT_OFFSET_RATIO),
|
|
1433
1500
|
);
|
|
1434
1501
|
|
|
1435
1502
|
await act(async () => {
|