@agent-native/core 0.100.2 → 0.101.2

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 (345) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +65 -0
  3. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +48 -30
  4. package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +7 -0
  5. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +48 -30
  6. package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +7 -0
  7. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +48 -30
  8. package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +14 -0
  9. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +48 -30
  10. package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +7 -0
  11. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +48 -30
  12. package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +7 -0
  13. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +48 -30
  14. package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +7 -0
  15. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +48 -30
  16. package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +7 -0
  17. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +48 -30
  18. package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +14 -0
  19. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +48 -30
  20. package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +7 -0
  21. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +48 -30
  22. package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +7 -0
  23. package/corpus/core/docs/content/pr-visual-recap.mdx +50 -32
  24. package/corpus/core/docs/content/template-plan.mdx +14 -0
  25. package/corpus/core/docs/content/toolkit-comments-review.mdx +16 -2
  26. package/corpus/core/package.json +4 -1
  27. package/corpus/core/src/a2a/index.ts +2 -1
  28. package/corpus/core/src/a2a/server.ts +34 -9
  29. package/corpus/core/src/agent/production-agent.ts +56 -10
  30. package/corpus/core/src/cli/create.ts +47 -19
  31. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -3
  32. package/corpus/core/src/cli/recap.ts +3 -4932
  33. package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +6 -4
  34. package/corpus/core/src/cli/skills-content/visual-recap-skill.ts +7 -1
  35. package/corpus/core/src/client/AgentPanel.tsx +19 -17
  36. package/corpus/core/src/client/analytics.ts +8 -1
  37. package/corpus/core/src/client/chat/markdown-renderer.tsx +54 -8
  38. package/corpus/core/src/client/chat/message-components.tsx +8 -2
  39. package/corpus/core/src/client/clipboard.ts +28 -1
  40. package/corpus/core/src/client/guided-questions.tsx +4 -0
  41. package/corpus/core/src/client/i18n.tsx +2 -1
  42. package/corpus/core/src/client/index.ts +4 -0
  43. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +108 -0
  44. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +482 -150
  45. package/corpus/core/src/client/review/index.ts +8 -0
  46. package/corpus/core/src/client/review/use-review.ts +35 -1
  47. package/corpus/core/src/client/use-agent-chat-context.ts +3 -2
  48. package/corpus/core/src/review/actions/create-review-comment.ts +2 -1
  49. package/corpus/core/src/review/actions/get-review-feedback.ts +6 -11
  50. package/corpus/core/src/review/actions/list-review-comments.ts +38 -4
  51. package/corpus/core/src/review/actions/reply-review-comment.ts +33 -22
  52. package/corpus/core/src/review/actions/resolve-review-thread.ts +29 -2
  53. package/corpus/core/src/review/actions/send-review-thread-to-agent.ts +62 -0
  54. package/corpus/core/src/review/identity.ts +101 -0
  55. package/corpus/core/src/review/index.ts +14 -0
  56. package/corpus/core/src/review/store.ts +315 -8
  57. package/corpus/core/src/review/types.ts +4 -0
  58. package/corpus/core/src/server/action-discovery.ts +4 -0
  59. package/corpus/core/src/server/action-routes.ts +125 -4
  60. package/corpus/core/src/server/agent-chat/plugin-options.ts +16 -0
  61. package/corpus/core/src/server/agent-chat-plugin.ts +1 -0
  62. package/corpus/core/src/server/index.ts +8 -0
  63. package/corpus/core/src/vite/action-types-plugin.ts +4 -0
  64. package/corpus/templates/analytics/changelog/2026-07-14-daily-dashboard-email-captures-authenticate-with-a-sessi.md +6 -0
  65. package/corpus/templates/analytics/server/lib/dashboard-report.ts +156 -3
  66. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +402 -142
  67. package/corpus/templates/assets/app/i18n/zh-TW.ts +1 -0
  68. package/corpus/templates/assets/app/i18n-data.ts +1 -0
  69. package/corpus/templates/assets/app/routes/library.tsx +187 -48
  70. package/corpus/templates/assets/changelog/2026-07-13-merged-the-candidates-panel-into-the-library-as-a-drafts-tab.md +6 -0
  71. package/corpus/templates/chat/app/lib/agent-page.tsx +40 -0
  72. package/corpus/templates/chat/app/routes/agent.tsx +5 -3
  73. package/corpus/templates/chat/changelog/2026-07-14-fixed-chat-template-startup-with-older-core-versions.md +6 -0
  74. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +5 -0
  75. package/corpus/templates/clips/AGENTS.md +4 -0
  76. package/corpus/templates/clips/actions/get-feature-flags.ts +30 -0
  77. package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
  78. package/corpus/templates/clips/actions/request-transcript.ts +41 -1
  79. package/corpus/templates/clips/actions/view-screen.ts +5 -9
  80. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  81. package/corpus/templates/clips/app/components/player/video-player.tsx +39 -1
  82. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -12
  83. package/corpus/templates/clips/app/hooks/use-mse-video-source.ts +146 -0
  84. package/corpus/templates/clips/app/lib/capture-install-options.ts +21 -0
  85. package/corpus/templates/clips/app/lib/fmp4.ts +271 -0
  86. package/corpus/templates/clips/app/lib/mse-video-loader.ts +585 -0
  87. package/corpus/templates/clips/app/routes/_app.dictate.tsx +0 -4
  88. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -13
  89. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +2 -37
  90. package/corpus/templates/clips/app/routes/download.tsx +3 -2
  91. package/corpus/templates/clips/changelog/2026-07-10-shared-clips-now-start-playing-instantly-instead-of-showing-.md +6 -0
  92. package/corpus/templates/clips/changelog/2026-07-14-clips-now-distinguishes-transcript-previews-from-incomplete-.md +6 -0
  93. package/corpus/templates/clips/changelog/2026-07-14-desktop-app-prompts-stay-hidden-after-you-download-the-insta.md +6 -0
  94. package/corpus/templates/clips/desktop/src/lib/recorder.ts +5 -0
  95. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +10 -0
  96. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -0
  97. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +2518 -0
  98. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +353 -0
  99. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +558 -67
  100. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +11 -1
  101. package/corpus/templates/clips/desktop/src-tauri/src/remote_flags.rs +188 -0
  102. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +1 -1
  103. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +1 -0
  104. package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -0
  105. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +5 -1
  106. package/corpus/templates/clips/shared/feature-flags.ts +41 -0
  107. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +3 -0
  108. package/corpus/templates/design/.agents/skills/design-review-feedback/SKILL.md +42 -0
  109. package/corpus/templates/design/AGENTS.md +6 -0
  110. package/corpus/templates/design/README.md +1 -0
  111. package/corpus/templates/design/actions/navigate.ts +4 -4
  112. package/corpus/templates/design/actions/view-screen.ts +116 -2
  113. package/corpus/templates/design/agent-native.app-skill.json +5 -0
  114. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +52 -59
  115. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +13 -9
  116. package/corpus/templates/design/app/components/design/EditPanel.tsx +79 -11
  117. package/corpus/templates/design/app/components/design/LayersPanel.tsx +78 -65
  118. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +161 -0
  119. package/corpus/templates/design/app/components/design/ReviewStatusControl.tsx +96 -0
  120. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +166 -0
  121. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  122. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +1077 -0
  123. package/corpus/templates/design/app/components/visual-editor/index.ts +1 -0
  124. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +42 -0
  125. package/corpus/templates/design/app/hooks/use-design-systems.ts +2 -2
  126. package/corpus/templates/design/app/hooks/use-navigation-state.ts +9 -6
  127. package/corpus/templates/design/app/hooks/use-question-flow.ts +7 -1
  128. package/corpus/templates/design/app/i18n/ar-SA.ts +56 -1
  129. package/corpus/templates/design/app/i18n/de-DE.ts +57 -1
  130. package/corpus/templates/design/app/i18n/en-US.ts +56 -1
  131. package/corpus/templates/design/app/i18n/es-ES.ts +56 -1
  132. package/corpus/templates/design/app/i18n/fr-FR.ts +57 -1
  133. package/corpus/templates/design/app/i18n/hi-IN.ts +56 -1
  134. package/corpus/templates/design/app/i18n/ja-JP.ts +56 -1
  135. package/corpus/templates/design/app/i18n/ko-KR.ts +56 -1
  136. package/corpus/templates/design/app/i18n/pt-BR.ts +57 -1
  137. package/corpus/templates/design/app/i18n/zh-CN.ts +56 -1
  138. package/corpus/templates/design/app/i18n/zh-TW.ts +51 -0
  139. package/corpus/templates/design/app/pages/Present.tsx +172 -14
  140. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +23 -0
  141. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +10 -0
  142. package/corpus/templates/design/app/pages/present-review-state.ts +18 -0
  143. package/corpus/templates/design/app/public-routes.ts +8 -0
  144. package/corpus/templates/design/app/root.tsx +7 -6
  145. package/corpus/templates/design/changelog/2026-07-13-dragged-design-assets-now-appear-at-the-visible-drop-locatio.md +6 -0
  146. package/corpus/templates/design/changelog/2026-07-13-reviewers-can-pin-comments-on-shared-designs-and-apply-verif.md +6 -0
  147. package/corpus/templates/design/server/plugins/agent-chat.ts +10 -0
  148. package/corpus/templates/design/server/plugins/auth.ts +7 -5
  149. package/corpus/templates/design/server/plugins/review.ts +9 -0
  150. package/corpus/templates/design/shared/review-anchor.ts +83 -0
  151. package/corpus/templates/design/shared/review-summary.ts +25 -0
  152. package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +2 -0
  153. package/corpus/templates/forms/AGENTS.md +4 -0
  154. package/corpus/templates/forms/actions/create-form.ts +2 -1
  155. package/corpus/templates/forms/actions/update-form.ts +5 -2
  156. package/corpus/templates/forms/app/i18n/ar-SA.ts +3 -0
  157. package/corpus/templates/forms/app/i18n/de-DE.ts +3 -0
  158. package/corpus/templates/forms/app/i18n/en-US.ts +3 -0
  159. package/corpus/templates/forms/app/i18n/es-ES.ts +3 -0
  160. package/corpus/templates/forms/app/i18n/fr-FR.ts +3 -0
  161. package/corpus/templates/forms/app/i18n/hi-IN.ts +3 -0
  162. package/corpus/templates/forms/app/i18n/ja-JP.ts +3 -0
  163. package/corpus/templates/forms/app/i18n/ko-KR.ts +3 -0
  164. package/corpus/templates/forms/app/i18n/pt-BR.ts +3 -0
  165. package/corpus/templates/forms/app/i18n/zh-CN.ts +3 -0
  166. package/corpus/templates/forms/app/i18n/zh-TW.ts +3 -0
  167. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +19 -0
  168. package/corpus/templates/forms/changelog/2026-07-14-fixed-the-ask-forms-loading-input-appearing-above-the-welcom.md +6 -0
  169. package/corpus/templates/forms/changelog/2026-07-14-form-social-previews-now-show-the-form-title-description-and.md +6 -0
  170. package/corpus/templates/forms/changelog/2026-07-14-forms-can-email-the-form-owner-when-new-responses-arrive.md +6 -0
  171. package/corpus/templates/forms/server/handlers/submissions.ts +17 -0
  172. package/corpus/templates/forms/server/lib/form-og-image.ts +64 -6
  173. package/corpus/templates/forms/server/lib/public-form-ssr.ts +6 -2
  174. package/corpus/templates/forms/server/lib/response-email.ts +65 -0
  175. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  176. package/corpus/templates/forms/server/routes/api/forms/og/[...slug]/og.png.get.ts +184 -0
  177. package/corpus/templates/forms/shared/types.ts +2 -0
  178. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +6 -4
  179. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +7 -1
  180. package/dist/a2a/index.d.ts +2 -1
  181. package/dist/a2a/index.d.ts.map +1 -1
  182. package/dist/a2a/index.js +1 -1
  183. package/dist/a2a/index.js.map +1 -1
  184. package/dist/a2a/server.d.ts +25 -0
  185. package/dist/a2a/server.d.ts.map +1 -1
  186. package/dist/a2a/server.js +30 -6
  187. package/dist/a2a/server.js.map +1 -1
  188. package/dist/agent/production-agent.d.ts +2 -0
  189. package/dist/agent/production-agent.d.ts.map +1 -1
  190. package/dist/agent/production-agent.js +37 -12
  191. package/dist/agent/production-agent.js.map +1 -1
  192. package/dist/cli/create.d.ts +5 -3
  193. package/dist/cli/create.d.ts.map +1 -1
  194. package/dist/cli/create.js +44 -19
  195. package/dist/cli/create.js.map +1 -1
  196. package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
  197. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  198. package/dist/cli/pr-visual-recap-workflow.js +2 -2
  199. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  200. package/dist/cli/recap.d.ts +3 -562
  201. package/dist/cli/recap.d.ts.map +1 -1
  202. package/dist/cli/recap.js +3 -3875
  203. package/dist/cli/recap.js.map +1 -1
  204. package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
  205. package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -1
  206. package/dist/cli/skills-content/visual-plan-skill.js +6 -4
  207. package/dist/cli/skills-content/visual-plan-skill.js.map +1 -1
  208. package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
  209. package/dist/cli/skills-content/visual-recap-skill.d.ts.map +1 -1
  210. package/dist/cli/skills-content/visual-recap-skill.js +7 -1
  211. package/dist/cli/skills-content/visual-recap-skill.js.map +1 -1
  212. package/dist/client/AgentPanel.d.ts.map +1 -1
  213. package/dist/client/AgentPanel.js +3 -3
  214. package/dist/client/AgentPanel.js.map +1 -1
  215. package/dist/client/analytics.d.ts +5 -0
  216. package/dist/client/analytics.d.ts.map +1 -1
  217. package/dist/client/analytics.js +3 -1
  218. package/dist/client/analytics.js.map +1 -1
  219. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  220. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  221. package/dist/client/chat/markdown-renderer.js +36 -1
  222. package/dist/client/chat/markdown-renderer.js.map +1 -1
  223. package/dist/client/chat/message-components.d.ts.map +1 -1
  224. package/dist/client/chat/message-components.js +5 -2
  225. package/dist/client/chat/message-components.js.map +1 -1
  226. package/dist/client/clipboard.d.ts +3 -1
  227. package/dist/client/clipboard.d.ts.map +1 -1
  228. package/dist/client/clipboard.js +24 -1
  229. package/dist/client/clipboard.js.map +1 -1
  230. package/dist/client/guided-questions.d.ts +3 -1
  231. package/dist/client/guided-questions.d.ts.map +1 -1
  232. package/dist/client/guided-questions.js +2 -1
  233. package/dist/client/guided-questions.js.map +1 -1
  234. package/dist/client/i18n.d.ts.map +1 -1
  235. package/dist/client/i18n.js +3 -1
  236. package/dist/client/i18n.js.map +1 -1
  237. package/dist/client/index.d.ts +1 -1
  238. package/dist/client/index.d.ts.map +1 -1
  239. package/dist/client/index.js +1 -1
  240. package/dist/client/index.js.map +1 -1
  241. package/dist/client/review/ReviewCommentComposer.d.ts +18 -0
  242. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -0
  243. package/dist/client/review/ReviewCommentComposer.js +30 -0
  244. package/dist/client/review/ReviewCommentComposer.js.map +1 -0
  245. package/dist/client/review/ReviewThreadPanel.d.ts +28 -1
  246. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  247. package/dist/client/review/ReviewThreadPanel.js +151 -39
  248. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  249. package/dist/client/review/index.d.ts +3 -2
  250. package/dist/client/review/index.d.ts.map +1 -1
  251. package/dist/client/review/index.js +2 -1
  252. package/dist/client/review/index.js.map +1 -1
  253. package/dist/client/review/use-review.d.ts +23 -0
  254. package/dist/client/review/use-review.d.ts.map +1 -1
  255. package/dist/client/review/use-review.js +3 -0
  256. package/dist/client/review/use-review.js.map +1 -1
  257. package/dist/client/use-agent-chat-context.d.ts +1 -1
  258. package/dist/client/use-agent-chat-context.d.ts.map +1 -1
  259. package/dist/client/use-agent-chat-context.js +4 -2
  260. package/dist/client/use-agent-chat-context.js.map +1 -1
  261. package/dist/collab/awareness.d.ts +2 -2
  262. package/dist/collab/awareness.d.ts.map +1 -1
  263. package/dist/collab/struct-routes.d.ts +1 -1
  264. package/dist/notifications/routes.d.ts +3 -3
  265. package/dist/observability/routes.d.ts +5 -5
  266. package/dist/progress/routes.d.ts +1 -1
  267. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  268. package/dist/resources/handlers.d.ts +2 -2
  269. package/dist/review/actions/create-review-comment.js +2 -1
  270. package/dist/review/actions/create-review-comment.js.map +1 -1
  271. package/dist/review/actions/get-review-feedback.js +6 -11
  272. package/dist/review/actions/get-review-feedback.js.map +1 -1
  273. package/dist/review/actions/list-review-comments.d.ts +1 -0
  274. package/dist/review/actions/list-review-comments.js +25 -4
  275. package/dist/review/actions/list-review-comments.js.map +1 -1
  276. package/dist/review/actions/reply-review-comment.js +12 -5
  277. package/dist/review/actions/reply-review-comment.js.map +1 -1
  278. package/dist/review/actions/resolve-review-thread.d.ts +3 -0
  279. package/dist/review/actions/resolve-review-thread.js +20 -3
  280. package/dist/review/actions/resolve-review-thread.js.map +1 -1
  281. package/dist/review/actions/send-review-thread-to-agent.d.ts +17 -0
  282. package/dist/review/actions/send-review-thread-to-agent.d.ts.map +1 -0
  283. package/dist/review/actions/send-review-thread-to-agent.js +49 -0
  284. package/dist/review/actions/send-review-thread-to-agent.js.map +1 -0
  285. package/dist/review/identity.d.ts +6 -0
  286. package/dist/review/identity.d.ts.map +1 -0
  287. package/dist/review/identity.js +68 -0
  288. package/dist/review/identity.js.map +1 -0
  289. package/dist/review/index.d.ts +3 -1
  290. package/dist/review/index.d.ts.map +1 -1
  291. package/dist/review/index.js +2 -1
  292. package/dist/review/index.js.map +1 -1
  293. package/dist/review/store.d.ts +36 -0
  294. package/dist/review/store.d.ts.map +1 -1
  295. package/dist/review/store.js +223 -8
  296. package/dist/review/store.js.map +1 -1
  297. package/dist/review/types.d.ts +4 -0
  298. package/dist/review/types.d.ts.map +1 -1
  299. package/dist/review/types.js.map +1 -1
  300. package/dist/secrets/routes.d.ts +9 -9
  301. package/dist/server/action-discovery.d.ts.map +1 -1
  302. package/dist/server/action-discovery.js +4 -0
  303. package/dist/server/action-discovery.js.map +1 -1
  304. package/dist/server/action-routes.d.ts +45 -0
  305. package/dist/server/action-routes.d.ts.map +1 -1
  306. package/dist/server/action-routes.js +71 -5
  307. package/dist/server/action-routes.js.map +1 -1
  308. package/dist/server/agent-chat/plugin-options.d.ts +16 -0
  309. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  310. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  311. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  312. package/dist/server/agent-chat-plugin.js +1 -0
  313. package/dist/server/agent-chat-plugin.js.map +1 -1
  314. package/dist/server/index.d.ts +3 -1
  315. package/dist/server/index.d.ts.map +1 -1
  316. package/dist/server/index.js +2 -0
  317. package/dist/server/index.js.map +1 -1
  318. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  319. package/dist/vite/action-types-plugin.js +4 -0
  320. package/dist/vite/action-types-plugin.js.map +1 -1
  321. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +48 -30
  322. package/docs/content/locales/ar-SA/template-plan.mdx +7 -0
  323. package/docs/content/locales/de-DE/pr-visual-recap.mdx +48 -30
  324. package/docs/content/locales/de-DE/template-plan.mdx +7 -0
  325. package/docs/content/locales/es-ES/pr-visual-recap.mdx +48 -30
  326. package/docs/content/locales/es-ES/template-plan.mdx +14 -0
  327. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +48 -30
  328. package/docs/content/locales/fr-FR/template-plan.mdx +7 -0
  329. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +48 -30
  330. package/docs/content/locales/hi-IN/template-plan.mdx +7 -0
  331. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +48 -30
  332. package/docs/content/locales/ja-JP/template-plan.mdx +7 -0
  333. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +48 -30
  334. package/docs/content/locales/ko-KR/template-plan.mdx +7 -0
  335. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +48 -30
  336. package/docs/content/locales/pt-BR/template-plan.mdx +14 -0
  337. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +48 -30
  338. package/docs/content/locales/zh-CN/template-plan.mdx +7 -0
  339. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +48 -30
  340. package/docs/content/locales/zh-TW/template-plan.mdx +7 -0
  341. package/docs/content/pr-visual-recap.mdx +50 -32
  342. package/docs/content/template-plan.mdx +14 -0
  343. package/docs/content/toolkit-comments-review.mdx +16 -2
  344. package/package.json +4 -1
  345. package/corpus/templates/forms/server/routes/api/forms/og/[slug]/og.png.get.ts +0 -45
@@ -0,0 +1,1077 @@
1
+ import {
2
+ buildReviewThreads,
3
+ ReviewCommentComposer,
4
+ useCreateReviewComment,
5
+ useReplyReviewComment,
6
+ useResolveReviewThread,
7
+ useReviewComments,
8
+ useT,
9
+ type ReviewThread,
10
+ } from "@agent-native/core/client";
11
+ import type { ReviewComment } from "@agent-native/core/review";
12
+ import {
13
+ IconArrowUp,
14
+ IconCircleCheck,
15
+ IconMessageCircle,
16
+ IconRobot,
17
+ IconX,
18
+ } from "@tabler/icons-react";
19
+ import {
20
+ useCallback,
21
+ useEffect,
22
+ useMemo,
23
+ useRef,
24
+ useState,
25
+ type ReactNode,
26
+ } from "react";
27
+ import { toast } from "sonner";
28
+
29
+ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
30
+ import { Button } from "@/components/ui/button";
31
+ import { Input } from "@/components/ui/input";
32
+ import { Spinner } from "@/components/ui/spinner";
33
+ import { cn } from "@/lib/utils";
34
+
35
+ import {
36
+ resolveReviewAnchor,
37
+ type DesignReviewAnchor,
38
+ type ReviewAnchorPoint,
39
+ } from "../../../shared/review-anchor";
40
+ import {
41
+ getReviewPopoverPlacement,
42
+ placeReviewDraftPin,
43
+ type ReviewDraftPin,
44
+ } from "./review-canvas-state";
45
+
46
+ export interface ReviewFocusRequest {
47
+ nonce: number;
48
+ anchor: unknown;
49
+ targetId?: string;
50
+ }
51
+
52
+ interface ReviewCanvasPinsProps {
53
+ active: boolean;
54
+ hidden?: boolean;
55
+ onClose: () => void;
56
+ canvasSelector?: string;
57
+ resourceType: string;
58
+ resourceId: string;
59
+ targetId: string;
60
+ canPost: boolean;
61
+ canResolve: boolean;
62
+ focusRequest?: ReviewFocusRequest | null;
63
+ onDispatchCommentToAgent?: (comment: ReviewComment) => void;
64
+ onSendThreadToAgent?: (thread: ReviewThread) => void;
65
+ sendingThreadId?: string | null;
66
+ }
67
+
68
+ interface PinPosition {
69
+ point: ReviewAnchorPoint;
70
+ source: "node" | "point";
71
+ }
72
+
73
+ interface ReviewFrameNodeGeometry {
74
+ rect: { left: number; top: number; width: number; height: number };
75
+ viewportWidth: number;
76
+ viewportHeight: number;
77
+ }
78
+
79
+ type ReviewPopoverPlacement = ReturnType<typeof getReviewPopoverPlacement>;
80
+
81
+ function findNodeElement(canvas: HTMLElement, nodeId: string): Element | null {
82
+ const iframe = canvas.querySelector<HTMLIFrameElement>(
83
+ "iframe[data-design-preview-iframe]",
84
+ );
85
+ try {
86
+ const document = iframe?.contentDocument;
87
+ if (!document) return null;
88
+ const escape = globalThis.CSS?.escape;
89
+ if (escape) {
90
+ return document.querySelector(
91
+ `[data-agent-native-node-id="${escape(nodeId)}"],` +
92
+ `[data-code-layer-id="${escape(nodeId)}"],` +
93
+ `[data-layer-id="${escape(nodeId)}"],` +
94
+ `[data-builder-id="${escape(nodeId)}"],#${escape(nodeId)}`,
95
+ );
96
+ }
97
+ return (
98
+ Array.from(
99
+ document.querySelectorAll(
100
+ "[data-agent-native-node-id],[data-code-layer-id],[data-layer-id],[data-builder-id],[id]",
101
+ ),
102
+ ).find((element) =>
103
+ [
104
+ "data-agent-native-node-id",
105
+ "data-code-layer-id",
106
+ "data-layer-id",
107
+ "data-builder-id",
108
+ "id",
109
+ ].some((attribute) => element.getAttribute(attribute) === nodeId),
110
+ ) ?? null
111
+ );
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+
117
+ function nodePoint(
118
+ canvas: HTMLElement,
119
+ nodeId: string,
120
+ frameGeometry?: ReviewFrameNodeGeometry,
121
+ ): ReviewAnchorPoint | null {
122
+ const element = findNodeElement(canvas, nodeId);
123
+ const iframe = canvas.querySelector<HTMLIFrameElement>(
124
+ "iframe[data-design-preview-iframe]",
125
+ );
126
+ if (!iframe) return null;
127
+ const canvasRect = canvas.getBoundingClientRect();
128
+ const iframeRect = iframe.getBoundingClientRect();
129
+ if (canvasRect.width <= 0 || canvasRect.height <= 0) return null;
130
+ const elementRect = element?.getBoundingClientRect() ?? frameGeometry?.rect;
131
+ if (!elementRect) return null;
132
+ const scaleX =
133
+ iframeRect.width /
134
+ Math.max(1, frameGeometry?.viewportWidth ?? iframe.clientWidth);
135
+ const scaleY =
136
+ iframeRect.height /
137
+ Math.max(1, frameGeometry?.viewportHeight ?? iframe.clientHeight);
138
+ return {
139
+ xPct:
140
+ ((iframeRect.left +
141
+ elementRect.left * scaleX +
142
+ (elementRect.width * scaleX) / 2 -
143
+ canvasRect.left) /
144
+ canvasRect.width) *
145
+ 100,
146
+ yPct:
147
+ ((iframeRect.top +
148
+ elementRect.top * scaleY +
149
+ (elementRect.height * scaleY) / 2 -
150
+ canvasRect.top) /
151
+ canvasRect.height) *
152
+ 100,
153
+ };
154
+ }
155
+
156
+ function elementAnchorAtPoint(
157
+ canvas: HTMLElement,
158
+ clientX: number,
159
+ clientY: number,
160
+ ): { nodeId?: string; layerName?: string; tagName?: string } {
161
+ const iframe = canvas.querySelector<HTMLIFrameElement>(
162
+ "iframe[data-design-preview-iframe]",
163
+ );
164
+ try {
165
+ const document = iframe?.contentDocument;
166
+ const iframeRect = iframe?.getBoundingClientRect();
167
+ if (!document || !iframe || !iframeRect) return {};
168
+ const scaleX = iframe.clientWidth / Math.max(1, iframeRect.width);
169
+ const scaleY = iframe.clientHeight / Math.max(1, iframeRect.height);
170
+ const element = document.elementFromPoint(
171
+ (clientX - iframeRect.left) * scaleX,
172
+ (clientY - iframeRect.top) * scaleY,
173
+ );
174
+ const anchor = element?.closest(
175
+ "[data-agent-native-node-id],[data-code-layer-id],[data-layer-id],[data-builder-id],[id]",
176
+ );
177
+ if (!anchor) return {};
178
+ const nodeId =
179
+ anchor.getAttribute("data-agent-native-node-id") ??
180
+ anchor.getAttribute("data-code-layer-id") ??
181
+ anchor.getAttribute("data-layer-id") ??
182
+ anchor.getAttribute("data-builder-id") ??
183
+ anchor.getAttribute("id") ??
184
+ undefined;
185
+ const layerName =
186
+ anchor.getAttribute("data-agent-native-layer-name") ?? undefined;
187
+ return {
188
+ ...(nodeId ? { nodeId } : {}),
189
+ ...(layerName ? { layerName } : {}),
190
+ tagName: anchor.tagName.toLowerCase(),
191
+ };
192
+ } catch {
193
+ return {};
194
+ }
195
+ }
196
+
197
+ function anchorAtPoint(
198
+ canvas: HTMLElement,
199
+ clientX: number,
200
+ clientY: number,
201
+ ): { anchor: DesignReviewAnchor; metadata: Record<string, unknown> } | null {
202
+ const rect = canvas.getBoundingClientRect();
203
+ if (rect.width <= 0 || rect.height <= 0) return null;
204
+ const xPct = ((clientX - rect.left) / rect.width) * 100;
205
+ const yPct = ((clientY - rect.top) / rect.height) * 100;
206
+ if (xPct < 0 || xPct > 100 || yPct < 0 || yPct > 100) return null; // i18n-ignore canvas coordinate guard
207
+ const element = elementAnchorAtPoint(canvas, clientX, clientY);
208
+ return {
209
+ anchor: {
210
+ ...(element.nodeId ? { nodeId: element.nodeId } : {}),
211
+ point: { xPct, yPct },
212
+ },
213
+ metadata: {
214
+ ...(element.layerName ? { layerName: element.layerName } : {}),
215
+ ...(element.tagName ? { tagName: element.tagName } : {}),
216
+ },
217
+ };
218
+ }
219
+
220
+ function resolvePinPosition(
221
+ canvas: HTMLElement,
222
+ anchor: unknown,
223
+ frameGeometry: Record<string, ReviewFrameNodeGeometry>,
224
+ ): PinPosition | null {
225
+ const resolved = resolveReviewAnchor(anchor, (nodeId) =>
226
+ nodePoint(canvas, nodeId, frameGeometry[nodeId]),
227
+ );
228
+ return resolved ? { point: resolved.point, source: resolved.source } : null;
229
+ }
230
+
231
+ export function ReviewCanvasPins({
232
+ active,
233
+ hidden = false,
234
+ onClose,
235
+ canvasSelector,
236
+ resourceType,
237
+ resourceId,
238
+ targetId,
239
+ canPost,
240
+ canResolve,
241
+ focusRequest,
242
+ onDispatchCommentToAgent,
243
+ onSendThreadToAgent,
244
+ sendingThreadId,
245
+ }: ReviewCanvasPinsProps) {
246
+ const t = useT();
247
+ const comments = useReviewComments(
248
+ {
249
+ resourceType,
250
+ resourceId,
251
+ targetId,
252
+ includeResolved: false,
253
+ limit: 500,
254
+ },
255
+ {
256
+ enabled: Boolean(!hidden && resourceType && resourceId && targetId),
257
+ },
258
+ );
259
+ const createComment = useCreateReviewComment();
260
+ const replyComment = useReplyReviewComment();
261
+ const resolveThread = useResolveReviewThread();
262
+ const [canvas, setCanvas] = useState<HTMLElement | null>(null);
263
+ const [layoutTick, setLayoutTick] = useState(0);
264
+ const [draftPin, setDraftPin] = useState<ReviewDraftPin | null>(null);
265
+ const [draftComposerOpen, setDraftComposerOpen] = useState(false);
266
+ const [activeThreadId, setActiveThreadId] = useState<string | null>(null);
267
+ const [replyDraft, setReplyDraft] = useState("");
268
+ const [frameNodeGeometry, setFrameNodeGeometry] = useState<
269
+ Record<string, ReviewFrameNodeGeometry>
270
+ >({});
271
+ const lastFocusNonceRef = useRef<number | null>(null);
272
+ const pendingFocusNonceRef = useRef<number | null>(null);
273
+ const frameCallbacksRef = useRef<
274
+ Map<string, (payload: Record<string, unknown>) => void>
275
+ >(new Map());
276
+
277
+ const cancelDraft = useCallback(() => {
278
+ setDraftPin(null);
279
+ setDraftComposerOpen(false);
280
+ }, []);
281
+
282
+ const threads = useMemo(
283
+ () => buildReviewThreads(comments.data?.comments ?? []),
284
+ [comments.data?.comments],
285
+ );
286
+
287
+ useEffect(() => {
288
+ if (!canvasSelector) {
289
+ setCanvas(null);
290
+ return;
291
+ }
292
+ const findCanvas = () => {
293
+ setCanvas(document.querySelector(canvasSelector) as HTMLElement | null);
294
+ };
295
+ findCanvas();
296
+ const timer = window.setTimeout(findCanvas, 60);
297
+ return () => window.clearTimeout(timer);
298
+ }, [canvasSelector, targetId]);
299
+
300
+ useEffect(() => {
301
+ if (!canvas) return;
302
+ let animationFrame = 0;
303
+ const bump = () => {
304
+ if (animationFrame) return;
305
+ animationFrame = window.requestAnimationFrame(() => {
306
+ animationFrame = 0;
307
+ setLayoutTick((current) => current + 1);
308
+ });
309
+ };
310
+ const resizeObserver = new ResizeObserver(bump);
311
+ resizeObserver.observe(canvas);
312
+ const iframe = canvas.querySelector<HTMLIFrameElement>(
313
+ "iframe[data-design-preview-iframe]",
314
+ );
315
+ if (iframe) resizeObserver.observe(iframe);
316
+ window.addEventListener("resize", bump);
317
+ window.addEventListener("scroll", bump, { capture: true, passive: true });
318
+ iframe?.addEventListener("load", bump);
319
+ return () => {
320
+ if (animationFrame) window.cancelAnimationFrame(animationFrame);
321
+ resizeObserver.disconnect();
322
+ window.removeEventListener("resize", bump);
323
+ window.removeEventListener("scroll", bump, true);
324
+ iframe?.removeEventListener("load", bump);
325
+ };
326
+ }, [canvas]);
327
+
328
+ useEffect(() => {
329
+ if (!canvas) return;
330
+ const iframe = canvas.querySelector<HTMLIFrameElement>(
331
+ "iframe[data-design-preview-iframe]",
332
+ );
333
+ if (!iframe) return;
334
+ const handleMessage = (event: MessageEvent) => {
335
+ if (event.source !== iframe.contentWindow || !event.data) return;
336
+ if (event.data.type === "agent-native:review-layout") {
337
+ setLayoutTick((current) => current + 1);
338
+ return;
339
+ }
340
+ if (event.data.type === "agent-native:review-node-rects-result") {
341
+ const viewportWidth = Number(event.data.viewportWidth);
342
+ const viewportHeight = Number(event.data.viewportHeight);
343
+ const rects = event.data.rects;
344
+ if (
345
+ !rects ||
346
+ typeof rects !== "object" ||
347
+ !Number.isFinite(viewportWidth) ||
348
+ !Number.isFinite(viewportHeight)
349
+ ) {
350
+ return;
351
+ }
352
+ const next: Record<string, ReviewFrameNodeGeometry> = {};
353
+ for (const [nodeId, rawRect] of Object.entries(rects)) {
354
+ if (!rawRect || typeof rawRect !== "object") continue;
355
+ const rect = rawRect as Record<string, unknown>;
356
+ const left = Number(rect.left);
357
+ const top = Number(rect.top);
358
+ const width = Number(rect.width);
359
+ const height = Number(rect.height);
360
+ if (![left, top, width, height].every(Number.isFinite)) continue;
361
+ next[nodeId] = {
362
+ rect: { left, top, width, height },
363
+ viewportWidth,
364
+ viewportHeight,
365
+ };
366
+ }
367
+ setFrameNodeGeometry(next);
368
+ return;
369
+ }
370
+ const correlationId = event.data.correlationId;
371
+ if (typeof correlationId !== "string") return;
372
+ const callback = frameCallbacksRef.current.get(correlationId);
373
+ if (!callback) return;
374
+ frameCallbacksRef.current.delete(correlationId);
375
+ callback(event.data as Record<string, unknown>);
376
+ };
377
+ window.addEventListener("message", handleMessage);
378
+ return () => window.removeEventListener("message", handleMessage);
379
+ }, [canvas]);
380
+
381
+ const anchoredNodeIds = useMemo(() => {
382
+ const nodeIds = new Set<string>();
383
+ for (const thread of threads) {
384
+ const resolved = resolveReviewAnchor(thread.root.anchor, () => null);
385
+ if (resolved?.anchor.nodeId) nodeIds.add(resolved.anchor.nodeId);
386
+ }
387
+ const draft = draftPin
388
+ ? resolveReviewAnchor(draftPin.anchor, () => null)
389
+ : null;
390
+ if (draft?.anchor.nodeId) nodeIds.add(draft.anchor.nodeId);
391
+ return [...nodeIds].sort();
392
+ }, [draftPin, threads]);
393
+
394
+ useEffect(() => {
395
+ if (!canvas || anchoredNodeIds.length === 0) {
396
+ setFrameNodeGeometry((current) =>
397
+ Object.keys(current).length > 0 ? {} : current,
398
+ );
399
+ return;
400
+ }
401
+ const iframe = canvas.querySelector<HTMLIFrameElement>(
402
+ "iframe[data-design-preview-iframe]",
403
+ );
404
+ iframe?.contentWindow?.postMessage(
405
+ {
406
+ type: "agent-native:review-node-rects",
407
+ correlationId: crypto.randomUUID(),
408
+ nodeIds: anchoredNodeIds,
409
+ },
410
+ "*",
411
+ );
412
+ }, [anchoredNodeIds, canvas, layoutTick, targetId]);
413
+
414
+ const focusAnchor = useCallback(
415
+ (anchor: unknown, nonce: number): boolean => {
416
+ if (!canvas) return false;
417
+ const resolved = resolveReviewAnchor(anchor, (nodeId) =>
418
+ nodePoint(canvas, nodeId, frameNodeGeometry[nodeId]),
419
+ );
420
+ if (!resolved) return true;
421
+ if (resolved.anchor.nodeId) {
422
+ const element = findNodeElement(canvas, resolved.anchor.nodeId);
423
+ if (element instanceof HTMLElement || element instanceof SVGElement) {
424
+ element.scrollIntoView({ block: "center", inline: "center" });
425
+ const previousBoxShadow = element.style.boxShadow;
426
+ element.style.boxShadow =
427
+ "0 0 0 2px var(--design-editor-accent-color, #2563eb)";
428
+ window.setTimeout(() => {
429
+ element.style.boxShadow = previousBoxShadow;
430
+ }, 700);
431
+ return true;
432
+ }
433
+ if (pendingFocusNonceRef.current === nonce) return false;
434
+ const iframe = canvas.querySelector<HTMLIFrameElement>(
435
+ "iframe[data-design-preview-iframe]",
436
+ );
437
+ if (!iframe?.contentWindow) return false;
438
+ const correlationId = crypto.randomUUID();
439
+ pendingFocusNonceRef.current = nonce;
440
+ frameCallbacksRef.current.set(correlationId, (payload) => {
441
+ pendingFocusNonceRef.current = null;
442
+ if (payload.focused === true) {
443
+ lastFocusNonceRef.current = nonce;
444
+ setLayoutTick((current) => current + 1);
445
+ }
446
+ });
447
+ iframe.contentWindow.postMessage(
448
+ {
449
+ type: "agent-native:review-focus",
450
+ correlationId,
451
+ nodeId: resolved.anchor.nodeId,
452
+ },
453
+ "*",
454
+ );
455
+ return false;
456
+ }
457
+ return true;
458
+ },
459
+ [canvas, frameNodeGeometry],
460
+ );
461
+
462
+ useEffect(() => {
463
+ if (
464
+ !focusRequest ||
465
+ focusRequest.nonce === lastFocusNonceRef.current ||
466
+ (focusRequest.targetId && focusRequest.targetId !== targetId)
467
+ )
468
+ return;
469
+ if (focusAnchor(focusRequest.anchor, focusRequest.nonce)) {
470
+ lastFocusNonceRef.current = focusRequest.nonce;
471
+ }
472
+ }, [focusAnchor, focusRequest, layoutTick, targetId]);
473
+
474
+ useEffect(() => {
475
+ if (!active) cancelDraft();
476
+ }, [active, cancelDraft]);
477
+
478
+ useEffect(() => {
479
+ if (!active && !activeThreadId && !draftComposerOpen) return;
480
+ const onKeyDown = (event: KeyboardEvent) => {
481
+ if (event.key !== "Escape") return;
482
+ if (draftComposerOpen && draftPin) {
483
+ cancelDraft();
484
+ return;
485
+ }
486
+ if (activeThreadId) {
487
+ setActiveThreadId(null);
488
+ setReplyDraft("");
489
+ return;
490
+ }
491
+ if (active) onClose();
492
+ };
493
+ window.addEventListener("keydown", onKeyDown);
494
+ return () => window.removeEventListener("keydown", onKeyDown);
495
+ }, [
496
+ active,
497
+ activeThreadId,
498
+ cancelDraft,
499
+ draftComposerOpen,
500
+ draftPin,
501
+ onClose,
502
+ ]);
503
+
504
+ useEffect(() => {
505
+ cancelDraft();
506
+ setActiveThreadId(null);
507
+ setReplyDraft("");
508
+ setFrameNodeGeometry({});
509
+ frameCallbacksRef.current.clear();
510
+ pendingFocusNonceRef.current = null;
511
+ }, [cancelDraft, resourceId, targetId]);
512
+
513
+ useEffect(() => {
514
+ if (!hidden) return;
515
+ cancelDraft();
516
+ setActiveThreadId(null);
517
+ setReplyDraft("");
518
+ if (active) onClose();
519
+ }, [active, cancelDraft, hidden, onClose]);
520
+
521
+ const dropPin = useCallback(
522
+ (clientX: number, clientY: number) => {
523
+ if (!canvas || !canPost) return;
524
+ const next = anchorAtPoint(canvas, clientX, clientY);
525
+ if (!next) return;
526
+ setActiveThreadId(null);
527
+ setReplyDraft("");
528
+ setDraftPin((current) =>
529
+ placeReviewDraftPin(current, {
530
+ id: crypto.randomUUID(),
531
+ anchor: next.anchor,
532
+ metadata: next.metadata,
533
+ }),
534
+ );
535
+ setDraftComposerOpen(true);
536
+
537
+ if (next.anchor.nodeId) return;
538
+ const iframe = canvas.querySelector<HTMLIFrameElement>(
539
+ "iframe[data-design-preview-iframe]",
540
+ );
541
+ const iframeRect = iframe?.getBoundingClientRect();
542
+ if (!iframe?.contentWindow || !iframeRect?.width || !iframeRect.height) {
543
+ return;
544
+ }
545
+ const correlationId = crypto.randomUUID();
546
+ frameCallbacksRef.current.set(correlationId, (payload) => {
547
+ const nodeId =
548
+ typeof payload.nodeId === "string" ? payload.nodeId : undefined;
549
+ const layerName =
550
+ typeof payload.layerName === "string" ? payload.layerName : undefined;
551
+ const tagName =
552
+ typeof payload.tagName === "string" ? payload.tagName : undefined;
553
+ if (!nodeId && !layerName && !tagName) return;
554
+ setDraftPin((current) => {
555
+ if (
556
+ !current ||
557
+ current.anchor.point.xPct !== next.anchor.point.xPct ||
558
+ current.anchor.point.yPct !== next.anchor.point.yPct
559
+ ) {
560
+ return current;
561
+ }
562
+ return {
563
+ ...current,
564
+ anchor: {
565
+ ...(nodeId ? { nodeId } : {}),
566
+ point: current.anchor.point,
567
+ },
568
+ metadata: {
569
+ ...current.metadata,
570
+ ...(layerName ? { layerName } : {}),
571
+ ...(tagName ? { tagName } : {}),
572
+ },
573
+ };
574
+ });
575
+ });
576
+ window.setTimeout(
577
+ () => frameCallbacksRef.current.delete(correlationId),
578
+ 2_000,
579
+ );
580
+ iframe.contentWindow.postMessage(
581
+ {
582
+ type: "agent-native:review-anchor-at-point",
583
+ correlationId,
584
+ x:
585
+ (clientX - iframeRect.left) *
586
+ (iframe.clientWidth / iframeRect.width),
587
+ y:
588
+ (clientY - iframeRect.top) *
589
+ (iframe.clientHeight / iframeRect.height),
590
+ },
591
+ "*",
592
+ );
593
+ },
594
+ [canPost, canvas],
595
+ );
596
+
597
+ const postDraft = useCallback(
598
+ (pin: ReviewDraftPin) => {
599
+ const body = pin.draft.trim();
600
+ if (!body || createComment.isPending) return;
601
+ createComment.mutate(
602
+ {
603
+ resourceType,
604
+ resourceId,
605
+ targetId,
606
+ kind: "annotation",
607
+ anchor: pin.anchor,
608
+ body,
609
+ resolutionTarget: pin.resolutionTarget,
610
+ metadata: pin.metadata,
611
+ },
612
+ {
613
+ onSuccess: (comment) => {
614
+ cancelDraft();
615
+ if (pin.resolutionTarget === "agent") {
616
+ onDispatchCommentToAgent?.(comment);
617
+ }
618
+ },
619
+ onError: () => toast.error(t("review.postFailed")),
620
+ },
621
+ );
622
+ },
623
+ [
624
+ cancelDraft,
625
+ createComment,
626
+ onDispatchCommentToAgent,
627
+ resourceId,
628
+ resourceType,
629
+ t,
630
+ targetId,
631
+ ],
632
+ );
633
+
634
+ if (hidden || !canvas) return null;
635
+ const rect = canvas.getBoundingClientRect();
636
+ void layoutTick;
637
+
638
+ const openThreads = threads.filter(
639
+ (thread) => thread.root.status === "open" && thread.root.anchor,
640
+ );
641
+ const draftPinPosition = draftPin
642
+ ? resolvePinPosition(canvas, draftPin.anchor, frameNodeGeometry)
643
+ : null;
644
+
645
+ return (
646
+ <>
647
+ {active && canPost ? (
648
+ <div
649
+ data-review-click-plane
650
+ className="fixed z-40 cursor-crosshair"
651
+ style={{
652
+ left: rect.left,
653
+ top: rect.top,
654
+ width: rect.width,
655
+ height: rect.height,
656
+ }}
657
+ onClick={(event) => {
658
+ event.preventDefault();
659
+ event.stopPropagation();
660
+ dropPin(event.clientX, event.clientY);
661
+ }}
662
+ />
663
+ ) : null}
664
+ {active && canPost ? (
665
+ <div className="pointer-events-none fixed left-1/2 top-16 z-[45] flex -translate-x-1/2 items-center gap-2 rounded-full border border-border bg-popover px-3 py-1.5 text-xs shadow-lg">
666
+ <IconMessageCircle className="size-3.5 text-primary" />
667
+ {t("review.clickToPin")}
668
+ <span className="text-[10px] text-muted-foreground">
669
+ {t("review.escToExit")}
670
+ </span>
671
+ </div>
672
+ ) : null}
673
+ {openThreads.map((thread, index) => {
674
+ const position = resolvePinPosition(
675
+ canvas,
676
+ thread.root.anchor,
677
+ frameNodeGeometry,
678
+ );
679
+ if (!position) return null;
680
+ return (
681
+ <ReviewPin
682
+ key={thread.root.threadId}
683
+ index={index}
684
+ canvasRect={rect}
685
+ point={position.point}
686
+ onClick={() => {
687
+ if (!draftPin?.draft.trim()) setDraftPin(null);
688
+ setDraftComposerOpen(false);
689
+ setReplyDraft("");
690
+ setActiveThreadId(thread.root.threadId);
691
+ }}
692
+ active={activeThreadId === thread.root.threadId}
693
+ >
694
+ {activeThreadId === thread.root.threadId ? (
695
+ <ReviewThreadPopover
696
+ thread={thread}
697
+ canResolve={canResolve}
698
+ sending={sendingThreadId === thread.root.threadId}
699
+ canReply={canPost}
700
+ placement={getReviewPopoverPlacement(position.point)}
701
+ replyDraft={replyDraft}
702
+ onReplyDraftChange={setReplyDraft}
703
+ onClose={() => {
704
+ setActiveThreadId(null);
705
+ setReplyDraft("");
706
+ }}
707
+ onReply={() => {
708
+ const body = replyDraft.trim();
709
+ if (!body) return;
710
+ replyComment.mutate(
711
+ {
712
+ resourceType,
713
+ resourceId,
714
+ commentId: thread.root.id,
715
+ body,
716
+ },
717
+ {
718
+ onSuccess: () => setReplyDraft(""),
719
+ onError: () => toast.error(t("review.replyFailed")),
720
+ },
721
+ );
722
+ }}
723
+ onResolve={() =>
724
+ resolveThread.mutate(
725
+ {
726
+ resourceType,
727
+ resourceId,
728
+ threadId: thread.root.threadId,
729
+ },
730
+ {
731
+ onSuccess: () => setActiveThreadId(null),
732
+ onError: () => toast.error(t("review.resolveFailed")),
733
+ },
734
+ )
735
+ }
736
+ onSendToAgent={
737
+ onSendThreadToAgent &&
738
+ (thread.root.resolutionTarget === "human" ||
739
+ Boolean(thread.root.consumedAt))
740
+ ? () => onSendThreadToAgent(thread)
741
+ : undefined
742
+ }
743
+ replying={replyComment.isPending}
744
+ resolving={resolveThread.isPending}
745
+ />
746
+ ) : null}
747
+ </ReviewPin>
748
+ );
749
+ })}
750
+ {draftPin && draftPinPosition ? (
751
+ <ReviewPin
752
+ key={draftPin.id}
753
+ index={openThreads.length}
754
+ canvasRect={rect}
755
+ point={draftPinPosition.point}
756
+ draft
757
+ onClick={() => {
758
+ setActiveThreadId(null);
759
+ setReplyDraft("");
760
+ setDraftComposerOpen(true);
761
+ }}
762
+ active={draftComposerOpen}
763
+ >
764
+ {draftComposerOpen ? (
765
+ <DraftComposer
766
+ value={draftPin.draft}
767
+ onChange={(value) =>
768
+ setDraftPin((current) =>
769
+ current ? { ...current, draft: value } : current,
770
+ )
771
+ }
772
+ onCancel={cancelDraft}
773
+ onSubmit={(resolutionTarget) => {
774
+ setDraftPin((current) =>
775
+ current ? { ...current, resolutionTarget } : current,
776
+ );
777
+ postDraft({ ...draftPin, resolutionTarget });
778
+ }}
779
+ resolutionTarget={draftPin.resolutionTarget}
780
+ placement={getReviewPopoverPlacement(draftPinPosition.point)}
781
+ submitting={createComment.isPending}
782
+ />
783
+ ) : null}
784
+ </ReviewPin>
785
+ ) : null}
786
+ </>
787
+ );
788
+ }
789
+
790
+ function ReviewPin({
791
+ index,
792
+ point,
793
+ canvasRect,
794
+ active,
795
+ draft = false,
796
+ onClick,
797
+ children,
798
+ }: {
799
+ index: number;
800
+ point: ReviewAnchorPoint;
801
+ canvasRect: DOMRect;
802
+ active: boolean;
803
+ draft?: boolean;
804
+ onClick: () => void;
805
+ children?: ReactNode;
806
+ }) {
807
+ const t = useT();
808
+ return (
809
+ <div
810
+ data-review-popover
811
+ className="fixed z-[45]"
812
+ style={{
813
+ left: canvasRect.left + (point.xPct / 100) * canvasRect.width,
814
+ top: canvasRect.top + (point.yPct / 100) * canvasRect.height,
815
+ }}
816
+ >
817
+ <button
818
+ type="button"
819
+ data-review-pin
820
+ className={cn(
821
+ "flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full rounded-bl-none border text-[10px] font-semibold shadow-md transition-transform hover:scale-110",
822
+ draft
823
+ ? "border-primary bg-primary text-primary-foreground"
824
+ : "border-amber-200 bg-amber-400 text-amber-950",
825
+ active && "ring-2 ring-primary/40",
826
+ )}
827
+ onClick={(event) => {
828
+ event.stopPropagation();
829
+ onClick();
830
+ }}
831
+ aria-label={t("review.commentNumber", { count: index + 1 })}
832
+ >
833
+ {index + 1}
834
+ </button>
835
+ {children}
836
+ </div>
837
+ );
838
+ }
839
+
840
+ function DraftComposer({
841
+ value,
842
+ onChange,
843
+ onCancel,
844
+ onSubmit,
845
+ resolutionTarget,
846
+ placement,
847
+ submitting,
848
+ }: {
849
+ value: string;
850
+ onChange: (value: string) => void;
851
+ onCancel: () => void;
852
+ onSubmit: (target: "agent" | "human") => void;
853
+ resolutionTarget: "agent" | "human";
854
+ placement: ReviewPopoverPlacement;
855
+ submitting: boolean;
856
+ }) {
857
+ const t = useT();
858
+ return (
859
+ <div
860
+ data-review-popover
861
+ className={cn(
862
+ "absolute z-[260] w-80 max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-border bg-popover shadow-xl",
863
+ placement.horizontal === "end" ? "right-3" : "left-3",
864
+ placement.vertical === "above" ? "bottom-3" : "top-1",
865
+ )}
866
+ onClick={(event) => event.stopPropagation()}
867
+ >
868
+ <div className="flex items-center justify-between px-3 pb-2 pt-3">
869
+ <span className="text-sm font-medium">{t("review.newComment")}</span>
870
+ <Button
871
+ variant="ghost"
872
+ size="icon"
873
+ className="size-7 text-muted-foreground"
874
+ disabled={submitting}
875
+ onClick={onCancel}
876
+ aria-label={t("designEditor.close")}
877
+ >
878
+ <IconX className="size-3.5" />
879
+ </Button>
880
+ </div>
881
+ <ReviewCommentComposer
882
+ className="px-3 pb-3"
883
+ autoFocus
884
+ value={value}
885
+ disabled={submitting}
886
+ onChange={onChange}
887
+ onSubmit={onSubmit}
888
+ submittingTarget={submitting ? resolutionTarget : null}
889
+ showAgentAction
890
+ placeholder={t("review.placeholder")}
891
+ commentLabel={t("review.commentMode")}
892
+ agentLabel={t("review.sendToAgent")}
893
+ submitOnEnter
894
+ onEscape={onCancel}
895
+ />
896
+ </div>
897
+ );
898
+ }
899
+
900
+ function ReviewThreadPopover({
901
+ thread,
902
+ canResolve,
903
+ sending,
904
+ replying,
905
+ resolving,
906
+ canReply,
907
+ placement,
908
+ replyDraft,
909
+ onReplyDraftChange,
910
+ onClose,
911
+ onReply,
912
+ onResolve,
913
+ onSendToAgent,
914
+ }: {
915
+ thread: ReviewThread;
916
+ canResolve: boolean;
917
+ sending: boolean;
918
+ replying: boolean;
919
+ resolving: boolean;
920
+ canReply: boolean;
921
+ placement: ReviewPopoverPlacement;
922
+ replyDraft: string;
923
+ onReplyDraftChange: (value: string) => void;
924
+ onClose: () => void;
925
+ onReply: () => void;
926
+ onResolve: () => void;
927
+ onSendToAgent?: () => void;
928
+ }) {
929
+ const t = useT();
930
+ const rootAuthor = reviewAuthorLabel(thread.root, t("review.reviewer"));
931
+ return (
932
+ <div
933
+ data-review-popover
934
+ className={cn(
935
+ "absolute z-[260] w-80 max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-border bg-popover shadow-xl",
936
+ placement.horizontal === "end" ? "right-3" : "left-3",
937
+ placement.vertical === "above" ? "bottom-3" : "top-1",
938
+ )}
939
+ onClick={(event) => event.stopPropagation()}
940
+ >
941
+ <div className="flex items-start gap-2.5 p-3">
942
+ <Avatar className="size-7 shrink-0">
943
+ <AvatarFallback className="text-[10px] font-semibold text-muted-foreground">
944
+ {reviewAuthorInitials(rootAuthor)}
945
+ </AvatarFallback>
946
+ </Avatar>
947
+ <div className="min-w-0 flex-1">
948
+ <div className="truncate text-xs font-medium text-muted-foreground">
949
+ {rootAuthor}
950
+ </div>
951
+ <p className="mt-1 whitespace-pre-wrap break-words text-sm leading-5 text-foreground">
952
+ {thread.root.body}
953
+ </p>
954
+ </div>
955
+ <Button
956
+ variant="ghost"
957
+ size="icon"
958
+ className="-me-1 -mt-1 size-7 shrink-0 text-muted-foreground"
959
+ onClick={onClose}
960
+ aria-label={t("designEditor.close")}
961
+ >
962
+ <IconX className="size-3.5" />
963
+ </Button>
964
+ </div>
965
+ {thread.replies.length ? (
966
+ <div className="ms-12 me-3 mb-3 flex flex-col gap-2 border-s border-border ps-3">
967
+ {thread.replies.map((reply) => (
968
+ <div key={reply.id} className="min-w-0">
969
+ <div className="truncate text-[10px] font-medium text-muted-foreground">
970
+ {reviewAuthorLabel(reply, t("review.reviewer"))}
971
+ </div>
972
+ <p className="mt-0.5 whitespace-pre-wrap break-words text-xs leading-5 text-foreground/90">
973
+ {reply.body}
974
+ </p>
975
+ </div>
976
+ ))}
977
+ </div>
978
+ ) : null}
979
+ {canReply || (canResolve && thread.root.status === "open") ? (
980
+ <div className="border-t border-border bg-muted/25 p-2.5">
981
+ {canReply ? (
982
+ <div className="flex items-center gap-1.5">
983
+ <Input
984
+ value={replyDraft}
985
+ disabled={replying || resolving}
986
+ onChange={(event) =>
987
+ onReplyDraftChange(event.currentTarget.value)
988
+ }
989
+ placeholder={t("review.replyPlaceholder")}
990
+ className="h-8 min-w-0 flex-1 bg-background text-xs"
991
+ onKeyDown={(event) => {
992
+ if (event.key === "Enter" && replyDraft.trim() && !replying) {
993
+ event.preventDefault();
994
+ onReply();
995
+ }
996
+ if (event.key === "Escape") {
997
+ event.stopPropagation();
998
+ event.preventDefault();
999
+ onClose();
1000
+ }
1001
+ }}
1002
+ />
1003
+ <Button
1004
+ type="button"
1005
+ size="icon"
1006
+ className="size-8 shrink-0"
1007
+ disabled={!replyDraft.trim() || replying}
1008
+ onClick={onReply}
1009
+ aria-label={t("review.reply")}
1010
+ >
1011
+ {replying ? (
1012
+ <Spinner className="size-3.5" />
1013
+ ) : (
1014
+ <IconArrowUp className="size-3.5" />
1015
+ )}
1016
+ </Button>
1017
+ </div>
1018
+ ) : null}
1019
+ {canResolve && thread.root.status === "open" ? (
1020
+ <div className="mt-2 flex items-center gap-1">
1021
+ {onSendToAgent ? (
1022
+ <Button
1023
+ type="button"
1024
+ variant="ghost"
1025
+ size="sm"
1026
+ className="h-7 gap-1.5 px-2 text-xs text-primary hover:text-primary"
1027
+ disabled={sending || resolving}
1028
+ onClick={onSendToAgent}
1029
+ >
1030
+ {sending ? (
1031
+ <Spinner className="size-3.5" />
1032
+ ) : (
1033
+ <IconRobot className="size-3.5" />
1034
+ )}
1035
+ {sending
1036
+ ? t("review.sendingToAgent")
1037
+ : t("review.sendToAgent")}
1038
+ </Button>
1039
+ ) : null}
1040
+ <div className="min-w-0 flex-1" />
1041
+ <Button
1042
+ type="button"
1043
+ variant="ghost"
1044
+ size="sm"
1045
+ className="h-7 gap-1.5 px-2 text-xs text-muted-foreground"
1046
+ disabled={resolving || sending}
1047
+ onClick={onResolve}
1048
+ >
1049
+ {resolving ? (
1050
+ <Spinner className="size-3.5" />
1051
+ ) : (
1052
+ <IconCircleCheck className="size-3.5" />
1053
+ )}
1054
+ {resolving ? t("review.resolving") : t("review.resolve")}
1055
+ </Button>
1056
+ </div>
1057
+ ) : null}
1058
+ </div>
1059
+ ) : null}
1060
+ </div>
1061
+ );
1062
+ }
1063
+
1064
+ function reviewAuthorLabel(comment: ReviewComment, fallback: string): string {
1065
+ return comment.authorName ?? comment.authorEmail ?? fallback;
1066
+ }
1067
+
1068
+ function reviewAuthorInitials(value: string): string {
1069
+ const localPart = value.split("@")[0]?.trim() ?? "";
1070
+ const initials = localPart
1071
+ .split(/[\s._+-]+/)
1072
+ .filter(Boolean)
1073
+ .slice(0, 2)
1074
+ .map((part) => part[0]?.toUpperCase())
1075
+ .join("");
1076
+ return initials || "R";
1077
+ }