@digithings-ai/ui 0.0.0-stage → 0.1.0

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 (424) hide show
  1. package/README.md +48 -2
  2. package/package.json +143 -4
  3. package/src/components/NavShell.tsx +776 -0
  4. package/src/components/SocialRow.tsx +87 -0
  5. package/src/components/Spinner.tsx +27 -0
  6. package/src/components/StackLogo.tsx +40 -0
  7. package/src/components/Terminal.tsx +110 -0
  8. package/src/components/TerminalManifest.tsx +212 -0
  9. package/src/components/ThemeProvider.tsx +271 -0
  10. package/src/components/account/AuthCard.tsx +309 -0
  11. package/src/components/assistant-ui/elements/mermaid-diagram.aui.tsx +63 -0
  12. package/src/components/assistant-ui/elements/mermaid-diagram.tsx +340 -0
  13. package/src/components/brand/BrandWordmark.tsx +32 -0
  14. package/src/components/changelog/ReleaseRail.tsx +81 -0
  15. package/src/components/changelog/index.ts +1 -0
  16. package/src/components/chat/ChatCodeBlock.tsx +67 -0
  17. package/src/components/chat/ChatMarkdown.tsx +66 -0
  18. package/src/components/chat/ChatMarkdownSource.tsx +203 -0
  19. package/src/components/chat/ChatMermaidBlock.tsx +202 -0
  20. package/src/components/chat/ChatMessage.tsx +77 -0
  21. package/src/components/chat/ChatPlayback.tsx +422 -0
  22. package/src/components/chat/ChatStreamCursor.tsx +25 -0
  23. package/src/components/chat/ChatThinking.tsx +113 -0
  24. package/src/components/chat/ChatToolCall.tsx +190 -0
  25. package/src/components/chat/ChatTranscript.tsx +60 -0
  26. package/src/components/chat/ChatWidgetFrame.tsx +107 -0
  27. package/src/components/chat/DigichatBootLoader.tsx +683 -0
  28. package/src/components/chat/DigichatLauncher.tsx +361 -0
  29. package/src/components/chat/DigichatThread.tsx +118 -0
  30. package/src/components/chat/DigichatThreadList.tsx +54 -0
  31. package/src/components/chat/DigichatWordmark.tsx +24 -0
  32. package/src/components/chat/DotMatrix.tsx +650 -0
  33. package/src/components/chat/boot-lab.tsx +766 -0
  34. package/src/components/chat/boot-signal.ts +144 -0
  35. package/src/components/chat/chat-surfaces.ts +52 -0
  36. package/src/components/chat/connection-status.ts +40 -0
  37. package/src/components/chat/digichat-wordmark.ts +355 -0
  38. package/src/components/chat/disclosure-chevron.tsx +49 -0
  39. package/src/components/chat/followable-href.ts +9 -0
  40. package/src/components/chat/gallery-thread/action-icons.tsx +25 -0
  41. package/src/components/chat/gallery-thread/attachment.aui.tsx +262 -0
  42. package/src/components/chat/gallery-thread/block-caret.tsx +439 -0
  43. package/src/components/chat/gallery-thread/cn.ts +1 -0
  44. package/src/components/chat/gallery-thread/composer-keyboard.ts +106 -0
  45. package/src/components/chat/gallery-thread/composer-trigger-popover.aui.tsx +235 -0
  46. package/src/components/chat/gallery-thread/css.d.ts +6 -0
  47. package/src/components/chat/gallery-thread/file.tsx +238 -0
  48. package/src/components/chat/gallery-thread/follow-up-suggestions.aui.tsx +92 -0
  49. package/src/components/chat/gallery-thread/format-json-dump.ts +112 -0
  50. package/src/components/chat/gallery-thread/hooks/use-attachment-src.ts +46 -0
  51. package/src/components/chat/gallery-thread/hooks/use-copy-to-clipboard.ts +9 -0
  52. package/src/components/chat/gallery-thread/image.tsx +519 -0
  53. package/src/components/chat/gallery-thread/markdown-text.tsx +278 -0
  54. package/src/components/chat/gallery-thread/message-error.aui.tsx +105 -0
  55. package/src/components/chat/gallery-thread/message-timing.aui.tsx +143 -0
  56. package/src/components/chat/gallery-thread/private-work-step.tsx +188 -0
  57. package/src/components/chat/gallery-thread/reasoning.tsx +332 -0
  58. package/src/components/chat/gallery-thread/scroll-mode.ts +174 -0
  59. package/src/components/chat/gallery-thread/source.tsx +76 -0
  60. package/src/components/chat/gallery-thread/thread.aui.tsx +1619 -0
  61. package/src/components/chat/gallery-thread/tooltip-icon-button.tsx +53 -0
  62. package/src/components/chat/gallery-thread/typed-welcome-copy.tsx +18 -0
  63. package/src/components/chat/gallery-thread/ui/tooltip.tsx +22 -0
  64. package/src/components/chat/gallery-thread/work-chain-summary.tsx +48 -0
  65. package/src/components/chat/gallery-thread/work-chain.tsx +110 -0
  66. package/src/components/chat/gallery-thread/work-status.ts +66 -0
  67. package/src/components/chat/overlay-scrollbar.tsx +86 -0
  68. package/src/components/chat/sanitize-mermaid-svg.ts +38 -0
  69. package/src/components/chat/skins/SOURCE.md +36 -0
  70. package/src/components/chat/skins/base/attachment.aui.tsx +287 -0
  71. package/src/components/chat/skins/base/aui-default/README.md +45 -0
  72. package/src/components/chat/skins/base/aui-default/collapsible-root.tsx +19 -0
  73. package/src/components/chat/skins/base/aui-default/collapsible.tsx +21 -0
  74. package/src/components/chat/skins/base/aui-default/reasoning.aui.tsx +120 -0
  75. package/src/components/chat/skins/base/aui-default/reasoning.tsx +331 -0
  76. package/src/components/chat/skins/base/aui-default/surfaces.ts +7 -0
  77. package/src/components/chat/skins/base/aui-default/tool-fallback.aui.tsx +772 -0
  78. package/src/components/chat/skins/base/aui-default/tool-group.aui.tsx +241 -0
  79. package/src/components/chat/skins/base/docs-model-options.ts +6 -0
  80. package/src/components/chat/skins/base/elements/assistant-trace.aui.tsx +169 -0
  81. package/src/components/chat/skins/base/elements/composer-trigger-popover.aui.tsx +271 -0
  82. package/src/components/chat/skins/base/elements/directive-text.aui.tsx +29 -0
  83. package/src/components/chat/skins/base/elements/directive-text.tsx +76 -0
  84. package/src/components/chat/skins/base/elements/message-timing.aui.tsx +104 -0
  85. package/src/components/chat/skins/base/elements/model-selector.aui.tsx +140 -0
  86. package/src/components/chat/skins/base/elements/model-selector.tsx +720 -0
  87. package/src/components/chat/skins/base/elements/quote.aui.tsx +290 -0
  88. package/src/components/chat/skins/base/elements/work-chain.tsx +100 -0
  89. package/src/components/chat/skins/base/markdown-text.tsx +294 -0
  90. package/src/components/chat/skins/base/model.ts +1 -0
  91. package/src/components/chat/skins/base/thread.tsx +1050 -0
  92. package/src/components/chat/skins/base/tooltip-icon-button.tsx +56 -0
  93. package/src/components/chat/skins/base/ui/avatar.tsx +115 -0
  94. package/src/components/chat/skins/base/ui/badge.tsx +51 -0
  95. package/src/components/chat/skins/base/ui/button.tsx +67 -0
  96. package/src/components/chat/skins/base/ui/command.tsx +185 -0
  97. package/src/components/chat/skins/base/ui/dialog.tsx +168 -0
  98. package/src/components/chat/skins/base/ui/dot-matrix.tsx +267 -0
  99. package/src/components/chat/skins/base/ui/popover.tsx +97 -0
  100. package/src/components/chat/skins/base/ui/skeleton.tsx +20 -0
  101. package/src/components/chat/skins/base/ui/textarea.tsx +24 -0
  102. package/src/components/chat/skins/base/ui/tooltip.tsx +73 -0
  103. package/src/components/chat/skins/base-assistant-ui/config-provider.tsx +90 -0
  104. package/src/components/chat/skins/base-assistant-ui/defaults.ts +419 -0
  105. package/src/components/chat/skins/base-assistant-ui/index.tsx +44 -0
  106. package/src/components/chat/skins/chatgpt.tsx +448 -0
  107. package/src/components/chat/skins/claude.tsx +380 -0
  108. package/src/components/chat/skins/digichat-composer-toolbar.tsx +151 -0
  109. package/src/components/chat/skins/digichat-context-meter-shares.ts +158 -0
  110. package/src/components/chat/skins/digichat-context-meter.tsx +243 -0
  111. package/src/components/chat/skins/digichat-meter-tone.ts +26 -0
  112. package/src/components/chat/skins/digichat-usage-meter.tsx +103 -0
  113. package/src/components/chat/skins/digichat.tsx +144 -0
  114. package/src/components/chat/skins/expo-react-native.tsx +29 -0
  115. package/src/components/chat/skins/gemini.tsx +322 -0
  116. package/src/components/chat/skins/grok-icon.tsx +34 -0
  117. package/src/components/chat/skins/grok.tsx +369 -0
  118. package/src/components/chat/skins/index.ts +46 -0
  119. package/src/components/chat/skins/perplexity.tsx +462 -0
  120. package/src/components/chat/skins/product-page-assistant/assistant-modal.tsx +73 -0
  121. package/src/components/chat/skins/product-page-assistant/index.tsx +19 -0
  122. package/src/components/chat/skins/product-page-assistant/product-dashboard.tsx +164 -0
  123. package/src/components/chat/skins/react-ink.tsx +97 -0
  124. package/src/components/chat/skins/thread-skin-host-contract.ts +102 -0
  125. package/src/components/chat/skins/thread-skin-view.tsx +156 -0
  126. package/src/components/chat/skins/thread-skins.ts +143 -0
  127. package/src/components/chat/skins/tooltip-icon-button.tsx +35 -0
  128. package/src/components/chat/skins/ui/dropdown-menu.tsx +272 -0
  129. package/src/components/chat/skins/webpage-assistant/assistant-config.ts +238 -0
  130. package/src/components/chat/skins/webpage-assistant/assistant-modal.tsx +78 -0
  131. package/src/components/chat/skins/webpage-assistant/config-provider.tsx +132 -0
  132. package/src/components/chat/skins/webpage-assistant/docs-app.tsx +106 -0
  133. package/src/components/chat/skins/webpage-assistant/docs-article.tsx +109 -0
  134. package/src/components/chat/skins/webpage-assistant/docs-assistant.tsx +73 -0
  135. package/src/components/chat/skins/webpage-assistant/docs-nav.tsx +81 -0
  136. package/src/components/chat/skins/webpage-assistant/host-ui.ts +79 -0
  137. package/src/components/chat/skins/webpage-assistant/index.tsx +12 -0
  138. package/src/components/chat/skins/webpage-assistant/markdown-page.tsx +11 -0
  139. package/src/components/chat/skins/webpage-assistant/pages.ts +9 -0
  140. package/src/components/chat/skins/webpage-assistant/types.ts +178 -0
  141. package/src/components/chat/skins/webpage-assistant/use-mobile.ts +19 -0
  142. package/src/components/chat/stock/SOURCE.md +90 -0
  143. package/src/components/chat/stock/artifacts-panel.tsx +305 -0
  144. package/src/components/chat/stock/attachment.aui.tsx +280 -0
  145. package/src/components/chat/stock/chat-notice.tsx +143 -0
  146. package/src/components/chat/stock/chat-private-mode.tsx +64 -0
  147. package/src/components/chat/stock/deploy-ui-context.tsx +89 -0
  148. package/src/components/chat/stock/embed-chat-prefs.tsx +471 -0
  149. package/src/components/chat/stock/file.tsx +265 -0
  150. package/src/components/chat/stock/follow-up-suggestions.aui.tsx +83 -0
  151. package/src/components/chat/stock/image.tsx +531 -0
  152. package/src/components/chat/stock/index.ts +129 -0
  153. package/src/components/chat/stock/json-view.tsx +140 -0
  154. package/src/components/chat/stock/markdown-text.tsx +319 -0
  155. package/src/components/chat/stock/message-error.aui.tsx +96 -0
  156. package/src/components/chat/stock/message-export-menu.tsx +156 -0
  157. package/src/components/chat/stock/palette-row-preview.tsx +26 -0
  158. package/src/components/chat/stock/reasoning.aui.tsx +120 -0
  159. package/src/components/chat/stock/reasoning.tsx +335 -0
  160. package/src/components/chat/stock/skin-chrome.tsx +81 -0
  161. package/src/components/chat/stock/skin-credit.tsx +63 -0
  162. package/src/components/chat/stock/skin-palette-bridge.tsx +235 -0
  163. package/src/components/chat/stock/skin-palette-context.ts +16 -0
  164. package/src/components/chat/stock/skin-runtime.tsx +52 -0
  165. package/src/components/chat/stock/skin-slash-palette.tsx +393 -0
  166. package/src/components/chat/stock/skin-types.ts +217 -0
  167. package/src/components/chat/stock/stock-chat-prefs-host.tsx +501 -0
  168. package/src/components/chat/stock/stock-send-gate.tsx +94 -0
  169. package/src/components/chat/stock/stock-shape.tsx +39 -0
  170. package/src/components/chat/stock/thread.aui.tsx +645 -0
  171. package/src/components/chat/stock/tool-display.ts +6 -0
  172. package/src/components/chat/stock/tool-fallback.aui.tsx +866 -0
  173. package/src/components/chat/stock/tool-group.aui.tsx +244 -0
  174. package/src/components/chat/stock/tool-surface.ts +120 -0
  175. package/src/components/chat/stock/tooltip-icon-button.tsx +49 -0
  176. package/src/components/chat/stock/ui/avatar.tsx +108 -0
  177. package/src/components/chat/stock/ui/button.tsx +81 -0
  178. package/src/components/chat/stock/ui/collapsible.tsx +21 -0
  179. package/src/components/chat/stock/ui/dialog.tsx +163 -0
  180. package/src/components/chat/stock/ui/skeleton.tsx +13 -0
  181. package/src/components/chat/stock/ui/textarea.tsx +20 -0
  182. package/src/components/chat/stock/ui/tooltip.tsx +68 -0
  183. package/src/components/chat/stock/use-attachment-src.ts +42 -0
  184. package/src/components/chat/stock/use-copy-to-clipboard.ts +29 -0
  185. package/src/components/chat/strip-model-boilerplate.ts +42 -0
  186. package/src/components/chat/suggestion-cycle.tsx +230 -0
  187. package/src/components/chat/suggestion-scroll-row.tsx +81 -0
  188. package/src/components/chat/trace-grouping/chain-group-by.ts +44 -0
  189. package/src/components/chat/trace-grouping/chain-state.ts +141 -0
  190. package/src/components/chat/trace-grouping/index.ts +32 -0
  191. package/src/components/chat/trace-grouping/tool-row.ts +74 -0
  192. package/src/components/chat/trace-grouping/use-deploy-work-disclosure.ts +35 -0
  193. package/src/components/chat/trace-grouping/use-work-chain.ts +103 -0
  194. package/src/components/chat/trace-i18n/core.ts +48 -0
  195. package/src/components/chat/trace-i18n/index.tsx +23 -0
  196. package/src/components/chat/trace-i18n/locales/en.ts +24 -0
  197. package/src/components/chat/trace-i18n/locales/fr.ts +28 -0
  198. package/src/components/chat/trace-i18n/types.ts +35 -0
  199. package/src/components/chat/trace-spacing.ts +249 -0
  200. package/src/components/chat/transcript/activity-view.ts +375 -0
  201. package/src/components/chat/transcript/chat-export.ts +133 -0
  202. package/src/components/chat/transcript/index.ts +10 -0
  203. package/src/components/chat/transcript/save-file.ts +209 -0
  204. package/src/components/chat/transcript/transcript-markdown.ts +428 -0
  205. package/src/components/chat/transcript/types.ts +81 -0
  206. package/src/components/chat/turn-outcome.ts +52 -0
  207. package/src/components/chat/use-warmup-phase.ts +18 -0
  208. package/src/components/chat/work-status-lead.tsx +49 -0
  209. package/src/components/chrome.tsx +264 -0
  210. package/src/components/command-palette/CommandPalette.tsx +404 -0
  211. package/src/components/command-palette/index.ts +6 -0
  212. package/src/components/contact/ContactMailto.tsx +112 -0
  213. package/src/components/contact/index.ts +5 -0
  214. package/src/components/conviction/ConvictionMeter.tsx +40 -0
  215. package/src/components/conviction/SignedConvictionBadge.tsx +26 -0
  216. package/src/components/conviction/index.ts +7 -0
  217. package/src/components/data-layout/BentoGrid.tsx +52 -0
  218. package/src/components/data-layout/CardRail.tsx +150 -0
  219. package/src/components/data-layout/DotMatrixStat.tsx +65 -0
  220. package/src/components/data-layout/FeatureCell.tsx +80 -0
  221. package/src/components/data-layout/MediaFrame.tsx +125 -0
  222. package/src/components/data-layout/ModuleGrid.tsx +735 -0
  223. package/src/components/data-layout/Odometer.tsx +149 -0
  224. package/src/components/data-layout/ProductFrame.tsx +79 -0
  225. package/src/components/data-layout/TestimonialWall.tsx +139 -0
  226. package/src/components/data-layout/index.ts +19 -0
  227. package/src/components/data-layout/treemap.ts +368 -0
  228. package/src/components/deck/DeckStack.tsx +243 -0
  229. package/src/components/desk/DataTable.tsx +373 -0
  230. package/src/components/desk/KpiGrid.tsx +80 -0
  231. package/src/components/desk/Sparkline.tsx +63 -0
  232. package/src/components/desk/StateBlock.tsx +132 -0
  233. package/src/components/desk/index.ts +18 -0
  234. package/src/components/diagrams/ArchitectureDiagram.tsx +350 -0
  235. package/src/components/diagrams/ArchitectureTour.tsx +912 -0
  236. package/src/components/diagrams/TerminalSchematic.tsx +192 -0
  237. package/src/components/diagrams/architecture-svg.tsx +384 -0
  238. package/src/components/diagrams/index.ts +30 -0
  239. package/src/components/diagrams/mermaid-theme.ts +115 -0
  240. package/src/components/diagrams/tour-camera.ts +171 -0
  241. package/src/components/docs/CodeTabs.tsx +90 -0
  242. package/src/components/docs/CopyCommand.tsx +151 -0
  243. package/src/components/docs/DocsLayout.tsx +197 -0
  244. package/src/components/docs/DocsSearch.tsx +97 -0
  245. package/src/components/docs/Endpoint.tsx +135 -0
  246. package/src/components/document/DocumentFrame.tsx +43 -0
  247. package/src/components/document/GlyphList.tsx +51 -0
  248. package/src/components/document/PageTitle.tsx +44 -0
  249. package/src/components/document/Prose.tsx +38 -0
  250. package/src/components/document/Section.tsx +55 -0
  251. package/src/components/document/index.ts +13 -0
  252. package/src/components/effects-chrome/AnnouncementBar.tsx +88 -0
  253. package/src/components/effects-chrome/HorizontalScrollTrack.tsx +461 -0
  254. package/src/components/effects-chrome/Pipeline.tsx +226 -0
  255. package/src/components/effects-chrome/RotatingPrompts.tsx +116 -0
  256. package/src/components/effects-chrome/StackingPanels.tsx +101 -0
  257. package/src/components/effects-chrome/TabStrip.tsx +232 -0
  258. package/src/components/effects-chrome/ToastStack.tsx +201 -0
  259. package/src/components/effects-chrome/horizontal-track-core.ts +70 -0
  260. package/src/components/effects-chrome/index.ts +35 -0
  261. package/src/components/emblems.tsx +188 -0
  262. package/src/components/faviconTheme.ts +23 -0
  263. package/src/components/figure/Figure.tsx +38 -0
  264. package/src/components/figure/index.ts +2 -0
  265. package/src/components/finance-charts/DrawdownPlot.tsx +76 -0
  266. package/src/components/finance-charts/EquityCurve.tsx +71 -0
  267. package/src/components/finance-charts/PriceChart.tsx +119 -0
  268. package/src/components/finance-charts/chart-host.tsx +236 -0
  269. package/src/components/finance-charts/chart-scale.ts +93 -0
  270. package/src/components/finance-charts/demo-data.ts +88 -0
  271. package/src/components/finance-charts/index.ts +46 -0
  272. package/src/components/finance-charts/lw-chart.tsx +292 -0
  273. package/src/components/finance-composites/MarketBar.tsx +243 -0
  274. package/src/components/finance-composites/OrderBook.tsx +133 -0
  275. package/src/components/finance-composites/PerformanceDashboard.tsx +185 -0
  276. package/src/components/finance-composites/SortableTable.tsx +144 -0
  277. package/src/components/finance-composites/StockTicker.tsx +65 -0
  278. package/src/components/finance-composites/SyncedTearsheet.tsx +226 -0
  279. package/src/components/finance-composites/index.ts +28 -0
  280. package/src/components/finance-tearsheet/KpiStrip.tsx +49 -0
  281. package/src/components/finance-tearsheet/LiveBadge.tsx +29 -0
  282. package/src/components/finance-tearsheet/ReturnsMatrix.tsx +321 -0
  283. package/src/components/finance-tearsheet/StrategyTearsheet.tsx +99 -0
  284. package/src/components/finance-tearsheet/TearsheetCard.tsx +54 -0
  285. package/src/components/finance-tearsheet/TradeLogTable.tsx +73 -0
  286. package/src/components/finance-tearsheet/charts.tsx +2624 -0
  287. package/src/components/finance-tearsheet/demo-data.ts +177 -0
  288. package/src/components/finance-tearsheet/format.ts +41 -0
  289. package/src/components/finance-tearsheet/index.ts +120 -0
  290. package/src/components/finance-tearsheet/print.ts +52 -0
  291. package/src/components/finance-tearsheet/risk-bands.ts +48 -0
  292. package/src/components/finance-tearsheet/stats.ts +35 -0
  293. package/src/components/finance-tearsheet/strategy-sheet-model.ts +238 -0
  294. package/src/components/finance-tearsheet/types.ts +58 -0
  295. package/src/components/footer/FooterCells.tsx +109 -0
  296. package/src/components/footer/index.ts +6 -0
  297. package/src/components/graph.tsx +158 -0
  298. package/src/components/icons.tsx +98 -0
  299. package/src/components/landing/PixelWordmark.tsx +36 -0
  300. package/src/components/logos.ts +39 -0
  301. package/src/components/marquee/Marquee.tsx +204 -0
  302. package/src/components/metrics/PerfMetrics.tsx +68 -0
  303. package/src/components/metrics/StatCounter.tsx +104 -0
  304. package/src/components/page-geometry.tsx +177 -0
  305. package/src/components/pricing/Pricing.tsx +174 -0
  306. package/src/components/pricing/PricingMatrix.tsx +219 -0
  307. package/src/components/prose/index.ts +2 -0
  308. package/src/components/prose/prose.tsx +86 -0
  309. package/src/components/repo-activity/RepoActivity.tsx +599 -0
  310. package/src/components/repo-activity/RepoHeatmap.tsx +225 -0
  311. package/src/components/repo-activity/demo.ts +201 -0
  312. package/src/components/repo-activity/fetch.ts +211 -0
  313. package/src/components/repo-activity/heatmap.ts +123 -0
  314. package/src/components/repo-activity/index.ts +28 -0
  315. package/src/components/repo-activity/types.ts +99 -0
  316. package/src/components/stages/NumberedStages.tsx +116 -0
  317. package/src/components/symbols/marks.tsx +116 -0
  318. package/src/components/symbols/terminal-lockup.tsx +136 -0
  319. package/src/components/symbols/terminal-marks.tsx +226 -0
  320. package/src/components/terminal/ChatResponseLoader.tsx +67 -0
  321. package/src/components/terminal/ContainerBootLoader.tsx +82 -0
  322. package/src/components/terminal/TerminalStepCaret.tsx +202 -0
  323. package/src/components/themeInit.ts +24 -0
  324. package/src/components/typography/MotionReveal.tsx +108 -0
  325. package/src/components/typography/TypewriterReveal.tsx +101 -0
  326. package/src/components/typography/WordReveal.tsx +143 -0
  327. package/src/data/modules.ts +378 -0
  328. package/src/data/subsystems.ts +101 -0
  329. package/src/index.ts +587 -0
  330. package/src/lib/base-path.ts +60 -0
  331. package/src/lib/cx.ts +25 -0
  332. package/src/lib/gloomberb.ts +69 -0
  333. package/src/lib/record.ts +19 -0
  334. package/src/lib/stub-payload.ts +183 -0
  335. package/src/lib/useBodyScrollLock.ts +44 -0
  336. package/src/lib/utils.ts +6 -0
  337. package/src/motion/primitives.tsx +105 -0
  338. package/src/motion/scroll-glide.ts +62 -0
  339. package/src/motion/scrolly-core.ts +38 -0
  340. package/src/motion/scrolly.tsx +95 -0
  341. package/src/navigation/HashScrollManager.tsx +167 -0
  342. package/src/navigation/hashScroll.ts +78 -0
  343. package/src/replay/browser.ts +280 -0
  344. package/src/replay/chat-export-run.ts +121 -0
  345. package/src/replay/export-format.ts +25 -0
  346. package/src/replay/export-mount.ts +32 -0
  347. package/src/replay/filename.ts +54 -0
  348. package/src/replay/index.ts +21 -0
  349. package/src/replay/redact-dom.ts +178 -0
  350. package/src/replay/redact.ts +113 -0
  351. package/src/replay/snapshot.ts +659 -0
  352. package/src/styles/account-auth.css +339 -0
  353. package/src/styles/brand-wordmark.css +11 -0
  354. package/src/styles/card-rail.css +120 -0
  355. package/src/styles/chat-aui.css +1143 -0
  356. package/src/styles/chat-core.css +469 -0
  357. package/src/styles/chat-digichat.css +2531 -0
  358. package/src/styles/chat-math.css +70 -0
  359. package/src/styles/chat-skin-palette.css +138 -0
  360. package/src/styles/chat-stock-menu.css +37 -0
  361. package/src/styles/chat-widgets.css +190 -0
  362. package/src/styles/command-palette.css +372 -0
  363. package/src/styles/controls-core.css +2261 -0
  364. package/src/styles/controls-overlay.css +603 -0
  365. package/src/styles/data-layout.css +288 -0
  366. package/src/styles/deck.css +141 -0
  367. package/src/styles/diagrams.css +1860 -0
  368. package/src/styles/digichat-app-theme.css +250 -0
  369. package/src/styles/digichat-boot-loader.css +487 -0
  370. package/src/styles/digichat-launcher.css +377 -0
  371. package/src/styles/docs.css +165 -0
  372. package/src/styles/effects-chrome.css +888 -0
  373. package/src/styles/finance-composites.css +214 -0
  374. package/src/styles/finance-tearsheet-overlay.css +90 -0
  375. package/src/styles/finance-tearsheet.css +945 -0
  376. package/src/styles/marquee.css +168 -0
  377. package/src/styles/metrics.css +82 -0
  378. package/src/styles/nav-shell.css +551 -0
  379. package/src/styles/pricing.css +79 -0
  380. package/src/styles/repo-activity.css +575 -0
  381. package/src/styles/stages.css +69 -0
  382. package/src/styles/terminal-loaders.css +122 -0
  383. package/src/styles/terminal-manifest.css +228 -0
  384. package/src/styles/web-theme.css +1007 -0
  385. package/src/styles/word-reveal.css +204 -0
  386. package/src/theme/apply-theme.ts +214 -0
  387. package/src/theme/index.ts +27 -0
  388. package/src/theme/palette-registry.ts +855 -0
  389. package/src/theme/tui-prefs.ts +64 -0
  390. package/src/ui/alert.tsx +78 -0
  391. package/src/ui/avatar.tsx +104 -0
  392. package/src/ui/badge.tsx +75 -0
  393. package/src/ui/breadcrumb.tsx +61 -0
  394. package/src/ui/button.tsx +67 -0
  395. package/src/ui/card.tsx +139 -0
  396. package/src/ui/checkbox.tsx +27 -0
  397. package/src/ui/collapsible.tsx +32 -0
  398. package/src/ui/date-pager.tsx +325 -0
  399. package/src/ui/dialog.tsx +157 -0
  400. package/src/ui/dropdown-menu.tsx +267 -0
  401. package/src/ui/empty-state.tsx +165 -0
  402. package/src/ui/field.tsx +104 -0
  403. package/src/ui/form.tsx +36 -0
  404. package/src/ui/icon-button.tsx +36 -0
  405. package/src/ui/index.ts +34 -0
  406. package/src/ui/input.tsx +33 -0
  407. package/src/ui/kbd.tsx +30 -0
  408. package/src/ui/label.tsx +32 -0
  409. package/src/ui/pager.tsx +119 -0
  410. package/src/ui/pagination.tsx +126 -0
  411. package/src/ui/radio-group.tsx +51 -0
  412. package/src/ui/search-bar.tsx +85 -0
  413. package/src/ui/segmented-control.tsx +76 -0
  414. package/src/ui/select.tsx +257 -0
  415. package/src/ui/separator.tsx +24 -0
  416. package/src/ui/sheet.tsx +141 -0
  417. package/src/ui/skeleton.tsx +76 -0
  418. package/src/ui/slider.tsx +76 -0
  419. package/src/ui/switch.tsx +31 -0
  420. package/src/ui/table.tsx +179 -0
  421. package/src/ui/tabs.tsx +89 -0
  422. package/src/ui/tags-input.tsx +168 -0
  423. package/src/ui/textarea.tsx +17 -0
  424. package/src/ui/tooltip.tsx +71 -0
@@ -0,0 +1,292 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Lightweight-charts scaffold (#1450 batch E) — the persistent-chart
5
+ * counterpart to `useFinanceChart` (chart-host.tsx), converged from the
6
+ * dashboard `lib/lw-chart.tsx` scaffold (#1420) so dashboards and display
7
+ * surfaces share ONE canon. Two sanctioned lifecycles:
8
+ *
9
+ * - `useFinanceChart` (chart-host.tsx) — setup-callback style; a data
10
+ * change tears the chart down and rebuilds it. Right for display
11
+ * specimens (PriceChart / EquityCurve / DrawdownPlot).
12
+ * - `useLightweightChart` (here) — the chart is exposed as state and
13
+ * persists across data changes; callers own their series in effects,
14
+ * guarding cleanup with `isAlive()`. Right for live dashboards
15
+ * (dashboard portfolio surfaces, tearsheet workspaces).
16
+ *
17
+ * Both wear the same token chrome (`chartChromeOptions`), keep the canvas
18
+ * transparent with the attribution logo off, disable kinetic-scroll inertia
19
+ * under prefers-reduced-motion (the resting chart IS the final state), and
20
+ * are SSR-safe — charts exist only after mount; hosts render as plain divs
21
+ * with no JS. This module is palette-agnostic: the caller supplies resolved
22
+ * token colors (dashboard binds its ChartColors; package consumers pair it
23
+ * with `useFinanceChartPalette()` from chart-host.tsx).
24
+ */
25
+
26
+ import {
27
+ useCallback,
28
+ useEffect,
29
+ useRef,
30
+ useState,
31
+ type ReactNode,
32
+ type RefObject,
33
+ } from "react";
34
+ import {
35
+ ColorType,
36
+ createChart,
37
+ CrosshairMode,
38
+ type ChartOptions,
39
+ type DeepPartial,
40
+ type IChartApi,
41
+ type LineData,
42
+ type MouseEventParams,
43
+ type Time,
44
+ type WhitespaceData,
45
+ } from "lightweight-charts";
46
+
47
+ /* ── Theme surface ─────────────────────────────────────────────────────── */
48
+
49
+ /** The four resolved token values chart chrome is painted with. */
50
+ export type ChartChrome = {
51
+ /** Axis text + crosshair line color — canon `--ink-mute`. */
52
+ text: string;
53
+ /** Grid lines + price/time scale borders — canon `--hair`. */
54
+ hair: string;
55
+ /** Crosshair label background — `--accent`, or a money token for P&L reads. */
56
+ label: string;
57
+ /** Axis font — canon `--font-mono` (see `hostMonoFont`). */
58
+ fontFamily: string;
59
+ };
60
+
61
+ /**
62
+ * Chart-wide options derived from the canon tokens: transparent canvas,
63
+ * hair-token grid + scale borders (both price scales), mono axis text,
64
+ * token-backed crosshair. Re-applied wholesale on theme flips.
65
+ */
66
+ export function chartChromeOptions(chrome: ChartChrome): DeepPartial<ChartOptions> {
67
+ return {
68
+ layout: {
69
+ background: { type: ColorType.Solid, color: "transparent" },
70
+ textColor: chrome.text,
71
+ fontFamily: chrome.fontFamily,
72
+ fontSize: 11,
73
+ attributionLogo: false,
74
+ },
75
+ grid: { vertLines: { color: chrome.hair }, horzLines: { color: chrome.hair } },
76
+ rightPriceScale: { borderColor: chrome.hair },
77
+ leftPriceScale: { borderColor: chrome.hair },
78
+ timeScale: { borderColor: chrome.hair, timeVisible: false },
79
+ crosshair: {
80
+ mode: CrosshairMode.Normal,
81
+ vertLine: { color: chrome.text, labelBackgroundColor: chrome.label },
82
+ horzLine: { color: chrome.text, labelBackgroundColor: chrome.label },
83
+ },
84
+ };
85
+ }
86
+
87
+ /** `--font-mono` read off the chart host (scoped overrides win), SSR-safe. */
88
+ export function hostMonoFont(host: HTMLElement | null): string {
89
+ if (!host || typeof window === "undefined") return "monospace";
90
+ return getComputedStyle(host).getPropertyValue("--font-mono").trim() || "monospace";
91
+ }
92
+
93
+ /* ── Chart lifecycle hook ──────────────────────────────────────────────── */
94
+
95
+ export interface UseLightweightChartResult<C> {
96
+ /** Attach to the chart host div (make it `relative` when using tooltips). */
97
+ containerRef: RefObject<HTMLDivElement | null>;
98
+ /** Live chart instance — `null` on the server and until mount (SSR-safe). */
99
+ chart: IChartApi | null;
100
+ /** The theme-reactive colors passed in, echoed for destructuring. */
101
+ colors: C;
102
+ /**
103
+ * Returns false once the chart has been disposed. Series-owning effects
104
+ * must guard their cleanup with this: React may run the hook's unmount
105
+ * cleanup (`chart.remove()`) before a later-declared effect's cleanup.
106
+ * Stable identity — safe in dependency arrays.
107
+ */
108
+ isAlive: () => boolean;
109
+ /** prefers-reduced-motion, read at mount. */
110
+ reducedMotion: boolean;
111
+ }
112
+
113
+ export interface UseLightweightChartConfig<C> {
114
+ /**
115
+ * Resolved theme colors — a NEW identity re-applies the themed options,
116
+ * so pass a per-theme-cached snapshot (`useFinanceChartPalette()` or an
117
+ * app hook like dashboard's `useChartColors()`), not a fresh object per
118
+ * render.
119
+ */
120
+ colors: C;
121
+ /** Colors + host element → chart chrome options (latest render wins). */
122
+ buildOptions: (colors: C, host: HTMLElement | null) => DeepPartial<ChartOptions>;
123
+ /**
124
+ * Optional live read used once at chart creation (client-side), so the
125
+ * first paint never wears an SSR-fallback palette. Defaults to the latest
126
+ * `colors` value.
127
+ */
128
+ readColors?: () => C;
129
+ /** Merged once at creation (first render wins). */
130
+ extraOptions?: DeepPartial<ChartOptions>;
131
+ }
132
+
133
+ /**
134
+ * Creates/destroys a lightweight-chart bound to `containerRef`: `autoSize`
135
+ * on, token theme from `buildOptions`, options re-applied when `colors`
136
+ * changes identity (theme flips), kinetic-scroll inertia disabled under
137
+ * reduced motion. The chart persists across data changes — callers add and
138
+ * remove their own series in effects, guarding cleanup with `isAlive()`.
139
+ */
140
+ export function useLightweightChart<C>(
141
+ config: UseLightweightChartConfig<C>
142
+ ): UseLightweightChartResult<C> {
143
+ const containerRef = useRef<HTMLDivElement | null>(null);
144
+ const [chart, setChart] = useState<IChartApi | null>(null);
145
+ const alive = useRef(false);
146
+ const isAlive = useCallback(() => alive.current, []);
147
+ const [reducedMotion] = useState(
148
+ () =>
149
+ typeof window !== "undefined" &&
150
+ window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true
151
+ );
152
+ const configRef = useRef(config);
153
+ configRef.current = config;
154
+ const extraRef = useRef(config.extraOptions);
155
+
156
+ useEffect(() => {
157
+ const host = containerRef.current;
158
+ if (!host) return;
159
+ const { buildOptions, readColors, colors } = configRef.current;
160
+ const created = createChart(host, {
161
+ autoSize: true,
162
+ ...buildOptions(readColors ? readColors() : colors, host),
163
+ ...(reducedMotion ? { kineticScroll: { touch: false, mouse: false } } : {}),
164
+ ...(extraRef.current ?? {}),
165
+ });
166
+ alive.current = true;
167
+ setChart(created);
168
+ return () => {
169
+ alive.current = false;
170
+ created.remove();
171
+ setChart(null);
172
+ };
173
+ }, [reducedMotion]);
174
+
175
+ // Theme-reactive: re-apply the token-derived options on theme flips.
176
+ // Series colors are re-applied by callers (they own their series).
177
+ useEffect(() => {
178
+ if (!chart || !alive.current) return;
179
+ chart.applyOptions(configRef.current.buildOptions(config.colors, containerRef.current));
180
+ }, [chart, config.colors]);
181
+
182
+ return { containerRef, chart, colors: config.colors, isAlive, reducedMotion };
183
+ }
184
+
185
+ /* ── Data adapters ─────────────────────────────────────────────────────── */
186
+
187
+ /**
188
+ * Array-of-objects rows (recharts-shaped) → lightweight-charts line/area
189
+ * points. `null`/`NaN` values become whitespace points, which render as
190
+ * gaps — the recharts `connectNulls` look is intentionally NOT reproduced
191
+ * for missing data inside a series (gaps are more honest). Rows must
192
+ * already be ascending by ISO date.
193
+ */
194
+ export function toLineData<T>(
195
+ rows: readonly T[],
196
+ getDate: (row: T) => string,
197
+ getValue: (row: T) => number | null | undefined
198
+ ): (LineData<Time> | WhitespaceData<Time>)[] {
199
+ const out: (LineData<Time> | WhitespaceData<Time>)[] = [];
200
+ let prev = "";
201
+ for (const row of rows) {
202
+ const date = getDate(row);
203
+ if (!date || date === prev) continue; // lightweight-charts requires unique ascending times
204
+ prev = date;
205
+ const value = getValue(row);
206
+ const time = date as Time;
207
+ out.push(value == null || Number.isNaN(value) ? { time } : { time, value });
208
+ }
209
+ return out;
210
+ }
211
+
212
+ /** Crosshair `Time` (string | BusinessDay | UTC seconds) → ISO `yyyy-mm-dd`. */
213
+ export function timeToISO(t: Time): string {
214
+ if (typeof t === "string") return t;
215
+ if (typeof t === "number") return new Date(t * 1000).toISOString().slice(0, 10);
216
+ const mm = String(t.month).padStart(2, "0");
217
+ const dd = String(t.day).padStart(2, "0");
218
+ return `${t.year}-${mm}-${dd}`;
219
+ }
220
+
221
+ /* ── Crosshair tooltip ─────────────────────────────────────────────────── */
222
+
223
+ export interface ChartTip {
224
+ /** Clamped position inside the chart host, px. */
225
+ left: number;
226
+ top: number;
227
+ /** ISO date under the crosshair. */
228
+ iso: string;
229
+ /** Raw crosshair params — read series values via `param.seriesData`. */
230
+ param: MouseEventParams<Time>;
231
+ }
232
+
233
+ const TIP_WIDTH_GUESS = 240;
234
+ const TIP_OFFSET = 14;
235
+
236
+ /**
237
+ * Subscribes to crosshair moves and yields a clamped tooltip anchor + params,
238
+ * or `null` when the pointer is off the pane. Render your own token-styled
239
+ * DOM tooltip from it (the house idiom — see ChartTipShell).
240
+ */
241
+ export function useChartTip(
242
+ chart: IChartApi | null,
243
+ containerRef: RefObject<HTMLDivElement | null>,
244
+ isAlive: () => boolean
245
+ ): ChartTip | null {
246
+ const [tip, setTip] = useState<ChartTip | null>(null);
247
+
248
+ useEffect(() => {
249
+ if (!chart) return;
250
+ const onMove = (param: MouseEventParams<Time>) => {
251
+ const host = containerRef.current;
252
+ if (
253
+ !host ||
254
+ !param.point ||
255
+ param.time == null ||
256
+ param.point.x < 0 ||
257
+ param.point.y < 0
258
+ ) {
259
+ setTip((old) => (old === null ? old : null));
260
+ return;
261
+ }
262
+ const width = host.clientWidth;
263
+ const flip = param.point.x > width - (TIP_WIDTH_GUESS + TIP_OFFSET);
264
+ const left = flip
265
+ ? Math.max(0, param.point.x - TIP_WIDTH_GUESS - TIP_OFFSET)
266
+ : param.point.x + TIP_OFFSET;
267
+ const top = Math.max(8, Math.min(param.point.y + TIP_OFFSET, host.clientHeight - 48));
268
+ setTip({ left, top, iso: timeToISO(param.time), param });
269
+ };
270
+ chart.subscribeCrosshairMove(onMove);
271
+ return () => {
272
+ if (isAlive()) chart.unsubscribeCrosshairMove(onMove);
273
+ };
274
+ }, [chart, containerRef, isAlive]);
275
+
276
+ return tip;
277
+ }
278
+
279
+ /**
280
+ * Token-styled floating tooltip chrome. Consuming apps must `@source` this
281
+ * directory so the utility classes below are generated (MIGRATION.md rule 3).
282
+ */
283
+ export function ChartTipShell({ tip, children }: { tip: ChartTip; children: ReactNode }) {
284
+ return (
285
+ <div
286
+ className="pointer-events-none absolute z-10 rounded-none border border-hair bg-term-bg px-3 py-2 text-[0.82rem] shadow-lg max-w-[240px]"
287
+ style={{ left: tip.left, top: tip.top }}
288
+ >
289
+ {children}
290
+ </div>
291
+ );
292
+ }
@@ -0,0 +1,243 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState, type ReactNode } from "react";
4
+ import { Marquee } from "../marquee/Marquee";
5
+ import { toneClass } from "../finance-tearsheet/format";
6
+ import { cn } from "../../lib/utils";
7
+
8
+ /**
9
+ * MarketBar — the terminal status line: a green square, then
10
+ * `BTC 63,410 ▲0.40% …`, then `[pause]`. Props-driven and honest: it renders
11
+ * exactly the cells it is given and never a price of its own. The consumer
12
+ * owns the feed, the clock and the status.
13
+ *
14
+ * Built on `Marquee` (seamless -50% loop, edge fade, pause-on-hover). Each
15
+ * cell carries its own horizontal padding and the group gap is zero, so the
16
+ * join where the list repeats is the same gap as every other pair. Honesty
17
+ * and a11y contract:
18
+ * - `status="live"` is a green square (it may pulse). The word is not shown.
19
+ * `connecting` / `stale` / `offline` show a muted bracketed word;
20
+ * - no quoted cells -> a red square and `Prices did not load.`;
21
+ * - a cell with `value: null` shows an em dash for the price;
22
+ * - a missing percent is an em dash, never a fabricated number;
23
+ * - `asOf` (e.g. "as of 09-29") and `source` label recorded cells — `asOf`
24
+ * shows inline, `source` in the tooltip and the screen-reader summary;
25
+ * - the scrolling strip is `aria-hidden` and the region is `aria-live="off"`;
26
+ * a plain-text `sr-only` summary carries the same facts once, statically;
27
+ * - a pause control (the marquee auto-runs well past 5s; hover also pauses);
28
+ * it is hidden under reduced motion, where nothing moves;
29
+ * - when a percent actually changes, only that number pulses. The first
30
+ * observation does not. The symbol, the price, and the row stay still;
31
+ * - `--up` / `--down` colour ONLY the signed change (`toneClass`, the
32
+ * finance-tearsheet `is-pos` / `is-neg`), never the symbol or value.
33
+ *
34
+ * Wiring (in the consuming app):
35
+ * globals.css @import "@digithings-ai/ui/styles/marquee.css";
36
+ * @import "@digithings-ai/ui/styles/finance-tearsheet.css";
37
+ * @source "<path-to>/packages/ui/src/components/finance-composites";
38
+ * CSS-only motion: no MotionProvider needed. Client component (pause state).
39
+ */
40
+ export type MarketBarStatus = "connecting" | "live" | "stale" | "offline";
41
+
42
+ export type MarketBarCell = {
43
+ /** Instrument symbol — "BTC", "SPY" … */
44
+ symbol: string;
45
+ /** Preformatted price, or null while unknown (renders "—"). */
46
+ value: string | null;
47
+ /** Signed percent change (0.4 = +0.40%). Null or missing renders an em dash. */
48
+ changePct?: number | null;
49
+ /** Freshness stamp shown inline for recorded cells — "as of 09-29". */
50
+ asOf?: string;
51
+ /** Origin of the number — tooltip and screen-reader text only. */
52
+ source?: string;
53
+ /** Retained for callers. The tape pulses from `changePct`, not this key. */
54
+ flashKey?: string | number;
55
+ };
56
+
57
+ export type MarketBarProps = {
58
+ /** The tape. A cell with no price is dropped. None left renders the empty state. */
59
+ cells: MarketBarCell[];
60
+ /** Feed state. Only "live" earns the green square, and only while quotes are scrolling. */
61
+ status: MarketBarStatus;
62
+ /** Right-aligned slot — a clock like "00:00Z". Consumer-rendered (hydration is theirs). */
63
+ trailing?: ReactNode;
64
+ /** Marquee loop duration in seconds. Omit to give each symbol time to be read. */
65
+ speed?: number;
66
+ /** Start paused. */
67
+ defaultPaused?: boolean;
68
+ /** Accessible name for the region. */
69
+ ariaLabel?: string;
70
+ className?: string;
71
+ };
72
+
73
+ /** Seconds for one symbol to travel its own width — long enough to read symbol, price, and percent. */
74
+ export const TAPE_SECONDS_PER_SYMBOL = 8;
75
+ /** Floor so a one-symbol tape still drifts slowly. */
76
+ export const TAPE_MIN_SECONDS = 48;
77
+
78
+ /** Loop duration. One period is one copy of the quoted strip. */
79
+ export function tapeDurationSeconds(count: number): number {
80
+ const n = Number.isFinite(count) && count > 0 ? Math.floor(count) : 1;
81
+ return Math.max(TAPE_MIN_SECONDS, n * TAPE_SECONDS_PER_SYMBOL);
82
+ }
83
+
84
+ /** A price string. An em dash, blank, or null is not a quote. */
85
+ export function isTapeQuote(cell: MarketBarCell): boolean {
86
+ if (cell.value === null) return false;
87
+ const value = cell.value.trim();
88
+ return value.length > 0 && value !== "—" && value !== "–" && value !== "-";
89
+ }
90
+
91
+ const STATUS_WORD: Record<Exclude<MarketBarStatus, "live">, string> = {
92
+ connecting: "connecting",
93
+ stale: "stale",
94
+ offline: "offline",
95
+ };
96
+
97
+ const STATUS_SR: Record<MarketBarStatus, string> = {
98
+ connecting: "Connecting to the price feed.",
99
+ live: "The price feed is ticking.",
100
+ stale: "Prices are stale; the feed has stopped ticking.",
101
+ offline: "The price feed is offline.",
102
+ };
103
+
104
+ /**
105
+ * Pulse generation for a percent. The first observation never pulses. A null
106
+ * or non-finite percent never pulses. An unchanged number never pulses.
107
+ */
108
+ export function percentPulseGeneration(
109
+ previous: number | null | undefined,
110
+ next: number | null,
111
+ generation: number,
112
+ ): { previous: number | null; generation: number } {
113
+ const pct = next !== null && Number.isFinite(next) ? next : null;
114
+ if (previous === undefined) return { previous: pct, generation };
115
+ if (pct !== null && pct !== previous) return { previous: pct, generation: generation + 1 };
116
+ return { previous: pct, generation };
117
+ }
118
+
119
+ /** Change magnitude — the sign is carried by the arrow and the tone. */
120
+ function fmtChange(pct: number): string {
121
+ return `${Math.abs(pct).toFixed(2)}%`;
122
+ }
123
+
124
+ function hasChange(c: MarketBarCell): c is MarketBarCell & { changePct: number } {
125
+ return c.value !== null && c.changePct !== null && c.changePct !== undefined;
126
+ }
127
+
128
+ function srCell(c: MarketBarCell): string {
129
+ const parts = [c.symbol, c.value ?? "no value"];
130
+ if (hasChange(c)) {
131
+ parts.push(c.changePct === 0 ? "unchanged" : `${c.changePct > 0 ? "up" : "down"} ${fmtChange(c.changePct)}`);
132
+ } else {
133
+ parts.push("percent unavailable");
134
+ }
135
+ if (c.asOf) parts.push(c.asOf);
136
+ if (c.source) parts.push(`source ${c.source}`);
137
+ return parts.join(" ");
138
+ }
139
+
140
+ function PercentNumber({ pct }: { pct: number | null }) {
141
+ const [generation, setGeneration] = useState(0);
142
+ const previous = useRef<number | null | undefined>(undefined);
143
+ useEffect(() => {
144
+ const next = percentPulseGeneration(previous.current, pct, generation);
145
+ previous.current = next.previous;
146
+ if (next.generation !== generation) setGeneration(next.generation);
147
+ }, [pct, generation]);
148
+ const pulsing = generation > 0 && pct !== null && Number.isFinite(pct);
149
+ return (
150
+ <span key={generation} data-mb="pct" className={pulsing ? "mb-pct-pulse" : undefined}>
151
+ {pct === null || !Number.isFinite(pct) ? "—" : fmtChange(pct)}
152
+ </span>
153
+ );
154
+ }
155
+
156
+ function Cell({ cell }: { cell: MarketBarCell }) {
157
+ const pct = hasChange(cell) ? cell.changePct : null;
158
+ return (
159
+ <span
160
+ title={cell.source ? `${cell.symbol} · source ${cell.source}` : undefined}
161
+ className="mb-cell inline-flex items-baseline gap-2 whitespace-nowrap px-[1.15rem] font-mono text-[0.72rem] [font-variant-numeric:tabular-nums]"
162
+ >
163
+ <span className="tracking-[0.02em] text-ink">{cell.symbol}</span>
164
+ <span className="mb-price text-ink-soft">{cell.value ?? "—"}</span>
165
+ <span
166
+ className={cn(
167
+ "mb-pct inline-flex items-center justify-end gap-1",
168
+ pct === null ? "text-ink-mute" : toneClass(pct) || "text-ink-mute",
169
+ )}
170
+ >
171
+ {pct === null ? null : (
172
+ <span aria-hidden="true">{pct > 0 ? "▲" : pct < 0 ? "▼" : "·"}</span>
173
+ )}
174
+ <PercentNumber pct={pct} />
175
+ </span>
176
+ {cell.asOf ? <span className="text-ink-mute">{cell.asOf}</span> : null}
177
+ </span>
178
+ );
179
+ }
180
+
181
+ export function MarketBar({
182
+ cells,
183
+ status,
184
+ trailing,
185
+ speed,
186
+ defaultPaused = false,
187
+ ariaLabel = "Market prices",
188
+ className,
189
+ }: MarketBarProps) {
190
+ const [paused, setPaused] = useState(defaultPaused);
191
+ const quoted = cells.filter(isTapeQuote);
192
+ const empty = quoted.length === 0;
193
+ const duration = speed ?? tapeDurationSeconds(quoted.length);
194
+
195
+ return (
196
+ <div
197
+ role="region"
198
+ aria-label={ariaLabel}
199
+ aria-live="off"
200
+ className={cn(
201
+ "flex h-8 min-w-0 items-center gap-3 font-mono text-[0.68rem] text-ink-mute",
202
+ className,
203
+ )}
204
+ >
205
+ {empty ? (
206
+ <span className="mb-status-miss" aria-hidden="true" />
207
+ ) : status === "live" ? (
208
+ <span className="mb-live-mark" aria-hidden="true" />
209
+ ) : (
210
+ <span className="shrink-0 tracking-[0.04em]">{`[${STATUS_WORD[status]}]`}</span>
211
+ )}
212
+
213
+ {empty ? (
214
+ <span className="min-w-0 flex-1 truncate text-ink-soft">Prices did not load.</span>
215
+ ) : (
216
+ <>
217
+ <div aria-hidden="true" className="min-w-0 flex-1">
218
+ <Marquee className="mb-tape" speed={duration} paused={paused}>
219
+ {quoted.map((c) => (
220
+ <Cell key={c.symbol} cell={c} />
221
+ ))}
222
+ </Marquee>
223
+ </div>
224
+ <p className="sr-only">{`${STATUS_SR[status]} ${quoted.map(srCell).join("; ")}.`}</p>
225
+ </>
226
+ )}
227
+
228
+ {empty || !trailing ? null : <span className="hidden shrink-0 sm:inline">{trailing}</span>}
229
+
230
+ {empty ? null : (
231
+ <button
232
+ type="button"
233
+ onClick={() => setPaused((p) => !p)}
234
+ aria-pressed={paused}
235
+ aria-label="Pause price ticker"
236
+ className="shrink-0 cursor-pointer bg-transparent p-0 font-[inherit] text-ink-mute transition-colors hover:text-ink focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent motion-reduce:hidden"
237
+ >
238
+ {paused ? "[play]" : "[pause]"}
239
+ </button>
240
+ )}
241
+ </div>
242
+ );
243
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * OrderBook — the exchange-grade depth ladder promoted from the design
3
+ * reference (finance/order-book): asks descending into the spread, a
4
+ * mid/spread divider, bids below. Each row carries a depth bar scaled to the
5
+ * largest resting size and tinted by side — sell side wears --down, buy side
6
+ * --up: the money colors, never a livery. Mono tabular numerals so prices
7
+ * never jitter. Server component — no state, no effects; token utilities
8
+ * only, so it needs nothing from styles/finance-composites.css (still
9
+ * `@source` this directory so the utilities are generated).
10
+ *
11
+ * Data convention (top to bottom, as rendered): `asks` descend INTO the
12
+ * spread (last element = best ask); `bids` descend AWAY from it (first
13
+ * element = best bid). `mid` and `spread` derive from that touch when
14
+ * omitted. Width is the call site's business (e.g. `max-w-[380px]`).
15
+ */
16
+ export type OrderBookLevel = {
17
+ price: number;
18
+ size: number;
19
+ };
20
+
21
+ export type OrderBookProps = {
22
+ /** Sell side, rendered top-down toward the spread (last = best ask). */
23
+ asks: OrderBookLevel[];
24
+ /** Buy side, rendered top-down away from the spread (first = best bid). */
25
+ bids: OrderBookLevel[];
26
+ /** Preformatted mid read for the divider; midpoint of the touch when omitted. */
27
+ mid?: string;
28
+ /** Preformatted spread read; best ask − best bid when omitted. */
29
+ spread?: string;
30
+ /** Price formatter for derived reads and level prices. */
31
+ formatPrice?: (price: number) => string;
32
+ /** Mono micro-caps column headers. */
33
+ priceLabel?: string;
34
+ sizeLabel?: string;
35
+ /** Label ahead of the spread read on the divider. */
36
+ spreadLabel?: string;
37
+ /** Extra classes on the board shell (margins, max-width …). */
38
+ className?: string;
39
+ /** Accessible name for the depth ladder (announced as the table's name). */
40
+ label?: string;
41
+ };
42
+
43
+ function Row({
44
+ level,
45
+ side,
46
+ peak,
47
+ formatPrice,
48
+ }: {
49
+ level: OrderBookLevel;
50
+ side: "ask" | "bid";
51
+ peak: number;
52
+ formatPrice: (price: number) => string;
53
+ }) {
54
+ const depth = `${(level.size / peak) * 100}%`;
55
+ const ask = side === "ask";
56
+ return (
57
+ <div
58
+ role="row"
59
+ className="relative flex justify-between overflow-hidden px-4 py-[0.28rem] text-[0.78rem]"
60
+ >
61
+ {/* depth bar fills from the right, tinted by side (money colors) */}
62
+ <span
63
+ className={`absolute inset-y-0 end-0 z-0 ${ask ? "bg-down/[0.14]" : "bg-up/[0.14]"}`}
64
+ style={{ width: depth }}
65
+ aria-hidden="true"
66
+ />
67
+ <span role="cell" className={`relative z-[1] ${ask ? "text-down" : "text-up"}`}>
68
+ {formatPrice(level.price)}
69
+ </span>
70
+ <span role="cell" className="relative z-[1] text-ink-soft">
71
+ {level.size}
72
+ </span>
73
+ </div>
74
+ );
75
+ }
76
+
77
+ export function OrderBook({
78
+ asks,
79
+ bids,
80
+ mid,
81
+ spread,
82
+ formatPrice = (price) => price.toFixed(2),
83
+ priceLabel = "price",
84
+ sizeLabel = "size",
85
+ spreadLabel = "spread",
86
+ className,
87
+ label = "Order book",
88
+ }: OrderBookProps) {
89
+ const bestAsk = asks[asks.length - 1]?.price ?? 0;
90
+ const bestBid = bids[0]?.price ?? 0;
91
+ const midText = mid ?? formatPrice((bestAsk + bestBid) / 2);
92
+ const spreadText = spread ?? formatPrice(bestAsk - bestBid);
93
+ const peak = Math.max(1, ...asks.map((l) => l.size), ...bids.map((l) => l.size));
94
+
95
+ return (
96
+ <div
97
+ role="table"
98
+ aria-label={label}
99
+ className={`rounded-none border border-hair bg-surface py-2 font-mono [font-variant-numeric:tabular-nums]${
100
+ className ? ` ${className}` : ""
101
+ }`}
102
+ >
103
+ <div
104
+ role="row"
105
+ className="flex justify-between border-b border-hair px-4 pb-2 pt-[0.3rem] text-[0.58rem] uppercase tracking-[0.1em] text-ink-mute"
106
+ >
107
+ <span role="columnheader">{priceLabel}</span>
108
+ <span role="columnheader">{sizeLabel}</span>
109
+ </div>
110
+ <div role="rowgroup">
111
+ {asks.map((l) => (
112
+ <Row key={l.price} level={l} side="ask" peak={peak} formatPrice={formatPrice} />
113
+ ))}
114
+ </div>
115
+ <div
116
+ role="row"
117
+ className="my-[0.2rem] flex items-baseline justify-between border-y border-hair px-4 py-2"
118
+ >
119
+ <span role="cell" className="text-base text-ink">
120
+ {midText}
121
+ </span>
122
+ <span role="cell" className="text-[0.62rem] uppercase tracking-[0.08em] text-ink-mute">
123
+ {spreadLabel} {spreadText}
124
+ </span>
125
+ </div>
126
+ <div role="rowgroup">
127
+ {bids.map((l) => (
128
+ <Row key={l.price} level={l} side="bid" peak={peak} formatPrice={formatPrice} />
129
+ ))}
130
+ </div>
131
+ </div>
132
+ );
133
+ }