@agent-native/core 0.98.4 → 0.98.6

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 (314) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +28 -0
  3. package/corpus/core/docs/content/durable-background-runs.mdx +37 -0
  4. package/corpus/core/docs/content/external-agents.mdx +62 -7
  5. package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +38 -7
  6. package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +2 -0
  7. package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +39 -7
  8. package/corpus/core/docs/content/locales/de-DE/tracking.mdx +2 -0
  9. package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +39 -7
  10. package/corpus/core/docs/content/locales/es-ES/tracking.mdx +2 -0
  11. package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +39 -7
  12. package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +4 -2
  13. package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +38 -7
  14. package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +2 -0
  15. package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +38 -7
  16. package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +2 -0
  17. package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +38 -7
  18. package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +2 -0
  19. package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +39 -7
  20. package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +2 -0
  21. package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +37 -7
  22. package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +2 -0
  23. package/corpus/core/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
  24. package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +37 -7
  25. package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +2 -0
  26. package/corpus/core/docs/content/tracking.mdx +2 -0
  27. package/corpus/core/package.json +1 -1
  28. package/corpus/core/src/a2a/handlers.ts +95 -21
  29. package/corpus/core/src/a2a/task-store.ts +65 -3
  30. package/corpus/core/src/agent/durable-background.ts +16 -16
  31. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +25 -8
  32. package/corpus/core/src/agent/engine/anthropic-engine.ts +17 -3
  33. package/corpus/core/src/agent/engine/output-tokens.ts +6 -6
  34. package/corpus/core/src/agent/production-agent.ts +200 -27
  35. package/corpus/core/src/agent/run-manager.ts +81 -1
  36. package/corpus/core/src/agent/run-store.ts +301 -41
  37. package/corpus/core/src/client/AgentPanel.tsx +6 -2
  38. package/corpus/core/src/client/AssistantChat.tsx +364 -69
  39. package/corpus/core/src/client/agent-chat-adapter.ts +169 -1
  40. package/corpus/core/src/client/chat/message-components.tsx +3 -0
  41. package/corpus/core/src/client/chat/tool-call-display.tsx +20 -36
  42. package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +7 -0
  43. package/corpus/core/src/client/extensions/AgentNativeExtensionFrame.tsx +2 -0
  44. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +2 -0
  45. package/corpus/core/src/client/extensions/ExtensionEditor.tsx +9 -2
  46. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +2 -0
  47. package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +2 -0
  48. package/corpus/core/src/client/extensions/portable-extension.ts +2 -0
  49. package/corpus/core/src/client/session-replay.ts +361 -29
  50. package/corpus/core/src/client/sse-event-processor.ts +43 -1
  51. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +1 -1
  52. package/corpus/core/src/demo/config.ts +6 -6
  53. package/corpus/core/src/demo/fetch-interceptor.ts +13 -4
  54. package/corpus/core/src/demo/redact.ts +13 -149
  55. package/corpus/core/src/extensions/html-shell.ts +3 -0
  56. package/corpus/core/src/extensions/session-replay-iframe.ts +131 -0
  57. package/corpus/core/src/integrations/adapters/slack.ts +123 -13
  58. package/corpus/core/src/integrations/identity-links-store.ts +210 -0
  59. package/corpus/core/src/integrations/identity.ts +132 -0
  60. package/corpus/core/src/integrations/index.ts +6 -0
  61. package/corpus/core/src/integrations/plugin.ts +58 -1
  62. package/corpus/core/src/integrations/types.ts +7 -0
  63. package/corpus/core/src/mcp/build-server.ts +127 -22
  64. package/corpus/core/src/mcp/external-agent-policy.ts +18 -0
  65. package/corpus/core/src/mcp/index.ts +1 -0
  66. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -20
  67. package/corpus/core/src/server/agent-chat/plugin-options.ts +12 -1
  68. package/corpus/core/src/server/agent-chat/script-entries.ts +20 -3
  69. package/corpus/core/src/server/agent-chat-plugin.ts +3 -0
  70. package/corpus/core/src/session-replay-iframe-protocol.ts +58 -0
  71. package/corpus/core/src/shared/reasoning-effort.ts +35 -0
  72. package/corpus/core/src/styles/agent-conversation.css +26 -0
  73. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
  74. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  75. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +30 -21
  76. package/corpus/templates/analytics/AGENTS.md +27 -0
  77. package/corpus/templates/analytics/actions/create-session-replay-agent-link.ts +0 -1
  78. package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
  79. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
  80. package/corpus/templates/analytics/actions/get-session-replay-timeline.ts +40 -0
  81. package/corpus/templates/analytics/actions/list-error-issues.ts +13 -0
  82. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
  83. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -0
  84. package/corpus/templates/analytics/app/global.css +2 -2
  85. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +41 -4
  86. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +52 -203
  87. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +69 -16
  88. package/corpus/templates/analytics/changelog/2026-07-12-analytics-chat-retries-before-showing-a-generic-no-data-mess.md +6 -0
  89. package/corpus/templates/analytics/changelog/2026-07-12-analytics-sessions-search-no-longer-loses-fast-keystrokes-an.md +6 -0
  90. package/corpus/templates/analytics/changelog/2026-07-12-chart-selection-respects-chat-state.md +6 -0
  91. package/corpus/templates/analytics/changelog/2026-07-12-connected-external-agents-can-now-look-up-sessions-error-iss.md +6 -0
  92. package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-anonymizes-error-reporting-emails.md +6 -0
  93. package/corpus/templates/analytics/changelog/2026-07-12-session-identities-stay-visible-when-demo-mode-is-off.md +6 -0
  94. package/corpus/templates/analytics/changelog/2026-07-12-session-replays-show-iframe-content.md +6 -0
  95. package/corpus/templates/analytics/netlify.toml +0 -3
  96. package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +14 -0
  97. package/corpus/templates/analytics/server/lib/error-capture.ts +101 -2
  98. package/corpus/templates/analytics/server/lib/real-data-actions.ts +24 -0
  99. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +49 -17
  100. package/corpus/templates/analytics/server/lib/session-replay.ts +18 -16
  101. package/corpus/templates/analytics/server/plugins/agent-chat.ts +43 -6
  102. package/corpus/templates/analytics/server/plugins/db.ts +15 -0
  103. package/corpus/templates/assets/app/global.css +1 -1
  104. package/corpus/templates/brain/app/global.css +2 -2
  105. package/corpus/templates/brain/changelog/2026-07-12-fix-long-response-progress.md +6 -0
  106. package/corpus/templates/forms/app/components/CloudUpgrade.tsx +1 -1
  107. package/corpus/templates/forms/app/components/ThemeToggle.tsx +22 -6
  108. package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +5 -5
  109. package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +3 -3
  110. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +16 -16
  111. package/corpus/templates/forms/app/global.css +164 -13
  112. package/corpus/templates/forms/app/pages/AskPage.tsx +35 -13
  113. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +143 -67
  114. package/corpus/templates/forms/app/pages/FormFillPage.tsx +44 -11
  115. package/corpus/templates/forms/app/pages/FormsListPage.tsx +42 -24
  116. package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +28 -15
  117. package/corpus/templates/forms/app/pages/ResponsesPage.tsx +46 -20
  118. package/corpus/templates/forms/app/routes/_app.settings.tsx +1 -1
  119. package/corpus/templates/forms/changelog/2026-07-12-ask-forms-suggestions-now-sit-beneath-the-composer-for-a-tig.md +6 -0
  120. package/corpus/templates/forms/changelog/2026-07-12-builder-response-tables-now-fill-the-view-without-a-redundan.md +6 -0
  121. package/corpus/templates/forms/changelog/2026-07-12-form-builder-surfaces-are-calmer-fields-stay-stable-on-hover.md +6 -0
  122. package/corpus/templates/forms/changelog/2026-07-12-response-tables-now-fill-the-available-pane-with-a-flexible-.md +6 -0
  123. package/corpus/templates/forms/changelog/2026-07-12-smoother-more-tactile-controls-throughout-animated-icon-and-.md +6 -0
  124. package/corpus/templates/mail/AGENTS.md +3 -1
  125. package/corpus/templates/mail/app/components/email/EmailThread.tsx +21 -13
  126. package/corpus/templates/mail/app/components/email/email-iframe-document.ts +18 -0
  127. package/corpus/templates/mail/server/lib/mail-integrations.ts +3 -0
  128. package/dist/a2a/handlers.d.ts.map +1 -1
  129. package/dist/a2a/handlers.js +84 -15
  130. package/dist/a2a/handlers.js.map +1 -1
  131. package/dist/a2a/task-store.d.ts +20 -1
  132. package/dist/a2a/task-store.d.ts.map +1 -1
  133. package/dist/a2a/task-store.js +57 -4
  134. package/dist/a2a/task-store.js.map +1 -1
  135. package/dist/agent/durable-background.d.ts +10 -10
  136. package/dist/agent/durable-background.d.ts.map +1 -1
  137. package/dist/agent/durable-background.js +16 -16
  138. package/dist/agent/durable-background.js.map +1 -1
  139. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  140. package/dist/agent/engine/ai-sdk-engine.js +19 -8
  141. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  142. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  143. package/dist/agent/engine/anthropic-engine.js +11 -3
  144. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  145. package/dist/agent/engine/output-tokens.js +6 -6
  146. package/dist/agent/engine/output-tokens.js.map +1 -1
  147. package/dist/agent/production-agent.d.ts +52 -0
  148. package/dist/agent/production-agent.d.ts.map +1 -1
  149. package/dist/agent/production-agent.js +154 -26
  150. package/dist/agent/production-agent.js.map +1 -1
  151. package/dist/agent/run-manager.d.ts +9 -0
  152. package/dist/agent/run-manager.d.ts.map +1 -1
  153. package/dist/agent/run-manager.js +71 -1
  154. package/dist/agent/run-manager.js.map +1 -1
  155. package/dist/agent/run-store.d.ts +9 -0
  156. package/dist/agent/run-store.d.ts.map +1 -1
  157. package/dist/agent/run-store.js +243 -48
  158. package/dist/agent/run-store.js.map +1 -1
  159. package/dist/client/AgentPanel.d.ts.map +1 -1
  160. package/dist/client/AgentPanel.js +6 -2
  161. package/dist/client/AgentPanel.js.map +1 -1
  162. package/dist/client/AssistantChat.d.ts +21 -0
  163. package/dist/client/AssistantChat.d.ts.map +1 -1
  164. package/dist/client/AssistantChat.js +298 -67
  165. package/dist/client/AssistantChat.js.map +1 -1
  166. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  167. package/dist/client/agent-chat-adapter.js +146 -1
  168. package/dist/client/agent-chat-adapter.js.map +1 -1
  169. package/dist/client/chat/message-components.d.ts.map +1 -1
  170. package/dist/client/chat/message-components.js +2 -1
  171. package/dist/client/chat/message-components.js.map +1 -1
  172. package/dist/client/chat/tool-call-display.d.ts +3 -1
  173. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  174. package/dist/client/chat/tool-call-display.js +13 -37
  175. package/dist/client/chat/tool-call-display.js.map +1 -1
  176. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +1 -0
  177. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
  178. package/dist/client/conversation/use-near-bottom-autoscroll.js +6 -0
  179. package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
  180. package/dist/client/extensions/AgentNativeExtensionFrame.d.ts.map +1 -1
  181. package/dist/client/extensions/AgentNativeExtensionFrame.js +2 -1
  182. package/dist/client/extensions/AgentNativeExtensionFrame.js.map +1 -1
  183. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  184. package/dist/client/extensions/EmbeddedExtension.js +2 -1
  185. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  186. package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
  187. package/dist/client/extensions/ExtensionEditor.js +5 -2
  188. package/dist/client/extensions/ExtensionEditor.js.map +1 -1
  189. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  190. package/dist/client/extensions/ExtensionViewer.js +2 -1
  191. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  192. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
  193. package/dist/client/extensions/InlineExtensionFrame.js +2 -1
  194. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
  195. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  196. package/dist/client/extensions/portable-extension.js +2 -0
  197. package/dist/client/extensions/portable-extension.js.map +1 -1
  198. package/dist/client/session-replay.d.ts.map +1 -1
  199. package/dist/client/session-replay.js +308 -29
  200. package/dist/client/session-replay.js.map +1 -1
  201. package/dist/client/sse-event-processor.d.ts +5 -0
  202. package/dist/client/sse-event-processor.d.ts.map +1 -1
  203. package/dist/client/sse-event-processor.js +37 -1
  204. package/dist/client/sse-event-processor.js.map +1 -1
  205. package/dist/collab/routes.d.ts +1 -1
  206. package/dist/demo/actions/toggle-demo-mode.js +1 -1
  207. package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
  208. package/dist/demo/config.js +6 -6
  209. package/dist/demo/config.js.map +1 -1
  210. package/dist/demo/fetch-interceptor.d.ts.map +1 -1
  211. package/dist/demo/fetch-interceptor.js +13 -4
  212. package/dist/demo/fetch-interceptor.js.map +1 -1
  213. package/dist/demo/redact.d.ts +7 -7
  214. package/dist/demo/redact.d.ts.map +1 -1
  215. package/dist/demo/redact.js +8 -145
  216. package/dist/demo/redact.js.map +1 -1
  217. package/dist/extensions/html-shell.d.ts.map +1 -1
  218. package/dist/extensions/html-shell.js +2 -0
  219. package/dist/extensions/html-shell.js.map +1 -1
  220. package/dist/extensions/session-replay-iframe.d.ts +10 -0
  221. package/dist/extensions/session-replay-iframe.d.ts.map +1 -0
  222. package/dist/extensions/session-replay-iframe.js +121 -0
  223. package/dist/extensions/session-replay-iframe.js.map +1 -0
  224. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  225. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  226. package/dist/integrations/adapters/slack.js +98 -13
  227. package/dist/integrations/adapters/slack.js.map +1 -1
  228. package/dist/integrations/identity-links-store.d.ts +23 -0
  229. package/dist/integrations/identity-links-store.d.ts.map +1 -0
  230. package/dist/integrations/identity-links-store.js +158 -0
  231. package/dist/integrations/identity-links-store.js.map +1 -0
  232. package/dist/integrations/identity.d.ts +11 -0
  233. package/dist/integrations/identity.d.ts.map +1 -0
  234. package/dist/integrations/identity.js +100 -0
  235. package/dist/integrations/identity.js.map +1 -0
  236. package/dist/integrations/index.d.ts +2 -0
  237. package/dist/integrations/index.d.ts.map +1 -1
  238. package/dist/integrations/index.js +2 -0
  239. package/dist/integrations/index.js.map +1 -1
  240. package/dist/integrations/plugin.d.ts.map +1 -1
  241. package/dist/integrations/plugin.js +43 -1
  242. package/dist/integrations/plugin.js.map +1 -1
  243. package/dist/integrations/types.d.ts +6 -0
  244. package/dist/integrations/types.d.ts.map +1 -1
  245. package/dist/integrations/types.js.map +1 -1
  246. package/dist/mcp/build-server.d.ts +9 -2
  247. package/dist/mcp/build-server.d.ts.map +1 -1
  248. package/dist/mcp/build-server.js +97 -20
  249. package/dist/mcp/build-server.js.map +1 -1
  250. package/dist/mcp/external-agent-policy.d.ts +19 -0
  251. package/dist/mcp/external-agent-policy.d.ts.map +1 -0
  252. package/dist/mcp/external-agent-policy.js +2 -0
  253. package/dist/mcp/external-agent-policy.js.map +1 -0
  254. package/dist/mcp/index.d.ts +1 -0
  255. package/dist/mcp/index.d.ts.map +1 -1
  256. package/dist/mcp/index.js.map +1 -1
  257. package/dist/notifications/routes.d.ts +1 -1
  258. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  259. package/dist/scripts/agent-engines/list-agent-engines.js +1 -19
  260. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  261. package/dist/server/agent-chat/plugin-options.d.ts +11 -1
  262. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  263. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  264. package/dist/server/agent-chat/script-entries.d.ts.map +1 -1
  265. package/dist/server/agent-chat/script-entries.js +16 -2
  266. package/dist/server/agent-chat/script-entries.js.map +1 -1
  267. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  268. package/dist/server/agent-chat-plugin.js +3 -0
  269. package/dist/server/agent-chat-plugin.js.map +1 -1
  270. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  271. package/dist/server/transcribe-voice.d.ts +1 -1
  272. package/dist/session-replay-iframe-protocol.d.ts +36 -0
  273. package/dist/session-replay-iframe-protocol.d.ts.map +1 -0
  274. package/dist/session-replay-iframe-protocol.js +24 -0
  275. package/dist/session-replay-iframe-protocol.js.map +1 -0
  276. package/dist/shared/reasoning-effort.d.ts +11 -0
  277. package/dist/shared/reasoning-effort.d.ts.map +1 -1
  278. package/dist/shared/reasoning-effort.js +36 -0
  279. package/dist/shared/reasoning-effort.js.map +1 -1
  280. package/dist/styles/agent-conversation.css +26 -0
  281. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
  282. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  283. package/docs/content/durable-background-runs.mdx +37 -0
  284. package/docs/content/external-agents.mdx +62 -7
  285. package/docs/content/locales/ar-SA/external-agents.mdx +38 -7
  286. package/docs/content/locales/ar-SA/tracking.mdx +2 -0
  287. package/docs/content/locales/de-DE/external-agents.mdx +39 -7
  288. package/docs/content/locales/de-DE/tracking.mdx +2 -0
  289. package/docs/content/locales/es-ES/external-agents.mdx +39 -7
  290. package/docs/content/locales/es-ES/tracking.mdx +2 -0
  291. package/docs/content/locales/fr-FR/external-agents.mdx +39 -7
  292. package/docs/content/locales/fr-FR/tracking.mdx +4 -2
  293. package/docs/content/locales/hi-IN/external-agents.mdx +38 -7
  294. package/docs/content/locales/hi-IN/tracking.mdx +2 -0
  295. package/docs/content/locales/ja-JP/external-agents.mdx +38 -7
  296. package/docs/content/locales/ja-JP/tracking.mdx +2 -0
  297. package/docs/content/locales/ko-KR/external-agents.mdx +38 -7
  298. package/docs/content/locales/ko-KR/tracking.mdx +2 -0
  299. package/docs/content/locales/pt-BR/external-agents.mdx +39 -7
  300. package/docs/content/locales/pt-BR/tracking.mdx +2 -0
  301. package/docs/content/locales/zh-CN/external-agents.mdx +37 -7
  302. package/docs/content/locales/zh-CN/tracking.mdx +2 -0
  303. package/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
  304. package/docs/content/locales/zh-TW/external-agents.mdx +37 -7
  305. package/docs/content/locales/zh-TW/tracking.mdx +2 -0
  306. package/docs/content/tracking.mdx +2 -0
  307. package/package.json +2 -2
  308. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
  309. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  310. package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
  311. package/dist/observability/hosted-model-experiment.d.ts +0 -39
  312. package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
  313. package/dist/observability/hosted-model-experiment.js +0 -90
  314. package/dist/observability/hosted-model-experiment.js.map +0 -1
@@ -118,7 +118,7 @@ export function FormsListPage() {
118
118
  <Button
119
119
  onClick={handleCreate}
120
120
  size="sm"
121
- className="shrink-0 cursor-pointer"
121
+ className="min-h-10 shrink-0 cursor-pointer active:scale-[0.96] transition-[background-color,box-shadow,transform]"
122
122
  >
123
123
  <IconPlus className="h-3.5 w-3.5" />
124
124
  <span className="hidden sm:inline">{t("forms.newForm")}</span>
@@ -269,7 +269,7 @@ export function FormsListPage() {
269
269
  if (isLoading) {
270
270
  return (
271
271
  <div className="p-3 sm:p-6 max-w-5xl mx-auto">
272
- <div className="overflow-hidden rounded-lg border border-border bg-card">
272
+ <div className="forms-list-shell overflow-hidden bg-card">
273
273
  {Array.from({ length: 6 }).map((_, i) => (
274
274
  <div
275
275
  key={i}
@@ -280,8 +280,10 @@ export function FormsListPage() {
280
280
  <Skeleton className="h-3 w-1/2" />
281
281
  </div>
282
282
  <Skeleton className="h-5 w-20 rounded-full" />
283
+ <Skeleton className="h-5 w-20 rounded-full" />
284
+ <Skeleton className="h-4 w-24" />
283
285
  <Skeleton className="h-4 w-24" />
284
- <Skeleton className="h-8 w-8 rounded-md md:ms-auto" />
286
+ <Skeleton className="h-10 w-10 rounded-lg md:ms-auto" />
285
287
  </div>
286
288
  ))}
287
289
  </div>
@@ -300,6 +302,7 @@ export function FormsListPage() {
300
302
  <Button
301
303
  variant="outline"
302
304
  size="sm"
305
+ className="min-h-10 active:scale-[0.96] transition-[background-color,box-shadow,transform]"
303
306
  onClick={() => {
304
307
  const next = encodeURIComponent(
305
308
  window.location.pathname + window.location.search,
@@ -322,8 +325,8 @@ export function FormsListPage() {
322
325
  <Button
323
326
  variant="outline"
324
327
  size="sm"
328
+ className="min-h-10 gap-2 active:scale-[0.96] transition-[background-color,box-shadow,transform]"
325
329
  onClick={() => refetch()}
326
- className="gap-2"
327
330
  >
328
331
  <IconRefresh className="h-3.5 w-3.5" />
329
332
  {t("common.retry")}
@@ -344,11 +347,17 @@ export function FormsListPage() {
344
347
  value={view}
345
348
  onValueChange={(v) => setView(v as "active" | "archive")}
346
349
  >
347
- <TabsList>
348
- <TabsTrigger value="active" className="text-xs gap-1.5">
350
+ <TabsList className="h-12">
351
+ <TabsTrigger
352
+ value="active"
353
+ className="min-h-10 gap-1.5 text-xs active:scale-[0.96] transition-[background-color,box-shadow,color,transform]"
354
+ >
349
355
  {t("header.forms")}
350
356
  </TabsTrigger>
351
- <TabsTrigger value="archive" className="text-xs gap-1.5">
357
+ <TabsTrigger
358
+ value="archive"
359
+ className="min-h-10 gap-1.5 text-xs active:scale-[0.96] transition-[background-color,box-shadow,color,transform]"
360
+ >
352
361
  <IconArchive className="h-3.5 w-3.5" />
353
362
  {t("forms.archive")}
354
363
  </TabsTrigger>
@@ -359,7 +368,7 @@ export function FormsListPage() {
359
368
  <Button
360
369
  variant={selectionMode ? "secondary" : "ghost"}
361
370
  size="sm"
362
- className="h-8 gap-1.5 text-xs"
371
+ className="min-h-10 gap-1.5 text-xs active:scale-[0.96] transition-[background-color,box-shadow,transform]"
363
372
  onClick={() => {
364
373
  setSelectionMode((current) => {
365
374
  if (current) setSelectedIds(new Set());
@@ -374,7 +383,7 @@ export function FormsListPage() {
374
383
  </div>
375
384
 
376
385
  {selectionMode && forms.length > 0 && (
377
- <div className="mb-4 flex flex-wrap items-center gap-2 rounded-lg border border-border bg-muted/30 px-3 py-2">
386
+ <div className="forms-selection-toolbar mb-4 flex flex-wrap items-center gap-2 rounded-xl bg-muted/30 px-3 py-2">
378
387
  <span className="text-xs font-medium text-foreground">
379
388
  {t("forms.selectedCount", {
380
389
  count: selectedCount,
@@ -385,7 +394,7 @@ export function FormsListPage() {
385
394
  <Button
386
395
  variant="ghost"
387
396
  size="sm"
388
- className="h-8 text-xs"
397
+ className="min-h-10 text-xs active:scale-[0.96] transition-[background-color,box-shadow,transform]"
389
398
  onClick={toggleSelectAll}
390
399
  >
391
400
  {allFormsSelected ? t("common.clearAll") : t("common.selectAll")}
@@ -393,7 +402,7 @@ export function FormsListPage() {
393
402
  <Button
394
403
  variant="ghost"
395
404
  size="sm"
396
- className="h-8 gap-1.5 text-xs text-destructive hover:text-destructive"
405
+ className="min-h-10 gap-1.5 text-xs text-destructive hover:text-destructive active:scale-[0.96] transition-[background-color,box-shadow,transform]"
397
406
  onClick={() =>
398
407
  isArchive ? setBulkPurgeOpen(true) : handleBulkDelete(false)
399
408
  }
@@ -405,7 +414,7 @@ export function FormsListPage() {
405
414
  <Button
406
415
  variant="ghost"
407
416
  size="icon"
408
- className="ms-auto h-8 w-8"
417
+ className="relative ms-auto size-10 transition-[background-color,box-shadow,transform] active:scale-[0.96] sm:size-8 sm:before:absolute sm:before:-inset-1 sm:before:content-['']"
409
418
  onClick={clearSelection}
410
419
  aria-label={t("forms.exitSelectionMode")}
411
420
  >
@@ -415,7 +424,7 @@ export function FormsListPage() {
415
424
  )}
416
425
 
417
426
  {forms.length === 0 ? (
418
- <div className="flex flex-col items-center justify-center py-20 border border-dashed border-border rounded-xl">
427
+ <div className="forms-empty-state flex flex-col items-center justify-center rounded-xl border border-dashed border-border bg-card py-20">
419
428
  {isArchive ? (
420
429
  <>
421
430
  <h3 className="font-medium mb-1">
@@ -431,7 +440,11 @@ export function FormsListPage() {
431
440
  <p className="text-sm text-muted-foreground mb-4">
432
441
  {t("forms.emptyDescription")}
433
442
  </p>
434
- <Button onClick={handleCreate} size="sm" className="gap-2">
443
+ <Button
444
+ onClick={handleCreate}
445
+ size="sm"
446
+ className="min-h-10 gap-2 active:scale-[0.96] transition-[background-color,box-shadow,transform]"
447
+ >
435
448
  <IconPlus className="h-4 w-4" />
436
449
  {t("forms.createForm")}
437
450
  </Button>
@@ -439,7 +452,7 @@ export function FormsListPage() {
439
452
  )}
440
453
  </div>
441
454
  ) : (
442
- <div className="overflow-hidden rounded-lg border border-border bg-card">
455
+ <div className="forms-list-shell overflow-hidden bg-card">
443
456
  {forms.map((form: any) => {
444
457
  const selected = selectedIds.has(form.id);
445
458
  const dateLabel =
@@ -515,10 +528,6 @@ export function FormsListPage() {
515
528
  <h3 className="min-w-0 flex-1 truncate text-sm font-medium">
516
529
  {form.title}
517
530
  </h3>
518
- <VisibilityBadge
519
- visibility={(form as any).visibility}
520
- className="shrink-0"
521
- />
522
531
  </div>
523
532
  {form.description && (
524
533
  <p className="mt-0.5 truncate text-xs text-muted-foreground">
@@ -528,6 +537,10 @@ export function FormsListPage() {
528
537
  </div>
529
538
  </div>
530
539
 
540
+ <div className="flex min-w-0 items-center">
541
+ <VisibilityBadge visibility={(form as any).visibility} />
542
+ </div>
543
+
531
544
  <div className="flex items-center md:justify-start">
532
545
  <Badge
533
546
  variant="outline"
@@ -558,7 +571,7 @@ export function FormsListPage() {
558
571
  <Button
559
572
  variant="ghost"
560
573
  size="sm"
561
- className="h-10 w-10 sm:h-8 sm:w-8 p-0 sm:opacity-0 sm:group-hover:opacity-100 focus:opacity-100"
574
+ className="relative size-10 rounded-lg p-0 transition-[background-color,opacity,transform] duration-150 ease-out active:scale-[0.96] focus:opacity-100 sm:size-8 sm:opacity-0 sm:before:absolute sm:before:-inset-1 sm:before:content-[''] sm:group-hover:opacity-100"
562
575
  aria-label={t("forms.formActions")}
563
576
  >
564
577
  <IconDots className="h-4 w-4" />
@@ -695,10 +708,12 @@ export function FormsListPage() {
695
708
  </AlertDialogDescription>
696
709
  </AlertDialogHeader>
697
710
  <AlertDialogFooter>
698
- <AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
711
+ <AlertDialogCancel className="min-h-10 active:scale-[0.96] transition-[background-color,box-shadow,transform]">
712
+ {t("common.cancel")}
713
+ </AlertDialogCancel>
699
714
  <AlertDialogAction
700
715
  onClick={handlePurge}
701
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
716
+ className="min-h-10 bg-destructive text-destructive-foreground hover:bg-destructive/90 active:scale-[0.96] transition-[background-color,box-shadow,transform]"
702
717
  >
703
718
  {t("forms.deleteForever")}
704
719
  </AlertDialogAction>
@@ -718,13 +733,16 @@ export function FormsListPage() {
718
733
  </AlertDialogDescription>
719
734
  </AlertDialogHeader>
720
735
  <AlertDialogFooter>
721
- <AlertDialogCancel disabled={bulkDeletePending}>
736
+ <AlertDialogCancel
737
+ disabled={bulkDeletePending}
738
+ className="min-h-10 active:scale-[0.96] transition-[background-color,box-shadow,transform]"
739
+ >
722
740
  {t("common.cancel")}
723
741
  </AlertDialogCancel>
724
742
  <AlertDialogAction
725
743
  onClick={() => handleBulkDelete(true)}
726
744
  disabled={bulkDeletePending}
727
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
745
+ className="min-h-10 bg-destructive text-destructive-foreground hover:bg-destructive/90 active:scale-[0.96] transition-[background-color,box-shadow,transform]"
728
746
  >
729
747
  {t("forms.deleteForever")}
730
748
  </AlertDialogAction>
@@ -52,7 +52,7 @@ function Metric({
52
52
  detail?: string;
53
53
  }) {
54
54
  return (
55
- <div className="min-w-0 rounded-md border border-border bg-background px-3 py-2">
55
+ <div className="min-w-0 rounded-xl border border-border/80 bg-background px-3 py-3 shadow-sm">
56
56
  <div className="text-[11px] font-medium uppercase tracking-normal text-muted-foreground">
57
57
  {label}
58
58
  </div>
@@ -97,7 +97,7 @@ function SubmissionBars({
97
97
  <div className="flex h-40 w-full items-end rounded-sm bg-muted/40 px-0.5">
98
98
  <div
99
99
  className={cn(
100
- "w-full rounded-sm bg-primary/80 transition-colors",
100
+ "w-full rounded-sm bg-primary/80 shadow-sm transition-[height,background-color,box-shadow] duration-300 ease-out hover:bg-primary hover:shadow-md motion-reduce:transition-none",
101
101
  point.submissions === 0 && "bg-muted-foreground/30",
102
102
  )}
103
103
  style={{ height }}
@@ -121,11 +121,11 @@ function LoadingState() {
121
121
  <Skeleton className="h-10 w-72" />
122
122
  <div className="forms-response-metrics-grid grid gap-3">
123
123
  {Array.from({ length: 4 }).map((_, index) => (
124
- <Skeleton key={index} className="h-20 rounded-md" />
124
+ <Skeleton key={index} className="h-20 rounded-xl" />
125
125
  ))}
126
126
  </div>
127
- <Skeleton className="h-64 rounded-md" />
128
- <Skeleton className="h-72 rounded-md" />
127
+ <Skeleton className="h-64 rounded-xl" />
128
+ <Skeleton className="h-72 rounded-xl" />
129
129
  </div>
130
130
  );
131
131
  }
@@ -160,7 +160,7 @@ export function ResponseInsightsPage() {
160
160
  type="button"
161
161
  variant="outline"
162
162
  size="sm"
163
- className="gap-1.5"
163
+ className="min-h-10 gap-1.5 transition-[scale,background-color,border-color,color,box-shadow] duration-150 active:scale-[0.96] motion-reduce:transition-none"
164
164
  onClick={() => refetch()}
165
165
  >
166
166
  <IconRefresh className="size-3.5" />
@@ -176,7 +176,12 @@ export function ResponseInsightsPage() {
176
176
  <div className="flex min-h-full flex-col bg-background">
177
177
  <header className="flex min-h-14 shrink-0 items-center justify-between gap-3 border-b border-border pl-12 pr-3 sm:px-4 md:pl-4">
178
178
  <div className="flex min-w-0 items-center gap-2">
179
- <Button variant="ghost" size="sm" asChild className="gap-1.5">
179
+ <Button
180
+ variant="ghost"
181
+ size="sm"
182
+ asChild
183
+ className="min-h-10 gap-1.5 transition-[scale,background-color,border-color,color,box-shadow] duration-150 active:scale-[0.96] motion-reduce:transition-none"
184
+ >
180
185
  <Link to="/forms">
181
186
  <IconArrowLeft className="size-3.5" />
182
187
  Forms
@@ -196,7 +201,12 @@ export function ResponseInsightsPage() {
196
201
  </div>
197
202
  </div>
198
203
  </div>
199
- <Button variant="outline" size="sm" asChild className="gap-1.5">
204
+ <Button
205
+ variant="outline"
206
+ size="sm"
207
+ asChild
208
+ className="min-h-10 gap-1.5 transition-[scale,background-color,border-color,color,box-shadow] duration-150 active:scale-[0.96] motion-reduce:transition-none"
209
+ >
200
210
  <Link to={display.primaryAction.href}>
201
211
  {display.primaryAction.label}
202
212
  <IconExternalLink className="size-3.5" />
@@ -204,7 +214,7 @@ export function ResponseInsightsPage() {
204
214
  </Button>
205
215
  </header>
206
216
 
207
- <main className="forms-response-insights-grid grid gap-4 p-4">
217
+ <main className="forms-response-insights-grid grid gap-4 bg-muted/20 p-4">
208
218
  <section className="space-y-4">
209
219
  <div className="forms-response-metrics-grid grid gap-3">
210
220
  <Metric
@@ -233,7 +243,7 @@ export function ResponseInsightsPage() {
233
243
  />
234
244
  </div>
235
245
 
236
- <div className="rounded-md border border-border bg-background p-4">
246
+ <div className="rounded-xl border border-border/80 bg-background p-4 shadow-sm">
237
247
  <div className="mb-3 flex items-center justify-between gap-3">
238
248
  <div>
239
249
  <h2 className="text-sm font-semibold">{chartSeries.title}</h2>
@@ -249,7 +259,7 @@ export function ResponseInsightsPage() {
249
259
  </div>
250
260
  </section>
251
261
 
252
- <section className="min-w-0 rounded-md border border-border bg-background">
262
+ <section className="min-w-0 overflow-hidden rounded-xl border border-border/80 bg-background shadow-sm">
253
263
  <div className="flex min-h-12 items-center justify-between gap-3 border-b border-border px-3">
254
264
  <div className="min-w-0">
255
265
  <h2 className="truncate text-sm font-semibold">{table.title}</h2>
@@ -268,13 +278,13 @@ export function ResponseInsightsPage() {
268
278
  ) : (
269
279
  <div className="max-h-[calc(100vh-15rem)] overflow-auto">
270
280
  <Table>
271
- <TableHeader className="sticky top-0 z-10 bg-background">
281
+ <TableHeader className="sticky top-0 z-10 bg-background shadow-sm">
272
282
  <TableRow>
273
283
  {table.columns.map((column) => (
274
284
  <TableHead
275
285
  key={column.key}
276
286
  className={cn(
277
- "h-9 whitespace-nowrap px-3 text-xs",
287
+ "h-11 whitespace-nowrap px-3 text-xs",
278
288
  column.align === "right" && "text-right",
279
289
  )}
280
290
  >
@@ -285,12 +295,15 @@ export function ResponseInsightsPage() {
285
295
  </TableHeader>
286
296
  <TableBody>
287
297
  {table.rows.map((row) => (
288
- <TableRow key={String(row.id)}>
298
+ <TableRow
299
+ key={String(row.id)}
300
+ className="transition-[background-color] duration-150 hover:bg-muted/30 motion-reduce:transition-none"
301
+ >
289
302
  {table.columns.map((column) => (
290
303
  <TableCell
291
304
  key={column.key}
292
305
  className={cn(
293
- "max-w-56 truncate px-3 py-2 text-xs",
306
+ "max-w-56 truncate px-3 py-3 text-xs",
294
307
  column.align === "right" && "text-right",
295
308
  )}
296
309
  title={formatCell(row[column.key])}
@@ -254,7 +254,7 @@ export function ResponsesPage() {
254
254
  variant="outline"
255
255
  size="sm"
256
256
  onClick={() => refetch()}
257
- className="gap-2"
257
+ className="min-h-10 gap-2 transition-[scale,background-color,border-color,color,box-shadow] duration-150 active:scale-[0.96] motion-reduce:transition-none"
258
258
  >
259
259
  <IconRefresh className="h-3.5 w-3.5" />
260
260
  {t("common.retry")}
@@ -272,7 +272,7 @@ export function ResponsesPage() {
272
272
  variant="ghost"
273
273
  size="sm"
274
274
  asChild
275
- className="gap-1.5 shrink-0"
275
+ className="min-h-10 shrink-0 gap-1.5 transition-[scale,background-color,border-color,color,box-shadow] duration-150 active:scale-[0.96] motion-reduce:transition-none"
276
276
  >
277
277
  <Link to={`/forms/${id}`}>
278
278
  <IconArrowLeft className="h-3.5 w-3.5" />
@@ -305,14 +305,14 @@ export function ResponsesPage() {
305
305
  value={search}
306
306
  onChange={(e) => setSearch(e.target.value)}
307
307
  placeholder={t("responses.filterPlaceholder")}
308
- className="h-8 pl-7 w-48 text-xs"
308
+ className="h-10 w-48 pl-7 text-xs transition-[background-color,border-color,box-shadow] duration-150 motion-reduce:transition-none"
309
309
  />
310
310
  </div>
311
311
  ) : null}
312
312
  <Button
313
313
  variant="outline"
314
314
  size="sm"
315
- className="gap-1.5 text-xs shrink-0"
315
+ className="min-h-10 shrink-0 gap-1.5 text-xs transition-[scale,background-color,border-color,color,box-shadow] duration-150 active:scale-[0.96] motion-reduce:transition-none"
316
316
  onClick={exportCsv}
317
317
  disabled={filteredSorted.length === 0}
318
318
  >
@@ -332,7 +332,7 @@ export function ResponsesPage() {
332
332
  value={search}
333
333
  onChange={(e) => setSearch(e.target.value)}
334
334
  placeholder={t("responses.filterPlaceholder")}
335
- className="h-8 pl-7 text-xs"
335
+ className="h-10 pl-7 text-xs transition-[background-color,border-color,box-shadow] duration-150 motion-reduce:transition-none"
336
336
  />
337
337
  </div>
338
338
  </div>
@@ -355,10 +355,10 @@ export function ResponsesPage() {
355
355
  </div>
356
356
  ) : (
357
357
  <div className="flex-1 min-w-0 overflow-auto overscroll-x-contain">
358
- <div className="min-w-full">
358
+ <div className="w-full min-w-full">
359
359
  <table
360
- className="min-w-full table-fixed text-sm"
361
- style={{ minWidth: responseTableMinWidth }}
360
+ className="w-full min-w-full table-fixed text-sm"
361
+ style={{ width: "100%", minWidth: responseTableMinWidth }}
362
362
  >
363
363
  <colgroup>
364
364
  <col className="w-16" />
@@ -366,15 +366,18 @@ export function ResponsesPage() {
366
366
  {hasSubmitterEmail ? <col className="w-56" /> : null}
367
367
  {hasPageUrl ? <col className="w-64" /> : null}
368
368
  {hasClientSurface ? <col className="w-40" /> : null}
369
- {fields.map((f) => (
370
- <col key={f.id} className="w-80" />
369
+ {fields.map((f, index) => (
370
+ <col
371
+ key={f.id}
372
+ className={index === fields.length - 1 ? "w-auto" : "w-80"}
373
+ />
371
374
  ))}
372
375
  </colgroup>
373
376
  <thead>
374
- <tr className="border-b border-border bg-muted/30">
377
+ <tr className="border-b border-border bg-muted/40">
375
378
  <th
376
379
  scope="col"
377
- className="min-w-16 px-4 py-2.5 text-left text-xs font-medium text-muted-foreground whitespace-nowrap"
380
+ className="min-w-16 px-4 py-3 text-left text-xs font-medium text-muted-foreground whitespace-nowrap"
378
381
  >
379
382
  #
380
383
  </th>
@@ -423,7 +426,7 @@ export function ResponsesPage() {
423
426
  {filteredSorted.map((response, idx) => (
424
427
  <tr
425
428
  key={response.id}
426
- className="border-b border-border hover:bg-muted/20"
429
+ className="border-b border-border transition-[background-color] duration-150 hover:bg-muted/20 motion-reduce:transition-none"
427
430
  >
428
431
  <td className="px-4 py-2.5 text-xs text-muted-foreground">
429
432
  {filteredSorted.length - idx}
@@ -525,27 +528,50 @@ function SortableHeader(props: {
525
528
  }) {
526
529
  const t = useT();
527
530
  const { label, active, dir, onClick } = props;
528
- const Icon = !active
529
- ? IconArrowsSort
531
+ const sortState: "neutral" | "asc" | "desc" = !active
532
+ ? "neutral"
530
533
  : dir === "asc"
531
- ? IconArrowUp
532
- : IconArrowDown;
534
+ ? "asc"
535
+ : "desc";
536
+ const iconBase =
537
+ "absolute inset-0 transition-[opacity,scale,filter] duration-200 ease-[cubic-bezier(0.2,0,0,1)]";
538
+ const iconVisible = "scale-100 opacity-60 blur-none";
539
+ const iconHidden = "scale-[0.25] opacity-0 blur-[4px]";
533
540
  return (
534
541
  <th
535
542
  scope="col"
536
- className="min-w-40 px-4 py-2.5 text-left text-xs font-medium text-muted-foreground whitespace-nowrap"
543
+ className="min-w-40 px-4 py-1 text-left text-xs font-medium text-muted-foreground whitespace-nowrap"
537
544
  >
538
545
  <button
539
546
  type="button"
540
547
  onClick={onClick}
541
548
  className={cn(
542
- "inline-flex items-center gap-1 cursor-pointer hover:text-foreground",
549
+ "relative -mx-2 inline-flex min-h-10 cursor-pointer items-center gap-1 rounded-md px-2 transition-[scale,background-color,color,box-shadow] duration-150 ease-out hover:bg-accent hover:text-foreground active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 motion-reduce:transition-none motion-reduce:active:scale-100 sm:min-h-0 sm:before:absolute sm:before:-inset-2.5 sm:before:content-['']",
543
550
  active && "text-foreground",
544
551
  )}
545
552
  aria-label={t("responses.sortBy", { label })}
546
553
  >
547
554
  {label}
548
- <Icon className="h-3 w-3 opacity-60" />
555
+ <span className="relative inline-block h-3 w-3 shrink-0">
556
+ <IconArrowsSort
557
+ className={cn(
558
+ iconBase,
559
+ sortState === "neutral" ? iconVisible : iconHidden,
560
+ )}
561
+ />
562
+ <IconArrowUp
563
+ className={cn(
564
+ iconBase,
565
+ sortState === "asc" ? iconVisible : iconHidden,
566
+ )}
567
+ />
568
+ <IconArrowDown
569
+ className={cn(
570
+ iconBase,
571
+ sortState === "desc" ? iconVisible : iconHidden,
572
+ )}
573
+ />
574
+ </span>
549
575
  </button>
550
576
  </th>
551
577
  );
@@ -54,7 +54,7 @@ export default function SettingsRoute() {
54
54
  {t("settings.description")}
55
55
  </p>
56
56
 
57
- <Card id="language" className="scroll-mt-16">
57
+ <Card id="language" className="forms-settings-card scroll-mt-16">
58
58
  <CardHeader>
59
59
  <CardTitle className="text-base">
60
60
  {t("settings.languageTitle")}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Ask Forms suggestions now sit beneath the composer and prefill it when clicked
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Builder response tables now fill the view without a redundant response count badge
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Form builder surfaces are calmer, fields stay stable on hover, and integration icons load correctly
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Response tables now fill the available pane with a flexible trailing cell
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Smoother, more tactile controls throughout: animated icon and sort toggles, press feedback on buttons and cards, and larger click targets
@@ -61,7 +61,9 @@ Detailed draft, queue, and contact-resolution patterns live in
61
61
  (`draft-queue` skill), not `send-email` directly. The requester and reviewer
62
62
  must both be members of the active organization; Slack intake resolves the
63
63
  sender's real email via `users.info` (requires `users:read.email` bot scope)
64
- before it will queue anything.
64
+ before it will queue anything. Managed installs request that scope through
65
+ the generated OAuth manifest; reconnect an existing Slack install after
66
+ granting it. Legacy bot-token installs must add the scope in Slack manually.
65
67
  - Never edit the email store to change a draft the user is currently composing;
66
68
  use `manage-draft` or the `compose-{id}` application-state key described in
67
69
  `email-drafts`.
@@ -74,6 +74,7 @@ import {
74
74
  formatShortcut,
75
75
  } from "@/lib/utils";
76
76
 
77
+ import { buildEmailIframeDocument } from "./email-iframe-document";
77
78
  import {
78
79
  InlineReplyComposer,
79
80
  type InlineReplyHandle,
@@ -2558,6 +2559,7 @@ function HtmlEmailBody({
2558
2559
  const frameHostRef = useRef<HTMLDivElement>(null);
2559
2560
  const iframeRef = useRef<HTMLIFrameElement>(null);
2560
2561
  const [height, setHeight] = useState(200);
2562
+ const [iframeLoadVersion, setIframeLoadVersion] = useState(0);
2561
2563
  const { resolvedTheme } = useTheme();
2562
2564
  const isDark = getResolvedTheme(resolvedTheme) === "dark";
2563
2565
  const sanitizedHtml = useMemo(() => sanitizeEmailHtml(html), [html]);
@@ -2603,6 +2605,14 @@ function HtmlEmailBody({
2603
2605
  blockedCount: headBlockedCount + bodyBlockedCount,
2604
2606
  };
2605
2607
  }, [sanitizedHtml.headHtml, sanitizedHtml.bodyHtml, effectivePolicy]);
2608
+ const iframeDocument = useMemo(
2609
+ () =>
2610
+ buildEmailIframeDocument(
2611
+ processedEmailHtml.headHtml,
2612
+ processedEmailHtml.bodyHtml,
2613
+ ),
2614
+ [processedEmailHtml.bodyHtml, processedEmailHtml.headHtml],
2615
+ );
2606
2616
 
2607
2617
  const handleAlwaysTrust = () => {
2608
2618
  if (!senderDomain) return;
@@ -2623,7 +2633,6 @@ function HtmlEmailBody({
2623
2633
  const doc = iframe.contentDocument;
2624
2634
  if (!doc) return;
2625
2635
 
2626
- doc.open();
2627
2636
  const iframeCss = useDarkIframeCss
2628
2637
  ? `
2629
2638
  html, body {
@@ -2694,16 +2703,10 @@ function HtmlEmailBody({
2694
2703
  .quote-toggle:hover, .sig-toggle:hover { color: rgba(0,0,0,0.7); }
2695
2704
  `;
2696
2705
 
2697
- doc.write(`<!DOCTYPE html>
2698
- <html>
2699
- <head>
2700
- <meta charset="utf-8">
2701
- ${processedEmailHtml.headHtml}
2702
- <style>${iframeCss} </style>
2703
- </head>
2704
- <body>${processedEmailHtml.bodyHtml}</body>
2705
- </html>`);
2706
- doc.close();
2706
+ const themeStyle = doc.createElement("style");
2707
+ themeStyle.setAttribute("data-mail-theme", "");
2708
+ themeStyle.textContent = iframeCss;
2709
+ doc.head.appendChild(themeStyle);
2707
2710
 
2708
2711
  const resize = () => {
2709
2712
  if (doc.body) {
@@ -3195,6 +3198,7 @@ function HtmlEmailBody({
3195
3198
  clearTimeout(timer);
3196
3199
  clearTimeout(timer2);
3197
3200
  images.forEach((img) => img.removeEventListener("load", resize));
3201
+ themeStyle.remove();
3198
3202
  };
3199
3203
  }, [
3200
3204
  processedEmailHtml.bodyHtml,
@@ -3202,6 +3206,7 @@ function HtmlEmailBody({
3202
3206
  isDark,
3203
3207
  useDarkIframeCss,
3204
3208
  IFRAME_BG,
3209
+ iframeLoadVersion,
3205
3210
  ]);
3206
3211
 
3207
3212
  // Inject / clear search highlights in the iframe whenever searchTerm or content changes
@@ -3268,7 +3273,7 @@ function HtmlEmailBody({
3268
3273
  // Small delay to ensure iframe DOM is ready after a processedHtml rewrite
3269
3274
  const timer = setTimeout(injectHighlights, 60);
3270
3275
  return () => clearTimeout(timer);
3271
- }, [searchTerm, processedEmailHtml.bodyHtml]);
3276
+ }, [searchTerm, processedEmailHtml.bodyHtml, iframeLoadVersion]);
3272
3277
 
3273
3278
  // Update which mark is "active" and scroll it into view
3274
3279
  useEffect(() => {
@@ -3289,7 +3294,7 @@ function HtmlEmailBody({
3289
3294
  active.style.color = "#000";
3290
3295
  active.scrollIntoView({ behavior: "smooth", block: "nearest" });
3291
3296
  }
3292
- }, [activeLocalIdx, searchTerm]);
3297
+ }, [activeLocalIdx, searchTerm, iframeLoadVersion]);
3293
3298
 
3294
3299
  const showBanner =
3295
3300
  effectivePolicy === "block-all" &&
@@ -3330,6 +3335,8 @@ function HtmlEmailBody({
3330
3335
  )}
3331
3336
  <iframe
3332
3337
  ref={iframeRef}
3338
+ data-agent-native-session-replay=""
3339
+ srcDoc={iframeDocument}
3333
3340
  sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"
3334
3341
  style={{
3335
3342
  width: "100%",
@@ -3340,6 +3347,7 @@ function HtmlEmailBody({
3340
3347
  borderRadius: hasDesignedBg && isDark ? "6px" : undefined,
3341
3348
  }}
3342
3349
  title={t("mail.thread.emailContent")}
3350
+ onLoad={() => setIframeLoadVersion((version) => version + 1)}
3343
3351
  />
3344
3352
  </div>
3345
3353
  );