@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,121 @@
1
+ /** Headless fixture verification; never attaches to the user's browser/Host. */
2
+ import { createRequire } from 'node:module';
3
+ import { readFile, mkdir, writeFile } from 'node:fs/promises';
4
+ import { createServer } from 'node:http';
5
+ import { homedir } from 'node:os';
6
+ import { dirname, join, resolve } from 'node:path';
7
+ import { pathToFileURL } from 'node:url';
8
+ import assert from 'node:assert/strict';
9
+
10
+ const harness = process.env.DSHX_HARNESS;
11
+ if (!harness) throw new Error('Set DSHX_HARNESS to the target checkout.');
12
+ const root = resolve(import.meta.dirname, '..');
13
+ const require = createRequire(join(root, 'package.json'));
14
+ const esbuild = createRequire(require.resolve('tsx')).resolve('esbuild');
15
+ const { build } = await import(pathToFileURL(esbuild).href);
16
+ const browserRuntime = join(homedir(), '.codex/playwright-runtime/runtime.mjs');
17
+ const { launchPinnedChromium } = await import(pathToFileURL(browserRuntime).href);
18
+ const out = join(root, '.evidence/official-bridge');
19
+ await mkdir(out, { recursive: true });
20
+ const paths = {
21
+ registry: 'ui-renderer/src/client/registry.ts', renderer: 'ui-renderer/src/client/scoped-slots.tsx',
22
+ feedback: 'ui-message-feedback/src/client/index.ts', 'read-row': 'ui-tool/src/client/tool/toolviews/read-row.tsx',
23
+ 'read-image-row': 'ui-tool/src/client/tool/toolviews/read-image-row.tsx', 'tool-locale': 'ui-conversation/src/client/locales.ts',
24
+ deliverables: 'ui-deliverables/src/client/Deliverables.tsx', 'deliverables-locale': 'ui-deliverables/src/client/locales.ts',
25
+ 'open-in-app-action': 'ui-open-in-app/src/client/FileRouteAction.tsx', 'open-in-app-locale': 'ui-open-in-app/src/client/locales.ts',
26
+ };
27
+ const webRequire = createRequire(join(harness, 'packages/client/web/package.json'));
28
+ await build({
29
+ entryPoints: [join(root, 'tests/browser-official-bridge.tsx')], outfile: join(out, 'fixture.js'), bundle: true,
30
+ format: 'esm', platform: 'browser', jsx: 'automatic', sourcemap: true, logLimit: 5,
31
+ alias: {
32
+ ...Object.fromEntries(Object.entries(paths).map(([name, path]) => [`@fixture/${name}`, join(harness, 'packages/client', path)])),
33
+ '@deepseek-ai/dsh-client-ui-primitives': join(harness, 'packages/client/ui-primitives/src/index.ts'),
34
+ '@deepseek-ai/dsh-client-ui-slots': join(harness, 'packages/client/ui-slots/src/index.ts'),
35
+ react: dirname(require.resolve('react/package.json')), 'react-dom': dirname(webRequire.resolve('react-dom/package.json')),
36
+ },
37
+ loader: { '.woff2': 'dataurl', '.woff': 'dataurl', '.ttf': 'dataurl', '.svg': 'dataurl' },
38
+ });
39
+ const pixel = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/lN8AAAAASUVORK5CYII=', 'base64');
40
+ const server = createServer(async (req, res) => {
41
+ if (req.url === '/') { res.setHeader('Content-Type', 'text/html'); res.end('<!doctype html><html lang="zh"><meta charset="utf-8"><link rel="stylesheet" href="/fixture.css"><style>body{font-family:system-ui;margin:24px}button{cursor:pointer}</style><div id="app"></div><script type="module" src="/fixture.js"></script></html>'); return; }
42
+ if (req.url?.startsWith('/api/file?') || req.url === '/pixel.png') { res.setHeader('Content-Type', 'image/png'); res.end(pixel); return; }
43
+ if (req.url?.startsWith('/api/present.open')) { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify([{ id: 'preview-app', name: 'Preview App', default: true, icon: null }, { id: 'other-app', name: 'Other App', default: false, icon: null }])); return; }
44
+ const file = req.url === '/fixture.js' ? 'fixture.js' : req.url === '/fixture.css' ? 'fixture.css' : null;
45
+ if (!file) { res.writeHead(404); res.end(); return; }
46
+ res.setHeader('Content-Type', file.endsWith('.js') ? 'text/javascript' : 'text/css');
47
+ res.end(await readFile(join(out, file)));
48
+ });
49
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
50
+ let browser;
51
+ try {
52
+ browser = await launchPinnedChromium();
53
+ const page = await browser.newPage({ viewport: { width: 1200, height: 900 } });
54
+ const errors = [];
55
+ page.setDefaultTimeout(10000);
56
+ page.on('pageerror', error => { errors.push(String(error)); console.error(String(error)); });
57
+ page.on('console', msg => { if (msg.type() === 'error') { errors.push(msg.text()); console.error(msg.text().slice(0, 1200)); } });
58
+ await page.goto(`http://127.0.0.1:${server.address().port}`);
59
+ await page.getByRole('button', { name: '好的回答', exact: true }).waitFor();
60
+ assert.equal(await page.locator('[data-nested]').textContent(), 'nested official child');
61
+ await page.getByRole('button', { name: 'Count 0' }).click();
62
+ await page.getByRole('button', { name: 'Count 1' }).waitFor();
63
+ await page.evaluate(() => window.fixture.update());
64
+ await page.getByText('session-a / updated / turn-context', { exact: true }).waitFor();
65
+ await page.evaluate(() => window.fixture.addAction());
66
+ await page.getByRole('button', { name: 'Future official action' }).waitFor();
67
+ assert.equal(await page.getByRole('button', { name: 'Count 1' }).count(), 1);
68
+ assert.equal(await page.evaluate(() => window.fixture.observations.mounts), 1);
69
+ // Uses the real official dialog/controller with in-memory Remote replies.
70
+ await page.getByRole('button', { name: '好的回答', exact: true }).click();
71
+ await page.getByRole('dialog').waitFor();
72
+ await page.screenshot({ path: join(out, 'feedback-dialog.png') });
73
+ await writeFile(join(out, 'dialog.txt'), await page.getByRole('dialog').innerText());
74
+ const cancel = page.getByRole('button', { name: 'close', exact: true });
75
+ await cancel.click();
76
+ await page.getByRole('dialog').waitFor({ state: 'hidden' });
77
+ await page.getByRole('button', { name: '好的回答', exact: true }).click();
78
+ await page.getByRole('button', { name: '任务结果', exact: true }).click();
79
+ await page.getByRole('textbox', { name: '反馈详情' }).fill('Local fixture only');
80
+ await page.getByRole('button', { name: 'submit', exact: true }).click();
81
+ await page.getByRole('dialog').waitFor({ state: 'hidden' });
82
+ await page.getByRole('button', { name: '取消标记', exact: true }).waitFor();
83
+ const put = await page.evaluate(() => window.fixture.observations.requests.find(([method]) => method === 'put')[1]);
84
+ assert.deepEqual(put, { sessionId: 'session-a', messageId: 'message-1', rating: 'positive', note: 'Local fixture only', category: 'task-result', ifVersion: null });
85
+ await page.getByRole('button', { name: '取消标记', exact: true }).click();
86
+ await page.getByRole('button', { name: '好的回答', exact: true }).waitFor();
87
+ const tool = page.locator('[data-reader-tool]');
88
+ await tool.getByRole('button', { name: 'sample.ts', exact: true }).click();
89
+ const open = await page.evaluate(() => window.fixture.observations.opens[0]);
90
+ assert.deepEqual(open, { path: '/fixture/sample.ts', options: { line: 12 } });
91
+ await tool.locator('[data-disclosure-row]').click();
92
+ await tool.getByText('export const value = 1;', { exact: true }).waitFor();
93
+ assert.equal(await page.locator('[data-presented-file]').count(), 1);
94
+ assert.equal(await page.locator('[data-produced-files-row]').count(), 0);
95
+ await page.getByRole('button', { name: '在侧边栏预览 /fixture/report.pdf', exact: true }).click();
96
+ assert.equal(await page.evaluate(() => window.fixture.observations.opens.at(-1).path), '/fixture/report.pdf');
97
+ await page.getByRole('button', { name: '更多打开方式', exact: true }).click();
98
+ await page.getByRole('menuitem', { name: 'Other App', exact: true }).click();
99
+ assert.deepEqual(await page.evaluate(() => window.fixture.observations.opens.at(-1)), ['presented', 'session-a', 42, 0, 'open', 'other-app']);
100
+ await page.evaluate(() => window.fixture.language());
101
+ await page.getByRole('button', { name: 'Good response', exact: true }).waitFor();
102
+ const image = page.locator('img[alt="本地样例"]');
103
+ assert.match(await image.getAttribute('src'), /\/api\/file\?path=%2Ffixture%2Fimage.png$/);
104
+ assert.equal(await image.evaluate(img => img.complete && img.naturalWidth > 0), true);
105
+ await page.screenshot({ path: join(out, 'reader-controls.png') });
106
+ await writeFile(join(out, 'tool.txt'), await page.locator('[data-reader-tool]').innerText());
107
+ const before = await page.evaluate(() => window.fixture.observations.subscriptions);
108
+ await page.evaluate(() => window.fixture.unmount());
109
+ await page.locator('[data-unmounted]').waitFor();
110
+ const after = await page.evaluate(() => window.fixture.observations.subscriptions);
111
+ assert.ok(after < before, `subscriptions did not release (${before} → ${after})`);
112
+ await page.evaluate(() => window.fixture.mount());
113
+ await page.getByRole('button', { name: 'Good response', exact: true }).waitFor();
114
+ assert.deepEqual(errors, []);
115
+ const result = { passed: true, assertions: ['real official feedback dialog', 'feedback submit/retract through mock Remote', 'official file opener with line', 'official read detail', 'official presented file preview/menu', 'no duplicate produced row', 'memo', 'locale change', 'session injection', 'store actions', 'context hook', 'nested slots', 'new official registration', 'stable mount', 'subscription cleanup', 'Reader remount', 'local image'], observations: await page.evaluate(() => window.fixture.observations) };
116
+ await writeFile(join(out, 'result.json'), JSON.stringify(result, null, 2) + '\n');
117
+ console.log(JSON.stringify(result));
118
+ } finally {
119
+ await browser?.close();
120
+ await new Promise(resolve => server.close(resolve));
121
+ }
@@ -0,0 +1,62 @@
1
+ /** Offline pixel regression for the shipped tool-summary CSS; no Host or model. */
2
+ import assert from 'node:assert/strict';
3
+ import { readFile, mkdir, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { join, resolve } from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+
8
+ const root = resolve(import.meta.dirname, '..');
9
+ const bundle = resolve(process.argv[2] ?? join(root, 'lib/client.js'));
10
+ const out = join(root, '.evidence/tool-shimmer');
11
+ await mkdir(out, { recursive: true });
12
+ const js = await readFile(bundle, 'utf8');
13
+ const line = js.split('\n').find(line => /const css\$\d+ = "\.\w+_root\{/.test(line));
14
+ assert.ok(line, 'Reader CSS must be present in the shipped bundle');
15
+ const css = JSON.parse(line.slice(line.indexOf('=') + 1).trim().replace(/;$/, ''));
16
+ const summary = css.match(/\.(\w+_nativeToolSummary)\[data-running\]/)?.[1];
17
+ assert.ok(summary);
18
+ const { launchPinnedChromium } = await import(pathToFileURL(join(homedir(), '.codex/playwright-runtime/runtime.mjs')).href);
19
+ const browser = await launchPinnedChromium();
20
+ const results = [];
21
+ try {
22
+ for (const theme of ['dark', 'light']) for (const width of [180, 800]) {
23
+ const page = await browser.newPage({ viewport: { width: 900, height: 120 }, deviceScaleFactor: 2 });
24
+ await page.setContent(`<style>${css}body{margin:20px;background:${theme === 'dark' ? '#141414' : '#fff'};--dsw-alias-label-tertiary:${theme === 'dark' ? '#aaa' : '#666'};font:14px system-ui}.row{display:flex;width:${width}px}</style><div class="row" data-motion="on"><span data-running class="${summary}">Re-run acceptance criteria verification</span></div>`);
25
+ const target = page.locator(`.${summary}`);
26
+ const capture = async time => {
27
+ await target.evaluate((el, time) => {
28
+ const animations = el.getAnimations();
29
+ if (animations.length !== 1) throw new Error(`Expected one animation, found ${animations.length}`);
30
+ animations[0].pause(); animations[0].currentTime = time;
31
+ }, time);
32
+ return target.screenshot();
33
+ };
34
+ const timing = await target.evaluate(el => el.getAnimations()[0].effect.getTiming().duration);
35
+ const first = await capture(0);
36
+ const middle = await capture(timing * .25);
37
+ const beforeWrap = await capture(timing - 1);
38
+ const afterWrap = await capture(timing);
39
+ assert.notDeepEqual(middle, first, 'The highlight must visibly travel across the text');
40
+ assert.deepEqual(beforeWrap, first, 'The sweep must fully leave the text before wrapping');
41
+ assert.deepEqual(afterWrap, first, 'The cycle boundary must preserve identical text pixels');
42
+ await writeFile(join(out, `${theme}-${width}-start.png`), first);
43
+ await writeFile(join(out, `${theme}-${width}-middle.png`), middle);
44
+ await writeFile(join(out, `${theme}-${width}-end.png`), beforeWrap);
45
+ const runningStyle = await target.evaluate(el => ({ repeat: getComputedStyle(el).backgroundRepeat, animationCount: el.getAnimations().length }));
46
+ assert.equal(runningStyle.repeat, 'no-repeat');
47
+ await page.locator('[data-motion]').evaluate(el => { el.dataset.motion = 'off'; });
48
+ assert.equal(await target.evaluate(el => el.getAnimations().length), 0, 'Reader motion-off must stop shimmer');
49
+ await page.locator('[data-motion]').evaluate(el => { el.dataset.motion = 'on'; });
50
+ await page.emulateMedia({ reducedMotion: 'reduce' });
51
+ const reduced = await target.evaluate(el => ({ count: el.getAnimations().length, fill: getComputedStyle(el).webkitTextFillColor }));
52
+ assert.equal(reduced.count, 0, 'System reduced motion must stop shimmer');
53
+ assert.notEqual(reduced.fill, 'rgba(0, 0, 0, 0)', 'Stopped animation must keep text readable');
54
+ await page.emulateMedia({ reducedMotion: 'no-preference' });
55
+ await target.evaluate(el => el.removeAttribute('data-running'));
56
+ assert.equal(await target.evaluate(el => el.getAnimations().length), 0, 'Settled tools must not shimmer');
57
+ results.push({ theme, width, loopPixelsContinuous: true, ...runningStyle, motionOff: true, reducedMotion: true, settled: true });
58
+ await page.close();
59
+ }
60
+ await writeFile(join(out, 'result.json'), JSON.stringify({ bundle, results }, null, 2));
61
+ console.log(JSON.stringify({ passed: true, results }));
62
+ } finally { await browser.close(); }
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: generative-mcpapps
3
+ description: "Render interactive HTML/JS widgets, product UI mockups, and clickable prototypes inside the chat (MCP Apps / SEP-1865). Use whenever the user asks to see, preview, compare, or interact with a design (mockup, prototype, 画出来看看): data visualizations, calculators, stateful forms, config wizards, or any UI richer than plain Markdown."
4
+ ---
5
+
6
+ # Generative MCP Apps
7
+
8
+ Create self-contained interactive HTML/JS widgets, UI mockups, and clickable prototypes in chat. This is an Agent Skill with YAML metadata and Markdown instructions; it has no Gemini-specific syntax or model requirement.
9
+
10
+ ## Select the delivery surface
11
+
12
+ - In DSH with Better Display enabled, emit an `mcp-app` fenced code block. The reading view renders it in an iframe with `sandbox="allow-scripts allow-forms"`.
13
+ - In another harness, use only its available, documented UI tool and actual input schema. The Better Display fence and message aliases below are not a universal MCP Apps transport.
14
+ - If no inline renderer is available, save a standalone HTML artifact and explain how to open it. Do not claim that a plain code block was rendered.
15
+
16
+ The harness must discover this skill before automatic selection can work. Keep the whole `generative-mcpapps/` folder together, including references and examples, in a skill directory supported by that harness. Merely placing it inside a plugin repository does not prove that the current agent has loaded it.
17
+
18
+ ## Build the widget
19
+
20
+ 1. Choose local interaction (tabs, filters, calculations) or an explicit submission back to chat. For Better Display, submission fills the composer; the user still sends the message. It does not execute tools or commands automatically.
21
+ 2. Inline critical CSS and JavaScript. Use semantic theme variables with readable fallbacks, a transparent body, and layouts that fit widths from 320px to 1200px. Avoid `100vh` and `min-h-screen`: Better Display measures content height and clamps it to 60–2400px.
22
+ 3. Read [HTML boilerplate and theme tokens](references/html_boilerplate.md) when assembling a card. Read [Better Display host messages](references/sep1865_protocol.md) only when using host communication. The latter documents this plugin's implementation, not a full MCP Apps specification.
23
+ 4. For a selectable local-state example, inspect [interactive_quiz.html](examples/interactive_quiz.html). Adapt its content and controls to the user's request.
24
+
25
+ ## Deliver in Better Display
26
+
27
+ Output the complete HTML document directly in the answer:
28
+
29
+ ````markdown
30
+ ```mcp-app title="交互原型"
31
+ <!DOCTYPE html>
32
+ <html lang="zh-CN">
33
+ <head>
34
+ <meta charset="UTF-8">
35
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
36
+ <title>交互原型</title>
37
+ </head>
38
+ <body>
39
+ <!-- Include the widget's inline styles, content, and script here. -->
40
+ </body>
41
+ </html>
42
+ ```
43
+ ````
44
+
45
+ ## Verify the result
46
+
47
+ Check the complete document, narrow-width layout, readable theme contrast, and the requested interaction. For a submission widget, verify that the chosen data reaches the chat composer; distinguish that from a sent message or completed agent action. A local-only widget needs no host submission. Report only the rendering and interaction actually observed in the available harness.
@@ -0,0 +1,153 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>双盲方案盲选评测器</title>
7
+ <style>
8
+ :root {
9
+ --app-bg: var(--background, #ffffff);
10
+ --card-bg: var(--card, #f8fafc);
11
+ --border-color: var(--border, #e2e8f0);
12
+ --text-main: var(--foreground, #0f172a);
13
+ --text-muted: var(--muted-foreground, #64748b);
14
+ --primary-color: var(--primary, #2563eb);
15
+ --primary-text: var(--primary-foreground, #ffffff);
16
+ --hover-bg: rgba(148, 163, 184, 0.12);
17
+ }
18
+ @media (prefers-color-scheme: dark) {
19
+ :root {
20
+ --app-bg: var(--background, #0f172a);
21
+ --card-bg: var(--card, #1e293b);
22
+ --border-color: var(--border, #334155);
23
+ --text-main: var(--foreground, #f8fafc);
24
+ --text-muted: var(--muted-foreground, #94a3b8);
25
+ }
26
+ }
27
+ * { box-sizing: border-box; margin: 0; padding: 0; }
28
+ body {
29
+ background: transparent;
30
+ color: var(--text-main);
31
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
32
+ padding: 12px;
33
+ }
34
+ .quiz-card {
35
+ background: var(--card-bg);
36
+ border: 1px solid var(--border-color);
37
+ border-radius: 12px;
38
+ padding: 16px;
39
+ max-width: 680px;
40
+ margin: 0 auto;
41
+ }
42
+ .header {
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ margin-bottom: 12px;
47
+ border-bottom: 1px solid var(--border-color);
48
+ padding-bottom: 8px;
49
+ }
50
+ .title { font-size: 15px; font-weight: 600; }
51
+ .badge {
52
+ font-size: 11px;
53
+ padding: 2px 8px;
54
+ border-radius: 9999px;
55
+ background: var(--hover-bg);
56
+ color: var(--text-muted);
57
+ }
58
+ .prompt {
59
+ font-size: 13px;
60
+ color: var(--text-muted);
61
+ margin-bottom: 14px;
62
+ line-height: 1.4;
63
+ }
64
+ .options { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
65
+ .option-btn {
66
+ text-align: left;
67
+ background: transparent;
68
+ border: 1px solid var(--border-color);
69
+ border-radius: 8px;
70
+ padding: 10px 14px;
71
+ color: var(--text-main);
72
+ font-size: 13px;
73
+ cursor: pointer;
74
+ line-height: 1.4;
75
+ transition: all 0.15s ease;
76
+ }
77
+ .option-btn:hover { background: var(--hover-bg); }
78
+ .option-btn.selected {
79
+ border-color: var(--primary-color);
80
+ background: var(--hover-bg);
81
+ font-weight: 500;
82
+ }
83
+ .footer {
84
+ display: flex;
85
+ justify-content: space-between;
86
+ align-items: center;
87
+ margin-top: 10px;
88
+ }
89
+ .submit-btn {
90
+ background: var(--primary-color);
91
+ color: var(--primary-text);
92
+ border: none;
93
+ border-radius: 6px;
94
+ padding: 7px 16px;
95
+ font-size: 13px;
96
+ cursor: pointer;
97
+ }
98
+ .submit-btn:disabled { opacity: 0.5; cursor: not-allowed; }
99
+ </style>
100
+ </head>
101
+ <body>
102
+ <div class="quiz-card">
103
+ <div class="header">
104
+ <span class="title">⚡ 架构方案盲选评测</span>
105
+ <span class="badge" id="step-indicator">第 1/2 题</span>
106
+ </div>
107
+ <p class="prompt" id="question-text">
108
+ 【场景】:订单超时自动关单,选 Redis 过期监听还是延迟消息队列?
109
+ </p>
110
+
111
+ <div class="options" id="options-container">
112
+ <button class="option-btn" onclick="selectOption('A')">A: 选 Redis 监听,轻量便捷无需额外运维</button>
113
+ <button class="option-btn" onclick="selectOption('B')">B: 坚决选延迟消息队列,Redis 监听会静默丢事件</button>
114
+ </div>
115
+
116
+ <div class="footer">
117
+ <span style="font-size: 12px; color: var(--text-muted);" id="status-text">请选择最符合你直觉的方案</span>
118
+ <button class="submit-btn" id="submit-btn" disabled onclick="submitChoice()">确认选择并提交</button>
119
+ </div>
120
+ </div>
121
+
122
+ <script>
123
+ let currentSelection = null;
124
+
125
+ function selectOption(key) {
126
+ currentSelection = key;
127
+ document.querySelectorAll('.option-btn').forEach((btn, idx) => {
128
+ btn.classList.toggle('selected', (idx === 0 && key === 'A') || (idx === 1 && key === 'B'));
129
+ });
130
+ document.getElementById('submit-btn').disabled = false;
131
+ }
132
+
133
+ function submitChoice() {
134
+ if (!currentSelection) return;
135
+
136
+ document.getElementById('status-text').innerText = "已发送到聊天输入框,请确认并发送。";
137
+ document.getElementById('submit-btn').disabled = true;
138
+
139
+ // 通过 Better Display 适配器将选择填入聊天输入框
140
+ window.parent.postMessage({
141
+ jsonrpc: "2.0",
142
+ id: "eval-" + Date.now(),
143
+ method: "ui/submit",
144
+ params: {
145
+ task: "architecture_quiz_completed",
146
+ choice: currentSelection,
147
+ desc: currentSelection === 'A' ? 'Redis 监听' : '延迟消息队列'
148
+ }
149
+ }, "*");
150
+ }
151
+ </script>
152
+ </body>
153
+ </html>
@@ -0,0 +1,122 @@
1
+ # HTML Boilerplate & Theme Tokens Reference
2
+
3
+ This document provides self-contained HTML/CSS templates optimized for MCP Apps iframes across dark and light modes.
4
+
5
+ ---
6
+
7
+ ## 1. Universal Theme Tokens Mapping
8
+
9
+ Host applications inject different CSS variables into iframes. Use this resilient CSS custom properties cascade to guarantee proper contrast regardless of the host:
10
+
11
+ ```css
12
+ :root {
13
+ /* Surface & Background */
14
+ --app-bg: var(--background, #ffffff);
15
+ --card-bg: var(--card, #f8fafc);
16
+ --border-color: var(--border, #e2e8f0);
17
+
18
+ /* Typography */
19
+ --text-main: var(--foreground, #0f172a);
20
+ --text-muted: var(--muted-foreground, #64748b);
21
+
22
+ /* Accents & States */
23
+ --primary-color: var(--primary, #2563eb);
24
+ --primary-text: var(--primary-foreground, #ffffff);
25
+ --hover-bg: rgba(148, 163, 184, 0.12);
26
+ }
27
+
28
+ @media (prefers-color-scheme: dark) {
29
+ :root {
30
+ --app-bg: var(--background, #0f172a);
31
+ --card-bg: var(--card, #1e293b);
32
+ --border-color: var(--border, #334155);
33
+ --text-main: var(--foreground, #f8fafc);
34
+ --text-muted: var(--muted-foreground, #94a3b8);
35
+ }
36
+ }
37
+ ```
38
+
39
+ ---
40
+
41
+ ## 2. Standard Responsive Card Template
42
+
43
+ ```html
44
+ <!DOCTYPE html>
45
+ <html lang="zh-CN">
46
+ <head>
47
+ <meta charset="UTF-8">
48
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
49
+ <style>
50
+ /* Insert Theme Tokens here */
51
+ * { box-sizing: border-box; margin: 0; padding: 0; }
52
+ body {
53
+ background: transparent;
54
+ color: var(--text-main);
55
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
56
+ padding: 12px;
57
+ }
58
+ .mcp-card {
59
+ background: var(--card-bg);
60
+ border: 1px solid var(--border-color);
61
+ border-radius: 12px;
62
+ padding: 16px;
63
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
64
+ max-width: 720px;
65
+ margin: 0 auto;
66
+ }
67
+ .mcp-header {
68
+ display: flex;
69
+ justify-content: space-between;
70
+ align-items: center;
71
+ margin-bottom: 12px;
72
+ }
73
+ .mcp-title {
74
+ font-size: 15px;
75
+ font-weight: 600;
76
+ color: var(--text-main);
77
+ }
78
+ .mcp-badge {
79
+ font-size: 11px;
80
+ padding: 2px 8px;
81
+ border-radius: 9999px;
82
+ background: var(--hover-bg);
83
+ color: var(--text-muted);
84
+ }
85
+ .btn-primary {
86
+ background: var(--primary-color);
87
+ color: var(--primary-text);
88
+ border: none;
89
+ border-radius: 6px;
90
+ padding: 8px 14px;
91
+ font-size: 13px;
92
+ font-weight: 500;
93
+ cursor: pointer;
94
+ transition: opacity 0.15s ease;
95
+ }
96
+ .btn-primary:hover { opacity: 0.9; }
97
+ </style>
98
+ </head>
99
+ <body>
100
+ <div class="mcp-card">
101
+ <div class="mcp-header">
102
+ <h3 class="mcp-title" id="widget-title">交互组件标题</h3>
103
+ <span class="mcp-badge" id="widget-badge">Interactive</span>
104
+ </div>
105
+ <div id="widget-content">
106
+ <!-- 动态正文内容 -->
107
+ </div>
108
+ </div>
109
+
110
+ <script>
111
+ function notifyHost(action, payload) {
112
+ window.parent.postMessage({
113
+ jsonrpc: "2.0",
114
+ id: "action-" + Date.now(),
115
+ method: "ui/submit",
116
+ params: { action, payload }
117
+ }, "*");
118
+ }
119
+ </script>
120
+ </body>
121
+ </html>
122
+ ```
@@ -0,0 +1,54 @@
1
+ # Better Display host messages
2
+
3
+ This documents the adapter in `src/client/McpAppFrame.tsx`, not the full SEP-1865 specification. Use these messages for Better Display's `mcp-app` iframe. Other hosts require their own documented transport and capabilities.
4
+
5
+ ## Initialization and theme
6
+
7
+ The iframe can send `ui/initialize` with a JSON-RPC request ID. Better Display responds with `result.protocolVersion` and `result.hostContext`, including `theme`, `locale`, and `styles.variables`.
8
+
9
+ For compatibility, Better Display also sends an initial `ui/initialize` notification with those theme fields in `params`. Later theme changes arrive as `ui/notifications/host-context-changed`. The plugin injects a theme and height helper into HTML documents.
10
+
11
+ When handling parent messages, verify the source:
12
+
13
+ ```javascript
14
+ window.addEventListener("message", (event) => {
15
+ if (event.source !== window.parent) return;
16
+ const msg = event.data;
17
+ if (!msg || msg.jsonrpc !== "2.0") return;
18
+ const context = msg.result?.hostContext || msg.params;
19
+ if (context?.theme) {
20
+ document.documentElement.dataset.theme = context.theme;
21
+ }
22
+ });
23
+ ```
24
+
25
+ The sandbox has an opaque origin, so messages to the parent use `"*"`. The host checks that incoming messages originate from this iframe's content window.
26
+
27
+ ## Submit a choice to the composer
28
+
29
+ ```javascript
30
+ window.parent.postMessage({
31
+ jsonrpc: "2.0",
32
+ id: "choice-" + Date.now(),
33
+ method: "ui/submit",
34
+ params: { task: "quiz_completed", choice: "B", desc: "Selected option B" }
35
+ }, "*");
36
+ ```
37
+
38
+ The host displays a receipt and fills the chat composer with a prompt derived from the parameters. The user must send it. `ui/update-model-context` is handled as the same composer action in this adapter; do not assume a silent model-context update. These submit handlers do not send a JSON-RPC success response, so do not wait indefinitely for one.
39
+
40
+ There is no `tools/call` handler in this adapter. Use submission for a requested follow-up instead of claiming that a click executed a tool.
41
+
42
+ ## Readiness and sizing
43
+
44
+ `ui/ready` and `ui/notifications/initialized` mark the iframe ready. The injected ResizeObserver normally handles height automatically. A widget may also send:
45
+
46
+ ```javascript
47
+ window.parent.postMessage({
48
+ jsonrpc: "2.0",
49
+ method: "ui/resize",
50
+ params: { height: document.body.scrollHeight }
51
+ }, "*");
52
+ ```
53
+
54
+ The host accepts positive finite heights and clamps them to 60–2400px. No `ui/updateState` handler is provided by this adapter.