@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,659 @@
1
+ /**
2
+ * Turn the active thread into one offline HTML file.
3
+ *
4
+ * The file is that thread's DOM: same classes, same open and closed disclosures.
5
+ * Disclosures become `<details>` so they still open offline. The composer
6
+ * and anything that sends a message are removed. No script is added.
7
+ */
8
+
9
+ import { redactSnapshotDom } from "./redact-dom";
10
+
11
+ export const SNAPSHOT_CSS = `
12
+ details > summary { list-style: none; cursor: pointer; }
13
+ details > summary::-webkit-details-marker { display: none; }
14
+ :is(.aui-theme-stage, [data-thread-skin="digichat"]) details[open] > summary
15
+ [data-slot="disclosure-chevron"] [data-slot="dot-matrix-grid"],
16
+ details[open] > summary .tc-caret,
17
+ details[open] > summary .tc-caret [data-slot="dot-matrix-grid"] {
18
+ transform: rotate(90deg);
19
+ }
20
+ details:not([open]) > summary .tc-caret.open { transform: none; }
21
+ [data-snapshot-static] { cursor: default; pointer-events: none; }
22
+ `.trim();
23
+
24
+ /** Anything else (scripts, remote images, event handlers) stays blocked. */
25
+ const SNAPSHOT_CSP = [
26
+ "default-src 'none'",
27
+ "style-src 'unsafe-inline'",
28
+ "img-src data:",
29
+ "font-src data:",
30
+ "media-src data:",
31
+ "form-action 'none'",
32
+ "base-uri 'none'",
33
+ ].join("; ");
34
+
35
+ const REMOVE = [
36
+ ".aui-composer-root",
37
+ "[data-slot='aui_composer-shell']",
38
+ "[data-slot='aui_composer-editing']",
39
+ ".aui-assistant-action-bar-root",
40
+ ".aui-user-action-bar-root",
41
+ ".aui-user-action-bar-slot",
42
+ "[data-slot='aui_user-branch-picker']",
43
+ "[data-slot='aui_assistant-branch-picker']",
44
+ ".aui-thread-scroll-to-bottom",
45
+ // The drawn scrollbar is positioned for the live viewport; the file scrolls
46
+ // natively.
47
+ ".aui-overlay-scroll",
48
+ // Skins without the aui class names mark their composer and message
49
+ // controls with this attribute instead.
50
+ "[data-export-chrome]",
51
+ // The code block keeps its language label; the copy button needs script.
52
+ ".aui-code-header-root button",
53
+ "[data-memory-thread-list]",
54
+ ".dc-thread-list",
55
+ "script",
56
+ "noscript",
57
+ "iframe",
58
+ "object",
59
+ "embed",
60
+ "link",
61
+ "base",
62
+ "style",
63
+ "nextjs-portal",
64
+ "#__next-build-watcher",
65
+ ].join(",");
66
+
67
+ const CONTENT_LINK = [
68
+ ".aui-user-message-content",
69
+ ".aui-assistant-message-content",
70
+ "[data-slot='aui_assistant-message-content']",
71
+ "[data-slot='reasoning-content']",
72
+ "[data-slot='tool-fallback-content']",
73
+ "[data-slot='tool-fallback-result']",
74
+ "[data-slot='tool-group-content']",
75
+ "[data-slot='aui_work-chain-content']",
76
+ "[data-slot='aui_source-url']",
77
+ "[data-slot='aui_source-document']",
78
+ ".aui-md",
79
+ ].join(",");
80
+
81
+ const USER_QUESTION = ".aui-user-message-content";
82
+
83
+ /**
84
+ * A thread root: the aui class, or `data-export-thread` on skins that do not
85
+ * use the aui class names.
86
+ */
87
+ const THREAD_ROOT = ".aui-thread-root, [data-export-thread]";
88
+
89
+ /**
90
+ * The work shown around an answer: reasoning, tool calls and their groups,
91
+ * the work chain. Every skin draws these with the same slots. A per-message
92
+ * file holds the answer text only, like the Markdown export.
93
+ * `data-export-work` marks anything else a skin wants left out.
94
+ */
95
+ const ANSWER_WORK = [
96
+ "[data-slot='aui_chain-of-thought']",
97
+ "[data-slot='aui_chain-tools']",
98
+ "[data-slot='aui_work-chain']",
99
+ "[data-slot='aui_work-chain-summary']",
100
+ "[data-slot='aui_private-work-step']",
101
+ "[data-slot='reasoning-root']",
102
+ "[data-slot='tool-group-root']",
103
+ "[data-slot='tool-fallback-root']",
104
+ "[data-export-work]",
105
+ ].join(", ");
106
+
107
+ /**
108
+ * Keep the assistant answer only: drop the user messages, then the reasoning
109
+ * and tool output inside the answer, so the file holds what the Markdown
110
+ * export holds.
111
+ */
112
+ export function keepAnswerOnly(root: ParentNode): void {
113
+ root.querySelectorAll("[data-role='user']").forEach((node) => node.remove());
114
+ root
115
+ .querySelectorAll("[data-role='assistant']")
116
+ .forEach((message) => message.querySelectorAll(ANSWER_WORK).forEach((node) => node.remove()));
117
+ }
118
+
119
+ const PANEL_STYLE_PROPS = [
120
+ "height",
121
+ "width",
122
+ "max-height",
123
+ "min-height",
124
+ "overflow",
125
+ "content-visibility",
126
+ "animation",
127
+ "transition",
128
+ ];
129
+
130
+ export function questionText(root: ParentNode): string {
131
+ const nodes = root.querySelectorAll(USER_QUESTION);
132
+ return nodes[nodes.length - 1]?.textContent?.replace(/\s+/g, " ").trim() ?? "";
133
+ }
134
+
135
+ export function deploymentText(root: ParentNode): string {
136
+ return root.querySelector("[data-deployment]")?.getAttribute("data-deployment")?.trim() ?? "";
137
+ }
138
+
139
+ /**
140
+ * The open conversation. A memory sidebar lists every other thread's title;
141
+ * those titles are not part of this subtree. When several thread roots are
142
+ * mounted, the one marked `data-active="true"` wins, otherwise the last
143
+ * visible root.
144
+ */
145
+ export function activeThreadRoot(root: ParentNode): HTMLElement | null {
146
+ const threads = [...root.querySelectorAll<HTMLElement>(THREAD_ROOT)].filter(isOpenThread);
147
+ if (threads.length === 0) return null;
148
+ const marked = threads.filter((el) => el.getAttribute("data-active") === "true");
149
+ const pool = marked.length > 0 ? marked : threads;
150
+ return pool[pool.length - 1] ?? null;
151
+ }
152
+
153
+ /**
154
+ * A document that holds only the active thread, with the live page's theme.
155
+ * Ancestors are attribute-only wrappers so rules scoped to `[data-stock-product]`
156
+ * still match. A node the live page has computed as `display: none` or
157
+ * `visibility: hidden` is left out. A closed disclosure panel stays, so a
158
+ * folded tool call is still in the file. The open page is not changed.
159
+ */
160
+ const MESSAGE_ROOT = "[data-role='user'], [data-role='assistant']";
161
+
162
+ /**
163
+ * The assistant message that owns this menu trigger. The trigger sits in
164
+ * the message; the menu item does not. A duplicate id earlier in the page
165
+ * is a different element, so this does not search the document.
166
+ */
167
+ export function messageAnchor(trigger: HTMLElement | null, messageId: string): HTMLElement | null {
168
+ const id = messageId.trim();
169
+ if (!trigger || !id) return null;
170
+ const selector = `[data-role="assistant"][data-message-id="${cssEscape(id)}"]`;
171
+ const message = trigger.closest(selector);
172
+ return message instanceof HTMLElement ? message : null;
173
+ }
174
+
175
+ /**
176
+ * A thread the message action may export. The open menu sets aria-hidden on
177
+ * an ancestor; that is not a closed thread. The thread element itself still
178
+ * has to be showing, and a memory-sidebar thread is never exported.
179
+ */
180
+ function isMessageThread(thread: HTMLElement): boolean {
181
+ if (thread.closest("[data-memory-thread-list]")) return false;
182
+ if (thread.hidden || thread.getAttribute("aria-hidden") === "true") return false;
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * `anchor` limits the file to that assistant message and the user message
188
+ * before it. Omitted, the file is the whole thread. An anchor that is not
189
+ * inside its thread, or not inside an assistant message, returns null — it
190
+ * does not widen to the whole thread.
191
+ */
192
+ export function snapshotSource(doc: Document, anchor?: HTMLElement | null): HTMLElement | null {
193
+ const thread = anchor ? anchor.closest<HTMLElement>(THREAD_ROOT) : activeThreadRoot(doc);
194
+ if (!thread || (anchor && !isMessageThread(thread))) return null;
195
+ if (anchor && !thread.contains(anchor)) return null;
196
+ const html = doc.createElement("html");
197
+ copyShellAttributes(doc.documentElement, html);
198
+ const body = threadShell(doc, thread, anchor ?? null);
199
+ if (!body) return null;
200
+ html.append(body);
201
+ stripDeploymentAttributes(html);
202
+ stripPageLock(html);
203
+ return html;
204
+ }
205
+
206
+ function threadShell(
207
+ doc: Document,
208
+ thread: HTMLElement,
209
+ scope: HTMLElement | null,
210
+ ): HTMLElement | null {
211
+ const clone = cloneWithoutHidden(thread, scope);
212
+ if (!clone) return null;
213
+ const body = doc.createElement("body");
214
+ if (doc.body) copyShellAttributes(doc.body, body);
215
+ const ancestors: HTMLElement[] = [];
216
+ for (
217
+ let cursor = thread.parentElement;
218
+ cursor && cursor !== doc.body && cursor !== doc.documentElement;
219
+ cursor = cursor.parentElement
220
+ ) {
221
+ ancestors.push(cursor);
222
+ }
223
+ ancestors.reverse();
224
+ let parent: HTMLElement = body;
225
+ for (const ancestor of ancestors) {
226
+ const wrap = doc.createElement(ancestor.tagName.toLowerCase());
227
+ copyShellAttributes(ancestor, wrap);
228
+ parent.append(wrap);
229
+ parent = wrap;
230
+ }
231
+ parent.append(clone);
232
+ return body;
233
+ }
234
+
235
+ /**
236
+ * Read computed style on the live thread first, then clone. Dropping the
237
+ * live nodes would change the open chat.
238
+ */
239
+ function cloneWithoutHidden(live: HTMLElement, scope: HTMLElement | null): HTMLElement | null {
240
+ const liveNodes = [live, ...live.querySelectorAll("*")];
241
+ const hidden = new Set<number>();
242
+ for (let i = 1; i < liveNodes.length; i += 1) {
243
+ if (suppressed(liveNodes[i]!)) hidden.add(i);
244
+ }
245
+ const clone = live.cloneNode(true) as HTMLElement;
246
+ const cloneNodes = [clone, ...clone.querySelectorAll("*")];
247
+ const liveMessages = [...live.querySelectorAll<HTMLElement>(MESSAGE_ROOT)];
248
+ const cloneMessages = [...clone.querySelectorAll<HTMLElement>(MESSAGE_ROOT)];
249
+ for (const index of [...hidden].sort((a, b) => b - a)) {
250
+ const node = cloneNodes[index];
251
+ if (node && clone.contains(node)) node.remove();
252
+ }
253
+ if (scope && !keepInteraction(liveMessages, cloneMessages, clone, scope)) return null;
254
+ return clone;
255
+ }
256
+
257
+ /**
258
+ * The assistant message that holds `anchor`, plus the user message before it.
259
+ * False when that message cannot be resolved — the caller must not keep the
260
+ * rest of the thread.
261
+ */
262
+ function keepInteraction(
263
+ liveMessages: readonly HTMLElement[],
264
+ cloneMessages: readonly HTMLElement[],
265
+ clone: HTMLElement,
266
+ anchor: HTMLElement,
267
+ ): boolean {
268
+ const answer = anchor.closest<HTMLElement>("[data-role='assistant']");
269
+ if (!answer) return false;
270
+ const index = liveMessages.indexOf(answer);
271
+ if (index < 0) return false;
272
+ const keep = new Set<number>([index]);
273
+ for (let i = index - 1; i >= 0; i -= 1) {
274
+ if (liveMessages[i]?.getAttribute("data-role") === "user") {
275
+ keep.add(i);
276
+ break;
277
+ }
278
+ }
279
+ for (let i = cloneMessages.length - 1; i >= 0; i -= 1) {
280
+ const node = cloneMessages[i];
281
+ if (node && !keep.has(i) && clone.contains(node)) node.remove();
282
+ }
283
+ return true;
284
+ }
285
+
286
+ function suppressed(el: Element): boolean {
287
+ // The closed panel itself stays so a folded tool call can open offline.
288
+ // A descendant the deployment has hidden is still dropped.
289
+ if (isClosedPanel(el)) return false;
290
+ const view = el.ownerDocument.defaultView;
291
+ if (!view) return false;
292
+ const style = view.getComputedStyle(el);
293
+ if (style.display === "none") return true;
294
+ if (style.visibility !== "hidden") return false;
295
+ const parent = el.parentElement;
296
+ if (!parent) return true;
297
+ // Visibility inherits. A child of an already-hidden parent is not a
298
+ // deployment hide of its own.
299
+ return view.getComputedStyle(parent).visibility !== "hidden";
300
+ }
301
+
302
+ function isClosedPanel(el: Element): boolean {
303
+ const hidden = el instanceof HTMLElement ? el.hidden : el.hasAttribute("hidden");
304
+ if (!hidden) return false;
305
+ const slot = el.getAttribute("data-slot") ?? "";
306
+ if (slot.endsWith("-content") || slot === "collapsible-content") return true;
307
+ const id = el.getAttribute("id");
308
+ if (!id) return false;
309
+ return el.ownerDocument.querySelector(`[aria-controls="${cssEscape(id)}"]`) !== null;
310
+ }
311
+
312
+ function isDeploymentAttribute(name: string): boolean {
313
+ return /deployment|tenant/i.test(name);
314
+ }
315
+
316
+ /** The slug names the file. It does not belong in the file. */
317
+ function stripDeploymentAttributes(root: Element): void {
318
+ const nodes = [root, ...root.querySelectorAll("*")];
319
+ for (const node of nodes) {
320
+ for (const attr of [...node.attributes]) {
321
+ if (isDeploymentAttribute(attr.name)) node.removeAttribute(attr.name);
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * An open menu locks the page: aria-hidden where Radix also sets
328
+ * data-aria-hidden, inert on the shell, and react-remove-scroll's
329
+ * `data-scroll-locked` plus `pointer-events: none` on the body. A
330
+ * decorative aria-hidden inside the thread stays. The open page is
331
+ * left as it is.
332
+ */
333
+ function stripPageLock(root: Element): void {
334
+ const nodes = [root, ...root.querySelectorAll("*")];
335
+ for (const node of nodes) {
336
+ node.removeAttribute("data-scroll-locked");
337
+ if (node.hasAttribute("data-aria-hidden")) {
338
+ node.removeAttribute("aria-hidden");
339
+ node.removeAttribute("data-aria-hidden");
340
+ }
341
+ if (node.tagName !== "BODY") continue;
342
+ const style = node.getAttribute("style");
343
+ if (!style) continue;
344
+ const kept = style
345
+ .split(";")
346
+ .map((part) => part.trim())
347
+ .filter((part) => part && !/^pointer-events\s*:/i.test(part));
348
+ if (kept.length === 0) node.removeAttribute("style");
349
+ else node.setAttribute("style", kept.join("; "));
350
+ }
351
+ const thread = root.querySelector(".aui-thread-root");
352
+ for (let cursor = thread?.parentElement ?? null; cursor; cursor = cursor.parentElement) {
353
+ cursor.removeAttribute("inert");
354
+ }
355
+ }
356
+
357
+ /** Drop rules that only exist while a menu holds the page scroll lock. */
358
+ export function omitScrollLockCss(css: string): string {
359
+ let out = "";
360
+ let i = 0;
361
+ while (i < css.length) {
362
+ const open = css.indexOf("{", i);
363
+ if (open < 0) {
364
+ out += css.slice(i);
365
+ break;
366
+ }
367
+ let depth = 0;
368
+ let j = open;
369
+ for (; j < css.length; j += 1) {
370
+ const ch = css[j];
371
+ if (ch === "{") depth += 1;
372
+ else if (ch === "}") {
373
+ depth -= 1;
374
+ if (depth === 0) {
375
+ j += 1;
376
+ break;
377
+ }
378
+ }
379
+ }
380
+ const block = css.slice(i, j);
381
+ if (!block.includes("data-scroll-locked")) {
382
+ out += block;
383
+ } else {
384
+ const prelude = css.slice(i, open).trim();
385
+ const inner = css.slice(open + 1, Math.max(open + 1, j - 1));
386
+ if (prelude.startsWith("@") && inner.includes("{")) {
387
+ const kept = omitScrollLockCss(inner);
388
+ if (kept.trim()) out += `${css.slice(i, open)}{${kept}}`;
389
+ }
390
+ }
391
+ i = j;
392
+ }
393
+ return out;
394
+ }
395
+
396
+ function copyShellAttributes(from: Element, to: Element): void {
397
+ for (const attr of from.attributes) {
398
+ if (attr.name.toLowerCase().startsWith("on")) continue;
399
+ if (isDeploymentAttribute(attr.name)) continue;
400
+ to.setAttribute(attr.name, attr.value);
401
+ }
402
+ }
403
+
404
+ function isOpenThread(el: HTMLElement): boolean {
405
+ if (el.closest("[data-memory-thread-list]")) return false;
406
+ for (let cursor: HTMLElement | null = el; cursor; cursor = cursor.parentElement) {
407
+ if (cursor.hidden || cursor.getAttribute("aria-hidden") === "true") return false;
408
+ }
409
+ return true;
410
+ }
411
+
412
+ /** `<` would close the style element. CSS `\3c ` is that character. */
413
+ export function escapeStyleText(css: string): string {
414
+ return css.replaceAll("<", "\\3c ");
415
+ }
416
+
417
+ /** Mutates `root` into a read-only snapshot of itself. */
418
+ export function transformSnapshotDom(root: HTMLElement): void {
419
+ stripRefreshMeta(root);
420
+ root.querySelectorAll(REMOVE).forEach((node) => node.remove());
421
+ root.querySelectorAll("[data-nextjs-dialog-overlay], [data-nextjs-toast]").forEach((node) => {
422
+ node.remove();
423
+ });
424
+ convertDisclosures(root);
425
+ freezeChrome(root);
426
+ }
427
+
428
+ export function serializeSnapshot(root: HTMLElement, css: string): string {
429
+ const doc = root.ownerDocument;
430
+ // Redact text and attribute values before the page CSS goes in, so the
431
+ // inlined fonts and the stylesheet are never rewritten.
432
+ redactSnapshotDom(root);
433
+ stripRefreshMeta(root);
434
+ const head = ensureHead(root);
435
+ head.querySelectorAll("style, link, base").forEach((node) => node.remove());
436
+ head
437
+ .querySelectorAll("meta[http-equiv='Content-Security-Policy' i]")
438
+ .forEach((node) => node.remove());
439
+ if (!head.querySelector("meta[charset]")) {
440
+ const meta = doc.createElement("meta");
441
+ meta.setAttribute("charset", "utf-8");
442
+ head.prepend(meta);
443
+ }
444
+ const csp = doc.createElement("meta");
445
+ csp.setAttribute("http-equiv", "Content-Security-Policy");
446
+ csp.setAttribute("content", SNAPSHOT_CSP);
447
+ // First, so it is in effect before the stylesheet is parsed.
448
+ head.prepend(csp);
449
+ const style = doc.createElement("style");
450
+ style.setAttribute("data-snapshot-css", "");
451
+ style.textContent = escapeStyleText(`${omitScrollLockCss(css)}\n${SNAPSHOT_CSS}`);
452
+ head.append(style);
453
+ for (const el of root.querySelectorAll<HTMLElement>("[style]")) {
454
+ const inline = el.getAttribute("style");
455
+ if (inline?.includes("<")) el.setAttribute("style", escapeStyleText(inline));
456
+ }
457
+ const html = root.tagName === "HTML" ? root.outerHTML : `<html>${root.outerHTML}</html>`;
458
+ return `<!DOCTYPE html>\n${html}`;
459
+ }
460
+
461
+ /** A refresh meta navigates the file. It is removed wherever it sits. */
462
+ function stripRefreshMeta(root: ParentNode): void {
463
+ for (const node of [...root.querySelectorAll("meta")]) {
464
+ if ((node.getAttribute("http-equiv") ?? "").toLowerCase() === "refresh") node.remove();
465
+ }
466
+ for (const template of [...root.querySelectorAll("template")]) {
467
+ if (template instanceof HTMLTemplateElement) stripRefreshMeta(template.content);
468
+ }
469
+ }
470
+
471
+ function ensureHead(root: HTMLElement): HTMLElement {
472
+ const existing = root.querySelector(":scope > head");
473
+ if (existing instanceof HTMLElement) return existing;
474
+ const head = root.ownerDocument.createElement("head");
475
+ root.prepend(head);
476
+ return head;
477
+ }
478
+
479
+ function convertDisclosures(root: HTMLElement): void {
480
+ const triggers = [...root.querySelectorAll<HTMLElement>("[aria-expanded]")]
481
+ .filter((trigger) => root.contains(trigger) && isChatDisclosure(trigger))
482
+ .sort((a, b) => depth(b) - depth(a));
483
+ for (const trigger of triggers) {
484
+ if (!root.contains(trigger)) continue;
485
+ convertDisclosure(root, trigger);
486
+ }
487
+ }
488
+
489
+ function isChatDisclosure(trigger: Element): boolean {
490
+ const slot = trigger.getAttribute("data-slot") ?? "";
491
+ if (slot.endsWith("-trigger") || trigger.classList.contains("tc-head")) return true;
492
+ return trigger.querySelector("[data-slot='disclosure-chevron']") !== null;
493
+ }
494
+
495
+ function convertDisclosure(root: HTMLElement, trigger: HTMLElement): void {
496
+ const panel = findPanel(root, trigger);
497
+ if (!panel || panel.contains(trigger)) return;
498
+ const host = trigger.parentElement;
499
+ if (!host || !root.contains(host)) return;
500
+ const details = trigger.ownerDocument.createElement("details");
501
+ copyAttrs(host, details, false);
502
+ if (disclosureOpen(trigger)) details.setAttribute("open", "");
503
+ const summary = trigger.ownerDocument.createElement("summary");
504
+ copyAttrs(trigger, summary, true);
505
+ while (trigger.firstChild) summary.append(trigger.firstChild);
506
+ const leftovers = [...host.childNodes].filter((node) => node !== trigger);
507
+ host.replaceWith(details);
508
+ details.append(summary);
509
+ for (const node of leftovers) details.append(node);
510
+ unlockPanel(panel);
511
+ if (!details.contains(panel)) details.append(panel);
512
+ }
513
+
514
+ function findPanel(root: ParentNode, trigger: Element): HTMLElement | null {
515
+ const id = trigger.getAttribute("aria-controls");
516
+ if (id) {
517
+ const found = root.querySelector<HTMLElement>(`[id="${cssEscape(id)}"]`);
518
+ if (found && found !== trigger && !found.contains(trigger)) return found;
519
+ }
520
+ if (trigger.classList.contains("tc-head")) {
521
+ const next = trigger.nextElementSibling;
522
+ if (next instanceof HTMLElement) return next;
523
+ }
524
+ const next = trigger.nextElementSibling;
525
+ if (next instanceof HTMLElement && looksLikePanel(next)) return next;
526
+ // Scan forward from the trigger: with two disclosures under one parent, the
527
+ // second trigger must not take the first one's panel.
528
+ for (let sib = trigger.nextElementSibling; sib; sib = sib.nextElementSibling) {
529
+ if (sib instanceof HTMLElement && looksLikePanel(sib)) return sib;
530
+ }
531
+ return null;
532
+ }
533
+
534
+ function looksLikePanel(el: Element): boolean {
535
+ const slot = el.getAttribute("data-slot") ?? "";
536
+ return slot.endsWith("-content") || slot === "collapsible-content";
537
+ }
538
+
539
+ function disclosureOpen(trigger: Element): boolean {
540
+ if (trigger.getAttribute("aria-expanded") === "true") return true;
541
+ if (trigger.hasAttribute("data-panel-open")) return true;
542
+ if (trigger.getAttribute("data-state") === "open") return true;
543
+ const host = trigger.parentElement;
544
+ return Boolean(host?.hasAttribute("data-open") && !host.hasAttribute("data-closed"));
545
+ }
546
+
547
+ function unlockPanel(panel: HTMLElement): void {
548
+ panel.hidden = false;
549
+ panel.removeAttribute("inert");
550
+ for (const name of ["data-closed", "data-open", "data-starting-style", "data-ending-style"]) {
551
+ panel.removeAttribute(name);
552
+ }
553
+ const style = panel.getAttribute("style");
554
+ if (!style) return;
555
+ const next = style
556
+ .split(";")
557
+ .map((part) => part.trim())
558
+ .filter((part) => {
559
+ const prop = part.split(":")[0]?.trim().toLowerCase() ?? "";
560
+ if (!part || PANEL_STYLE_PROPS.includes(prop)) return false;
561
+ return !prop.startsWith("--collapsible-panel");
562
+ })
563
+ .join("; ");
564
+ if (next) panel.setAttribute("style", next);
565
+ else panel.removeAttribute("style");
566
+ }
567
+
568
+ function freezeChrome(root: HTMLElement): void {
569
+ root.querySelectorAll("textarea, input, select").forEach((node) => node.remove());
570
+ root.querySelectorAll("[contenteditable='true']").forEach((node) => {
571
+ node.removeAttribute("contenteditable");
572
+ });
573
+ root.querySelectorAll("form").forEach((form) => {
574
+ form.removeAttribute("action");
575
+ form.removeAttribute("method");
576
+ });
577
+ for (const button of [...root.querySelectorAll("button")]) freezeControl(button);
578
+ for (const link of [...root.querySelectorAll("a")]) freezeLink(link);
579
+ stripHandlers(root);
580
+ }
581
+
582
+ function freezeControl(el: Element): void {
583
+ const span = el.ownerDocument.createElement("span");
584
+ copyAttrs(el, span, true);
585
+ span.setAttribute("data-snapshot-static", "");
586
+ while (el.firstChild) span.append(el.firstChild);
587
+ el.replaceWith(span);
588
+ }
589
+
590
+ function freezeLink(link: Element): void {
591
+ const href = link.getAttribute("href") ?? "";
592
+ // Only http(s), mailto and in-page links survive; javascript:, vbscript:,
593
+ // data: and any other scheme are dropped.
594
+ if (!safeHref(href)) link.removeAttribute("href");
595
+ if (link.closest(CONTENT_LINK)) return;
596
+ link.removeAttribute("href");
597
+ link.removeAttribute("target");
598
+ link.setAttribute("data-snapshot-static", "");
599
+ }
600
+
601
+ function safeHref(href: string): boolean {
602
+ const value = href.trim().toLowerCase();
603
+ if (!value || value.startsWith("#")) return true;
604
+ const scheme = /^([a-z][a-z0-9+.-]{0,31}):/.exec(value)?.[1];
605
+ if (!scheme) return true; // relative
606
+ return scheme === "http" || scheme === "https" || scheme === "mailto";
607
+ }
608
+
609
+ function stripHandlers(root: HTMLElement): void {
610
+ const elements = [root, ...root.querySelectorAll("*")];
611
+ for (const el of elements) {
612
+ for (const attr of [...el.attributes]) {
613
+ if (attr.name.toLowerCase().startsWith("on")) el.removeAttribute(attr.name);
614
+ }
615
+ }
616
+ }
617
+
618
+ function copyAttrs(from: Element, to: Element, trigger: boolean): void {
619
+ for (const attr of from.attributes) {
620
+ const name = attr.name.toLowerCase();
621
+ if (name.startsWith("on") || name === "hidden" || name === "style") continue;
622
+ if (name.startsWith("aria-") && name !== "aria-label") continue;
623
+ if (
624
+ name === "type" ||
625
+ name === "disabled" ||
626
+ name === "tabindex" ||
627
+ name === "role" ||
628
+ name === "href" ||
629
+ name === "src"
630
+ ) {
631
+ continue;
632
+ }
633
+ if (
634
+ !trigger &&
635
+ (name === "data-open" ||
636
+ name === "data-closed" ||
637
+ name === "data-state" ||
638
+ name === "data-panel-open")
639
+ ) {
640
+ continue;
641
+ }
642
+ to.setAttribute(attr.name, attr.value);
643
+ }
644
+ }
645
+
646
+ function depth(el: Element): number {
647
+ let n = 0;
648
+ let cursor: Element | null = el;
649
+ while (cursor) {
650
+ n += 1;
651
+ cursor = cursor.parentElement;
652
+ }
653
+ return n;
654
+ }
655
+
656
+ function cssEscape(value: string): string {
657
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") return CSS.escape(value);
658
+ return value.replace(/["\\]/g, "\\$&");
659
+ }