gloomberb 0.16.2 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/account-cache.ts +45 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +524 -148
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +223 -42
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +200 -73
- package/src/cli/desktop-pane-shot.ts +100 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +40 -7
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +21 -13
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +55 -7
- package/src/cli/pane-functions/capabilities.ts +13 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +89 -28
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +71 -24
- package/src/cli/pane-functions/headless.ts +168 -35
- package/src/cli/pane-functions/index.ts +12 -2
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +12 -1
- package/src/cli/pane-functions/resolver.ts +32 -8
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +109 -23
- package/src/cli/registry.ts +8 -4
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +63 -16
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/export.ts +6 -51
- package/src/components/data-table/view.tsx +31 -10
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +16 -7
- package/src/components/layout/shell/drag/live.ts +193 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +84 -57
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +461 -180
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +156 -11
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/freshness.ts +5 -0
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/pane-model.ts +10 -6
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/catalysts/detail.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +32 -6
- package/src/plugins/builtin/holders/pane.tsx +25 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/members/pane.tsx +10 -0
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/perps/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +260 -0
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -6
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +113 -17
- package/src/renderers/dom/host/drag-shield.ts +35 -0
- package/src/renderers/dom/host/live-frame.ts +62 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +26 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
- package/src/renderers/electrobun/shared/protocol.ts +12 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +13 -7
- package/src/sources/provider-router/financials.ts +106 -16
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +24 -6
- package/src/sources/sec-edgar.ts +68 -8
- package/src/state/pane-scroll-registry.tsx +67 -35
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/margin-account.ts +108 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolution.ts +15 -0
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/financials.ts +32 -3
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +10 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history-integrity.ts +19 -6
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -3,6 +3,7 @@ import type { PaneRuntimeState } from "../../../core/state/app/state";
|
|
|
3
3
|
import type { DesktopDockPreviewState, DesktopSharedStateSnapshot, DesktopThemePreviewState } from "../../../types/desktop-window";
|
|
4
4
|
import type { DesktopApplicationMenuCommand } from "../../../types/desktop-menu";
|
|
5
5
|
import type { AppConfig } from "../../../types/config";
|
|
6
|
+
import type { CachedResourceRecord, ResourceCacheKey } from "../../../data/resource-store";
|
|
6
7
|
import type { TickerRecord } from "../../../types/ticker";
|
|
7
8
|
import type { ReleaseInfo, UpdateCheckResult, UpdateProgress } from "../../../updater";
|
|
8
9
|
import type { CapabilityManifest } from "../../../capabilities";
|
|
@@ -18,8 +19,12 @@ export interface ElectrobunBackendInit {
|
|
|
18
19
|
desktopSnapshot: DesktopSharedStateSnapshot | null;
|
|
19
20
|
desktopThemePreview: DesktopThemePreviewState;
|
|
20
21
|
pluginState: Record<string, Record<string, unknown>>;
|
|
22
|
+
/** Saved broker account snapshots, which the view's in-memory store starts from. */
|
|
23
|
+
savedResources?: CachedResourceRecord[];
|
|
21
24
|
capabilityManifests: CapabilityManifest[];
|
|
22
25
|
desktopPlatform: string;
|
|
26
|
+
/** The machine name, which a remote assistant sees this desktop app under. */
|
|
27
|
+
hostName?: string;
|
|
23
28
|
windowKind: "main" | "detached";
|
|
24
29
|
paneId?: string;
|
|
25
30
|
/** What the view needs to send crash reports and usage counts the same way the Bun process would. */
|
|
@@ -226,6 +231,9 @@ interface DesktopBackendRequestMap {
|
|
|
226
231
|
response: null;
|
|
227
232
|
};
|
|
228
233
|
"session.delete": { request: { sessionId: string }; response: null };
|
|
234
|
+
/** Saves or drops a broker account snapshot in the cache database; nothing else is accepted. */
|
|
235
|
+
"resources.set": { request: { record: CachedResourceRecord }; response: null };
|
|
236
|
+
"resources.delete": { request: { key: ResourceCacheKey }; response: null };
|
|
229
237
|
}
|
|
230
238
|
|
|
231
239
|
export type DesktopBackendRequestMethod = keyof DesktopBackendRequestMap;
|
|
@@ -251,7 +259,10 @@ export type DesktopWorkspaceRequest = DesktopBackendRequestFor<Extract<DesktopBa
|
|
|
251
259
|
export type DesktopPluginStateRequest = DesktopBackendRequestFor<Extract<DesktopBackendRequestMethod, `pluginState.${string}`>>;
|
|
252
260
|
export type DesktopHostRequest = DesktopBackendRequestFor<Extract<DesktopBackendRequestMethod, `host.${string}`>>;
|
|
253
261
|
export type DesktopCoreRequest = DesktopBackendRequestFor<
|
|
254
|
-
Extract<
|
|
262
|
+
Extract<
|
|
263
|
+
DesktopBackendRequestMethod,
|
|
264
|
+
`update.${string}` | `ticker.${string}` | `config.${string}` | `session.${string}` | `resources.${string}`
|
|
265
|
+
>
|
|
255
266
|
>;
|
|
256
267
|
|
|
257
268
|
interface BackendRequestPayload {
|
|
@@ -397,6 +397,9 @@ function HydratePayload({
|
|
|
397
397
|
tickers: new Map(payload.tickers.map((ticker) => [ticker.metadata.ticker, ticker])),
|
|
398
398
|
});
|
|
399
399
|
dispatch({ type: "HYDRATE_FINANCIALS", financials: new Map(payload.financials) });
|
|
400
|
+
for (const [instanceId, accounts] of Object.entries(payload.brokerAccounts ?? {})) {
|
|
401
|
+
dispatch({ type: "SET_BROKER_ACCOUNTS", instanceId, accounts });
|
|
402
|
+
}
|
|
400
403
|
dispatch({ type: "SET_INITIALIZED" });
|
|
401
404
|
|
|
402
405
|
return waitForShotReadiness();
|
|
@@ -438,7 +441,7 @@ function ShotPane({ payload, registry }: { payload: DesktopPaneShotPayload; regi
|
|
|
438
441
|
const height = payload.heightCells;
|
|
439
442
|
// PORT keeps what its basket covers, so a capture never reads as the whole account.
|
|
440
443
|
return (
|
|
441
|
-
<PaneShotFrame paneId={instance.instanceId} title={title} width={width} height={height} preserveStatus={["time-sales", "gpu", "hiring", "apps", "catalysts", "litigation", "perps", "members", "analytics"].includes(pane.id)}>
|
|
444
|
+
<PaneShotFrame paneId={instance.instanceId} title={title} width={width} height={height} statusLine={payload.statusLine === true} preserveStatus={["time-sales", "gpu", "hiring", "apps", "catalysts", "litigation", "perps", "members", "analytics"].includes(pane.id)}>
|
|
442
445
|
{(bodyFrame) => <PaneContent
|
|
443
446
|
component={pane.component}
|
|
444
447
|
paneId={instance.instanceId}
|
|
@@ -1,20 +1,116 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
|
-
import type
|
|
2
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
3
3
|
import { FloatingPaneWrapper } from "../../../components/layout/floating-pane";
|
|
4
|
-
import { PaneFooterProvider, hasPaneFooterContent } from "../../../components/layout/pane/footer";
|
|
4
|
+
import { PaneFooterProvider, hasPaneFooterContent, type PaneFooterSegment } from "../../../components/layout/pane/footer";
|
|
5
5
|
import { resolvePaneBodyFrame } from "../../../components/layout/pane/sizing";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
declare global {
|
|
8
|
+
interface Window {
|
|
9
|
+
/**
|
|
10
|
+
* Set by a capture that carries a status line: shows the first of the
|
|
11
|
+
* variants (longest first) that fits the footer, cut in the middle when
|
|
12
|
+
* none does, and resolves to the text shown.
|
|
13
|
+
*/
|
|
14
|
+
__GLOOM_CLI_SHOT_FIT_STATUS__?: (variants: string[]) => Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Holds the footer row from the first frame, while the status line is still
|
|
20
|
+
* being worked out from what the pane loaded, so the body never resizes.
|
|
21
|
+
*/
|
|
22
|
+
const PENDING_STATUS = "\u00a0";
|
|
23
|
+
|
|
24
|
+
function nextFrames(count: number): Promise<void> {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const step = (left: number) => (left <= 0 ? resolve() : requestAnimationFrame(() => step(left - 1)));
|
|
27
|
+
step(count);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Keeps the start and the end of a line that does not fit, `keep` characters in all. */
|
|
32
|
+
function middleEllipsis(text: string, keep: number): string {
|
|
33
|
+
if (keep >= text.length) return text;
|
|
34
|
+
const head = Math.ceil(keep / 2);
|
|
35
|
+
const tail = Math.max(0, keep - head);
|
|
36
|
+
return `${text.slice(0, head).trimEnd()}…${tail > 0 ? text.slice(text.length - tail).trimStart() : ""}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The first variant that fits, else the last one cut in the middle as little
|
|
41
|
+
* as it must be. `show` puts a text on screen and resolves once it is drawn.
|
|
42
|
+
*/
|
|
43
|
+
async function fitStatusLine(
|
|
44
|
+
variants: readonly string[],
|
|
45
|
+
show: (text: string) => Promise<void>,
|
|
46
|
+
overflows: () => boolean,
|
|
47
|
+
): Promise<string> {
|
|
48
|
+
for (const variant of variants) {
|
|
49
|
+
await show(variant);
|
|
50
|
+
if (!overflows()) return variant;
|
|
51
|
+
}
|
|
52
|
+
const last = variants.at(-1) ?? "";
|
|
53
|
+
let low = 1;
|
|
54
|
+
let high = last.length - 1;
|
|
55
|
+
let best = middleEllipsis(last, 1);
|
|
56
|
+
while (low <= high) {
|
|
57
|
+
const keep = Math.floor((low + high) / 2);
|
|
58
|
+
const text = middleEllipsis(last, keep);
|
|
59
|
+
await show(text);
|
|
60
|
+
if (overflows()) {
|
|
61
|
+
high = keep - 1;
|
|
62
|
+
} else {
|
|
63
|
+
best = text;
|
|
64
|
+
low = keep + 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await show(best);
|
|
68
|
+
return best;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function footerOverflows(): boolean {
|
|
72
|
+
const footer = document.querySelector('[data-gloom-role="pane-footer"]');
|
|
73
|
+
if (!footer) return false;
|
|
74
|
+
return [footer, ...footer.querySelectorAll("*")].some((element) => (
|
|
75
|
+
element instanceof HTMLElement && element.scrollWidth > element.clientWidth + 1
|
|
76
|
+
));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function useShotStatusLine(enabled: boolean): string {
|
|
80
|
+
const [text, setText] = useState(PENDING_STATUS);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!enabled) return;
|
|
83
|
+
window.__GLOOM_CLI_SHOT_FIT_STATUS__ = (variants) => fitStatusLine(variants, async (next) => {
|
|
84
|
+
setText(next);
|
|
85
|
+
await nextFrames(2);
|
|
86
|
+
}, footerOverflows);
|
|
87
|
+
return () => {
|
|
88
|
+
delete window.__GLOOM_CLI_SHOT_FIT_STATUS__;
|
|
89
|
+
};
|
|
90
|
+
}, [enabled]);
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Static captures retain warnings and optional source status without action
|
|
96
|
+
* shortcuts. With `statusLine` the footer also carries the capture's dated
|
|
97
|
+
* status line (`Fri 9 Oct close · 15 min delayed · markets closed until Mon`),
|
|
98
|
+
* inside the requested size: the body gives up the footer row for it.
|
|
99
|
+
*/
|
|
100
|
+
export function PaneShotFrame({ paneId, title, width, height, preserveStatus = false, statusLine = false, children }: {
|
|
9
101
|
paneId: string;
|
|
10
102
|
title: string;
|
|
11
103
|
width: number;
|
|
12
104
|
height: number;
|
|
13
105
|
preserveStatus?: boolean;
|
|
106
|
+
statusLine?: boolean;
|
|
14
107
|
children: (frame: ReturnType<typeof resolvePaneBodyFrame>) => ReactNode;
|
|
15
108
|
}) {
|
|
109
|
+
const statusText = useShotStatusLine(statusLine);
|
|
16
110
|
return <PaneFooterProvider>{(registeredFooter) => {
|
|
17
|
-
const
|
|
111
|
+
const status: PaneFooterSegment[] = statusLine ? [{ id: "shot-status", parts: [{ text: statusText, tone: "label" }] }] : [];
|
|
112
|
+
const info = registeredFooter.info.filter((segment) => preserveStatus || segment.icon === "warning");
|
|
113
|
+
const footer = { info: [...info, ...status], hints: [], menu: [], keys: [] };
|
|
18
114
|
const bodyFrame = resolvePaneBodyFrame({
|
|
19
115
|
width,
|
|
20
116
|
height,
|
|
@@ -14,16 +14,6 @@ const contextMenuActionScope = new DesktopContextMenuActionScope(
|
|
|
14
14
|
CONTEXT_MENU_ACTION_TTL_MS,
|
|
15
15
|
);
|
|
16
16
|
|
|
17
|
-
export function startElectrobunWindowDrag(): void {
|
|
18
|
-
window.__electrobunInternalBridge?.postMessage(JSON.stringify([
|
|
19
|
-
JSON.stringify({
|
|
20
|
-
type: "message",
|
|
21
|
-
id: "startWindowMove",
|
|
22
|
-
payload: { id: window.__electrobunWindowId },
|
|
23
|
-
}),
|
|
24
|
-
]));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
export async function showDesktopContextMenu(items: ContextMenuItem[]): Promise<boolean> {
|
|
28
18
|
if (!NATIVE_CONTEXT_MENU_SUPPORTED) return false;
|
|
29
19
|
contextMenuActionScope.clear();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function postWindowMove(id: "startWindowMove" | "stopWindowMove"): void {
|
|
2
|
+
window.__electrobunInternalBridge?.postMessage(JSON.stringify([
|
|
3
|
+
JSON.stringify({ type: "message", id, payload: { id: window.__electrobunWindowId } }),
|
|
4
|
+
]));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let endWindowMove: (() => void) | null = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Moves the window with the pointer until the button comes up. Electrobun
|
|
11
|
+
* stops the move when its native monitor sees the release, but only if the
|
|
12
|
+
* start reached it first: the start goes through the Bun process, and a quick
|
|
13
|
+
* click releases before it lands, leaving the window following the bare
|
|
14
|
+
* pointer until the next click. The release is sent from here too, as
|
|
15
|
+
* Electrobun's own drag regions do; it always arrives after the start.
|
|
16
|
+
*/
|
|
17
|
+
export function startElectrobunWindowDrag(): void {
|
|
18
|
+
// A press whose release never reached the page (it ended outside the window) is over.
|
|
19
|
+
endWindowMove?.();
|
|
20
|
+
postWindowMove("startWindowMove");
|
|
21
|
+
const end = () => {
|
|
22
|
+
window.removeEventListener("mouseup", end, true);
|
|
23
|
+
if (endWindowMove === end) endWindowMove = null;
|
|
24
|
+
postWindowMove("stopWindowMove");
|
|
25
|
+
};
|
|
26
|
+
endWindowMove = end;
|
|
27
|
+
window.addEventListener("mouseup", end, true);
|
|
28
|
+
}
|
|
@@ -210,7 +210,10 @@ async function boot() {
|
|
|
210
210
|
});
|
|
211
211
|
|
|
212
212
|
const remoteControlAdapter = init.windowKind === "main"
|
|
213
|
-
? {
|
|
213
|
+
? {
|
|
214
|
+
registerHandler: setElectrobunRemoteRequestHandler,
|
|
215
|
+
terminalDevice: { kind: "desktop" as const, name: init.hostName?.trim() || "Gloom desktop" },
|
|
216
|
+
}
|
|
214
217
|
: undefined;
|
|
215
218
|
measurePerfAsync("startup.electrobun.root-render", async () => {
|
|
216
219
|
root.render(
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
SESSION_SAVE_DEBOUNCE_MS,
|
|
5
5
|
} from "../../../../state/persist-scheduler";
|
|
6
6
|
import { backendRequest, getElectrobunBackendInitSnapshot } from "../backend-rpc";
|
|
7
|
-
import {
|
|
7
|
+
import { WriteThroughResourceStore } from "../../../../data/memory-resource-store";
|
|
8
|
+
import { isBrokerAccountSnapshotKey } from "../../../../brokers/account-cache";
|
|
8
9
|
|
|
9
10
|
class RemoteSessionStore {
|
|
10
11
|
private snapshot = getElectrobunBackendInitSnapshot()?.sessionSnapshot ?? null;
|
|
@@ -123,9 +124,21 @@ class RemotePluginStateStore {
|
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Caches stay in the view's memory, except broker account snapshots: the Bun
|
|
129
|
+
* process saves those in the cache database, as the terminal does, so a
|
|
130
|
+
* broker portfolio opens with its last known equity and cash.
|
|
131
|
+
*/
|
|
132
|
+
function createViewResourceStore(): WriteThroughResourceStore {
|
|
133
|
+
return new WriteThroughResourceStore(getElectrobunBackendInitSnapshot()?.savedResources ?? [], {
|
|
134
|
+
set: (record) => void backendRequest("resources.set", { record }).catch(() => {}),
|
|
135
|
+
delete: (key) => void backendRequest("resources.delete", { key }).catch(() => {}),
|
|
136
|
+
}, isBrokerAccountSnapshotKey);
|
|
137
|
+
}
|
|
138
|
+
|
|
126
139
|
export class RemotePersistence {
|
|
127
140
|
readonly tickers = {};
|
|
128
|
-
readonly resources =
|
|
141
|
+
readonly resources = createViewResourceStore();
|
|
129
142
|
readonly pluginState = new RemotePluginStateStore(getElectrobunBackendInitSnapshot()?.pluginState ?? {});
|
|
130
143
|
readonly sessions = new RemoteSessionStore();
|
|
131
144
|
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
import type { RendererHost, UiHost } from "../../../ui/host";
|
|
3
3
|
import { flushUsageCounts } from "../../../telemetry/usage-counts";
|
|
4
4
|
import { backendRequest } from "./backend-rpc";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
showDesktopContextMenu,
|
|
8
|
-
startElectrobunWindowDrag,
|
|
9
|
-
} from "./host/native";
|
|
5
|
+
import { NATIVE_CONTEXT_MENU_SUPPORTED, showDesktopContextMenu } from "./host/native";
|
|
6
|
+
import { startElectrobunWindowDrag } from "./host/window-move";
|
|
10
7
|
import { createDomUiHost } from "../../dom/dom-ui-host";
|
|
11
8
|
|
|
12
9
|
export function createWebUiHost(desktopPlatform?: string): UiHost {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { flushSync } from "@opentui/react";
|
|
1
2
|
import { useMemo, type ReactNode } from "react";
|
|
2
3
|
import { useNativeRenderer } from "../../ui";
|
|
3
4
|
import {
|
|
4
5
|
createShortcutRegistry,
|
|
5
6
|
InputHostProvider,
|
|
7
|
+
useRegisteredBeforeShortcut,
|
|
6
8
|
useRegisteredShortcut,
|
|
7
9
|
type InputHost,
|
|
8
10
|
} from "../../react/input";
|
|
@@ -10,7 +12,7 @@ import { toKeyEventLike, useKeyboard, useTerminalDimensions } from "./host";
|
|
|
10
12
|
|
|
11
13
|
export function OpenTuiInputHostProvider({ children }: { children: ReactNode }) {
|
|
12
14
|
const renderer = useNativeRenderer();
|
|
13
|
-
const shortcutRegistry = useMemo(() => createShortcutRegistry(), []);
|
|
15
|
+
const shortcutRegistry = useMemo(() => createShortcutRegistry({ flushSync }), []);
|
|
14
16
|
|
|
15
17
|
useKeyboard((event) => {
|
|
16
18
|
const shortcutEvent = toKeyEventLike(event);
|
|
@@ -22,6 +24,9 @@ export function OpenTuiInputHostProvider({ children }: { children: ReactNode })
|
|
|
22
24
|
useShortcut(handler, options) {
|
|
23
25
|
useRegisteredShortcut(shortcutRegistry, handler, options);
|
|
24
26
|
},
|
|
27
|
+
useBeforeShortcut(listener) {
|
|
28
|
+
useRegisteredBeforeShortcut(shortcutRegistry, listener);
|
|
29
|
+
},
|
|
25
30
|
useViewport() {
|
|
26
31
|
const dimensions = useTerminalDimensions();
|
|
27
32
|
return { width: dimensions.width, height: dimensions.height };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hostname } from "os";
|
|
1
2
|
import { getGloomberbDirs } from "../../data/config/home";
|
|
2
3
|
import { existsSync, mkdirSync } from "fs";
|
|
3
4
|
import { App } from "../../app";
|
|
@@ -38,6 +39,8 @@ import {
|
|
|
38
39
|
import { currentTelemetryConfig } from "../../telemetry/live-config";
|
|
39
40
|
import { flushUsageCounts, installUsageCounter, usageCountsEnabled } from "../../telemetry/usage-counts";
|
|
40
41
|
import { attentionCountsEnabled, installAttentionCounter } from "../../telemetry/attention-counts";
|
|
42
|
+
import { starPromptEnvironmentAllows } from "../../app/star-prompt/model";
|
|
43
|
+
import { allowStarPrompt } from "../../app/star-prompt/runtime";
|
|
41
44
|
|
|
42
45
|
// Declared here rather than sniffed: the desktop view and the hosted browser
|
|
43
46
|
// app are both browser contexts but differ in what plugins may do.
|
|
@@ -56,6 +59,7 @@ export async function startOpenTuiApp({ externalPlugins, cliLaunchRequest }: Sta
|
|
|
56
59
|
const appLog = debugLog.createLogger("app");
|
|
57
60
|
appLog.info("Gloomberb starting");
|
|
58
61
|
const remoteControlAdapter: RemoteControlAdapter = {
|
|
62
|
+
terminalDevice: { kind: "tui", name: hostname() },
|
|
59
63
|
startServer: ({ dataDir, handle }) => {
|
|
60
64
|
let closed = false;
|
|
61
65
|
const serverPromise: Promise<RemoteControlServer> = startRemoteControlServer({
|
|
@@ -121,6 +125,11 @@ export async function startOpenTuiApp({ externalPlugins, cliLaunchRequest }: Sta
|
|
|
121
125
|
officialPluginIds: loadOfficialPluginIds,
|
|
122
126
|
});
|
|
123
127
|
const stopAttention = installAttentionCounter(() => attentionCountsEnabled(currentTelemetryConfig(config), process.env));
|
|
128
|
+
allowStarPrompt(starPromptEnvironmentAllows({
|
|
129
|
+
env: process.env,
|
|
130
|
+
stdinIsTTY: process.stdin.isTTY,
|
|
131
|
+
stdoutIsTTY: process.stdout.isTTY,
|
|
132
|
+
}));
|
|
124
133
|
host = await measurePerfAsync("startup.opentui.create-host", () => createOpenTuiHost());
|
|
125
134
|
const renderer = host.renderer;
|
|
126
135
|
renderer.once("destroy", stopAttention);
|
|
@@ -2,6 +2,7 @@ import { RGBA, StyledText as OpenTuiStyledText, SyntaxStyle, TextAttributes as O
|
|
|
2
2
|
import { createElement, forwardRef, useEffect, useRef, useState, type ReactNode } from "react";
|
|
3
3
|
import { TextAttributes, type UiHost, type TextProps } from "../../ui/host";
|
|
4
4
|
import { renderAsciiText } from "../../ui/ascii-font";
|
|
5
|
+
import { scrollBoxScrollsVertically } from "../../ui/scroll-box-axis";
|
|
5
6
|
import { OpenTuiImageSurface } from "./image/surface";
|
|
6
7
|
import { OpenTuiChartSurface } from "./chart-surface";
|
|
7
8
|
|
|
@@ -141,7 +142,12 @@ function mapTextContent(content: unknown): unknown {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
const OpenTuiBox = createOpenTuiPrimitive("box");
|
|
144
|
-
const
|
|
145
|
+
const OpenTuiScrollBoxPrimitive = createOpenTuiPrimitive("scrollbox");
|
|
146
|
+
// The native box scrolls vertically unless scrollY is false; the shared rule
|
|
147
|
+
// also keeps the one-row horizontal strips from scrolling on that axis.
|
|
148
|
+
const OpenTuiScrollBox = forwardRef<unknown, OpenTuiPrimitiveProps>(function OpenTuiScrollBox(props, ref) {
|
|
149
|
+
return createElement(OpenTuiScrollBoxPrimitive, { ...props, scrollY: scrollBoxScrollsVertically(props), ref });
|
|
150
|
+
});
|
|
145
151
|
const OpenTuiInput = createOpenTuiPrimitive("input");
|
|
146
152
|
const OpenTuiTextarea = createOpenTuiPrimitive("textarea");
|
|
147
153
|
const OpenTuiMediaSurface = createOpenTuiPrimitive("box");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PaneRuntimeState } from "../core/state/app/types";
|
|
2
|
+
import { foldPaneLayout } from "../data/config/store/fold-panes";
|
|
2
3
|
import { publicTickerBindingSymbol } from "../tickers/selection";
|
|
3
4
|
import { pinFollowingPane } from "../layout/pane-follow";
|
|
4
5
|
import {
|
|
@@ -641,28 +642,37 @@ export function materializeMarketplaceLayout(
|
|
|
641
642
|
`${paneId.slice(0, 80)}:shared-${crypto.randomUUID().replaceAll("-", "").slice(0, 20)}`
|
|
642
643
|
),
|
|
643
644
|
): { layout: LayoutConfig; paneState: Record<string, PaneRuntimeState> } {
|
|
644
|
-
|
|
645
|
+
// A layout or pane shared before SIV and SOVR folded into SI and CDX still
|
|
646
|
+
// names the old panes. It opens the way a saved layout migrates.
|
|
647
|
+
const folded = foldPaneLayout(payload.layout);
|
|
648
|
+
const source = {
|
|
649
|
+
layout: folded.layout,
|
|
650
|
+
paneState: Object.fromEntries(Object.entries(payload.paneState).flatMap(([id, state]) => (
|
|
651
|
+
folded.removed.has(id) ? [] : [[folded.idMap.get(id) ?? id, state]]
|
|
652
|
+
))),
|
|
653
|
+
};
|
|
654
|
+
const ids = new Map(source.layout.instances.map((instance, index) => [
|
|
645
655
|
instance.instanceId,
|
|
646
656
|
createId(instance.paneId, index),
|
|
647
657
|
]));
|
|
648
658
|
if (new Set(ids.values()).size !== ids.size) throw new Error("Could not create unique pane ids for this layout.");
|
|
649
659
|
const layout: LayoutConfig = {
|
|
650
|
-
dockRoot: mapDockNode(
|
|
651
|
-
instances:
|
|
660
|
+
dockRoot: mapDockNode(source.layout.dockRoot, ids),
|
|
661
|
+
instances: source.layout.instances.map((instance) => ({
|
|
652
662
|
...structuredClone(instance),
|
|
653
663
|
instanceId: ids.get(instance.instanceId)!,
|
|
654
664
|
...(instance.binding ? { binding: mapBinding(instance.binding, ids) } : {}),
|
|
655
665
|
})),
|
|
656
|
-
floating:
|
|
666
|
+
floating: source.layout.floating.map((entry) => ({
|
|
657
667
|
...entry,
|
|
658
668
|
instanceId: ids.get(entry.instanceId) ?? entry.instanceId,
|
|
659
669
|
})),
|
|
660
|
-
detached:
|
|
670
|
+
detached: source.layout.detached.map((entry) => ({
|
|
661
671
|
...entry,
|
|
662
672
|
instanceId: ids.get(entry.instanceId) ?? entry.instanceId,
|
|
663
673
|
})),
|
|
664
674
|
};
|
|
665
|
-
const paneState = Object.fromEntries(Object.entries(
|
|
675
|
+
const paneState = Object.fromEntries(Object.entries(source.paneState).flatMap(([id, state]) => {
|
|
666
676
|
const materializedId = ids.get(id);
|
|
667
677
|
return materializedId ? [[materializedId, structuredClone(state)]] : [];
|
|
668
678
|
}));
|
|
@@ -39,13 +39,13 @@ import { normalizeNewsFeed } from "../../news/news-model";
|
|
|
39
39
|
import { resolveCurrencyUnit } from "../../utils/currency-units";
|
|
40
40
|
import { canonicalExchange, canonicalTickerKey, parsePublicTickerKey } from "../../utils/exchanges";
|
|
41
41
|
import { normalizePriceHistory, priceHistoryIntervalMs, reachesLatestSettledSession } from "../../utils/price-history";
|
|
42
|
-
import { createProviderMiss } from "../provider-errors";
|
|
42
|
+
import { createProviderMiss, providerMissReason } from "../provider-errors";
|
|
43
43
|
import { assertSecRegistrantMatches } from "../sec-registrant";
|
|
44
44
|
import { nonUsSecListingVenue } from "../../utils/sec";
|
|
45
45
|
import { publicListingTarget } from "../listing-target";
|
|
46
46
|
import { canonicalHistoryInterval, HistoryRetentionError, parseHistoryRecoveryCandidate, parseHistoryRetention, type HistoryRetention } from "../history-retention";
|
|
47
47
|
import { getRouterEntityKey } from "../provider-router/cache";
|
|
48
|
-
import { tickerHasListingSuffix } from "../listing-symbols";
|
|
48
|
+
import { hongKongListingCode, tickerHasListingSuffix } from "../listing-symbols";
|
|
49
49
|
import { parseSecAcceptanceTime } from "../sec-edgar/acceptance-time";
|
|
50
50
|
import { hasMalformedIntradayHistory } from "../../time-series/history-quality";
|
|
51
51
|
import {
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
formatCloudDateTime,
|
|
58
58
|
isEmptyCloudStatus,
|
|
59
59
|
mapBatchError,
|
|
60
|
+
NOT_FOUND_REASON,
|
|
60
61
|
mapCloudFinancials,
|
|
61
62
|
mapOptionsChain,
|
|
62
63
|
mapPricePoint,
|
|
@@ -124,7 +125,7 @@ async function withCloudFallback<T>(load: () => Promise<T>, message: string): Pr
|
|
|
124
125
|
return await load();
|
|
125
126
|
} catch (error) {
|
|
126
127
|
if (isCloudProviderMiss(error)) {
|
|
127
|
-
throw createProviderMiss(message);
|
|
128
|
+
throw createProviderMiss(message, providerMissReason(error));
|
|
128
129
|
}
|
|
129
130
|
throw error;
|
|
130
131
|
}
|
|
@@ -237,7 +238,7 @@ function cloudHistoryResolution(interval: string): ManualChartResolution | null
|
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
function quoteTargetKey(symbol: string, exchange?: string): string {
|
|
240
|
-
const target =
|
|
241
|
+
const target = cloudInstrumentTarget(symbol, exchange);
|
|
241
242
|
const base = target.exchange && tickerHasListingSuffix(target.symbol) ? target.symbol.slice(0, target.symbol.indexOf(".")) : target.symbol;
|
|
242
243
|
return canonicalTickerKey(base, target.exchange);
|
|
243
244
|
}
|
|
@@ -251,7 +252,14 @@ function cloudResponseTargetKey(key: string, requested: Set<string>): string | u
|
|
|
251
252
|
? symbol : undefined;
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The listing as Gloom Cloud knows it. A Hong Kong code goes out with its four
|
|
257
|
+
* digits: Cloud has no 700 or 5 on HKEX, only 0700 and 0005.
|
|
258
|
+
*/
|
|
259
|
+
function cloudInstrumentTarget(symbol: string, exchange?: string) {
|
|
260
|
+
const target = publicListingTarget(symbol, exchange);
|
|
261
|
+
return target.exchange ? { ...target, symbol: hongKongListingCode(target.symbol, target.exchange) } : target;
|
|
262
|
+
}
|
|
255
263
|
|
|
256
264
|
function cloudHistoryRecovery(
|
|
257
265
|
context: MarketDataRequestContext | undefined,
|
|
@@ -323,7 +331,7 @@ function unwrapRequiredCloudResponse<T>(response: CloudMarketResponse<T>, messag
|
|
|
323
331
|
return response.data;
|
|
324
332
|
}
|
|
325
333
|
if (isEmptyCloudStatus(response.status)) {
|
|
326
|
-
throw createProviderMiss(response.reasonCode ?? message);
|
|
334
|
+
throw createProviderMiss(response.reasonCode ?? message, response.message, { notFound: response.reasonCode === NOT_FOUND_REASON });
|
|
327
335
|
}
|
|
328
336
|
throw new Error(response.reasonCode ?? message);
|
|
329
337
|
}
|
|
@@ -404,13 +412,14 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
404
412
|
return withCloudFallback(
|
|
405
413
|
async () => {
|
|
406
414
|
const response = await apiClient.getCloudQuote(target.symbol, target.exchange);
|
|
407
|
-
|
|
408
|
-
throw createProviderMiss(`Cloud quotes are stale for ${ticker}`);
|
|
409
|
-
}
|
|
410
|
-
return retainRequestedQuoteSymbol(mapQuote(
|
|
415
|
+
const quote = retainRequestedQuoteSymbol(mapQuote(
|
|
411
416
|
unwrapRequiredCloudResponse(response, `Cloud quotes are unavailable for ${ticker}`),
|
|
412
417
|
response.providerMeta,
|
|
413
418
|
), ticker);
|
|
419
|
+
// A stale answer is the last price the service has. It stays flagged, so
|
|
420
|
+
// no freshness check passes it and only a request with no current quote
|
|
421
|
+
// from any source falls back to it.
|
|
422
|
+
return isStaleCloudResponse(response) ? { ...quote, stale: true } : quote;
|
|
414
423
|
},
|
|
415
424
|
`Cloud quotes are unavailable for ${ticker}`,
|
|
416
425
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { futuresGenericListing, futuresGenericPriceBasis } from "../../utils/futures-generic";
|
|
2
2
|
import type { TimeRange } from "../../time-series/range";
|
|
3
|
+
import { RANGE_HISTORY_RESOLUTION } from "../../time-series/resolution";
|
|
3
4
|
import type {
|
|
4
5
|
FinancialStatement,
|
|
5
6
|
Fundamentals,
|
|
@@ -305,6 +306,9 @@ export function mapOptionsChain(
|
|
|
305
306
|
};
|
|
306
307
|
}
|
|
307
308
|
|
|
309
|
+
/** The reason code Gloom Cloud gives an empty answer for a symbol no listing carries. */
|
|
310
|
+
export const NOT_FOUND_REASON = "NOT_FOUND";
|
|
311
|
+
|
|
308
312
|
export function isEmptyCloudStatus(
|
|
309
313
|
status: CloudMarketResponse<unknown>["status"],
|
|
310
314
|
): boolean {
|
|
@@ -316,7 +320,7 @@ export function mapBatchError<T>(
|
|
|
316
320
|
fallbackMessage: string,
|
|
317
321
|
): Error {
|
|
318
322
|
if (isEmptyCloudStatus(item.status)) {
|
|
319
|
-
return createProviderMiss(item.reasonCode ?? fallbackMessage);
|
|
323
|
+
return createProviderMiss(item.reasonCode ?? fallbackMessage, item.message, { notFound: item.reasonCode === NOT_FOUND_REASON });
|
|
320
324
|
}
|
|
321
325
|
return new Error(item.reasonCode ?? fallbackMessage);
|
|
322
326
|
}
|
|
@@ -370,29 +374,24 @@ export function formatCloudDateTime(
|
|
|
370
374
|
return `${year}-${month}-${day}`;
|
|
371
375
|
}
|
|
372
376
|
|
|
377
|
+
const HISTORY_OUTPUT_SIZE: Record<TimeRange, number> = {
|
|
378
|
+
"1D": 24 * 12,
|
|
379
|
+
"1W": 7 * 24,
|
|
380
|
+
"1M": 31,
|
|
381
|
+
"3M": 93,
|
|
382
|
+
"6M": 186,
|
|
383
|
+
"1Y": 366,
|
|
384
|
+
"5Y": 261,
|
|
385
|
+
"ALL": 600,
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
/** The bar size comes from the shared range table, which `gloomberb help history` also lists. */
|
|
373
389
|
export function toHistoryRequest(range: TimeRange): {
|
|
374
390
|
interval: string;
|
|
375
391
|
outputsize: number;
|
|
376
392
|
rangeKey: TimeRange;
|
|
377
393
|
} {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
case "1W":
|
|
382
|
-
return { interval: "1h", outputsize: 7 * 24, rangeKey: range };
|
|
383
|
-
case "1M":
|
|
384
|
-
return { interval: "1day", outputsize: 31, rangeKey: range };
|
|
385
|
-
case "3M":
|
|
386
|
-
return { interval: "1day", outputsize: 93, rangeKey: range };
|
|
387
|
-
case "6M":
|
|
388
|
-
return { interval: "1day", outputsize: 186, rangeKey: range };
|
|
389
|
-
case "1Y":
|
|
390
|
-
return { interval: "1day", outputsize: 366, rangeKey: range };
|
|
391
|
-
case "5Y":
|
|
392
|
-
return { interval: "1week", outputsize: 261, rangeKey: range };
|
|
393
|
-
case "ALL":
|
|
394
|
-
return { interval: "1month", outputsize: 600, rangeKey: range };
|
|
395
|
-
default:
|
|
396
|
-
return { interval: "1day", outputsize: 366, rangeKey: range };
|
|
397
|
-
}
|
|
394
|
+
const resolution = RANGE_HISTORY_RESOLUTION[range];
|
|
395
|
+
if (!resolution) return { interval: "1day", outputsize: 366, rangeKey: range };
|
|
396
|
+
return { interval: toCloudInterval(resolution), outputsize: HISTORY_OUTPUT_SIZE[range], rangeKey: range };
|
|
398
397
|
}
|
|
@@ -23,7 +23,7 @@ const EXCHANGE_SUFFIX_MAP: Record<string, string> = {
|
|
|
23
23
|
TASE: ".TA",
|
|
24
24
|
JSE: ".JO",
|
|
25
25
|
BVMF: ".SA", MEXI: ".MX", BYMA: ".BA", BCS: ".SN",
|
|
26
|
-
TADAWUL: ".
|
|
26
|
+
TADAWUL: ".SR", QE: ".QA", DFM: ".AE",
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const EXCHANGE_FALLBACKS: Record<string, string[]> = {
|
|
@@ -143,11 +143,17 @@ export function listingSuffixExchange(ticker: string): string | undefined {
|
|
|
143
143
|
return venues.size === 1 ? [...venues][0] : undefined;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* A Hong Kong listing's four-digit code. Brokers such as Interactive Brokers
|
|
148
|
+
* report Tencent as 700 and HSBC as 5; the exchange and market data know
|
|
149
|
+
* them as 0700 and 0005.
|
|
150
|
+
*/
|
|
151
|
+
export function hongKongListingCode(ticker: string, exchange: string): string {
|
|
152
|
+
return isHongKongExchange(exchange) && /^\d+$/.test(ticker) ? ticker.padStart(4, "0") : ticker;
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
function normalizeListingTicker(ticker: string, exchange: string): string {
|
|
147
|
-
|
|
148
|
-
return ticker.padStart(4, "0");
|
|
149
|
-
}
|
|
150
|
-
return ticker.replace(/ /g, "-");
|
|
156
|
+
return hongKongListingCode(ticker, exchange).replace(/ /g, "-");
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
function isHongKongExchange(exchange: string): boolean {
|