gloomberb 0.16.1 → 0.16.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.
Files changed (574) hide show
  1. package/README.md +2 -1
  2. package/package.json +2 -1
  3. package/src/api-client/beneficial-owners.ts +92 -0
  4. package/src/api-client/data.ts +26 -8
  5. package/src/api-client/errors.ts +12 -0
  6. package/src/api-client/geo.ts +363 -0
  7. package/src/api-client/index.ts +2 -0
  8. package/src/api-client/market-discovery.ts +10 -0
  9. package/src/api-client/paths.ts +30 -1
  10. package/src/api-client/perps.ts +37 -0
  11. package/src/api-client/request.ts +5 -2
  12. package/src/api-client/types.ts +2 -0
  13. package/src/brokers/account-cache.ts +24 -0
  14. package/src/cli/commands/command-utils.ts +8 -0
  15. package/src/cli/commands/crypto-hints.ts +75 -0
  16. package/src/cli/commands/market.ts +414 -106
  17. package/src/cli/commands/overview.ts +77 -32
  18. package/src/cli/commands/ticker.ts +101 -43
  19. package/src/cli/desktop-pane-shot.ts +34 -4
  20. package/src/cli/freshness.ts +61 -0
  21. package/src/cli/help.ts +1 -0
  22. package/src/cli/helpers.ts +4 -8
  23. package/src/cli/index.ts +12 -2
  24. package/src/cli/listing-arg.ts +321 -0
  25. package/src/cli/pane-functions/access-gate.ts +42 -0
  26. package/src/cli/pane-functions/catalog.ts +28 -1
  27. package/src/cli/pane-functions/data.ts +3 -1
  28. package/src/cli/pane-functions/dom.ts +20 -2
  29. package/src/cli/pane-functions/freshness.ts +400 -0
  30. package/src/cli/pane-functions/function-help.ts +27 -20
  31. package/src/cli/pane-functions/headless.ts +39 -11
  32. package/src/cli/pane-functions/index.ts +15 -4
  33. package/src/cli/pane-functions/options.ts +3 -0
  34. package/src/cli/pane-functions/report.ts +4 -0
  35. package/src/cli/pane-functions/resolver.ts +35 -7
  36. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  37. package/src/cli/pane-functions/screenshot.ts +7 -3
  38. package/src/cli/result.ts +41 -27
  39. package/src/cli/ticker-resolution.ts +7 -5
  40. package/src/components/chart/composite/composite-chart.tsx +88 -2536
  41. package/src/components/chart/composite/drawing-store.ts +59 -0
  42. package/src/components/chart/composite/legend.tsx +319 -0
  43. package/src/components/chart/composite/panel-bitmap.ts +139 -0
  44. package/src/components/chart/composite/panel-gestures.ts +59 -0
  45. package/src/components/chart/composite/panel-labels.ts +107 -0
  46. package/src/components/chart/composite/panel-surface.tsx +876 -0
  47. package/src/components/chart/composite/tool-catalog.ts +98 -0
  48. package/src/components/chart/composite/toolbar.tsx +186 -0
  49. package/src/components/chart/composite/tools.ts +8 -0
  50. package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
  51. package/src/components/chart/composite/use-composite-viewport.ts +143 -0
  52. package/src/components/command-bar/list/model.ts +5 -0
  53. package/src/components/command-bar/list/view.tsx +16 -3
  54. package/src/components/command-bar/pane-templates/workflow.ts +1 -0
  55. package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
  56. package/src/components/command-bar/workflow/ops.ts +1 -1
  57. package/src/components/command-bar/workflow/tickers.ts +6 -0
  58. package/src/components/data-table/view.tsx +29 -10
  59. package/src/components/feedback-dialog.tsx +2 -1
  60. package/src/components/form-modal/field-row.tsx +2 -2
  61. package/src/components/layout/detached-pane-shell.tsx +6 -3
  62. package/src/components/layout/pane/content.tsx +23 -10
  63. package/src/components/layout/pane/error-boundary.tsx +103 -0
  64. package/src/components/layout/pane/footer/registration.tsx +68 -23
  65. package/src/components/layout/pane/header.tsx +11 -11
  66. package/src/components/layout/shell/menu.ts +2 -1
  67. package/src/components/layout/shell/pane/actions.ts +2 -1
  68. package/src/components/layout/shell/pane/layers.tsx +14 -3
  69. package/src/components/metric-treemap/heat-surface.tsx +43 -16
  70. package/src/components/metric-treemap/heat-tiles.ts +18 -0
  71. package/src/components/metric-treemap/layout.ts +2 -0
  72. package/src/components/onboarding/onboarding-keyboard.ts +311 -0
  73. package/src/components/onboarding/onboarding-wizard.tsx +101 -788
  74. package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
  75. package/src/components/onboarding/use-onboarding-progress.ts +298 -0
  76. package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
  77. package/src/components/ticker/list-table-view.tsx +13 -1
  78. package/src/components/ui/fields.tsx +8 -0
  79. package/src/components/ui/range-track.tsx +10 -3
  80. package/src/components/ui/status.tsx +8 -2
  81. package/src/components/ui/tabs.tsx +7 -4
  82. package/src/components/ui/text-prompt-dialog.tsx +4 -2
  83. package/src/data/config/store/normalize.ts +17 -1
  84. package/src/i18n/es.ts +19 -1
  85. package/src/i18n/ja.ts +19 -1
  86. package/src/i18n/ko.ts +19 -1
  87. package/src/i18n/zh-cn.ts +19 -1
  88. package/src/i18n/zh-tw.ts +19 -1
  89. package/src/market-data/coordinator/chart.ts +1 -1
  90. package/src/market-data/coordinator/quotes.ts +23 -4
  91. package/src/market-data/fx-closes.ts +72 -0
  92. package/src/market-data/market/freshness.ts +78 -6
  93. package/src/market-data/market/session-close-note.ts +57 -0
  94. package/src/market-data/market/status.ts +151 -0
  95. package/src/market-data/published-apac-sessions.ts +66 -0
  96. package/src/market-data/quotes/contributions.ts +2 -0
  97. package/src/market-data/quotes/resolution.ts +2 -0
  98. package/src/market-data/quotes/screener-live-quotes.ts +49 -4
  99. package/src/plugins/builtin/account-management/pane.tsx +4 -0
  100. package/src/plugins/builtin/alerts/cli.ts +10 -5
  101. package/src/plugins/builtin/alerts/index.tsx +1 -0
  102. package/src/plugins/builtin/analytics/metrics.ts +8 -3
  103. package/src/plugins/builtin/analytics/risk-client.ts +191 -66
  104. package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
  105. package/src/plugins/builtin/analytics/risk-headless.ts +19 -13
  106. package/src/plugins/builtin/analytics/risk-model.ts +45 -17
  107. package/src/plugins/builtin/analytics/risk-pane.tsx +1 -0
  108. package/src/plugins/builtin/analytics/risk-test-data.ts +25 -1
  109. package/src/plugins/builtin/attention/headless.ts +6 -4
  110. package/src/plugins/builtin/attention/index.tsx +1 -1
  111. package/src/plugins/builtin/attention/pane.tsx +1 -1
  112. package/src/plugins/builtin/awards/headless.ts +1 -0
  113. package/src/plugins/builtin/awards/pane.tsx +1 -1
  114. package/src/plugins/builtin/backtest/headless.ts +1 -0
  115. package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
  116. package/src/plugins/builtin/buildout/index.tsx +1 -0
  117. package/src/plugins/builtin/catalysts/headless.ts +3 -1
  118. package/src/plugins/builtin/cds/headless.ts +2 -0
  119. package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
  120. package/src/plugins/builtin/chart-composer/headless.ts +38 -3
  121. package/src/plugins/builtin/chart-composer/index.tsx +3 -1
  122. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
  123. package/src/plugins/builtin/chat/channels.ts +19 -3
  124. package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
  125. package/src/plugins/builtin/chat/content/footer.tsx +15 -6
  126. package/src/plugins/builtin/chat/content/index.tsx +120 -21
  127. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
  128. package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
  129. package/src/plugins/builtin/chat/direct-messages.ts +200 -0
  130. package/src/plugins/builtin/chat/index.tsx +1 -0
  131. package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
  132. package/src/plugins/builtin/chat/message/header.tsx +16 -6
  133. package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
  134. package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
  135. package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
  136. package/src/plugins/builtin/chat/message/types.ts +14 -2
  137. package/src/plugins/builtin/chat/profile-popover.ts +18 -13
  138. package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
  139. package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
  140. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  141. package/src/plugins/builtin/cloud/team/module.tsx +1 -0
  142. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
  143. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  144. package/src/plugins/builtin/comment-letters/index.tsx +1 -0
  145. package/src/plugins/builtin/company-attention/headless.ts +5 -1
  146. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  147. package/src/plugins/builtin/company-kpis/headless.ts +1 -0
  148. package/src/plugins/builtin/congress-trades/headless.ts +1 -0
  149. package/src/plugins/builtin/correlation/compute.ts +59 -16
  150. package/src/plugins/builtin/correlation/geo.ts +34 -0
  151. package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
  152. package/src/plugins/builtin/correlation/headless.ts +42 -8
  153. package/src/plugins/builtin/correlation/history.ts +2 -0
  154. package/src/plugins/builtin/correlation/index.tsx +68 -14
  155. package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
  156. package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
  157. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  158. package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
  159. package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
  160. package/src/plugins/builtin/correlation/settings.ts +1 -1
  161. package/src/plugins/builtin/cot/headless.ts +6 -3
  162. package/src/plugins/builtin/cot/index.tsx +3 -3
  163. package/src/plugins/builtin/cot/model.ts +36 -3
  164. package/src/plugins/builtin/cpi/headless.ts +4 -0
  165. package/src/plugins/builtin/credit-boards/headless.ts +2 -0
  166. package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
  167. package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
  168. package/src/plugins/builtin/credit-documents/headless.ts +2 -0
  169. package/src/plugins/builtin/crypto-board/headless.ts +3 -0
  170. package/src/plugins/builtin/custom-view/loader.ts +1 -1
  171. package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
  172. package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
  173. package/src/plugins/builtin/doe/headless.ts +7 -0
  174. package/src/plugins/builtin/doe/range-bar.tsx +7 -37
  175. package/src/plugins/builtin/earnings/headless.ts +5 -0
  176. package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
  177. package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
  178. package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
  179. package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
  180. package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
  181. package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
  182. package/src/plugins/builtin/econ/index.tsx +1 -0
  183. package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
  184. package/src/plugins/builtin/equity-screener/headless.ts +2 -0
  185. package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
  186. package/src/plugins/builtin/executives/index.tsx +1 -0
  187. package/src/plugins/builtin/exposure/headless.ts +3 -1
  188. package/src/plugins/builtin/fear-greed/headless.ts +2 -0
  189. package/src/plugins/builtin/filing-events/index.tsx +1 -0
  190. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  191. package/src/plugins/builtin/futures-curve/headless.ts +39 -10
  192. package/src/plugins/builtin/futures-curve/index.tsx +3 -3
  193. package/src/plugins/builtin/futures-curve/model.ts +188 -4
  194. package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
  195. package/src/plugins/builtin/gpu/headless.ts +7 -3
  196. package/src/plugins/builtin/gpu/ladder.tsx +26 -30
  197. package/src/plugins/builtin/gpu/model.ts +27 -1
  198. package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
  199. package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
  200. package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
  201. package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
  202. package/src/plugins/builtin/holders/headless.ts +139 -5
  203. package/src/plugins/builtin/holders/index.tsx +10 -3
  204. package/src/plugins/builtin/holders/pane.tsx +20 -3
  205. package/src/plugins/builtin/holders/report-header.ts +95 -0
  206. package/src/plugins/builtin/holders/table-model.ts +20 -5
  207. package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
  208. package/src/plugins/builtin/holders/types.ts +2 -1
  209. package/src/plugins/builtin/insider/headless.ts +2 -0
  210. package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
  211. package/src/plugins/builtin/iv-history/headless.ts +4 -1
  212. package/src/plugins/builtin/jobs/headless.ts +1 -0
  213. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
  214. package/src/plugins/builtin/macro-day/headless.ts +2 -0
  215. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  216. package/src/plugins/builtin/market-heatmap/data.ts +2 -0
  217. package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
  218. package/src/plugins/builtin/market-heatmap/index.tsx +39 -46
  219. package/src/plugins/builtin/market-heatmap/model.ts +146 -0
  220. package/src/plugins/builtin/market-heatmap/portfolio.ts +23 -4
  221. package/src/plugins/builtin/market-movers/headless.ts +2 -0
  222. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  223. package/src/plugins/builtin/members/client.ts +20 -3
  224. package/src/plugins/builtin/members/headless.ts +5 -3
  225. package/src/plugins/builtin/members/model.ts +10 -2
  226. package/src/plugins/builtin/members/pane.tsx +13 -6
  227. package/src/plugins/builtin/mna/distress/headless.ts +1 -0
  228. package/src/plugins/builtin/mna/headless.ts +1 -0
  229. package/src/plugins/builtin/money-markets/headless.ts +1 -0
  230. package/src/plugins/builtin/news/headless.ts +2 -0
  231. package/src/plugins/builtin/news/wire/index.tsx +8 -1
  232. package/src/plugins/builtin/notes/cli.ts +14 -6
  233. package/src/plugins/builtin/notes/index.tsx +1 -0
  234. package/src/plugins/builtin/openfda/index.tsx +1 -0
  235. package/src/plugins/builtin/options-calculator/headless.ts +2 -0
  236. package/src/plugins/builtin/options-positioning/headless.ts +3 -0
  237. package/src/plugins/builtin/options-scenario/headless.ts +5 -0
  238. package/src/plugins/builtin/pe-band/client.ts +48 -3
  239. package/src/plugins/builtin/pe-band/headless.ts +25 -7
  240. package/src/plugins/builtin/pe-band/model.ts +92 -16
  241. package/src/plugins/builtin/pe-band/pane.tsx +33 -16
  242. package/src/plugins/builtin/perps/client.ts +50 -3
  243. package/src/plugins/builtin/perps/headless.ts +144 -16
  244. package/src/plugins/builtin/perps/index.tsx +6 -6
  245. package/src/plugins/builtin/perps/model.ts +189 -12
  246. package/src/plugins/builtin/perps/pane.tsx +248 -47
  247. package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
  248. package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
  249. package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
  250. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
  251. package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
  252. package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
  253. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
  254. package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
  255. package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
  256. package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
  257. package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
  258. package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
  259. package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
  260. package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
  261. package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
  262. package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
  263. package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
  264. package/src/plugins/builtin/power/headless.ts +3 -1
  265. package/src/plugins/builtin/rate-path/headless.ts +2 -0
  266. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  267. package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
  268. package/src/plugins/builtin/research/analyst-headless.ts +3 -0
  269. package/src/plugins/builtin/research/events-headless.ts +2 -0
  270. package/src/plugins/builtin/research/headless.ts +2 -0
  271. package/src/plugins/builtin/research/index.tsx +1 -0
  272. package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
  273. package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
  274. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  275. package/src/plugins/builtin/research-search/index.tsx +1 -0
  276. package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
  277. package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
  278. package/src/plugins/builtin/risk-factors/headless.ts +2 -0
  279. package/src/plugins/builtin/scanner/flow-history.ts +2 -13
  280. package/src/plugins/builtin/scanner/headless.ts +14 -0
  281. package/src/plugins/builtin/seasonality/headless.ts +2 -0
  282. package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
  283. package/src/plugins/builtin/sec/filing-content.ts +42 -17
  284. package/src/plugins/builtin/sec/forms.ts +12 -0
  285. package/src/plugins/builtin/sec/headless.ts +2 -0
  286. package/src/plugins/builtin/sectors/client.ts +1 -0
  287. package/src/plugins/builtin/sectors/headless.ts +9 -1
  288. package/src/plugins/builtin/sectors/sector-model.ts +2 -0
  289. package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
  290. package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
  291. package/src/plugins/builtin/shared/report-freshness.ts +57 -0
  292. package/src/plugins/builtin/shared/research-cells.tsx +4 -9
  293. package/src/plugins/builtin/short-interest/headless.ts +2 -0
  294. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  295. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  296. package/src/plugins/builtin/social-mentions/headless.ts +1 -0
  297. package/src/plugins/builtin/supply-chain/headless.ts +7 -1
  298. package/src/plugins/builtin/supply-chain/index.tsx +10 -1
  299. package/src/plugins/builtin/supply-chain/model.ts +27 -2
  300. package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
  301. package/src/plugins/builtin/themes/headless.ts +3 -0
  302. package/src/plugins/builtin/thirteenf/headless.ts +110 -25
  303. package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
  304. package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
  305. package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
  306. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
  308. package/src/plugins/builtin/time-sales/headless.ts +5 -1
  309. package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
  310. package/src/plugins/builtin/volatility/headless.ts +6 -1
  311. package/src/plugins/builtin/world-indices/headless.ts +2 -0
  312. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
  313. package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
  314. package/src/plugins/builtin/world-venue-map/client.ts +45 -0
  315. package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
  316. package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
  317. package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
  318. package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
  319. package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
  320. package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
  321. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
  322. package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
  323. package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
  324. package/src/plugins/builtin/world-venue-map/index.ts +67 -4
  325. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
  326. package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
  327. package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
  328. package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
  329. package/src/plugins/builtin/world-venue-map/model.ts +7 -4
  330. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
  331. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
  332. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
  333. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
  334. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
  335. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
  336. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
  337. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
  338. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
  339. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
  340. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
  341. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
  342. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
  343. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
  344. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
  345. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
  346. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
  347. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
  348. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
  349. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
  350. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
  351. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
  352. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
  353. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
  354. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
  355. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
  356. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
  357. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
  358. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
  359. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
  360. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
  361. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
  362. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
  363. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
  364. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
  365. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
  366. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
  367. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
  368. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
  369. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
  370. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
  371. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
  372. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
  373. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
  374. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
  375. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
  376. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
  377. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
  378. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
  379. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
  380. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
  381. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
  382. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
  383. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
  384. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
  385. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
  386. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
  387. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
  388. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
  389. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
  390. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
  391. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
  392. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
  393. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
  394. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
  395. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
  396. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
  397. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
  398. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
  399. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
  400. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
  401. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
  402. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
  403. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
  404. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
  405. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
  406. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
  407. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
  408. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
  409. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
  410. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
  411. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
  412. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
  413. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
  414. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
  415. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
  416. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
  417. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
  418. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
  419. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
  420. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
  421. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
  422. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
  423. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
  424. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
  425. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
  426. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
  429. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
  430. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
  431. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
  432. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
  433. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
  434. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
  435. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
  436. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
  437. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
  438. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
  439. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
  440. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
  441. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
  442. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
  443. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
  444. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
  445. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
  446. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
  447. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
  448. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
  449. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
  450. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
  451. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
  452. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
  453. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
  454. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
  455. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
  456. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
  457. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
  458. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
  459. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
  460. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
  461. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
  462. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
  463. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
  464. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
  465. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
  466. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
  467. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
  468. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
  469. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
  470. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
  471. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
  472. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
  473. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
  474. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
  475. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
  476. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
  477. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
  478. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
  479. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
  480. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
  481. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
  482. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
  483. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
  484. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
  485. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
  486. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
  487. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
  488. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
  489. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
  490. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
  491. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
  492. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
  493. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
  494. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
  495. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
  496. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
  497. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
  498. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
  499. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
  500. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
  501. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
  502. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
  503. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
  504. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
  505. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
  506. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
  507. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
  508. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
  509. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
  510. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
  511. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
  512. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
  513. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
  514. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
  515. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
  516. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
  517. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
  518. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
  519. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
  520. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
  521. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
  522. package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
  523. package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
  524. package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
  525. package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
  526. package/src/plugins/builtin/yield-curve/headless.ts +16 -0
  527. package/src/plugins/loader.ts +13 -1
  528. package/src/plugins/registry/pane-settings.ts +1 -0
  529. package/src/renderers/dom/build-assets.ts +81 -3
  530. package/src/renderers/dom/data-table/row.tsx +2 -0
  531. package/src/renderers/dom/desktop/controls.tsx +7 -3
  532. package/src/renderers/dom/desktop/list-view.tsx +35 -10
  533. package/src/renderers/dom/desktop/popover.tsx +73 -14
  534. package/src/renderers/dom/host/input.tsx +14 -3
  535. package/src/renderers/dom/styles.css +6 -7
  536. package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
  537. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
  538. package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
  539. package/src/renderers/opentui/host.tsx +5 -0
  540. package/src/sources/gloomberb-cloud/index.ts +25 -4
  541. package/src/sources/gloomberb-cloud/normalizers.ts +27 -3
  542. package/src/sources/provider-router/cached-routes.ts +4 -1
  543. package/src/sources/provider-router/financials.ts +8 -7
  544. package/src/sources/provider-router/history.ts +15 -10
  545. package/src/sources/sec-registrant.ts +75 -0
  546. package/src/state/app/context/index.tsx +24 -8
  547. package/src/state/hooks/quote-streaming.ts +41 -26
  548. package/src/sync/core-contributors.ts +4 -0
  549. package/src/telemetry/crash-reports.ts +4 -1
  550. package/src/test-support/cli-context.ts +10 -2
  551. package/src/theme/heat-colors.ts +266 -84
  552. package/src/time-series/resolve.ts +1 -1
  553. package/src/time-series/use-chart-resolution.ts +1 -1
  554. package/src/types/context-menu.ts +6 -0
  555. package/src/types/data-provider.ts +2 -0
  556. package/src/types/financials.ts +3 -0
  557. package/src/types/headless.ts +79 -2
  558. package/src/types/plugin.ts +20 -1
  559. package/src/types/ticker.ts +15 -0
  560. package/src/ui/host.tsx +14 -0
  561. package/src/updater/index.ts +9 -11
  562. package/src/utils/async-deadline.ts +21 -0
  563. package/src/utils/cli-output.ts +5 -1
  564. package/src/utils/crypto-pair.ts +41 -0
  565. package/src/utils/currency-units.ts +14 -0
  566. package/src/utils/depositary-receipt.ts +26 -0
  567. package/src/utils/exchanges.ts +72 -0
  568. package/src/utils/fire-and-forget.ts +24 -0
  569. package/src/utils/format.ts +13 -0
  570. package/src/utils/price-earnings.ts +70 -3
  571. package/src/utils/price-history.ts +58 -13
  572. package/src/utils/rpc-timeout-error.ts +27 -0
  573. package/src/utils/sec.ts +13 -1
  574. package/src/utils/utc-time.ts +79 -0
@@ -2,6 +2,7 @@ import { createElement, useCallback, useEffect, useMemo, useRef, useState, type
2
2
  import type { CloudWorldVenuePayload } from "../../../api-client";
3
3
  import { Box, ChartSurface, Text, useNativeRenderer, useUiCapabilities, useUiHost } from "../../../ui";
4
4
  import { useThemeColors } from "../../../theme/theme-context";
5
+ import { blendHex, parseHex as parseRgb, relativeLuminance } from "../../../theme/color-utils";
5
6
  import { resolveNativeBitmapSize, shouldRenderNativeBitmap } from "../../../components/chart/native/bitmap-support";
6
7
  import { drawCircle, drawLine, fillOpaque, parseHex } from "../../../components/chart/native/raster/primitives";
7
8
  import type { NativeChartBitmap } from "../../../components/chart/native/chart-rasterizer";
@@ -15,12 +16,27 @@ import {
15
16
  MAX_WORLD_MAP_ZOOM,
16
17
  panWorldMapViewport,
17
18
  projectWorldPoint,
19
+ unprojectWorldPoint,
18
20
  zoomWorldMapViewport,
19
21
  type WorldMapPoint,
20
22
  type WorldMapViewport,
21
23
  type WorldVenueCluster,
22
24
  } from "./model";
23
25
  import { WORLD_OUTLINES } from "./world-outlines";
26
+ import { drawGeoBitmap, hitTestGeo, rasterGeoCells, type GeoHit, type GeoMapOverlay } from "./geo-draw";
27
+ import { GeoSvgLayers, MapIconSwatch, toneColor, type MapMatrix, type SymbolTones } from "./geo-svg";
28
+ import { BasemapLayer, type BasemapPalette } from "./basemap-layer";
29
+ import type { GeoBox } from "./basemap";
30
+ import { layerSymbol, shipClass, usesClassTones, type MapSymbolId, type SymbolTone } from "./map-symbols";
31
+ import { geoViewForViewport, sameGeoView, WORLD_GEO_VIEW, type GeoView } from "./layers";
32
+
33
+ /** Where the map should look: a new key centres it, at least at `zoom`. */
34
+ export interface WorldMapFocus {
35
+ key: string;
36
+ longitude: number;
37
+ latitude: number;
38
+ zoom?: number;
39
+ }
24
40
 
25
41
  interface WorldVenueMapProps {
26
42
  venues: readonly CloudWorldVenuePayload[];
@@ -28,6 +44,14 @@ interface WorldVenueMapProps {
28
44
  width: number;
29
45
  height: number;
30
46
  onSelect: (venue: CloudWorldVenuePayload) => void;
47
+ /** Geo layers drawn on the same projection; without them the map is the venue map alone. */
48
+ overlay?: GeoMapOverlay;
49
+ onSelectGeo?: (hit: Extract<GeoHit, { kind: "feature" }>) => void;
50
+ /** The area and zoom in view, as the layers request it. */
51
+ onViewChange?: (view: GeoView) => void;
52
+ focus?: WorldMapFocus | null;
53
+ /** How far the map may zoom; geo layers go closer than venues. */
54
+ maxZoom?: number;
31
55
  }
32
56
 
33
57
  function clusterVenue(cluster: WorldVenueCluster, selectedMic: string | null): CloudWorldVenuePayload {
@@ -117,10 +141,14 @@ function renderWorldBitmap(
117
141
  width: number,
118
142
  height: number,
119
143
  colors: ReturnType<typeof useThemeColors>,
144
+ overlay?: GeoMapOverlay,
120
145
  ): NativeChartBitmap {
121
146
  const bitmap = { width, height, pixels: new Uint8Array(width * height * 4) };
122
147
  fillOpaque(bitmap.pixels, parseHex(colors.bg));
123
148
  drawWorldOutlines(bitmap.pixels, width, height, parseHex(colors.textDim, 0.62), Math.max(1, width / 900));
149
+ if (overlay) {
150
+ drawGeoBitmap(bitmap, overlay, (longitude, latitude) => projectWorldPoint(longitude, latitude, width, height), colors.selectedText);
151
+ }
124
152
 
125
153
  const clusters = clusterWorldVenues(venues, width, height);
126
154
  for (const cluster of clusters) {
@@ -151,6 +179,7 @@ function renderAsciiMap(
151
179
  width: number,
152
180
  height: number,
153
181
  cellAspect: number,
182
+ overlay?: GeoMapOverlay,
154
183
  ): string[] {
155
184
  const grid = Array.from({ length: height }, () => Array.from({ length: width }, () => " "));
156
185
  for (const outline of WORLD_OUTLINES) {
@@ -161,6 +190,8 @@ function renderAsciiMap(
161
190
  if (grid[y]?.[x] === " ") grid[y]![x] = ".";
162
191
  }
163
192
  }
193
+ const project = (longitude: number, latitude: number) => projectWorldPoint(longitude, latitude, width, height, cellAspect);
194
+ if (overlay) rasterGeoCells(grid, overlay, project);
164
195
  for (const cluster of clusterWorldVenues(venues, width, height, cellAspect)) {
165
196
  const x = Math.round(cluster.x);
166
197
  const y = Math.round(cluster.y);
@@ -170,6 +201,11 @@ function renderAsciiMap(
170
201
  ? String(Math.min(cluster.venues.length, 9))
171
202
  : cluster.isOpen ? "O" : "o";
172
203
  }
204
+ if (overlay?.selected) {
205
+ const point = project(overlay.selected.longitude, overlay.selected.latitude);
206
+ const row = grid[Math.round(point.y)];
207
+ if (row && Math.round(point.x) >= 0 && Math.round(point.x) < width) row[Math.round(point.x)] = "@";
208
+ }
173
209
  return grid.map((row) => row.join(""));
174
210
  }
175
211
 
@@ -194,7 +230,7 @@ function TerminalWorldVenueMap(props: WorldVenueMapProps) {
194
230
  cellHeightPx,
195
231
  pixelRatio,
196
232
  });
197
- return renderWorldBitmap(props.venues, props.selectedMic, size.pixelWidth, size.pixelHeight, colors);
233
+ return renderWorldBitmap(props.venues, props.selectedMic, size.pixelWidth, size.pixelHeight, colors, props.overlay);
198
234
  }, [
199
235
  cellHeightPx,
200
236
  cellWidthPx,
@@ -202,6 +238,7 @@ function TerminalWorldVenueMap(props: WorldVenueMapProps) {
202
238
  nativeCharts,
203
239
  pixelRatio,
204
240
  props.height,
241
+ props.overlay,
205
242
  props.selectedMic,
206
243
  props.venues,
207
244
  props.width,
@@ -217,13 +254,31 @@ function TerminalWorldVenueMap(props: WorldVenueMapProps) {
217
254
  [cellAspect, props.height, props.venues, props.width],
218
255
  );
219
256
  const ascii = useMemo(
220
- () => renderAsciiMap(props.venues, props.selectedMic, props.width, props.height, cellAspect),
221
- [cellAspect, props.height, props.selectedMic, props.venues, props.width],
257
+ () => renderAsciiMap(props.venues, props.selectedMic, props.width, props.height, cellAspect, props.overlay),
258
+ [cellAspect, props.height, props.overlay, props.selectedMic, props.venues, props.width],
222
259
  );
223
260
 
261
+ // The terminal map never pans or zooms, so it always covers the world.
262
+ const onViewChange = props.onViewChange;
263
+ useEffect(() => {
264
+ onViewChange?.(WORLD_GEO_VIEW);
265
+ }, [onViewChange]);
266
+
224
267
  const selectAt = (event: ChartMouseEvent) => {
225
268
  const pointer = getLocalPlotPointer(event, surfaceRef.current, renderer);
226
269
  if (!pointer) return;
270
+ if (props.overlay) {
271
+ const hit = hitTestGeo(
272
+ props.overlay,
273
+ { x: pointer.cellX, y: pointer.cellY },
274
+ (longitude, latitude) => projectWorldPoint(longitude, latitude, props.width, props.height, cellAspect),
275
+ 2.5,
276
+ );
277
+ if (hit?.kind === "feature") {
278
+ props.onSelectGeo?.(hit);
279
+ return;
280
+ }
281
+ }
227
282
  const cluster = closestWorldVenueCluster(clusters, pointer.cellX, pointer.cellY, 4);
228
283
  if (cluster) props.onSelect(clusterVenue(cluster, props.selectedMic));
229
284
  };
@@ -244,6 +299,7 @@ function TerminalWorldVenueMap(props: WorldVenueMapProps) {
244
299
  );
245
300
  }
246
301
 
302
+ /** Desktop rows are about twice as tall as they are wide; the measured surface replaces this once it is laid out. */
247
303
  const DESKTOP_MAP_ASPECT = 2.12;
248
304
  const MAP_PAN_THRESHOLD_PX = 4;
249
305
  const MAP_CLICK_HIT_PX = 14;
@@ -283,13 +339,59 @@ function clientDeltaToMapDelta(
283
339
  };
284
340
  }
285
341
 
342
+ /** The surface's size in pixels, once laid out. */
343
+ function useElementSize(ref: { current: HTMLElement | null }): { width: number; height: number } | null {
344
+ const [size, setSize] = useState<{ width: number; height: number } | null>(null);
345
+ useEffect(() => {
346
+ const element = ref.current;
347
+ if (!element) return;
348
+ const measure = () => {
349
+ const rect = element.getBoundingClientRect();
350
+ if (rect.width <= 0 || rect.height <= 0) return;
351
+ setSize((current) => current && Math.abs(current.width - rect.width) < 0.5 && Math.abs(current.height - rect.height) < 0.5
352
+ ? current
353
+ : { width: rect.width, height: rect.height });
354
+ };
355
+ measure();
356
+ if (typeof ResizeObserver === "undefined") return;
357
+ const observer = new ResizeObserver(measure);
358
+ observer.observe(element);
359
+ return () => observer.disconnect();
360
+ }, [ref]);
361
+ return size;
362
+ }
363
+
364
+ interface HoverState {
365
+ hit: GeoHit;
366
+ x: number;
367
+ y: number;
368
+ }
369
+
370
+ function hoverLabel(hit: GeoHit, overlay: GeoMapOverlay): { title: string; detail: string | null } {
371
+ const layer = overlay.layers.find((entry) => entry.id === hit.layerId);
372
+ if (hit.kind === "cluster") {
373
+ return { title: `${hit.cluster.count.toLocaleString("en-US")} ${layer?.name.toLowerCase() ?? ""}`.trim(), detail: null };
374
+ }
375
+ const props = hit.feature.props;
376
+ if (usesClassTones(hit.layerId) || hit.feature.entityKind === "vessel") {
377
+ const kind = shipClass(hit.feature);
378
+ const speed = typeof props.speedKn === "number" && props.speedKn >= 1 ? `${props.speedKn.toFixed(0)} kn` : null;
379
+ return { title: hit.feature.label, detail: [kind.charAt(0).toUpperCase() + kind.slice(1), speed].filter(Boolean).join(" · ") };
380
+ }
381
+ return { title: hit.feature.label, detail: null };
382
+ }
383
+
286
384
  function DesktopWorldVenueMap(props: WorldVenueMapProps) {
287
385
  const colors = useThemeColors();
386
+ const maxZoom = props.maxZoom ?? MAX_WORLD_MAP_ZOOM;
288
387
  const surfaceRef = useRef<HTMLDivElement | null>(null);
388
+ const surfaceSize = useElementSize(surfaceRef);
289
389
  const [viewport, setViewport] = useState<WorldMapViewport>(DEFAULT_WORLD_MAP_VIEWPORT);
290
390
  const viewportRef = useRef(viewport);
291
391
  viewportRef.current = viewport;
292
392
  const [dragging, setDragging] = useState(false);
393
+ const [hover, setHover] = useState<HoverState | null>(null);
394
+ const hoverFrame = useRef<number | null>(null);
293
395
  const dragRef = useRef<{
294
396
  pointerId: number;
295
397
  lastX: number;
@@ -298,37 +400,88 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
298
400
  originY: number;
299
401
  moved: boolean;
300
402
  } | null>(null);
301
- // Desktop rows are roughly twice as tall as they are wide. Matching the SVG
302
- // view box to those pixels lets the contained world projection keep its shape.
303
- const plotHeight = props.height * DESKTOP_MAP_ASPECT;
403
+ // The view box takes the surface's own proportions, so the drawing fills it
404
+ // and a pointer maps to the same place it is drawn.
405
+ const plotHeight = surfaceSize ? (props.width * surfaceSize.height) / surfaceSize.width : props.height * DESKTOP_MAP_ASPECT;
406
+ const unitPx = surfaceSize ? surfaceSize.width / Math.max(props.width, 1) : 8;
304
407
  const zoomed = viewport.zoom > 1;
305
408
  const clusters = useMemo(
306
409
  () => clusterWorldVenues(props.venues, props.width, plotHeight, 1, viewport),
307
410
  [plotHeight, props.venues, props.width, viewport],
308
411
  );
309
- const outlinePaths = useMemo(() => WORLD_OUTLINES.map((outline) => {
310
- let drawing = false;
311
- let previousLongitude: number | null = null;
312
- return outline.map(([longitude, latitude]) => {
313
- if (latitude < -60 || (previousLongitude != null && Math.abs(longitude - previousLongitude) > 180)) {
314
- drawing = false;
315
- previousLongitude = longitude;
316
- return "";
317
- }
318
- const point = projectWorldPoint(longitude, latitude, props.width, plotHeight, 1, viewport);
319
- previousLongitude = longitude;
320
- const command = drawing ? "L" : "M";
321
- drawing = true;
322
- return `${command}${point.x.toFixed(2)} ${point.y.toFixed(2)}`;
323
- }).join(" ");
324
- }), [plotHeight, props.width, viewport]);
325
412
 
326
- const selectAt = useCallback((point: WorldMapPoint, element: HTMLElement) => {
413
+ const project = useCallback(
414
+ (longitude: number, latitude: number) => projectWorldPoint(longitude, latitude, props.width, plotHeight, 1, viewport),
415
+ [plotHeight, props.width, viewport],
416
+ );
417
+ // The projection is linear, so land and layers are drawn once in degrees and
418
+ // placed with one matrix: a pan or zoom changes that matrix, not the paths.
419
+ const matrix = useMemo<MapMatrix>(() => {
420
+ const origin = project(0, 0);
421
+ return { a: project(1, 0).x - origin.x, bx: origin.x, by: origin.y };
422
+ }, [project]);
423
+ const view = useMemo<GeoBox>(() => {
424
+ const topLeft = unprojectWorldPoint(0, 0, props.width, plotHeight, 1, viewport);
425
+ const bottomRight = unprojectWorldPoint(props.width, plotHeight, props.width, plotHeight, 1, viewport);
426
+ return [topLeft.longitude, bottomRight.latitude, bottomRight.longitude, topLeft.latitude];
427
+ }, [plotHeight, props.width, viewport]);
428
+ const pxPerDegree = matrix.a * unitPx;
429
+ const dark = relativeLuminance(colors.text) > relativeLuminance(colors.bg);
430
+ const palette = useMemo<BasemapPalette>(() => ({
431
+ land: blendHex(colors.bg, colors.text, dark ? 0.1 : 0.07),
432
+ coast: blendHex(colors.bg, colors.textDim, dark ? 0.85 : 0.75),
433
+ border: blendHex(colors.bg, colors.textDim, dark ? 0.42 : 0.35),
434
+ }), [colors.bg, colors.text, colors.textDim, dark]);
435
+ // Five colours in all: the layer's own for cargo, amber tankers, bright
436
+ // passenger ships, green fishing boats, and one quiet grey for the rest.
437
+ const tones = useMemo<SymbolTones>(() => {
438
+ const quiet = blendHex(colors.textDim, colors.text, 0.45);
439
+ return { tanker: colors.warning, passenger: colors.textBright, fishing: colors.positive, service: quiet, other: quiet };
440
+ }, [colors.positive, colors.text, colors.textBright, colors.textDim, colors.warning]);
441
+
442
+ const hitRadius = useCallback((element: HTMLElement) => {
327
443
  const rect = element.getBoundingClientRect();
328
- const hit = rect.height <= 0 ? 2.4 : (MAP_CLICK_HIT_PX / rect.height) * plotHeight;
444
+ return rect.height <= 0 ? 2.4 : (MAP_CLICK_HIT_PX / rect.height) * plotHeight;
445
+ }, [plotHeight]);
446
+
447
+ const selectAt = useCallback((point: WorldMapPoint, element: HTMLElement) => {
448
+ const hit = hitRadius(element);
449
+ if (props.overlay) {
450
+ const geoHit = hitTestGeo(props.overlay, point, project, Math.max(1.2, hit * 0.8));
451
+ if (geoHit?.kind === "cluster") {
452
+ setViewport((current) => zoomWorldMapViewport(current, props.width, plotHeight, project(geoHit.cluster.lon, geoHit.cluster.lat), 2.5, 1, maxZoom));
453
+ return;
454
+ }
455
+ if (geoHit?.kind === "feature") {
456
+ props.onSelectGeo?.(geoHit);
457
+ return;
458
+ }
459
+ }
329
460
  const cluster = closestWorldVenueCluster(clusters, point.x, point.y, Math.max(1.6, hit));
330
461
  if (cluster) props.onSelect(clusterVenue(cluster, props.selectedMic));
331
- }, [clusters, plotHeight, props]);
462
+ }, [clusters, hitRadius, maxZoom, plotHeight, project, props]);
463
+
464
+ const onViewChange = props.onViewChange;
465
+ const reportedViewRef = useRef<GeoView | null>(null);
466
+ useEffect(() => {
467
+ if (!onViewChange) return;
468
+ const next = geoViewForViewport(viewport, props.width, plotHeight);
469
+ if (reportedViewRef.current && sameGeoView(reportedViewRef.current, next)) return;
470
+ reportedViewRef.current = next;
471
+ onViewChange(next);
472
+ }, [onViewChange, plotHeight, props.width, viewport]);
473
+
474
+ // A new focus (an entity picked in the table) centres the map on it.
475
+ const focusKey = props.focus?.key ?? null;
476
+ useEffect(() => {
477
+ const focus = props.focus;
478
+ if (!focus) return;
479
+ setViewport((current) => {
480
+ const zoom = Math.max(current.zoom, focus.zoom ?? 1);
481
+ if (zoom <= 1) return current;
482
+ return clampWorldMapViewport({ zoom, centerLongitude: focus.longitude, centerLatitude: focus.latitude }, props.width, plotHeight, 1, maxZoom);
483
+ });
484
+ }, [focusKey]);
332
485
 
333
486
  const selectedVenue = props.venues.find((venue) => venue.mic === props.selectedMic) ?? null;
334
487
  const selectedVenueRef = useRef(selectedVenue);
@@ -346,9 +499,9 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
346
499
  const venue = selectedVenueRef.current;
347
500
  const anchor = venue ? projectWorldPoint(venue.longitude, venue.latitude, props.width, plotHeight, 1, current) : null;
348
501
  const point = anchor && insidePlot(anchor) ? anchor : { x: props.width / 2, y: plotHeight / 2 };
349
- return zoomWorldMapViewport(current, props.width, plotHeight, point, factor);
502
+ return zoomWorldMapViewport(current, props.width, plotHeight, point, factor, 1, maxZoom);
350
503
  });
351
- }, [insidePlot, plotHeight, props.width]);
504
+ }, [insidePlot, maxZoom, plotHeight, props.width]);
352
505
 
353
506
  // A venue picked in the table pans a zoomed map to it when it is out of view.
354
507
  // Only a new selection moves the map, so a drag away from it stays put.
@@ -359,11 +512,11 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
359
512
  if (current.zoom <= 1) return current;
360
513
  const point = projectWorldPoint(venue.longitude, venue.latitude, props.width, plotHeight, 1, current);
361
514
  if (insidePlot(point)) return current;
362
- return clampWorldMapViewport({ ...current, centerLongitude: venue.longitude, centerLatitude: venue.latitude }, props.width, plotHeight);
515
+ return clampWorldMapViewport({ ...current, centerLongitude: venue.longitude, centerLatitude: venue.latitude }, props.width, plotHeight, 1, maxZoom);
363
516
  });
364
517
  }, [props.selectedMic]);
365
518
 
366
- const atMaxZoom = viewport.zoom >= MAX_WORLD_MAP_ZOOM;
519
+ const atMaxZoom = viewport.zoom >= maxZoom;
367
520
  usePaneFooter("world-venue-map:zoom", () => ({
368
521
  hints: [
369
522
  { id: "zoom-in", key: "+", label: " zoom in", title: "Zoom In", onPress: () => zoomBy(MAP_DOUBLE_CLICK_ZOOM), disabled: atMaxZoom },
@@ -384,11 +537,28 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
384
537
  event.stopPropagation();
385
538
  const point = clientToMapPoint(event, element, props.width, plotHeight);
386
539
  const factor = wheelZoomFactor(event);
387
- setViewport((current) => zoomWorldMapViewport(current, props.width, plotHeight, point, factor));
540
+ setViewport((current) => zoomWorldMapViewport(current, props.width, plotHeight, point, factor, 1, maxZoom));
388
541
  };
389
542
  element.addEventListener("wheel", onWheel, { passive: false });
390
543
  return () => element.removeEventListener("wheel", onWheel);
391
- }, [plotHeight, props.width]);
544
+ }, [maxZoom, plotHeight, props.width]);
545
+
546
+ useEffect(() => () => {
547
+ if (hoverFrame.current !== null) cancelAnimationFrame(hoverFrame.current);
548
+ }, []);
549
+ // What sits under the pointer, at most once a frame: its name shows beside it.
550
+ const trackHover = (clientX: number, clientY: number, element: HTMLElement) => {
551
+ if (!props.overlay) return;
552
+ if (hoverFrame.current !== null) cancelAnimationFrame(hoverFrame.current);
553
+ hoverFrame.current = requestAnimationFrame(() => {
554
+ hoverFrame.current = null;
555
+ if (dragRef.current?.moved || !props.overlay) return;
556
+ const point = clientToMapPoint({ clientX, clientY }, element, props.width, plotHeight);
557
+ const hit = hitTestGeo(props.overlay, point, project, Math.max(1.2, hitRadius(element) * 0.8));
558
+ const rect = element.getBoundingClientRect();
559
+ setHover(hit ? { hit, x: clientX - rect.left, y: clientY - rect.top } : null);
560
+ });
561
+ };
392
562
 
393
563
  const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {
394
564
  const drag = dragRef.current;
@@ -401,6 +571,9 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
401
571
  if (!drag.moved) selectAt(clientToMapPoint(event, event.currentTarget, props.width, plotHeight), event.currentTarget);
402
572
  };
403
573
 
574
+ const hovered = hover && !dragging ? hover : null;
575
+ const tooltip = hovered && props.overlay ? hoverLabel(hovered.hit, props.overlay) : null;
576
+
404
577
  return (
405
578
  <Box width={props.width} height={props.height} overflow="hidden">
406
579
  <div
@@ -424,7 +597,10 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
424
597
  }}
425
598
  onPointerMove={(event) => {
426
599
  const drag = dragRef.current;
427
- if (!drag || drag.pointerId !== event.pointerId) return;
600
+ if (!drag || drag.pointerId !== event.pointerId) {
601
+ if (event.pointerType !== "touch") trackHover(event.clientX, event.clientY, event.currentTarget);
602
+ return;
603
+ }
428
604
  const travel = Math.hypot(event.clientX - drag.originX, event.clientY - drag.originY);
429
605
  if (!drag.moved && travel < MAP_PAN_THRESHOLD_PX) return;
430
606
  if (viewportRef.current.zoom <= 1) return;
@@ -439,13 +615,14 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
439
615
  drag.lastX = event.clientX;
440
616
  drag.lastY = event.clientY;
441
617
  setDragging(true);
442
- setViewport((current) => panWorldMapViewport(current, props.width, plotHeight, delta.x, delta.y));
618
+ setViewport((current) => panWorldMapViewport(current, props.width, plotHeight, delta.x, delta.y, 1, maxZoom));
443
619
  }}
444
620
  onPointerUp={endDrag}
445
621
  onPointerCancel={endDrag}
622
+ onPointerLeave={() => setHover(null)}
446
623
  onDoubleClick={(event) => {
447
624
  const point = clientToMapPoint(event, event.currentTarget, props.width, plotHeight);
448
- setViewport((current) => zoomWorldMapViewport(current, props.width, plotHeight, point, MAP_DOUBLE_CLICK_ZOOM));
625
+ setViewport((current) => zoomWorldMapViewport(current, props.width, plotHeight, point, MAP_DOUBLE_CLICK_ZOOM, 1, maxZoom));
449
626
  }}
450
627
  style={{
451
628
  position: "relative",
@@ -456,7 +633,7 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
456
633
  overflow: "hidden",
457
634
  touchAction: "none",
458
635
  userSelect: "none",
459
- cursor: dragging ? "grabbing" : zoomed ? "grab" : "pointer",
636
+ cursor: dragging ? "grabbing" : hovered ? "pointer" : zoomed ? "grab" : "default",
460
637
  background: colors.bg,
461
638
  }}
462
639
  >
@@ -467,17 +644,22 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
467
644
  aria-hidden="true"
468
645
  style={{ display: "block", background: colors.bg, pointerEvents: "none" }}
469
646
  >
470
- {outlinePaths.map((path, index) => (
471
- <path
472
- key={index}
473
- d={path}
474
- fill="none"
475
- stroke={colors.textDim}
476
- strokeOpacity="0.64"
477
- strokeWidth="0.55"
478
- vectorEffect="non-scaling-stroke"
647
+ <BasemapLayer matrix={matrix} view={view} pxPerDegree={pxPerDegree} palette={palette} />
648
+ {props.overlay ? (
649
+ <GeoSvgLayers
650
+ overlay={props.overlay}
651
+ matrix={matrix}
652
+ project={project}
653
+ view={view}
654
+ unitPx={unitPx}
655
+ mapWidthPx={surfaceSize?.width ?? props.width * 8}
656
+ background={colors.bg}
657
+ selectedColor={colors.selectedText}
658
+ textColor={colors.textBright}
659
+ tones={tones}
660
+ hovered={hovered?.hit ?? null}
479
661
  />
480
- ))}
662
+ ) : null}
481
663
  {clusters.map((cluster) => {
482
664
  const selected = isSelectedCluster(cluster, props.selectedMic);
483
665
  const venue = clusterVenue(cluster, props.selectedMic);
@@ -509,11 +691,165 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
509
691
  );
510
692
  })}
511
693
  </svg>
694
+ {tooltip && hovered ? (
695
+ <MapTooltip
696
+ title={tooltip.title}
697
+ detail={tooltip.detail}
698
+ x={hovered.x}
699
+ y={hovered.y}
700
+ width={surfaceSize?.width ?? 0}
701
+ />
702
+ ) : null}
703
+ {props.overlay ? <GeoLegend overlay={props.overlay} venues={props.venues.length > 0} tones={tones} /> : null}
512
704
  </div>
513
705
  </Box>
514
706
  );
515
707
  }
516
708
 
709
+ function withAlpha(hex: string, alpha: number): string {
710
+ const [r, g, b] = parseRgb(hex);
711
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
712
+ }
713
+
714
+ /** The name under the pointer, beside it and inside the map. */
715
+ function MapTooltip({ title, detail, x, y, width }: { title: string; detail: string | null; x: number; y: number; width: number }) {
716
+ const colors = useThemeColors();
717
+ const flip = width > 0 && x > width - 200;
718
+ return createElement(
719
+ "div",
720
+ {
721
+ "data-gloom-role": "geo-hover",
722
+ style: {
723
+ position: "absolute",
724
+ left: flip ? undefined : x + 14,
725
+ right: flip ? width - x + 14 : undefined,
726
+ top: Math.max(4, y - 30),
727
+ padding: "3px 8px",
728
+ background: colors.panel,
729
+ border: `1px solid ${colors.border}`,
730
+ borderRadius: 4,
731
+ color: colors.textBright,
732
+ fontSize: "0.85em",
733
+ lineHeight: 1.35,
734
+ whiteSpace: "nowrap",
735
+ pointerEvents: "none",
736
+ },
737
+ },
738
+ title,
739
+ detail ? createElement("span", { style: { color: colors.textDim, marginLeft: 8 } }, detail) : null,
740
+ );
741
+ }
742
+
743
+ interface LegendItem {
744
+ id: string;
745
+ name: string;
746
+ color: string;
747
+ symbol: MapSymbolId | null;
748
+ line?: boolean;
749
+ }
750
+
751
+ /** Which icon and colour is which layer, once there is more than one on the map. Ships open their class key on hover. */
752
+ function GeoLegend({ overlay, venues, tones }: { overlay: GeoMapOverlay; venues: boolean; tones: SymbolTones }) {
753
+ const colors = useThemeColors();
754
+ const [classesOpen, setClassesOpen] = useState(false);
755
+ const items: LegendItem[] = [
756
+ ...(venues ? [{ id: "venues", name: "Venues", color: colors.positive, symbol: "dot" as const }] : []),
757
+ // Only what is drawn: a layer below its zoom or unable to serve has nothing to explain.
758
+ ...overlay.layers
759
+ .filter((layer) => layer.features.length > 0 || layer.clusters.length > 0)
760
+ .map((layer) => ({
761
+ id: layer.id,
762
+ name: layer.name,
763
+ color: layer.color,
764
+ symbol: layer.geometry === "point" || layer.geometry === "area" ? layerSymbol(layer.id, layer.features[0]) : null,
765
+ line: layer.geometry === "line",
766
+ })),
767
+ ];
768
+ if (items.length < 2) return null;
769
+ const ships = overlay.layers.find((layer) => usesClassTones(layer.id));
770
+ return createElement(
771
+ "div",
772
+ {
773
+ "data-gloom-role": "geo-legend",
774
+ style: {
775
+ position: "absolute",
776
+ left: 6,
777
+ bottom: 6,
778
+ display: "flex",
779
+ flexDirection: "column",
780
+ alignItems: "flex-start",
781
+ gap: 6,
782
+ color: colors.textDim,
783
+ fontSize: "0.85em",
784
+ pointerEvents: "none",
785
+ },
786
+ },
787
+ classesOpen && ships ? createElement(
788
+ "div",
789
+ {
790
+ "data-gloom-role": "geo-legend-classes",
791
+ style: {
792
+ display: "grid",
793
+ gridTemplateColumns: "14px auto",
794
+ alignItems: "center",
795
+ gap: "3px 8px",
796
+ padding: "6px 9px",
797
+ background: colors.panel,
798
+ border: `1px solid ${colors.border}`,
799
+ borderRadius: 4,
800
+ },
801
+ },
802
+ ...SHIP_CLASS_KEY.flatMap(({ id, label, tone, symbol }) => [
803
+ createElement("span", { key: `${id}:icon` }, <MapIconSwatch symbol={symbol} color={toneColor(ships, tone, tones)} background={colors.bg} />),
804
+ createElement("span", { key: `${id}:label` }, label),
805
+ ]),
806
+ ) : null,
807
+ createElement(
808
+ "div",
809
+ // A soft backing so the names read over coastlines and markers.
810
+ { style: { display: "flex", flexWrap: "wrap", gap: "4px 14px", padding: "3px 6px", borderRadius: 4, background: withAlpha(colors.bg, 0.78) } },
811
+ ...items.map((item) => createElement(
812
+ "span",
813
+ {
814
+ key: item.id,
815
+ style: {
816
+ display: "inline-flex",
817
+ alignItems: "center",
818
+ gap: 6,
819
+ ...(item.id === ships?.id ? { pointerEvents: "auto", cursor: "default" } : {}),
820
+ },
821
+ ...(item.id === ships?.id ? {
822
+ // A mouse opens the key on hover; a touch toggles it.
823
+ onPointerEnter: (event: ReactPointerEvent) => {
824
+ if (event.pointerType === "mouse") setClassesOpen(true);
825
+ },
826
+ onPointerLeave: (event: ReactPointerEvent) => {
827
+ if (event.pointerType === "mouse") setClassesOpen(false);
828
+ },
829
+ onPointerDown: (event: ReactPointerEvent) => {
830
+ event.stopPropagation();
831
+ if (event.pointerType !== "mouse") setClassesOpen((open) => !open);
832
+ },
833
+ } : {}),
834
+ },
835
+ item.line
836
+ ? createElement("span", { style: { width: 12, height: 2, borderRadius: 1, background: item.color } })
837
+ : <MapIconSwatch symbol={item.symbol ?? "dot"} color={item.color} background={colors.bg} />,
838
+ item.name,
839
+ )),
840
+ ),
841
+ );
842
+ }
843
+
844
+ const SHIP_CLASS_KEY: { id: string; label: string; tone: SymbolTone; symbol: MapSymbolId }[] = [
845
+ { id: "tanker", label: "Tanker", tone: "tanker", symbol: "ship" },
846
+ { id: "cargo", label: "Cargo", tone: "layer", symbol: "ship" },
847
+ { id: "passenger", label: "Passenger", tone: "passenger", symbol: "ship" },
848
+ { id: "fishing", label: "Fishing", tone: "fishing", symbol: "ship-small" },
849
+ { id: "service", label: "Tug and special", tone: "service", symbol: "ship-small" },
850
+ { id: "other", label: "Other", tone: "other", symbol: "ship-other" },
851
+ ];
852
+
517
853
  export function WorldVenueMap(props: WorldVenueMapProps) {
518
854
  return useUiCapabilities().nativePaneChrome === true
519
855
  ? <DesktopWorldVenueMap {...props} />
@@ -91,8 +91,9 @@ export function clampWorldMapViewport(
91
91
  width: number,
92
92
  height: number,
93
93
  yUnitAspect = 1,
94
+ maxZoom = MAX_WORLD_MAP_ZOOM,
94
95
  ): WorldMapViewport {
95
- const zoom = clamp(viewport.zoom, 1, MAX_WORLD_MAP_ZOOM);
96
+ const zoom = clamp(viewport.zoom, 1, maxZoom);
96
97
  if (zoom <= 1) return DEFAULT_WORLD_MAP_VIEWPORT;
97
98
 
98
99
  const { availableWidth, effectiveHeight, latitudeSpan } = mapExtent(width, height, yUnitAspect);
@@ -122,8 +123,9 @@ export function zoomWorldMapViewport(
122
123
  point: WorldMapPoint,
123
124
  zoomFactor: number,
124
125
  yUnitAspect = 1,
126
+ maxZoom = MAX_WORLD_MAP_ZOOM,
125
127
  ): WorldMapViewport {
126
- const nextZoom = clamp(viewport.zoom * zoomFactor, 1, MAX_WORLD_MAP_ZOOM);
128
+ const nextZoom = clamp(viewport.zoom * zoomFactor, 1, maxZoom);
127
129
  if (nextZoom <= 1) return DEFAULT_WORLD_MAP_VIEWPORT;
128
130
 
129
131
  const geographic = unprojectWorldPoint(point.x, point.y, width, height, yUnitAspect, viewport);
@@ -133,7 +135,7 @@ export function zoomWorldMapViewport(
133
135
  zoom: nextZoom,
134
136
  centerLongitude: geographic.longitude - (point.x - availableWidth / 2) / scale,
135
137
  centerLatitude: geographic.latitude + (point.y * unitAspect - effectiveHeight / 2) / scale,
136
- }, width, height, yUnitAspect);
138
+ }, width, height, yUnitAspect, maxZoom);
137
139
  }
138
140
 
139
141
  export function panWorldMapViewport(
@@ -143,6 +145,7 @@ export function panWorldMapViewport(
143
145
  deltaX: number,
144
146
  deltaY: number,
145
147
  yUnitAspect = 1,
148
+ maxZoom = MAX_WORLD_MAP_ZOOM,
146
149
  ): WorldMapViewport {
147
150
  if (viewport.zoom <= 1) return DEFAULT_WORLD_MAP_VIEWPORT;
148
151
  const scale = worldMapScale(width, height, yUnitAspect, viewport);
@@ -151,7 +154,7 @@ export function panWorldMapViewport(
151
154
  zoom: viewport.zoom,
152
155
  centerLongitude: viewport.centerLongitude - deltaX / scale,
153
156
  centerLatitude: viewport.centerLatitude + (deltaY * unitAspect) / scale,
154
- }, width, height, yUnitAspect);
157
+ }, width, height, yUnitAspect, maxZoom);
155
158
  }
156
159
 
157
160
  export function clusterWorldVenues(