@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
@@ -1,14 +1,31 @@
1
+ import { Avatar, AvatarFallback } from "@agent-native/toolkit/ui/avatar";
2
+ import { Button } from "@agent-native/toolkit/ui/button";
1
3
  import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from "@agent-native/toolkit/ui/dropdown-menu";
9
+ import { Input } from "@agent-native/toolkit/ui/input";
10
+ import { Skeleton } from "@agent-native/toolkit/ui/skeleton";
11
+ import { Spinner } from "@agent-native/toolkit/ui/spinner";
12
+ import {
13
+ IconCircleCheck,
14
+ IconDots,
2
15
  IconMessageCircle,
3
- IconPlus,
4
16
  IconSend,
5
17
  IconTrash,
6
- IconCircleCheck,
18
+ IconX,
7
19
  } from "@tabler/icons-react";
8
- import { useMemo, useState } from "react";
20
+ import { useMemo, useState, type ReactNode } from "react";
9
21
 
10
- import type { ReviewComment } from "../../review/types.js";
22
+ import type {
23
+ ReviewComment,
24
+ ReviewResolutionTarget,
25
+ } from "../../review/types.js";
26
+ import { useFormatters } from "../i18n.js";
11
27
  import { cn } from "../utils.js";
28
+ import { ReviewCommentComposer } from "./ReviewCommentComposer.js";
12
29
  import { ReviewStatusBadge } from "./ReviewStatusBadge.js";
13
30
  import {
14
31
  useCreateReviewComment,
@@ -23,6 +40,14 @@ export interface ReviewThread {
23
40
  replies: ReviewComment[];
24
41
  }
25
42
 
43
+ export type ReviewThreadCapability =
44
+ | boolean
45
+ | ((thread: ReviewThread) => boolean);
46
+
47
+ export type ReviewCommentCapability =
48
+ | boolean
49
+ | ((comment: ReviewComment, thread: ReviewThread) => boolean);
50
+
26
51
  export interface ReviewThreadPanelProps {
27
52
  resourceType: string;
28
53
  resourceId: string;
@@ -31,9 +56,33 @@ export interface ReviewThreadPanelProps {
31
56
  className?: string;
32
57
  includeResolved?: boolean;
33
58
  showComposer?: boolean;
59
+ showHeader?: boolean;
60
+ variant?: "card" | "plain";
34
61
  placeholder?: string;
35
62
  emptyState?: string;
63
+ loadingLabel?: string;
64
+ replyLabel?: string;
65
+ replyPlaceholder?: string;
66
+ cancelReplyLabel?: string;
67
+ resolveLabel?: string;
68
+ deleteLabel?: string;
69
+ moreActionsLabel?: string;
70
+ resolvedLabel?: string;
71
+ reviewerLabel?: string;
36
72
  onSelectThread?: (thread: ReviewThread) => void;
73
+ onCommentCreated?: (comment: ReviewComment) => void;
74
+ /** Allow signed-in commenters to reply. Omitted capabilities fail closed. */
75
+ canReply?: ReviewThreadCapability;
76
+ /** Allow editors to resolve a thread. Omitted capabilities fail closed. */
77
+ canResolve?: ReviewThreadCapability;
78
+ /** Allow deletion only for comments the caller has authorized. */
79
+ canDeleteComment?: ReviewCommentCapability;
80
+ /** Extra per-thread controls rendered next to reply/resolve/delete. */
81
+ renderThreadActions?: (thread: ReviewThread) => ReactNode;
82
+ /** Show a separate agent-submit action when the host supports agent routing. */
83
+ showComposerTargetPicker?: boolean;
84
+ composerCommentLabel?: string;
85
+ composerAgentLabel?: string;
37
86
  }
38
87
 
39
88
  export function ReviewThreadPanel({
@@ -44,12 +93,35 @@ export function ReviewThreadPanel({
44
93
  className,
45
94
  includeResolved = true,
46
95
  showComposer = true,
96
+ showHeader = true,
97
+ variant = "card",
47
98
  placeholder = "Add a comment...",
48
99
  emptyState = "No review comments yet.",
100
+ loadingLabel = "Loading comments",
101
+ replyLabel = "Reply",
102
+ replyPlaceholder = "Reply...",
103
+ cancelReplyLabel = "Cancel reply",
104
+ resolveLabel = "Resolve",
105
+ deleteLabel = "Delete comment",
106
+ moreActionsLabel = "More actions",
107
+ resolvedLabel = "Resolved",
108
+ reviewerLabel = "Reviewer",
49
109
  onSelectThread,
110
+ onCommentCreated,
111
+ canReply = false,
112
+ canResolve = false,
113
+ canDeleteComment = false,
114
+ renderThreadActions,
115
+ showComposerTargetPicker = false,
116
+ composerCommentLabel = "Comment",
117
+ composerAgentLabel = "Send to agent",
50
118
  }: ReviewThreadPanelProps) {
51
119
  const [draft, setDraft] = useState("");
120
+ const [submittingTarget, setSubmittingTarget] =
121
+ useState<ReviewResolutionTarget | null>(null);
122
+ const [replyingThreadId, setReplyingThreadId] = useState<string | null>(null);
52
123
  const [replyDrafts, setReplyDrafts] = useState<Record<string, string>>({});
124
+ const { formatDate } = useFormatters();
53
125
  const comments = useReviewComments({
54
126
  resourceType,
55
127
  resourceId,
@@ -65,153 +137,291 @@ export function ReviewThreadPanel({
65
137
  [comments.data?.comments],
66
138
  );
67
139
 
140
+ const submitDraft = (resolutionTarget: ReviewResolutionTarget) => {
141
+ const body = draft.trim();
142
+ if (!body || createComment.isPending) return;
143
+ setSubmittingTarget(resolutionTarget);
144
+ createComment.mutate(
145
+ {
146
+ resourceType,
147
+ resourceId,
148
+ targetId,
149
+ body,
150
+ ...(showComposerTargetPicker ? { resolutionTarget } : {}),
151
+ },
152
+ {
153
+ onSuccess: (comment) => {
154
+ setDraft("");
155
+ onCommentCreated?.(comment);
156
+ },
157
+ onSettled: () => setSubmittingTarget(null),
158
+ },
159
+ );
160
+ };
161
+
68
162
  return (
69
163
  <section
70
164
  className={cn(
71
- "rounded-lg border border-border bg-card text-card-foreground",
165
+ "@container/review overflow-hidden text-card-foreground",
166
+ variant === "card"
167
+ ? "rounded-lg border border-border bg-card"
168
+ : "bg-transparent",
72
169
  className,
73
170
  )}
74
171
  >
75
- <div className="flex items-center justify-between gap-3 border-b border-border px-4 py-3">
76
- <div className="flex items-center gap-2">
77
- <IconMessageCircle className="h-4 w-4 text-muted-foreground" />
78
- <h2 className="text-sm font-medium">{title}</h2>
172
+ {showHeader ? (
173
+ <div className="flex items-center justify-between gap-3 border-b border-border px-3 py-2.5">
174
+ <div className="flex min-w-0 items-center gap-2">
175
+ <IconMessageCircle className="size-4 shrink-0 text-muted-foreground" />
176
+ <h2 className="truncate text-sm font-medium">{title}</h2>
177
+ </div>
178
+ <ReviewStatusBadge
179
+ status={comments.data?.reviewStatus?.status}
180
+ className="shrink-0"
181
+ />
79
182
  </div>
80
- <ReviewStatusBadge status={comments.data?.reviewStatus?.status} />
81
- </div>
183
+ ) : null}
82
184
 
83
185
  {showComposer ? (
84
- <form
85
- className="border-b border-border p-3"
86
- onSubmit={(event) => {
87
- event.preventDefault();
88
- const body = draft.trim();
89
- if (!body) return;
90
- createComment.mutate(
91
- { resourceType, resourceId, targetId, body },
92
- { onSuccess: () => setDraft("") },
93
- );
94
- }}
95
- >
96
- <textarea
97
- value={draft}
98
- onChange={(event) => setDraft(event.currentTarget.value)}
99
- placeholder={placeholder}
100
- className="min-h-20 w-full resize-y rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring"
101
- />
102
- <div className="mt-2 flex justify-end">
103
- <button
104
- type="submit"
105
- disabled={!draft.trim() || createComment.isPending}
106
- className="inline-flex h-8 items-center gap-1.5 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
107
- >
108
- <IconPlus className="h-3.5 w-3.5" />
109
- Comment
110
- </button>
111
- </div>
112
- </form>
186
+ <ReviewCommentComposer
187
+ className="border-b border-border px-3 py-3"
188
+ value={draft}
189
+ onChange={setDraft}
190
+ onSubmit={submitDraft}
191
+ submittingTarget={submittingTarget}
192
+ disabled={createComment.isPending}
193
+ showAgentAction={showComposerTargetPicker}
194
+ placeholder={placeholder}
195
+ commentLabel={composerCommentLabel}
196
+ agentLabel={composerAgentLabel}
197
+ />
113
198
  ) : null}
114
199
 
115
200
  <div className="divide-y divide-border">
116
201
  {comments.isLoading ? (
117
- <div className="px-4 py-6 text-sm text-muted-foreground">
118
- Loading comments...
202
+ <div
203
+ className="flex flex-col gap-3 px-3 py-4"
204
+ aria-label={loadingLabel}
205
+ >
206
+ <div className="flex items-center gap-2.5">
207
+ <Skeleton className="size-7 rounded-full" />
208
+ <div className="flex min-w-0 flex-1 flex-col gap-1.5">
209
+ <Skeleton className="h-3 w-2/5" />
210
+ <Skeleton className="h-3 w-4/5" />
211
+ </div>
212
+ </div>
213
+ <Skeleton className="h-8 w-full" />
119
214
  </div>
120
215
  ) : threads.length ? (
121
- threads.map((thread) => (
122
- <article
123
- key={thread.root.threadId}
124
- className="px-4 py-3"
125
- onClick={() => onSelectThread?.(thread)}
126
- >
127
- <CommentBubble comment={thread.root} />
128
- {thread.replies.length ? (
129
- <div className="mt-3 space-y-3 border-l border-border pl-4">
130
- {thread.replies.map((reply) => (
131
- <CommentBubble key={reply.id} comment={reply} compact />
132
- ))}
133
- </div>
134
- ) : null}
135
- <form
136
- className="mt-3 flex gap-2"
137
- onSubmit={(event) => {
138
- event.preventDefault();
139
- const body = replyDrafts[thread.root.id]?.trim();
140
- if (!body) return;
141
- replyComment.mutate(
142
- {
143
- resourceType,
144
- resourceId,
145
- commentId: thread.root.id,
146
- body,
147
- },
148
- {
149
- onSuccess: () =>
150
- setReplyDrafts((current) => ({
151
- ...current,
152
- [thread.root.id]: "",
153
- })),
154
- },
155
- );
156
- }}
216
+ threads.map((thread) => {
217
+ const replyDraft = replyDrafts[thread.root.id] ?? "";
218
+ const replying = replyingThreadId === thread.root.threadId;
219
+ const threadIsOpen = thread.root.status === "open";
220
+ const replyAllowed =
221
+ threadIsOpen && capabilityAllowsThread(canReply, thread);
222
+ const resolveAllowed =
223
+ threadIsOpen && capabilityAllowsThread(canResolve, thread);
224
+ const deleteAllowed = capabilityAllowsComment(
225
+ canDeleteComment,
226
+ thread.root,
227
+ thread,
228
+ );
229
+ const threadActions = renderThreadActions?.(thread);
230
+ const hasActions =
231
+ replyAllowed ||
232
+ resolveAllowed ||
233
+ deleteAllowed ||
234
+ Boolean(threadActions);
235
+ return (
236
+ <article
237
+ key={thread.root.threadId}
238
+ className={cn(
239
+ "group/thread px-3 py-3 transition-colors",
240
+ onSelectThread && "cursor-pointer hover:bg-muted/30",
241
+ )}
242
+ onClick={() => onSelectThread?.(thread)}
157
243
  >
158
- <input
159
- value={replyDrafts[thread.root.id] ?? ""}
160
- onChange={(event) =>
161
- setReplyDrafts((current) => ({
162
- ...current,
163
- [thread.root.id]: event.currentTarget.value,
164
- }))
165
- }
166
- placeholder="Reply..."
167
- className="h-8 min-w-0 flex-1 rounded-md border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring"
244
+ <CommentBubble
245
+ comment={thread.root}
246
+ resolvedLabel={resolvedLabel}
247
+ reviewerLabel={reviewerLabel}
248
+ formatDate={formatDate}
168
249
  />
169
- <button
170
- type="submit"
171
- disabled={
172
- !replyDrafts[thread.root.id]?.trim() ||
173
- replyComment.isPending
174
- }
175
- className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-input hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
176
- aria-label="Reply"
177
- >
178
- <IconSend className="h-3.5 w-3.5" />
179
- </button>
180
- {thread.root.status !== "resolved" ? (
181
- <button
182
- type="button"
183
- className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-input hover:bg-accent"
184
- aria-label="Resolve thread"
185
- onClick={() =>
186
- resolveThread.mutate({
187
- resourceType,
188
- resourceId,
189
- threadId: thread.root.threadId,
190
- })
191
- }
250
+ {thread.replies.length ? (
251
+ <div className="ms-3 mt-3 flex flex-col gap-3 border-s border-border ps-3">
252
+ {thread.replies.map((reply) => (
253
+ <CommentBubble
254
+ key={reply.id}
255
+ comment={reply}
256
+ compact
257
+ resolvedLabel={resolvedLabel}
258
+ reviewerLabel={reviewerLabel}
259
+ formatDate={formatDate}
260
+ />
261
+ ))}
262
+ </div>
263
+ ) : null}
264
+
265
+ {replying && replyAllowed ? (
266
+ <form
267
+ className="mt-3 flex min-w-0 items-center gap-1.5"
268
+ onClick={(event) => event.stopPropagation()}
269
+ onSubmit={(event) => {
270
+ event.preventDefault();
271
+ const body = replyDraft.trim();
272
+ if (!body || replyComment.isPending) return;
273
+ replyComment.mutate(
274
+ {
275
+ resourceType,
276
+ resourceId,
277
+ commentId: thread.root.id,
278
+ body,
279
+ },
280
+ {
281
+ onSuccess: () => {
282
+ setReplyDrafts((current) => ({
283
+ ...current,
284
+ [thread.root.id]: "",
285
+ }));
286
+ setReplyingThreadId(null);
287
+ },
288
+ },
289
+ );
290
+ }}
291
+ >
292
+ <Input
293
+ autoFocus
294
+ value={replyDraft}
295
+ onChange={(event) =>
296
+ setReplyDrafts((current) => ({
297
+ ...current,
298
+ [thread.root.id]: event.currentTarget.value,
299
+ }))
300
+ }
301
+ onKeyDown={(event) => {
302
+ if (event.key === "Escape") {
303
+ event.preventDefault();
304
+ event.stopPropagation();
305
+ setReplyingThreadId(null);
306
+ }
307
+ }}
308
+ placeholder={replyPlaceholder}
309
+ className="h-8 min-w-0 flex-1 text-sm"
310
+ />
311
+ <Button
312
+ type="submit"
313
+ size="icon"
314
+ variant="outline"
315
+ className="size-8 shrink-0"
316
+ disabled={!replyDraft.trim() || replyComment.isPending}
317
+ aria-label={replyLabel}
318
+ >
319
+ {replyComment.isPending ? (
320
+ <Spinner className="size-3.5" />
321
+ ) : (
322
+ <IconSend className="size-3.5" />
323
+ )}
324
+ </Button>
325
+ <Button
326
+ type="button"
327
+ size="icon"
328
+ variant="ghost"
329
+ className="size-8 shrink-0"
330
+ aria-label={cancelReplyLabel}
331
+ onClick={() => setReplyingThreadId(null)}
332
+ >
333
+ <IconX className="size-3.5" />
334
+ </Button>
335
+ </form>
336
+ ) : hasActions ? (
337
+ <div
338
+ className="mt-2.5 flex min-w-0 items-center gap-0.5"
339
+ onClick={(event) => event.stopPropagation()}
192
340
  >
193
- <IconCircleCheck className="h-3.5 w-3.5" />
194
- </button>
341
+ {replyAllowed ? (
342
+ <Button
343
+ type="button"
344
+ variant="ghost"
345
+ size="sm"
346
+ className="h-7 min-w-0 gap-1.5 px-1.5 text-xs @xs/review:px-2"
347
+ aria-label={replyLabel}
348
+ onClick={() =>
349
+ setReplyingThreadId(thread.root.threadId)
350
+ }
351
+ >
352
+ <IconMessageCircle className="size-3.5" />
353
+ <span className="hidden @2xs/review:inline">
354
+ {replyLabel}
355
+ </span>
356
+ </Button>
357
+ ) : null}
358
+ <div className="ms-auto flex min-w-0 items-center gap-0.5">
359
+ {threadActions}
360
+ {resolveAllowed ? (
361
+ <Button
362
+ type="button"
363
+ variant="ghost"
364
+ size="sm"
365
+ className="h-7 min-w-0 gap-1.5 px-1.5 text-xs @xs/review:px-2"
366
+ disabled={resolveThread.isPending}
367
+ aria-label={resolveLabel}
368
+ onClick={() =>
369
+ resolveThread.mutate({
370
+ resourceType,
371
+ resourceId,
372
+ threadId: thread.root.threadId,
373
+ })
374
+ }
375
+ >
376
+ {resolveThread.isPending ? (
377
+ <Spinner className="size-3.5" />
378
+ ) : (
379
+ <IconCircleCheck className="size-3.5" />
380
+ )}
381
+ <span className="hidden @xs/review:inline">
382
+ {resolveLabel}
383
+ </span>
384
+ </Button>
385
+ ) : null}
386
+ {deleteAllowed ? (
387
+ <DropdownMenu>
388
+ <DropdownMenuTrigger asChild>
389
+ <Button
390
+ type="button"
391
+ variant="ghost"
392
+ size="icon"
393
+ className="size-7 shrink-0"
394
+ aria-label={moreActionsLabel}
395
+ >
396
+ <IconDots className="size-4" />
397
+ </Button>
398
+ </DropdownMenuTrigger>
399
+ <DropdownMenuContent align="end" className="w-44">
400
+ <DropdownMenuItem
401
+ disabled={deleteComment.isPending}
402
+ className="text-destructive focus:text-destructive"
403
+ onSelect={() =>
404
+ deleteComment.mutate({
405
+ resourceType,
406
+ resourceId,
407
+ commentId: thread.root.id,
408
+ })
409
+ }
410
+ >
411
+ <IconTrash className="size-4" />
412
+ {deleteLabel}
413
+ </DropdownMenuItem>
414
+ </DropdownMenuContent>
415
+ </DropdownMenu>
416
+ ) : null}
417
+ </div>
418
+ </div>
195
419
  ) : null}
196
- <button
197
- type="button"
198
- className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-input text-muted-foreground hover:bg-accent"
199
- aria-label="Delete comment"
200
- onClick={() =>
201
- deleteComment.mutate({
202
- resourceType,
203
- resourceId,
204
- commentId: thread.root.id,
205
- })
206
- }
207
- >
208
- <IconTrash className="h-3.5 w-3.5" />
209
- </button>
210
- </form>
211
- </article>
212
- ))
420
+ </article>
421
+ );
422
+ })
213
423
  ) : (
214
- <div className="px-4 py-6 text-sm text-muted-foreground">
424
+ <div className="px-4 py-8 text-center text-sm text-muted-foreground">
215
425
  {emptyState}
216
426
  </div>
217
427
  )}
@@ -243,36 +453,158 @@ export function buildReviewThreads(comments: ReviewComment[]): ReviewThread[] {
243
453
  function CommentBubble({
244
454
  comment,
245
455
  compact = false,
456
+ resolvedLabel,
457
+ reviewerLabel,
458
+ formatDate,
246
459
  }: {
247
460
  comment: ReviewComment;
248
461
  compact?: boolean;
462
+ resolvedLabel: string;
463
+ reviewerLabel: string;
464
+ formatDate: ReturnType<typeof useFormatters>["formatDate"];
249
465
  }) {
466
+ const author = comment.authorName ?? comment.authorEmail ?? reviewerLabel;
467
+ const resolutionNote =
468
+ comment.status === "resolved" ? getReviewResolutionNote(comment) : null;
469
+ const bodyIsResolutionNote =
470
+ resolutionNote !== null &&
471
+ resolutionNote === comment.body.trim() &&
472
+ isResolutionNoteMetadata(comment.metadata);
250
473
  return (
251
- <div className={compact ? "text-sm" : ""}>
252
- <div className="flex flex-wrap items-center gap-2">
253
- <span className="text-sm font-medium">
254
- {comment.authorName ?? comment.authorEmail ?? "Reviewer"}
255
- </span>
256
- <span className="text-xs text-muted-foreground">
257
- {formatCommentDate(comment.createdAt)}
258
- </span>
259
- {comment.status === "resolved" ? (
260
- <span className="rounded-full bg-emerald-50 px-2 py-0.5 text-xs text-emerald-700">
261
- Resolved
262
- </span>
474
+ <div className="flex min-w-0 items-start gap-2.5">
475
+ <Avatar className={compact ? "size-5" : "size-7"}>
476
+ <AvatarFallback className="text-[10px] font-semibold text-muted-foreground">
477
+ {authorInitials(author)}
478
+ </AvatarFallback>
479
+ </Avatar>
480
+ <div className="min-w-0 flex-1">
481
+ <div className="flex min-w-0 items-center gap-2">
482
+ <span className="truncate text-sm font-medium">{author}</span>
483
+ <time
484
+ className="shrink-0 text-[11px] text-muted-foreground"
485
+ dateTime={comment.createdAt}
486
+ >
487
+ {formatCommentDate(comment.createdAt, formatDate)}
488
+ </time>
489
+ {comment.status === "resolved" ? (
490
+ <span className="hidden shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground @xs/review:inline-flex">
491
+ {resolvedLabel}
492
+ </span>
493
+ ) : null}
494
+ </div>
495
+ {!bodyIsResolutionNote ? (
496
+ <p
497
+ className={cn(
498
+ "mt-1 whitespace-pre-wrap break-words text-foreground",
499
+ compact ? "text-xs leading-5" : "text-sm leading-5",
500
+ )}
501
+ >
502
+ {comment.body}
503
+ </p>
504
+ ) : null}
505
+ {resolutionNote ? (
506
+ <div
507
+ className="mt-1.5 flex min-w-0 items-start gap-1.5 rounded-md bg-muted/60 px-2 py-1.5 text-muted-foreground"
508
+ aria-label={resolvedLabel}
509
+ >
510
+ <IconCircleCheck className="mt-0.5 size-3.5 shrink-0" />
511
+ <p className="min-w-0 whitespace-pre-wrap break-words text-xs leading-4">
512
+ {resolutionNote}
513
+ </p>
514
+ </div>
263
515
  ) : null}
264
516
  </div>
265
- <p className="mt-1 whitespace-pre-wrap text-sm text-foreground">
266
- {comment.body}
267
- </p>
268
517
  </div>
269
518
  );
270
519
  }
271
520
 
272
- function formatCommentDate(value: string): string {
521
+ function capabilityAllowsThread(
522
+ capability: ReviewThreadCapability,
523
+ thread: ReviewThread,
524
+ ): boolean {
525
+ return typeof capability === "function" ? capability(thread) : capability;
526
+ }
527
+
528
+ function capabilityAllowsComment(
529
+ capability: ReviewCommentCapability,
530
+ comment: ReviewComment,
531
+ thread: ReviewThread,
532
+ ): boolean {
533
+ return typeof capability === "function"
534
+ ? capability(comment, thread)
535
+ : capability;
536
+ }
537
+
538
+ function getReviewResolutionNote(comment: ReviewComment): string | null {
539
+ const direct = comment.resolutionNote;
540
+ const metadata = comment.metadata;
541
+ const nestedResolution = metadata?.resolution;
542
+ const nestedNote =
543
+ isRecord(nestedResolution) && typeof nestedResolution.note === "string"
544
+ ? nestedResolution.note
545
+ : null;
546
+ const metadataNote =
547
+ typeof metadata?.resolutionNote === "string"
548
+ ? metadata.resolutionNote
549
+ : typeof metadata?.resolvedNote === "string"
550
+ ? metadata.resolvedNote
551
+ : isResolutionNoteMetadata(metadata) &&
552
+ typeof metadata?.note === "string"
553
+ ? metadata.note
554
+ : isResolutionNoteMetadata(metadata)
555
+ ? comment.body
556
+ : null;
557
+ const candidate =
558
+ typeof direct === "string" ? direct : (metadataNote ?? nestedNote);
559
+ const normalized = candidate?.trim();
560
+ return normalized || null;
561
+ }
562
+
563
+ function isResolutionNoteMetadata(
564
+ metadata: Record<string, unknown> | null,
565
+ ): boolean {
566
+ const marker = metadata?.type ?? metadata?.kind;
567
+ return (
568
+ marker === "resolution" ||
569
+ marker === "resolution_note" ||
570
+ marker === "resolution-note"
571
+ );
572
+ }
573
+
574
+ function isRecord(value: unknown): value is Record<string, unknown> {
575
+ return typeof value === "object" && value !== null && !Array.isArray(value);
576
+ }
577
+
578
+ function authorInitials(value: string): string {
579
+ const normalized = value.split("@")[0]?.trim() ?? "";
580
+ const parts = normalized.split(/[\s._+-]+/).filter(Boolean);
581
+ const initials = parts
582
+ .slice(0, 2)
583
+ .map((part) => part[0]?.toUpperCase())
584
+ .join("");
585
+ return initials || "R";
586
+ }
587
+
588
+ function formatCommentDate(
589
+ value: string,
590
+ formatDate: ReturnType<typeof useFormatters>["formatDate"],
591
+ ): string {
273
592
  const date = new Date(value);
274
- if (Number.isNaN(date.getTime())) {
275
- return value;
593
+ if (Number.isNaN(date.getTime())) return value;
594
+ const now = new Date();
595
+ const sameDay =
596
+ date.getFullYear() === now.getFullYear() &&
597
+ date.getMonth() === now.getMonth() &&
598
+ date.getDate() === now.getDate();
599
+ if (sameDay) {
600
+ return formatDate(date, { hour: "numeric", minute: "2-digit" });
276
601
  }
277
- return date.toLocaleString();
602
+ if (date.getFullYear() === now.getFullYear()) {
603
+ return formatDate(date, { month: "short", day: "numeric" });
604
+ }
605
+ return formatDate(date, {
606
+ month: "short",
607
+ day: "numeric",
608
+ year: "numeric",
609
+ });
278
610
  }