@drscrewdriver/dsh-better-display 0.3.3-fork.1

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 (390) hide show
  1. package/CHANGELOG.md +259 -0
  2. package/DESIGN.md +72 -0
  3. package/LICENSE +24 -0
  4. package/README.en.md +55 -0
  5. package/README.md +55 -0
  6. package/THIRD_PARTY_NOTICES.md +65 -0
  7. package/compat/harness-rc2.json +99 -0
  8. package/cordis.patch.yml +5 -0
  9. package/cordis.yml +4 -0
  10. package/docs/official-rendering-bridge.md +65 -0
  11. package/dshx.yml +5 -0
  12. package/lib/client.js +49292 -0
  13. package/lib/client.js.map +1 -0
  14. package/lib/dsh-better-display.js +101 -0
  15. package/lib/types/client/Blocks.d.ts +49 -0
  16. package/lib/types/client/Blocks.d.ts.map +1 -0
  17. package/lib/types/client/Blocks.js +143 -0
  18. package/lib/types/client/Blocks.js.map +1 -0
  19. package/lib/types/client/ChoreographedFlow.d.ts +22 -0
  20. package/lib/types/client/ChoreographedFlow.d.ts.map +1 -0
  21. package/lib/types/client/ChoreographedFlow.js +218 -0
  22. package/lib/types/client/ChoreographedFlow.js.map +1 -0
  23. package/lib/types/client/ClosedProcessSummary.d.ts +9 -0
  24. package/lib/types/client/ClosedProcessSummary.d.ts.map +1 -0
  25. package/lib/types/client/ClosedProcessSummary.js +16 -0
  26. package/lib/types/client/ClosedProcessSummary.js.map +1 -0
  27. package/lib/types/client/DiffPanel.d.ts +13 -0
  28. package/lib/types/client/DiffPanel.d.ts.map +1 -0
  29. package/lib/types/client/DiffPanel.js +115 -0
  30. package/lib/types/client/DiffPanel.js.map +1 -0
  31. package/lib/types/client/LiveFold.d.ts +10 -0
  32. package/lib/types/client/LiveFold.d.ts.map +1 -0
  33. package/lib/types/client/LiveFold.js +39 -0
  34. package/lib/types/client/LiveFold.js.map +1 -0
  35. package/lib/types/client/McpAppFrame.d.ts +32 -0
  36. package/lib/types/client/McpAppFrame.d.ts.map +1 -0
  37. package/lib/types/client/McpAppFrame.js +193 -0
  38. package/lib/types/client/McpAppFrame.js.map +1 -0
  39. package/lib/types/client/OfficialContent.d.ts +25 -0
  40. package/lib/types/client/OfficialContent.d.ts.map +1 -0
  41. package/lib/types/client/OfficialContent.js +53 -0
  42. package/lib/types/client/OfficialContent.js.map +1 -0
  43. package/lib/types/client/Reader.d.ts +3 -0
  44. package/lib/types/client/Reader.d.ts.map +1 -0
  45. package/lib/types/client/Reader.js +603 -0
  46. package/lib/types/client/Reader.js.map +1 -0
  47. package/lib/types/client/ReasoningCard.d.ts +11 -0
  48. package/lib/types/client/ReasoningCard.d.ts.map +1 -0
  49. package/lib/types/client/ReasoningCard.js +322 -0
  50. package/lib/types/client/ReasoningCard.js.map +1 -0
  51. package/lib/types/client/SettingsSection.d.ts +37 -0
  52. package/lib/types/client/SettingsSection.d.ts.map +1 -0
  53. package/lib/types/client/SettingsSection.js +63 -0
  54. package/lib/types/client/SettingsSection.js.map +1 -0
  55. package/lib/types/client/StickyLane.d.ts +8 -0
  56. package/lib/types/client/StickyLane.d.ts.map +1 -0
  57. package/lib/types/client/StickyLane.js +39 -0
  58. package/lib/types/client/StickyLane.js.map +1 -0
  59. package/lib/types/client/TimelineRail.d.ts +10 -0
  60. package/lib/types/client/TimelineRail.d.ts.map +1 -0
  61. package/lib/types/client/TimelineRail.js +168 -0
  62. package/lib/types/client/TimelineRail.js.map +1 -0
  63. package/lib/types/client/ToolActivity.d.ts +17 -0
  64. package/lib/types/client/ToolActivity.d.ts.map +1 -0
  65. package/lib/types/client/ToolActivity.js +244 -0
  66. package/lib/types/client/ToolActivity.js.map +1 -0
  67. package/lib/types/client/TurnMetrics.d.ts +11 -0
  68. package/lib/types/client/TurnMetrics.d.ts.map +1 -0
  69. package/lib/types/client/TurnMetrics.js +44 -0
  70. package/lib/types/client/TurnMetrics.js.map +1 -0
  71. package/lib/types/client/WaitClock.d.ts +16 -0
  72. package/lib/types/client/WaitClock.d.ts.map +1 -0
  73. package/lib/types/client/WaitClock.js +29 -0
  74. package/lib/types/client/WaitClock.js.map +1 -0
  75. package/lib/types/client/WaitingClock.d.ts +15 -0
  76. package/lib/types/client/WaitingClock.d.ts.map +1 -0
  77. package/lib/types/client/WaitingClock.js +30 -0
  78. package/lib/types/client/WaitingClock.js.map +1 -0
  79. package/lib/types/client/WaitingStatus.d.ts +6 -0
  80. package/lib/types/client/WaitingStatus.d.ts.map +1 -0
  81. package/lib/types/client/WaitingStatus.js +9 -0
  82. package/lib/types/client/WaitingStatus.js.map +1 -0
  83. package/lib/types/client/conversation-scroll.d.ts +19 -0
  84. package/lib/types/client/conversation-scroll.d.ts.map +1 -0
  85. package/lib/types/client/conversation-scroll.js +25 -0
  86. package/lib/types/client/conversation-scroll.js.map +1 -0
  87. package/lib/types/client/deliverables.d.ts +24 -0
  88. package/lib/types/client/deliverables.d.ts.map +1 -0
  89. package/lib/types/client/deliverables.js +104 -0
  90. package/lib/types/client/deliverables.js.map +1 -0
  91. package/lib/types/client/entry-policy.d.ts +10 -0
  92. package/lib/types/client/entry-policy.d.ts.map +1 -0
  93. package/lib/types/client/entry-policy.js +24 -0
  94. package/lib/types/client/entry-policy.js.map +1 -0
  95. package/lib/types/client/entry.d.ts +4 -0
  96. package/lib/types/client/entry.d.ts.map +1 -0
  97. package/lib/types/client/entry.js +34 -0
  98. package/lib/types/client/entry.js.map +1 -0
  99. package/lib/types/client/fold-choreography.d.ts +40 -0
  100. package/lib/types/client/fold-choreography.d.ts.map +1 -0
  101. package/lib/types/client/fold-choreography.js +48 -0
  102. package/lib/types/client/fold-choreography.js.map +1 -0
  103. package/lib/types/client/fold-intensity.d.ts +11 -0
  104. package/lib/types/client/fold-intensity.d.ts.map +1 -0
  105. package/lib/types/client/fold-intensity.js +32 -0
  106. package/lib/types/client/fold-intensity.js.map +1 -0
  107. package/lib/types/client/index.d.ts +7 -0
  108. package/lib/types/client/index.d.ts.map +1 -0
  109. package/lib/types/client/index.js +195 -0
  110. package/lib/types/client/index.js.map +1 -0
  111. package/lib/types/client/live-turn.d.ts +58 -0
  112. package/lib/types/client/live-turn.d.ts.map +1 -0
  113. package/lib/types/client/live-turn.js +190 -0
  114. package/lib/types/client/live-turn.js.map +1 -0
  115. package/lib/types/client/markdown/MarkdownText.d.ts +41 -0
  116. package/lib/types/client/markdown/MarkdownText.d.ts.map +1 -0
  117. package/lib/types/client/markdown/MarkdownText.js +178 -0
  118. package/lib/types/client/markdown/MarkdownText.js.map +1 -0
  119. package/lib/types/client/markdown/cjkFriendlyStrong.d.ts +9 -0
  120. package/lib/types/client/markdown/cjkFriendlyStrong.d.ts.map +1 -0
  121. package/lib/types/client/markdown/cjkFriendlyStrong.js +73 -0
  122. package/lib/types/client/markdown/cjkFriendlyStrong.js.map +1 -0
  123. package/lib/types/client/markdown/incremental.d.ts +69 -0
  124. package/lib/types/client/markdown/incremental.d.ts.map +1 -0
  125. package/lib/types/client/markdown/incremental.js +108 -0
  126. package/lib/types/client/markdown/incremental.js.map +1 -0
  127. package/lib/types/client/markdown/katex.d.ts +27 -0
  128. package/lib/types/client/markdown/katex.d.ts.map +1 -0
  129. package/lib/types/client/markdown/katex.js +88 -0
  130. package/lib/types/client/markdown/katex.js.map +1 -0
  131. package/lib/types/client/markdown/mathCompatibility.d.ts +11 -0
  132. package/lib/types/client/markdown/mathCompatibility.d.ts.map +1 -0
  133. package/lib/types/client/markdown/mathCompatibility.js +293 -0
  134. package/lib/types/client/markdown/mathCompatibility.js.map +1 -0
  135. package/lib/types/client/markdown/parse.d.ts +25 -0
  136. package/lib/types/client/markdown/parse.d.ts.map +1 -0
  137. package/lib/types/client/markdown/parse.js +42 -0
  138. package/lib/types/client/markdown/parse.js.map +1 -0
  139. package/lib/types/client/markdown/render.d.ts +121 -0
  140. package/lib/types/client/markdown/render.d.ts.map +1 -0
  141. package/lib/types/client/markdown/render.js +452 -0
  142. package/lib/types/client/markdown/render.js.map +1 -0
  143. package/lib/types/client/mcp-app.d.ts +50 -0
  144. package/lib/types/client/mcp-app.d.ts.map +1 -0
  145. package/lib/types/client/mcp-app.js +328 -0
  146. package/lib/types/client/mcp-app.js.map +1 -0
  147. package/lib/types/client/message-chrome.d.ts +19 -0
  148. package/lib/types/client/message-chrome.d.ts.map +1 -0
  149. package/lib/types/client/message-chrome.js +42 -0
  150. package/lib/types/client/message-chrome.js.map +1 -0
  151. package/lib/types/client/motion.d.ts +38 -0
  152. package/lib/types/client/motion.d.ts.map +1 -0
  153. package/lib/types/client/motion.js +453 -0
  154. package/lib/types/client/motion.js.map +1 -0
  155. package/lib/types/client/native/ContextBody.d.ts +130 -0
  156. package/lib/types/client/native/ContextBody.d.ts.map +1 -0
  157. package/lib/types/client/native/ContextBody.js +398 -0
  158. package/lib/types/client/native/ContextBody.js.map +1 -0
  159. package/lib/types/client/native/ContextInjectionRow.d.ts +35 -0
  160. package/lib/types/client/native/ContextInjectionRow.d.ts.map +1 -0
  161. package/lib/types/client/native/ContextInjectionRow.js +42 -0
  162. package/lib/types/client/native/ContextInjectionRow.js.map +1 -0
  163. package/lib/types/client/native/ReferenceIcon.d.ts +16 -0
  164. package/lib/types/client/native/ReferenceIcon.d.ts.map +1 -0
  165. package/lib/types/client/native/ReferenceIcon.js +16 -0
  166. package/lib/types/client/native/ReferenceIcon.js.map +1 -0
  167. package/lib/types/client/native/tool-call-model.d.ts +58 -0
  168. package/lib/types/client/native/tool-call-model.d.ts.map +1 -0
  169. package/lib/types/client/native/tool-call-model.js +219 -0
  170. package/lib/types/client/native/tool-call-model.js.map +1 -0
  171. package/lib/types/client/native/tool-update.d.ts +23 -0
  172. package/lib/types/client/native/tool-update.d.ts.map +1 -0
  173. package/lib/types/client/native/tool-update.js +22 -0
  174. package/lib/types/client/native/tool-update.js.map +1 -0
  175. package/lib/types/client/official-slots.d.ts +71 -0
  176. package/lib/types/client/official-slots.d.ts.map +1 -0
  177. package/lib/types/client/official-slots.js +183 -0
  178. package/lib/types/client/official-slots.js.map +1 -0
  179. package/lib/types/client/open-file.d.ts +29 -0
  180. package/lib/types/client/open-file.d.ts.map +1 -0
  181. package/lib/types/client/open-file.js +71 -0
  182. package/lib/types/client/open-file.js.map +1 -0
  183. package/lib/types/client/pending-submission.d.ts +28 -0
  184. package/lib/types/client/pending-submission.d.ts.map +1 -0
  185. package/lib/types/client/pending-submission.js +28 -0
  186. package/lib/types/client/pending-submission.js.map +1 -0
  187. package/lib/types/client/platform-media.d.ts +6 -0
  188. package/lib/types/client/platform-media.d.ts.map +1 -0
  189. package/lib/types/client/platform-media.js +18 -0
  190. package/lib/types/client/platform-media.js.map +1 -0
  191. package/lib/types/client/primitive-labels.d.ts +10 -0
  192. package/lib/types/client/primitive-labels.d.ts.map +1 -0
  193. package/lib/types/client/primitive-labels.js +38 -0
  194. package/lib/types/client/primitive-labels.js.map +1 -0
  195. package/lib/types/client/projection.d.ts +41 -0
  196. package/lib/types/client/projection.d.ts.map +1 -0
  197. package/lib/types/client/projection.js +112 -0
  198. package/lib/types/client/projection.js.map +1 -0
  199. package/lib/types/client/reasoning-follow.d.ts +10 -0
  200. package/lib/types/client/reasoning-follow.d.ts.map +1 -0
  201. package/lib/types/client/reasoning-follow.js +21 -0
  202. package/lib/types/client/reasoning-follow.js.map +1 -0
  203. package/lib/types/client/resource-address.d.ts +3 -0
  204. package/lib/types/client/resource-address.d.ts.map +1 -0
  205. package/lib/types/client/resource-address.js +33 -0
  206. package/lib/types/client/resource-address.js.map +1 -0
  207. package/lib/types/client/settings-copy.d.ts +7 -0
  208. package/lib/types/client/settings-copy.d.ts.map +1 -0
  209. package/lib/types/client/settings-copy.js +53 -0
  210. package/lib/types/client/settings-copy.js.map +1 -0
  211. package/lib/types/client/settings-slots.d.ts +23 -0
  212. package/lib/types/client/settings-slots.d.ts.map +1 -0
  213. package/lib/types/client/settings-slots.js +2 -0
  214. package/lib/types/client/settings-slots.js.map +1 -0
  215. package/lib/types/client/settings.d.ts +4 -0
  216. package/lib/types/client/settings.d.ts.map +1 -0
  217. package/lib/types/client/settings.js +60 -0
  218. package/lib/types/client/settings.js.map +1 -0
  219. package/lib/types/client/skill-status.d.ts +25 -0
  220. package/lib/types/client/skill-status.d.ts.map +1 -0
  221. package/lib/types/client/skill-status.js +44 -0
  222. package/lib/types/client/skill-status.js.map +1 -0
  223. package/lib/types/client/store.d.ts +35 -0
  224. package/lib/types/client/store.d.ts.map +1 -0
  225. package/lib/types/client/store.js +37 -0
  226. package/lib/types/client/store.js.map +1 -0
  227. package/lib/types/client/stream-buffer.d.ts +37 -0
  228. package/lib/types/client/stream-buffer.d.ts.map +1 -0
  229. package/lib/types/client/stream-buffer.js +150 -0
  230. package/lib/types/client/stream-buffer.js.map +1 -0
  231. package/lib/types/client/streaming.d.ts +18 -0
  232. package/lib/types/client/streaming.d.ts.map +1 -0
  233. package/lib/types/client/streaming.js +84 -0
  234. package/lib/types/client/streaming.js.map +1 -0
  235. package/lib/types/client/timeline.d.ts +26 -0
  236. package/lib/types/client/timeline.d.ts.map +1 -0
  237. package/lib/types/client/timeline.js +48 -0
  238. package/lib/types/client/timeline.js.map +1 -0
  239. package/lib/types/client/tool-activity.d.ts +98 -0
  240. package/lib/types/client/tool-activity.d.ts.map +1 -0
  241. package/lib/types/client/tool-activity.js +301 -0
  242. package/lib/types/client/tool-activity.js.map +1 -0
  243. package/lib/types/client/types.d.ts +91 -0
  244. package/lib/types/client/types.d.ts.map +1 -0
  245. package/lib/types/client/types.js +2 -0
  246. package/lib/types/client/types.js.map +1 -0
  247. package/lib/types/client/waiting-clock.d.ts +49 -0
  248. package/lib/types/client/waiting-clock.d.ts.map +1 -0
  249. package/lib/types/client/waiting-clock.js +80 -0
  250. package/lib/types/client/waiting-clock.js.map +1 -0
  251. package/lib/types/client/word-motion.d.ts +16 -0
  252. package/lib/types/client/word-motion.d.ts.map +1 -0
  253. package/lib/types/client/word-motion.js +90 -0
  254. package/lib/types/client/word-motion.js.map +1 -0
  255. package/lib/types/client/word-timeline.d.ts +32 -0
  256. package/lib/types/client/word-timeline.d.ts.map +1 -0
  257. package/lib/types/client/word-timeline.js +119 -0
  258. package/lib/types/client/word-timeline.js.map +1 -0
  259. package/lib/types/dsh-better-display.d.ts +17 -0
  260. package/lib/types/dsh-better-display.d.ts.map +1 -0
  261. package/lib/types/dsh-better-display.js +93 -0
  262. package/lib/types/dsh-better-display.js.map +1 -0
  263. package/lib/types/skill-roots.d.ts +22 -0
  264. package/lib/types/skill-roots.d.ts.map +1 -0
  265. package/lib/types/skill-roots.js +126 -0
  266. package/lib/types/skill-roots.js.map +1 -0
  267. package/lib/types/skill-status.d.ts +36 -0
  268. package/lib/types/skill-status.d.ts.map +1 -0
  269. package/lib/types/skill-status.js +51 -0
  270. package/lib/types/skill-status.js.map +1 -0
  271. package/package.json +133 -0
  272. package/scripts/build-motion-fixture.mjs +26 -0
  273. package/scripts/check-harness-compat.mjs +71 -0
  274. package/scripts/link-harness-dependencies.mjs +54 -0
  275. package/scripts/verify-auto-fold.mjs +114 -0
  276. package/scripts/verify-official-bridge.mjs +121 -0
  277. package/scripts/verify-tool-shimmer.mjs +62 -0
  278. package/skills/generative-mcpapps/SKILL.md +47 -0
  279. package/skills/generative-mcpapps/examples/interactive_quiz.html +153 -0
  280. package/skills/generative-mcpapps/references/html_boilerplate.md +122 -0
  281. package/skills/generative-mcpapps/references/sep1865_protocol.md +54 -0
  282. package/src/client/Blocks.tsx +213 -0
  283. package/src/client/ChoreographedFlow.tsx +232 -0
  284. package/src/client/ClosedProcessSummary.tsx +23 -0
  285. package/src/client/DiffPanel.tsx +124 -0
  286. package/src/client/LiveFold.tsx +43 -0
  287. package/src/client/McpAppFrame.module.css +210 -0
  288. package/src/client/McpAppFrame.tsx +317 -0
  289. package/src/client/OfficialContent.tsx +69 -0
  290. package/src/client/Reader.module.css +692 -0
  291. package/src/client/Reader.tsx +878 -0
  292. package/src/client/ReasoningCard.tsx +306 -0
  293. package/src/client/SettingsSection.module.css +184 -0
  294. package/src/client/SettingsSection.tsx +194 -0
  295. package/src/client/StickyLane.tsx +34 -0
  296. package/src/client/TimelineRail.module.css +170 -0
  297. package/src/client/TimelineRail.tsx +262 -0
  298. package/src/client/ToolActivity.tsx +274 -0
  299. package/src/client/TurnMetrics.module.css +139 -0
  300. package/src/client/TurnMetrics.tsx +160 -0
  301. package/src/client/WaitClock.tsx +33 -0
  302. package/src/client/WaitingStatus.tsx +13 -0
  303. package/src/client/conversation-scroll.ts +28 -0
  304. package/src/client/deliverables.ts +122 -0
  305. package/src/client/entry-policy.ts +20 -0
  306. package/src/client/entry.tsx +40 -0
  307. package/src/client/fold-choreography.ts +55 -0
  308. package/src/client/fold-intensity.ts +36 -0
  309. package/src/client/index.tsx +222 -0
  310. package/src/client/live-turn.ts +198 -0
  311. package/src/client/markdown/MarkdownText.module.css +304 -0
  312. package/src/client/markdown/MarkdownText.tsx +195 -0
  313. package/src/client/markdown/cjkFriendlyStrong.ts +84 -0
  314. package/src/client/markdown/incremental.ts +131 -0
  315. package/src/client/markdown/katex.tsx +91 -0
  316. package/src/client/markdown/mathCompatibility.ts +350 -0
  317. package/src/client/markdown/parse.ts +45 -0
  318. package/src/client/markdown/render.tsx +647 -0
  319. package/src/client/markdown/upstream.json +14 -0
  320. package/src/client/mcp-app.ts +323 -0
  321. package/src/client/message-chrome.ts +47 -0
  322. package/src/client/motion.tsx +410 -0
  323. package/src/client/native/ContextBody.module.css +161 -0
  324. package/src/client/native/ContextBody.tsx +591 -0
  325. package/src/client/native/ContextInjectionRow.module.css +66 -0
  326. package/src/client/native/ContextInjectionRow.tsx +106 -0
  327. package/src/client/native/ReferenceIcon.tsx +35 -0
  328. package/src/client/native/tool-call-model.ts +248 -0
  329. package/src/client/native/tool-update.ts +32 -0
  330. package/src/client/native/upstream.json +41 -0
  331. package/src/client/official-slots.tsx +211 -0
  332. package/src/client/open-file.ts +94 -0
  333. package/src/client/pending-submission.ts +49 -0
  334. package/src/client/platform-media.ts +17 -0
  335. package/src/client/primitive-labels.ts +54 -0
  336. package/src/client/projection.ts +118 -0
  337. package/src/client/reasoning-follow.ts +28 -0
  338. package/src/client/settings-copy.ts +80 -0
  339. package/src/client/settings-slots.ts +20 -0
  340. package/src/client/settings.tsx +73 -0
  341. package/src/client/skill-status.ts +78 -0
  342. package/src/client/store.ts +75 -0
  343. package/src/client/stream-buffer.ts +136 -0
  344. package/src/client/streaming.tsx +70 -0
  345. package/src/client/style.d.ts +4 -0
  346. package/src/client/timeline.ts +77 -0
  347. package/src/client/tool-activity.ts +298 -0
  348. package/src/client/types.ts +89 -0
  349. package/src/client/waiting-clock.ts +83 -0
  350. package/src/client/word-motion.tsx +92 -0
  351. package/src/client/word-timeline.ts +113 -0
  352. package/src/dsh-better-display.ts +108 -0
  353. package/src/skill-roots.ts +138 -0
  354. package/src/skill-status.ts +81 -0
  355. package/tests/browser-auto-fold.tsx +64 -0
  356. package/tests/browser-closed-gap.tsx +20 -0
  357. package/tests/browser-motion.tsx +65 -0
  358. package/tests/browser-official-bridge.tsx +152 -0
  359. package/tests/browser-sticky-lanes.tsx +22 -0
  360. package/tests/browser-waiting-clock.tsx +14 -0
  361. package/tests/chat-flow-hook.test.ts +66 -0
  362. package/tests/cloud-gate.test.ts +34 -0
  363. package/tests/context-tool-update.test.ts +33 -0
  364. package/tests/conversation-scroll.test.ts +47 -0
  365. package/tests/deliverables.test.ts +128 -0
  366. package/tests/entry.test.ts +42 -0
  367. package/tests/fixtures/maid-composer-hide.html +31 -0
  368. package/tests/fold-choreography.test.ts +54 -0
  369. package/tests/fold-intensity.test.ts +94 -0
  370. package/tests/footer-precedence.test.ts +129 -0
  371. package/tests/history-word-regression.test.ts +16 -0
  372. package/tests/live-turn.test.ts +218 -0
  373. package/tests/mcp-apps.test.ts +157 -0
  374. package/tests/message-chrome.test.ts +29 -0
  375. package/tests/official-slots.test.ts +112 -0
  376. package/tests/open-file.test.ts +114 -0
  377. package/tests/pending-submission.test.ts +45 -0
  378. package/tests/platform-media.test.ts +23 -0
  379. package/tests/projection.test.ts +184 -0
  380. package/tests/reasoning-follow.test.ts +34 -0
  381. package/tests/settings-copy.test.ts +63 -0
  382. package/tests/skill-status.test.ts +137 -0
  383. package/tests/stock-install.test.ts +58 -0
  384. package/tests/stream-buffer.test.ts +102 -0
  385. package/tests/timeline.test.ts +59 -0
  386. package/tests/tool-diff.test.ts +57 -0
  387. package/tests/waiting-clock.test.ts +83 -0
  388. package/tests/word-timeline.test.ts +146 -0
  389. package/tsconfig.json +11 -0
  390. package/tsdown.config.ts +51 -0
@@ -0,0 +1,410 @@
1
+ import { useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import type { ReactNode, RefObject } from 'react';
3
+ import css from './Reader.module.css';
4
+ import { StreamMotionContext } from './streaming.js';
5
+
6
+ const EASING = 'cubic-bezier(.22,1,.36,1)';
7
+
8
+ export function useMotionAllowed(enabled: boolean): boolean {
9
+ const [reduced, setReduced] = useState(() => window.matchMedia('(prefers-reduced-motion: reduce)').matches);
10
+ useEffect(() => {
11
+ const query = window.matchMedia('(prefers-reduced-motion: reduce)');
12
+ const change = () => setReduced(query.matches);
13
+ query.addEventListener('change', change);
14
+ return () => query.removeEventListener('change', change);
15
+ }, []);
16
+ return enabled && !reduced;
17
+ }
18
+
19
+ export function usePinnedSelection(root: RefObject<HTMLElement>, selector = '[data-reader-answer], [data-reader-process]'): readonly string[] {
20
+ const [keys, setKeys] = useState<readonly string[]>([]);
21
+ useEffect(() => {
22
+ const update = () => {
23
+ const selection = document.getSelection();
24
+ const range = selection && !selection.isCollapsed && selection.rangeCount ? selection.getRangeAt(0) : null;
25
+ const next = range && root.current
26
+ ? [...new Set(Array.from(root.current.querySelectorAll<HTMLElement>(selector))
27
+ .filter(element => range.intersectsNode(element))
28
+ .map(element => element.dataset.readerKey ?? element.dataset.readerProcessKey!).filter(Boolean))]
29
+ : [];
30
+ setKeys(previous => previous.length === next.length && previous.every((key, index) => key === next[index]) ? previous : next);
31
+ };
32
+ document.addEventListener('selectionchange', update);
33
+ return () => document.removeEventListener('selectionchange', update);
34
+ }, [root, selector]);
35
+ return keys;
36
+ }
37
+
38
+ export function StatusText({ text, motion, shimmer = false }: { text: string; motion: boolean; shimmer?: boolean }) {
39
+ const ref = useRef<HTMLSpanElement>(null);
40
+ const [visible, setVisible] = useState(() => !document.hidden);
41
+ const [forcedColors, setForcedColors] = useState(() => window.matchMedia('(forced-colors: active)').matches);
42
+ const allowed = motion && visible && !forcedColors;
43
+ const [frame, setFrame] = useState<{
44
+ text: string; id: number; phase: 'idle' | 'start' | 'running';
45
+ outgoing: { text: string; id: number } | null;
46
+ }>({ text, id: 0, phase: 'idle', outgoing: null });
47
+ // Adjust before commit, so a new label cannot paint once before its entry state.
48
+ // Reuse the previous incoming key: a rapid change exits from its current pose.
49
+ if (frame.text !== text) setFrame({
50
+ text, id: frame.id + 1, phase: allowed ? 'start' : 'idle',
51
+ outgoing: allowed ? { text: frame.text, id: frame.id } : null,
52
+ });
53
+ useEffect(() => {
54
+ const update = () => setVisible(!document.hidden);
55
+ const query = window.matchMedia('(forced-colors: active)');
56
+ const colors = () => setForcedColors(query.matches);
57
+ document.addEventListener('visibilitychange', update);
58
+ query.addEventListener('change', colors);
59
+ return () => { document.removeEventListener('visibilitychange', update); query.removeEventListener('change', colors); };
60
+ }, []);
61
+ useLayoutEffect(() => {
62
+ const id = frame.id;
63
+ const settle = () => setFrame(current => current.id === id && current.outgoing
64
+ ? { ...current, phase: 'idle', outgoing: null } : current);
65
+ if (!allowed) { settle(); return; }
66
+ if (!frame.outgoing || !ref.current) return;
67
+ // Commit the supplied .is-enter-start pose before releasing CSS transitions.
68
+ ref.current.getBoundingClientRect();
69
+ let timer = 0;
70
+ const tick = requestAnimationFrame(() => {
71
+ setFrame(current => current.id === id ? { ...current, phase: 'running' } : current);
72
+ timer = window.setTimeout(settle, 200); // 150ms swap + 50ms incoming gap.
73
+ });
74
+ return () => { cancelAnimationFrame(tick); window.clearTimeout(timer); };
75
+ }, [frame.id, allowed]);
76
+ const active = shimmer && allowed;
77
+ const swapping = allowed && frame.outgoing !== null;
78
+ return <span className={css.statusText} data-reader-status data-reader-busy={shimmer} data-ud-check="reader-status">
79
+ <span className={css.think} aria-hidden="true" data-active={active} data-reader-status-phase={swapping ? frame.phase : 'idle'} data-ud-motion="reader-thinking-state">
80
+ <span className={css.thinkSizer}>{text}</span>
81
+ {swapping && <span key={frame.outgoing!.id} className={`${css.thinkText} ${frame.phase === 'running' ? css.isExit : ''}`}
82
+ data-reader-status-copy="outgoing" data-text={frame.outgoing!.text}>{frame.outgoing!.text}</span>}
83
+ <span key={frame.id} ref={ref} className={`${css.thinkText} ${swapping && frame.phase === 'start' ? css.isEnterStart : ''}`}
84
+ data-reader-status-copy="current" data-reader-shimmer={active || undefined} data-text={text}>{text}</span>
85
+ </span>
86
+ <span className={css.srOnly} role="status" aria-live="polite" aria-atomic="true">{text}</span>
87
+ </span>;
88
+ }
89
+
90
+ export function Disclosure({ open, onChange, label, controls, buttonRef, ariaLabel }: {
91
+ open: boolean; onChange: (value: boolean) => void; label: ReactNode;
92
+ controls?: string; buttonRef: RefObject<HTMLButtonElement>;
93
+ ariaLabel?: string;
94
+ }) {
95
+ const name = ariaLabel ?? '思考与过程';
96
+ return <div className={css.disclosure} data-reader-disclosure data-expanded={open}>
97
+ <button ref={buttonRef} type="button" className={css.disclosureButton} aria-label={`${open ? '收起' : '展开'}${name}`} aria-expanded={open} {...(controls ? { 'aria-controls': controls } : {})} onClick={() => onChange(!open)}>
98
+ {label}
99
+ <svg className={css.chevron} data-open={open} viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"><path d="m6 4 4 4-4 4" /></svg>
100
+ </button>
101
+ </div>;
102
+ }
103
+
104
+ /** Supplemental details stay in source order beside their own narration. */
105
+ export function ProcessFragment({ open, motion, onRead, returnFocusTo, nodeKey, children, framed = false }: {
106
+ open: boolean; motion: boolean; onRead: () => void; nodeKey: string;
107
+ returnFocusTo: RefObject<HTMLElement>; children: ReactNode; framed?: boolean;
108
+ }) {
109
+ const body = useRef<HTMLDivElement>(null);
110
+ const running = useRef<Animation | null>(null);
111
+ const previous = useRef(open);
112
+ const [present, setPresent] = useState(open);
113
+ useLayoutEffect(() => {
114
+ const element = body.current;
115
+ if (!element) return;
116
+ const from = running.current ? element.getBoundingClientRect().height : previous.current ? element.scrollHeight : 0;
117
+ running.current?.cancel();
118
+ running.current = null;
119
+ const changed = previous.current !== open;
120
+ previous.current = open;
121
+ if (open) setPresent(true);
122
+ if (!open && element.contains(document.activeElement)) returnFocusTo.current?.focus();
123
+ element.style.height = open ? 'auto' : '0px';
124
+ const target = open ? element.scrollHeight : 0;
125
+ if (!motion || !changed || Math.abs(from - target) < 1) {
126
+ setPresent(open);
127
+ return;
128
+ }
129
+ const animation = element.animate(
130
+ from > target
131
+ ? [
132
+ { offset: 0, height: `${from}px`, opacity: 1, transform: 'scaleY(1) translateY(0)' },
133
+ { offset: 0.32, height: `${Math.round(from * 0.92)}px`, opacity: 0.35, transform: 'scaleY(0.96) translateY(-2px)' },
134
+ { offset: 1, height: `${target}px`, opacity: 0, transform: 'scaleY(0.68) translateY(-8px)' },
135
+ ]
136
+ : [
137
+ { offset: 0, height: `${from}px`, opacity: 0, transform: 'scaleY(0.8) translateY(6px)' },
138
+ { offset: 0.3, height: `${Math.round(target * 0.4)}px`, opacity: 0.4, transform: 'scaleY(0.92) translateY(3px)' },
139
+ { offset: 1, height: `${target}px`, opacity: 1, transform: 'scaleY(1) translateY(0)' },
140
+ ],
141
+ { duration: 380, easing: 'cubic-bezier(0.16, 1, 0.3, 1)', fill: 'both' },
142
+ );
143
+ running.current = animation;
144
+ let settled = false;
145
+ const settle = () => {
146
+ if (settled) return;
147
+ settled = true;
148
+ if (running.current === animation) running.current = null;
149
+ animation.cancel();
150
+ setPresent(open);
151
+ };
152
+ animation.onfinish = settle;
153
+ // `fill: 'both'\` pins the opening keyframe (height 0, opacity 0), so while this
154
+ // animation runs the body is *invisible* no matter what the element's style says.
155
+ // An animation that never finishes — cancelled by a re-run, an unmount, or a
156
+ // compositor that drops it — would then leave the row looking like it refused to
157
+ // open, which is exactly the reported symptom. Cancel is only safe once the
158
+ // state change has been committed, so the deadline does both.
159
+ const deadline = window.setTimeout(settle, 380 + 240);
160
+ return () => {
161
+ window.clearTimeout(deadline);
162
+ running.current?.cancel();
163
+ };
164
+ }, [open, motion, returnFocusTo]);
165
+ if (!open && !present) return null;
166
+ return <div ref={body} className={css.disclosureBody} data-reader-process data-reader-process-key={nodeKey} data-ud-motion="reader-process-size"
167
+ aria-hidden={!open} onPointerDown={() => { if (open) onRead(); }} onFocusCapture={() => { if (open) onRead(); }} {...(!open ? { inert: '' } : {})}>
168
+ <div className={framed ? css.processFrame : css.processContents}>{children}</div>
169
+ </div>;
170
+ }
171
+
172
+ /** Retire only narration that was actually visible; historical rows stay folded. */
173
+ export function RetiringContent({ visible, children }: { visible: boolean; children: ReactNode }) {
174
+ const { enabled } = useContext(StreamMotionContext);
175
+ const root = useRef<HTMLDivElement>(null);
176
+ const animation = useRef<Animation | null>(null);
177
+ const [present, setPresent] = useState(visible);
178
+ const [focusHeld, setFocusHeld] = useState(false);
179
+ useLayoutEffect(() => {
180
+ const element = root.current;
181
+ if (visible) {
182
+ animation.current?.cancel(); animation.current = null;
183
+ setPresent(true);
184
+ return;
185
+ }
186
+ if (!element) return;
187
+ if (element.contains(document.activeElement)) { setFocusHeld(true); return; }
188
+ if (focusHeld) return;
189
+ const from = element.getBoundingClientRect().height;
190
+ animation.current?.cancel(); animation.current = null;
191
+ if (!enabled || from < 1) { setPresent(false); return; }
192
+ const next = element.animate([{ height: `${from}px`, opacity: 1 }, { height: '0px', opacity: 0 }], { duration: 220, easing: EASING, fill: 'both' });
193
+ animation.current = next;
194
+ next.onfinish = () => { if (animation.current === next) { animation.current = null; next.cancel(); setPresent(false); } };
195
+ }, [visible, enabled, focusHeld]);
196
+ useEffect(() => () => animation.current?.cancel(), []);
197
+ if (!visible && !present) return null;
198
+ return <div ref={root} className={css.retiringContent} data-reader-retiring={visible ? 'visible' : 'retiring'} data-ud-motion="reader-progress-retire"
199
+ onBlur={event => { if (!event.currentTarget.contains(event.relatedTarget)) setFocusHeld(false); }}>
200
+ {children}
201
+ </div>;
202
+ }
203
+
204
+ // DOM-only behavior: the native Session remains the sole source of business data.
205
+ export function useReadingScroll(root: RefObject<HTMLElement>, motion: boolean): {
206
+ detached: boolean;
207
+ jump: () => void;
208
+ /** Stop tail-follow so a rail landing is not pulled back to the live bottom. */
209
+ release: () => void;
210
+ } {
211
+ const port = useRef<HTMLElement | null>(null);
212
+ const following = useRef(true);
213
+ // Tracked separately from `following`: only an explicit reader action clears
214
+ // this, so a fold-induced scroll clamp cannot permanently drop the tail.
215
+ const pinned = useRef(true);
216
+ const anchor = useRef<{ element: HTMLElement; docTop: number } | null>(null);
217
+ const cancelFollow = useRef<() => void>(() => {});
218
+ const [detached, setDetached] = useState(false);
219
+ useLayoutEffect(() => {
220
+ const content = root.current;
221
+ if (!content) return;
222
+ const scroll = content.closest<HTMLElement>('[data-conversation-scroll]') ?? content;
223
+ port.current = scroll;
224
+ // Browser scroll anchoring moves the viewport on its own as the transcript
225
+ // grows, which reads as the page freezing mid-follow.
226
+ const previousOverflowAnchor = scroll.style.overflowAnchor;
227
+ scroll.style.overflowAnchor = 'none';
228
+ let followFrame = 0;
229
+ let lastFrameAt = 0;
230
+ let layoutDepth = 0;
231
+ let lastWrittenTop: number | null = null;
232
+ const layoutStart = () => {
233
+ layoutDepth++;
234
+ cancelAnimationFrame(followFrame); followFrame = 0;
235
+ capture();
236
+ };
237
+ const layoutEnd = () => {
238
+ layoutDepth = Math.max(0, layoutDepth - 1);
239
+ if (!layoutDepth && following.current && !followFrame) {
240
+ lastFrameAt = performance.now(); followFrame = requestAnimationFrame(follow);
241
+ }
242
+ };
243
+ cancelFollow.current = () => {
244
+ cancelAnimationFrame(followFrame);
245
+ followFrame = 0;
246
+ lastWrittenTop = null;
247
+ };
248
+ const selected = () => {
249
+ const selection = document.getSelection();
250
+ return selection && !selection.isCollapsed && selection.anchorNode && content.contains(selection.anchorNode);
251
+ };
252
+ // Only a focused text field suspends tail-follow. Clicking a fold control
253
+ // inside the reader also moves document.activeElement into `content`, and
254
+ // treating that as "user is busy" froze following for the rest of the turn.
255
+ const editingText = () => {
256
+ const active = document.activeElement;
257
+ if (!(active instanceof HTMLElement)) return false;
258
+ // The composer lives outside this subtree: focusing it must never stop
259
+ // the transcript from following.
260
+ if (!content.contains(active)) return false;
261
+ return active.isContentEditable || active.tagName === 'TEXTAREA' || active.tagName === 'INPUT';
262
+ };
263
+ const capture = () => {
264
+ const top = scroll.getBoundingClientRect().top;
265
+ const candidates = content.querySelectorAll<HTMLElement>('[data-reader-anchor]');
266
+ let candidate: HTMLElement | null = null;
267
+ for (let index = 0; index < candidates.length; index++) {
268
+ const element = candidates[index];
269
+ if (element.getBoundingClientRect().bottom > top + 8) { candidate = element; break; }
270
+ }
271
+ // Record the anchor in DOCUMENT space, never viewport space. The viewport
272
+ // offset also changes whenever the reader scrolls, so comparing it back
273
+ // turned a plain scroll into an "anchor correction" that yanked the
274
+ // viewport back to wherever the anchor had last been captured.
275
+ anchor.current = candidate
276
+ ? { element: candidate, docTop: candidate.getBoundingClientRect().top + scroll.scrollTop }
277
+ : null;
278
+ };
279
+ // Anchor capture is only consumed while detached; coalesce DOM scans to one per frame.
280
+ let captureFrame = 0;
281
+ const scheduleCapture = () => {
282
+ if (captureFrame) return;
283
+ captureFrame = requestAnimationFrame(() => { captureFrame = 0; capture(); });
284
+ };
285
+ const atBottom = () => scroll.scrollHeight - scroll.scrollTop - scroll.clientHeight < 25;
286
+ // Intent to stay pinned to the bottom, tracked independently of the scroll
287
+ // position. A fold collapse shrinks the transcript and the browser clamps
288
+ // scrollTop, producing a scroll event indistinguishable from the reader
289
+ // scrolling up; without this the reader silently detached exactly when the
290
+ // turn's last content arrived, and the page stopped following.
291
+ pinned.current = true;
292
+ const onScroll = () => {
293
+ // While a follow animation is actively driving scroll, do not cancel following midway.
294
+ if (followFrame !== 0 || layoutDepth > 0) return;
295
+ // Our easing frames must not be mistaken for a user leaving the bottom.
296
+ if (lastWrittenTop !== null && Math.abs(scroll.scrollTop - lastWrittenTop) < 1) return;
297
+ // Bind the follow intent to the position observed HERE. Every scroll that
298
+ // reaches this point is the viewport moving under someone else's hand: a
299
+ // wheel, a drag, a key, or the browser clamping the offset after a shrink.
300
+ //
301
+ // The previous form inferred detaching from a strictly upward delta against
302
+ // lastWrittenTop, which left two holes. That baseline is nulled by
303
+ // cancelFollow/onWheel/onTouch/onKey, and the guard above already returned
304
+ // for any scroll whose delta is under 1px — so a reader parked mid-transcript
305
+ // by a scrollbar drag stayed following === true AND pinned === true. The next
306
+ // content growth then took the re-attach branch and yanked the viewport to
307
+ // the bottom, discarding what they were reading. Reading the position
308
+ // directly needs no baseline: a fold's clamp lands at the very bottom and
309
+ // still keeps following, while any other offset detaches.
310
+ const bottom = atBottom();
311
+ pinned.current = bottom;
312
+ if (!bottom) { cancelAnimationFrame(followFrame); followFrame = 0; scheduleCapture(); }
313
+ if (!following.current) {
314
+ // Only an explicit return to the bottom resumes following.
315
+ if (bottom) { following.current = true; setDetached(false); }
316
+ return;
317
+ }
318
+ if (!bottom) { following.current = false; setDetached(true); }
319
+ };
320
+ const onWheel = (event: WheelEvent) => {
321
+ cancelAnimationFrame(followFrame); followFrame = 0; lastWrittenTop = null;
322
+ if (event.deltaY < 0) { following.current = false; setDetached(true); pinned.current = false; scheduleCapture(); }
323
+ };
324
+ const onTouch = () => {
325
+ cancelAnimationFrame(followFrame); followFrame = 0; lastWrittenTop = null;
326
+ };
327
+ const onKey = (event: KeyboardEvent) => {
328
+ if (event.target instanceof HTMLElement && event.target.closest('textarea,input,[contenteditable=true]')) return;
329
+ if (['PageUp', 'Home', 'ArrowUp'].includes(event.key)) {
330
+ cancelAnimationFrame(followFrame); followFrame = 0; lastWrittenTop = null;
331
+ following.current = false; setDetached(true); pinned.current = false; scheduleCapture();
332
+ }
333
+ };
334
+ const writeTop = (top: number) => { scroll.scrollTop = top; lastWrittenTop = scroll.scrollTop; };
335
+ const follow = (now: number) => {
336
+ followFrame = 0;
337
+ if (!following.current || selected() || editingText()) return;
338
+ if (layoutDepth > 0) return;
339
+ const gap = scroll.scrollHeight - scroll.clientHeight - scroll.scrollTop;
340
+ const delta = Math.min(48, Math.max(1, now - lastFrameAt));
341
+ lastFrameAt = now;
342
+ if (!motion || Math.abs(gap) < 1.5) { writeTop(scroll.scrollHeight); capture(); return; }
343
+ writeTop(scroll.scrollTop + gap * (1 - Math.exp(-delta / 52)));
344
+ followFrame = requestAnimationFrame(follow);
345
+ };
346
+ const firstFrame = requestAnimationFrame(() => {
347
+ if (following.current && !selected()) writeTop(scroll.scrollHeight);
348
+ capture();
349
+ });
350
+ let lastHeight = content.scrollHeight;
351
+ const observer = new ResizeObserver(() => {
352
+ if (selected()) return;
353
+ const height = scroll.scrollHeight;
354
+ const grew = height > lastHeight + 1;
355
+ lastHeight = height;
356
+ // Growth while the reader intends to be at the bottom re-attaches the
357
+ // tail. This is the self-healing path for the clamp case above and is
358
+ // also what keeps the viewport moving when a turn ends.
359
+ if (grew && pinned.current && !editingText()) { following.current = true; setDetached(false); }
360
+ if (following.current && !editingText()) {
361
+ // Height is already animated: no second, lagging scroll easing.
362
+ if (!motion || layoutDepth > 0) writeTop(scroll.scrollHeight);
363
+ else if (!followFrame) { lastFrameAt = performance.now(); followFrame = requestAnimationFrame(follow); }
364
+ } else if (!following.current && anchor.current?.element.isConnected) {
365
+ // Only a real layout change above the anchor needs compensation. A reader
366
+ // scroll moves the viewport offset but not the document offset, so this
367
+ // stays silent while the reader is scrolling and cannot snap back.
368
+ const delta = anchor.current.element.getBoundingClientRect().top + scroll.scrollTop - anchor.current.docTop;
369
+ if (Math.abs(delta) > .5) writeTop(scroll.scrollTop + delta);
370
+ }
371
+ capture();
372
+ });
373
+ observer.observe(content);
374
+ if (scroll !== content) observer.observe(scroll);
375
+ content.addEventListener('reader-layout-start', layoutStart);
376
+ content.addEventListener('reader-layout-end', layoutEnd);
377
+ scroll.addEventListener('scroll', onScroll, { passive: true });
378
+ scroll.addEventListener('wheel', onWheel, { passive: true });
379
+ scroll.addEventListener('touchstart', onTouch, { passive: true });
380
+ scroll.addEventListener('touchmove', onTouch, { passive: true });
381
+ scroll.addEventListener('keydown', onKey);
382
+ return () => {
383
+ cancelAnimationFrame(firstFrame); cancelAnimationFrame(followFrame); cancelAnimationFrame(captureFrame); observer.disconnect();
384
+ content.removeEventListener('reader-layout-start', layoutStart);
385
+ content.removeEventListener('reader-layout-end', layoutEnd);
386
+ scroll.style.overflowAnchor = previousOverflowAnchor;
387
+ scroll.removeEventListener('scroll', onScroll); scroll.removeEventListener('wheel', onWheel);
388
+ scroll.removeEventListener('touchstart', onTouch); scroll.removeEventListener('touchmove', onTouch);
389
+ scroll.removeEventListener('keydown', onKey);
390
+ };
391
+ }, [root, motion]);
392
+ const jump = useCallback(() => {
393
+ cancelFollow.current();
394
+ anchor.current = null;
395
+ following.current = true;
396
+ pinned.current = true;
397
+ setDetached(false);
398
+ if (port.current) {
399
+ port.current.scrollTop = port.current.scrollHeight;
400
+ }
401
+ }, []);
402
+ const release = useCallback(() => {
403
+ cancelFollow.current();
404
+ following.current = false;
405
+ pinned.current = false;
406
+ anchor.current = null;
407
+ setDetached(true);
408
+ }, []);
409
+ return { detached, jump, release };
410
+ }
@@ -0,0 +1,161 @@
1
+ /* Expanded context bodies: one code-block surface shared by every form, so the
2
+ disclosure keeps the Figma 10:2482 geometry whichever form renders inside. */
3
+
4
+ .text {
5
+ margin: 0;
6
+ color: var(--dsw-alias-label-secondary);
7
+ font: inherit;
8
+ white-space: pre-wrap;
9
+ overflow-wrap: anywhere;
10
+ }
11
+
12
+ /* Source fields beneath the text: dimmer than the content they describe. */
13
+ .fields {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 2px;
17
+ margin: 8px 0 0;
18
+ padding-top: 8px;
19
+ border-top: 1px solid var(--dsw-alias-line-secondary);
20
+ }
21
+
22
+ .field {
23
+ display: flex;
24
+ gap: 8px;
25
+ min-width: 0;
26
+ }
27
+
28
+ .fieldKey {
29
+ flex: none;
30
+ min-width: 96px;
31
+ color: var(--dsw-alias-label-caption);
32
+ }
33
+
34
+ .fieldValue {
35
+ flex: 1 1 auto;
36
+ min-width: 0;
37
+ margin: 0;
38
+ color: var(--dsw-alias-label-tertiary);
39
+ overflow-wrap: anywhere;
40
+ }
41
+
42
+ /* instructions: the reconciled files, above their text. */
43
+ .files {
44
+ display: flex;
45
+ flex-wrap: wrap;
46
+ gap: 4px 12px;
47
+ margin: 0 0 8px;
48
+ padding: 0;
49
+ list-style: none;
50
+ }
51
+
52
+ .file {
53
+ display: flex;
54
+ align-items: baseline;
55
+ gap: 6px;
56
+ min-width: 0;
57
+ }
58
+
59
+ .filePath {
60
+ color: var(--dsw-alias-label-secondary);
61
+ overflow-wrap: anywhere;
62
+ }
63
+
64
+ .fileAction {
65
+ color: var(--dsw-alias-label-caption);
66
+ }
67
+
68
+ /* catalog: a replacement notice above one row per published entry. */
69
+ .catalogNotice {
70
+ margin: 0 0 6px;
71
+ color: var(--dsw-alias-label-caption);
72
+ }
73
+
74
+
75
+ .entries {
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: 4px;
79
+ margin: 0;
80
+ padding: 0;
81
+ list-style: none;
82
+ }
83
+
84
+ .entry {
85
+ display: flex;
86
+ gap: 8px;
87
+ min-width: 0;
88
+ }
89
+
90
+ .entryName {
91
+ flex: none;
92
+ color: var(--dsw-alias-label-secondary);
93
+ }
94
+
95
+ .entryDescription {
96
+ flex: 1 1 auto;
97
+ min-width: 0;
98
+ overflow: hidden;
99
+ color: var(--dsw-alias-label-tertiary);
100
+ text-overflow: ellipsis;
101
+ white-space: nowrap;
102
+ }
103
+
104
+ /* snapshot: one titled block per contributing subsystem. */
105
+ .sections {
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 8px;
109
+ margin: 0;
110
+ }
111
+
112
+ .section {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 2px;
116
+ min-width: 0;
117
+ }
118
+
119
+ .sectionName {
120
+ color: var(--dsw-alias-label-caption);
121
+ }
122
+
123
+ .sectionText {
124
+ margin: 0;
125
+ color: var(--dsw-alias-label-secondary);
126
+ white-space: pre-wrap;
127
+ overflow-wrap: anywhere;
128
+ }
129
+
130
+ /* relay: who sent this, above what they said. */
131
+ .relaySender {
132
+ margin: 0 0 6px;
133
+ color: var(--dsw-alias-label-caption);
134
+ overflow-wrap: anywhere;
135
+ }
136
+
137
+ /* recall: one row per source session, with how much of it survived. */
138
+ .recalls {
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 2px;
142
+ margin: 0 0 8px;
143
+ padding: 0;
144
+ list-style: none;
145
+ }
146
+
147
+ .recall {
148
+ display: flex;
149
+ gap: 8px;
150
+ min-width: 0;
151
+ }
152
+
153
+ .recallLabel {
154
+ color: var(--dsw-alias-label-secondary);
155
+ overflow-wrap: anywhere;
156
+ }
157
+
158
+ .recallCounts {
159
+ flex: none;
160
+ color: var(--dsw-alias-label-caption);
161
+ }