@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,591 @@
1
+ // Expanded bodies for the context disclosure, one per durable context form.
2
+ // The producer declares the form; this module only chooses a presentation for
3
+ // it. Every form falls back to OpaqueBody, which is the documented default for
4
+ // an absent, unknown, or malformed form — a resumed or foreign log must render
5
+ // even when this UI version has never seen its producer.
6
+
7
+ import type { ReactNode } from 'react'
8
+ import type { ContextMessageNode, KnownContextForm } from '@deepseek-ai/dsh-client-ui-conversation/client'
9
+ import { JsonBlock } from '@deepseek-ai/dsh-client-ui-primitives'
10
+ import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
11
+ import css from './ContextBody.module.css'
12
+
13
+ /** Model-facing text stays bounded at the disclosure, not at the producer. */
14
+ const MAX_CHARS = 20_000
15
+
16
+ /** Rows a list body materializes before summarizing the remainder. */
17
+ const MAX_ENTRIES = 200
18
+
19
+ type Translate = TranslateNS<'chat'>
20
+
21
+ /** One durable source narrowed to the readable-record shape; null for anything else. */
22
+ function asRecord(value: unknown): Record<string, unknown> | null {
23
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
24
+ ? value as Record<string, unknown>
25
+ : null
26
+ }
27
+
28
+ /** One run of the model-facing content: adjacent text, or one unknown block. */
29
+ type ContentRun = { text: string } | { block: unknown }
30
+
31
+ /**
32
+ * The content blocks as runs, IN THE ORDER the model received them.
33
+ *
34
+ * Adjacent text blocks join with no separator, matching how provider adapters
35
+ * flatten them — inserting a line break would show the reader a line the model
36
+ * never saw. An unknown block breaks the run and keeps its own fallback rather
37
+ * than being hoisted past the text around it or vanishing; the block union is
38
+ * merge-extensible, so a foreign log may interleave shapes this build does not
39
+ * know.
40
+ */
41
+ function contentRuns(content: ContextMessageNode['content']): ContentRun[] {
42
+ const runs: ContentRun[] = []
43
+ for (const block of content) {
44
+ if (block.type !== 'text') {
45
+ runs.push({ block })
46
+ continue
47
+ }
48
+ const last = runs[runs.length - 1]
49
+ if (last !== undefined && 'text' in last) last.text += block.text
50
+ else runs.push({ text: block.text })
51
+ }
52
+ return runs
53
+ }
54
+
55
+ /** Only the blocks this UI version does not know, for bodies that replace the text. */
56
+ function unknownBlocks(content: ContextMessageNode['content']): unknown[] {
57
+ return contentRuns(content).flatMap(run => 'block' in run ? [run.block] : [])
58
+ }
59
+
60
+ /** The model-facing text, truncated to the display bound. */
61
+ function boundedText(text: string, t: Translate): string {
62
+ return text.length > MAX_CHARS
63
+ ? `${text.slice(0, MAX_CHARS)}\n${t('json.truncated', { total: text.length })}`
64
+ : text
65
+ }
66
+
67
+ /**
68
+ * One source field rendered as a value row; nested shapes stay compact JSON.
69
+ * Bounded on its own, because source fields are as unbounded as the text: an unknown
70
+ * producer may record an arbitrarily large string or array.
71
+ */
72
+ function fieldValue(value: unknown, t: Translate): string {
73
+ const text = typeof value === 'string'
74
+ ? value
75
+ : typeof value === 'number' || typeof value === 'boolean' ? String(value) : JSON.stringify(value)
76
+ return boundedText(text, t)
77
+ }
78
+
79
+ /**
80
+ * Source fields as a key/value list. `kind` is always omitted because the
81
+ * row header already names the producer. `form` is omitted only when a
82
+ * dedicated body rendered for it — then the presentation the reader is looking
83
+ * at IS that value. On the opaque fallback the declaration is kept, because
84
+ * that is the one place a form this version cannot present would otherwise
85
+ * disappear from the UI entirely.
86
+ */
87
+ function SourceFields({ source, formRendered, t }: {
88
+ source: unknown
89
+ formRendered: boolean
90
+ t: Translate
91
+ }): ReactNode {
92
+ const record = asRecord(source)
93
+ if (record === null) return null
94
+ const hidden = formRendered ? ['kind', 'form'] : ['kind']
95
+ const rows = Object.entries(record).filter(([key]) => !hidden.includes(key))
96
+ if (rows.length === 0) return null
97
+ return (
98
+ <dl className={css.fields} data-context-fields>
99
+ {rows.map(([key, value]) => (
100
+ <div key={key} className={css.field}>
101
+ <dt className={css.fieldKey}>{key}</dt>
102
+ <dd className={css.fieldValue}>{fieldValue(value, t)}</dd>
103
+ </div>
104
+ ))}
105
+ </dl>
106
+ )
107
+ }
108
+
109
+ /**
110
+ * Content blocks this UI version does not know, kept visible rather than
111
+ * dropped: the block union is merge-extensible, so a newer or foreign log may
112
+ * carry a shape this build has no presentation for.
113
+ * @param props - The unrecognized blocks and the locale seat.
114
+ * @returns One generic JSON block per unknown entry.
115
+ */
116
+ function UnknownBlocks({ blocks, t }: { blocks: readonly unknown[]; t: Translate }): ReactNode {
117
+ return (
118
+ <>
119
+ {blocks.map((block, index) => (
120
+ <JsonBlock
121
+ key={index}
122
+ label={t('message.unknownBlock')}
123
+ payload={block}
124
+ truncatedLabel={total => t('json.truncated', { total })}
125
+ />
126
+ ))}
127
+ </>
128
+ )
129
+ }
130
+
131
+ /**
132
+ * The model-facing content of one context, shared by every form that shows it:
133
+ * the text with its real line breaks, then any block this UI version does not
134
+ * know, which keeps its own fallback rather than vanishing.
135
+ * @param props - Durable content and the locale seat.
136
+ * @returns The content blocks as the model received them.
137
+ */
138
+ function ModelFacingContent({ content, t }: {
139
+ content: ContextMessageNode['content']
140
+ t: Translate
141
+ }): ReactNode {
142
+ return (
143
+ <>
144
+ {contentRuns(content).map((run, index) => ('text' in run
145
+ ? run.text !== '' && (
146
+ <pre key={index} className={css.text} data-context-text>{boundedText(run.text, t)}</pre>
147
+ )
148
+ : (
149
+ <JsonBlock
150
+ key={index}
151
+ label={t('message.unknownBlock')}
152
+ payload={run.block}
153
+ truncatedLabel={total => t('json.truncated', { total })}
154
+ />
155
+ )))}
156
+ </>
157
+ )
158
+ }
159
+
160
+ /**
161
+ * Default presentation: the model-facing text as text, with its real line
162
+ * breaks, and the remaining source fields beneath it. This is what every form
163
+ * this UI version does not recognize renders as.
164
+ * @param props - Durable content, its source, and the locale seat.
165
+ * @returns The opaque context body.
166
+ */
167
+ export function OpaqueBody({ content, source, t }: {
168
+ content: ContextMessageNode['content']
169
+ source: unknown
170
+ t: Translate
171
+ }): ReactNode {
172
+ return (
173
+ <>
174
+ <ModelFacingContent content={content} t={t} />
175
+ <SourceFields source={source} formRendered={false} t={t} />
176
+ </>
177
+ )
178
+ }
179
+
180
+ /** One reconciled instruction file, as the durable source records it. */
181
+ interface InstructionChange {
182
+ action: 'set' | 'replace' | 'remove'
183
+ path: string
184
+ digest?: string
185
+ }
186
+
187
+ /**
188
+ * Instruction changes read off the source, or null when the record is not a
189
+ * usable instruction list.
190
+ *
191
+ * The read is all-or-nothing: silently dropping one unreadable entry would show
192
+ * a confident, incomplete file list for a log this version cannot fully read.
193
+ * Paths are deduplicated in first-seen order, matching how the header label is
194
+ * derived from the same array.
195
+ */
196
+ function instructionChanges(source: unknown): InstructionChange[] | null {
197
+ const record = asRecord(source)
198
+ const list = record === null ? undefined : record['changes']
199
+ if (!Array.isArray(list)) return null
200
+ const changes: InstructionChange[] = []
201
+ const seen = new Set<string>()
202
+ for (const entry of list as readonly unknown[]) {
203
+ const change = asRecord(entry)
204
+ if (change === null) return null
205
+ const path = change['path']
206
+ if (typeof path !== 'string' || path === '') return null
207
+ const action = change['action']
208
+ // The action decides which word the row shows, so an unrecognized one is
209
+ // not a readable change — it would be presented as loaded or updated.
210
+ if (action !== 'set' && action !== 'replace' && action !== 'remove') return null
211
+ const digest = change['digest']
212
+ if (seen.has(path)) continue
213
+ seen.add(path)
214
+ changes.push({ action, path, ...typeof digest === 'string' ? { digest } : {} })
215
+ }
216
+ return changes.length === 0 ? null : changes
217
+ }
218
+
219
+ /**
220
+ * Locale key for one reconciled file. The baseline loads a file; a later delta
221
+ * distinguishes a newly reconciled path from a rewritten one, which `set` and
222
+ * `replace` already separate at the producer.
223
+ * @param action - the durable change action.
224
+ * @param baseline - whether this context is the startup/resume baseline.
225
+ * @returns the key naming what happened to that file.
226
+ */
227
+ function instructionAction(
228
+ action: InstructionChange['action'],
229
+ baseline: boolean,
230
+ ): 'message.context.instructions.removed' | 'message.context.instructions.loaded'
231
+ | 'message.context.instructions.added' | 'message.context.instructions.updated' {
232
+ if (action === 'remove') return 'message.context.instructions.removed'
233
+ if (baseline) return 'message.context.instructions.loaded'
234
+ return action === 'set' ? 'message.context.instructions.added' : 'message.context.instructions.updated'
235
+ }
236
+
237
+ /**
238
+ * `instructions` form: the files this context reconciled, then their text.
239
+ *
240
+ * The text keeps its `<system-reminder>` framing verbatim — the framing is part
241
+ * of what the model read, so hiding it would misreport the request.
242
+ * @param props - Durable content, its source, and the locale seat.
243
+ * @returns The instructions context body, or the opaque body when the change
244
+ * list is unreadable.
245
+ */
246
+ export function InstructionsBody({ content, source, t }: {
247
+ content: ContextMessageNode['content']
248
+ source: unknown
249
+ t: Translate
250
+ }): ReactNode {
251
+ const changes = instructionChanges(source)
252
+ if (changes === null) return <OpaqueBody content={content} source={source} t={t} />
253
+ const baseline = asRecord(source)?.['baseline'] === true
254
+ return (
255
+ <>
256
+ <ul className={css.files} data-context-files>
257
+ {changes.map(change => (
258
+ <li key={change.path} className={css.file} title={change.digest}>
259
+ <span className={css.filePath}>{change.path}</span>
260
+ <span className={css.fileAction}>
261
+ {t(instructionAction(change.action, baseline))}
262
+ </span>
263
+ </li>
264
+ ))}
265
+ </ul>
266
+ <ModelFacingContent content={content} t={t} />
267
+ </>
268
+ )
269
+ }
270
+
271
+ /** One catalog entry, as the durable source records it. */
272
+ interface CatalogEntry {
273
+ name: string
274
+ description: string
275
+ }
276
+
277
+ /**
278
+ * Catalog entries read off the source, or null when the record is not a usable
279
+ * catalog. All-or-nothing for the same reason as the instruction list: this body
280
+ * replaces the model-facing text, so a partial list would hide the only complete
281
+ * account of what the model read.
282
+ */
283
+ function catalogEntries(source: unknown): CatalogEntry[] | null {
284
+ const record = asRecord(source)
285
+ const list = record === null ? undefined : record['entries']
286
+ if (!Array.isArray(list)) return null
287
+ const entries: CatalogEntry[] = []
288
+ for (const item of list as readonly unknown[]) {
289
+ const entry = asRecord(item)
290
+ if (entry === null) return null
291
+ const name = entry['name']
292
+ const description = entry['description']
293
+ if (typeof name !== 'string' || name === '' || typeof description !== 'string') return null
294
+ entries.push({ name, description })
295
+ }
296
+ // An empty list is a real catalog: a replacement with no entries retires
297
+ // every earlier name. Only an unreadable shape falls back.
298
+ return entries
299
+ }
300
+
301
+ /**
302
+ * `catalog` form: the published entries as a list, read from the source rather
303
+ * than re-parsed out of the model-facing prose.
304
+ *
305
+ * A catalog whose source carries no usable entries falls through to the opaque
306
+ * body, so an older or hand-edited log still shows its text.
307
+ * @param props - Durable content, its source, and the locale seat.
308
+ * @returns The catalog context body, or the opaque body when the entry list is
309
+ * unreadable.
310
+ */
311
+ export function CatalogBody({ content, source, t }: {
312
+ content: ContextMessageNode['content']
313
+ source: unknown
314
+ t: Translate
315
+ }): ReactNode {
316
+ const entries = catalogEntries(source)
317
+ if (entries === null) return <OpaqueBody content={content} source={source} t={t} />
318
+ const update = asRecord(source)?.['update'] === true
319
+ // Entry count is unbounded (a provider may publish any number of skills), and
320
+ // the scrollport bounds height, not node count — so the list bounds itself.
321
+ const shown = entries.slice(0, MAX_ENTRIES)
322
+ const rest = unknownBlocks(content)
323
+ return (
324
+ <>
325
+ {update && <p className={css.catalogNotice} data-context-catalog-update>{t('message.context.catalog.replaced')}</p>}
326
+ <ul className={css.entries} data-context-entries>
327
+ {shown.map((entry, index) => (
328
+ // Index key: a hand-edited or foreign log may repeat a name, and a
329
+ // duplicate React key would drop a row the model did see.
330
+ <li key={index} className={css.entry}>
331
+ <code className={css.entryName}>{entry.name}</code>
332
+ <span className={css.entryDescription}>{entry.description}</span>
333
+ </li>
334
+ ))}
335
+ </ul>
336
+ {shown.length < entries.length && (
337
+ <p className={css.catalogNotice} data-context-entries-truncated>
338
+ {t('message.context.catalog.more', { count: entries.length - shown.length })}
339
+ </p>
340
+ )}
341
+ {/* The block union is merge-extensible: a catalog message carrying an
342
+ unknown block still shows it rather than dropping model-visible content. */}
343
+ <UnknownBlocks blocks={rest} t={t} />
344
+ </>
345
+ )
346
+ }
347
+
348
+ /** One named contribution to a runtime snapshot, as the durable source records it. */
349
+ interface SnapshotSection {
350
+ name: string
351
+ text: string
352
+ }
353
+
354
+ /** Snapshot sections read off the source, or null when the record is unusable. */
355
+ function snapshotSections(source: unknown): SnapshotSection[] | null {
356
+ const record = asRecord(source)
357
+ const list = record === null ? undefined : record['sections']
358
+ if (!Array.isArray(list)) return null
359
+ const sections: SnapshotSection[] = []
360
+ for (const item of list as readonly unknown[]) {
361
+ const section = asRecord(item)
362
+ if (section === null) return null
363
+ const name = section['name']
364
+ const text = section['text']
365
+ if (typeof name !== 'string' || name === '' || typeof text !== 'string') return null
366
+ sections.push({ name, text })
367
+ }
368
+ return sections.length === 0 ? null : sections
369
+ }
370
+
371
+ /**
372
+ * `snapshot` form: the named contributions this snapshot assembled, in order.
373
+ *
374
+ * The sections are the same bytes the model read, split at the boundaries the
375
+ * producer assembled them on, so a reader sees which subsystem contributed
376
+ * which state instead of one undifferentiated wall.
377
+ *
378
+ * One sentence of the model-facing text is NOT in any section: the producer's
379
+ * framing line declaring that this snapshot supersedes earlier ones. Unlike the
380
+ * `<system-reminder>` wrapper an instruction context carries — which wraps
381
+ * content and cannot be separated from it — that line states the form's own
382
+ * semantics, so the body states them as a caption instead of reprinting the
383
+ * joined prose beside the sections it was split from.
384
+ * @param props - Durable content, its source, and the locale seat.
385
+ * @returns The snapshot context body, or the opaque body when unreadable.
386
+ */
387
+ export function SnapshotBody({ content, source, t }: {
388
+ content: ContextMessageNode['content']
389
+ source: unknown
390
+ t: Translate
391
+ }): ReactNode {
392
+ const sections = snapshotSections(source)
393
+ /* v8 ignore next -- contextBody reads the sections before choosing this body. */
394
+ if (sections === null) return <OpaqueBody content={content} source={source} t={t} />
395
+ return (
396
+ <>
397
+ <p className={css.catalogNotice} data-context-snapshot-supersedes>
398
+ {t('message.context.snapshot.supersedes')}
399
+ </p>
400
+ <dl className={css.sections} data-context-sections>
401
+ {sections.map((section, index) => (
402
+ <div key={index} className={css.section}>
403
+ <dt className={css.sectionName}>{section.name}</dt>
404
+ <dd className={css.sectionText}>{boundedText(section.text, t)}</dd>
405
+ </div>
406
+ ))}
407
+ </dl>
408
+ </>
409
+ )
410
+ }
411
+
412
+ /**
413
+ * `notice` form: what just happened, with the model-facing text beneath it.
414
+ *
415
+ * The one-line account also rides the collapsed row ({@link contextBody}), so a
416
+ * notice is usually readable without expanding at all.
417
+ * @param props - Durable content, its source, and the locale seat.
418
+ * @returns The notice context body.
419
+ */
420
+ export function NoticeBody({ content, t }: {
421
+ content: ContextMessageNode['content']
422
+ source: unknown
423
+ t: Translate
424
+ }): ReactNode {
425
+ return <ModelFacingContent content={content} t={t} />
426
+ }
427
+
428
+ /**
429
+ * `relay` form: which agent sent this, then what it said.
430
+ *
431
+ * The sender is an opaque session id; it is shown as a field rather than a
432
+ * label, because this client cannot resolve it to a title.
433
+ * @param props - Durable content, its source, and the locale seat.
434
+ * @returns The relay context body.
435
+ */
436
+ export function RelayBody({ content, source, t }: {
437
+ content: ContextMessageNode['content']
438
+ source: unknown
439
+ t: Translate
440
+ }): ReactNode {
441
+ const sender = relaySender(source)
442
+ /* v8 ignore next -- contextBody resolves the sender before choosing this body. */
443
+ if (sender === null) return <OpaqueBody content={content} source={source} t={t} />
444
+ return (
445
+ <>
446
+ <p className={css.relaySender} data-context-relay-sender>
447
+ {t('message.context.relay.from', { session: sender })}
448
+ </p>
449
+ <ModelFacingContent content={content} t={t} />
450
+ </>
451
+ )
452
+ }
453
+
454
+ /** The sending agent's session id, or null when the record does not name one. */
455
+ function relaySender(source: unknown): string | null {
456
+ const sender = asRecord(source)?.['senderSessionId']
457
+ return typeof sender === 'string' && sender !== '' ? sender : null
458
+ }
459
+
460
+ /** One recalled session, as the durable source records it. */
461
+ interface RecalledSession {
462
+ label: string
463
+ retained: number
464
+ omitted: number
465
+ truncated: boolean
466
+ }
467
+
468
+ /** Recalled sessions read off the source, or null when the record is unusable. */
469
+ function recalledSessions(source: unknown): RecalledSession[] | null {
470
+ const record = asRecord(source)
471
+ const list = record === null ? undefined : record['references']
472
+ if (!Array.isArray(list)) return null
473
+ const sessions: RecalledSession[] = []
474
+ for (const item of list as readonly unknown[]) {
475
+ const reference = asRecord(item)
476
+ if (reference === null) return null
477
+ const label = reference['label']
478
+ const retained = reference['retainedMessages']
479
+ const omitted = reference['omittedMessages']
480
+ const truncated = reference['truncated']
481
+ // Completeness is the fact this card exists to report, so a reference that
482
+ // cannot state it is not a readable recall — showing the label alone would
483
+ // present a confident card over unknown loss.
484
+ if (typeof label !== 'string' || label === ''
485
+ || typeof retained !== 'number' || typeof omitted !== 'number'
486
+ || typeof truncated !== 'boolean') return null
487
+ sessions.push({ label, retained, omitted, truncated })
488
+ }
489
+ return sessions.length === 0 ? null : sessions
490
+ }
491
+
492
+ /**
493
+ * `recall` form: which sessions this material came from and how much of each
494
+ * survived the read, then the material itself.
495
+ *
496
+ * Completeness is the fact a reader needs first: recalled context is bounded on
497
+ * the way in, so a card that hid the omitted count would overstate what the
498
+ * model received.
499
+ * @param props - Durable content, its source, and the locale seat.
500
+ * @returns The recall context body, or the opaque body when unreadable.
501
+ */
502
+ export function RecallBody({ content, source, t }: {
503
+ content: ContextMessageNode['content']
504
+ source: unknown
505
+ t: Translate
506
+ }): ReactNode {
507
+ const sessions = recalledSessions(source)
508
+ if (sessions === null) return <OpaqueBody content={content} source={source} t={t} />
509
+ return (
510
+ <>
511
+ <ul className={css.recalls} data-context-recalls>
512
+ {sessions.map((session, index) => (
513
+ <li key={index} className={css.recall}>
514
+ <span className={css.recallLabel}>{session.label}</span>
515
+ <span className={css.recallCounts}>
516
+ {t('message.context.recall.counts', {
517
+ retained: session.retained,
518
+ omitted: session.omitted,
519
+ })}
520
+ </span>
521
+ {session.truncated && (
522
+ <span className={css.recallCounts}>{t('message.context.recall.truncated')}</span>
523
+ )}
524
+ </li>
525
+ ))}
526
+ </ul>
527
+ <ModelFacingContent content={content} t={t} />
528
+ </>
529
+ )
530
+ }
531
+
532
+ /** The one-line account a `notice` puts on its collapsed row, when it records one. */
533
+ function noticeSummary(source: unknown): string | null {
534
+ const summary = asRecord(source)?.['summary']
535
+ return typeof summary === 'string' && summary !== '' ? summary : null
536
+ }
537
+
538
+ /**
539
+ * Choose the body for one context node.
540
+ *
541
+ * Returns the form the body actually rendered as, which is not always the
542
+ * declared one: a declared form whose fields are unreadable falls back to
543
+ * opaque, and the caller labels the row with what it really shows.
544
+ * `summary` is the collapsed row's one-line account, which only a `notice`
545
+ * records: its whole point is being readable without expanding.
546
+ * @param form - the producer-declared form projected onto the node.
547
+ * @param props - durable content, its source, and the locale seat.
548
+ * @returns the rendered form (null for opaque), its collapsed summary, and its body.
549
+ */
550
+ export function contextBody(
551
+ form: ContextMessageNode['form'],
552
+ props: { content: ContextMessageNode['content']; source: unknown; t: Translate },
553
+ ): { rendered: KnownContextForm | null; summary: string | null; body: ReactNode } {
554
+ const opaque = { rendered: null, summary: null, body: <OpaqueBody {...props} /> }
555
+ switch (form) {
556
+ case 'instructions':
557
+ return instructionChanges(props.source) === null
558
+ ? opaque
559
+ : { rendered: 'instructions', summary: null, body: <InstructionsBody {...props} /> }
560
+ case 'catalog':
561
+ return catalogEntries(props.source) === null
562
+ ? opaque
563
+ : { rendered: 'catalog', summary: null, body: <CatalogBody {...props} /> }
564
+ case 'snapshot':
565
+ return snapshotSections(props.source) === null
566
+ ? opaque
567
+ : { rendered: 'snapshot', summary: null, body: <SnapshotBody {...props} /> }
568
+ case 'notice': {
569
+ const summary = noticeSummary(props.source)
570
+ return summary === null
571
+ ? opaque
572
+ : { rendered: 'notice', summary, body: <NoticeBody {...props} /> }
573
+ }
574
+ case 'relay':
575
+ return relaySender(props.source) === null
576
+ ? opaque
577
+ : { rendered: 'relay', summary: null, body: <RelayBody {...props} /> }
578
+ case 'recall':
579
+ return recalledSessions(props.source) === null
580
+ ? opaque
581
+ : { rendered: 'recall', summary: null, body: <RecallBody {...props} /> }
582
+ case null:
583
+ return opaque
584
+ /* v8 ignore next 4 -- closed-union backstop; the compiler rejects a new
585
+ KnownContextForm here rather than letting it degrade to opaque silently. */
586
+ default: {
587
+ const unreachable: never = form
588
+ throw new Error(`unreachable context form: ${String(unreachable)}`)
589
+ }
590
+ }
591
+ }
@@ -0,0 +1,66 @@
1
+ /* Figma 10:2482: 24px Tool calls header, 4px gap, 141px code block cap. */
2
+
3
+ .root {
4
+ min-width: 0;
5
+ }
6
+
7
+ .root[data-open] {
8
+ padding-bottom: 4px;
9
+ }
10
+
11
+ .chevron {
12
+ color: var(--dsw-alias-label-secondary);
13
+ }
14
+
15
+ /* Separator and producer name beside the role title: ToolRow's summary geometry,
16
+ so the two disclosure rows keep one 24px rhythm and one separator shape. */
17
+ .sep {
18
+ flex: none;
19
+ width: 2px;
20
+ height: 2px;
21
+ margin: 0 8px;
22
+ border-radius: 1px;
23
+ background: var(--dsw-alias-label-caption);
24
+ }
25
+
26
+ .source {
27
+ flex: none;
28
+ min-width: 0;
29
+ overflow: hidden;
30
+ color: var(--dsw-alias-label-tertiary);
31
+ font-size: 14px;
32
+ line-height: 24px;
33
+ text-overflow: ellipsis;
34
+ white-space: nowrap;
35
+ }
36
+
37
+ /* A notice's one-line account: the reason it rarely needs expanding. */
38
+ .summary {
39
+ flex: 1 1 auto;
40
+ min-width: 0;
41
+ overflow: hidden;
42
+ color: var(--dsw-alias-label-tertiary);
43
+ font-size: 14px;
44
+ line-height: 24px;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
47
+ }
48
+
49
+ .body {
50
+ box-sizing: border-box;
51
+ width: calc(100% - 22px);
52
+ max-height: 141px;
53
+ margin: 4px 0 0 22px;
54
+ overflow: auto;
55
+ padding: 10px 16px 12px 12px;
56
+ border: none;
57
+ border-radius: 8px;
58
+ background: var(--dsw-alias-markdown-code-block);
59
+ color: var(--dsw-alias-label-tertiary);
60
+ /* Figma 10:2482 code text: the form bodies inherit it from the scrollport. */
61
+ font: 400 11px/16px var(--ds-font-family-code);
62
+ }
63
+
64
+ .toolChanges {
65
+ white-space: nowrap;
66
+ }