gloomberb 0.8.1 → 0.8.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 +102 -52
- package/package.json +1 -1
- package/src/api-client/chat.ts +8 -0
- package/src/api-client/index.test.ts +30 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/types.ts +1 -0
- package/src/capabilities/factories.ts +4 -0
- package/src/components/chart/comparison/data.test.ts +54 -0
- package/src/components/chart/comparison/data.ts +47 -24
- package/src/components/chart/core/projection.ts +51 -27
- package/src/components/data-table/stack-view.test.tsx +14 -11
- package/src/components/data-table/stack-view.tsx +1 -2
- package/src/components/data-table/view.test.tsx +24 -18
- package/src/components/data-table/view.tsx +313 -83
- package/src/components/feed-data-table/stack-view.tsx +6 -8
- package/src/components/layout/pane/footer/index.test.tsx +6 -0
- package/src/components/layout/pane/footer/index.tsx +14 -2
- package/src/components/layout/pane/title.ts +5 -1
- package/src/components/pane-settings-dialog/field-dialogs.tsx +0 -2
- package/src/components/pane-settings-dialog/tui.tsx +1 -1
- package/src/components/ticker/list-table-view.tsx +11 -25
- package/src/components/ui/choice-dialog.tsx +12 -2
- package/src/components/ui/multi-select/dialog.tsx +17 -5
- package/src/components/ui/shortcut-hint.tsx +14 -2
- package/src/components/ui/table-layout.test.ts +2 -2
- package/src/components/ui/table-layout.ts +8 -2
- package/src/components/ui/tabs.tsx +20 -6
- package/src/components/ui/ui.test.tsx +30 -5
- package/src/market-data/coordinator/auxiliary.ts +29 -1
- package/src/market-data/coordinator/index.ts +17 -1
- package/src/market-data/hooks.tsx +17 -1
- package/src/market-data/selectors.ts +4 -0
- package/src/plugins/builtin/account-management/form-components.tsx +0 -4
- package/src/plugins/builtin/account-management/model.test.ts +54 -0
- package/src/plugins/builtin/account-management/model.ts +13 -3
- package/src/plugins/builtin/account-management/pane.tsx +5 -11
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +5 -3
- package/src/plugins/builtin/analytics/index.tsx +0 -3
- package/src/plugins/builtin/analytics/view.tsx +6 -5
- package/src/plugins/builtin/broker-manager/index.tsx +6 -6
- package/src/plugins/builtin/buildout/pane/index.tsx +6 -6
- package/src/plugins/builtin/changelog/index.tsx +40 -27
- package/src/plugins/builtin/changelog/model.test.ts +77 -0
- package/src/plugins/builtin/changelog/model.ts +98 -0
- package/src/plugins/builtin/chat/channel-labels.ts +53 -0
- package/src/plugins/builtin/chat/channels.test.ts +28 -0
- package/src/plugins/builtin/chat/channels.ts +36 -19
- package/src/plugins/builtin/chat/chat.test.tsx +264 -5
- package/src/plugins/builtin/chat/content/channel-navigation.ts +6 -0
- package/src/plugins/builtin/chat/content/composer-runtime.test.tsx +144 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +151 -20
- package/src/plugins/builtin/chat/content/composer.tsx +23 -2
- package/src/plugins/builtin/chat/content/index.tsx +61 -0
- package/src/plugins/builtin/chat/content/layout-metrics.ts +3 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +20 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +8 -0
- package/src/plugins/builtin/chat/content/types.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +41 -0
- package/src/plugins/builtin/chat/controller/messages.ts +18 -0
- package/src/plugins/builtin/chat/controller/notifications.ts +10 -4
- package/src/plugins/builtin/chat/controller/utils.ts +6 -3
- package/src/plugins/builtin/chat/controller/view.ts +1 -1
- package/src/plugins/builtin/chat/controller.test.ts +189 -0
- package/src/plugins/builtin/chat/edit-window.ts +24 -0
- package/src/plugins/builtin/chat/layout.ts +94 -31
- package/src/plugins/builtin/chat/message/action-chip.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +53 -20
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +16 -10
- package/src/plugins/builtin/chat/message/render-state.ts +5 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +49 -20
- package/src/plugins/builtin/chat/message/types.ts +2 -0
- package/src/plugins/builtin/chat/pane.tsx +35 -7
- package/src/plugins/builtin/chat/sidebar.test.tsx +1 -0
- package/src/plugins/builtin/chat/status-widget.tsx +29 -11
- package/src/plugins/builtin/chat/test-harness.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +7 -11
- package/src/plugins/builtin/congress-trades/pane.tsx +14 -22
- package/src/plugins/builtin/earnings/index.tsx +8 -7
- package/src/plugins/builtin/econ/index.tsx +6 -8
- package/src/plugins/builtin/help/index.tsx +6 -0
- package/src/plugins/builtin/holders/pane.tsx +7 -6
- package/src/plugins/builtin/market-movers/index.tsx +6 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +7 -6
- package/src/plugins/builtin/options/view.tsx +16 -8
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +358 -7
- package/src/plugins/builtin/research/index.test.ts +30 -1
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +6 -8
- package/src/plugins/builtin/sec/filing-documents.ts +66 -0
- package/src/plugins/builtin/sec/index.tsx +132 -20
- package/src/plugins/builtin/sectors/index.tsx +13 -23
- package/src/plugins/builtin/shared/ticker-request.ts +1 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +26 -25
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +6 -5
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +10 -8
- package/src/plugins/builtin/world-indices/index.tsx +9 -6
- package/src/plugins/prediction-markets/pane.tsx +10 -9
- package/src/plugins/registry/index.ts +3 -0
- package/src/plugins/runtime/context.tsx +1 -0
- package/src/plugins/runtime/index.tsx +4 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +3 -2
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -2
- package/src/renderers/electrobun/view/host/input.tsx +23 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -0
- package/src/sources/provider-router/cache.ts +1 -0
- package/src/sources/provider-router/document-routes.ts +45 -0
- package/src/sources/provider-router/index.ts +4 -0
- package/src/sources/sec-edgar/content.ts +9 -3
- package/src/sources/sec-edgar.test.ts +108 -1
- package/src/sources/sec-edgar.ts +105 -1
- package/src/sources/yahoo-finance.ts +4 -0
- package/src/test-support/plugin-runtime.ts +1 -0
- package/src/types/data-provider.ts +11 -0
- package/src/ui/host.tsx +1 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { SecFilingDocument, SecFilingItem } from "../../../types/data-provider";
|
|
2
|
+
|
|
3
|
+
const SEC_EXHIBIT_TYPE_RE = /^EX[-\s]?\d/i;
|
|
4
|
+
const SEC_XBRL_EXHIBIT_TYPE_RE = /^EX[-\s]?101(?:\.|$)/i;
|
|
5
|
+
const SEC_NON_READABLE_DOCUMENT_RE = /\.(?:cal|def|gif|jpg|jpeg|json|lab|png|pre|sch|xsd|zip)(?:$|[?#])/i;
|
|
6
|
+
const SEC_SUPPORT_DOCUMENT_TYPE_RE = /^(?:GRAPHIC|XML|ZIP|EX[-\s]?101(?:\.|$)|EX-FILING FEES)/i;
|
|
7
|
+
|
|
8
|
+
export function documentContentKey(filing: SecFilingItem, document: SecFilingDocument): string {
|
|
9
|
+
return `${filing.accessionNumber}:${document.document || document.url}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function documentContentTarget(filing: SecFilingItem, document: SecFilingDocument): SecFilingItem {
|
|
13
|
+
return {
|
|
14
|
+
...filing,
|
|
15
|
+
accessionNumber: documentContentKey(filing, document),
|
|
16
|
+
form: document.type || filing.form,
|
|
17
|
+
primaryDocument: document.document,
|
|
18
|
+
primaryDocDescription: document.description,
|
|
19
|
+
primaryDocumentUrl: document.url,
|
|
20
|
+
filingUrl: document.url,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatDocumentLabel(document: SecFilingDocument): string {
|
|
25
|
+
const label = document.isPrimary ? "PRIMARY" : document.type || "DOCUMENT";
|
|
26
|
+
const description = document.description
|
|
27
|
+
&& document.description !== document.document
|
|
28
|
+
&& document.description !== document.type
|
|
29
|
+
? ` | ${document.description}`
|
|
30
|
+
: "";
|
|
31
|
+
const size = document.size ? ` | ${document.size}` : "";
|
|
32
|
+
return `${label.padEnd(8)} ${document.document}${description}${size}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function formatCompactDocumentLabel(document: SecFilingDocument): string {
|
|
36
|
+
const label = document.isPrimary ? "PRIMARY" : document.type || "DOCUMENT";
|
|
37
|
+
const description = document.description
|
|
38
|
+
&& document.description !== document.document
|
|
39
|
+
&& document.description !== document.type
|
|
40
|
+
? ` | ${document.description}`
|
|
41
|
+
: "";
|
|
42
|
+
return `${label} ${document.document}${description}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function documentHeading(document: SecFilingDocument): string {
|
|
46
|
+
const label = document.type || "DOCUMENT";
|
|
47
|
+
const description = document.description
|
|
48
|
+
&& document.description !== document.document
|
|
49
|
+
&& document.description !== document.type
|
|
50
|
+
? ` | ${document.description}`
|
|
51
|
+
: "";
|
|
52
|
+
return `${label} | ${document.document}${description}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isInlineExhibitDocument(document: SecFilingDocument): boolean {
|
|
56
|
+
const type = document.type.trim();
|
|
57
|
+
if (!SEC_EXHIBIT_TYPE_RE.test(type)) return false;
|
|
58
|
+
if (SEC_XBRL_EXHIBIT_TYPE_RE.test(type)) return false;
|
|
59
|
+
return !SEC_NON_READABLE_DOCUMENT_RE.test(document.document) && !SEC_NON_READABLE_DOCUMENT_RE.test(document.url);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isDefaultVisibleFilingDocument(document: SecFilingDocument): boolean {
|
|
63
|
+
if (document.isPrimary) return true;
|
|
64
|
+
if (isInlineExhibitDocument(document)) return true;
|
|
65
|
+
return !SEC_SUPPORT_DOCUMENT_TYPE_RE.test(document.type.trim());
|
|
66
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Text } from "../../../ui";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import type { GloomPlugin } from "../../../types/plugin";
|
|
4
|
-
import type { SecFilingItem } from "../../../types/data-provider";
|
|
5
|
-
import { useResolvedEntryValue, useSecFilingContent, useSecFilingsQuery } from "../../../market-data/hooks";
|
|
4
|
+
import type { SecFilingDocument, SecFilingItem } from "../../../types/data-provider";
|
|
5
|
+
import { useResolvedEntryValue, useSecFilingContent, useSecFilingDocuments, useSecFilingsQuery } from "../../../market-data/hooks";
|
|
6
6
|
import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
7
|
-
import {
|
|
7
|
+
import { useDebouncedPluginPaneState } from "../../runtime";
|
|
8
8
|
import { usePaneTicker } from "../../../state/app/context";
|
|
9
9
|
import { colors } from "../../../theme/colors";
|
|
10
10
|
import { FeedDataTableStackView, Spinner, useExternalLinkFooter, type FeedDataTableItem } from "../../../components";
|
|
@@ -17,6 +17,14 @@ import {
|
|
|
17
17
|
formatFilingMetaDate,
|
|
18
18
|
renderFilingNotice,
|
|
19
19
|
} from "./filing-display";
|
|
20
|
+
import {
|
|
21
|
+
documentContentKey,
|
|
22
|
+
documentContentTarget,
|
|
23
|
+
documentHeading,
|
|
24
|
+
formatCompactDocumentLabel,
|
|
25
|
+
isDefaultVisibleFilingDocument,
|
|
26
|
+
isInlineExhibitDocument,
|
|
27
|
+
} from "./filing-documents";
|
|
20
28
|
|
|
21
29
|
const SEC_FILING_LIMIT = 50;
|
|
22
30
|
const OWNERSHIP_FORMS = new Set(["3", "4", "5"]);
|
|
@@ -80,6 +88,50 @@ function buildDetailBody(filing: SecFilingItem): string {
|
|
|
80
88
|
: "No additional SEC filing description is available for this entry.";
|
|
81
89
|
}
|
|
82
90
|
|
|
91
|
+
function buildDetailBodyWithDocuments({
|
|
92
|
+
filing,
|
|
93
|
+
documents,
|
|
94
|
+
documentsLoading,
|
|
95
|
+
contentCache,
|
|
96
|
+
primaryContent,
|
|
97
|
+
}: {
|
|
98
|
+
filing: SecFilingItem;
|
|
99
|
+
documents: SecFilingDocument[];
|
|
100
|
+
documentsLoading: boolean;
|
|
101
|
+
contentCache: Map<string, string | null>;
|
|
102
|
+
primaryContent: string;
|
|
103
|
+
}): string {
|
|
104
|
+
const lines: string[] = [];
|
|
105
|
+
lines.push("Documents");
|
|
106
|
+
if (documentsLoading && documents.length === 0) {
|
|
107
|
+
lines.push("Loading filing documents...");
|
|
108
|
+
} else if (documents.length === 0) {
|
|
109
|
+
lines.push("No filing documents were listed for this filing.");
|
|
110
|
+
} else {
|
|
111
|
+
const visibleDocuments = documents.filter(isDefaultVisibleFilingDocument);
|
|
112
|
+
lines.push(...visibleDocuments.map(formatCompactDocumentLabel));
|
|
113
|
+
const hiddenCount = documents.length - visibleDocuments.length;
|
|
114
|
+
if (hiddenCount > 0) lines.push(`+ ${hiddenCount} support documents hidden`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const exhibits = documents.filter(isInlineExhibitDocument);
|
|
118
|
+
if (exhibits.length > 0) {
|
|
119
|
+
lines.push("", "Inline Exhibits");
|
|
120
|
+
for (const document of exhibits) {
|
|
121
|
+
const key = documentContentKey(filing, document);
|
|
122
|
+
const hasContent = contentCache.has(key);
|
|
123
|
+
const content = contentCache.get(key);
|
|
124
|
+
lines.push("", documentHeading(document));
|
|
125
|
+
lines.push(hasContent
|
|
126
|
+
? content || "Readable document content was not available for this exhibit."
|
|
127
|
+
: "Loading exhibit content...");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
lines.push("", "Primary Filing Content", primaryContent);
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
83
135
|
function buildForm4Preview(content: string | null): string | null {
|
|
84
136
|
if (!content) return null;
|
|
85
137
|
const tx = parseForm4Xml(content);
|
|
@@ -134,6 +186,8 @@ function toFeedItems(
|
|
|
134
186
|
selectedAccessionNumber: string | undefined,
|
|
135
187
|
contentCache: Map<string, string | null>,
|
|
136
188
|
loadingContent: boolean,
|
|
189
|
+
selectedDocuments: SecFilingDocument[],
|
|
190
|
+
loadingDocuments: boolean,
|
|
137
191
|
): FeedDataTableItem[] {
|
|
138
192
|
return filings.map((filing) => {
|
|
139
193
|
const displayTitle = getFilingDisplayTitle(filing);
|
|
@@ -152,6 +206,19 @@ function toFeedItems(
|
|
|
152
206
|
const form4Detail = isOwnership && hasFetchedContent
|
|
153
207
|
? buildForm4Detail(fetchedContent ?? null, filing)
|
|
154
208
|
: null;
|
|
209
|
+
const selected = filing.accessionNumber === selectedAccessionNumber;
|
|
210
|
+
const primaryDetailBody = loadingContent && selected
|
|
211
|
+
? "Loading filing content..."
|
|
212
|
+
: form4Detail ?? fetchedContent ?? fallbackBody;
|
|
213
|
+
const detailBody = selected
|
|
214
|
+
? buildDetailBodyWithDocuments({
|
|
215
|
+
filing,
|
|
216
|
+
documents: selectedDocuments,
|
|
217
|
+
documentsLoading: loadingDocuments,
|
|
218
|
+
contentCache,
|
|
219
|
+
primaryContent: primaryDetailBody,
|
|
220
|
+
})
|
|
221
|
+
: form4Detail ?? fallbackBody;
|
|
155
222
|
|
|
156
223
|
const enrichedTitle = formDesc
|
|
157
224
|
? `${displayTitle} — ${formDesc}`
|
|
@@ -170,13 +237,7 @@ function toFeedItems(
|
|
|
170
237
|
`Accession ${filing.accessionNumber}`,
|
|
171
238
|
...(filing.items ? [`Items ${filing.items}`] : []),
|
|
172
239
|
],
|
|
173
|
-
detailBody
|
|
174
|
-
? (
|
|
175
|
-
loadingContent
|
|
176
|
-
? "Loading filing content..."
|
|
177
|
-
: form4Detail ?? fetchedContent ?? fallbackBody
|
|
178
|
-
)
|
|
179
|
-
: form4Detail ?? fallbackBody,
|
|
240
|
+
detailBody,
|
|
180
241
|
};
|
|
181
242
|
});
|
|
182
243
|
}
|
|
@@ -184,7 +245,7 @@ function toFeedItems(
|
|
|
184
245
|
function SecView({ width, height, focused }: { width: number; height: number; focused: boolean }) {
|
|
185
246
|
const { ticker } = usePaneTicker();
|
|
186
247
|
const selectionKey = `selectedIdx:${ticker?.metadata.ticker ?? "none"}`;
|
|
187
|
-
const [selectedIdx, setSelectedIdx] =
|
|
248
|
+
const [selectedIdx, setSelectedIdx] = useDebouncedPluginPaneState<number>(selectionKey, 0);
|
|
188
249
|
const [contentCache, setContentCache] = useState<Map<string, string | null>>(new Map());
|
|
189
250
|
const [openItemId, setOpenItemId] = useState<string | null>(null);
|
|
190
251
|
const contentFetchRef = useRef(0);
|
|
@@ -204,28 +265,67 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
204
265
|
contentFetchRef.current += 1;
|
|
205
266
|
}, [eligibleTicker, ticker?.metadata.exchange, ticker?.metadata.ticker]);
|
|
206
267
|
|
|
207
|
-
const selected = filings[selectedIdx];
|
|
208
268
|
const openFiling = openItemId
|
|
209
269
|
? filings.find((filing) => filing.accessionNumber === openItemId) ?? null
|
|
210
270
|
: null;
|
|
211
|
-
const
|
|
271
|
+
const cachedOpenContent = openFiling ? contentCache.get(openFiling.accessionNumber) : undefined;
|
|
212
272
|
const filingContentEntry = useSecFilingContent(
|
|
213
|
-
|
|
273
|
+
openFiling && cachedOpenContent === undefined ? openFiling : null,
|
|
214
274
|
);
|
|
215
275
|
const filingContent = useResolvedEntryValue(filingContentEntry);
|
|
216
276
|
const loadingContent = filingContentEntry?.phase === "loading" || filingContentEntry?.phase === "refreshing";
|
|
277
|
+
const documentsEntry = useSecFilingDocuments(openFiling ?? null);
|
|
278
|
+
const openDocuments = useResolvedEntryValue(documentsEntry) ?? [];
|
|
279
|
+
const loadingDocuments = !!openFiling && (
|
|
280
|
+
documentsEntry?.phase === "idle"
|
|
281
|
+
|| documentsEntry?.phase === "loading"
|
|
282
|
+
|| documentsEntry?.phase === "refreshing"
|
|
283
|
+
);
|
|
217
284
|
|
|
218
285
|
useEffect(() => {
|
|
219
|
-
if (!
|
|
220
|
-
if (
|
|
286
|
+
if (!openFiling) return;
|
|
287
|
+
if (cachedOpenContent !== undefined) return;
|
|
221
288
|
if (filingContentEntry?.phase === "error") {
|
|
222
|
-
setContentCache((prev) => new Map(prev).set(
|
|
289
|
+
setContentCache((prev) => new Map(prev).set(openFiling.accessionNumber, null));
|
|
223
290
|
return;
|
|
224
291
|
}
|
|
225
292
|
if (filingContent !== null) {
|
|
226
|
-
setContentCache((prev) => new Map(prev).set(
|
|
293
|
+
setContentCache((prev) => new Map(prev).set(openFiling.accessionNumber, filingContent));
|
|
294
|
+
}
|
|
295
|
+
}, [cachedOpenContent, filingContent, filingContentEntry?.phase, openFiling]);
|
|
296
|
+
|
|
297
|
+
const documentFetchStartedRef = useRef(new Set<string>());
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
if (!openFiling || openDocuments.length === 0) return;
|
|
300
|
+
const coordinator = getSharedMarketDataCoordinator();
|
|
301
|
+
if (!coordinator) return;
|
|
302
|
+
const gen = contentFetchRef.current;
|
|
303
|
+
const documentsToFetch = openDocuments.filter((document) => {
|
|
304
|
+
if (!isInlineExhibitDocument(document)) return false;
|
|
305
|
+
const key = documentContentKey(openFiling, document);
|
|
306
|
+
return !contentCache.has(key) && !documentFetchStartedRef.current.has(key);
|
|
307
|
+
});
|
|
308
|
+
if (documentsToFetch.length === 0) return;
|
|
309
|
+
|
|
310
|
+
for (const document of documentsToFetch) {
|
|
311
|
+
documentFetchStartedRef.current.add(documentContentKey(openFiling, document));
|
|
227
312
|
}
|
|
228
|
-
|
|
313
|
+
|
|
314
|
+
(async () => {
|
|
315
|
+
for (const document of documentsToFetch) {
|
|
316
|
+
if (contentFetchRef.current !== gen) return;
|
|
317
|
+
const key = documentContentKey(openFiling, document);
|
|
318
|
+
try {
|
|
319
|
+
const entry = await coordinator.loadSecFilingContent(documentContentTarget(openFiling, document));
|
|
320
|
+
if (contentFetchRef.current !== gen) return;
|
|
321
|
+
setContentCache((prev) => new Map(prev).set(key, entry.data ?? entry.lastGoodData ?? null));
|
|
322
|
+
} catch {
|
|
323
|
+
if (contentFetchRef.current !== gen) return;
|
|
324
|
+
setContentCache((prev) => new Map(prev).set(key, null));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
})();
|
|
328
|
+
}, [contentCache, openFiling, openDocuments]);
|
|
229
329
|
|
|
230
330
|
useEffect(() => {
|
|
231
331
|
if (filings.length > 0 && selectedIdx >= filings.length) {
|
|
@@ -235,6 +335,11 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
235
335
|
|
|
236
336
|
// Background prefetch Form 4 content for enriched previews
|
|
237
337
|
const prefetchStartedRef = useRef(new Set<string>());
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
prefetchStartedRef.current.clear();
|
|
340
|
+
documentFetchStartedRef.current.clear();
|
|
341
|
+
}, [eligibleTicker, ticker?.metadata.exchange, ticker?.metadata.ticker]);
|
|
342
|
+
|
|
238
343
|
useEffect(() => {
|
|
239
344
|
if (filings.length === 0) return;
|
|
240
345
|
const coordinator = getSharedMarketDataCoordinator();
|
|
@@ -288,7 +393,14 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
288
393
|
width={width}
|
|
289
394
|
height={height}
|
|
290
395
|
focused={focused}
|
|
291
|
-
items={toFeedItems(
|
|
396
|
+
items={toFeedItems(
|
|
397
|
+
filings,
|
|
398
|
+
openFiling?.accessionNumber,
|
|
399
|
+
contentCache,
|
|
400
|
+
loadingContent,
|
|
401
|
+
openDocuments,
|
|
402
|
+
loadingDocuments,
|
|
403
|
+
)}
|
|
292
404
|
selectedIdx={selectedIdx}
|
|
293
405
|
onSelect={setSelectedIdx}
|
|
294
406
|
onOpenItemIdChange={setOpenItemId}
|
|
@@ -71,11 +71,6 @@ function SectorPerformancePane({ focused, width, height }: PaneProps) {
|
|
|
71
71
|
label: collection.label,
|
|
72
72
|
value: collection.id,
|
|
73
73
|
})), []);
|
|
74
|
-
const selectedIdx = selectedEtf
|
|
75
|
-
? sortedRows.findIndex((row) => row.etf === selectedEtf)
|
|
76
|
-
: -1;
|
|
77
|
-
const activeIdx = selectedIdx >= 0 ? selectedIdx : (sortedRows.length > 0 ? 0 : -1);
|
|
78
|
-
|
|
79
74
|
const fetchAll = useCallback(() => {
|
|
80
75
|
fetchGenRef.current += 1;
|
|
81
76
|
const gen = fetchGenRef.current;
|
|
@@ -189,19 +184,6 @@ function SectorPerformancePane({ focused, width, height }: PaneProps) {
|
|
|
189
184
|
setSortPreference((current) => nextSortPreference(current, columnId));
|
|
190
185
|
}, [setSortPreference]);
|
|
191
186
|
|
|
192
|
-
const selectIndex = useCallback((index: number) => {
|
|
193
|
-
const row = sortedRows[index];
|
|
194
|
-
if (row) setSelectedEtf(row.etf);
|
|
195
|
-
}, [setSelectedEtf, sortedRows]);
|
|
196
|
-
|
|
197
|
-
const selectRow = useCallback((row: SectorRow) => {
|
|
198
|
-
if (row.etf === selectedEtf) {
|
|
199
|
-
openRow(row);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
setSelectedEtf(row.etf);
|
|
203
|
-
}, [openRow, selectedEtf, setSelectedEtf]);
|
|
204
|
-
|
|
205
187
|
const handleTableKeyDown = useCallback((event: DataTableKeyEvent) => {
|
|
206
188
|
if (event.name === "r") {
|
|
207
189
|
event.preventDefault?.();
|
|
@@ -291,9 +273,19 @@ function SectorPerformancePane({ focused, width, height }: PaneProps) {
|
|
|
291
273
|
return (
|
|
292
274
|
<DataTableView<SectorRow, SectorColumn>
|
|
293
275
|
focused={focused}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
276
|
+
selection={{
|
|
277
|
+
kind: "id",
|
|
278
|
+
selectedId: selectedEtf,
|
|
279
|
+
getId: (row) => row.etf,
|
|
280
|
+
onChange: (id, row, _index, reason) => {
|
|
281
|
+
if (reason === "pointer" && id === selectedEtf) {
|
|
282
|
+
openRow(row);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
setSelectedEtf(id);
|
|
286
|
+
},
|
|
287
|
+
}}
|
|
288
|
+
onActivate={openRow}
|
|
297
289
|
onRootKeyDown={handleTableKeyDown}
|
|
298
290
|
rootBefore={rootBefore}
|
|
299
291
|
rootWidth={width}
|
|
@@ -305,8 +297,6 @@ function SectorPerformancePane({ focused, width, height }: PaneProps) {
|
|
|
305
297
|
sortDirection={sortPreference.direction}
|
|
306
298
|
onHeaderClick={handleHeaderClick}
|
|
307
299
|
getItemKey={(row) => row.etf}
|
|
308
|
-
isSelected={(row) => row.etf === selectedEtf}
|
|
309
|
-
onSelect={selectRow}
|
|
310
300
|
renderCell={renderCell}
|
|
311
301
|
emptyStateTitle="No sector data available"
|
|
312
302
|
showHorizontalScrollbar={false}
|
|
@@ -344,9 +344,13 @@ export function ThirteenFPane({ focused, width, height }: PaneProps) {
|
|
|
344
344
|
) : (
|
|
345
345
|
<Box flexGrow={1} />
|
|
346
346
|
)}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
selection={{
|
|
348
|
+
kind: "id",
|
|
349
|
+
selectedId,
|
|
350
|
+
getId: (row) => row.id,
|
|
351
|
+
onChange: (id) => setSelectedId(id),
|
|
352
|
+
}}
|
|
353
|
+
onActivate={openDetail}
|
|
350
354
|
onRootKeyDown={handleRootKeyDown}
|
|
351
355
|
rootWidth={width}
|
|
352
356
|
rootBefore={rootBefore}
|
|
@@ -365,9 +369,6 @@ export function ThirteenFPane({ focused, width, height }: PaneProps) {
|
|
|
365
369
|
));
|
|
366
370
|
}}
|
|
367
371
|
getItemKey={(row) => row.id}
|
|
368
|
-
isSelected={(row) => row.id === selectedId}
|
|
369
|
-
onSelect={(row) => setSelectedId(row.id)}
|
|
370
|
-
onActivate={openDetail}
|
|
371
372
|
renderCell={renderBrowserCell}
|
|
372
373
|
emptyStateTitle={emptyTitle}
|
|
373
374
|
showHorizontalScrollbar={false}
|
|
@@ -625,11 +626,13 @@ function FundDetailView({
|
|
|
625
626
|
) : (
|
|
626
627
|
<Box flexGrow={1} />
|
|
627
628
|
)}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
629
|
+
selection={{
|
|
630
|
+
kind: "id",
|
|
631
|
+
selectedId: filingSelectedId,
|
|
632
|
+
getId: (row) => row.id,
|
|
633
|
+
onChange: (id) => setFilingSelectedId(id),
|
|
632
634
|
}}
|
|
635
|
+
onActivate={openFilingInPane}
|
|
633
636
|
onDetailKeyDown={(event) => {
|
|
634
637
|
if (event.name !== "o" || !openFiling?.url) return false;
|
|
635
638
|
event.preventDefault?.();
|
|
@@ -648,11 +651,6 @@ function FundDetailView({
|
|
|
648
651
|
columnId === "period" || columnId === "filed" ? "desc" : "desc",
|
|
649
652
|
))}
|
|
650
653
|
getItemKey={(row) => row.id}
|
|
651
|
-
isSelected={(row) => row.id === filingSelectedId}
|
|
652
|
-
onSelect={(row) => setFilingSelectedId(row.id)}
|
|
653
|
-
onActivate={(row) => {
|
|
654
|
-
openFilingInPane(row);
|
|
655
|
-
}}
|
|
656
654
|
renderCell={renderTimelineCell}
|
|
657
655
|
emptyStateTitle="No 13F filings."
|
|
658
656
|
showHorizontalScrollbar={false}
|
|
@@ -660,8 +658,12 @@ function FundDetailView({
|
|
|
660
658
|
) : (
|
|
661
659
|
<DataTableView<FundHoldingRow, FundHoldingColumn>
|
|
662
660
|
focused={focused}
|
|
663
|
-
|
|
664
|
-
|
|
661
|
+
selection={{
|
|
662
|
+
kind: "id",
|
|
663
|
+
selectedId: holdingSelectedId,
|
|
664
|
+
getId: (row) => row.id,
|
|
665
|
+
onChange: (id) => setHoldingSelectedId(id),
|
|
666
|
+
}}
|
|
665
667
|
rootWidth={width}
|
|
666
668
|
columns={buildHoldingColumns(width)}
|
|
667
669
|
items={visibleHoldingRows}
|
|
@@ -675,8 +677,6 @@ function FundDetailView({
|
|
|
675
677
|
));
|
|
676
678
|
}}
|
|
677
679
|
getItemKey={(row) => row.id}
|
|
678
|
-
isSelected={(row) => row.id === holdingSelectedId}
|
|
679
|
-
onSelect={(row) => setHoldingSelectedId(row.id)}
|
|
680
680
|
onActivate={(row) => {
|
|
681
681
|
if (row.ticker) pinTicker(row.ticker, { floating: true, paneType: TICKER_RESEARCH_PANE_ID });
|
|
682
682
|
}}
|
|
@@ -795,9 +795,13 @@ function FilingDetailView({
|
|
|
795
795
|
<Box flexDirection="column" width={width} flexGrow={1} overflow="hidden">
|
|
796
796
|
<DataTableView<FilingPositionRow, FilingPositionColumn>
|
|
797
797
|
focused={focused}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
798
|
+
selection={{
|
|
799
|
+
kind: "id",
|
|
800
|
+
selectedId: selectedPositionId,
|
|
801
|
+
getId: (row) => row.id,
|
|
802
|
+
onChange: (id) => setSelectedPositionId(id),
|
|
803
|
+
}}
|
|
804
|
+
onActivate={openPositionTicker}
|
|
801
805
|
onRootKeyDown={handlePositionKeyDown}
|
|
802
806
|
rootWidth={width}
|
|
803
807
|
rootBefore={summary}
|
|
@@ -814,9 +818,6 @@ function FilingDetailView({
|
|
|
814
818
|
));
|
|
815
819
|
}}
|
|
816
820
|
getItemKey={(row) => row.id}
|
|
817
|
-
isSelected={(row) => row.id === selectedPositionId}
|
|
818
|
-
onSelect={(row) => setSelectedPositionId(row.id)}
|
|
819
|
-
onActivate={openPositionTicker}
|
|
820
821
|
renderCell={renderFilingPositionCell}
|
|
821
822
|
emptyStateTitle={emptyTitle}
|
|
822
823
|
showHorizontalScrollbar={false}
|
|
@@ -137,8 +137,11 @@ export function FundamentalGraphContent({
|
|
|
137
137
|
<StaticBarChartSurface width={width} height={chartHeight} series={chartSeries} />
|
|
138
138
|
<DataTableView<FundamentalGraphRow, FundamentalColumn>
|
|
139
139
|
focused={focused}
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
selection={{
|
|
141
|
+
kind: "index",
|
|
142
|
+
selectedIndex: boundedSelectedIdx,
|
|
143
|
+
onChange: (index) => setSelectedIdx(index),
|
|
144
|
+
}}
|
|
142
145
|
onRootKeyDown={handleKeyDown}
|
|
143
146
|
rootWidth={width}
|
|
144
147
|
rootHeight={tableHeight}
|
|
@@ -148,8 +151,6 @@ export function FundamentalGraphContent({
|
|
|
148
151
|
sortDirection="asc"
|
|
149
152
|
onHeaderClick={() => {}}
|
|
150
153
|
getItemKey={(row) => row.key}
|
|
151
|
-
isSelected={(_row, index) => index === boundedSelectedIdx}
|
|
152
|
-
onSelect={(_row, index) => setSelectedIdx(index)}
|
|
153
154
|
renderCell={renderCell}
|
|
154
155
|
emptyStateTitle={loading ? "Loading fundamentals..." : "No graph data"}
|
|
155
156
|
/>
|
|
@@ -172,8 +172,11 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
172
172
|
return (
|
|
173
173
|
<DataTableView<HistoricalPriceRow, HistoryColumn>
|
|
174
174
|
focused={focused}
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
selection={{
|
|
176
|
+
kind: "index",
|
|
177
|
+
selectedIndex: boundedSelectedIdx,
|
|
178
|
+
onChange: (index) => setSelectedIdx(index),
|
|
179
|
+
}}
|
|
177
180
|
onRootKeyDown={handleKeyDown}
|
|
178
181
|
rootWidth={width}
|
|
179
182
|
rootHeight={height}
|
|
@@ -183,8 +186,6 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
183
186
|
sortDirection="desc"
|
|
184
187
|
onHeaderClick={() => {}}
|
|
185
188
|
getItemKey={(row) => row.key}
|
|
186
|
-
isSelected={(_row, index) => index === boundedSelectedIdx}
|
|
187
|
-
onSelect={(_row, index) => setSelectedIdx(index)}
|
|
188
189
|
renderCell={renderCell}
|
|
189
190
|
emptyStateTitle={loading ? "Loading historical prices..." : "No historical prices"}
|
|
190
191
|
/>
|
|
@@ -121,9 +121,12 @@ export function ProviderSearchPane({ focused, width, height }: PaneProps) {
|
|
|
121
121
|
return (
|
|
122
122
|
<DataTableView<InstrumentSearchResult, DataTableColumn & { id: "symbol" | "name" | "exchange" | "type" }>
|
|
123
123
|
focused={focused}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
selection={{
|
|
125
|
+
kind: "index",
|
|
126
|
+
selectedIndex: boundedSelectedIdx,
|
|
127
|
+
onChange: (index) => setSelectedIdx(index),
|
|
128
|
+
}}
|
|
129
|
+
onActivate={(row) => openResult(row)}
|
|
127
130
|
onRootKeyDown={handleKeyDown}
|
|
128
131
|
rootWidth={width}
|
|
129
132
|
rootHeight={height}
|
|
@@ -133,8 +136,6 @@ export function ProviderSearchPane({ focused, width, height }: PaneProps) {
|
|
|
133
136
|
sortDirection="asc"
|
|
134
137
|
onHeaderClick={() => {}}
|
|
135
138
|
getItemKey={(row, index) => `${row.providerId}:${row.symbol}:${row.exchange}:${row.type}:${index}`}
|
|
136
|
-
isSelected={(_row, index) => index === boundedSelectedIdx}
|
|
137
|
-
onSelect={(_row, index) => setSelectedIdx(index)}
|
|
138
139
|
renderCell={renderCell}
|
|
139
140
|
emptyStateTitle={state.loading ? "Searching..." : query ? "No search results" : "No search query"}
|
|
140
141
|
/>
|
|
@@ -380,19 +380,21 @@ export function ResolvedFinancialsTab({
|
|
|
380
380
|
bodyScrollId={bodyScrollId}
|
|
381
381
|
columns={columns}
|
|
382
382
|
items={rows}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
383
|
+
selection={{
|
|
384
|
+
kind: "id",
|
|
385
|
+
selectedId: selectedRowId,
|
|
386
|
+
getId: (row) => row.id,
|
|
387
|
+
onChange: (_id, row, _index, reason) => {
|
|
388
|
+
setSelectedRowId(row.id);
|
|
389
|
+
if (reason === "pointer" && row.kind === "group" && row.toggleable) {
|
|
390
|
+
toggleGroup(row.id);
|
|
391
|
+
}
|
|
392
|
+
},
|
|
386
393
|
}}
|
|
387
394
|
sortColumnId={null}
|
|
388
395
|
sortDirection="desc"
|
|
389
396
|
onHeaderClick={() => {}}
|
|
390
397
|
getItemKey={(row) => row.id}
|
|
391
|
-
isSelected={(_row, index) => index === effectiveSelectedIndex}
|
|
392
|
-
onSelect={(row) => {
|
|
393
|
-
setSelectedRowId(row.id);
|
|
394
|
-
if (row.kind === "group" && row.toggleable) toggleGroup(row.id);
|
|
395
|
-
}}
|
|
396
398
|
onActivate={(row) => {
|
|
397
399
|
if (row.kind === "group" && row.toggleable) toggleGroup(row.id);
|
|
398
400
|
}}
|
|
@@ -162,10 +162,16 @@ function WorldIndicesPane({ focused, width, height }: PaneProps) {
|
|
|
162
162
|
return (
|
|
163
163
|
<DataTableView<WorldIndexTableRow, WorldIndexColumn>
|
|
164
164
|
focused={focused}
|
|
165
|
-
|
|
165
|
+
selection={{
|
|
166
|
+
kind: "id",
|
|
167
|
+
selectedId: selectedSymbol,
|
|
168
|
+
getId: (row) => row.type === "row" ? row.entry.symbol : `header-${row.region}`,
|
|
169
|
+
onChange: (_id, row, index) => {
|
|
170
|
+
if (row.type === "row") selectFlatIndex(index);
|
|
171
|
+
},
|
|
172
|
+
}}
|
|
166
173
|
isNavigable={(row) => row.type === "row"}
|
|
167
|
-
|
|
168
|
-
onActivateIndex={(index) => openSelected(index)}
|
|
174
|
+
onActivate={(_row, index) => openSelected(index)}
|
|
169
175
|
rootWidth={width}
|
|
170
176
|
rootHeight={height}
|
|
171
177
|
columns={columns}
|
|
@@ -174,9 +180,6 @@ function WorldIndicesPane({ focused, width, height }: PaneProps) {
|
|
|
174
180
|
sortDirection={sortPreference.direction}
|
|
175
181
|
onHeaderClick={handleHeaderClick}
|
|
176
182
|
getItemKey={(row) => row.type === "header" ? `header-${row.region}` : row.entry.symbol}
|
|
177
|
-
isSelected={(row) => row.type === "row" && row.entry.symbol === selectedSymbol}
|
|
178
|
-
onSelect={(_row, index) => selectFlatIndex(index)}
|
|
179
|
-
onActivate={(_row, index) => openSelected(index)}
|
|
180
183
|
renderSectionHeader={(row) => row.type === "header"
|
|
181
184
|
? { text: REGION_LABELS[row.region] }
|
|
182
185
|
: null}
|
|
@@ -260,12 +260,16 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
260
260
|
rootWidth={width}
|
|
261
261
|
rootHeight={height}
|
|
262
262
|
rootBackgroundColor={colors.panel}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
controller.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
263
|
+
selection={{
|
|
264
|
+
kind: "id",
|
|
265
|
+
selectedId: controller.selectedRow?.key ?? null,
|
|
266
|
+
getId: (row) => row.key,
|
|
267
|
+
onChange: (key, _row, _index, reason) =>
|
|
268
|
+
controller.actions.setBrowseSelection(key, {
|
|
269
|
+
debounceDetail: reason === "keyboard",
|
|
270
|
+
}),
|
|
271
|
+
}}
|
|
272
|
+
onActivate={(row) =>
|
|
269
273
|
controller.actions.openSelectedRow(row.key)}
|
|
270
274
|
columns={controller.visibleColumns}
|
|
271
275
|
items={controller.visibleRows}
|
|
@@ -275,9 +279,6 @@ export function PredictionMarketsPane({ focused, width, height }: PaneProps) {
|
|
|
275
279
|
headerScrollRef={controller.headerScrollRef}
|
|
276
280
|
scrollRef={controller.scrollRef}
|
|
277
281
|
getItemKey={(row) => row.key}
|
|
278
|
-
isSelected={(row) => controller.selectedRow?.key === row.key}
|
|
279
|
-
onSelect={(row) => controller.actions.setBrowseSelection(row.key)}
|
|
280
|
-
onActivate={(row) => controller.actions.openSelectedRow(row.key)}
|
|
281
282
|
virtualize
|
|
282
283
|
renderCell={renderCell}
|
|
283
284
|
emptyStateTitle="No markets matched."
|
|
@@ -147,6 +147,9 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
147
147
|
hidePane = (paneId: string) => {
|
|
148
148
|
this.hidePaneFn(paneId);
|
|
149
149
|
};
|
|
150
|
+
focusPane = (paneId: string) => {
|
|
151
|
+
this.focusPaneFn(paneId);
|
|
152
|
+
};
|
|
150
153
|
|
|
151
154
|
getLayoutFn: (() => LayoutConfig) = () => ({ dockRoot: null, instances: [], floating: [], detached: [] });
|
|
152
155
|
updateLayoutFn: ((layout: LayoutConfig) => void) = () => {};
|
|
@@ -33,6 +33,7 @@ export interface PluginRuntimeAccess {
|
|
|
33
33
|
showPane(paneId: string): void;
|
|
34
34
|
createPaneFromTemplate(templateId: string, options?: PaneTemplateCreateOptions): void;
|
|
35
35
|
hidePane(paneId: string): void;
|
|
36
|
+
focusPane(paneId: string): void;
|
|
36
37
|
openPaneSettings(paneId?: string): void;
|
|
37
38
|
openPluginCommandWorkflow(commandId: string): void;
|
|
38
39
|
notify(notification: AppNotificationRequest): void;
|