gloomberb 0.19.0 → 0.19.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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +23 -1
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/data.ts +2 -1
- package/src/api-client/errors.ts +38 -0
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +12 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/architecture/core-builtin-allowlist.json +2 -2
- package/src/capabilities/chart-series.ts +2 -1
- package/src/cli/commands/market.ts +44 -16
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/overview.ts +84 -19
- package/src/cli/commands/search.ts +13 -13
- package/src/cli/commands/ticker.ts +32 -2
- package/src/cli/context.ts +1 -0
- package/src/cli/errors.ts +17 -2
- package/src/cli/glossary.ts +8 -1
- package/src/cli/history-rows.ts +35 -7
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/known-gaps.ts +11 -0
- package/src/cli/listing-arg.ts +74 -11
- package/src/cli/pane-functions/capabilities.ts +1 -0
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/freshness.ts +7 -1
- package/src/cli/pane-functions/function-help.ts +7 -0
- package/src/cli/pane-functions/index.ts +6 -3
- package/src/cli/quote-answer.ts +30 -7
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/routes/root/runtime.ts +29 -8
- package/src/components/command-bar/selection-runtime.ts +64 -32
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +2 -2
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +23 -6
- package/src/components/layout/pane/footer/model.ts +116 -16
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +37 -3
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-frame.tsx +2 -1
- package/src/components/onboarding/onboarding-wizard.tsx +6 -3
- package/src/components/ticker/list-table-view.tsx +4 -20
- package/src/components/ui/display.tsx +29 -3
- package/src/components/ui/icon.tsx +12 -3
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/plugin-cache.ts +20 -3
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/resource-store.ts +130 -58
- package/src/data/saved-copies.ts +8 -0
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +313 -0
- package/src/data/sqlite/checkpointer.ts +198 -0
- package/src/data/sqlite/database.ts +47 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +32 -5
- package/src/i18n/es.ts +2 -4
- package/src/i18n/ja.ts +2 -4
- package/src/i18n/ko.ts +2 -4
- package/src/i18n/zh-cn.ts +2 -4
- package/src/i18n/zh-tw.ts +2 -4
- package/src/market-data/depositary-pair.ts +25 -1
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -1
- package/src/news/types.ts +6 -0
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli.ts +34 -11
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +15 -6
- package/src/plugins/builtin/alerts/headless.ts +10 -6
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +5 -0
- package/src/plugins/builtin/chart-composer/index.tsx +4 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +5 -1
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -22
- package/src/plugins/builtin/clinical-trials/pane.tsx +46 -27
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +87 -9
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/company-attention/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/pane.tsx +3 -0
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -1
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +4 -2
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/credit-documents/headless.ts +3 -3
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/series.ts +4 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +37 -17
- package/src/plugins/builtin/earnings-calls/headless.ts +13 -3
- package/src/plugins/builtin/earnings-calls/pane.tsx +34 -14
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +18 -4
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +10 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +3 -0
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/gpu/client.ts +4 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +61 -59
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +4 -2
- package/src/plugins/builtin/holders/headless.ts +4 -2
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/macro-day/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- package/src/plugins/builtin/market-movers/client.ts +3 -0
- package/src/plugins/builtin/market-movers/headless.ts +3 -1
- package/src/plugins/builtin/market-movers/index.tsx +10 -5
- package/src/plugins/builtin/market-movers/screener.ts +27 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +3 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -6
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -6
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +9 -7
- package/src/plugins/builtin/research/event-model.ts +30 -4
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +8 -25
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +52 -15
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- package/src/plugins/builtin/risk-factors/data.ts +12 -6
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- package/src/plugins/builtin/scanner/live-headless.ts +21 -2
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +20 -7
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/report-freshness.ts +8 -0
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/thirteenf/api.ts +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +39 -11
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +21 -5
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -2
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +59 -8
- package/src/plugins/builtin/ticker-detail/overview/model.ts +10 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +11 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +24 -13
- package/src/plugins/builtin/treasury-auctions/pane.tsx +18 -6
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +11 -4
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/yield-curve/client.ts +59 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/installer.ts +50 -39
- package/src/portfolio/column-values.ts +11 -7
- package/src/renderers/dom/desktop/icons.tsx +10 -2
- package/src/renderers/electrobun/bun/desktop/backend.ts +9 -10
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +9 -1
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +45 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/launcher/preload.ts +13 -0
- package/src/renderers/electrobun/launcher/windows-emulation.ts +50 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/news-source.ts +2 -0
- package/src/renderers/electrobun/view/ui-host.tsx +16 -1
- package/src/series/resolve.ts +2 -1
- package/src/series/types.ts +5 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +8 -4
- package/src/sources/gloomberb-cloud/index.ts +42 -8
- package/src/telemetry/stall-reports-node.ts +13 -2
- package/src/telemetry/stall-reports.ts +189 -13
- package/src/test-support/outage.ts +8 -0
- package/src/tickers/company-name-listing.ts +110 -40
- package/src/tickers/company-name.ts +71 -2
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/search/home-listing.ts +93 -8
- package/src/tickers/search/index.ts +7 -3
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/market.ts +5 -3
- package/src/time-series/price-comparison.ts +20 -2
- package/src/time-series/resolve.ts +20 -1
- package/src/time-series/types.ts +7 -0
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +45 -3
- package/src/types/headless.ts +6 -0
- package/src/ui/host.tsx +6 -1
- package/src/utils/crypto-pair.ts +121 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +53 -10
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +47 -2
- package/src/utils/perf-marks.ts +23 -11
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/version.ts +2 -2
package/src/cli/quote-answer.ts
CHANGED
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
notTradedMessage,
|
|
22
22
|
oneWordCompanyName,
|
|
23
23
|
quoteBareListing,
|
|
24
|
+
serviceNamedCompany,
|
|
25
|
+
type ListingArg,
|
|
24
26
|
requireCliListing,
|
|
25
27
|
resolveCliListing,
|
|
26
28
|
tradingBoardNote,
|
|
@@ -64,7 +66,9 @@ export interface QuoteAnswer {
|
|
|
64
66
|
/**
|
|
65
67
|
* One symbol's row of `quote` and `compare`. A listing on an exchange its symbol
|
|
66
68
|
* is not listed on says so. A bare symbol the data service read on its home
|
|
67
|
-
* listing abroad names that venue (SXR8 -> XETRA). One no source quotes
|
|
69
|
+
* listing abroad names that venue (SXR8 -> XETRA). One no source quotes that
|
|
70
|
+
* the service marks as another listing's company name is that company
|
|
71
|
+
* (`serviceNamedCompany`: "siemens" is SIE:XETRA); else it is
|
|
68
72
|
* quoted on its listings (`quoteBareListing`), else names them, or asks for an
|
|
69
73
|
* exchange when several companies list it (2222); a bare index root (VIX) also
|
|
70
74
|
* names its index (^VIX), and a futures board code a security keeps (GC) its
|
|
@@ -98,6 +102,10 @@ export async function answerQuote(
|
|
|
98
102
|
const notTraded = await notTradedMessage(listing, market);
|
|
99
103
|
if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
|
|
100
104
|
if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
|
|
105
|
+
// A word the service marks as a company's name is that company, before the symbol is read on its listings abroad.
|
|
106
|
+
const named = !listing.exchange && listing.typed ? await serviceNamedCompany(listing.typed, market) : null;
|
|
107
|
+
const meant = named ? await answerNamedListing(named, market, request, refresh, datesChangeBasis) : null;
|
|
108
|
+
if (meant) return meant;
|
|
101
109
|
const other = await quoteBareListing(listing, market, request, { refresh, datesChangeBasis });
|
|
102
110
|
if (other?.kind === "quoted") {
|
|
103
111
|
return {
|
|
@@ -155,17 +163,32 @@ async function answerCompanyName(
|
|
|
155
163
|
const named = await oneWordCompanyName(listing.typed, market);
|
|
156
164
|
// A real ticker a source failed to quote is not a company's name, even when the company carries the word.
|
|
157
165
|
if (!named || (unsettled && named.symbol.toUpperCase() === listing.symbol.toUpperCase())) return null;
|
|
158
|
-
|
|
166
|
+
const answer = await answerNamedListing(named, market, request, refresh, datesChangeBasis);
|
|
167
|
+
if (answer) return answer;
|
|
168
|
+
if (!unsettled) return null;
|
|
169
|
+
const sentence = await notATickerWords(listing.typed, market);
|
|
170
|
+
return { result: { ...result, error: new Error(sentence) }, listing, notes: [] };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The row of the listing a company's name was read as (`named`), saying so;
|
|
175
|
+
* null when the app cannot read its venue or it has no quote.
|
|
176
|
+
*/
|
|
177
|
+
async function answerNamedListing(
|
|
178
|
+
named: ListingArg,
|
|
179
|
+
market: MarketContext,
|
|
180
|
+
request: ListingDataRequest,
|
|
181
|
+
refresh: boolean,
|
|
182
|
+
datesChangeBasis: boolean,
|
|
183
|
+
): Promise<QuoteAnswer | null> {
|
|
184
|
+
// A listing on a venue the app cannot read is no answer: the word keeps its own.
|
|
159
185
|
const resolved = await resolveCliListing(named.key, null, market).catch((error: unknown) => {
|
|
160
186
|
if (error instanceof ListingArgError) return null;
|
|
161
187
|
throw error;
|
|
162
188
|
});
|
|
163
|
-
const found = resolved ? { ...resolved, readAs: named.readAs, ...named.meant ? { meant: named.meant } : {} } : null;
|
|
189
|
+
const found = resolved ? { ...resolved, readAs: named.readAs, ...named.meant ? { meant: named.meant } : {}, ...named.readFromName ? { readFromName: named.readFromName } : {} } : null;
|
|
164
190
|
const [quoted] = found ? await market.dataProvider.getQuotesBatch([found.request], { forceRefresh: refresh, revalidate: true, datesChangeBasis }) : [];
|
|
165
|
-
|
|
166
|
-
if (!unsettled) return null;
|
|
167
|
-
const sentence = await notATickerWords(listing.typed, market);
|
|
168
|
-
return { result: { ...result, error: new Error(sentence) }, listing, notes: [] };
|
|
191
|
+
return found && quoted?.quote ? answerQuote(quoted, found, market, request, refresh, null, { datesChangeBasis }) : null;
|
|
169
192
|
}
|
|
170
193
|
|
|
171
194
|
/** A listing a command found a quote for, with what `quote` says beside it. */
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { AppResourceStorePort } from "../core/app-service-ports";
|
|
2
|
+
import type { JsonValue } from "../data/sqlite/json";
|
|
3
|
+
import type { PersistedResourceValue } from "../types/persistence";
|
|
4
|
+
import type { PluginPersistence } from "../types/plugin";
|
|
5
|
+
import { logFailure } from "../utils/log-failure";
|
|
6
|
+
|
|
7
|
+
type SetResourceOptions = Parameters<PluginPersistence["setResource"]>[3];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A plugin's persistence in a command: what the command fetches through a
|
|
11
|
+
* plugin's cache is saved in the profile, under the namespace the app keeps
|
|
12
|
+
* it in (`plugin:<state id>`), so a later run whose refresh fails answers
|
|
13
|
+
* from that saved copy as the app does. Only a read that may take an expired
|
|
14
|
+
* copy (`allowExpired`, what a cache falls back to when its refresh fails)
|
|
15
|
+
* reaches the saved copies; any other read sees only what this run fetched,
|
|
16
|
+
* so a command still asks for current data rather than answering from the
|
|
17
|
+
* app's cache. Plugin state is not read or kept. With no resource store,
|
|
18
|
+
* nothing outlives the run.
|
|
19
|
+
*
|
|
20
|
+
* The profile's store closes when the command ends, while a plugin may keep
|
|
21
|
+
* the persistence it was set up with: `release()` leaves the store alone from
|
|
22
|
+
* then on, and a store that fails (closed, full) costs the saved copy only,
|
|
23
|
+
* never the command.
|
|
24
|
+
*/
|
|
25
|
+
export function createCommandPluginPersistence(
|
|
26
|
+
resources: AppResourceStorePort | null | undefined,
|
|
27
|
+
namespace: string,
|
|
28
|
+
): PluginPersistence & { release(): void } {
|
|
29
|
+
const fetched = new Map<string, PersistedResourceValue<unknown>>();
|
|
30
|
+
const keyOf = (kind: string, key: string) => `${kind}:${key}`;
|
|
31
|
+
let store = resources ?? null;
|
|
32
|
+
const saved = <T>(operation: string, run: (store: AppResourceStorePort) => T, fallback: T): T => {
|
|
33
|
+
if (!store) return fallback;
|
|
34
|
+
try {
|
|
35
|
+
return run(store);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return logFailure("cli.saved-copies", `${operation} a saved copy failed`, fallback)(error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
release: () => { store = null; },
|
|
42
|
+
getState: () => null,
|
|
43
|
+
setState: () => {},
|
|
44
|
+
deleteState: () => {},
|
|
45
|
+
getResource: <T = unknown>(kind: string, key: string, options?: { sourceKey?: string; schemaVersion?: number; allowExpired?: boolean }) => {
|
|
46
|
+
const own = fetched.get(keyOf(kind, key)) as PersistedResourceValue<T> | undefined;
|
|
47
|
+
if (own || !options?.allowExpired) return own ?? null;
|
|
48
|
+
const record = saved("Reading", (resources) => resources.get<T>(
|
|
49
|
+
{ namespace, kind, entityKey: key, sourceKey: options.sourceKey },
|
|
50
|
+
{ schemaVersion: options.schemaVersion, allowExpired: true },
|
|
51
|
+
), null);
|
|
52
|
+
return record && {
|
|
53
|
+
value: record.value,
|
|
54
|
+
fetchedAt: record.fetchedAt,
|
|
55
|
+
staleAt: record.staleAt,
|
|
56
|
+
expiresAt: record.expiresAt,
|
|
57
|
+
sourceKey: record.sourceKey,
|
|
58
|
+
schemaVersion: record.schemaVersion,
|
|
59
|
+
provenance: record.provenance,
|
|
60
|
+
stale: record.stale,
|
|
61
|
+
expired: record.expired,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
setResource: <T = unknown>(kind: string, key: string, value: T, options: SetResourceOptions) => {
|
|
65
|
+
const fetchedAt = Date.now();
|
|
66
|
+
const entry: PersistedResourceValue<T> = {
|
|
67
|
+
value,
|
|
68
|
+
fetchedAt,
|
|
69
|
+
staleAt: fetchedAt + options.cachePolicy.staleMs,
|
|
70
|
+
expiresAt: fetchedAt + options.cachePolicy.expireMs,
|
|
71
|
+
sourceKey: options.sourceKey ?? "",
|
|
72
|
+
schemaVersion: options.schemaVersion ?? 1,
|
|
73
|
+
provenance: options.provenance ?? null,
|
|
74
|
+
};
|
|
75
|
+
fetched.set(keyOf(kind, key), entry);
|
|
76
|
+
saved("Writing", (resources) => resources.set<T>({ namespace, kind, entityKey: key, sourceKey: options.sourceKey }, value, {
|
|
77
|
+
cachePolicy: options.cachePolicy,
|
|
78
|
+
schemaVersion: options.schemaVersion,
|
|
79
|
+
provenance: options.provenance as JsonValue | null | undefined,
|
|
80
|
+
}), undefined);
|
|
81
|
+
return entry;
|
|
82
|
+
},
|
|
83
|
+
deleteResource: (kind: string, key: string, options?: { sourceKey?: string }) => {
|
|
84
|
+
fetched.delete(keyOf(kind, key));
|
|
85
|
+
saved("Deleting", (resources) => resources.delete({ namespace, kind, entityKey: key, sourceKey: options?.sourceKey }), undefined);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -73,6 +73,11 @@ export interface ResultItem {
|
|
|
73
73
|
action: () => void | Promise<void>;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/** A row plain Enter can run, so the untouched selection may rest on it. */
|
|
77
|
+
export function isDefaultSelectable(item: ResultItem): boolean {
|
|
78
|
+
return item.disabled !== true && item.defaultSelectable !== false;
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
type ListScreenKind = "root" | "mode" | "picker";
|
|
77
82
|
|
|
78
83
|
export interface ListScreenState {
|
|
@@ -7,7 +7,7 @@ import type { TickerRecord } from "../../../../types/ticker";
|
|
|
7
7
|
import { listingChoiceQuery, symbolSearchQuery, type TickerSearchCandidate } from "../../../../tickers/search";
|
|
8
8
|
import type { AssistRowHandlers } from "../../assist/model";
|
|
9
9
|
import { matchPrefix, type Command } from "../../commands/registry";
|
|
10
|
-
import type
|
|
10
|
+
import { isDefaultSelectable, type ResultItem } from "../../list/model";
|
|
11
11
|
import type { CommandBarCategoryPriorities } from "../../view-model";
|
|
12
12
|
import type { CommandBarRoute } from "../../workflow/types";
|
|
13
13
|
import { normalizeCommandTickerSearchText, readRootTickerQuery } from "../ticker-search/results";
|
|
@@ -20,11 +20,6 @@ import { buildRootShortcutFeedback } from "./shortcut-feedback";
|
|
|
20
20
|
import type { ShortcutIntent } from "./shortcuts";
|
|
21
21
|
import { clampIndex } from "../../../../utils/math";
|
|
22
22
|
|
|
23
|
-
/** A row plain Enter can run, so the untouched selection may rest on it. */
|
|
24
|
-
function isDefaultSelectable(item: ResultItem): boolean {
|
|
25
|
-
return item.disabled !== true && item.defaultSelectable !== false;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
23
|
interface UseCommandBarRootRuntimeOptions {
|
|
29
24
|
activeCollectionId: string | null;
|
|
30
25
|
activePortfolio?: AppState["config"]["portfolios"][number];
|
|
@@ -159,6 +154,13 @@ export function useCommandBarRootRuntime({
|
|
|
159
154
|
} {
|
|
160
155
|
const previousRootSelectionContextRef = useRef<{ query: string; mode: string } | null>(null);
|
|
161
156
|
const previousRootResultIdsRef = useRef<string[]>([]);
|
|
157
|
+
/**
|
|
158
|
+
* The row the untouched highlight rests on once the query's answer is in,
|
|
159
|
+
* and the answer it was on. A later answer that reorders the rows (a
|
|
160
|
+
* fresher search over a cached one, a broker's) moves the highlight with
|
|
161
|
+
* its row, so Enter opens the row that was highlighted.
|
|
162
|
+
*/
|
|
163
|
+
const pinnedRootRowRef = useRef<{ id: string; answer: string | null } | null>(null);
|
|
162
164
|
const activeMatch = matchPrefix(rootQuery, availableCommands);
|
|
163
165
|
|
|
164
166
|
const tickerSearchRouteQuery = currentRoute?.kind === "mode" && currentRoute.screen === "ticker-search"
|
|
@@ -331,6 +333,7 @@ export function useCommandBarRootRuntime({
|
|
|
331
333
|
previousRootSelectionContextRef.current = { query: rootQuery, mode: rootModeKind };
|
|
332
334
|
if (selectionContextChanged) {
|
|
333
335
|
rootSelectionNavigatedRef.current = false;
|
|
336
|
+
pinnedRootRowRef.current = null;
|
|
334
337
|
}
|
|
335
338
|
|
|
336
339
|
setRootSelectedIdx((current) => {
|
|
@@ -343,11 +346,23 @@ export function useCommandBarRootRuntime({
|
|
|
343
346
|
if (shiftedIdx >= 0) return shiftedIdx;
|
|
344
347
|
return clampIndex(current, resultIds.length);
|
|
345
348
|
}
|
|
346
|
-
|
|
349
|
+
const pinnedIdx = pinnedRootRowRef.current ? resultIds.indexOf(pinnedRootRowRef.current.id) : -1;
|
|
350
|
+
if (pinnedIdx >= 0) return pinnedIdx;
|
|
351
|
+
// Untouched, the selection follows the best row on offer until the
|
|
352
|
+
// answer is in. When every row is an offer or a placeholder, none of
|
|
353
|
+
// them is the answer and none is selected: Enter must not take one of
|
|
354
|
+
// them for the ticker still being searched.
|
|
347
355
|
const defaultIdx = orderedRootResults.findIndex(isDefaultSelectable);
|
|
348
|
-
|
|
356
|
+
if (defaultIdx < 0) return -1;
|
|
357
|
+
const selected = clampIndex(Math.max(rootResultModel.initialIdx, defaultIdx), resultIds.length);
|
|
358
|
+
pinnedRootRowRef.current = rootResultsCurrent && resultIds[selected]
|
|
359
|
+
? { id: resultIds[selected]!, answer: activeRootProviderResultsKey }
|
|
360
|
+
: null;
|
|
361
|
+
return selected;
|
|
349
362
|
});
|
|
350
363
|
}, [
|
|
364
|
+
activeRootProviderResultsKey,
|
|
365
|
+
rootResultsCurrent,
|
|
351
366
|
activeMatch?.command.id,
|
|
352
367
|
currentRoute,
|
|
353
368
|
orderedRootResults,
|
|
@@ -361,7 +376,13 @@ export function useCommandBarRootRuntime({
|
|
|
361
376
|
|
|
362
377
|
useEffect(() => {
|
|
363
378
|
if (!activeRootProviderResultsKey) return;
|
|
379
|
+
// A later answer that only reorders the rows leaves the highlight on its row.
|
|
380
|
+
const pinned = pinnedRootRowRef.current;
|
|
381
|
+
if (pinned && pinned.answer !== activeRootProviderResultsKey
|
|
382
|
+
&& orderedRootResults.some((item) => item.id === pinned.id)) return;
|
|
364
383
|
setRootSelectedIdx(0);
|
|
384
|
+
const top = orderedRootResults[0];
|
|
385
|
+
pinnedRootRowRef.current = top ? { id: top.id, answer: activeRootProviderResultsKey } : null;
|
|
365
386
|
setRootHoveredIdx(null);
|
|
366
387
|
nativeListScrollRef.current?.scrollTo(0);
|
|
367
388
|
}, [activeRootProviderResultsKey, nativeListScrollRef, setRootHoveredIdx, setRootSelectedIdx]);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useCallback, type MutableRefObject } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, type MutableRefObject } from "react";
|
|
2
2
|
import type { AppState } from "../../state/app/context";
|
|
3
3
|
import type { TickerRecord } from "../../types/ticker";
|
|
4
4
|
import { t } from "../../i18n";
|
|
5
|
-
import {
|
|
6
|
-
import { isKnownExchangeCode
|
|
5
|
+
import { listingChoiceQuery } from "../../tickers/search";
|
|
6
|
+
import { isKnownExchangeCode } from "../../utils/exchanges";
|
|
7
7
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
8
8
|
import type { Command } from "./commands/registry";
|
|
9
9
|
import type {
|
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
PaneTemplateDef,
|
|
13
13
|
} from "../../types/plugin";
|
|
14
14
|
import { resolveCommandBarMode, type CommandBarMode } from "./view-model";
|
|
15
|
-
import type
|
|
15
|
+
import { isDefaultSelectable, type ListScreenState, type ResultItem } from "./list/model";
|
|
16
16
|
import type { CommandBarRoute } from "./workflow/types";
|
|
17
17
|
import type { CollectionCommandId } from "./helpers";
|
|
18
18
|
import type { CommandBarCollectionWorkflowActions } from "./workflow/collection-actions";
|
|
@@ -67,6 +67,7 @@ interface UseCommandBarSelectionRuntimeOptions {
|
|
|
67
67
|
rootModeKindRef: MutableRefObject<CommandBarMode>;
|
|
68
68
|
rootQuery: string;
|
|
69
69
|
rootQueryRef: MutableRefObject<string>;
|
|
70
|
+
rootSelectedIdx: number;
|
|
70
71
|
rootSelectionNavigatedRef: MutableRefObject<boolean>;
|
|
71
72
|
rootShortcutIntent: ShortcutIntent;
|
|
72
73
|
rootThemeBaseIdRef: MutableRefObject<string | null>;
|
|
@@ -78,6 +79,11 @@ interface UseCommandBarSelectionRuntimeOptions {
|
|
|
78
79
|
*/
|
|
79
80
|
runRootRow?: RunRootRow;
|
|
80
81
|
runSecurityDescriptionShortcut: (query?: string, options?: { forceNewPane?: boolean }) => void | Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* The search that decides the best row is still out, so the list on screen
|
|
84
|
+
* is a stand-in for the answer.
|
|
85
|
+
*/
|
|
86
|
+
searchPending: boolean;
|
|
81
87
|
setRootQuery: (query: string) => void;
|
|
82
88
|
stateConfigLayout: AppState["config"]["layout"];
|
|
83
89
|
stateRef: MutableRefObject<AppState>;
|
|
@@ -108,12 +114,14 @@ export function useCommandBarSelectionRuntime({
|
|
|
108
114
|
rootModeKindRef,
|
|
109
115
|
rootQuery,
|
|
110
116
|
rootQueryRef,
|
|
117
|
+
rootSelectedIdx,
|
|
111
118
|
rootSelectionNavigatedRef,
|
|
112
119
|
rootShortcutIntent,
|
|
113
120
|
rootThemeBaseIdRef,
|
|
114
121
|
runDirectCommand,
|
|
115
122
|
runRootRow,
|
|
116
123
|
runSecurityDescriptionShortcut,
|
|
124
|
+
searchPending,
|
|
117
125
|
setRootQuery,
|
|
118
126
|
stateConfigLayout,
|
|
119
127
|
stateRef,
|
|
@@ -276,42 +284,39 @@ export function useCommandBarSelectionRuntime({
|
|
|
276
284
|
updateTopRoute,
|
|
277
285
|
]);
|
|
278
286
|
|
|
287
|
+
/**
|
|
288
|
+
* An Enter pressed with no row to run while the search is still out. It
|
|
289
|
+
* waits for the answer instead of running whatever else the list shows, and
|
|
290
|
+
* is dropped when the user types, moves the selection or leaves the root.
|
|
291
|
+
*/
|
|
292
|
+
const pendingEnterRef = useRef<{ query: string; secondary: boolean } | null>(null);
|
|
293
|
+
|
|
279
294
|
const activateListSelection = useCallback((options?: { secondary?: boolean; item?: ResultItem }) => {
|
|
280
295
|
const listState = visibleListStateRef.current;
|
|
281
296
|
if (!listState) return;
|
|
282
|
-
// Enter
|
|
283
|
-
//
|
|
284
|
-
//
|
|
297
|
+
// Enter opens exactly the highlighted row. The typed command runs as typed
|
|
298
|
+
// only while no listing for it is highlighted: before its search answers
|
|
299
|
+
// (`DES HFC` then opens the listing `quote HFC` reads), or while the rows
|
|
300
|
+
// on screen are an earlier query's.
|
|
285
301
|
const queryChanged = rootQueryRef.current !== rootQuery;
|
|
286
302
|
const tickerArgument = rootShortcutIntent.kind !== "none" && rootShortcutIntent.argKind === "ticker"
|
|
287
303
|
? rootShortcutIntent.argText : null;
|
|
288
304
|
const listingQuery = listingChoiceQuery(tickerArgument ?? rootQueryRef.current);
|
|
289
305
|
const choosingVenue = listingQuery && !isKnownExchangeCode(listingQuery.suffix);
|
|
290
|
-
const
|
|
306
|
+
const highlighted = listState.results[listState.selectedIdx];
|
|
307
|
+
const listingHighlighted = !queryChanged && (highlighted?.kind === "ticker" || highlighted?.kind === "search");
|
|
308
|
+
const submitTyped = !options?.item && !currentRoute && !listingHighlighted
|
|
291
309
|
&& (queryChanged || (!rootSelectionNavigatedRef.current && !choosingVenue));
|
|
292
|
-
|
|
310
|
+
const typed = submitTyped && (queryChanged || tickerArgument !== null)
|
|
293
311
|
? resolveImmediateRootSelection(rootQueryRef.current) : null;
|
|
294
|
-
const first = listState.results[listState.selectedIdx];
|
|
295
|
-
if (typed && !queryChanged && rootShortcutIntent.kind !== "none" && rootShortcutIntent.argKind === "ticker"
|
|
296
|
-
&& first && (first.kind === "ticker" || first.kind === "search")) {
|
|
297
|
-
const argument = rootShortcutIntent.argText;
|
|
298
|
-
// Company names and partial symbols still accept the suggested match.
|
|
299
|
-
// An exact symbol or named venue goes through listing resolution.
|
|
300
|
-
if (!parsePublicTickerKey(argument).exchange && !findExactTickerSearchMatch(
|
|
301
|
-
listState.results.filter((item) => item.kind === "ticker" || item.kind === "search"), argument,
|
|
302
|
-
)) typed = null;
|
|
303
|
-
}
|
|
304
|
-
// A plain symbol follows the same path once it is the default ticker row.
|
|
305
|
-
// Commands and company-name searches keep their own default actions.
|
|
306
|
-
const plainSymbol = submitTyped && !typed && first && (first.kind === "ticker" || first.kind === "search")
|
|
307
|
-
&& findExactTickerSearchMatch([first], rootQueryRef.current);
|
|
308
|
-
if (plainSymbol && first) {
|
|
309
|
-
const symbol = rootQueryRef.current;
|
|
310
|
-
typed = { ...first, action: () => runSecurityDescriptionShortcut(symbol),
|
|
311
|
-
secondaryAction: () => runSecurityDescriptionShortcut(symbol, { forceNewPane: true }) };
|
|
312
|
-
}
|
|
313
312
|
const selected = options?.item ?? typed ?? listState.results[listState.selectedIdx];
|
|
314
|
-
if (!selected
|
|
313
|
+
if (!selected) {
|
|
314
|
+
if (searchPending && !currentRoute && !options?.item && !rootSelectionNavigatedRef.current) {
|
|
315
|
+
pendingEnterRef.current = { query: rootQueryRef.current, secondary: options?.secondary === true };
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (selected.disabled) return;
|
|
315
320
|
|
|
316
321
|
// Opened from an add row: a venue picked from the colon list joins that
|
|
317
322
|
// collection. Any other text or screen runs as usual.
|
|
@@ -378,8 +383,8 @@ export function useCommandBarSelectionRuntime({
|
|
|
378
383
|
item: selected,
|
|
379
384
|
query,
|
|
380
385
|
rank: selected === typed ? 0 : Math.max(0, listState.results.indexOf(selected)),
|
|
381
|
-
// A
|
|
382
|
-
isShortcut: () => !
|
|
386
|
+
// A listing row stays a ticker choice; a submitted command is the shortcut itself.
|
|
387
|
+
isShortcut: () => !listingHighlighted && (selected === typed || resolveImmediateRootSelection(query)?.id === selected.id),
|
|
383
388
|
}, run);
|
|
384
389
|
}, [
|
|
385
390
|
closeAll,
|
|
@@ -395,12 +400,39 @@ export function useCommandBarSelectionRuntime({
|
|
|
395
400
|
rootSelectionNavigatedRef,
|
|
396
401
|
rootShortcutIntent,
|
|
397
402
|
runRootRow,
|
|
398
|
-
|
|
403
|
+
searchPending,
|
|
399
404
|
stateConfigLayout,
|
|
400
405
|
stateRef,
|
|
401
406
|
visibleListStateRef,
|
|
402
407
|
]);
|
|
403
408
|
|
|
409
|
+
useEffect(() => {
|
|
410
|
+
const pending = pendingEnterRef.current;
|
|
411
|
+
if (!pending) return;
|
|
412
|
+
if (currentRoute || rootQueryRef.current !== pending.query || rootSelectionNavigatedRef.current) {
|
|
413
|
+
pendingEnterRef.current = null;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const listState = visibleListStateRef.current;
|
|
417
|
+
if (!listState?.results.some(isDefaultSelectable)) {
|
|
418
|
+
// Still searching: the answer may bring a row. Answered with none: there is nothing to run.
|
|
419
|
+
if (!searchPending) pendingEnterRef.current = null;
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
// The selection follows the rows in the render after they land.
|
|
423
|
+
if (!listState.results[listState.selectedIdx]) return;
|
|
424
|
+
pendingEnterRef.current = null;
|
|
425
|
+
activateListSelection({ secondary: pending.secondary });
|
|
426
|
+
}, [
|
|
427
|
+
activateListSelection,
|
|
428
|
+
currentRoute,
|
|
429
|
+
rootQueryRef,
|
|
430
|
+
rootSelectedIdx,
|
|
431
|
+
rootSelectionNavigatedRef,
|
|
432
|
+
searchPending,
|
|
433
|
+
visibleListStateRef,
|
|
434
|
+
]);
|
|
435
|
+
|
|
404
436
|
return {
|
|
405
437
|
acceptRootShortcutTab,
|
|
406
438
|
acceptSelectedShortcutTab,
|
|
@@ -532,12 +532,14 @@ export function CommandBar({
|
|
|
532
532
|
rootModeKindRef,
|
|
533
533
|
rootQuery,
|
|
534
534
|
rootQueryRef,
|
|
535
|
+
rootSelectedIdx,
|
|
535
536
|
rootSelectionNavigatedRef,
|
|
536
537
|
rootShortcutIntent,
|
|
537
538
|
rootThemeBaseIdRef,
|
|
538
539
|
runDirectCommand,
|
|
539
540
|
runRootRow,
|
|
540
541
|
runSecurityDescriptionShortcut,
|
|
542
|
+
searchPending: rootSearching || !rootResultsCurrent,
|
|
541
543
|
setRootQuery,
|
|
542
544
|
stateConfigLayout: state.config.layout,
|
|
543
545
|
stateRef,
|
|
@@ -170,9 +170,9 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
|
|
|
170
170
|
fireAndForget(desktopWindowBridge.closeDetachedPane?.(desktopWindowBridge.paneId), "desktop.closeDetachedPane");
|
|
171
171
|
}, [desktopWindowBridge, locked, pluginRegistry]);
|
|
172
172
|
|
|
173
|
-
const startWindowDrag = useCallback(() => {
|
|
173
|
+
const startWindowDrag = useCallback((press?: { detail?: number }) => {
|
|
174
174
|
focusPane();
|
|
175
|
-
if (nativeWindowChrome) void rendererHost.startWindowDrag?.();
|
|
175
|
+
if (nativeWindowChrome) void rendererHost.startWindowDrag?.(press);
|
|
176
176
|
}, [focusPane, nativeWindowChrome, rendererHost]);
|
|
177
177
|
|
|
178
178
|
const sharePane = useCallback(() => {
|
|
@@ -870,9 +870,9 @@ export function Header({
|
|
|
870
870
|
// bar itself wears the prompt's fill and reads as a single strip.
|
|
871
871
|
const headerBg = nativePaneChrome ? colors.header : headerSurface(colors);
|
|
872
872
|
|
|
873
|
-
const startWindowDrag = useCallback(() => {
|
|
873
|
+
const startWindowDrag = useCallback((press?: { detail?: number }) => {
|
|
874
874
|
if (!titleBarOverlay || !nativeWindowChrome) return;
|
|
875
|
-
void rendererHost.startWindowDrag?.();
|
|
875
|
+
void rendererHost.startWindowDrag?.(press);
|
|
876
876
|
}, [nativeWindowChrome, rendererHost, titleBarOverlay]);
|
|
877
877
|
|
|
878
878
|
const openCommandBar = useCallback((event?: HeaderActionEvent) => {
|
|
@@ -28,10 +28,10 @@ export function usePaneCaptureStamp(paneId: string): string | null {
|
|
|
28
28
|
|
|
29
29
|
export function captureStampFooter(footer: CombinedPaneFooter, text: string | null): CombinedPaneFooter {
|
|
30
30
|
if (!text) return footer;
|
|
31
|
-
//
|
|
31
|
+
// The footer sets it apart from what the pane says about its data, which may itself be a time.
|
|
32
32
|
const segment: PaneFooterSegment = {
|
|
33
33
|
id: "capture-stamp",
|
|
34
|
-
parts: [
|
|
34
|
+
parts: [{ text, tone: "label" }],
|
|
35
35
|
};
|
|
36
36
|
return {
|
|
37
37
|
...footer,
|
|
@@ -11,7 +11,9 @@ import { useOptionalDialog, type PromptContext } from "../../../../ui/dialog";
|
|
|
11
11
|
import { nativePaneFooterRows } from "../sizing";
|
|
12
12
|
import {
|
|
13
13
|
EMPTY_FOOTER,
|
|
14
|
+
PANE_FOOTER_SEPARATOR,
|
|
14
15
|
fitPaneFooterInfo,
|
|
16
|
+
separatesPaneFooterSegments,
|
|
15
17
|
hasPaneFooterContent,
|
|
16
18
|
layoutPaneFooterHintRow,
|
|
17
19
|
totalHintsWidth,
|
|
@@ -22,10 +24,12 @@ import {
|
|
|
22
24
|
} from "./model";
|
|
23
25
|
|
|
24
26
|
export {
|
|
27
|
+
companyDataFooterSegment,
|
|
25
28
|
datesPaneData,
|
|
26
29
|
DELAYED_SEGMENT,
|
|
27
30
|
delayedFooterSegment,
|
|
28
31
|
hasPaneFooterContent,
|
|
32
|
+
savedCopyFooterInfo,
|
|
29
33
|
savedCopyFooterSegment,
|
|
30
34
|
splitDelayedStatus,
|
|
31
35
|
type CombinedPaneFooter,
|
|
@@ -267,6 +271,7 @@ function FooterContent({
|
|
|
267
271
|
// A segment with shorter forms takes the longest that fits beside the hints, gaps and terminal keys included.
|
|
268
272
|
const keysWidth = nativePaneChrome ? 0 : footer.info.reduce((total, segment) => total + terminalSegmentKey(segment, focused).length, 0);
|
|
269
273
|
const info = infoWidth != null ? fitPaneFooterInfo(footer.info, infoWidth - keysWidth, { besideHints: hasHints }) : footer.info;
|
|
274
|
+
const lastTextIndex = info.findLastIndex((segment) => !segment.icon);
|
|
270
275
|
|
|
271
276
|
if (!hasInfo && !hasHints) {
|
|
272
277
|
return <Box flexGrow={1} height={1} />;
|
|
@@ -291,12 +296,24 @@ function FooterContent({
|
|
|
291
296
|
flexShrink={1}
|
|
292
297
|
{...(infoWidth != null ? { width: infoWidth } : {})}
|
|
293
298
|
>
|
|
294
|
-
{info.map((segment, index) =>
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
299
|
+
{info.map((segment, index) => {
|
|
300
|
+
// Every pane's segments are set apart the same way, so two never read as one sentence.
|
|
301
|
+
const separated = index > 0 && separatesPaneFooterSegments(info[index - 1]!, segment);
|
|
302
|
+
return (
|
|
303
|
+
<Box
|
|
304
|
+
key={segment.id}
|
|
305
|
+
flexDirection="row"
|
|
306
|
+
overflow="hidden"
|
|
307
|
+
// A row too narrow cuts from its end: the last text segment gives way, then the row clips the
|
|
308
|
+
// one before it. An icon is a few cells that mean nothing cut, and earlier text says more.
|
|
309
|
+
flexShrink={!segment.icon && index === lastTextIndex ? 1 : 0}
|
|
310
|
+
marginLeft={index > 0 && !separated ? 1 : 0}
|
|
311
|
+
>
|
|
312
|
+
{separated && <Box flexShrink={0}><Text fg={colors.textMuted}>{PANE_FOOTER_SEPARATOR}</Text></Box>}
|
|
313
|
+
<SegmentView segment={segment} focused={focused} />
|
|
314
|
+
</Box>
|
|
315
|
+
);
|
|
316
|
+
})}
|
|
300
317
|
</Box>
|
|
301
318
|
)}
|
|
302
319
|
{hasHints && (
|