@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,106 @@
1
+ import { useState } from 'react'
2
+ import type { ContextMessageNode } from '@deepseek-ai/dsh-client-ui-conversation/client'
3
+ import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
4
+ import { DisclosureRow, IconBrowseOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'
5
+ import { ReferenceIcon } from './ReferenceIcon.js'
6
+ import { contextBody } from './ContextBody.js'
7
+ import { toolUpdateRows } from './tool-update.js'
8
+ import css from './ContextInjectionRow.module.css'
9
+
10
+ /** Props for the logged non-user message presentation. */
11
+ export interface ContextInjectionRowProps {
12
+ content: ContextMessageNode['content']
13
+ source: ContextMessageNode['source']
14
+ /**
15
+ * Role and producer name projected from the durable source.
16
+ *
17
+ * Host generations disagree on the field name: newer builds project
18
+ * `provenance`, the 0.1.6-alpha generations project `producer`. Both are
19
+ * optional here and resolved below, so the row renders on either host instead
20
+ * of throwing on `undefined.role` and degrading its whole block boundary.
21
+ */
22
+ provenance?: ContextMessageNode['producer'] | null
23
+ /** Alpha-generation spelling of {@link provenance}. */
24
+ producer?: ContextMessageNode['producer'] | null
25
+ /** Producer-declared information form; null renders the opaque body. */
26
+ form: ContextMessageNode['form']
27
+ /** The owning view's locale seat, passed down as a plain prop. */
28
+ t: TranslateNS<'chat'>
29
+ }
30
+
31
+ /**
32
+ * Render logged context with the Tool calls disclosure chrome from Figma.
33
+ *
34
+ * The header names the role the context plays and, beside it, the producer the
35
+ * durable source identifies, so a reader can tell an injected skill catalog
36
+ * from a workspace instruction file or a recalled session without expanding.
37
+ * The expanded body follows the producer-declared form; an absent or unknown
38
+ * form renders the opaque body.
39
+ * @param props - Durable content, its projected producer role/name and form, and the locale seat.
40
+ * @returns A collapsed context row with a bounded, form-specific body.
41
+ */
42
+ export function ContextInjectionRow({ content, source, provenance, producer, form, t }: ContextInjectionRowProps) {
43
+ const [open, setOpen] = useState(false)
44
+ // Two host generations, one component. A row whose projection is absent
45
+ // entirely still renders: the marker falls back to a plain injection.
46
+ const view = provenance ?? producer ?? { role: 'inject' as const, label: null }
47
+ // Resolved rather than declared: a form whose fields are unreadable renders
48
+ // the opaque body, and the marker must say what the row actually shows.
49
+ const { rendered, summary, body } = contextBody(form, { content, source, t })
50
+ const tools = toolUpdateRows(content)
51
+ const toolSummary = tools === null || tools.single !== null ? null
52
+ : tools.added.length > 0 && tools.removed.length > 0
53
+ ? t('message.toolsChanged', { added: tools.added.length, removed: tools.removed.length })
54
+ : tools.added.length > 0
55
+ ? t('message.toolsAddedCount', { count: tools.added.length })
56
+ : t('message.toolsRemovedCount', { count: tools.removed.length })
57
+
58
+ return (
59
+ <DisclosureRow
60
+ className={css.root}
61
+ icon={view.role === 'recall' && tools === null
62
+ ? <span data-context-recall-icon><ReferenceIcon kind="session" /></span>
63
+ : <IconBrowseOutlineRegular size={14} />}
64
+ chevronClassName={css.chevron}
65
+ title={tools?.single !== undefined && tools.single !== null
66
+ ? t(tools.single.type === 'tool-addition' ? 'message.toolAdded' : 'message.toolRemoved', { name: tools.single.toolName })
67
+ : tools !== null
68
+ ? t('message.toolsUpdated')
69
+ : t(view.role === 'recall' ? 'message.contextRecall' : 'message.contextInjection')}
70
+ collapsedContent={toolSummary !== null ? (
71
+ <>
72
+ <span className={css.sep} aria-hidden />
73
+ <span className={css.summary}>{toolSummary}</span>
74
+ </>
75
+ ) : tools !== null || view.label === null ? undefined : (
76
+ /* ToolRow's separator shape: an aria-hidden dot, so the accessible name
77
+ stays the two readable parts and the two disclosure rows expose one
78
+ name shape. A source that names no producer drops the dot with it. */
79
+ <>
80
+ <span className={css.sep} aria-hidden />
81
+ <span className={css.source} data-context-source>{view.label}</span>
82
+ {summary !== null && (
83
+ <>
84
+ <span className={css.sep} aria-hidden />
85
+ <span className={css.summary} data-context-summary>{summary}</span>
86
+ </>
87
+ )}
88
+ </>
89
+ )}
90
+ keepContentWhenOpen
91
+ open={open && tools?.single == null}
92
+ expandable={tools?.single == null}
93
+ expandOnRowClick
94
+ onToggle={() => { setOpen(value => !value) }}
95
+ >
96
+ <div className={css.body} data-context-injection-body data-context-form={rendered ?? undefined}>
97
+ {tools === null ? body : (
98
+ <div className={css.toolChanges}>
99
+ {tools.added.length > 0 && <div>{t('message.toolsAdded', { names: tools.added.join(', ') })}</div>}
100
+ {tools.removed.length > 0 && <div>{t('message.toolsRemoved', { names: tools.removed.join(', ') })}</div>}
101
+ </div>
102
+ )}
103
+ </div>
104
+ </DisclosureRow>
105
+ )
106
+ }
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from 'react'
2
+ import {
3
+ IconBrowseOutlineRegular, IconFolderCloseRegular,
4
+ } from '@deepseek-ai/dsh-client-ui-primitives'
5
+
6
+ /** Reference domains with distinct composer and transcript glyphs. */
7
+ export type ReferenceIconKind = 'session' | 'file' | 'folder'
8
+
9
+ /** Props shared by inline reference glyphs. */
10
+ export interface ReferenceIconProps {
11
+ kind: ReferenceIconKind
12
+ size?: number
13
+ className?: string | undefined
14
+ }
15
+
16
+ /**
17
+ * Render the icon that identifies one inline reference domain.
18
+ * @param props - Reference kind, optional size, and optional CSS class.
19
+ * @returns The corresponding current-color SVG glyph.
20
+ */
21
+ export function ReferenceIcon({ kind, size = 16, className }: ReferenceIconProps): ReactNode {
22
+ switch (kind) {
23
+ case 'session':
24
+ return (
25
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" aria-hidden>
26
+ <path
27
+ d="M8 0.597656C3.91296 0.597656 0.599716 3.91103 0.599609 7.99805C0.599609 9.13171 0.854567 10.2079 1.31152 11.1699L1.59277 11.7607L2.77441 11.1992L2.49414 10.6084L2.36035 10.3076C2.06865 9.59612 1.90723 8.81645 1.90723 7.99805C1.90733 4.63362 4.63554 1.90625 8 1.90625C11.3644 1.90635 14.0917 4.63368 14.0918 7.99805C14.0918 11.3625 11.3644 14.0907 8 14.0908C7.311 14.0908 6.80642 14.0414 6.35938 13.918C5.919 13.7963 5.50105 13.5929 5.00098 13.2441C4.26805 12.7329 3.21756 12.5526 2.35156 13.0996L2.33789 13.1084L2.32422 13.1182L1.74805 13.5234L2.18164 14.8184L3.05957 14.2002C3.37505 14.0068 3.84248 14.0319 4.25195 14.3174C4.84447 14.7307 5.39718 15.009 6.01172 15.1787C6.61963 15.3465 7.25579 15.3984 8 15.3984C12.087 15.3983 15.4004 12.0851 15.4004 7.99805C15.4003 3.9111 12.087 0.59776 8 0.597656ZM4.56836 8.50977V9.80371H8.12402V8.50977H4.56836ZM4.56836 7.30078H11.4619V6.00684H4.56836V7.30078Z"
28
+ fill="currentColor"
29
+ />
30
+ </svg>
31
+ )
32
+ case 'file': return <IconBrowseOutlineRegular size={size} className={className} />
33
+ case 'folder': return <IconFolderCloseRegular size={size} className={className} />
34
+ }
35
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Pure row-model derivation for tool summary rows: variant classification,
3
+ * one-line summary, expanded-body text, and flattened result output from the
4
+ * frozen call slice. Input material comes from the call ARGUMENTS; output and
5
+ * error material from the settled result node. A call whose render intent is
6
+ * a terminal card gets its expanded body from the views instead, through
7
+ * `terminalCardModel` in terminal-card-model.ts.
8
+ */
9
+ // The block union's defining home is runtime (fold-product types); this
10
+ // contract only forwards it (type-definition authority stays with the layer
11
+ // that produces the values).
12
+ import { abbreviateHomePath } from '@deepseek-ai/dsh-util-workspace-path'
13
+ import type { ToolCallBlock, ToolResultNode } from '@deepseek-ai/dsh-client-ui-conversation/client'
14
+
15
+ export type { ToolCallBlock } from '@deepseek-ai/dsh-client-ui-conversation/client'
16
+
17
+ /** Tool-call row variants selected by the generic atomic renderer. */
18
+ export type ToolRowVariant = 'search' | 'read' | 'bash' | 'write' | 'edit' | 'code' | 'others'
19
+
20
+ /** Row state semantic; colors self-supplied via StateDot (design gives none). */
21
+ export type ToolRowState = 'running' | 'ok' | 'error' | 'stopped'
22
+
23
+ /** Figma row titles per variant (design literals, not translatable copy). */
24
+ export const VARIANT_TITLES: Record<ToolRowVariant, string> = {
25
+ search: 'Search', read: 'Read', bash: 'Bash',
26
+ write: 'Write', edit: 'Edit', code: 'Code', others: 'Tool call',
27
+ }
28
+
29
+ /**
30
+ * Known tool name -> variant.
31
+ *
32
+ * `cordis_define` is deliberately absent: ui-cordis registers a keyed
33
+ * `tool.call.toolview` entry for it, and a keyed hit REPLACES the generic row
34
+ * (this table is only reached through GenericToolCard, the dispatch fallback in
35
+ * ToolCallTree). An entry here would be unreachable, and a second title for the
36
+ * same call would be a second answer to a question the card already owns.
37
+ */
38
+ const TOOL_VARIANTS: Record<string, ToolRowVariant> = {
39
+ bash: 'bash',
40
+ // The PowerShell twin is a shell tool: the bash row family (icon, colors)
41
+ // with its own title from TOOL_TITLES, not the generic `others` row.
42
+ pwsh: 'bash',
43
+ read: 'read',
44
+ web_fetch: 'read',
45
+ web_search: 'search',
46
+ grep: 'search',
47
+ glob: 'search',
48
+ write: 'write',
49
+ edit: 'edit',
50
+ run_code: 'code',
51
+ cordis_package_inspect: 'read',
52
+ cordis_runtime_inspect: 'read',
53
+ // The three run-control verbs take one package id and produce a receipt, so
54
+ // the generic row is the decided intent, not an unclassified default: there is
55
+ // no program to show (that is `cordis_define`'s card) and no file to open. The
56
+ // id lands in the summary slot, and the titles below name the act.
57
+ cordis_run: 'others',
58
+ cordis_stop: 'others',
59
+ cordis_undefine: 'others',
60
+ }
61
+
62
+ /** Tool-owned titles that refine a generic row variant without replacing it. */
63
+ const TOOL_TITLES: Record<string, string> = {
64
+ cordis_package_inspect: 'Inspect',
65
+ cordis_runtime_inspect: 'Inspect',
66
+ cordis_run: 'Run Cordis Plugin',
67
+ cordis_stop: 'Stop Cordis Plugin',
68
+ cordis_undefine: 'Remove Cordis Plugin',
69
+ pwsh: 'Pwsh',
70
+ }
71
+
72
+ /**
73
+ * Classify a tool name into its row variant.
74
+ * @param toolName - wire tool name.
75
+ * @returns matching variant, others when unknown.
76
+ */
77
+ export function classifyTool(toolName: string): ToolRowVariant {
78
+ return TOOL_VARIANTS[toolName] ?? 'others'
79
+ }
80
+
81
+ /** Everything ToolRow needs, derived once from the frozen slice. */
82
+ export interface ToolRowModel {
83
+ variant: ToolRowVariant
84
+ title: string
85
+ summary: string
86
+ /**
87
+ * Filesystem path from args (`path` / `file_path`) when the row is a file
88
+ * tool; absent for URL reads and non-file tools. The chat view resolves
89
+ * relative values against the session cwd before opening.
90
+ */
91
+ filePath: string | undefined
92
+ /** Expanded-body input text (pretty args); null = no input section. */
93
+ body: string | null
94
+ /** Flattened result text ({@link resultText}); null while running or when the result carries no text. */
95
+ output: string | null
96
+ /** First line of the result text on an error row; null for every other state. */
97
+ errorSummary: string | null
98
+ state: ToolRowState
99
+ }
100
+
101
+ /**
102
+ * Flatten a settled result's content blocks to display text: text blocks
103
+ * verbatim, other block shapes as pretty JSON. Empty content on a failed call
104
+ * falls back to the structured error's `name: code` line.
105
+ * @param node - the settled result node.
106
+ * @returns the flattened result text (may be empty).
107
+ */
108
+ export function resultText(node: ToolResultNode): string {
109
+ const parts: string[] = []
110
+ for (const block of node.content) {
111
+ if (block.type === 'text') parts.push(block.text)
112
+ else parts.push(JSON.stringify(block, null, 2))
113
+ }
114
+ if (parts.length === 0 && node.error !== undefined) {
115
+ parts.push(`${node.error.name}: ${node.error.code}`)
116
+ }
117
+ return parts.join('\n')
118
+ }
119
+
120
+ function parseArgs(argsRaw: string): unknown {
121
+ try {
122
+ return JSON.parse(argsRaw)
123
+ } catch {
124
+ // Non-JSON args (mid-stream truncation): summary/body fall back to the raw string.
125
+ return undefined
126
+ }
127
+ }
128
+
129
+ function firstLine(text: string): string {
130
+ const nl = text.indexOf('\n')
131
+ return nl === -1 ? text : text.slice(0, nl)
132
+ }
133
+
134
+ function pickString(args: Record<string, unknown>, keys: readonly string[]): string | undefined {
135
+ for (const key of keys) {
136
+ const v = args[key]
137
+ if (typeof v === 'string' && v !== '') return v
138
+ }
139
+ return undefined
140
+ }
141
+
142
+ /** Summary key preference per variant (args-derived; result-derived summaries are a ledger item). */
143
+ const SUMMARY_KEYS: Record<ToolRowVariant, readonly string[]> = {
144
+ bash: ['description', 'command'],
145
+ read: ['path', 'file_path', 'url'],
146
+ search: ['query', 'pattern', 'url'],
147
+ write: ['path', 'file_path'],
148
+ edit: ['path', 'file_path'],
149
+ code: ['description'],
150
+ others: [],
151
+ }
152
+
153
+ /**
154
+ * Strip the workspace root from a workspace-rooted absolute path (display only).
155
+ * @param text - the path to shorten.
156
+ * @param cwd - session workspace root; absent or empty leaves the path unchanged.
157
+ * @returns the path relative to the workspace root, or unchanged when it is not rooted there.
158
+ */
159
+ export function relativizeToCwd(text: string, cwd: string | undefined): string {
160
+ if (cwd === undefined || cwd === '') return text
161
+ const root = cwd.replace(/[/\\]+$/, '')
162
+ if (text.startsWith(`${root}/`) || text.startsWith(`${root}\\`)) return text.slice(root.length + 1)
163
+ return text
164
+ }
165
+
166
+ function deriveSummary(variant: ToolRowVariant, argsRaw: string): string {
167
+ const parsed = parseArgs(argsRaw)
168
+ if (typeof parsed !== 'object' || parsed === null) return firstLine(argsRaw)
169
+ const args = parsed as Record<string, unknown>
170
+ if (variant === 'search' && Array.isArray(args.queries)) {
171
+ const queries = args.queries.filter((query): query is string => typeof query === 'string' && query !== '')
172
+ if (queries.length > 0) return queries.map(firstLine).join(', ')
173
+ }
174
+ const picked = pickString(args, SUMMARY_KEYS[variant])
175
+ if (picked !== undefined) return firstLine(picked)
176
+ for (const v of Object.values(args)) {
177
+ if (typeof v === 'string' && v !== '') return firstLine(v)
178
+ }
179
+ return firstLine(argsRaw)
180
+ }
181
+
182
+ /** Path keys only — never `url` (web_fetch lands on the read variant). */
183
+ const FILE_PATH_KEYS = ['path', 'file_path'] as const
184
+
185
+ /** File-tool variants whose summary may be an openable workspace path. */
186
+ const FILE_PATH_VARIANTS: ReadonlySet<ToolRowVariant> = new Set(['read', 'write', 'edit'])
187
+
188
+ function deriveFilePath(variant: ToolRowVariant, argsRaw: string): string | undefined {
189
+ if (!FILE_PATH_VARIANTS.has(variant)) return undefined
190
+ const parsed = parseArgs(argsRaw)
191
+ if (typeof parsed !== 'object' || parsed === null) return undefined
192
+ const picked = pickString(parsed as Record<string, unknown>, FILE_PATH_KEYS)
193
+ return picked === undefined ? undefined : firstLine(picked)
194
+ }
195
+
196
+ function deriveBody(variant: ToolRowVariant, argsRaw: string): string | null {
197
+ if (argsRaw === '') return null
198
+ const parsed = parseArgs(argsRaw)
199
+ if (parsed === undefined) return argsRaw
200
+ // The code row's expanded body IS the program (monospace via the row's
201
+ // variant styling), not the args JSON envelope around it.
202
+ if (variant === 'code' && typeof parsed === 'object' && parsed !== null) {
203
+ const code = (parsed as Record<string, unknown>).code
204
+ if (typeof code === 'string' && code !== '') return code
205
+ }
206
+ return JSON.stringify(parsed, null, 2)
207
+ }
208
+
209
+ /**
210
+ * Derive the full row model from a frozen call slice.
211
+ * @param toolName - wire tool name (dispatch-supplied; survives windowless results).
212
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
213
+ * @param cwd - session workspace root; workspace-rooted path summaries display relative to it.
214
+ * @param home - host account home; a leftover POSIX home path displays as `~`.
215
+ * @returns the row model.
216
+ */
217
+ export function toolRowModel(toolName: string, block: ToolCallBlock, cwd?: string, home?: string): ToolRowModel {
218
+ const variant = classifyTool(toolName)
219
+ const done = 'kind' in block
220
+ const argsRaw = (done ? block.call?.argsRaw : ('argsRaw' in block ? block.argsRaw : '')) ?? ''
221
+ const state: ToolRowState = !done ? 'running'
222
+ : block.error?.code === 'interrupted' ? 'stopped'
223
+ : block.isError ? 'error' : 'ok'
224
+ const base = argsRaw === ''
225
+ ? block.callId
226
+ : abbreviateHomePath(relativizeToCwd(deriveSummary(variant, argsRaw), cwd), home)
227
+ const toolTitle = TOOL_TITLES[toolName]
228
+ // Others keeps the static "Tool call" title (figma literal); the real tool
229
+ // name rides the mutable summary slot unless the tool owns a specific title.
230
+ const summary = variant === 'others' && toolName !== '' && toolTitle === undefined
231
+ ? `${toolName} · ${base}`
232
+ : base
233
+ // The empty string is "no text" for both derived result fields: a settled
234
+ // call with blank content has nothing to expand, and a blank first line
235
+ // would erase the collapsed error row's summary slot.
236
+ const output = done ? (resultText(block) || null) : null
237
+ const errorSummary = state === 'error' && output !== null ? firstLine(output) : null
238
+ return {
239
+ variant,
240
+ title: toolTitle ?? VARIANT_TITLES[variant],
241
+ summary,
242
+ filePath: deriveFilePath(variant, argsRaw),
243
+ body: deriveBody(variant, argsRaw),
244
+ output,
245
+ errorSummary,
246
+ state,
247
+ }
248
+ }
@@ -0,0 +1,32 @@
1
+ /** One developer tool-change block the 0.1.7-rc.2 context row names in place. */
2
+ export interface ToolChangeBlock {
3
+ type: 'tool-addition' | 'tool-removal'
4
+ toolName: string
5
+ }
6
+
7
+ /** Names collected from a context whose every block is a tool change. */
8
+ export interface ToolUpdateRows {
9
+ added: readonly string[]
10
+ removed: readonly string[]
11
+ /** The only block, when the row is a single add or removal. */
12
+ single: ToolChangeBlock | null
13
+ }
14
+
15
+ /**
16
+ * Classify a context as the rc.2 tool-update row.
17
+ * Mixed or empty content stays on the generic injection presentation.
18
+ * @param content - durable context blocks.
19
+ * @returns collected names, or null when any block is not a tool change.
20
+ */
21
+ export function toolUpdateRows(content: readonly { readonly type: string; readonly toolName?: string }[]): ToolUpdateRows | null {
22
+ if (content.length === 0) return null
23
+ const rows: ToolChangeBlock[] = []
24
+ for (const block of content) {
25
+ if (block.type !== 'tool-addition' && block.type !== 'tool-removal') return null
26
+ if (typeof block.toolName !== 'string') return null
27
+ rows.push({ type: block.type, toolName: block.toolName })
28
+ }
29
+ const added = rows.flatMap(row => row.type === 'tool-addition' ? [row.toolName] : [])
30
+ const removed = rows.flatMap(row => row.type === 'tool-removal' ? [row.toolName] : [])
31
+ return { added, removed, single: rows.length === 1 ? rows[0]! : null }
32
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "repository": "DeepSeek Harness",
3
+ "commit": "b150a551b8d465e31e418e1b2eaf5e79bbb7d28e",
4
+ "adaptations": [
5
+ "Public type import in place of private type-only path",
6
+ "ESM .js import suffixes",
7
+ "Local ReferenceIcon location"
8
+ ],
9
+ "files": [
10
+ {
11
+ "source": "packages/client/ui-conversation/src/client/chat/ContextInjectionRow.tsx",
12
+ "output": "ContextInjectionRow.tsx",
13
+ "sha256": "afb41e6285f2abaf0eb6a1f843dbc135c8480bd48848beed1f94e395ac0f68c8"
14
+ },
15
+ {
16
+ "source": "packages/client/ui-conversation/src/client/chat/ContextInjectionRow.module.css",
17
+ "output": "ContextInjectionRow.module.css",
18
+ "sha256": "af61636789223d2476ec818a76d795bc7c13ca1a433f834d78702a825bd000de"
19
+ },
20
+ {
21
+ "source": "packages/client/ui-conversation/src/client/chat/ContextBody.tsx",
22
+ "output": "ContextBody.tsx",
23
+ "sha256": "94f41356d59bdcdae7225f3cb8cd9b0c0b1a32d24153d82512bfba7c40443cc8"
24
+ },
25
+ {
26
+ "source": "packages/client/ui-conversation/src/client/chat/ContextBody.module.css",
27
+ "output": "ContextBody.module.css",
28
+ "sha256": "b3838bf9cf38af53b31fd6333b4766ad063024d9a59cd636a38e3e0e07d7bf61"
29
+ },
30
+ {
31
+ "source": "packages/client/ui-conversation/src/client/reference/ReferenceIcon.tsx",
32
+ "output": "ReferenceIcon.tsx",
33
+ "sha256": "6315cb1ceafa830cd9e38ce296ddf5791ab93562e4f5a7bdbda1ef9ac6f7ed57"
34
+ },
35
+ {
36
+ "source": "packages/client/ui-tool/src/client/tool/models/tool-call-model.ts",
37
+ "output": "tool-call-model.ts",
38
+ "sha256": "02536cb0be80168351857083d0f0b71554216cd3195bad8016b1e4bb567acc7f"
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,211 @@
1
+ import { createElement, memo, useMemo, type ComponentType, type ReactNode } from 'react';
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { SlotEntryDef, SlotMap, SlotSpec, StoredEntry } from '@deepseek-ai/dsh-client-ui-slots';
4
+ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client';
5
+
6
+ /**
7
+ * RC2 composition adapter. Only public registry operations are used. We lend
8
+ * registrations a Reader-owned seat, not a second implementation of their UI.
9
+ * The platform still binds injection, hooks, locale, stores and boundaries.
10
+ * Original entries, declarations and components are never changed.
11
+ *
12
+ * Child seats have unique names because RC2 declarations have one owner. The
13
+ * sole component wrapper translates those names before delegating to the
14
+ * framework-provided renderSlot. It does not implement a slot renderer.
15
+ */
16
+ export const OFFICIAL_SLOTS = {
17
+ actions: 'conversation.chat.assistant-actions',
18
+ tools: 'tool.call.toolview',
19
+ tail: 'conversation.chat.turnTail',
20
+ nodes: 'conversation.chat.node',
21
+ images: 'conversation.message.images',
22
+ } as const;
23
+ export type OfficialFamily = keyof typeof OFFICIAL_SLOTS;
24
+ export const OFFICIAL_SEATS = {
25
+ actions: 'dsh-better-display.official.actions/conversation.chat.assistant-actions',
26
+ tools: 'dsh-better-display.official.tools/tool.call.toolview',
27
+ tail: 'dsh-better-display.official.tail/conversation.chat.turnTail',
28
+ nodes: 'dsh-better-display.official.nodes/conversation.chat.node',
29
+ images: 'dsh-better-display.official.images/conversation.message.images',
30
+ } as const;
31
+ export type OfficialSeat = typeof OFFICIAL_SEATS[OfficialFamily];
32
+ export const officialSeat = (family: OfficialFamily, source: string = OFFICIAL_SLOTS[family]) =>
33
+ `dsh-better-display.official.${family}/${source}`;
34
+
35
+ type Spec = SlotSpec<SlotEntryDef>;
36
+ type Render = (key: string, owner: object, options?: object) => ReactNode;
37
+ type RenderProps = { renderSlot: Render; renderSlotChain?: Render } & Record<string, unknown>;
38
+ type Registration = StoredEntry['options'] & Omit<StoredEntry, 'options' | 'component'> & { name: string };
39
+
40
+ /** The documented, type-erased registry inspection/registration boundary. */
41
+ export interface CompositionRegistry {
42
+ spec(key: string): Spec | undefined;
43
+ entriesOfSlot(key: string): readonly StoredEntry[];
44
+ subscribe(key: string, listener: () => void): () => void;
45
+ inject(key: string, effect: () => () => void): () => void;
46
+ register(options: Registration, component: unknown): () => void;
47
+ }
48
+
49
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
50
+ interface SlotMap {
51
+ 'dsh-better-display.official.actions/conversation.chat.assistant-actions': SlotMap['conversation.chat.assistant-actions'];
52
+ 'dsh-better-display.official.tools/tool.call.toolview': SlotMap['tool.call.toolview'];
53
+ 'dsh-better-display.official.tail/conversation.chat.turnTail': SlotMap['conversation.chat.turnTail'];
54
+ 'dsh-better-display.official.nodes/conversation.chat.node': SlotMap['conversation.chat.node'];
55
+ 'dsh-better-display.official.images/conversation.message.images': SlotMap['conversation.message.images'];
56
+ }
57
+ }
58
+
59
+ // Existing Reader presentations own these rows. All other node kinds, including
60
+ // future registrations, reach the official renderer through the fallback seat.
61
+ const READER_NODES = new Set(['user', 'steering', 'assistant-step', 'tool-call', 'turn-tail', 'turn-process']);
62
+ const EXPECTED: Record<OfficialFamily, Spec> = {
63
+ actions: { kind: 'list', scope: 'session' },
64
+ tools: { kind: 'keyed', scope: 'session' },
65
+ tail: { kind: 'list', scope: 'session' },
66
+ nodes: { kind: 'keyed', scope: 'session' },
67
+ images: { kind: 'single', scope: 'session' },
68
+ };
69
+
70
+ /**
71
+ * Reader already presents produced paths with its copy/reveal actions. RC2's
72
+ * official tail additionally owns explicit `present` artifacts. Leave those
73
+ * cards, actions and state intact, and omit only the duplicate produced chips.
74
+ * This changes a presentation prop, never the source match or session data.
75
+ * Unrecognized shapes pass through intact for safe forward degradation.
76
+ */
77
+ export function readerTailMatch(value: unknown, displayedPaths?: readonly string[]): unknown {
78
+ if (!value || typeof value !== 'object') return value;
79
+ const match = value as Record<string, unknown>;
80
+ const displayed = new Set(displayedPaths ?? []);
81
+ return displayed.size > 0 && Array.isArray(match.produced) && Array.isArray(match.presented)
82
+ ? { ...match, produced: match.produced.filter(path => !displayed.has(path)) }
83
+ : value;
84
+ }
85
+
86
+ function tailPresentation(component: unknown) {
87
+ const Original = component as ComponentType<Record<string, unknown>>;
88
+ return memo(function ReaderTailPresentation(props: Record<string, unknown>) {
89
+ const matched = useMemo(() => readerTailMatch(props.matched, props.readerProducedPaths as readonly string[] | undefined), [props.matched, props.readerProducedPaths]);
90
+ return createElement(Original, { ...props, matched });
91
+ });
92
+ }
93
+
94
+ export function officialChildren(slots: Pick<CompositionRegistry, 'spec'>): { [K in OfficialSeat]: SlotSpec<SlotMap[K]> } {
95
+ return Object.fromEntries(Object.entries(OFFICIAL_SLOTS).map(([name, source]) => {
96
+ const family = name as OfficialFamily;
97
+ const spec = slots.spec(source);
98
+ if (spec && (spec.kind !== EXPECTED[family].kind || spec.scope !== EXPECTED[family].scope)) {
99
+ throw new Error(`Official slot contract changed: ${source} (${spec.kind}/${spec.scope})`);
100
+ }
101
+ // In particular, retain conversation.chat.node's contextual turn-data hook.
102
+ return [officialSeat(family), spec ?? EXPECTED[family]];
103
+ })) as { [K in OfficialSeat]: SlotSpec<SlotMap[K]> };
104
+ }
105
+
106
+ function translatedComponent(entry: StoredEntry, names: ReadonlyMap<string, string>) {
107
+ if (names.size === 0) return entry.component;
108
+ const Original = entry.component as ComponentType<Record<string, unknown>>;
109
+ return memo(function OfficialSlotChildren(props: RenderProps) {
110
+ const renders = useMemo(() => {
111
+ const translate = (render: Render | undefined): Render => (key, owner, options) => {
112
+ const seat = names.get(key);
113
+ if (!seat || !render) throw new Error(`Undeclared official child slot: ${key}`);
114
+ return render(seat, owner, options);
115
+ };
116
+ return {
117
+ renderSlot: translate(props.renderSlot),
118
+ ...(props.renderSlotChain ? { renderSlotChain: translate(props.renderSlotChain) } : {}),
119
+ };
120
+ }, [props.renderSlot, props.renderSlotChain]);
121
+ return createElement(Original, { ...props, ...renders });
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Mirror one elected contribution set incrementally. Unrelated additions do not
127
+ * remount existing cards or recreate their subscriptions. A source unload/HMR
128
+ * disposes the corresponding subtree and closures before its replacement.
129
+ */
130
+ export function mirrorOfficialSlot(
131
+ slots: CompositionRegistry,
132
+ source: string,
133
+ target: string,
134
+ namespace: string,
135
+ accept: (entry: StoredEntry) => boolean = () => true,
136
+ ): () => void {
137
+ const mounted = new Map<StoredEntry, () => void>();
138
+ let stopped = false;
139
+ const reconcile = () => {
140
+ if (stopped) return;
141
+ const entries = slots.entriesOfSlot(source).filter(accept);
142
+ const wanted = new Set(entries);
143
+ for (const [entry, dispose] of mounted) {
144
+ if (wanted.has(entry)) continue;
145
+ dispose();
146
+ mounted.delete(entry);
147
+ }
148
+ for (const entry of entries) {
149
+ if (mounted.has(entry)) continue;
150
+ const names = new Map(Object.keys(entry.children ?? {}).map(key => [key, `${namespace}/${key}`]));
151
+ const children = Object.fromEntries([...names].map(([key, seat]) => [seat, entry.children![key]]));
152
+ const disposers: (() => void)[] = [];
153
+ const dispose = () => { for (const stop of disposers.splice(0).reverse()) stop(); };
154
+ try {
155
+ const { component: _component, options, children: _children, ...metadata } = entry;
156
+ const presentation = source === OFFICIAL_SLOTS.tail && entry.locale === 'deliverables'
157
+ ? { ...entry, component: tailPresentation(entry.component) }
158
+ : entry;
159
+ disposers.push(slots.register({
160
+ ...options, ...metadata, name: target,
161
+ ...(names.size ? { children } : {}),
162
+ registrant: `dsh-better-display → ${entry.registrant ?? source}`,
163
+ }, translatedComponent(presentation, names)));
164
+ for (const [key, seat] of names) {
165
+ disposers.push(mirrorOfficialSlot(slots, key, seat, namespace));
166
+ }
167
+ mounted.set(entry, dispose);
168
+ } catch (error) {
169
+ dispose();
170
+ throw error;
171
+ }
172
+ }
173
+ };
174
+ const unsubscribe = slots.subscribe(source, reconcile);
175
+ const dispose = () => {
176
+ stopped = true;
177
+ unsubscribe();
178
+ for (const stop of [...mounted.values()].reverse()) stop();
179
+ mounted.clear();
180
+ };
181
+ try { reconcile(); } catch (error) { dispose(); throw error; }
182
+ return dispose;
183
+ }
184
+
185
+ export function installOfficialSlots(ctx: Context): () => void {
186
+ // The SlotMap overloads are for statically declared keys. Aliases are a
187
+ // runtime composition, using exactly the public erased metadata contract.
188
+ const slots = ctx.slots as unknown as CompositionRegistry;
189
+ const disposers: (() => void)[] = [];
190
+ try {
191
+ for (const [name, source] of Object.entries(OFFICIAL_SLOTS)) {
192
+ const family = name as OfficialFamily;
193
+ disposers.push(slots.inject(source, () => {
194
+ const spec = slots.spec(source)!;
195
+ const target = officialSeat(family);
196
+ const declared = slots.spec(target);
197
+ if (spec.kind !== declared?.kind || spec.scope !== declared.scope) {
198
+ throw new Error(`Official slot contract changed: ${source}`);
199
+ }
200
+ return mirrorOfficialSlot(slots, source, target, `dsh-better-display.official.${family}`,
201
+ family === 'nodes' ? entry => !READER_NODES.has(entry.options.key ?? '') : undefined);
202
+ }));
203
+ }
204
+ } catch (error) {
205
+ for (const stop of disposers.reverse()) stop();
206
+ throw error;
207
+ }
208
+ return () => { for (const stop of disposers.splice(0).reverse()) stop(); };
209
+ }
210
+
211
+ export type OfficialSlotKey = typeof OFFICIAL_SLOTS[OfficialFamily] & keyof SlotMap;