gloomberb 0.18.3 → 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (886) hide show
  1. package/README.md +8 -8
  2. package/README.zh-CN.md +11 -11
  3. package/bin/{gloomberb → gloom} +3 -1
  4. package/package.json +6 -2
  5. package/src/account/checkout-handoff.tsx +225 -0
  6. package/src/account/device-signin-dialog.tsx +2 -2
  7. package/src/account/device-signin.ts +2 -2
  8. package/src/account/upgrade-dialog.tsx +1 -1
  9. package/src/api-client/askg.ts +2 -2
  10. package/src/api-client/auth.ts +2 -2
  11. package/src/api-client/central-bank-rates.ts +6 -0
  12. package/src/api-client/collections.ts +5 -1
  13. package/src/api-client/cot.ts +2 -0
  14. package/src/api-client/crypto-etf-flows.ts +37 -0
  15. package/src/api-client/crypto-markets.ts +60 -0
  16. package/src/api-client/data.ts +84 -5
  17. package/src/api-client/doe.ts +15 -0
  18. package/src/api-client/equity-screener.ts +28 -0
  19. package/src/api-client/exchange-announcements.ts +38 -0
  20. package/src/api-client/fund-facts.ts +39 -0
  21. package/src/api-client/futures-curve.ts +9 -1
  22. package/src/api-client/hiring.ts +301 -0
  23. package/src/api-client/index.ts +22 -5
  24. package/src/api-client/market-discovery.ts +14 -0
  25. package/src/api-client/members.ts +6 -0
  26. package/src/api-client/mna.ts +6 -0
  27. package/src/api-client/paths.ts +18 -1
  28. package/src/api-client/request.ts +21 -4
  29. package/src/api-client/short-side-publications.ts +37 -0
  30. package/src/api-client/socket.ts +64 -4
  31. package/src/api-client/swap-curve.ts +35 -0
  32. package/src/api-client/themes.ts +7 -1
  33. package/src/api-client/types.ts +90 -2
  34. package/src/app/pane-runtime/plugin-bindings.ts +9 -2
  35. package/src/app/runtime/desktop-deeplink.ts +5 -5
  36. package/src/app/runtime/plugin-bindings.ts +2 -2
  37. package/src/app/runtime/startup.ts +13 -0
  38. package/src/app/star-prompt/status-line.tsx +2 -2
  39. package/src/architecture/core-builtin-allowlist.json +5 -2
  40. package/src/architecture/ui-literals-allowlist.json +3 -3
  41. package/src/architecture/ui-tokens-allowlist.json +1 -1
  42. package/src/brokers/broker-portfolio-sync.ts +37 -0
  43. package/src/brokers/directory.ts +11 -0
  44. package/src/brokers/logos.ts +3 -1
  45. package/src/brokers/signed-in/adapter.ts +304 -26
  46. package/src/brokers/signed-in/client.ts +14 -2
  47. package/src/brokers/signed-in/connect.ts +10 -2
  48. package/src/brokers/sync-broker-instance.ts +9 -1
  49. package/src/capabilities/chart-series.ts +22 -6
  50. package/src/cli/bank-section.ts +1 -1
  51. package/src/cli/base-converter.ts +23 -14
  52. package/src/cli/central-bank-events.ts +1 -1
  53. package/src/cli/command-options.ts +59 -4
  54. package/src/cli/commands/api.ts +9 -6
  55. package/src/cli/commands/broker.ts +2 -2
  56. package/src/cli/commands/command-utils.ts +2 -2
  57. package/src/cli/commands/crypto-hints.ts +7 -20
  58. package/src/cli/commands/fred-series.ts +4 -2
  59. package/src/cli/commands/keybindings.ts +2 -2
  60. package/src/cli/commands/market.ts +1117 -264
  61. package/src/cli/commands/news-hints.ts +2 -2
  62. package/src/cli/commands/news-search.ts +59 -6
  63. package/src/cli/commands/options-leaps.ts +41 -26
  64. package/src/cli/commands/overview.ts +121 -48
  65. package/src/cli/commands/plugins.ts +9 -9
  66. package/src/cli/commands/remote.ts +13 -10
  67. package/src/cli/commands/search.ts +64 -44
  68. package/src/cli/commands/system.ts +27 -19
  69. package/src/cli/commands/themes.ts +1 -1
  70. package/src/cli/commands/ticker.ts +192 -47
  71. package/src/cli/context.ts +3 -2
  72. package/src/cli/desktop-pane-shot-view.ts +2 -2
  73. package/src/cli/desktop-pane-shot.ts +1 -1
  74. package/src/cli/earnings-calendar.ts +4 -4
  75. package/src/cli/entry.ts +18 -1
  76. package/src/cli/errors.ts +20 -2
  77. package/src/cli/exit-codes.ts +1 -1
  78. package/src/cli/export-source.ts +10 -4
  79. package/src/cli/freshness.ts +2 -8
  80. package/src/cli/fx-forward.ts +130 -0
  81. package/src/cli/fx-pair.ts +10 -9
  82. package/src/cli/glossary.ts +64 -20
  83. package/src/cli/help.ts +19 -13
  84. package/src/cli/history-export.ts +126 -0
  85. package/src/cli/history-rows.ts +196 -43
  86. package/src/cli/index-roots.ts +5 -45
  87. package/src/cli/index.ts +24 -18
  88. package/src/cli/known-gaps.ts +118 -25
  89. package/src/cli/listing-arg.ts +300 -41
  90. package/src/cli/name-entries.ts +30 -18
  91. package/src/cli/native-smoke.ts +2 -0
  92. package/src/cli/not-a-ticker.ts +19 -9
  93. package/src/cli/opentui-libc.ts +5 -0
  94. package/src/cli/options.ts +12 -0
  95. package/src/cli/pane-functions/capabilities.ts +75 -20
  96. package/src/cli/pane-functions/catalog.ts +24 -23
  97. package/src/cli/pane-functions/discovery.ts +1 -1
  98. package/src/cli/pane-functions/dom.ts +10 -7
  99. package/src/cli/pane-functions/financial-ratio-shot.ts +2 -2
  100. package/src/cli/pane-functions/freshness.ts +174 -23
  101. package/src/cli/pane-functions/function-help.ts +66 -27
  102. package/src/cli/pane-functions/headless.ts +24 -11
  103. package/src/cli/pane-functions/index.ts +18 -16
  104. package/src/cli/pane-functions/options.ts +22 -2
  105. package/src/cli/pane-functions/report-notices.ts +12 -4
  106. package/src/cli/pane-functions/report.ts +5 -5
  107. package/src/cli/pane-functions/resolver.ts +47 -10
  108. package/src/cli/pane-functions/screenshot-assessment.ts +322 -0
  109. package/src/cli/pane-functions/screenshot-bridge.ts +152 -0
  110. package/src/cli/pane-functions/screenshot-chart-evidence.ts +396 -0
  111. package/src/cli/pane-functions/screenshot-data-evidence.ts +383 -0
  112. package/src/cli/pane-functions/screenshot-evidence.ts +1 -1
  113. package/src/cli/pane-functions/screenshot-payload.ts +453 -0
  114. package/src/cli/pane-functions/screenshot-render.ts +316 -0
  115. package/src/cli/pane-functions/screenshot-types.ts +162 -0
  116. package/src/cli/pane-functions/shot-options.ts +8 -3
  117. package/src/cli/peso-dollar-rates.ts +79 -0
  118. package/src/cli/quote-answer.ts +38 -3
  119. package/src/cli/report-tables.ts +61 -16
  120. package/src/cli/restore-plugins.ts +29 -14
  121. package/src/cli/result.ts +111 -22
  122. package/src/cli/series-search.ts +5 -5
  123. package/src/cli/statement-filings.ts +19 -0
  124. package/src/cli/suffixed-listing-rows.ts +49 -0
  125. package/src/cli/symbol-args.ts +50 -15
  126. package/src/cli/symbol-cells.ts +120 -0
  127. package/src/cli/symbol-list.ts +198 -68
  128. package/src/cli/symbol-rows.ts +131 -25
  129. package/src/cli/ticker-resolution.ts +108 -11
  130. package/src/cli/treasury-yields.ts +5 -5
  131. package/src/cli/types.ts +2 -0
  132. package/src/cli/uncovered-instrument.ts +3 -3
  133. package/src/cli/unknown-command.ts +6 -6
  134. package/src/components/chart/composite/chart-layout.ts +131 -0
  135. package/src/components/chart/composite/composite-chart.tsx +373 -16
  136. package/src/components/chart/composite/interactions.ts +7 -2
  137. package/src/components/chart/composite/legend.tsx +75 -3
  138. package/src/components/chart/composite/levels.ts +5 -0
  139. package/src/components/chart/composite/panel-gestures.ts +41 -0
  140. package/src/components/chart/composite/panel-labels.ts +2 -1
  141. package/src/components/chart/composite/panel-surface.tsx +305 -11
  142. package/src/components/chart/composite/price-axis-labels.tsx +27 -3
  143. package/src/components/chart/composite/rasterizer.ts +44 -19
  144. package/src/components/chart/composite/scene.ts +16 -4
  145. package/src/components/chart/composite/text-renderer.ts +48 -21
  146. package/src/components/chart/composite/toolbar.tsx +52 -5
  147. package/src/components/chart/composite/types.ts +13 -0
  148. package/src/components/chart/composite/use-composite-chart-keyboard.ts +120 -13
  149. package/src/components/chart/composite/use-composite-value-scales.ts +44 -0
  150. package/src/components/chart/composite/use-composite-viewport.ts +12 -3
  151. package/src/components/chart/composite/value-scale.ts +149 -0
  152. package/src/components/chart/composite/volume-profile-paint.ts +6 -3
  153. package/src/components/chart/core/pointer.ts +2 -0
  154. package/src/components/chart-table/index.ts +2 -0
  155. package/src/components/chart-table/read.tsx +74 -0
  156. package/src/components/command-bar/commands/collection-list.ts +41 -19
  157. package/src/components/command-bar/commands/collection.ts +9 -3
  158. package/src/components/command-bar/commands/direct/index.ts +1 -1
  159. package/src/components/command-bar/commands/direct/runtime.ts +3 -2
  160. package/src/components/command-bar/pane-templates/items.ts +59 -6
  161. package/src/components/command-bar/pane-templates/workflow.ts +10 -1
  162. package/src/components/command-bar/routes/root/plugin-install.ts +1 -1
  163. package/src/components/command-bar/routes/root/provider-search.ts +42 -11
  164. package/src/components/command-bar/routes/root/results.ts +18 -4
  165. package/src/components/command-bar/routes/root/runtime.ts +15 -3
  166. package/src/components/command-bar/routes/root/search-providers.ts +25 -2
  167. package/src/components/command-bar/routes/root/selection.ts +6 -1
  168. package/src/components/command-bar/routes/root/shortcut-feedback.ts +4 -1
  169. package/src/components/command-bar/routes/root/shortcuts.ts +30 -3
  170. package/src/components/command-bar/routes/ticker-search/results.ts +73 -4
  171. package/src/components/command-bar/selection-runtime.ts +50 -10
  172. package/src/components/command-bar/surface/index.tsx +24 -2
  173. package/src/components/command-bar/view-model.ts +7 -0
  174. package/src/components/command-bar/workflow/broker.ts +2 -2
  175. package/src/components/command-bar/workflow/collection-actions.ts +46 -7
  176. package/src/components/command-bar/workflow/collection-ticker.ts +32 -4
  177. package/src/components/command-bar/workflow/fields.ts +8 -2
  178. package/src/components/command-bar/workflow/ops.ts +18 -4
  179. package/src/components/command-bar/workflow/portfolio-position.ts +53 -4
  180. package/src/components/command-bar/workflow/submit.ts +3 -2
  181. package/src/components/command-bar/workflow/tickers.ts +5 -1
  182. package/src/components/company-logo.tsx +39 -0
  183. package/src/components/data-table/sections.ts +4 -2
  184. package/src/components/form-modal/confirm.tsx +2 -1
  185. package/src/components/form-modal/request.ts +5 -0
  186. package/src/components/form-modal/routes.ts +4 -1
  187. package/src/components/index.ts +12 -6
  188. package/src/components/layout/header.tsx +7 -6
  189. package/src/components/layout/market-summary.ts +9 -6
  190. package/src/components/layout/pane/capture-status.ts +1 -1
  191. package/src/components/layout/pane/footer/model.ts +11 -5
  192. package/src/components/layout/shell/chrome.ts +19 -6
  193. package/src/components/layout/shell/index.tsx +2 -1
  194. package/src/components/layout/shell/pane/layers.tsx +19 -1
  195. package/src/components/layout/status-bar.tsx +2 -2
  196. package/src/components/market-board/index.tsx +11 -3
  197. package/src/components/onboarding/onboarding-wizard.tsx +1 -0
  198. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +2 -2
  199. package/src/components/onboarding/wizard-broker-sync.ts +4 -1
  200. package/src/components/pane-settings-dialog/desktop.tsx +20 -7
  201. package/src/components/pane-settings-dialog/tui.tsx +17 -9
  202. package/src/components/pane-settings-dialog/value.ts +8 -1
  203. package/src/components/pane-settings-dialog.tsx +44 -13
  204. package/src/components/sign-in-code-panel.tsx +27 -19
  205. package/src/components/ui/column-grid.tsx +48 -4
  206. package/src/components/ui/country-flag-art.tsx +180 -0
  207. package/src/components/ui/country-flag.tsx +118 -12
  208. package/src/components/ui/data-table/opentui/index.tsx +61 -2
  209. package/src/components/ui/data-table/opentui/model.ts +109 -2
  210. package/src/components/ui/data-table/types.ts +1 -1
  211. package/src/components/ui/display.tsx +5 -1
  212. package/src/components/ui/index.ts +3 -1
  213. package/src/components/ui/logo-mark.tsx +11 -2
  214. package/src/components/ui/query-bar.tsx +1 -1
  215. package/src/components/ui/row-notes.ts +79 -0
  216. package/src/core/state/app/layout.ts +4 -4
  217. package/src/data/cached-query.ts +8 -1
  218. package/src/data/config/chart-settings.ts +3 -3
  219. package/src/data/config/home.ts +10 -2
  220. package/src/data/config/layout.ts +4 -1
  221. package/src/data/config/store/node.ts +4 -0
  222. package/src/data/config/store/normalize.ts +13 -3
  223. package/src/data/config/store/portfolio-as-of-column.ts +2 -2
  224. package/src/data/json-storage.ts +2 -0
  225. package/src/data/saved-copies.ts +84 -0
  226. package/src/docs/snapshot.json +65 -32
  227. package/src/i18n/es.ts +29 -12
  228. package/src/i18n/ja.ts +29 -12
  229. package/src/i18n/ko.ts +29 -12
  230. package/src/i18n/zh-cn.ts +29 -12
  231. package/src/i18n/zh-tw.ts +29 -12
  232. package/src/layout/desks.ts +1 -1
  233. package/src/layout/launch-layout.ts +1 -1
  234. package/src/layout/pane-manager/floating-actions.ts +5 -3
  235. package/src/layout/pane-manager/floating.ts +43 -1
  236. package/src/layout/pane-manager/index.ts +2 -0
  237. package/src/layout/selected-ticker.ts +74 -0
  238. package/src/market-data/depositary-pair.ts +251 -0
  239. package/src/market-data/futures-board.ts +1 -1
  240. package/src/market-data/futures-contract.ts +124 -40
  241. package/src/market-data/fx-closes.ts +17 -2
  242. package/src/market-data/hooks.tsx +19 -11
  243. package/src/market-data/market/change-basis.ts +49 -7
  244. package/src/market-data/market/freshness.ts +34 -5
  245. package/src/market-data/market/period-return.ts +163 -0
  246. package/src/market-data/market/trading-sessions.ts +13 -0
  247. package/src/market-data/market/year-to-date.ts +8 -13
  248. package/src/market-data/performance.ts +20 -14
  249. package/src/market-data/perpetual-premium.ts +32 -0
  250. package/src/market-data/published-apac-sessions.ts +2 -1
  251. package/src/market-data/published-futures-sessions.ts +69 -0
  252. package/src/market-data/quotes/contributions.ts +5 -1
  253. package/src/market-data/quotes/freshness.ts +42 -0
  254. package/src/market-data/quotes/intraday-bar.ts +37 -0
  255. package/src/market-data/quotes/regular-session-valuation.ts +13 -8
  256. package/src/market-data/quotes/resolution.ts +4 -2
  257. package/src/market-data/tax-equivalent.ts +1 -1
  258. package/src/market-data/ticker-price-returns.ts +3 -1
  259. package/src/news/aggregator.ts +5 -1
  260. package/src/news/news-model.ts +4 -0
  261. package/src/news/types.ts +11 -0
  262. package/src/notifications/app-notifier.ts +1 -1
  263. package/src/plugins/auto-update.ts +2 -2
  264. package/src/plugins/builtin/account-management/index.tsx +1 -0
  265. package/src/plugins/builtin/account-management/pane.tsx +9 -8
  266. package/src/plugins/builtin/alerts/alert-engine.ts +29 -19
  267. package/src/plugins/builtin/alerts/calendar-events.ts +378 -0
  268. package/src/plugins/builtin/alerts/cli.ts +485 -76
  269. package/src/plugins/builtin/alerts/command.ts +5 -10
  270. package/src/plugins/builtin/alerts/event-rows.ts +9 -5
  271. package/src/plugins/builtin/alerts/events-pane.tsx +71 -56
  272. package/src/plugins/builtin/alerts/events.ts +107 -5
  273. package/src/plugins/builtin/alerts/fired-log.ts +78 -0
  274. package/src/plugins/builtin/alerts/format.ts +2 -1
  275. package/src/plugins/builtin/alerts/headless.ts +100 -39
  276. package/src/plugins/builtin/alerts/history-pane.tsx +105 -62
  277. package/src/plugins/builtin/alerts/history-rows.ts +89 -0
  278. package/src/plugins/builtin/alerts/index.tsx +45 -18
  279. package/src/plugins/builtin/alerts/iv-level.ts +365 -0
  280. package/src/plugins/builtin/alerts/levels.ts +3 -3
  281. package/src/plugins/builtin/alerts/pane.tsx +97 -29
  282. package/src/plugins/builtin/alerts/price.ts +35 -0
  283. package/src/plugins/builtin/alerts/quotes.ts +24 -0
  284. package/src/plugins/builtin/alerts/research-builder.ts +84 -11
  285. package/src/plugins/builtin/alerts/research-rules.ts +76 -3
  286. package/src/plugins/builtin/alerts/scheduled-check.ts +206 -32
  287. package/src/plugins/builtin/alerts/scheduled-sources.ts +26 -3
  288. package/src/plugins/builtin/alerts/scheduled.ts +188 -29
  289. package/src/plugins/builtin/alerts/storage.ts +13 -10
  290. package/src/plugins/builtin/alerts/targets.ts +12 -3
  291. package/src/plugins/builtin/analytics/index.tsx +8 -2
  292. package/src/plugins/builtin/analytics/metrics.ts +1 -1
  293. package/src/plugins/builtin/analytics/risk-book.ts +215 -0
  294. package/src/plugins/builtin/analytics/risk-client.ts +13 -6
  295. package/src/plugins/builtin/analytics/risk-headless.ts +55 -21
  296. package/src/plugins/builtin/analytics/risk-math.ts +6 -3
  297. package/src/plugins/builtin/analytics/risk-model.ts +297 -104
  298. package/src/plugins/builtin/analytics/risk-options.ts +17 -2
  299. package/src/plugins/builtin/analytics/risk-pane.tsx +97 -36
  300. package/src/plugins/builtin/attention/pane.tsx +6 -2
  301. package/src/plugins/builtin/awards/headless.ts +7 -3
  302. package/src/plugins/builtin/awards/model.ts +18 -0
  303. package/src/plugins/builtin/awards/pane.tsx +6 -3
  304. package/src/plugins/builtin/backtest/headless.ts +3 -3
  305. package/src/plugins/builtin/backtest/rules.ts +41 -5
  306. package/src/plugins/builtin/broker-manager/add-flow.tsx +12 -7
  307. package/src/plugins/builtin/broker-manager/detail.tsx +47 -19
  308. package/src/plugins/builtin/broker-manager/footer.ts +27 -11
  309. package/src/plugins/builtin/broker-manager/index.tsx +21 -12
  310. package/src/plugins/builtin/broker-manager/keyboard.ts +1 -1
  311. package/src/plugins/builtin/broker-manager/model.ts +12 -2
  312. package/src/plugins/builtin/broker-manager/pane-actions.tsx +183 -57
  313. package/src/plugins/builtin/broker-manager/table.ts +28 -12
  314. package/src/plugins/builtin/broker-trading/account-picker.tsx +2 -1
  315. package/src/plugins/builtin/broker-trading/chart-levels.ts +82 -13
  316. package/src/plugins/builtin/broker-trading/controller.ts +45 -13
  317. package/src/plugins/builtin/broker-trading/dock.tsx +22 -4
  318. package/src/plugins/builtin/broker-trading/headless.ts +2 -1
  319. package/src/plugins/builtin/broker-trading/index.tsx +7 -1
  320. package/src/plugins/builtin/broker-trading/linked-orders.ts +148 -0
  321. package/src/plugins/builtin/broker-trading/order-feed.ts +75 -7
  322. package/src/plugins/builtin/broker-trading/orders.tsx +109 -31
  323. package/src/plugins/builtin/broker-trading/overview-orders.tsx +9 -3
  324. package/src/plugins/builtin/broker-trading/pane-model.ts +30 -2
  325. package/src/plugins/builtin/broker-trading/pane.tsx +110 -23
  326. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
  327. package/src/plugins/builtin/broker-trading/ticket.tsx +112 -31
  328. package/src/plugins/builtin/buildout/cells.tsx +4 -0
  329. package/src/plugins/builtin/buildout/detail/index.tsx +5 -1
  330. package/src/plugins/builtin/buildout/detail/ui.tsx +7 -4
  331. package/src/plugins/builtin/buildout/pane/index.tsx +25 -12
  332. package/src/plugins/builtin/buildout/pane/status.tsx +0 -4
  333. package/src/plugins/builtin/builtin-plugin-meta.ts +1 -1
  334. package/src/plugins/builtin/catalysts/detail.tsx +3 -2
  335. package/src/plugins/builtin/catalysts/index.tsx +1 -1
  336. package/src/plugins/builtin/catalysts/model.ts +49 -4
  337. package/src/plugins/builtin/catalysts/pane.tsx +6 -8
  338. package/src/plugins/builtin/central-bank-rates/client.ts +5 -0
  339. package/src/plugins/builtin/central-bank-rates/headless.ts +36 -10
  340. package/src/plugins/builtin/central-bank-rates/model.ts +33 -8
  341. package/src/plugins/builtin/changelog/index.tsx +1 -0
  342. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
  343. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +2 -1
  344. package/src/plugins/builtin/chart-composer/chart-spec.ts +7 -0
  345. package/src/plugins/builtin/chart-composer/cli-options.ts +2 -5
  346. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  347. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -0
  348. package/src/plugins/builtin/chart-composer/editor.tsx +4 -1
  349. package/src/plugins/builtin/chart-composer/headless-schema.ts +28 -26
  350. package/src/plugins/builtin/chart-composer/headless.ts +253 -35
  351. package/src/plugins/builtin/chart-composer/index.tsx +12 -2
  352. package/src/plugins/builtin/chart-composer/leverage-overlay.ts +142 -0
  353. package/src/plugins/builtin/chart-composer/pane.tsx +127 -28
  354. package/src/plugins/builtin/chart-composer/series-catalog.ts +1 -0
  355. package/src/plugins/builtin/chart-composer/series-expression.ts +65 -6
  356. package/src/plugins/builtin/chart-composer/session-history.ts +15 -21
  357. package/src/plugins/builtin/chart-composer/session-range.ts +4 -2
  358. package/src/plugins/builtin/chart-composer/settings.ts +31 -19
  359. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +5 -6
  360. package/src/plugins/builtin/chart-composer/viewport-labels.ts +18 -8
  361. package/src/plugins/builtin/chat/attachments/model.ts +3 -1
  362. package/src/plugins/builtin/chat/content/index.tsx +1 -1
  363. package/src/plugins/builtin/chat/pane-links/open.ts +1 -1
  364. package/src/plugins/builtin/chat/people.ts +1 -1
  365. package/src/plugins/builtin/clinical-trials/client.ts +5 -2
  366. package/src/plugins/builtin/clinical-trials/model.ts +13 -4
  367. package/src/plugins/builtin/clinical-trials/pane.tsx +32 -12
  368. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +17 -0
  369. package/src/plugins/builtin/cloud/askg/controller.ts +1 -1
  370. package/src/plugins/builtin/cloud/askg/lock-note.ts +1 -1
  371. package/src/plugins/builtin/cloud/askg/tool-display.ts +1 -1
  372. package/src/plugins/builtin/cloud/auth-commands.ts +3 -3
  373. package/src/plugins/builtin/cloud/team/pane-sections.tsx +105 -47
  374. package/src/plugins/builtin/cloud/team/pane-ui.tsx +42 -4
  375. package/src/plugins/builtin/cloud/team/pane.tsx +204 -64
  376. package/src/plugins/builtin/cloud/thesis/store.ts +45 -22
  377. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  378. package/src/plugins/builtin/commodities-energy/index.tsx +29 -2
  379. package/src/plugins/builtin/commodities-energy/view-state.ts +2 -0
  380. package/src/plugins/builtin/commodity-balances/pane.tsx +11 -4
  381. package/src/plugins/builtin/company-attention/app-rank.tsx +7 -5
  382. package/src/plugins/builtin/company-attention/building-model.ts +222 -0
  383. package/src/plugins/builtin/company-attention/building.tsx +172 -0
  384. package/src/plugins/builtin/company-attention/client.ts +117 -1
  385. package/src/plugins/builtin/company-attention/evidence.ts +16 -3
  386. package/src/plugins/builtin/company-attention/headless.ts +101 -5
  387. package/src/plugins/builtin/company-attention/index.tsx +6 -3
  388. package/src/plugins/builtin/company-attention/model.ts +33 -7
  389. package/src/plugins/builtin/company-attention/moves-model.ts +253 -0
  390. package/src/plugins/builtin/company-attention/moves.tsx +159 -0
  391. package/src/plugins/builtin/company-attention/pane.tsx +79 -23
  392. package/src/plugins/builtin/company-attention/signals-model.ts +229 -0
  393. package/src/plugins/builtin/company-attention/signals.tsx +160 -0
  394. package/src/plugins/builtin/company-kpis/headless.ts +3 -2
  395. package/src/plugins/builtin/company-kpis/model.ts +2 -2
  396. package/src/plugins/builtin/company-kpis/pane.tsx +1 -1
  397. package/src/plugins/builtin/composite-plugins.ts +2 -1
  398. package/src/plugins/builtin/congress-trades/headless.ts +1 -1
  399. package/src/plugins/builtin/connections/index.ts +1 -0
  400. package/src/plugins/builtin/correlation/headless-schema.ts +3 -2
  401. package/src/plugins/builtin/correlation/headless.ts +5 -3
  402. package/src/plugins/builtin/cot/headless.ts +28 -6
  403. package/src/plugins/builtin/cot/model.ts +12 -0
  404. package/src/plugins/builtin/cot/pane.tsx +3 -2
  405. package/src/plugins/builtin/cpi/headless.ts +5 -1
  406. package/src/plugins/builtin/cpi/pane.tsx +11 -9
  407. package/src/plugins/builtin/credit-boards/model.ts +2 -0
  408. package/src/plugins/builtin/credit-documents/headless.ts +1 -1
  409. package/src/plugins/builtin/crypto-board/client.ts +86 -0
  410. package/src/plugins/builtin/crypto-board/defi-view.tsx +218 -0
  411. package/src/plugins/builtin/crypto-board/defi.ts +239 -0
  412. package/src/plugins/builtin/crypto-board/headless.ts +117 -6
  413. package/src/plugins/builtin/crypto-board/index.tsx +8 -3
  414. package/src/plugins/builtin/crypto-board/model.ts +40 -8
  415. package/src/plugins/builtin/crypto-board/pane.tsx +55 -21
  416. package/src/plugins/builtin/crypto-board/series.ts +19 -1
  417. package/src/plugins/builtin/crypto-etf-flows/client.ts +60 -0
  418. package/src/plugins/builtin/crypto-etf-flows/headless.ts +132 -0
  419. package/src/plugins/builtin/crypto-etf-flows/index.tsx +44 -0
  420. package/src/plugins/builtin/crypto-etf-flows/model.ts +121 -0
  421. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +193 -0
  422. package/src/plugins/builtin/debug/index.tsx +1 -0
  423. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  424. package/src/plugins/builtin/docs/index.tsx +1 -1
  425. package/src/plugins/builtin/docs/pane.tsx +2 -2
  426. package/src/plugins/builtin/docs/targets.ts +1 -1
  427. package/src/plugins/builtin/doe/client.ts +5 -1
  428. package/src/plugins/builtin/doe/headless.ts +12 -6
  429. package/src/plugins/builtin/doe/model.ts +43 -6
  430. package/src/plugins/builtin/doe/pane.tsx +18 -11
  431. package/src/plugins/builtin/earnings/board-model.ts +13 -6
  432. package/src/plugins/builtin/earnings/board-table.ts +4 -1
  433. package/src/plugins/builtin/earnings/board.tsx +253 -50
  434. package/src/plugins/builtin/earnings/calendar-model.ts +188 -0
  435. package/src/plugins/builtin/earnings/calendar.tsx +338 -0
  436. package/src/plugins/builtin/earnings/client.ts +4 -3
  437. package/src/plugins/builtin/earnings/headless.ts +53 -15
  438. package/src/plugins/builtin/earnings/history-model.ts +53 -36
  439. package/src/plugins/builtin/earnings/index.tsx +15 -22
  440. package/src/plugins/builtin/earnings/model.ts +17 -12
  441. package/src/plugins/builtin/earnings/scope.ts +89 -0
  442. package/src/plugins/builtin/earnings-calls/calendar.tsx +310 -0
  443. package/src/plugins/builtin/earnings-calls/data.ts +146 -2
  444. package/src/plugins/builtin/earnings-calls/headless.ts +151 -9
  445. package/src/plugins/builtin/earnings-calls/index.tsx +3 -3
  446. package/src/plugins/builtin/earnings-calls/lock-note.ts +1 -1
  447. package/src/plugins/builtin/earnings-calls/pane.tsx +149 -31
  448. package/src/plugins/builtin/earnings-calls/upcoming.ts +195 -0
  449. package/src/plugins/builtin/earnings-ripple/pane.tsx +10 -5
  450. package/src/plugins/builtin/econ/calendar-model.ts +132 -20
  451. package/src/plugins/builtin/econ/detail-view.tsx +48 -10
  452. package/src/plugins/builtin/econ/fred-catalog.ts +23 -6
  453. package/src/plugins/builtin/econ/fred-series-map.ts +4 -0
  454. package/src/plugins/builtin/econ/index.tsx +112 -25
  455. package/src/plugins/builtin/econ/print-watch.ts +2 -1
  456. package/src/plugins/builtin/econ/types.ts +32 -1
  457. package/src/plugins/builtin/econ-statistics/client.ts +16 -3
  458. package/src/plugins/builtin/econ-statistics/defs.ts +4 -0
  459. package/src/plugins/builtin/econ-statistics/detail.tsx +2 -2
  460. package/src/plugins/builtin/econ-statistics/headless.ts +13 -2
  461. package/src/plugins/builtin/econ-statistics/index.tsx +3 -3
  462. package/src/plugins/builtin/econ-statistics/pane.tsx +2 -1
  463. package/src/plugins/builtin/econ-statistics/stats.ts +103 -0
  464. package/src/plugins/builtin/econ-statistics/view.ts +16 -1
  465. package/src/plugins/builtin/equity-screener/client.ts +21 -3
  466. package/src/plugins/builtin/equity-screener/headless.ts +68 -6
  467. package/src/plugins/builtin/equity-screener/live.ts +1 -1
  468. package/src/plugins/builtin/equity-screener/model.ts +6 -0
  469. package/src/plugins/builtin/equity-screener/pane.tsx +45 -10
  470. package/src/plugins/builtin/equity-screener/screen-check.ts +90 -0
  471. package/src/plugins/builtin/equity-screener/ticker-check.tsx +148 -0
  472. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -1
  473. package/src/plugins/builtin/estimate-revisions/lock-note.ts +3 -3
  474. package/src/plugins/builtin/estimate-revisions/model.ts +8 -10
  475. package/src/plugins/builtin/exposure/headless.ts +51 -6
  476. package/src/plugins/builtin/exposure/index.tsx +28 -2
  477. package/src/plugins/builtin/exposure/model.ts +7 -0
  478. package/src/plugins/builtin/exposure/pane.tsx +6 -1
  479. package/src/plugins/builtin/fear-greed/index.tsx +1 -1
  480. package/src/plugins/builtin/filing-events/index.tsx +1 -1
  481. package/src/plugins/builtin/futures/model.ts +10 -9
  482. package/src/plugins/builtin/futures/returns.ts +8 -36
  483. package/src/plugins/builtin/futures/table.tsx +1 -1
  484. package/src/plugins/builtin/futures-curve/client.ts +40 -9
  485. package/src/plugins/builtin/futures-curve/headless.ts +39 -17
  486. package/src/plugins/builtin/futures-curve/model.ts +258 -69
  487. package/src/plugins/builtin/futures-curve/pane.tsx +46 -17
  488. package/src/plugins/builtin/fx-matrix/client.ts +15 -8
  489. package/src/plugins/builtin/fx-matrix/headless.ts +32 -14
  490. package/src/plugins/builtin/fx-matrix/index.tsx +13 -11
  491. package/src/plugins/builtin/fx-matrix/live-legs.ts +22 -0
  492. package/src/plugins/builtin/fx-matrix/settings.ts +2 -1
  493. package/src/plugins/builtin/gpu/headless.ts +9 -4
  494. package/src/plugins/builtin/help/index.tsx +13 -3
  495. package/src/plugins/builtin/holders/beneficial-model.ts +8 -11
  496. package/src/plugins/builtin/holders/beneficial-report.ts +6 -5
  497. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  498. package/src/plugins/builtin/holders/client.ts +13 -1
  499. package/src/plugins/builtin/holders/format.ts +28 -17
  500. package/src/plugins/builtin/holders/headless.ts +46 -7
  501. package/src/plugins/builtin/holders/index-holders.ts +264 -0
  502. package/src/plugins/builtin/holders/pane.tsx +79 -15
  503. package/src/plugins/builtin/holders/report-rows.ts +35 -7
  504. package/src/plugins/builtin/holders/table-model.ts +2 -1
  505. package/src/plugins/builtin/holders/treemap.tsx +2 -2
  506. package/src/plugins/builtin/insider/headless.ts +1 -1
  507. package/src/plugins/builtin/insider/signals-client.ts +1 -1
  508. package/src/plugins/builtin/insider/signals.tsx +1 -1
  509. package/src/plugins/builtin/ipo-calendar/pane.tsx +7 -4
  510. package/src/plugins/builtin/iv-history/client.ts +20 -0
  511. package/src/plugins/builtin/iv-history/headless.ts +4 -4
  512. package/src/plugins/builtin/iv-history/index.tsx +1 -1
  513. package/src/plugins/builtin/iv-history/model.ts +13 -4
  514. package/src/plugins/builtin/iv-history/series.ts +3 -1
  515. package/src/plugins/builtin/jobs/lock-note.ts +1 -1
  516. package/src/plugins/builtin/jobs/model.ts +4 -15
  517. package/src/plugins/builtin/jobs/pane.tsx +7 -5
  518. package/src/plugins/builtin/jobs/share-bars.tsx +11 -5
  519. package/src/plugins/builtin/jodi/pane.tsx +10 -4
  520. package/src/plugins/builtin/layout-manager/index.tsx +1 -0
  521. package/src/plugins/builtin/macro-day/headless.ts +20 -7
  522. package/src/plugins/builtin/macro-day/index.tsx +3 -2
  523. package/src/plugins/builtin/macro-day/model.ts +29 -8
  524. package/src/plugins/builtin/macro-day/pane.tsx +46 -10
  525. package/src/plugins/builtin/macro-day/releases.ts +55 -13
  526. package/src/plugins/builtin/market-heatmap/data.ts +9 -2
  527. package/src/plugins/builtin/market-heatmap/evidence.ts +1 -1
  528. package/src/plugins/builtin/market-heatmap/headless.ts +9 -6
  529. package/src/plugins/builtin/market-heatmap/index.tsx +3 -3
  530. package/src/plugins/builtin/market-heatmap/model.ts +10 -7
  531. package/src/plugins/builtin/market-movers/client.ts +33 -19
  532. package/src/plugins/builtin/market-movers/headless.ts +8 -2
  533. package/src/plugins/builtin/market-movers/index.tsx +16 -10
  534. package/src/plugins/builtin/market-movers/screener.ts +29 -8
  535. package/src/plugins/builtin/market-movers/session-body.tsx +4 -2
  536. package/src/plugins/builtin/market-movers/session-table.ts +4 -2
  537. package/src/plugins/builtin/market-movers/table.ts +4 -1
  538. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  539. package/src/plugins/builtin/market-valuation/view.ts +5 -0
  540. package/src/plugins/builtin/members/headless.ts +6 -5
  541. package/src/plugins/builtin/members/pane.tsx +11 -4
  542. package/src/plugins/builtin/mna/client.ts +1 -0
  543. package/src/plugins/builtin/mna/detail.tsx +20 -32
  544. package/src/plugins/builtin/mna/distress/detail.tsx +16 -4
  545. package/src/plugins/builtin/mna/distress/model.ts +3 -2
  546. package/src/plugins/builtin/mna/distress/pane.tsx +7 -4
  547. package/src/plugins/builtin/mna/headless.ts +6 -13
  548. package/src/plugins/builtin/mna/model.ts +93 -15
  549. package/src/plugins/builtin/mna/pane.tsx +5 -4
  550. package/src/plugins/builtin/news/headless.ts +12 -6
  551. package/src/plugins/builtin/news/index.tsx +10 -1
  552. package/src/plugins/builtin/news/wire/index.tsx +3 -2
  553. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +26 -10
  554. package/src/plugins/builtin/news/wire/news/query-presets.ts +6 -2
  555. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -3
  556. package/src/plugins/builtin/notes/cli.ts +49 -14
  557. package/src/plugins/builtin/openfda/pane.tsx +1 -1
  558. package/src/plugins/builtin/options/carry.ts +2 -2
  559. package/src/plugins/builtin/options/index.tsx +1 -1
  560. package/src/plugins/builtin/options/view.tsx +10 -10
  561. package/src/plugins/builtin/options-calculator/headless.ts +46 -7
  562. package/src/plugins/builtin/options-scenario/client.ts +123 -28
  563. package/src/plugins/builtin/options-scenario/collar.ts +8 -6
  564. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +127 -0
  565. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +165 -0
  566. package/src/plugins/builtin/options-scenario/csp-screen.ts +258 -0
  567. package/src/plugins/builtin/options-scenario/csp.ts +101 -21
  568. package/src/plugins/builtin/options-scenario/headless.ts +36 -15
  569. package/src/plugins/builtin/options-scenario/hedge.ts +20 -14
  570. package/src/plugins/builtin/options-scenario/index.tsx +28 -3
  571. package/src/plugins/builtin/options-scenario/model.ts +13 -0
  572. package/src/plugins/builtin/options-scenario/pane.tsx +9 -5
  573. package/src/plugins/builtin/perps/equity-row.tsx +11 -3
  574. package/src/plugins/builtin/perps/freshness.ts +8 -5
  575. package/src/plugins/builtin/perps/headless.ts +3 -1
  576. package/src/plugins/builtin/perps/list-perpetuals.ts +25 -4
  577. package/src/plugins/builtin/perps/live-rows.ts +161 -0
  578. package/src/plugins/builtin/perps/markets-section.tsx +154 -0
  579. package/src/plugins/builtin/perps/pane.tsx +36 -17
  580. package/src/plugins/builtin/perps/venue-markets.ts +96 -0
  581. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -1
  582. package/src/plugins/builtin/plugin-marketplace/pane.tsx +143 -59
  583. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +72 -24
  584. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +158 -141
  585. package/src/plugins/builtin/portfolio-list/cli/render.ts +49 -20
  586. package/src/plugins/builtin/portfolio-list/cli/shared.ts +3 -3
  587. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +29 -24
  588. package/src/plugins/builtin/portfolio-list/headless.ts +6 -3
  589. package/src/plugins/builtin/portfolio-list/income.ts +13 -2
  590. package/src/plugins/builtin/portfolio-list/index.tsx +9 -2
  591. package/src/plugins/builtin/portfolio-list/pane/index.tsx +21 -8
  592. package/src/plugins/builtin/portfolio-list/pane/purchase-date-fx.ts +46 -0
  593. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +16 -9
  594. package/src/plugins/builtin/portfolio-list/summary/totals.ts +2 -15
  595. package/src/plugins/builtin/power/client.ts +4 -1
  596. package/src/plugins/builtin/power/evidence.ts +22 -7
  597. package/src/plugins/builtin/power/gas.tsx +5 -1
  598. package/src/plugins/builtin/power/pane.tsx +8 -1
  599. package/src/plugins/builtin/power/prices.tsx +10 -3
  600. package/src/plugins/builtin/research/analyst-headless.ts +25 -8
  601. package/src/plugins/builtin/research/analyst-model.ts +68 -3
  602. package/src/plugins/builtin/research/corporate-actions-pane.tsx +63 -14
  603. package/src/plugins/builtin/research/event-model.ts +3 -1
  604. package/src/plugins/builtin/research/event-sources.ts +12 -4
  605. package/src/plugins/builtin/research/index.tsx +2 -1
  606. package/src/plugins/builtin/research/relative-valuation-basis.ts +58 -0
  607. package/src/plugins/builtin/research/relative-valuation-columns.ts +31 -2
  608. package/src/plugins/builtin/research/relative-valuation-currency.ts +12 -5
  609. package/src/plugins/builtin/research/relative-valuation-headless.ts +84 -26
  610. package/src/plugins/builtin/research/relative-valuation-model.ts +88 -19
  611. package/src/plugins/builtin/research/relative-valuation-pane.tsx +76 -9
  612. package/src/plugins/builtin/research/relative-valuation-peers.ts +1 -1
  613. package/src/plugins/builtin/reverse-dcf/model.ts +6 -2
  614. package/src/plugins/builtin/risk-factors/index.tsx +2 -2
  615. package/src/plugins/builtin/scanner/lock-note.ts +1 -1
  616. package/src/plugins/builtin/sec/forms.ts +13 -0
  617. package/src/plugins/builtin/sec/fund-abroad.ts +32 -0
  618. package/src/plugins/builtin/sec/fund-facts.ts +68 -8
  619. package/src/plugins/builtin/sec/headless.ts +85 -8
  620. package/src/plugins/builtin/sec/index.tsx +58 -12
  621. package/src/plugins/builtin/sectors/client.ts +2 -1
  622. package/src/plugins/builtin/sectors/sector-model.ts +11 -15
  623. package/src/plugins/builtin/series-inventory.ts +2 -0
  624. package/src/plugins/builtin/shared/broker-reconnect.ts +107 -22
  625. package/src/plugins/builtin/shared/cloud-upgrade.ts +34 -6
  626. package/src/plugins/builtin/shared/earnings-calendar.ts +375 -0
  627. package/src/plugins/builtin/{earnings/estimate-basis.ts → shared/earnings-estimate-basis.ts} +1 -1
  628. package/src/plugins/builtin/shared/earnings-report.ts +3 -3
  629. package/src/plugins/builtin/shared/fx-rate-freshness.ts +23 -1
  630. package/src/plugins/builtin/shared/job-posting.ts +129 -0
  631. package/src/plugins/builtin/shared/pro-lock-note.ts +3 -3
  632. package/src/plugins/builtin/shared/report-freshness.ts +3 -0
  633. package/src/plugins/builtin/shared/research-trading.tsx +21 -0
  634. package/src/plugins/builtin/shared/series-cache.ts +2 -0
  635. package/src/plugins/builtin/shared/ticker-surface.ts +2 -1
  636. package/src/plugins/builtin/short-interest/client.ts +2 -0
  637. package/src/plugins/builtin/short-interest/headless.ts +41 -12
  638. package/src/plugins/builtin/short-interest/index.tsx +23 -1
  639. package/src/plugins/builtin/short-interest/model.ts +70 -19
  640. package/src/plugins/builtin/short-interest/pane.tsx +19 -2
  641. package/src/plugins/builtin/short-interest/publications-client.ts +82 -0
  642. package/src/plugins/builtin/short-interest/publications-headless.ts +29 -0
  643. package/src/plugins/builtin/short-interest/publications-model.ts +50 -0
  644. package/src/plugins/builtin/short-interest/publications-pane.tsx +66 -0
  645. package/src/plugins/builtin/short-interest/schedule.ts +35 -2
  646. package/src/plugins/builtin/short-interest/surface.tsx +11 -4
  647. package/src/plugins/builtin/short-interest/types.ts +9 -0
  648. package/src/plugins/builtin/short-interest/watch-client.ts +14 -8
  649. package/src/plugins/builtin/short-interest/watch-model.ts +12 -25
  650. package/src/plugins/builtin/short-interest/watch-pane.tsx +5 -1
  651. package/src/plugins/builtin/supply-chain/flow.tsx +33 -9
  652. package/src/plugins/builtin/supply-chain/model.ts +51 -3
  653. package/src/plugins/builtin/supply-chain/pane.tsx +78 -39
  654. package/src/plugins/builtin/supply-chain/row-evidence.tsx +1 -1
  655. package/src/plugins/builtin/themes/headless.ts +8 -5
  656. package/src/plugins/builtin/themes/model.ts +14 -4
  657. package/src/plugins/builtin/themes/pane.tsx +24 -5
  658. package/src/plugins/builtin/thirteenf/headless.ts +5 -5
  659. package/src/plugins/builtin/thirteenf/signals.ts +3 -1
  660. package/src/plugins/builtin/ticker-detail/adr-pair/headless.ts +123 -0
  661. package/src/plugins/builtin/ticker-detail/adr-pair/model.ts +132 -0
  662. package/src/plugins/builtin/ticker-detail/adr-pair/tab.tsx +87 -0
  663. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +40 -26
  664. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +8 -4
  665. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +67 -3
  666. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +112 -5
  667. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +17 -2
  668. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +21 -7
  669. package/src/plugins/builtin/ticker-detail/financials/model.ts +36 -1
  670. package/src/plugins/builtin/ticker-detail/financials/offerings.ts +56 -0
  671. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +62 -13
  672. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +30 -4
  673. package/src/plugins/builtin/ticker-detail/headless-schema.ts +14 -2
  674. package/src/plugins/builtin/ticker-detail/headless.ts +148 -35
  675. package/src/plugins/builtin/ticker-detail/index.tsx +37 -5
  676. package/src/plugins/builtin/ticker-detail/overview/model.ts +65 -10
  677. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +9 -1
  678. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +68 -11
  679. package/src/plugins/builtin/ticker-detail/pane.tsx +11 -0
  680. package/src/plugins/builtin/ticker-detail/quote-footer.ts +8 -3
  681. package/src/plugins/builtin/ticker-detail/research-tabs.ts +11 -0
  682. package/src/plugins/builtin/vol-surface/client.ts +5 -4
  683. package/src/plugins/builtin/vol-surface/headless.ts +12 -3
  684. package/src/plugins/builtin/vol-surface/model.ts +4 -2
  685. package/src/plugins/builtin/volatility/model.ts +5 -10
  686. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +4 -3
  687. package/src/plugins/builtin/world-venue-map/basemap.ts +3 -1
  688. package/src/plugins/builtin/world-venue-map/chokepoint-chart.ts +40 -0
  689. package/src/plugins/builtin/world-venue-map/evidence.ts +1 -1
  690. package/src/plugins/builtin/world-venue-map/geo-series.ts +90 -11
  691. package/src/plugins/builtin/world-venue-map/headless.ts +1 -1
  692. package/src/plugins/builtin/world-venue-map/index.ts +3 -26
  693. package/src/plugins/builtin/world-venue-map/layers.ts +1 -1
  694. package/src/plugins/builtin/world-venue-map/map.tsx +2 -1
  695. package/src/plugins/builtin/world-venue-map/pane.tsx +4 -2
  696. package/src/plugins/builtin/world-venue-map/venue-header.tsx +15 -4
  697. package/src/plugins/builtin/yield-curve/client.ts +25 -0
  698. package/src/plugins/builtin/yield-curve/curves.ts +7 -4
  699. package/src/plugins/builtin/yield-curve/headless.ts +38 -4
  700. package/src/plugins/builtin/yield-curve/history.ts +4 -3
  701. package/src/plugins/builtin/yield-curve/index.tsx +12 -6
  702. package/src/plugins/builtin/yield-curve/swaps-model.ts +93 -0
  703. package/src/plugins/builtin/yield-curve/swaps.tsx +99 -0
  704. package/src/plugins/bundle.ts +1 -1
  705. package/src/plugins/catalog-browser.ts +2 -1
  706. package/src/plugins/compat.ts +3 -3
  707. package/src/plugins/dependencies.ts +75 -14
  708. package/src/plugins/extracted-plugins.ts +132 -34
  709. package/src/plugins/host-link.ts +149 -34
  710. package/src/plugins/installer.ts +35 -10
  711. package/src/plugins/loader.ts +206 -38
  712. package/src/plugins/manager-node.ts +2 -2
  713. package/src/plugins/marketplace/deactivation.ts +22 -0
  714. package/src/plugins/marketplace/model.ts +2 -2
  715. package/src/plugins/marketplace/packs.ts +1 -1
  716. package/src/plugins/ownership.ts +1 -1
  717. package/src/plugins/peer-retry.ts +155 -0
  718. package/src/plugins/registry/contributions.ts +3 -1
  719. package/src/plugins/registry/index.ts +4 -3
  720. package/src/plugins/registry/plugin-state.ts +3 -0
  721. package/src/plugins/seed-notice.ts +20 -0
  722. package/src/plugins/shortcut-argument.ts +39 -0
  723. package/src/portfolio/allocation.ts +1 -1
  724. package/src/portfolio/collection-name.ts +31 -0
  725. package/src/portfolio/column-values.ts +10 -8
  726. package/src/portfolio/cost-fx.ts +296 -19
  727. package/src/portfolio/manual-lots.ts +75 -0
  728. package/src/portfolio/mutations.ts +60 -22
  729. package/src/portfolio/suffixed-listings.ts +209 -0
  730. package/src/portfolio/totals-currency.ts +24 -0
  731. package/src/public/__snapshots__/public-api.test.ts.snap +14 -0
  732. package/src/public/time-series.ts +11 -8
  733. package/src/remote/schema.ts +1 -1
  734. package/src/renderers/browser/config-host.ts +3 -0
  735. package/src/renderers/browser/main.tsx +23 -3
  736. package/src/renderers/dom/boot-frame-page.ts +187 -0
  737. package/src/renderers/dom/boot-frame.ts +237 -0
  738. package/src/renderers/dom/build-assets.ts +46 -8
  739. package/src/renderers/dom/dom-ui-host.tsx +4 -29
  740. package/src/renderers/dom/host/ascii-text.ts +16 -2
  741. package/src/renderers/dom/host/box.tsx +43 -2
  742. package/src/renderers/dom/host/chart-surface.tsx +2 -0
  743. package/src/renderers/dom/host/hls.chunk.ts +3 -0
  744. package/src/renderers/dom/host/image-surface.tsx +49 -0
  745. package/src/renderers/dom/host/media-surface.tsx +24 -18
  746. package/src/renderers/dom/host/mouse.ts +30 -0
  747. package/src/renderers/dom/host/size-observer.ts +1 -1
  748. package/src/renderers/dom/host/style.ts +1 -1
  749. package/src/renderers/dom/styles.css +76 -0
  750. package/src/renderers/dom/test-utils.tsx +2 -1
  751. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -3
  752. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -0
  753. package/src/renderers/electrobun/bun/desktop/backend.ts +46 -3
  754. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +1 -1
  755. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -3
  756. package/src/renderers/electrobun/bun/desktop/initialization.ts +81 -18
  757. package/src/renderers/electrobun/bun/external-plugins.ts +29 -7
  758. package/src/renderers/electrobun/bun/index.ts +11 -1
  759. package/src/renderers/electrobun/bun/window/boot-frame.ts +67 -0
  760. package/src/renderers/electrobun/bun/window/create.ts +4 -1
  761. package/src/renderers/electrobun/shared/boot-frame.ts +7 -0
  762. package/src/renderers/electrobun/shared/protocol.ts +6 -1
  763. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  764. package/src/renderers/electrobun/view/config-host.ts +3 -0
  765. package/src/renderers/electrobun/view/fatal-screen.tsx +1 -1
  766. package/src/renderers/electrobun/view/main.tsx +30 -4
  767. package/src/renderers/electrobun/view/news-source.ts +5 -5
  768. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -1
  769. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  770. package/src/renderers/opentui/browser-open.ts +88 -0
  771. package/src/renderers/opentui/host.tsx +5 -14
  772. package/src/renderers/opentui/image/loader.ts +3 -2
  773. package/src/renderers/opentui/native-smoke.ts +10 -1
  774. package/src/renderers/opentui/pane-image/index.ts +2 -1
  775. package/src/renderers/opentui/render-lib.ts +253 -0
  776. package/src/renderers/opentui/render-timing-smoke.ts +1 -1
  777. package/src/renderers/opentui/start.tsx +1 -1
  778. package/src/renderers/share/layout-view.tsx +2 -2
  779. package/src/renderers/share/main.tsx +4 -4
  780. package/src/renderers/share/view.tsx +1 -1
  781. package/src/series/resolve.ts +22 -6
  782. package/src/series/standalone.ts +30 -2
  783. package/src/series/types.ts +8 -1
  784. package/src/shares/api.ts +4 -1
  785. package/src/shares/pane.ts +1 -1
  786. package/src/sources/gloomberb-cloud/fred-series.ts +46 -1
  787. package/src/sources/gloomberb-cloud/index.ts +32 -16
  788. package/src/sources/gloomberb-cloud/news.ts +39 -0
  789. package/src/sources/gloomberb-cloud/normalizers.ts +14 -1
  790. package/src/sources/gloomberb-cloud/perpetuals.ts +49 -1
  791. package/src/sources/listing-symbols.ts +37 -0
  792. package/src/sources/provider-errors.ts +16 -2
  793. package/src/sources/provider-router/financial-routes.ts +21 -3
  794. package/src/sources/provider-router/financials.ts +15 -2
  795. package/src/sources/provider-router/history.ts +30 -8
  796. package/src/sources/provider-router/primary.ts +1 -1
  797. package/src/state/app/bootstrap.ts +23 -0
  798. package/src/state/hooks/inline-tickers.ts +30 -5
  799. package/src/sync/controller.ts +1 -1
  800. package/src/sync/core-contributors.ts +46 -14
  801. package/src/team/collections-sync.ts +2 -1
  802. package/src/team/collections.ts +178 -17
  803. package/src/telemetry/crash-reports.ts +17 -4
  804. package/src/test-support/builtin-panes.ts +1 -1
  805. package/src/theme/colors.ts +3 -30
  806. package/src/theme/ui-tokens.ts +37 -1
  807. package/src/tickers/company-name-listing.ts +210 -33
  808. package/src/tickers/index-roots.ts +47 -0
  809. package/src/tickers/instrument-label.ts +5 -1
  810. package/src/tickers/metadata.ts +10 -0
  811. package/src/tickers/perpetual-listing.ts +5 -2
  812. package/src/tickers/search/home-listing.ts +75 -0
  813. package/src/tickers/search/index.ts +76 -27
  814. package/src/tickers/search/ranking.ts +54 -4
  815. package/src/tickers/search/upsert.ts +29 -0
  816. package/src/tickers/terminal-codes.ts +416 -40
  817. package/src/time-series/bar-aggregation.ts +243 -0
  818. package/src/time-series/base-series.ts +307 -0
  819. package/src/time-series/chart-bounds.ts +349 -0
  820. package/src/time-series/chart-seed.ts +142 -0
  821. package/src/time-series/economic.ts +5 -0
  822. package/src/time-series/fundamentals.ts +18 -3
  823. package/src/time-series/history-acquisition.ts +499 -0
  824. package/src/time-series/leverage.ts +100 -0
  825. package/src/time-series/market-resolution.ts +4 -2
  826. package/src/time-series/market.ts +2 -1
  827. package/src/time-series/price-comparison.ts +175 -30
  828. package/src/time-series/range.ts +9 -1
  829. package/src/time-series/resolution.ts +174 -16
  830. package/src/time-series/resolve-context.ts +157 -0
  831. package/src/time-series/resolve.ts +126 -1577
  832. package/src/time-series/series-name.ts +38 -0
  833. package/src/time-series/series-presentation.ts +208 -0
  834. package/src/time-series/spec.ts +11 -6
  835. package/src/time-series/studies.ts +109 -12
  836. package/src/time-series/tail-reconcile.ts +121 -0
  837. package/src/time-series/types.ts +29 -2
  838. package/src/time-series/use-chart-resolution.ts +4 -5
  839. package/src/types/broker.ts +10 -0
  840. package/src/types/config.ts +14 -0
  841. package/src/types/data-provider.ts +14 -0
  842. package/src/types/exchange-rate.ts +6 -0
  843. package/src/types/financials.ts +104 -7
  844. package/src/types/headless.ts +76 -6
  845. package/src/types/instrument.ts +3 -0
  846. package/src/types/plugin.ts +65 -8
  847. package/src/types/price-history.ts +2 -0
  848. package/src/types/ticker.ts +13 -0
  849. package/src/types/trading.ts +59 -1
  850. package/src/ui/ascii-font.ts +13 -1
  851. package/src/ui/host.tsx +6 -1
  852. package/src/updater/command-names.ts +175 -0
  853. package/src/updater/index.ts +10 -5
  854. package/src/utils/calendar-date.ts +32 -15
  855. package/src/utils/cash-runway.ts +241 -0
  856. package/src/utils/central-banks.ts +42 -0
  857. package/src/utils/cli-output.ts +259 -80
  858. package/src/utils/contract-month.ts +16 -0
  859. package/src/utils/crypto-pair.ts +61 -3
  860. package/src/utils/currency-units.ts +29 -0
  861. package/src/utils/datetime-format.ts +10 -0
  862. package/src/utils/debug-log.ts +1 -1
  863. package/src/utils/decimal-comma.ts +39 -6
  864. package/src/utils/exchange-announcements.ts +71 -0
  865. package/src/utils/exchange-rate-snapshot.ts +10 -0
  866. package/src/utils/financial-statements.ts +83 -4
  867. package/src/utils/format.ts +34 -16
  868. package/src/utils/fundamentals.ts +150 -34
  869. package/src/utils/fx-market-hours.ts +10 -1
  870. package/src/utils/latest-financial-period.ts +8 -0
  871. package/src/utils/linux-libc.ts +54 -0
  872. package/src/utils/market-capitalization.ts +111 -15
  873. package/src/utils/max-drawdown.ts +64 -0
  874. package/src/utils/network-failure.ts +83 -0
  875. package/src/utils/option-expiry.ts +139 -7
  876. package/src/utils/price-earnings.ts +11 -5
  877. package/src/utils/price-history.ts +18 -8
  878. package/src/utils/price-window.ts +131 -0
  879. package/src/utils/release-date.ts +47 -0
  880. package/src/utils/sec.ts +3 -0
  881. package/src/utils/throttled-fetch.ts +11 -4
  882. package/src/utils/trailing-eps.ts +67 -0
  883. package/src/utils/typed-text.ts +21 -0
  884. package/src/utils/utc-time.ts +24 -1
  885. package/src/version.ts +6 -0
  886. package/src/cli/pane-functions/screenshot.ts +0 -2122
@@ -1,12 +1,19 @@
1
1
  import { apiClient } from "../../../api-client";
2
2
  import type { AppAttentionFilter, AppAttentionPayload, AppRankPayload } from "../../../api-client/app-attention";
3
- import type { HiringBoard, HiringPayload, HiringSummary } from "../../../api-client/hiring";
3
+ import type { HiringBoard, HiringBuildingBoard, HiringBuildingFilter, HiringBuildingShift, HiringCompanyBuilding, HiringCompanyMoves, HiringCompanySignals, HiringMixRow, HiringMove, HiringMovePosting, HiringMovesBoard, HiringMovesFilter, HiringMoveWeek, HiringPayload, HiringSignalGroup, HiringSignalsBoard, HiringSignalsFilter, HiringSummary, HiringWeekSignal } from "../../../api-client/hiring";
4
+ import type { CloudJobsPostingDetail } from "../../../api-client/types";
4
5
  import { createPluginCache } from "../../../data/plugin-cache";
5
6
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
6
7
  import type { AppFocus, AttentionKind } from "./model";
7
8
 
8
9
  export type AttentionPayload = HiringBoard | HiringPayload | AppAttentionPayload;
9
10
  export const attentionCache = createPluginCache<AttentionPayload>({ kind: "company-attention", source: "gloom-cloud", schemaVersion: 1, policy: { staleMs: 30 * 60_000, expireMs: 7 * 86_400_000 } });
11
+ export type MovesPayload = HiringMovesBoard | HiringCompanyMoves;
12
+ export const movesCache = createPluginCache<MovesPayload>({ kind: "hiring-moves", source: "gloom-cloud", schemaVersion: 1, policy: { staleMs: 30 * 60_000, expireMs: 7 * 86_400_000 } });
13
+ export type SignalsPayload = HiringSignalsBoard | HiringCompanySignals;
14
+ export const signalsCache = createPluginCache<SignalsPayload>({ kind: "hiring-signals", source: "gloom-cloud", schemaVersion: 1, policy: { staleMs: 30 * 60_000, expireMs: 7 * 86_400_000 } });
15
+ export type BuildingPayload = HiringBuildingBoard | HiringCompanyBuilding;
16
+ export const buildingCache = createPluginCache<BuildingPayload>({ kind: "hiring-building", source: "gloom-cloud", schemaVersion: 1, policy: { staleMs: 30 * 60_000, expireMs: 7 * 86_400_000 } });
10
17
  export const appRankCache = createPluginCache<AppRankPayload>({ kind: "app-rank-history", source: "gloom-cloud", schemaVersion: 1, policy: { staleMs: 30 * 60_000, expireMs: 7 * 86_400_000 } });
11
18
  const finite = (value: unknown) => typeof value === "number" && Number.isFinite(value);
12
19
  const positive = (value: unknown) => finite(value) && (value as number) >= 0;
@@ -60,6 +67,95 @@ const cacheKey = (kind: AttentionKind, symbol: string | undefined, access: strin
60
67
  export const cachedAttention = (kind: AttentionKind, symbol: string | undefined, access: string, filters: AppAttentionFilter) => cachedCloudResource(attentionCache, cacheKey(kind, symbol, access, filters), (data) => validateAttention(kind, data));
61
68
  export const loadAttention = (kind: AttentionKind, symbol: string | undefined, access: string, filters: AppAttentionFilter, force = false) => loadCloudResource(attentionCache, cacheKey(kind, symbol, access, filters), () => fetchAttention(kind, symbol, filters), { force, validate: (data) => validateAttention(kind, data) });
62
69
 
70
+ const SIGNALS = new Set(["accelerating", "slowing", "with_peers", null]);
71
+ const hiringMove = (row: HiringMove) => !!row && typeof row.symbol === "string" && typeof row.name === "string" && date(row.week) && positive(row.newRoles)
72
+ && !!row.baseline && positive(row.baseline.mean) && Array.isArray(row.baseline.values) && finite(row.zScore) && !!row.peers && typeof row.peers.label === "string"
73
+ && SIGNALS.has(row.signal) && Array.isArray(row.drivers) && row.drivers.every((driver) => typeof driver.label === "string" && positive(driver.newRoles))
74
+ && Array.isArray(row.topTitles) && typeof row.why === "string" && Array.isArray(row.sourceTickers);
75
+ const moveWeek = (row: HiringMoveWeek) => !!row && date(row.week) && typeof row.measured === "boolean" && Array.isArray(row.excluded);
76
+ const movePosting = (row: HiringMovePosting) => !!row && typeof row.title === "string" && date(row.firstSeenAt) && (row.url === null || url(row.url));
77
+ export function validateMoves(payload: MovesPayload): MovesPayload {
78
+ const valid = !!payload && date(payload.generatedAt) && typeof payload.preview === "boolean" && numericTree(payload) && ("moves" in payload
79
+ ? (payload.week === null || date(payload.week)) && Array.isArray(payload.moves) && payload.moves.every(hiringMove) && positive(payload.total) && positive(payload.locked) && !!payload.coverage?.signals
80
+ : typeof payload.symbol === "string" && (payload.latest === null || hiringMove(payload.latest)) && (payload.week === null || moveWeek(payload.week))
81
+ && Array.isArray(payload.weeks) && payload.weeks.every(moveWeek) && Array.isArray(payload.history) && !!payload.evidence
82
+ && [payload.evidence.added, payload.evidence.closed].every((rows) => Array.isArray(rows) && rows.every(movePosting)) && !!payload.locked);
83
+ if (!valid) throw new Error("The server returned unreadable hiring moves.");
84
+ return payload;
85
+ }
86
+ export interface MovesQuery { filter?: HiringMovesFilter; offset?: number; limit?: number }
87
+ export async function fetchMoves(symbol: string | undefined, query: MovesQuery = {}, client: Pick<typeof apiClient, "getCloudHiringMoves"> = apiClient, signal?: AbortSignal) {
88
+ try {
89
+ const data = validateMoves(await client.getCloudHiringMoves(symbol, { limit: query.limit, offset: query.offset, signal: query.filter }, signal));
90
+ if (symbol && (!("symbol" in data) || data.symbol.toUpperCase() !== symbol.toUpperCase() && data.requestedSymbol?.toUpperCase() !== symbol.toUpperCase())) throw new Error("The server returned moves for a different company.");
91
+ return data;
92
+ } catch (error) { throw unavailableOnServer(error, "Hiring moves are not available yet.", [404, 503]); }
93
+ }
94
+ const movesKey = (symbol: string | undefined, access: string, filter: HiringMovesFilter) => `${symbol ?? `board:${filter}`}:${access}`;
95
+ export const cachedMoves = (symbol: string | undefined, access: string, filter: HiringMovesFilter) => cachedCloudResource(movesCache, movesKey(symbol, access, filter), validateMoves);
96
+ export const loadMoves = (symbol: string | undefined, access: string, filter: HiringMovesFilter, force = false) => loadCloudResource(movesCache, movesKey(symbol, access, filter), () => fetchMoves(symbol, { filter }), { force, validate: validateMoves });
97
+
98
+ const KINDS = new Set(["new_country", "new_city", "location_spike", "function_spike", "new_role_type"]);
99
+ const weekSignal = (row: HiringWeekSignal) => !!row && typeof row.symbol === "string" && typeof row.name === "string" && date(row.week) && KINDS.has(row.kind)
100
+ && ["expansion", "anomalies"].includes(row.group) && typeof row.key === "string" && typeof row.label === "string" && positive(row.newRoles)
101
+ && !!row.history && date(row.history.watchedSince) && positive(row.history.priorPostings) && (row.history.priorShare === null || fraction(row.history.priorShare))
102
+ && (row.baseline === null || (!!row.baseline && positive(row.baseline.mean) && Array.isArray(row.baseline.values))) && Array.isArray(row.flags)
103
+ && Array.isArray(row.topTitles) && Array.isArray(row.places) && Array.isArray(row.postings) && row.postings.every(movePosting)
104
+ && typeof row.why === "string" && Array.isArray(row.sourceTickers);
105
+ export function validateSignals(payload: SignalsPayload): SignalsPayload {
106
+ const valid = !!payload && date(payload.generatedAt) && typeof payload.preview === "boolean" && numericTree(payload) && ("group" in payload
107
+ ? ["expansion", "anomalies"].includes(payload.group) && (payload.week === null || date(payload.week)) && Array.isArray(payload.signals) && payload.signals.every(weekSignal)
108
+ && positive(payload.total) && positive(payload.locked) && !!payload.coverage?.counts
109
+ : typeof payload.symbol === "string" && (payload.week === null || date(payload.week)) && (payload.watchedSince === null || date(payload.watchedSince))
110
+ && Array.isArray(payload.signals) && payload.signals.every(weekSignal) && Array.isArray(payload.history) && payload.history.every(weekSignal)
111
+ && Array.isArray(payload.skipped) && !!payload.locked);
112
+ if (!valid) throw new Error("The server returned unreadable hiring signals.");
113
+ return payload;
114
+ }
115
+ export interface SignalsQuery { filter?: HiringSignalsFilter; offset?: number; limit?: number }
116
+ /** A group's board, or a company's signals (both groups; the view keeps its own). */
117
+ export async function fetchSignals(group: HiringSignalGroup, symbol: string | undefined, query: SignalsQuery = {}, client: Pick<typeof apiClient, "getCloudHiringSignals" | "getCloudHiringCompanySignals"> = apiClient, signal?: AbortSignal) {
118
+ try {
119
+ const data = validateSignals(symbol ? await client.getCloudHiringCompanySignals(symbol, signal) : await client.getCloudHiringSignals(group, { limit: query.limit, offset: query.offset, kind: query.filter }, signal));
120
+ if (symbol && (!("symbol" in data) || data.symbol.toUpperCase() !== symbol.toUpperCase() && data.requestedSymbol?.toUpperCase() !== symbol.toUpperCase())) throw new Error("The server returned signals for a different company.");
121
+ if (!symbol && (!("group" in data) || data.group !== group)) throw new Error("The server returned another tab's signals.");
122
+ return data;
123
+ } catch (error) { throw unavailableOnServer(error, "Hiring signals are not available yet.", [404, 503]); }
124
+ }
125
+ const signalsKey = (group: HiringSignalGroup, symbol: string | undefined, access: string, filter: HiringSignalsFilter) => `${symbol ?? `${group}:${filter}`}:${access}`;
126
+ export const cachedSignals = (group: HiringSignalGroup, symbol: string | undefined, access: string, filter: HiringSignalsFilter) => cachedCloudResource(signalsCache, signalsKey(group, symbol, access, filter), validateSignals);
127
+ export const loadSignals = (group: HiringSignalGroup, symbol: string | undefined, access: string, filter: HiringSignalsFilter, force = false) => loadCloudResource(signalsCache, signalsKey(group, symbol, access, filter), () => fetchSignals(group, symbol, { filter }), { force, validate: validateSignals });
128
+
129
+ const DIMENSIONS = new Set(["function", "theme"]);
130
+ const mixRow = (row: HiringMixRow) => !!row && DIMENSIONS.has(row.dimension) && typeof row.key === "string" && typeof row.label === "string"
131
+ && positive(row.recentRoles) && fraction(row.recentShare) && positive(row.beforeRoles) && fraction(row.beforeShare) && finite(row.change) && typeof row.read === "boolean";
132
+ const shiftPart = (part: HiringBuildingShift["recent"]) => !!part && positive(part.weeks) && positive(part.roles) && positive(part.total) && fraction(part.share) && Array.isArray(part.values);
133
+ const buildingShift = (row: HiringBuildingShift) => !!row && typeof row.symbol === "string" && typeof row.name === "string" && date(row.week) && DIMENSIONS.has(row.dimension)
134
+ && typeof row.key === "string" && typeof row.label === "string" && shiftPart(row.recent) && shiftPart(row.before) && finite(row.change) && finite(row.zScore)
135
+ && Array.isArray(row.topTitles) && Array.isArray(row.postings) && row.postings.every(movePosting) && typeof row.why === "string" && Array.isArray(row.sourceTickers);
136
+ export function validateBuilding(payload: BuildingPayload): BuildingPayload {
137
+ const valid = !!payload && date(payload.generatedAt) && typeof payload.preview === "boolean" && numericTree(payload) && ("shifts" in payload && "filter" in payload
138
+ ? (payload.week === null || date(payload.week)) && Array.isArray(payload.shifts) && payload.shifts.every(buildingShift) && positive(payload.total) && positive(payload.locked) && !!payload.coverage?.counts
139
+ : "functions" in payload && typeof payload.symbol === "string" && (payload.week === null || date(payload.week)) && Array.isArray(payload.functions) && payload.functions.every(mixRow)
140
+ && Array.isArray(payload.themes) && payload.themes.every(mixRow) && Array.isArray(payload.shifts) && payload.shifts.every(buildingShift)
141
+ && Array.isArray(payload.history) && payload.history.every(buildingShift) && Array.isArray(payload.skipped) && !!payload.locked);
142
+ if (!valid) throw new Error("The server returned an unreadable hiring mix.");
143
+ return payload;
144
+ }
145
+ export interface BuildingQuery { filter?: HiringBuildingFilter; offset?: number; limit?: number }
146
+ /** The Building board, or one company's mix and shifts. */
147
+ export async function fetchBuilding(symbol: string | undefined, query: BuildingQuery = {}, client: Pick<typeof apiClient, "getCloudHiringBuilding" | "getCloudHiringCompanyBuilding"> = apiClient, signal?: AbortSignal) {
148
+ try {
149
+ const data = validateBuilding(symbol ? await client.getCloudHiringCompanyBuilding(symbol, signal) : await client.getCloudHiringBuilding({ limit: query.limit, offset: query.offset, dimension: query.filter }, signal));
150
+ if (symbol && (!("symbol" in data) || data.symbol.toUpperCase() !== symbol.toUpperCase() && data.requestedSymbol?.toUpperCase() !== symbol.toUpperCase())) throw new Error("The server returned a hiring mix for a different company.");
151
+ if (!symbol && !("filter" in data)) throw new Error("The server returned a company's mix for the board.");
152
+ return data;
153
+ } catch (error) { throw unavailableOnServer(error, "What companies are building is not available yet.", [404, 503]); }
154
+ }
155
+ const buildingKey = (symbol: string | undefined, access: string, filter: HiringBuildingFilter) => `${symbol ?? `board:${filter}`}:${access}`;
156
+ export const cachedBuilding = (symbol: string | undefined, access: string, filter: HiringBuildingFilter) => cachedCloudResource(buildingCache, buildingKey(symbol, access, filter), validateBuilding);
157
+ export const loadBuilding = (symbol: string | undefined, access: string, filter: HiringBuildingFilter, force = false) => loadCloudResource(buildingCache, buildingKey(symbol, access, filter), () => fetchBuilding(symbol, { filter }), { force, validate: validateBuilding });
158
+
63
159
  export async function fetchAppRank(focus: AppFocus, days: number, offset = 0, signal?: AbortSignal, client: Pick<typeof apiClient, "getCloudAppRankHistory"> = apiClient, limit = 500) {
64
160
  const data = await client.getCloudAppRankHistory(focus.store, focus.appId, { country: focus.country, chart: focus.chart, days, offset, limit }, signal);
65
161
  if (!data || data.appId !== focus.appId || data.store !== focus.store || !["pro", "preview"].includes(data.access)
@@ -67,3 +163,23 @@ export async function fetchAppRank(focus: AppFocus, days: number, offset = 0, si
67
163
  return data;
68
164
  }
69
165
  export const loadAppRank = (focus: AppFocus, access: string, days: number, force = false) => loadCloudResource(appRankCache, `${access}:${focus.store}:${focus.appId}:${focus.country}:${focus.chart}:${days}`, () => fetchAppRank(focus, days), { force });
166
+
167
+ const POSTING_TTL_MS = 5 * 60_000;
168
+ const postingCache = new Map<string, { at: number; posting: Promise<CloudJobsPostingDetail> }>();
169
+ /**
170
+ * The page HIRE's evidence row was read from: its title, place, date, pay,
171
+ * hours and description. Kept a few minutes, so stepping back and forth
172
+ * through the evidence does not read it again.
173
+ */
174
+ export function fetchPostingDetail(ticker: string, externalId: string, client: Pick<typeof apiClient, "getCloudJobsPosting"> = apiClient): Promise<CloudJobsPostingDetail> {
175
+ const key = `${ticker.toUpperCase()}:${externalId}`;
176
+ const cached = postingCache.get(key);
177
+ if (cached && Date.now() - cached.at < POSTING_TTL_MS) return cached.posting;
178
+ const posting = client.getCloudJobsPosting(ticker, { externalId }).then((detail) => {
179
+ if (!detail || typeof detail.title !== "string" || detail.externalId !== externalId) throw new Error("The server returned an unreadable posting.");
180
+ return detail;
181
+ });
182
+ postingCache.set(key, { at: Date.now(), posting });
183
+ posting.catch(() => postingCache.delete(key));
184
+ return posting;
185
+ }
@@ -1,7 +1,10 @@
1
1
  import type { PaneScreenshotEvidenceHook } from "../../../cli/pane-functions/screenshot-evidence";
2
2
  import { useRemoteUiNode } from "../../../remote/semantic-tree";
3
3
  import { isRecord } from "../../../utils/guards";
4
- import { fetchAppRank, fetchAttention, validateAttention, type AttentionPayload } from "./client";
4
+ import { fetchAppRank, fetchAttention, fetchBuilding, fetchMoves, fetchSignals, validateAttention, type AttentionPayload } from "./client";
5
+ import { isBuildingFilter } from "./building-model";
6
+ import { isMovesFilter } from "./moves-model";
7
+ import { isSignalGroup, isSignalsFilter } from "./signals-model";
5
8
  import type { AttentionKind } from "./model";
6
9
  import type { AppChart, AppRankPayload } from "../../../api-client/app-attention";
7
10
 
@@ -27,11 +30,21 @@ export function attentionScreenshotEvidence(kind: AttentionKind): PaneScreenshot
27
30
  }
28
31
  const symbol = resolved.createOptions?.symbol ?? resolved.createOptions?.arg;
29
32
  const payload = await fetchAttention(kind, symbol, { country: typeof resolved.options.country === "string" ? resolved.options.country : undefined, chart: resolved.options.chart as "free" | "paid" | undefined, days: Number(resolved.options.days) || 90 });
30
- return { settings: { attentionSnapshot: payload }, financials: symbol ? [[symbol, { annualStatements: [], quarterlyStatements: [], priceHistory: [] }]] : [] };
33
+ // HIRE's Moves tab reads its own payload.
34
+ const moves = kind === "hiring" && resolved.options.tab === "moves"
35
+ ? await fetchMoves(symbol, { filter: typeof resolved.options.moves === "string" && isMovesFilter(resolved.options.moves) ? resolved.options.moves : "moves" }) : null;
36
+ // So do Expansion and Anomalies.
37
+ const group = kind === "hiring" && typeof resolved.options.tab === "string" && isSignalGroup(resolved.options.tab) ? resolved.options.tab : null;
38
+ const signalsKind = typeof resolved.options.kind === "string" ? resolved.options.kind : "all";
39
+ const signals = group ? await fetchSignals(group, symbol, { filter: isSignalsFilter(group, signalsKind) ? signalsKind : "all" }) : null;
40
+ // And Building.
41
+ const dimension = typeof resolved.options.dimension === "string" && isBuildingFilter(resolved.options.dimension) ? resolved.options.dimension : "all";
42
+ const building = kind === "hiring" && resolved.options.tab === "building" ? await fetchBuilding(symbol, { filter: dimension }) : null;
43
+ return { settings: { attentionSnapshot: payload, ...(moves ? { movesSnapshot: moves } : {}), ...(group && signals ? { [`${group}Snapshot`]: signals } : {}), ...(building ? { buildingSnapshot: building } : {}) }, financials: symbol ? [[symbol, { annualStatements: [], quarterlyStatements: [], priceHistory: [] }]] : [] };
31
44
  },
32
45
  read(value) {
33
46
  if (!isRecord(value) || value.kind !== kind || value.version !== 1 || value.complete !== true || !isRecord(value.payload)
34
- || !["table", "chart", "mix", "peers", "evidence"].includes(String(value.tab)) || !Array.isArray(value.rowIds) || !value.rowIds.every((id) => typeof id === "string") || value.plottedValueCount !== value.rowIds.length) return null;
47
+ || !["table", "chart", "mix", "peers", "evidence", ...(kind === "hiring" ? ["moves", "expansion", "anomalies", "building"] : [])].includes(String(value.tab)) || !Array.isArray(value.rowIds) || !value.rowIds.every((id) => typeof id === "string") || value.plottedValueCount !== value.rowIds.length) return null;
35
48
  try { if (!(kind === "apps" && Array.isArray(value.payload.rankHistory))) validateAttention(kind, value.payload as unknown as AttentionPayload); return value as unknown as AttentionEvidence; } catch { return null; }
36
49
  },
37
50
  mismatches(evidence, { resolved, payload }) {
@@ -2,8 +2,12 @@ import type { HeadlessPaneColumn, HeadlessPaneDefinition, HeadlessPaneRow } from
2
2
  import type { DataTableColumn } from "../../../components";
3
3
  import type { AppAttentionPayload, AppChart } from "../../../api-client/app-attention";
4
4
  import type { HiringBoard, HiringPayload } from "../../../api-client/hiring";
5
- import { fetchAppRank, fetchAttention } from "./client";
5
+ import { fetchAppRank, fetchAttention, fetchBuilding, fetchMoves, fetchSignals, type BuildingPayload, type SignalsPayload } from "./client";
6
+ import { BUILDING_FILTERS, BUILDING_VIEWS, buildingBoardModel, buildingNumber, buildingPreviewNote, companyBuildingModel, isBuildingFilter, type BuildingSection } from "./building-model";
6
7
  import { appsModel, appsPreviewNote, attentionNumber, attentionSectionTitle, attentionTabs, hiringModel, hiringPreviewNote, remoteText, type AttentionKind } from "./model";
8
+ import { companyMovesModel, isMovesFilter, movesBoardModel, movesNumber, movesPreviewNote, MOVES_FILTERS, MOVES_VIEWS, type MovesSection } from "./moves-model";
9
+ import type { HiringMovesFilter, HiringSignalGroup } from "../../../api-client/hiring";
10
+ import { companySignalsModel, holdText, isSignalsFilter, signalsBoardModel, signalsNumber, signalsPreviewNote, SIGNAL_FILTERS, SIGNAL_GROUPS, SIGNALS_VIEWS, type SignalsSection } from "./signals-model";
7
11
  import { REPORT_PAGE, REPORT_ROWS } from "../shared/headless-options";
8
12
 
9
13
  /**
@@ -19,6 +23,56 @@ function reportColumn(column: DataTableColumn): HeadlessPaneColumn {
19
23
  ? remoteText(value, typeof row.remoteKnown === "number" ? row.remoteKnown : null) ?? "-" : "-" } : {}) };
20
24
  }
21
25
 
26
+ /** A moves view as a report section: paces as `3.4x`, scores signed, and on the board each move's why. */
27
+ function movesSection(title: string, section: MovesSection, why: boolean) {
28
+ return {
29
+ title, empty: section.empty, ...(section.locked ? { locked: true } : {}),
30
+ columns: [...section.columns.map((column): HeadlessPaneColumn => column.align !== "right" ? { key: column.id, header: column.label, align: column.align }
31
+ : { key: column.id, header: column.label, align: column.align, format: (value: unknown) => typeof value === "number" ? movesNumber(column.id, value) : value == null ? "-" : String(value) }),
32
+ ...(why ? [{ key: "why", header: "Why", align: "left" as const }] : [])],
33
+ rows: section.rows.map((row) => ({ ...row.values, ...(row.move ? { why: row.move.why } : {}), ...(row.url ? { sourceUrl: row.url } : {}) })),
34
+ };
35
+ }
36
+
37
+ /** A signals view as a report section: paces as `3.4x`, and each signal's why. */
38
+ function signalsSection(title: string, section: SignalsSection) {
39
+ return {
40
+ title, empty: section.empty, ...(section.locked ? { locked: true } : {}),
41
+ columns: [...section.columns.map((column): HeadlessPaneColumn => column.align !== "right" ? { key: column.id, header: column.label, align: column.align }
42
+ : { key: column.id, header: column.label, align: column.align, format: (value: unknown) => typeof value === "number" ? signalsNumber(column.id, value) : value == null ? "-" : String(value) }),
43
+ ...(section.rows.some((row) => row.signal && !row.jobPosting) ? [{ key: "why", header: "Why", align: "left" as const }] : [])],
44
+ rows: section.rows.map((row) => ({ ...row.values, ...(row.signal && !row.jobPosting ? { why: row.signal.why } : {}), ...(row.url ? { sourceUrl: row.url } : {}) })),
45
+ };
46
+ }
47
+ /** A building view as a report section: shares as percents, changes in points, and each shift's why. */
48
+ function buildingSection(title: string, section: BuildingSection) {
49
+ const why = section.rows.some((row) => row.shift && !row.jobPosting && !row.mix);
50
+ return {
51
+ title, empty: section.empty, ...(section.locked ? { locked: true } : {}),
52
+ columns: [...section.columns.map((column): HeadlessPaneColumn => column.align !== "right" ? { key: column.id, header: column.label, align: column.align }
53
+ : { key: column.id, header: column.label, align: column.align, format: (value: unknown) => typeof value === "number" ? buildingNumber(column.id, value) : value == null ? "-" : String(value) }),
54
+ ...(why ? [{ key: "why", header: "Why", align: "left" as const }] : [])],
55
+ rows: section.rows.map((row) => ({ ...row.values, ...(why && row.shift && !row.jobPosting ? { why: row.shift.why } : {}), ...(row.url ? { sourceUrl: row.url } : {}) })),
56
+ };
57
+ }
58
+ /** The Building board, or a company's mix by family and theme, its shifts and the postings behind them. */
59
+ function buildingSections(data: BuildingPayload, filter: string): ReturnType<typeof buildingSection>[] {
60
+ if ("filter" in data) return [buildingSection("Building", buildingBoardModel(data, isBuildingFilter(filter) ? filter : "all").sections.board!)];
61
+ const model = companyBuildingModel(data);
62
+ return ([["families", "Building: role families"], ["themes", "Building: themes"], ["shifts", "Building: shifts"], ["postings", "Building postings"]] as const)
63
+ .filter(([id]) => id !== "postings" || model.sections.postings!.rows.length).map(([id, title]) => buildingSection(title, model.sections[id]!));
64
+ }
65
+ const SIGNAL_TITLES: Record<HiringSignalGroup, string> = { expansion: "Expansion", anomalies: "Anomalies" };
66
+ /** A board's two groups, or a company's signals of both, as report sections. */
67
+ function signalsSections(reads: Array<{ group: HiringSignalGroup; data: SignalsPayload }>, kind: string): ReturnType<typeof signalsSection>[] {
68
+ return reads.flatMap(({ group, data }) => {
69
+ if ("group" in data) return [signalsSection(SIGNAL_TITLES[group], signalsBoardModel(data, isSignalsFilter(group, kind) ? kind : "all").sections.board!)];
70
+ const model = companySignalsModel(data, group);
71
+ return [signalsSection(SIGNAL_TITLES[group], model.sections.signals!),
72
+ ...(model.sections.postings!.rows.length ? [signalsSection(`${SIGNAL_TITLES[group]} postings`, model.sections.postings!)] : [])];
73
+ });
74
+ }
75
+
22
76
  export function attentionHeadless(kind: AttentionKind): HeadlessPaneDefinition<"bundle"> {
23
77
  return {
24
78
  shape: "bundle",
@@ -29,7 +83,20 @@ export function attentionHeadless(kind: AttentionKind): HeadlessPaneDefinition<"
29
83
  options: [
30
84
  { key: "limit", shot: REPORT_ROWS, type: "string", defaultValue: "100", description: "Rows per REST page, up to 200 for hiring and 500 for apps." },
31
85
  { key: "offset", shot: REPORT_PAGE, type: "string", defaultValue: "0", description: "Continuation offset from the prior report metadata." },
32
- { key: "tab", type: "enum", settingKey: "tab", values: ["table", "chart", "mix", "peers", "evidence"].map((value) => ({ value })), defaultValue: "table", description: "Table, dated history, geographic or role mix, peers, or source evidence." },
86
+ { key: "tab", type: "enum", settingKey: "tab", values: [...["table", "chart", "mix", "peers", "evidence"], ...(kind === "hiring" ? ["moves", "expansion", "anomalies", "building"] : [])].map((value) => ({ value })), defaultValue: "table",
87
+ description: kind === "hiring" ? "Table, dated history, role mix, peers, source evidence, moves against the company's own weeks and peers, first roles in a new country or city, place and role-family spikes, or the role families and themes whose share of new roles rose." : "Table, dated history, geographic or role mix, peers, or source evidence." },
88
+ ...(kind === "hiring" ? [
89
+ { key: "moves", type: "enum" as const, settingKey: "movesFilter", values: MOVES_FILTERS.map(({ value }) => ({ value })), defaultValue: "moves", description: "Which hiring moves the board lists: own moves, one direction, or every measured company." },
90
+ { key: "view", type: "enum" as const, settingKey: "movesView", values: MOVES_VIEWS.map(({ value }) => ({ value })), defaultValue: "weeks", description: "A company's Moves tab: its weeks, the role families or titles behind the move, or the postings first seen or closed that week." },
91
+ { key: "kind", type: "enum" as const, settingKey: "signalsKind", values: [...new Set(SIGNAL_GROUPS.flatMap((group) => SIGNAL_FILTERS[group].map(({ value }) => value)))].map((value) => ({ value })), defaultValue: "all",
92
+ description: "Which signals the Expansion or Anomalies board lists: new countries or cities, place or role-family spikes, or new role families." },
93
+ { key: "signalsView", aliases: ["signals-view"], type: "enum" as const, settingKey: "signalsView", values: SIGNALS_VIEWS.map(({ value }) => ({ value })), defaultValue: "signals",
94
+ description: "A company's Expansion or Anomalies tab: its signals, or the postings behind this week's." },
95
+ { key: "dimension", type: "enum" as const, settingKey: "buildingDimension", values: BUILDING_FILTERS.map(({ value }) => ({ value })), defaultValue: "all",
96
+ description: "Which shifts the Building board lists: role families, themes, or both." },
97
+ { key: "buildingView", aliases: ["building-view"], type: "enum" as const, settingKey: "buildingView", values: BUILDING_VIEWS.map(({ value }) => ({ value })), defaultValue: "families",
98
+ description: "A company's Building tab: its mix by role family or by theme, its shifts, or the postings behind this week's." },
99
+ ] : []),
33
100
  ...(kind === "apps" ? [
34
101
  { key: "appId", aliases: ["app-id"], type: "string" as const, settingKey: "appId", description: "Open one app's exact country/chart rank history instead of a company." },
35
102
  { key: "store", type: "enum" as const, settingKey: "store", values: [{ value: "app-store" }, { value: "google-play" }], defaultValue: "app-store", description: "Store for an individual app history." },
@@ -48,6 +115,17 @@ export function attentionHeadless(kind: AttentionKind): HeadlessPaneDefinition<"
48
115
  const rank = await fetchAppRank({ store, appId: args.options.appId, name: args.options.appId, country: String(args.options.country || (store === "google-play" ? "GLOBAL" : "US")).toUpperCase(), chart: (args.options.chart || (store === "google-play" ? "unranked" : "free")) as AppChart }, Number(args.options.days) || 90, Number(args.options.offset) || 0, ctx.signal, ctx.apiClient, Number(args.options.limit) || 100);
49
116
  return { complete: rank.access === "pro" && rank.page.nextOffset === null, sections: [{ title: "App rank history", rows: rank.rankHistory }], metadata: { nextOffset: rank.page.nextOffset, preview: rank.access === "preview", lockedRows: rank.locked, provenance: rank } };
50
117
  }
118
+ const movesFilter: HiringMovesFilter = typeof args.options.moves === "string" && isMovesFilter(args.options.moves) ? args.options.moves : "moves";
119
+ // Moves are their own read; a server without them leaves the rest of the report as it was.
120
+ const failed = (error: unknown) => error instanceof Error ? error : new Error(String(error));
121
+ const movesRead = kind === "hiring" ? fetchMoves(args.symbols[0], { filter: movesFilter }, ctx.apiClient, ctx.signal).catch(failed) : null;
122
+ // So are Expansion and Anomalies: a company's both groups in one read, the board one read each.
123
+ const signalsKind = typeof args.options.kind === "string" ? args.options.kind : "all";
124
+ const signalsRead = kind !== "hiring" ? null : Promise.all((args.symbols[0] ? ["expansion" as const] : SIGNAL_GROUPS).map((group) =>
125
+ fetchSignals(group, args.symbols[0], { filter: isSignalsFilter(group, signalsKind) ? signalsKind : "all" }, ctx.apiClient, ctx.signal))).catch(failed);
126
+ // And Building.
127
+ const dimension = typeof args.options.dimension === "string" && isBuildingFilter(args.options.dimension) ? args.options.dimension : "all";
128
+ const buildingRead = kind === "hiring" ? fetchBuilding(args.symbols[0], { filter: dimension }, ctx.apiClient, ctx.signal).catch(failed) : null;
51
129
  const data = await fetchAttention(kind, args.symbols[0], { limit: Number(args.options.limit) || 100, offset: Number(args.options.offset) || 0, ...(typeof args.options.country === "string" ? { country: args.options.country } : {}), ...(typeof args.options.chart === "string" ? { chart: args.options.chart as AppChart } : {}), days: Number(args.options.days) || 90 }, ctx.apiClient);
52
130
  const filtered = typeof args.options.country === "string" && !!args.options.country || typeof args.options.chart === "string" && !!args.options.chart;
53
131
  const model = kind === "hiring" ? hiringModel(data as HiringPayload | HiringBoard) : appsModel(data as AppAttentionPayload, undefined, filtered);
@@ -55,10 +133,28 @@ export function attentionHeadless(kind: AttentionKind): HeadlessPaneDefinition<"
55
133
  // The free preview ignores the offset, so it has no next page to point at.
56
134
  const nextOffset = kind === "hiring" && "total" in data ? !data.preview && offset + data.companies.length < data.total ? offset + data.companies.length : null : (data as AppAttentionPayload).page?.nextOffset ?? null;
57
135
  const lock = kind === "hiring" ? hiringPreviewNote(data as HiringPayload | HiringBoard) : appsPreviewNote(data as AppAttentionPayload);
58
- const notes = [...model.notices, ...(nextOffset !== null ? [`Additional observations available with --offset ${nextOffset}.`] : [])];
136
+ const movesData = movesRead ? await movesRead : null;
137
+ const movesModel = movesData && !(movesData instanceof Error) ? "moves" in movesData ? movesBoardModel(movesData, movesFilter) : companyMovesModel(movesData) : null;
138
+ const movesLock = movesData && !(movesData instanceof Error) ? movesPreviewNote(movesData, movesFilter) : null;
139
+ const signalsData = signalsRead ? await signalsRead : null;
140
+ const signalsReads = !signalsData || signalsData instanceof Error ? [] : args.symbols[0] ? SIGNAL_GROUPS.map((group) => ({ group, data: signalsData[0]! })) : signalsData.map((data, index) => ({ group: SIGNAL_GROUPS[index]!, data }));
141
+ const signalsLocks = !signalsData || signalsData instanceof Error ? [] : signalsData.map((data) => signalsPreviewNote(data, "group" in data && isSignalsFilter(data.group, signalsKind) ? signalsKind : undefined));
142
+ const buildingData = buildingRead ? await buildingRead : null;
143
+ const buildingLock = buildingData && !(buildingData instanceof Error) ? buildingPreviewNote(buildingData, dimension) : null;
144
+ const buildingHeld = buildingData && !(buildingData instanceof Error) && !("filter" in buildingData) ? companyBuildingModel(buildingData).held : null;
145
+ // A company's move says why in one sentence; the board's rows each carry theirs.
146
+ const why = movesModel?.latest?.why;
147
+ const company = args.symbols[0] ? signalsReads[0]?.data : undefined;
148
+ const held = company && !("group" in company) && company.held ? holdText(company.held) : null;
149
+ const notes = [...model.notices, ...(nextOffset !== null ? [`Additional observations available with --offset ${nextOffset}.`] : []),
150
+ ...(movesData instanceof Error ? [movesData.message] : []), ...(why ? [why] : []),
151
+ ...(signalsData instanceof Error ? [signalsData.message] : []), ...(held ? [`No expansion or anomaly signal this week: ${held.toLowerCase()}.`] : []),
152
+ ...(buildingData instanceof Error ? [buildingData.message] : []), ...(buildingHeld ? [`No mix to compare this week: ${buildingHeld.toLowerCase()}.`] : [])];
153
+ const movesSections = !movesModel ? [] : (args.symbols[0] ? ([["weeks", "Hiring moves"], ["drivers", "Move drivers"], ["added", "Postings first seen"]] as const) : ([["board", "Hiring moves"]] as const))
154
+ .map(([id, title]) => movesSection(title, movesModel.sections[id]!, id === "board"));
59
155
  return {
60
156
  complete: !model.preview && !model.partial && nextOffset === null,
61
- ...(lock ? { errors: [lock] } : {}),
157
+ ...(lock || movesLock || buildingLock || signalsLocks.some(Boolean) ? { errors: [lock, movesLock, ...signalsLocks, buildingLock].filter((line): line is string => !!line) } : {}),
62
158
  ...(notes.length ? { notes } : {}),
63
159
  // A ticker no observed app is mapped to says so once, not under each empty view.
64
160
  ...(model.unmapped ? { empty: model.unmapped } : {}),
@@ -68,7 +164,7 @@ export function attentionHeadless(kind: AttentionKind): HeadlessPaneDefinition<"
68
164
  return { title: attentionSectionTitle(kind, tab, args.symbols[0]),
69
165
  columns: section.columns.map(reportColumn), empty: section.empty, ...(section.locked ? { locked: true } : {}),
70
166
  rows: section.rows.map((row) => ({ ...row.values, ...(row.url ? { sourceUrl: row.url } : {}), ...(row.details ? { evidence: row.details } : {}) })) };
71
- }),
167
+ }).concat(movesSections, signalsSections(signalsReads, signalsKind), buildingData && !(buildingData instanceof Error) ? buildingSections(buildingData, dimension) : []),
72
168
  metadata: { nextOffset, asOf: model.asOf, preview: model.preview, lockedRows: model.locked, unit: model.chart.unit, provenance: data },
73
169
  };
74
170
  },
@@ -1,12 +1,12 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import type { PaneTemplateDef } from "../../../types/plugin";
3
- import { appRankCache, attentionCache } from "./client";
3
+ import { appRankCache, attentionCache, buildingCache, movesCache, signalsCache } from "./client";
4
4
  import { attentionHeadless } from "./headless";
5
5
  import { AppsPane, HiringPane } from "./pane";
6
6
  import { COMPANY_ATTENTION_SERIES } from "./series";
7
7
 
8
8
  const definitions = [
9
- { id: "hiring", prefix: "HIRE", label: "Hiring Momentum", component: HiringPane, description: "Pro hiring momentum: observed weekly open roles, additions, removals, role and location mix, peer comparisons and primary posting evidence.", keywords: ["hiring", "workforce", "roles", "recruitment", "surge", "freeze"] },
9
+ { id: "hiring", prefix: "HIRE", label: "Hiring Momentum", component: HiringPane, description: "Pro hiring momentum: observed weekly open roles, additions, removals, role and location mix, peer comparisons, hiring moves against each company's own weeks and peers, first roles in a new country or city, place and role-family spikes, and primary posting evidence.", keywords: ["hiring", "workforce", "roles", "recruitment", "surge", "freeze"] },
10
10
  { id: "apps", prefix: "APPS", label: "App Attention", component: AppsPane, description: "Pro app attention: public global app ranks, rank velocity, rating drift, company ownership and source evidence.", keywords: ["apps", "app attention", "mobile", "rank", "ratings", "developer"] },
11
11
  ] as const;
12
12
  const templates: PaneTemplateDef[] = definitions.map((def) => ({
@@ -22,11 +22,14 @@ export const companyAttentionModule: PluginModule = {
22
22
  series: COMPANY_ATTENTION_SERIES,
23
23
  setup(ctx) {
24
24
  attentionCache.attach(ctx.persistence);
25
+ movesCache.attach(ctx.persistence);
26
+ signalsCache.attach(ctx.persistence);
27
+ buildingCache.attach(ctx.persistence);
25
28
  appRankCache.attach(ctx.persistence);
26
29
  ctx.registerTickerResearchTab({ id: "hiring-momentum", name: "Hiring momentum", order: 38, component: HiringPane, instruments: ["equity"] });
27
30
  ctx.registerTickerResearchTab({ id: "app-attention", name: "Apps", order: 39, component: AppsPane, instruments: ["equity"] });
28
31
  },
29
- dispose() { attentionCache.reset(); appRankCache.reset(); },
32
+ dispose() { attentionCache.reset(); movesCache.reset(); signalsCache.reset(); buildingCache.reset(); appRankCache.reset(); },
30
33
  panes: definitions.map((def) => ({ id: def.id, name: def.label, icon: def.prefix[0]!, component: def.component, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 130, height: 32 }, tickerFollower: true, instruments: ["equity"], tableExport: true, headless: attentionHeadless(def.id) })),
31
34
  paneTemplates: templates,
32
35
  };
@@ -5,6 +5,8 @@ import type { AppAttentionPayload, AppCompany } from "../../../api-client/app-at
5
5
  import { humanLabel } from "../shared/research-cells";
6
6
  import { formatNumber } from "../../../utils/format";
7
7
  import { countOf, freePreviewNote } from "../shared/free-preview";
8
+ import { countryName, postingPlace, postingTitle } from "../shared/job-posting";
9
+ import { formatShortDate } from "../../../utils/datetime-format";
8
10
 
9
11
  export type AttentionKind = "hiring" | "apps";
10
12
  export type AttentionTab = "table" | "chart" | "mix" | "peers" | "evidence";
@@ -14,9 +16,15 @@ export interface AttentionRow {
14
16
  symbol?: string;
15
17
  date?: string;
16
18
  url?: string | null;
17
- details?: Array<{ label: string; value: string }>;
19
+ /** What the row's detail reads: facts in words, only those the row has (no dashes, ids, revisions or confidences). */
20
+ details?: AttentionFact[];
21
+ /** Links the detail offers by name (an ownership filing, a ratings page), never as a bare address. */
22
+ links?: Array<{ label: string; url: string }>;
23
+ /** A posting whose page the detail reads: its title, place, date, pay, hours and description. */
24
+ posting?: { ticker: string; externalId: string };
18
25
  app?: AppFocus;
19
26
  }
27
+ interface AttentionFact { label: string; value: string }
20
28
  export interface AppFocus { store: "app-store" | "google-play"; appId: string; name: string; country: string; chart: "free" | "paid" | "grossing" | "unranked" }
21
29
  interface AttentionSection {
22
30
  columns: DataTableColumn[];
@@ -48,6 +56,17 @@ export function attentionTabs(kind: AttentionKind, symbol: string | null | undef
48
56
  return kind === "apps" || symbol ? ["table", "chart", "mix", "peers", "evidence"] : ["table"];
49
57
  }
50
58
 
59
+ /**
60
+ * A pane tab: a report view, or HIRE's moves, expansion, anomalies and
61
+ * building, which have their own reads (moves-model.ts, signals-model.ts,
62
+ * building-model.ts).
63
+ */
64
+ export type AttentionPaneTab = AttentionTab | "moves" | "expansion" | "anomalies" | "building";
65
+ const HIRING_READ_TABS = ["moves", "expansion", "anomalies", "building"] as const;
66
+ export function attentionPaneTabs(kind: AttentionKind, symbol: string | null | undefined): AttentionPaneTab[] {
67
+ return kind === "hiring" ? [...attentionTabs(kind, symbol), ...HIRING_READ_TABS] : attentionTabs(kind, symbol);
68
+ }
69
+
51
70
  /**
52
71
  * A view's heading where the report prints it in full, as `fn HIRE` does:
53
72
  * what its rows are (Open roles, Hiring trend), where the pane's tab says it
@@ -81,7 +100,12 @@ export function remoteText(percentRemote: number | null | undefined, known: numb
81
100
  return `${formatNumber(percentRemote, 0)}% of ${known == null ? "" : `${formatNumber(known, 0)} `}known`;
82
101
  }
83
102
  const signed = (value: number | null | undefined, decimals = 0) => value == null ? "--" : `${value > 0 ? "+" : ""}${value.toLocaleString("en-US", { maximumFractionDigits: decimals })}`;
84
- const percent = (value: number | null | undefined) => value == null ? "--" : `${(value * 100).toFixed(1)}%`;
103
+ /** A date in words ("Oct 22, 2026"), for a capture's ISO timestamp. */
104
+ const day = (value: string | null | undefined) => value ? formatShortDate(value.slice(0, 10), { utc: true, fallback: "" }) || null : null;
105
+ /** The facts that have a value, in order. */
106
+ const facts = (entries: Array<[string, string | number | null | undefined]>): AttentionFact[] =>
107
+ entries.flatMap(([label, value]) => value == null || value === "" ? [] : [{ label, value: String(value) }]);
108
+ const CAPTURES: Record<string, string> = { complete: "Complete", partial: "Partial", unknown: "Coverage not known" };
85
109
  const column = (id: string, label: string, width: number, numeric = false, flex = false): DataTableColumn => ({ id, label, width, align: numeric ? "right" : "left", ...(flex ? { flexGrow: 1 } : {}) });
86
110
  const roleFamily = (value: string | null) => value === "data_ai" || value === "data ai" ? "Data / AI" : value ? humanLabel(value) : null;
87
111
  const COMPANY_COLUMNS = [column("symbol", "Ticker", 12), column("name", "Company", 24, false, true), column("open", "Open roles", 12, true), column("net", "Net / week", 12, true), column("remote", "Remote %", 17, true), column("z", "Z-score", 10, true), column("signal", "Signal", 12), column("date", "As of", 12)];
@@ -140,7 +164,7 @@ export function hiringModel(data: HiringBoard | HiringPayload, mix = "functions"
140
164
  const history = detail?.series ?? [];
141
165
  const latest = detail?.latest;
142
166
  const mixKey = ["functions", "seniority", "countries", "locations", "signals"].includes(mix) ? mix as "functions" | "seniority" | "countries" | "locations" | "signals" : "functions";
143
- const rows = mixKey === "signals" ? (detail?.signals ?? []).map((row, i): AttentionRow => ({ id: `${row.type}:${i}`, url: row.evidenceUrls[0], values: { label: row.label, count: row.value, share: row.confidence * 100, date: row.asOf.slice(0, 10) }, details: [{ label: "Observed", value: row.asOf }, { label: "Confidence", value: percent(row.confidence) }, { label: "Location", value: row.location ?? "--" }] }))
167
+ const rows = mixKey === "signals" ? (detail?.signals ?? []).map((row, i): AttentionRow => ({ id: `${row.type}:${i}`, url: row.evidenceUrls[0], values: { label: row.label, count: row.value, share: row.confidence * 100, date: row.asOf.slice(0, 10) }, details: facts([["As of", day(row.asOf)], ["Location", row.location]]) }))
144
168
  : (detail?.[mixKey] ?? []).map((row): AttentionRow => ({ id: row.id, values: { label: mixKey === "functions" ? roleFamily(row.label) : mixKey === "seniority" ? humanLabel(row.label) : row.label, count: row.count, share: row.share * 100 } }));
145
169
  const model: AttentionModel = {
146
170
  asOf: latest?.observedAt ?? data.generatedAt,
@@ -160,10 +184,11 @@ export function hiringModel(data: HiringBoard | HiringPayload, mix = "functions"
160
184
  ] : [],
161
185
  sections: {
162
186
  table: { columns: COMPANY_COLUMNS, rows: companies.map(companyRow), empty: "Hiring observations are not available yet." },
163
- chart: { columns: HISTORY_COLUMNS, rows: [...history].reverse().map((row): AttentionRow => ({ id: row.week, date: row.week, values: { date: row.week, open: row.openCount, added: row.added, removed: row.removed, net: row.netChange, change: row.changePct, remote: row.remoteShare == null ? null : row.remoteShare * 100, remoteKnown: row.remoteKnown, coverage: row.completeness }, details: [{ label: "Observed", value: row.observedAt }, { label: "Confidence", value: percent(row.confidence) }, { label: "Capture", value: row.kind }, { label: "Evergreen roles", value: count(row.evergreenCount) }] })), empty: hiringHistoryLocked(data) ? "Weekly history is part of Gloom Pro." : "History is accumulating from the first capture.", ...(hiringHistoryLocked(data) ? { locked: true } : {}) },
187
+ chart: { columns: HISTORY_COLUMNS, rows: [...history].reverse().map((row): AttentionRow => ({ id: row.week, date: row.week, values: { date: row.week, open: row.openCount, added: row.added, removed: row.removed, net: row.netChange, change: row.changePct, remote: row.remoteShare == null ? null : row.remoteShare * 100, remoteKnown: row.remoteKnown, coverage: row.completeness }, details: facts([["Captured", day(row.observedAt)], ["Coverage", CAPTURES[row.completeness] ?? null], ["Open roles", count(row.openCount)], ["Standing roles", row.evergreenCount ? count(row.evergreenCount) : null]]) })), empty: hiringHistoryLocked(data) ? "Weekly history is part of Gloom Pro." : "History is accumulating from the first capture.", ...(hiringHistoryLocked(data) ? { locked: true } : {}) },
164
188
  mix: { columns: [column("label", mixKey === "signals" ? "Signal" : "Group", 28, false, true), column("count", mixKey === "signals" ? "Value" : "Roles", 12, true), column("share", mixKey === "signals" ? "Confidence %" : "Share %", mixKey === "signals" ? 14 : 30, true), ...(mixKey === "signals" ? [column("date", "Observed", 12)] : [])], rows, empty: "No classified observations for this group yet." },
165
189
  peers: { columns: COMPANY_COLUMNS, rows: (detail?.peers ?? []).map(companyRow), empty: "No comparable mapped peers yet." },
166
- evidence: { columns: [column("title", "Role", 32, false, true), column("location", "Location", 24), column("function", "Function", 18), column("seniority", "Seniority", 13), column("date", "Observed", 12), column("confidence", "Confidence %", 13, true)], rows: (detail?.evidence ?? []).map((row): AttentionRow => ({ id: `${row.snapshotId}:${row.id}`, url: row.url ?? row.sourceUrl, values: { title: row.title, location: row.location, function: roleFamily(row.jobFunction), seniority: row.seniority ? humanLabel(row.seniority) : null, date: row.observedAt.slice(0, 10), confidence: row.confidence * 100 }, details: [{ label: "Posting title", value: row.title }, { label: "Location", value: row.location ?? "--" }, { label: "Country", value: row.country ?? "Unknown" }, { label: "Function", value: row.jobFunction ?? "Unclassified" }, { label: "Seniority", value: row.seniority ?? "Unclassified" }, { label: "Remote", value: row.remote === null ? "Unknown" : row.remote ? "Yes" : "No" }, { label: "Observed", value: row.observedAt }, { label: "Revision", value: String(row.revision) }, { label: "Confidence", value: percent(row.confidence) }, { label: "Reporting ticker", value: row.sourceTicker }] })), empty: "No primary posting evidence is available yet." },
190
+ evidence: { columns: [column("title", "Role", 32, false, true), column("location", "Location", 24), column("function", "Function", 18), column("seniority", "Seniority", 13), column("date", "Observed", 12), column("confidence", "Confidence %", 13, true)], rows: (detail?.evidence ?? []).map((row): AttentionRow => ({ id: `${row.snapshotId}:${row.id}`, url: row.url ?? row.sourceUrl, values: { title: postingTitle(row.title, row.id), location: row.location, function: roleFamily(row.jobFunction), seniority: row.seniority ? humanLabel(row.seniority) : null, date: row.observedAt.slice(0, 10), confidence: row.confidence * 100 }, details: facts([["Place", postingPlace(row)], ["Role family", roleFamily(row.jobFunction)], ["Seniority", row.seniority ? humanLabel(row.seniority) : null], ["Seen", day(row.observedAt)]]),
191
+ posting: { ticker: row.sourceTicker, externalId: row.id } })), empty: "No primary posting evidence is available yet." },
167
192
  },
168
193
  chart: { label: "Open roles", unit: "roles", points: history.map((point) => ({ date: point.week, value: point.openCount })), since: detail?.coverage.firstObservedAt ?? null },
169
194
  };
@@ -215,7 +240,8 @@ function appsUnmappedReason(data: AppAttentionPayload, filtered: boolean): strin
215
240
  /** `filtered`: a country or chart narrows the rows, so an empty table is about the filter, not the company. */
216
241
  export function appsModel(data: AppAttentionPayload, mix = "countries", filtered = false): AttentionModel {
217
242
  const unmapped = appsUnmappedReason(data, filtered);
218
- const evidence = data.evidence.map((row): AttentionRow => ({ id: `${row.revisionId}:${row.appId}`, url: row.sourceUrl, values: { name: row.name, country: row.country, chart: row.chart, rank: row.rank, observed: row.observedAt.slice(0, 10), confidence: row.confidence * 100 }, details: [{ label: "App", value: row.name }, { label: "App ID", value: row.appId }, { label: "Country / chart", value: `${row.country} / ${row.chart}` }, { label: "Rank", value: count(row.rank) }, { label: "Rating", value: row.rating?.toFixed(2) ?? "--" }, { label: "Ratings", value: count(row.ratingCount) }, { label: "Written reviews", value: count(row.reviewCount) }, { label: "Observed", value: row.observedAt }, { label: "Published", value: row.sourceUpdatedAt ?? "Unavailable" }, { label: "Revision", value: row.revisionId }, { label: "Supersedes", value: row.supersedesRevisionId ?? "Initial capture" }, { label: "Mapping confidence", value: percent(row.confidence) }, ...(row.mappingSourceUrl ? [{ label: "Ownership evidence", value: row.mappingSourceUrl }] : []), ...(row.ratingSourceUrl ? [{ label: "Rating evidence", value: row.ratingSourceUrl }] : [])] }));
243
+ const evidence = data.evidence.map((row): AttentionRow => ({ id: `${row.revisionId}:${row.appId}`, url: row.sourceUrl, values: { name: row.name, country: row.country, chart: row.chart, rank: row.rank, observed: row.observedAt.slice(0, 10), confidence: row.confidence * 100 }, details: facts([["Chart", `${countryName(row.country) ?? row.country} · ${humanLabel(row.chart)}`], ["Rank", row.rank == null ? null : count(row.rank)], ["Rating", row.rating == null ? null : `${row.rating.toFixed(2)} / 5`], ["Ratings", row.ratingCount == null ? null : count(row.ratingCount)], ["Written reviews", row.reviewCount == null ? null : count(row.reviewCount)], ["Seen", day(row.observedAt)], ["Updated in store", day(row.sourceUpdatedAt)]]),
244
+ links: [...(row.mappingSourceUrl ? [{ label: "Ownership", url: row.mappingSourceUrl }] : []), ...(row.ratingSourceUrl ? [{ label: "Ratings", url: row.ratingSourceUrl }] : [])] }));
219
245
  return {
220
246
  asOf: data.summary.lastObservedAt,
221
247
  preview: data.access === "preview",
@@ -232,7 +258,7 @@ export function appsModel(data: AppAttentionPayload, mix = "countries", filtered
232
258
  ...(data.summary.ratingCountGrowth7d != null ? [{ label: "Ratings added", value: signed(data.summary.ratingCountGrowth7d), detail: "7D" }] : []),
233
259
  ],
234
260
  sections: {
235
- table: { columns: APP_COLUMNS, rows: data.apps.map((row): AttentionRow => ({ id: `${row.sourceId}:${row.appId}`, app: { store: row.store, appId: row.appId, name: row.name, country: row.country, chart: row.chart }, symbol: row.symbol ?? undefined, url: row.appUrl, values: { name: row.name, symbol: row.symbol, country: row.country, chart: row.chart, rank: row.rank, change: row.rankChange7d, rating: row.rating, ratings: row.ratingCount, drift: row.ratingChange7d, growth: row.ratingCountGrowth7d }, details: [{ label: "App", value: row.name }, { label: "Developer", value: row.developer }, { label: "Parent ticker", value: row.symbol ?? "Unmapped" }, { label: "Genres", value: row.genres.join(", ") || "--" }, { label: "Observed", value: row.observedAt }, { label: "Ratings observed", value: row.ratingObservedAt ?? "Unavailable" }, { label: "Mapping confidence", value: percent(row.confidence) }, { label: "Mapping revision", value: row.mappingRevisionId ?? "Unmapped" }, { label: "Capture revision", value: row.revisionId }] })), empty: unmapped ?? "No app observations match these filters yet." },
261
+ table: { columns: APP_COLUMNS, rows: data.apps.map((row): AttentionRow => ({ id: `${row.sourceId}:${row.appId}`, app: { store: row.store, appId: row.appId, name: row.name, country: row.country, chart: row.chart }, symbol: row.symbol ?? undefined, url: row.appUrl, values: { name: row.name, symbol: row.symbol, country: row.country, chart: row.chart, rank: row.rank, change: row.rankChange7d, rating: row.rating, ratings: row.ratingCount, drift: row.ratingChange7d, growth: row.ratingCountGrowth7d }, details: facts([["Developer", row.developer], ["Company", row.symbol], ["Genres", row.genres.join(", ")], ["Seen", day(row.observedAt)], ["Ratings seen", day(row.ratingObservedAt)]]) })), empty: unmapped ?? "No app observations match these filters yet." },
236
262
  chart: { columns: [column("date", "Date", 12), column("score", "Score / 100", 14, true), column("change", "Change", 10, true), column("apps", "Apps", 9, true), column("countries", "Countries", 12, true), column("rating", "Rating / 5", 12, true), column("ratings", "Ratings", 13, true)], rows: data.history.map((point, i, all): AttentionRow => ({ id: point.date, date: point.date, values: { date: point.date, score: point.attentionScore, change: point.attentionScore != null && all[i - 1]?.attentionScore != null ? point.attentionScore - all[i - 1]!.attentionScore! : null, apps: point.appCount, countries: point.countryCount, rating: point.rating, ratings: point.ratingCount } })).reverse(), empty: data.access === "preview" && data.locked.history > 0 ? "Daily history is part of Gloom Pro." : "History is accumulating from the first capture.", ...(data.access === "preview" && data.locked.history > 0 ? { locked: true } : {}) },
237
263
  mix: mix === "spreads" ? { columns: [column("name", "App", 28, false, true), column("chart", "Chart", 10), column("best", "Best country", 15), column("bestRank", "Best rank", 11, true), column("worst", "Worst country", 15), column("worstRank", "Worst rank", 12, true), column("spread", "Rank spread", 13, true)], rows: data.spreads.map((row): AttentionRow => ({ id: `${row.appId}:${row.chart}:${row.category}`, values: { name: row.name, chart: row.chart, best: row.bestCountry, bestRank: row.bestRank, worst: row.worstCountry, worstRank: row.worstRank, spread: row.spread } })), empty: "Comparable country observations are accumulating." }
238
264
  : { columns: [column("country", "Country", 16, false, true), column("apps", "Apps", 9, true), column("ranked", "Ranked apps", 13, true), column("score", "Score / 100", 14, true), column("velocity", "Places / day", 15, true), column("date", "Observed", 12)], rows: data.countries.map((row): AttentionRow => ({ id: row.country, values: { country: row.country, apps: row.appCount, ranked: row.rankedApps, score: row.attentionScore, velocity: row.rankVelocity7d, date: row.observedAt.slice(0, 10) } })), empty: "Country observations are not available yet." },