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
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Which geo layers a map shows and how they read: the layer picker, presets,
3
+ * caps, the view a request covers, and plain-value formatting shared by the
4
+ * pane, the terminal map and the headless report.
5
+ */
6
+ import {
7
+ GEO_MAX_LIVE_LAYERS,
8
+ type GeoBbox,
9
+ type GeoColumn,
10
+ type GeoColumnFormat,
11
+ type GeoFeature,
12
+ type GeoLayerInfo,
13
+ type GeoPropValue,
14
+ type GeoTickerLink,
15
+ } from "../../../api-client/geo";
16
+ import type { PaneSettingField, PaneSettingsDef } from "../../../types/plugin";
17
+ import { publicTickerKey } from "../../../utils/exchanges";
18
+ import { unprojectWorldPoint, type WorldMapViewport } from "./model";
19
+
20
+ export const LAYERS_SETTING_KEY = "layers";
21
+ export const VENUES_SETTING_KEY = "venues";
22
+ /** Layers that refresh on their own (ships, flights) are the expensive ones. */
23
+ const MAX_LIVE_LAYERS = GEO_MAX_LIVE_LAYERS;
24
+ const MAX_DATA_LAYERS = 4;
25
+ /** Drawn per layer; the server caps an answer at 1500 and clusters above it. */
26
+ export const MAX_RENDERED_FEATURES = 2000;
27
+ export const MAX_RENDERED_CLUSTERS = 600;
28
+
29
+ const GROUP_PREFIX = "layers:";
30
+
31
+ const GROUP_TITLES: Record<string, string> = {
32
+ ships: "Ships",
33
+ air: "Air",
34
+ energy: "Energy",
35
+ ports: "Ports",
36
+ infrastructure: "Infrastructure",
37
+ };
38
+
39
+ export function groupTitle(group: string): string {
40
+ return GROUP_TITLES[group] ?? (group ? group.charAt(0).toUpperCase() + group.slice(1) : "Other");
41
+ }
42
+
43
+ /** Layer ids or group names, from a setting (array) or typed text ("ships", "ports, airports"). */
44
+ export function parseLayerTokens(value: unknown): string[] {
45
+ const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(/[\s,;+]+/) : [];
46
+ const tokens: string[] = [];
47
+ for (const entry of raw) {
48
+ if (typeof entry !== "string") continue;
49
+ const token = entry.trim().toLowerCase();
50
+ if (token && token !== "venues" && !tokens.includes(token)) tokens.push(token);
51
+ }
52
+ return tokens;
53
+ }
54
+
55
+ function isLive(layer: GeoLayerInfo): boolean {
56
+ return layer.cadence === "live";
57
+ }
58
+
59
+ /**
60
+ * Keeps the newest choices within the caps: at most two live layers and four
61
+ * in all. `ordered` is oldest first, so the earliest pick gives way.
62
+ */
63
+ function capLayerSelection(ordered: readonly GeoLayerInfo[]): GeoLayerInfo[] {
64
+ const kept: GeoLayerInfo[] = [];
65
+ let live = 0;
66
+ for (let index = ordered.length - 1; index >= 0; index -= 1) {
67
+ const layer = ordered[index]!;
68
+ if (kept.length >= MAX_DATA_LAYERS) break;
69
+ if (isLive(layer)) {
70
+ if (live >= MAX_LIVE_LAYERS) continue;
71
+ live += 1;
72
+ }
73
+ kept.unshift(layer);
74
+ }
75
+ return kept;
76
+ }
77
+
78
+ /** Expands groups to their layers, drops what the server does not list, and applies the caps. */
79
+ export function resolveActiveLayers(tokens: readonly string[], layers: readonly GeoLayerInfo[] | null | undefined): GeoLayerInfo[] {
80
+ if (!layers?.length || !tokens.length) return [];
81
+ const ordered: GeoLayerInfo[] = [];
82
+ const add = (layer: GeoLayerInfo) => {
83
+ if (!ordered.includes(layer)) ordered.push(layer);
84
+ };
85
+ for (const token of tokens) {
86
+ const layer = layers.find((entry) => entry.id.toLowerCase() === token);
87
+ if (layer) {
88
+ add(layer);
89
+ continue;
90
+ }
91
+ for (const member of layers.filter((entry) => String(entry.group).toLowerCase() === token)) add(member);
92
+ }
93
+ return capLayerSelection(ordered);
94
+ }
95
+
96
+ export interface MapPreset {
97
+ layers: string[];
98
+ venues: boolean;
99
+ }
100
+
101
+ /** `MAP ships` and friends: the typed words become layer tokens, resolved once the catalog is known. */
102
+ export function parseMapPreset(arg: string | null | undefined): MapPreset | null {
103
+ const tokens = parseLayerTokens(arg ?? "");
104
+ if (!tokens.length) return null;
105
+ return { layers: tokens, venues: /\bvenues\b/i.test(arg ?? "") };
106
+ }
107
+
108
+ export const MAP_PRESET_OPTIONS = [
109
+ { value: "ships", label: "Ships and chokepoints" },
110
+ { value: "ports", label: "Ports" },
111
+ { value: "energy", label: "Pipelines, fields and terminals" },
112
+ { value: "air", label: "Airports" },
113
+ ] as const;
114
+
115
+ function shortDate(iso: string): string | null {
116
+ const time = Date.parse(iso);
117
+ if (!Number.isFinite(time)) return null;
118
+ return new Date(time).toLocaleDateString("en-GB", { day: "numeric", month: "short", timeZone: "UTC" });
119
+ }
120
+
121
+ /** Status and cadence in plain words, for the layer picker. */
122
+ function describeLayer(layer: GeoLayerInfo): string {
123
+ if (layer.status === "unavailable") return layer.statusNote ? `Unavailable: ${layer.statusNote}` : "Unavailable";
124
+ const cadence = layer.cadence === "live"
125
+ ? layer.refreshSeconds ? `Live, every ${formatSeconds(layer.refreshSeconds)}` : "Live"
126
+ : layer.cadence === "daily"
127
+ ? layer.asOf && shortDate(layer.asOf) ? `Daily, as of ${shortDate(layer.asOf)}` : "Daily"
128
+ : "Reference";
129
+ const parts = [
130
+ ...(layer.access === "pro" ? ["Pro"] : []),
131
+ cadence,
132
+ ...(layer.status === "partial" ? [layer.statusNote ? `partial: ${layer.statusNote}` : "partial"] : []),
133
+ ];
134
+ return parts.join(" · ");
135
+ }
136
+
137
+ function formatSeconds(seconds: number): string {
138
+ if (seconds < 60) return `${Math.round(seconds)}s`;
139
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
140
+ return `${Math.round(seconds / 3600)}h`;
141
+ }
142
+
143
+ function groupsOf(layers: readonly GeoLayerInfo[]): string[] {
144
+ const groups: string[] = [];
145
+ for (const layer of layers) {
146
+ const group = String(layer.group || "other");
147
+ if (!groups.includes(group)) groups.push(group);
148
+ }
149
+ return groups;
150
+ }
151
+
152
+ export function readVenuesSetting(settings: Record<string, unknown> | undefined, activeLayers: number): boolean {
153
+ const value = settings?.[VENUES_SETTING_KEY];
154
+ if (typeof value === "boolean") return value;
155
+ // A preset opens on its layers; a plain MAP stays the venue map.
156
+ return activeLayers === 0;
157
+ }
158
+
159
+ /**
160
+ * The layer picker in the pane settings: venues, then one list per group with
161
+ * each layer's cadence and status. Without a catalog there is nothing to add,
162
+ * so a server without geo layers shows the settings it always had.
163
+ */
164
+ export function buildMapSettingsDef(
165
+ settings: Record<string, unknown>,
166
+ layers: readonly GeoLayerInfo[] | null | undefined,
167
+ ): PaneSettingsDef | undefined {
168
+ if (!layers?.length) return undefined;
169
+ const active = resolveActiveLayers(parseLayerTokens(settings[LAYERS_SETTING_KEY]), layers);
170
+ const values: Record<string, unknown> = { [VENUES_SETTING_KEY]: readVenuesSetting(settings, active.length) };
171
+ const fields: PaneSettingField[] = [{
172
+ key: VENUES_SETTING_KEY,
173
+ label: "Venues",
174
+ description: "Trading venues with open or closed status.",
175
+ type: "toggle",
176
+ }];
177
+ for (const group of groupsOf(layers)) {
178
+ const members = layers.filter((layer) => String(layer.group || "other") === group);
179
+ const key = `${GROUP_PREFIX}${group}`;
180
+ values[key] = active.filter((layer) => members.includes(layer)).map((layer) => layer.id);
181
+ fields.push({
182
+ key,
183
+ label: groupTitle(group),
184
+ type: "multi-select",
185
+ options: members.map((layer) => ({ value: layer.id, label: layer.name, description: describeLayer(layer) })),
186
+ });
187
+ }
188
+ return {
189
+ title: "Map Layers",
190
+ values,
191
+ fields,
192
+ applyValue: (current, field, value) => applyMapSetting(current, field.key, value, layers),
193
+ };
194
+ }
195
+
196
+ export function applyMapSetting(
197
+ settings: Record<string, unknown>,
198
+ key: string,
199
+ value: unknown,
200
+ layers: readonly GeoLayerInfo[],
201
+ ): Record<string, unknown> {
202
+ if (key === VENUES_SETTING_KEY) return { ...settings, [VENUES_SETTING_KEY]: value === true };
203
+ if (!key.startsWith(GROUP_PREFIX)) return { ...settings, [key]: value };
204
+ const group = key.slice(GROUP_PREFIX.length);
205
+ const members = new Set(layers.filter((layer) => String(layer.group || "other") === group).map((layer) => layer.id));
206
+ const picked = Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string" && members.has(entry)) : [];
207
+ const before = resolveActiveLayers(parseLayerTokens(settings[LAYERS_SETTING_KEY]), layers);
208
+ // Other groups keep their order; this group's picks follow, newest last, so the caps drop the oldest.
209
+ const kept = before.filter((layer) => !members.has(layer.id) || picked.includes(layer.id));
210
+ const added = picked.filter((id) => !kept.some((layer) => layer.id === id))
211
+ .map((id) => layers.find((layer) => layer.id === id)!)
212
+ .filter(Boolean);
213
+ const next = capLayerSelection([...kept, ...added]).map((layer) => layer.id);
214
+ const venues = readVenuesSetting(settings, before.length);
215
+ return { ...settings, [LAYERS_SETTING_KEY]: next, [VENUES_SETTING_KEY]: venues };
216
+ }
217
+
218
+ export interface GeoView {
219
+ bbox: GeoBbox;
220
+ /** 0 for the whole world, one more for each doubling, as the server reads it. */
221
+ zoom: number;
222
+ }
223
+
224
+ export const WORLD_GEO_VIEW: GeoView = { bbox: [-180, -90, 180, 90], zoom: 0 };
225
+
226
+ /** The map shows all 360 degrees at its widest, so each doubling of its zoom is one level. */
227
+ function geoZoomLevel(viewport: WorldMapViewport): number {
228
+ return Math.log2(Math.max(1, viewport.zoom));
229
+ }
230
+
231
+ /** What a viewport covers, rounded so small pans reuse the same request. */
232
+ export function geoViewForViewport(
233
+ viewport: WorldMapViewport,
234
+ width: number,
235
+ height: number,
236
+ yUnitAspect = 1,
237
+ ): GeoView {
238
+ if (viewport.zoom <= 1 || width <= 1 || height <= 1) return WORLD_GEO_VIEW;
239
+ const topLeft = unprojectWorldPoint(0, 0, width, height, yUnitAspect, viewport);
240
+ const bottomRight = unprojectWorldPoint(width - 1, height - 1, width, height, yUnitAspect, viewport);
241
+ const step = viewport.zoom >= 6 ? 0.1 : 0.5;
242
+ const floor = (value: number) => Math.floor(value / step) * step;
243
+ const ceil = (value: number) => Math.ceil(value / step) * step;
244
+ const round = (value: number) => Number(value.toFixed(2));
245
+ return {
246
+ bbox: [
247
+ round(Math.max(-180, floor(topLeft.longitude))),
248
+ round(Math.max(-90, floor(bottomRight.latitude))),
249
+ round(Math.min(180, ceil(bottomRight.longitude))),
250
+ round(Math.min(90, ceil(topLeft.latitude))),
251
+ ],
252
+ zoom: Number(geoZoomLevel(viewport).toFixed(1)),
253
+ };
254
+ }
255
+
256
+ export function geoViewKey(view: GeoView): string {
257
+ return `${view.bbox.join(",")}@${Math.floor(view.zoom)}`;
258
+ }
259
+
260
+ export function sameGeoView(left: GeoView, right: GeoView): boolean {
261
+ return geoViewKey(left) === geoViewKey(right);
262
+ }
263
+
264
+ /** The point a feature is placed, selected and centred at. */
265
+ export function featureAnchor(feature: Pick<GeoFeature, "geometry">): [number, number] {
266
+ const geometry = feature.geometry;
267
+ if (geometry.type === "Point") return geometry.coordinates;
268
+ const points = geometry.type === "LineString" ? geometry.coordinates : geometry.coordinates.flat();
269
+ if (!points.length) return [0, 0];
270
+ if (geometry.type === "LineString") return points[Math.floor(points.length / 2)]!;
271
+ const lon = points.reduce((sum, point) => sum + point[0], 0) / points.length;
272
+ const lat = points.reduce((sum, point) => sum + point[1], 0) / points.length;
273
+ return [lon, lat];
274
+ }
275
+
276
+ /** Lines of a feature as coordinate runs; areas give their rings. */
277
+ export function featureLines(feature: Pick<GeoFeature, "geometry">): [number, number][][] {
278
+ const geometry = feature.geometry;
279
+ switch (geometry.type) {
280
+ case "Point": return [];
281
+ case "LineString": return [geometry.coordinates];
282
+ case "MultiLineString": return geometry.coordinates;
283
+ case "Polygon": return geometry.coordinates;
284
+ }
285
+ }
286
+
287
+ function groupDigits(value: number, decimals: number): string {
288
+ return value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
289
+ }
290
+
291
+ function relativeAge(iso: string, now: number): string | null {
292
+ const time = Date.parse(iso);
293
+ if (!Number.isFinite(time)) return null;
294
+ const seconds = Math.max(0, Math.round((now - time) / 1000));
295
+ if (seconds < 60) return `${seconds}s ago`;
296
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m ago`;
297
+ if (seconds < 36 * 3600) return `${Math.round(seconds / 3600)}h ago`;
298
+ return shortDate(iso);
299
+ }
300
+
301
+ /** A column that reads as a change (a sign and a colour), not a level such as a share. */
302
+ export function isChangeColumn(column: Pick<GeoColumn, "key" | "label" | "format">): boolean {
303
+ return column.format === "percent1" && /chg|change|delta/i.test(`${column.key} ${column.label}`);
304
+ }
305
+
306
+ /** A prop as the table shows it; `signed` puts a + on rises. */
307
+ export function formatGeoValue(value: GeoPropValue | undefined, format: GeoColumnFormat | undefined, now = Date.now(), signed = false): string {
308
+ if (value === null || value === undefined || value === "") return "";
309
+ if (typeof value === "boolean") return value ? "Yes" : "No";
310
+ if (typeof value === "number") {
311
+ switch (format) {
312
+ case "int": return groupDigits(Math.round(value), 0);
313
+ case "decimal1": return groupDigits(value, 1);
314
+ // Percentage points: 12.3 reads 12.3%.
315
+ case "percent1": return `${signed && value > 0 ? "+" : ""}${value.toFixed(1)}%`;
316
+ case "knots": return `${value.toFixed(1)} kn`;
317
+ case "km": return `${groupDigits(Math.round(value), 0)} km`;
318
+ // Identifiers (an MMSI, an IMO number) read as written.
319
+ case "text": return String(value);
320
+ default: return Number.isInteger(value) ? groupDigits(value, 0) : String(value);
321
+ }
322
+ }
323
+ if (format === "datetime") return relativeAge(value, now) ?? value;
324
+ // Class words arrive lower case ("tanker"); a table reads them capitalised.
325
+ return value.charAt(0).toUpperCase() + value.slice(1);
326
+ }
327
+
328
+ /** The same prop as exports and reports write it: a bare number or the source text. */
329
+ export function plainGeoValue(value: GeoPropValue | undefined): number | string | null {
330
+ if (value === null || value === undefined || value === "") return null;
331
+ if (typeof value === "boolean") return value ? "yes" : "no";
332
+ return value;
333
+ }
334
+
335
+ export function tickerLinkKey(link: GeoTickerLink): string {
336
+ return publicTickerKey(link.symbol, link.exchange);
337
+ }
338
+
339
+ /** The ticker column: the first link, and a count of the rest. */
340
+ export function formatTickerLinks(links: readonly GeoTickerLink[] | undefined): string {
341
+ if (!links?.length) return "";
342
+ const first = links[0]!.symbol;
343
+ return links.length > 1 ? `${first} +${links.length - 1}` : first;
344
+ }
345
+
346
+ export function columnValue(row: { label: string; props: Record<string, GeoPropValue> }, column: GeoColumn): GeoPropValue {
347
+ return column.key === "label" ? row.label : row.props[column.key] ?? null;
348
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * The map's icons: one shared set every point layer picks from by layer id,
3
+ * then by entity kind, and a plain dot for anything else, so a layer the
4
+ * server adds without an app release still draws. Each icon is a path in a
5
+ * 16-unit box centred on the feature and pointing north; the desktop and web
6
+ * draw it as SVG, the terminal uses its cell glyph.
7
+ */
8
+ import type { GeoFeature } from "../../../api-client/geo";
9
+
10
+ export type MapSymbolId =
11
+ | "ship"
12
+ | "ship-small"
13
+ | "ship-other"
14
+ | "ship-still"
15
+ | "port"
16
+ | "airport"
17
+ | "aircraft"
18
+ | "terminal"
19
+ | "field"
20
+ | "chokepoint"
21
+ | "dot";
22
+
23
+ export interface MapSymbol {
24
+ /** Path in a 16-unit box centred on the anchor, pointing north. */
25
+ d: string;
26
+ /** Solid shapes are filled; line art is stroked. */
27
+ paint: "fill" | "stroke";
28
+ /** Drawn size in pixels of the 16-unit box. */
29
+ size: number;
30
+ /** Few, large features (a strait, an LNG terminal) stay icons at any density. */
31
+ landmark?: boolean;
32
+ /** Degrees it is drawn turned when nothing gives it a heading: the airport sign's plane flies north-east. */
33
+ pose?: number;
34
+ /** What the terminal draws in the cell. */
35
+ cell: string;
36
+ }
37
+
38
+ const HULL = "M0 -8C2.4 -5.4 3.4 -2.6 3.4 0.6V6.4Q3.4 7.6 2.2 7.6H-2.2Q-3.4 7.6 -3.4 6.4V0.6C-3.4 -2.6 -2.4 -5.4 0 -8Z";
39
+
40
+ export const MAP_SYMBOLS: Record<MapSymbolId, MapSymbol> = {
41
+ ship: { d: HULL, paint: "fill", size: 13, cell: "■" },
42
+ "ship-small": { d: HULL, paint: "fill", size: 10, cell: "▪" },
43
+ "ship-other": { d: "M0 -7.5L5 6.5L0 3.6L-5 6.5Z", paint: "fill", size: 10, cell: "•" },
44
+ "ship-still": { d: "M0 -3.6A3.6 3.6 0 1 1 0 3.6A3.6 3.6 0 1 1 0 -3.6Z", paint: "fill", size: 10, cell: "•" },
45
+ port: {
46
+ d: "M0 -4.2V6.6M-3.2 -2.2H3.2M-5.6 1.6Q-5 6.4 0 6.6Q5 6.4 5.6 1.6M-5.6 1.6L-6.8 3.2M5.6 1.6L6.8 3.2M0 -7.6A1.7 1.7 0 1 1 0 -4.2A1.7 1.7 0 1 1 0 -7.6Z",
47
+ paint: "stroke",
48
+ size: 14,
49
+ cell: "□",
50
+ },
51
+ airport: {
52
+ d: "M0 -8C0.8 -8 1.2 -7.2 1.2 -6.2V-2.4L7.6 1.6V3.4L1.2 1.4V5L3.2 6.6V8L0 7.1L-3.2 8V6.6L-1.2 5V1.4L-7.6 3.4V1.6L-1.2 -2.4V-6.2C-1.2 -7.2 -0.8 -8 0 -8Z",
53
+ paint: "fill",
54
+ size: 13,
55
+ pose: 45,
56
+ cell: "×",
57
+ },
58
+ aircraft: {
59
+ d: "M0 -8C0.8 -8 1.2 -7.2 1.2 -6.2V-2.4L7.6 1.6V3.4L1.2 1.4V5L3.2 6.6V8L0 7.1L-3.2 8V6.6L-1.2 5V1.4L-7.6 3.4V1.6L-1.2 -2.4V-6.2C-1.2 -7.2 -0.8 -8 0 -8Z",
60
+ paint: "fill",
61
+ size: 13,
62
+ cell: "^",
63
+ },
64
+ // A storage tank: a dome on a base, split by a band.
65
+ terminal: {
66
+ d: "M-6.4 7.4V-0.6C-6.4 -7.4 6.4 -7.4 6.4 -0.6V7.4ZM-7 1.2H7V3H-7Z",
67
+ paint: "fill",
68
+ size: 14,
69
+ landmark: true,
70
+ cell: "▲",
71
+ },
72
+ // A drop of oil.
73
+ field: {
74
+ d: "M0 -8C2.6 -4.2 5.6 -0.6 5.6 2.6A5.6 5.6 0 0 1 -5.6 2.6C-5.6 -0.6 -2.6 -4.2 0 -8Z",
75
+ paint: "fill",
76
+ size: 12,
77
+ landmark: true,
78
+ cell: "",
79
+ },
80
+ // Two banks closing on a passage.
81
+ chokepoint: {
82
+ d: "M-7.6 -5.4L-0.9 0L-7.6 5.4ZM7.6 -5.4L0.9 0L7.6 5.4Z",
83
+ paint: "fill",
84
+ size: 13,
85
+ landmark: true,
86
+ cell: "◆",
87
+ },
88
+ dot: { d: "M0 -3.4A3.4 3.4 0 1 1 0 3.4A3.4 3.4 0 1 1 0 -3.4Z", paint: "fill", size: 11, cell: "•" },
89
+ };
90
+
91
+ /** Ship classes as the layer names them, lower case; anything else reads as other. */
92
+ export type ShipClass = "tanker" | "cargo" | "passenger" | "fishing" | "tug and special" | "other";
93
+
94
+ /** Colour roles; the map turns them into theme colours, the layer's own colour first. */
95
+ export type SymbolTone = "layer" | "tanker" | "passenger" | "fishing" | "service" | "other";
96
+
97
+ export interface FeatureSymbol {
98
+ id: MapSymbolId;
99
+ /** Degrees clockwise from north, or null for an icon that does not turn. */
100
+ rotation: number | null;
101
+ tone: SymbolTone;
102
+ /** Size relative to the icon's own, for a feature that matters more (a large airport). */
103
+ scale: number;
104
+ }
105
+
106
+ type SymbolRule = (feature: GeoFeature) => FeatureSymbol;
107
+
108
+ /** Making way at a knot or more, with a course to point along. */
109
+ const MOVING_KNOTS = 1;
110
+
111
+ function finite(value: unknown): number | null {
112
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
113
+ }
114
+
115
+ export function shipClass(feature: Pick<GeoFeature, "props">): ShipClass {
116
+ const value = typeof feature.props.class === "string" ? feature.props.class.trim().toLowerCase() : "";
117
+ switch (value) {
118
+ case "tanker":
119
+ case "cargo":
120
+ case "passenger":
121
+ case "fishing":
122
+ case "tug and special":
123
+ return value;
124
+ default:
125
+ return "other";
126
+ }
127
+ }
128
+
129
+ const SHIP_TONES: Record<ShipClass, SymbolTone> = {
130
+ tanker: "tanker",
131
+ cargo: "layer",
132
+ passenger: "passenger",
133
+ fishing: "fishing",
134
+ "tug and special": "service",
135
+ other: "other",
136
+ };
137
+
138
+ const ship: SymbolRule = (feature) => {
139
+ const kind = shipClass(feature);
140
+ const speed = finite(feature.props.speedKn);
141
+ const course = finite(feature.props.courseDeg);
142
+ const tone = SHIP_TONES[kind];
143
+ // AIS reports 360 for "no course".
144
+ if (speed === null || speed < MOVING_KNOTS || course === null || course < 0 || course >= 360) {
145
+ return { id: "ship-still", rotation: null, tone, scale: kind === "other" || kind === "tug and special" || kind === "fishing" ? 0.8 : 1 };
146
+ }
147
+ const id: MapSymbolId = kind === "other" ? "ship-other" : kind === "fishing" || kind === "tug and special" ? "ship-small" : "ship";
148
+ return { id, rotation: course, tone, scale: 1 };
149
+ };
150
+
151
+ const fixed = (id: MapSymbolId, scale = 1): FeatureSymbol => ({ id, rotation: null, tone: "layer", scale });
152
+
153
+ const RULES: Record<string, SymbolRule> = {
154
+ vessel: ship,
155
+ chokepoint: () => fixed("chokepoint"),
156
+ port: () => fixed("port"),
157
+ // The airport sign, smaller for a medium airport.
158
+ airport: (feature) => ({ id: "airport", rotation: null, tone: "layer", scale: String(feature.props.size).toLowerCase() === "large" ? 1 : 0.8 }),
159
+ flight: (feature) => {
160
+ const heading = finite(feature.props.headingDeg);
161
+ return { id: "aircraft", rotation: heading !== null && heading >= 0 && heading < 360 ? heading : null, tone: "layer", scale: 1 };
162
+ },
163
+ terminal: () => fixed("terminal"),
164
+ field: () => fixed("field"),
165
+ };
166
+
167
+ /** The layer registry: layer ids the app knows, then the kinds their entities carry. */
168
+ const LAYER_KINDS: Record<string, string> = {
169
+ vessels: "vessel",
170
+ chokepoints: "chokepoint",
171
+ ports: "port",
172
+ airports: "airport",
173
+ flights: "flight",
174
+ terminals: "terminal",
175
+ "oil-gas-fields": "field",
176
+ };
177
+
178
+ const DOT: FeatureSymbol = fixed("dot");
179
+
180
+ /** Whether a layer colours its features by class (ships), beyond its own colour. */
181
+ export function usesClassTones(layerId: string): boolean {
182
+ return LAYER_KINDS[layerId] === "vessel";
183
+ }
184
+
185
+ /** The icon a feature draws: by its layer, then its entity kind, else a dot. */
186
+ export function featureSymbol(layerId: string, feature: GeoFeature): FeatureSymbol {
187
+ const rule = RULES[LAYER_KINDS[layerId] ?? ""] ?? RULES[feature.entityKind];
188
+ return rule ? rule(feature) : DOT;
189
+ }
190
+
191
+ /** The icon that stands for a layer in the legend: what most of its features draw. */
192
+ export function layerSymbol(layerId: string, sample: GeoFeature | undefined): MapSymbolId {
193
+ const kind = LAYER_KINDS[layerId] ?? sample?.entityKind ?? "";
194
+ if (kind === "vessel") return "ship";
195
+ if (kind === "airport") return "airport";
196
+ if (kind === "flight") return "aircraft";
197
+ const rule = RULES[kind];
198
+ return rule && sample ? rule(sample).id : rule ? (kind as MapSymbolId) : "dot";
199
+ }
200
+
201
+ /**
202
+ * Past this many features in view a point layer draws plain dots: icons that
203
+ * dense overlap into clumps, and each pan step costs more. Measured on the
204
+ * ships layer over the South China Sea at 1440 px: 1,300 icons took 12 ms of
205
+ * main thread per pan step against 8 ms as dots, so 900 keeps a machine twice
206
+ * as slow inside a 60 fps frame.
207
+ */
208
+ export const ICON_MAX_VISIBLE = 900;
209
+
210
+ export type PointStyle = "icons" | "dots";
211
+
212
+ export function pointStyle(landmark: boolean, visible: number): PointStyle {
213
+ return landmark || visible <= ICON_MAX_VISIBLE ? "icons" : "dots";
214
+ }
215
+
216
+ /** Icons shrink as more share the view, and on a small map, so a crowded anchorage stays readable. */
217
+ export function iconDensityScale(visible: number, mapWidthPx = Infinity): number {
218
+ const crowd = visible <= 150 ? 1 : visible <= 450 ? 0.85 : 0.72;
219
+ return mapWidthPx < 560 ? crowd * 0.8 : crowd;
220
+ }