gloomberb 0.9.7 → 0.9.9

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 (276) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/auth.ts +20 -0
  3. package/src/api-client/index.test.ts +3 -3
  4. package/src/api-client/index.ts +48 -6
  5. package/src/api-client/request.test.ts +63 -0
  6. package/src/api-client/request.ts +53 -5
  7. package/src/api-client/types.ts +45 -2
  8. package/src/app/pane-runtime/layout-placement.ts +0 -1
  9. package/src/app/runtime/desktop-deeplink.test.ts +87 -0
  10. package/src/app/runtime/desktop-deeplink.ts +496 -0
  11. package/src/app.tsx +34 -0
  12. package/src/brokers/account-cache.ts +2 -10
  13. package/src/brokers/broker-portfolio-sync.ts +0 -1
  14. package/src/brokers/broker-ticker-sync.ts +20 -16
  15. package/src/brokers/remote-broker-adapter.ts +3 -0
  16. package/src/capabilities/registry.test.ts +25 -0
  17. package/src/capabilities/registry.ts +22 -5
  18. package/src/cli/commands/automation.ts +1 -3
  19. package/src/cli/commands/command-utils.ts +0 -7
  20. package/src/cli/commands/overview.ts +2 -2
  21. package/src/cli/commands/plugins.ts +34 -17
  22. package/src/cli/index.ts +2 -2
  23. package/src/cli.test.ts +0 -157
  24. package/src/components/chart/bar-chart-renderer.test.ts +87 -12
  25. package/src/components/chart/bar-chart-renderer.ts +111 -55
  26. package/src/components/chart/comparison/renderer.ts +0 -1
  27. package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
  28. package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
  29. package/src/components/chart/core/index.test.ts +0 -24
  30. package/src/components/chart/core/renderer.ts +0 -4
  31. package/src/components/chart/core/resolution.test.ts +0 -17
  32. package/src/components/chart/core/resolution.ts +0 -26
  33. package/src/components/chart/core/scroll.test.ts +0 -6
  34. package/src/components/chart/core/scroll.ts +0 -22
  35. package/src/components/chart/core/viewport.test.ts +2 -23
  36. package/src/components/chart/core/viewport.ts +0 -27
  37. package/src/components/chart/market-session.ts +1 -1
  38. package/src/components/chart/native/surface/hooks.ts +10 -3
  39. package/src/components/chart/static/bar-chart-surface.tsx +104 -20
  40. package/src/components/chart/stock-chart/data-runtime.ts +0 -1
  41. package/src/components/chart/stock-chart/footer.test.ts +65 -0
  42. package/src/components/chart/stock-chart/footer.ts +68 -13
  43. package/src/components/chart/stock-chart/resolved.tsx +18 -4
  44. package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
  45. package/src/components/command-bar/helpers.test.ts +1 -80
  46. package/src/components/command-bar/helpers.ts +1 -36
  47. package/src/components/command-bar/pane-settings.ts +1 -1
  48. package/src/components/command-bar/pane-templates/items.ts +11 -3
  49. package/src/components/command-bar/panel/runtime.ts +0 -2
  50. package/src/components/command-bar/routes/root/results.ts +1 -1
  51. package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
  52. package/src/components/command-bar/surface/index.test.tsx +52 -16
  53. package/src/components/command-bar/surface/index.tsx +0 -1
  54. package/src/components/command-bar/workflow/ops.ts +81 -20
  55. package/src/components/feed-data-table/stack-view.test.tsx +36 -130
  56. package/src/components/feed-data-table/stack-view.tsx +0 -1
  57. package/src/components/index.ts +1 -0
  58. package/src/components/layout/shell/index.test.tsx +2 -113
  59. package/src/components/layout/shell/index.tsx +0 -1
  60. package/src/components/layout/shell/menu.ts +0 -1
  61. package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
  62. package/src/components/onboarding/onboarding-wizard.tsx +3 -3
  63. package/src/components/onboarding/wizard-keyboard.ts +2 -23
  64. package/src/components/price-selector-dialog.tsx +3 -3
  65. package/src/components/ticker/badge/text.tsx +0 -1
  66. package/src/components/toggle-list.tsx +29 -73
  67. package/src/components/ui/button.tsx +4 -1
  68. package/src/components/ui/checkbox.tsx +76 -0
  69. package/src/components/ui/choice-dialog.tsx +15 -3
  70. package/src/components/ui/frame.tsx +1 -1
  71. package/src/components/ui/index.ts +2 -0
  72. package/src/components/ui/message-composer.tsx +4 -0
  73. package/src/components/ui/native-select.tsx +4 -2
  74. package/src/components/ui/page-stack-view.tsx +1 -1
  75. package/src/components/ui/ui.test.tsx +0 -78
  76. package/src/core/state/app/layout-reducer.ts +0 -1
  77. package/src/data/config/layout.ts +3 -3
  78. package/src/data/resource-store.ts +6 -0
  79. package/src/data/sqlite/cache.test.ts +5 -0
  80. package/src/data/ticker-store.ts +0 -7
  81. package/src/market-data/coordinator/auxiliary.ts +1 -44
  82. package/src/market-data/coordinator/entries.ts +0 -1
  83. package/src/market-data/coordinator/events.test.ts +49 -0
  84. package/src/market-data/coordinator/events.ts +25 -4
  85. package/src/market-data/coordinator/index.test.ts +223 -10
  86. package/src/market-data/coordinator/index.ts +41 -21
  87. package/src/market-data/coordinator/quotes.ts +13 -11
  88. package/src/market-data/coordinator/subscriptions.test.ts +1 -0
  89. package/src/market-data/hooks.tsx +17 -13
  90. package/src/market-data/market/freshness.ts +58 -1
  91. package/src/market-data/market/status.ts +19 -1
  92. package/src/market-data/query-store.ts +0 -4
  93. package/src/market-data/quotes/freshness.test.ts +49 -1
  94. package/src/market-data/quotes/freshness.ts +8 -0
  95. package/src/market-data/quotes/resolution.test.ts +185 -0
  96. package/src/market-data/quotes/resolution.ts +96 -10
  97. package/src/market-data/request-types.test.ts +61 -1
  98. package/src/market-data/request-types.ts +42 -8
  99. package/src/market-data/result-types.ts +0 -11
  100. package/src/market-data/selectors.ts +1 -5
  101. package/src/news/aggregator.ts +5 -1
  102. package/src/pane-settings.test.ts +0 -18
  103. package/src/plugins/builtin/account-management/footer.ts +2 -5
  104. package/src/plugins/builtin/account-management/form-components.tsx +175 -59
  105. package/src/plugins/builtin/account-management/keyboard.ts +32 -2
  106. package/src/plugins/builtin/account-management/model.test.ts +99 -4
  107. package/src/plugins/builtin/account-management/model.ts +259 -23
  108. package/src/plugins/builtin/account-management/pane.tsx +784 -155
  109. package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
  110. package/src/plugins/builtin/buildout/cells.tsx +1 -1
  111. package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
  112. package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
  113. package/src/plugins/builtin/buildout/format.ts +2 -11
  114. package/src/plugins/builtin/chat/chat.test.tsx +127 -1
  115. package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
  116. package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
  117. package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
  118. package/src/plugins/builtin/chat/content/composer.tsx +84 -1
  119. package/src/plugins/builtin/chat/content/index.tsx +153 -16
  120. package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
  121. package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
  122. package/src/plugins/builtin/chat/content/mentions.ts +88 -0
  123. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
  124. package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
  125. package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
  126. package/src/plugins/builtin/chat/content/transcript.tsx +0 -5
  127. package/src/plugins/builtin/chat/controller/index.ts +0 -10
  128. package/src/plugins/builtin/chat/controller.test.ts +5 -5
  129. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
  130. package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
  131. package/src/plugins/builtin/chat/sidebar.test.tsx +83 -6
  132. package/src/plugins/builtin/chat/sidebar.tsx +26 -2
  133. package/src/plugins/builtin/chat/test-harness.tsx +22 -12
  134. package/src/plugins/builtin/cloud/plugin.tsx +8 -1
  135. package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
  136. package/src/plugins/builtin/congress-trades/model.ts +2 -7
  137. package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
  138. package/src/plugins/builtin/correlation/index.tsx +4 -1
  139. package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
  140. package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
  141. package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
  142. package/src/plugins/builtin/earnings/index.tsx +0 -9
  143. package/src/plugins/builtin/econ/index.tsx +1 -5
  144. package/src/plugins/builtin/help/index.tsx +2 -2
  145. package/src/plugins/builtin/holders/pane.tsx +0 -5
  146. package/src/plugins/builtin/insider/index.tsx +1 -8
  147. package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
  148. package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
  149. package/src/plugins/builtin/market-movers/index.tsx +0 -2
  150. package/src/plugins/builtin/news/index.tsx +0 -1
  151. package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
  152. package/src/plugins/builtin/options/view.test.tsx +1 -38
  153. package/src/plugins/builtin/plugin-groups.ts +17 -3
  154. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
  155. package/src/plugins/builtin/portfolio-list/index.test.ts +52 -25
  156. package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
  157. package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
  158. package/src/plugins/builtin/portfolio-list/pane/data.ts +50 -3
  159. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +314 -16
  160. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -4
  161. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +83 -24
  162. package/src/plugins/builtin/sec/filing-documents.ts +0 -11
  163. package/src/plugins/builtin/sec/index.tsx +0 -2
  164. package/src/plugins/builtin/substack/article-detail.tsx +1 -1
  165. package/src/plugins/builtin/thirteenf/api.ts +0 -15
  166. package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
  167. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +42 -6
  168. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
  169. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
  170. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
  171. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
  172. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
  173. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
  174. package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
  175. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
  176. package/src/plugins/builtin/world-indices/index.tsx +0 -4
  177. package/src/plugins/builtin/world-indices/model.ts +0 -7
  178. package/src/plugins/ibkr/account-cache.ts +1 -1
  179. package/src/plugins/ibkr/gateway/market-data.ts +12 -5
  180. package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
  181. package/src/plugins/ibkr/portfolio-performance.ts +1 -1
  182. package/src/plugins/ibkr/trade/utils.ts +1 -7
  183. package/src/plugins/loader.ts +2 -1
  184. package/src/plugins/prediction-markets/detail.test.tsx +0 -34
  185. package/src/plugins/registry/context.ts +15 -0
  186. package/src/plugins/registry/contributions.ts +22 -11
  187. package/src/plugins/registry/index.test.ts +44 -0
  188. package/src/plugins/registry/index.ts +75 -26
  189. package/src/remote/controller.ts +1 -7
  190. package/src/remote/json-patch.ts +15 -1
  191. package/src/remote/server.ts +16 -6
  192. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  193. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
  194. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
  195. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
  196. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  197. package/src/renderers/electrobun/bun/index.ts +42 -0
  198. package/src/renderers/electrobun/shared/protocol.ts +5 -0
  199. package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
  200. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
  201. package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
  202. package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
  203. package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
  204. package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
  205. package/src/renderers/electrobun/view/host/box.tsx +0 -2
  206. package/src/renderers/electrobun/view/host/mouse.ts +0 -11
  207. package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
  208. package/src/renderers/electrobun/view/host/text.tsx +1 -2
  209. package/src/renderers/electrobun/view/http-fetch.ts +15 -2
  210. package/src/renderers/electrobun/view/main.tsx +3 -0
  211. package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
  212. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
  213. package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
  214. package/src/renderers/electrobun/view/ui-host.tsx +2 -0
  215. package/src/renderers/opentui/host.tsx +11 -3
  216. package/src/renderers/opentui/input-host.tsx +5 -1
  217. package/src/sources/gloomberb-cloud/index.test.ts +0 -72
  218. package/src/sources/gloomberb-cloud/index.ts +1 -30
  219. package/src/sources/gloomberb-cloud/news.ts +0 -12
  220. package/src/sources/provider-router/batches.ts +11 -3
  221. package/src/sources/provider-router/cached-financials.test.ts +123 -0
  222. package/src/sources/provider-router/financial-routes.ts +77 -5
  223. package/src/sources/provider-router/financials.test.ts +49 -0
  224. package/src/sources/provider-router/financials.ts +40 -3
  225. package/src/sources/provider-router/index.test.ts +314 -0
  226. package/src/sources/provider-router/primary.ts +17 -3
  227. package/src/sources/provider-router/search.ts +5 -0
  228. package/src/sources/provider-router/streaming.ts +6 -1
  229. package/src/sources/provider-router/supplemental.ts +4 -3
  230. package/src/sources/sec-edgar.test.ts +42 -0
  231. package/src/sources/sec-edgar.ts +1 -2
  232. package/src/sources/yahoo-finance/history.ts +5 -1
  233. package/src/sources/yahoo-finance/mappers.test.ts +29 -0
  234. package/src/sources/yahoo-finance/mappers.ts +2 -1
  235. package/src/sources/yahoo-finance/options.ts +59 -14
  236. package/src/sources/yahoo-finance/quote-summary.ts +4 -1
  237. package/src/sources/yahoo-finance/requests.ts +2 -1
  238. package/src/sources/yahoo-finance/snapshots.ts +13 -2
  239. package/src/sources/yahoo-finance.test.ts +40 -21
  240. package/src/sources/yahoo-finance.ts +19 -10
  241. package/src/state/app/context/index.test.tsx +0 -81
  242. package/src/state/app/context/selector.test.tsx +6 -2
  243. package/src/state/hooks/quote-streaming.test.ts +14 -1
  244. package/src/state/hooks/quote-streaming.ts +5 -4
  245. package/src/sync/controller.test.ts +31 -0
  246. package/src/sync/controller.ts +300 -0
  247. package/src/sync/core-contributors.test.ts +251 -0
  248. package/src/sync/core-contributors.ts +400 -0
  249. package/src/sync/profile-analytics.ts +39 -0
  250. package/src/sync/react.ts +53 -0
  251. package/src/sync/types.ts +77 -0
  252. package/src/test-support/data-provider.ts +0 -1
  253. package/src/types/desktop-deeplink.ts +7 -0
  254. package/src/types/plugin.ts +3 -0
  255. package/src/ui/host.tsx +15 -1
  256. package/src/ui/index.tsx +2 -0
  257. package/src/updater.test.ts +0 -21
  258. package/src/updater.ts +20 -16
  259. package/src/utils/async-deadline.ts +26 -0
  260. package/src/utils/exchanges.test.ts +11 -1
  261. package/src/utils/exchanges.ts +5 -1
  262. package/src/utils/selection-clipboard.ts +2 -2
  263. package/src/version.ts +1 -1
  264. package/src/components/layout/pane/header.test.tsx +0 -39
  265. package/src/plugins/builtin/chat/channels.test.ts +0 -28
  266. package/src/plugins/builtin/chat/index.tsx +0 -4
  267. package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
  268. package/src/plugins/builtin/earnings/index.test.ts +0 -12
  269. package/src/plugins/builtin/help/index.test.tsx +0 -242
  270. package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
  271. package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
  272. package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
  273. package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
  274. package/src/renderers/electrobun/view/host/text.test.ts +0 -22
  275. package/src/renderers/opentui/dialog-host.test.tsx +0 -44
  276. /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  import type { BarChartSeries } from "../../../../../components/chart/bar-chart-renderer";
2
2
  import { colors } from "../../../../../theme/colors";
3
- import type { FinancialStatement, TickerFinancials } from "../../../../../types/financials";
3
+ import type { FinancialStatement, PricePoint, TickerFinancials } from "../../../../../types/financials";
4
4
  import { formatCompact, formatNumber, formatPercent } from "../../../../../utils/format";
5
5
  import type {
6
6
  FundamentalColumn,
@@ -13,13 +13,62 @@ import type {
13
13
  ValuationMetricKey,
14
14
  } from "./types";
15
15
 
16
+ function finiteNumber(value: unknown): value is number {
17
+ return typeof value === "number" && Number.isFinite(value);
18
+ }
19
+
20
+ function financialRatio(
21
+ numerator: number | null | undefined,
22
+ denominator: number | null | undefined,
23
+ ): number | null {
24
+ return finiteNumber(numerator) && finiteNumber(denominator) && denominator !== 0
25
+ ? numerator / denominator
26
+ : null;
27
+ }
28
+
29
+ function freeCashFlowValue(statement: FinancialStatement): number | null {
30
+ if (finiteNumber(statement.freeCashFlow)) return statement.freeCashFlow;
31
+ if (finiteNumber(statement.operatingCashFlow) && finiteNumber(statement.capitalExpenditure)) {
32
+ return statement.operatingCashFlow + statement.capitalExpenditure;
33
+ }
34
+ return null;
35
+ }
36
+
37
+ function formatMargin(value: number): string {
38
+ return `${formatNumber(value * 100, 1)}%`;
39
+ }
40
+
16
41
  const FUNDAMENTAL_METRICS: ReadonlyArray<MetricDefinition<FundamentalMetricKey>> = [
17
42
  { key: "totalRevenue", label: "Revenue", format: formatCompact },
18
43
  { key: "grossProfit", label: "Gross Profit", format: formatCompact },
44
+ {
45
+ key: "grossMargin",
46
+ label: "Gross Margin",
47
+ format: formatMargin,
48
+ value: (statement) => financialRatio(statement.grossProfit, statement.totalRevenue),
49
+ },
19
50
  { key: "operatingIncome", label: "Operating Income", format: formatCompact },
51
+ {
52
+ key: "operatingMargin",
53
+ label: "Operating Margin",
54
+ format: formatMargin,
55
+ value: (statement) => financialRatio(statement.operatingIncome, statement.totalRevenue),
56
+ },
20
57
  { key: "netIncome", label: "Net Income", format: formatCompact },
58
+ {
59
+ key: "netMargin",
60
+ label: "Net Margin",
61
+ format: formatMargin,
62
+ value: (statement) => financialRatio(statement.netIncome, statement.totalRevenue),
63
+ },
21
64
  { key: "operatingCashFlow", label: "Operating Cash Flow", format: formatCompact },
22
- { key: "freeCashFlow", label: "Free Cash Flow", format: formatCompact },
65
+ { key: "freeCashFlow", label: "Free Cash Flow", format: formatCompact, value: freeCashFlowValue },
66
+ {
67
+ key: "freeCashFlowMargin",
68
+ label: "FCF Margin",
69
+ format: formatMargin,
70
+ value: (statement) => financialRatio(freeCashFlowValue(statement), statement.totalRevenue),
71
+ },
23
72
  { key: "totalAssets", label: "Total Assets", format: formatCompact },
24
73
  { key: "totalDebt", label: "Total Debt", format: formatCompact },
25
74
  { key: "totalEquity", label: "Equity", format: formatCompact },
@@ -52,52 +101,221 @@ export function formatMaybePercent(value: number | null): string {
52
101
  }
53
102
 
54
103
  function selectedStatements(financials: TickerFinancials | null, period: FundamentalPeriod): FinancialStatement[] {
55
- return period === "annual"
56
- ? financials?.annualStatements ?? []
57
- : financials?.quarterlyStatements ?? [];
104
+ if (period === "annual") return financials?.annualStatements ?? [];
105
+ return deriveQuarterlyStatements(financials?.quarterlyStatements ?? [], financials?.annualStatements ?? []);
58
106
  }
59
107
 
60
108
  function periodCategory(date: string, period: FundamentalPeriod): string {
61
- const year = date.slice(0, 4);
62
- if (period === "annual") return `FY${year}`;
63
- const month = Number(date.slice(5, 7));
109
+ const parsedYear = Number(date.slice(0, 4));
110
+ let year = Number.isFinite(parsedYear) ? parsedYear : 0;
111
+ if (period === "annual") return year > 0 ? `FY${year}` : date;
112
+ let month = Number(date.slice(5, 7));
113
+ const day = Number(date.slice(8, 10));
114
+ if (Number.isFinite(month) && Number.isFinite(day) && day <= 7 && (month - 1) % 3 === 0) {
115
+ month -= 1;
116
+ if (month === 0) {
117
+ month = 12;
118
+ year -= 1;
119
+ }
120
+ }
64
121
  const quarter = Number.isFinite(month) && month > 0 ? Math.ceil(month / 3) : 0;
65
- return quarter > 0 ? `${year} Q${quarter}` : date;
122
+ return quarter > 0 && year > 0 ? `${year} Q${quarter}` : date;
66
123
  }
67
124
 
68
- export function buildFundamentalGraphRows(
69
- statements: FinancialStatement[],
70
- metric: FundamentalMetricKey,
71
- symbol = "",
72
- period: FundamentalPeriod = "annual",
125
+ type NumericStatementField = Exclude<keyof FinancialStatement, "date">;
126
+
127
+ const QUARTERLY_FLOW_FIELDS: NumericStatementField[] = [
128
+ "totalRevenue",
129
+ "grossProfit",
130
+ "operatingIncome",
131
+ "netIncome",
132
+ "ebitda",
133
+ "operatingCashFlow",
134
+ "capitalExpenditure",
135
+ "freeCashFlow",
136
+ "eps",
137
+ ];
138
+
139
+ const QUARTERLY_SNAPSHOT_FIELDS: NumericStatementField[] = [
140
+ "totalAssets",
141
+ "cashAndCashEquivalents",
142
+ "cashCashEquivalentsAndShortTermInvestments",
143
+ "totalDebt",
144
+ "totalEquity",
145
+ "basicShares",
146
+ "dilutedShares",
147
+ "shareIssued",
148
+ "ordinarySharesNumber",
149
+ ];
150
+
151
+ function statementNumber(statement: FinancialStatement, field: NumericStatementField): number | null {
152
+ const value = statement[field];
153
+ return finiteNumber(value) ? value : null;
154
+ }
155
+
156
+ function setStatementNumber(statement: FinancialStatement, field: NumericStatementField, value: number): void {
157
+ (statement as Record<NumericStatementField, number | undefined>)[field] = value;
158
+ }
159
+
160
+ function statementTime(statement: FinancialStatement): number {
161
+ const time = Date.parse(statement.date);
162
+ return Number.isFinite(time) ? time : Number.NaN;
163
+ }
164
+
165
+ function precedingQuarterValues(
166
+ quarterlyStatements: FinancialStatement[],
167
+ annualStatement: FinancialStatement,
168
+ field: NumericStatementField,
169
+ ): number[] {
170
+ const annualTime = statementTime(annualStatement);
171
+ if (!Number.isFinite(annualTime)) return [];
172
+
173
+ const annualCategory = periodCategory(annualStatement.date, "quarterly");
174
+ const byCategory = new Map<string, { date: string; time: number; value: number }>();
175
+ for (const statement of quarterlyStatements) {
176
+ const time = statementTime(statement);
177
+ if (!Number.isFinite(time) || time >= annualTime || annualTime - time > 370 * 24 * 60 * 60 * 1000) {
178
+ continue;
179
+ }
180
+ const category = periodCategory(statement.date, "quarterly");
181
+ if (category === annualCategory) continue;
182
+ const value = statementNumber(statement, field);
183
+ if (value === null) continue;
184
+ const previous = byCategory.get(category);
185
+ if (!previous || statement.date.localeCompare(previous.date) > 0) {
186
+ byCategory.set(category, { date: statement.date, time, value });
187
+ }
188
+ }
189
+
190
+ return [...byCategory.values()]
191
+ .sort((left, right) => right.time - left.time)
192
+ .slice(0, 3)
193
+ .sort((left, right) => left.time - right.time)
194
+ .map((entry) => entry.value);
195
+ }
196
+
197
+ function mergeStatementsByDate(statements: FinancialStatement[]): FinancialStatement[] {
198
+ const byDate = new Map<string, FinancialStatement>();
199
+ for (const statement of statements) {
200
+ const existing = byDate.get(statement.date);
201
+ if (!existing) {
202
+ byDate.set(statement.date, { ...statement });
203
+ continue;
204
+ }
205
+ for (const [key, value] of Object.entries(statement) as Array<[keyof FinancialStatement, unknown]>) {
206
+ if (key === "date") continue;
207
+ if (value !== undefined && existing[key] === undefined) {
208
+ (existing as unknown as Record<string, unknown>)[key] = value;
209
+ }
210
+ }
211
+ }
212
+ return [...byDate.values()];
213
+ }
214
+
215
+ function deriveQuarterlyStatements(
216
+ quarterlyStatements: FinancialStatement[],
217
+ annualStatements: FinancialStatement[],
218
+ ): FinancialStatement[] {
219
+ const mergedQuarterly = mergeStatementsByDate(quarterlyStatements);
220
+ const byDate = new Map(mergedQuarterly.map((statement) => [statement.date, { ...statement }]));
221
+
222
+ for (const annualStatement of annualStatements) {
223
+ let target = byDate.get(annualStatement.date);
224
+ let changed = false;
225
+ if (!target) {
226
+ target = { date: annualStatement.date };
227
+ }
228
+
229
+ for (const field of QUARTERLY_FLOW_FIELDS) {
230
+ if (statementNumber(target, field) !== null) continue;
231
+ const annualValue = statementNumber(annualStatement, field);
232
+ if (annualValue === null) continue;
233
+ const previousValues = precedingQuarterValues(mergedQuarterly, annualStatement, field);
234
+ if (previousValues.length !== 3) continue;
235
+ const derived = annualValue - previousValues.reduce((sum, value) => sum + value, 0);
236
+ if (!Number.isFinite(derived)) continue;
237
+ setStatementNumber(target, field, derived);
238
+ changed = true;
239
+ }
240
+
241
+ for (const field of QUARTERLY_SNAPSHOT_FIELDS) {
242
+ if (statementNumber(target, field) !== null) continue;
243
+ const annualValue = statementNumber(annualStatement, field);
244
+ if (annualValue === null) continue;
245
+ setStatementNumber(target, field, annualValue);
246
+ changed = true;
247
+ }
248
+
249
+ if (changed) byDate.set(target.date, target);
250
+ }
251
+
252
+ return [...byDate.values()].sort((left, right) => left.date.localeCompare(right.date));
253
+ }
254
+
255
+ function fundamentalMetricDef(metric: FundamentalMetricKey): MetricDefinition<FundamentalMetricKey> {
256
+ return FUNDAMENTAL_METRICS.find((definition) => definition.key === metric) ?? FUNDAMENTAL_METRICS[0]!;
257
+ }
258
+
259
+ function fundamentalMetricValue(statement: FinancialStatement, metric: FundamentalMetricKey): number | null {
260
+ const definition = fundamentalMetricDef(metric);
261
+ const value = definition.value
262
+ ? definition.value(statement)
263
+ : statement[metric as keyof FinancialStatement];
264
+ return finiteNumber(value) ? value : null;
265
+ }
266
+
267
+ interface GraphRowEntry {
268
+ date: string;
269
+ category: string;
270
+ value: number;
271
+ }
272
+
273
+ function buildRowsFromEntries(
274
+ entries: GraphRowEntry[],
275
+ metric: GraphMetricKey,
276
+ symbol: string,
73
277
  ): FundamentalGraphRow[] {
74
- const sortedStatements = [...statements]
75
- .filter((statement) => typeof statement[metric] === "number")
76
- .sort((left, right) => left.date.localeCompare(right.date));
77
- const byCategory = new Map<string, FinancialStatement>();
78
- for (const statement of sortedStatements) {
79
- const category = periodCategory(statement.date, period);
80
- const key = `${symbol}\u0000${category}`;
81
- const previous = byCategory.get(key);
82
- if (!previous || statement.date.localeCompare(previous.date) >= 0) {
83
- byCategory.set(key, statement);
278
+ const byCategory = new Map<string, GraphRowEntry>();
279
+ for (const entry of [...entries].sort((left, right) => left.date.localeCompare(right.date))) {
280
+ const previous = byCategory.get(entry.category);
281
+ if (!previous || entry.date.localeCompare(previous.date) >= 0) {
282
+ byCategory.set(entry.category, entry);
84
283
  }
85
284
  }
86
285
  const sorted = [...byCategory.values()].sort((left, right) => left.date.localeCompare(right.date));
87
- const maxAbs = Math.max(1, ...sorted.map((statement) => Math.abs(statement[metric] as number)));
88
- return sorted.map((statement, index) => {
89
- const value = statement[metric] as number;
90
- const previous = sorted[index - 1]?.[metric];
286
+ const maxAbs = Math.max(1, ...sorted.map((entry) => Math.abs(entry.value)));
287
+
288
+ return sorted.map((entry, index) => {
289
+ const previous = sorted[index - 1]?.value;
91
290
  return {
92
- key: [symbol, statement.date, metric].filter(Boolean).join(":"),
291
+ key: [symbol, entry.date, metric].filter(Boolean).join(":"),
93
292
  symbol,
293
+ date: entry.date,
294
+ category: entry.category,
295
+ value: entry.value,
296
+ growth: typeof previous === "number" && previous !== 0
297
+ ? (entry.value - previous) / Math.abs(previous)
298
+ : null,
299
+ barWidth: Math.max(1, Math.round((Math.abs(entry.value) / maxAbs) * 24)),
300
+ };
301
+ });
302
+ }
303
+
304
+ export function buildFundamentalGraphRows(
305
+ statements: FinancialStatement[],
306
+ metric: FundamentalMetricKey,
307
+ symbol = "",
308
+ period: FundamentalPeriod = "annual",
309
+ ): FundamentalGraphRow[] {
310
+ const entries = statements
311
+ .map((statement) => ({ statement, value: fundamentalMetricValue(statement, metric) }))
312
+ .filter((entry): entry is { statement: FinancialStatement; value: number } => entry.value !== null)
313
+ .map(({ statement, value }) => ({
94
314
  date: statement.date,
95
315
  category: periodCategory(statement.date, period),
96
316
  value,
97
- growth: typeof previous === "number" && previous !== 0 ? (value - previous) / Math.abs(previous) : null,
98
- barWidth: Math.max(1, Math.round((Math.abs(value) / maxAbs) * 24)),
99
- };
100
- });
317
+ }));
318
+ return buildRowsFromEntries(entries, metric, symbol);
101
319
  }
102
320
 
103
321
  function resolveMarketCap(financials: TickerFinancials | null): number | null {
@@ -111,21 +329,141 @@ function resolveMarketCap(financials: TickerFinancials | null): number | null {
111
329
  : null;
112
330
  }
113
331
 
114
- function valuationMetricValue(
332
+ function timeFromDate(value: Date | string): number {
333
+ return value instanceof Date ? value.getTime() : Date.parse(value);
334
+ }
335
+
336
+ function priceAtOrBefore(priceHistory: PricePoint[], date: string): number | null {
337
+ const target = Date.parse(date);
338
+ if (!Number.isFinite(target)) return null;
339
+
340
+ let closest: { time: number; close: number } | null = null;
341
+ for (const point of priceHistory) {
342
+ if (!finiteNumber(point.close) || point.close <= 0) continue;
343
+ const time = timeFromDate(point.date);
344
+ if (!Number.isFinite(time) || time > target) continue;
345
+ if (!closest || time > closest.time) {
346
+ closest = { time, close: point.close };
347
+ }
348
+ }
349
+ return closest?.close ?? null;
350
+ }
351
+
352
+ function statementShares(statement: FinancialStatement): number | null {
353
+ const shares = statement.dilutedShares
354
+ ?? statement.basicShares
355
+ ?? statement.ordinarySharesNumber
356
+ ?? statement.shareIssued;
357
+ return finiteNumber(shares) && shares > 0 ? shares : null;
358
+ }
359
+
360
+ function statementEpsValue(statement: FinancialStatement): number | null {
361
+ if (finiteNumber(statement.eps) && statement.eps !== 0) return statement.eps;
362
+ const shares = statementShares(statement);
363
+ return finiteNumber(statement.netIncome) && shares ? statement.netIncome / shares : null;
364
+ }
365
+
366
+ function aggregateEps(statements: FinancialStatement[]): number | null {
367
+ if (statements.length === 0) return null;
368
+ const epsValues = statements.map(statementEpsValue);
369
+ if (epsValues.every((value): value is number => finiteNumber(value))) {
370
+ return epsValues.reduce((sum, value) => sum + value, 0);
371
+ }
372
+
373
+ const latestShares = statementShares(statements.at(-1)!);
374
+ const incomeValues = statements.map((statement) => statement.netIncome);
375
+ if (!latestShares || !incomeValues.every((value): value is number => finiteNumber(value))) {
376
+ return null;
377
+ }
378
+ return incomeValues.reduce((sum, value) => sum + value, 0) / latestShares;
379
+ }
380
+
381
+ function trailingEpsValue(
382
+ statements: FinancialStatement[],
383
+ index: number,
384
+ period: FundamentalPeriod,
385
+ ): number | null {
386
+ if (period === "annual") return statementEpsValue(statements[index]!);
387
+ if (index < 3) return null;
388
+ return aggregateEps(statements.slice(index - 3, index + 1));
389
+ }
390
+
391
+ function forwardEpsValue(
392
+ statements: FinancialStatement[],
393
+ index: number,
394
+ period: FundamentalPeriod,
395
+ ): number | null {
396
+ if (period === "annual") {
397
+ const nextStatement = statements[index + 1];
398
+ return nextStatement ? statementEpsValue(nextStatement) : null;
399
+ }
400
+ if (index + 4 >= statements.length) return null;
401
+ return aggregateEps(statements.slice(index + 1, index + 5));
402
+ }
403
+
404
+ function historicalMarketCap(
405
+ financials: TickerFinancials | null,
406
+ statement: FinancialStatement,
407
+ ): number | null {
408
+ const price = financials ? priceAtOrBefore(financials.priceHistory, statement.date) : null;
409
+ const shares = statementShares(statement);
410
+ if (price && shares) return price * shares;
411
+ return resolveMarketCap(financials);
412
+ }
413
+
414
+ function historicalEnterpriseValue(
415
+ financials: TickerFinancials | null,
416
+ statement: FinancialStatement,
417
+ ): number | null {
418
+ const marketCap = historicalMarketCap(financials, statement);
419
+ if (!marketCap) return financials?.fundamentals?.enterpriseValue ?? null;
420
+ const debt = finiteNumber(statement.totalDebt) ? statement.totalDebt : 0;
421
+ const cash = statement.cashCashEquivalentsAndShortTermInvestments
422
+ ?? statement.cashAndCashEquivalents
423
+ ?? 0;
424
+ return marketCap + debt - (finiteNumber(cash) ? cash : 0);
425
+ }
426
+
427
+ function currentValuationMetricValue(
115
428
  financials: TickerFinancials | null,
116
429
  metric: ValuationMetricKey,
117
- statement: FinancialStatement | null,
118
430
  ): number | null {
119
431
  const fundamentals = financials?.fundamentals;
120
432
  if (metric === "trailingPE") return fundamentals?.trailingPE ?? null;
121
433
  if (metric === "forwardPE") return fundamentals?.forwardPE ?? null;
122
434
  if (metric === "pegRatio") return fundamentals?.pegRatio ?? null;
435
+ return null;
436
+ }
437
+
438
+ function valuationMetricValue(
439
+ financials: TickerFinancials | null,
440
+ metric: ValuationMetricKey,
441
+ statement: FinancialStatement | null,
442
+ context?: {
443
+ statements: FinancialStatement[];
444
+ statementIndex: number;
445
+ period: FundamentalPeriod;
446
+ },
447
+ ): number | null {
448
+ if (metric === "pegRatio" || statement === null) {
449
+ return currentValuationMetricValue(financials, metric);
450
+ }
123
451
 
124
- const marketCap = resolveMarketCap(financials);
125
- const enterpriseValue = fundamentals?.enterpriseValue ?? null;
452
+ if (metric === "trailingPE" || metric === "forwardPE") {
453
+ const price = financials ? priceAtOrBefore(financials.priceHistory, statement.date) : null;
454
+ const eps = context
455
+ ? metric === "trailingPE"
456
+ ? trailingEpsValue(context.statements, context.statementIndex, context.period)
457
+ : forwardEpsValue(context.statements, context.statementIndex, context.period)
458
+ : statementEpsValue(statement);
459
+ return price && eps && eps > 0 ? price / eps : null;
460
+ }
461
+
462
+ const marketCap = historicalMarketCap(financials, statement);
463
+ const enterpriseValue = historicalEnterpriseValue(financials, statement);
126
464
  const revenue = statement?.totalRevenue;
127
465
  const ebitda = statement?.ebitda;
128
- const freeCashFlow = statement?.freeCashFlow;
466
+ const freeCashFlow = statement ? freeCashFlowValue(statement) : null;
129
467
 
130
468
  if (metric === "priceSales" && marketCap && revenue) return marketCap / revenue;
131
469
  if (metric === "evSales" && enterpriseValue && revenue) return enterpriseValue / revenue;
@@ -140,33 +478,32 @@ export function buildValuationGraphRows(
140
478
  symbol = "",
141
479
  period: FundamentalPeriod = "annual",
142
480
  ): FundamentalGraphRow[] {
143
- const statementMetrics = new Set<ValuationMetricKey>(["priceSales", "evSales", "evEbitda", "priceFcf"]);
144
- const sourceStatements = statementMetrics.has(metric)
145
- ? selectedStatements(financials, period)
146
- : [{ date: "Current" } as FinancialStatement];
147
- const sorted = sourceStatements
148
- .map((statement) => ({
149
- statement,
150
- value: valuationMetricValue(financials, metric, statement.date === "Current" ? null : statement),
481
+ const sourceStatements = metric === "pegRatio"
482
+ ? [{ date: "Current" } as FinancialStatement]
483
+ : selectedStatements(financials, period);
484
+ const entries = [...sourceStatements]
485
+ .sort((left, right) => left.date.localeCompare(right.date))
486
+ .map((statement, statementIndex, statements) => ({
487
+ date: statement.date,
488
+ category: statement.date === "Current" ? "Current" : periodCategory(statement.date, period),
489
+ value: valuationMetricValue(
490
+ financials,
491
+ metric,
492
+ statement.date === "Current" ? null : statement,
493
+ { statements, statementIndex, period },
494
+ ),
151
495
  }))
152
- .filter((entry): entry is { statement: FinancialStatement; value: number } => (
153
- typeof entry.value === "number" && Number.isFinite(entry.value)
154
- ))
155
- .sort((left, right) => left.statement.date.localeCompare(right.statement.date));
156
- const maxAbs = Math.max(1, ...sorted.map((entry) => Math.abs(entry.value)));
496
+ .filter((entry): entry is GraphRowEntry => finiteNumber(entry.value));
497
+ const currentValue = currentValuationMetricValue(financials, metric);
498
+ if (finiteNumber(currentValue) && !entries.some((entry) => entry.date === "Current") && (
499
+ metric === "pegRatio"
500
+ || metric === "forwardPE"
501
+ || (metric === "trailingPE" && entries.length === 0)
502
+ )) {
503
+ entries.push({ date: "Current", category: "Current", value: currentValue });
504
+ }
157
505
 
158
- return sorted.map((entry, index) => {
159
- const previous = sorted[index - 1]?.value;
160
- return {
161
- key: [symbol, entry.statement.date, metric].filter(Boolean).join(":"),
162
- symbol,
163
- date: entry.statement.date,
164
- category: entry.statement.date === "Current" ? "Current" : periodCategory(entry.statement.date, period),
165
- value: entry.value,
166
- growth: typeof previous === "number" && previous !== 0 ? (entry.value - previous) / Math.abs(previous) : null,
167
- barWidth: Math.max(1, Math.round((Math.abs(entry.value) / maxAbs) * 24)),
168
- };
169
- });
506
+ return buildRowsFromEntries(entries, metric, symbol);
170
507
  }
171
508
 
172
509
  export function metricDefs(kind: GraphKind): ReadonlyArray<MetricDefinition> {
@@ -242,9 +579,23 @@ export function buildFundamentalColumns(width: number, multiSymbol: boolean): Fu
242
579
  return columns;
243
580
  }
244
581
 
582
+ function categorySortKey(row: FundamentalGraphRow): string {
583
+ return row.date === "Current" ? "9999-12-31" : row.date;
584
+ }
585
+
245
586
  export function buildGraphBarSeries(rows: FundamentalGraphRow[]): BarChartSeries[] {
246
587
  const symbols = [...new Set(rows.map((row) => row.symbol || ""))];
247
- const categories = [...new Set(rows.map((row) => row.category))];
588
+ const categoryOrder = new Map<string, string>();
589
+ for (const row of rows) {
590
+ const sortKey = categorySortKey(row);
591
+ const previous = categoryOrder.get(row.category);
592
+ if (!previous || sortKey.localeCompare(previous) < 0) {
593
+ categoryOrder.set(row.category, sortKey);
594
+ }
595
+ }
596
+ const categories = [...categoryOrder.entries()]
597
+ .sort((left, right) => left[1].localeCompare(right[1]) || left[0].localeCompare(right[0]))
598
+ .map(([category]) => category);
248
599
  return symbols.map((symbol, index) => ({
249
600
  id: symbol || "value",
250
601
  label: symbol || "Value",
@@ -1,7 +1,9 @@
1
- import type { PaneTemplateCreateOptions } from "../../../../../types/plugin";
2
- import { formatTickerListInput, parseTickerListInput } from "../../../../../tickers/list";
1
+ import type { PaneSettingsDef, PaneTemplateCreateOptions } from "../../../../../types/plugin";
2
+ import { formatTickerListInput, MAX_TICKER_LIST_SIZE, parseTickerListInput } from "../../../../../tickers/list";
3
3
  import type { GraphKind } from "./types";
4
4
 
5
+ export const FUNDAMENTAL_GRAPH_PANE_ID = "fundamental-graph";
6
+
5
7
  export function symbolsFromPaneSettings(settings: Record<string, unknown> | undefined, fallbackSymbol: string | null): string[] {
6
8
  const symbols = settings?.symbols;
7
9
  if (Array.isArray(symbols)) {
@@ -26,6 +28,10 @@ export function graphKindFromSettings(settings: Record<string, unknown> | undefi
26
28
  return settings?.chartKind === "valuation" ? "valuation" : fallback;
27
29
  }
28
30
 
31
+ export function graphShortcutForKind(kind: GraphKind): "GF" | "GE" {
32
+ return kind === "valuation" ? "GE" : "GF";
33
+ }
34
+
29
35
  export function graphTemplateSymbols(
30
36
  activeTicker: string | null,
31
37
  options: Pick<PaneTemplateCreateOptions, "arg" | "values" | "symbols"> | undefined,
@@ -42,3 +48,18 @@ export function graphTemplateSymbols(
42
48
  export function graphTemplateTitle(shortcut: "GF" | "GE", symbols: string[]): string {
43
49
  return `${shortcut} ${formatTickerListInput(symbols)}`;
44
50
  }
51
+
52
+ export function buildGraphPaneSettingsDef(): PaneSettingsDef {
53
+ return {
54
+ title: "Graph Pane Settings",
55
+ fields: [
56
+ {
57
+ key: "symbolsText",
58
+ label: "Tickers",
59
+ description: `Enter up to ${MAX_TICKER_LIST_SIZE} tickers separated by commas.`,
60
+ type: "text",
61
+ placeholder: "AMD, NVDA",
62
+ },
63
+ ],
64
+ };
65
+ }
@@ -1,13 +1,17 @@
1
1
  import type { DataTableColumn } from "../../../../../components";
2
- import type { TickerFinancials } from "../../../../../types/financials";
2
+ import type { FinancialStatement, TickerFinancials } from "../../../../../types/financials";
3
3
 
4
4
  export type FundamentalMetricKey =
5
5
  | "totalRevenue"
6
6
  | "grossProfit"
7
+ | "grossMargin"
7
8
  | "operatingIncome"
9
+ | "operatingMargin"
8
10
  | "netIncome"
11
+ | "netMargin"
9
12
  | "operatingCashFlow"
10
13
  | "freeCashFlow"
14
+ | "freeCashFlowMargin"
11
15
  | "totalAssets"
12
16
  | "totalDebt"
13
17
  | "totalEquity"
@@ -42,6 +46,9 @@ export type MetricDefinition<Key extends GraphMetricKey = GraphMetricKey> = {
42
46
  key: Key;
43
47
  label: string;
44
48
  format: (value: number) => string;
49
+ value?: Key extends FundamentalMetricKey
50
+ ? (statement: FinancialStatement) => number | null | undefined
51
+ : never;
45
52
  };
46
53
 
47
54
  export type SymbolFinancials = {
@@ -12,6 +12,7 @@ import {
12
12
  isMetricForKind,
13
13
  } from "./fundamental-graph/model";
14
14
  import {
15
+ FUNDAMENTAL_GRAPH_PANE_ID,
15
16
  graphKindFromSettings,
16
17
  graphTemplateSymbols,
17
18
  graphTemplateTitle,
@@ -20,6 +21,7 @@ import {
20
21
  import type { FundamentalPeriod, GraphKind, GraphMetricKey } from "./fundamental-graph/types";
21
22
 
22
23
  export { buildFundamentalGraphRows, buildValuationGraphRows } from "./fundamental-graph/model";
24
+ export { buildGraphPaneSettingsDef, FUNDAMENTAL_GRAPH_PANE_ID } from "./fundamental-graph/settings";
23
25
 
24
26
  export function FundamentalGraphPane({ focused, width, height }: PaneProps) {
25
27
  const pane = usePaneInstance();
@@ -99,7 +101,7 @@ export function createGraphPaneTemplate({
99
101
  }): PaneTemplateDef {
100
102
  return {
101
103
  id,
102
- paneId: "fundamental-graph",
104
+ paneId: FUNDAMENTAL_GRAPH_PANE_ID,
103
105
  label,
104
106
  description,
105
107
  keywords: chartKind === "valuation"