@agent-native/core 0.98.4 → 0.98.5

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 (217) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/durable-background-runs.mdx +37 -0
  4. package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +2 -0
  5. package/corpus/core/docs/content/locales/de-DE/tracking.mdx +2 -0
  6. package/corpus/core/docs/content/locales/es-ES/tracking.mdx +2 -0
  7. package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +4 -2
  8. package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +2 -0
  9. package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +2 -0
  10. package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +2 -0
  11. package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +2 -0
  12. package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +2 -0
  13. package/corpus/core/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
  14. package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +2 -0
  15. package/corpus/core/docs/content/tracking.mdx +2 -0
  16. package/corpus/core/package.json +1 -1
  17. package/corpus/core/src/agent/durable-background.ts +16 -16
  18. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +25 -8
  19. package/corpus/core/src/agent/engine/anthropic-engine.ts +17 -3
  20. package/corpus/core/src/agent/engine/output-tokens.ts +6 -6
  21. package/corpus/core/src/agent/production-agent.ts +200 -27
  22. package/corpus/core/src/agent/run-manager.ts +81 -1
  23. package/corpus/core/src/agent/run-store.ts +301 -41
  24. package/corpus/core/src/client/AgentPanel.tsx +6 -2
  25. package/corpus/core/src/client/AssistantChat.tsx +364 -69
  26. package/corpus/core/src/client/agent-chat-adapter.ts +169 -1
  27. package/corpus/core/src/client/chat/message-components.tsx +3 -0
  28. package/corpus/core/src/client/chat/tool-call-display.tsx +20 -36
  29. package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +7 -0
  30. package/corpus/core/src/client/extensions/AgentNativeExtensionFrame.tsx +2 -0
  31. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +2 -0
  32. package/corpus/core/src/client/extensions/ExtensionEditor.tsx +9 -2
  33. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +2 -0
  34. package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +2 -0
  35. package/corpus/core/src/client/extensions/portable-extension.ts +2 -0
  36. package/corpus/core/src/client/session-replay.ts +361 -29
  37. package/corpus/core/src/client/sse-event-processor.ts +43 -1
  38. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +1 -1
  39. package/corpus/core/src/demo/config.ts +6 -6
  40. package/corpus/core/src/demo/fetch-interceptor.ts +13 -4
  41. package/corpus/core/src/demo/redact.ts +13 -149
  42. package/corpus/core/src/extensions/html-shell.ts +3 -0
  43. package/corpus/core/src/extensions/session-replay-iframe.ts +131 -0
  44. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -20
  45. package/corpus/core/src/session-replay-iframe-protocol.ts +58 -0
  46. package/corpus/core/src/shared/reasoning-effort.ts +35 -0
  47. package/corpus/core/src/styles/agent-conversation.css +26 -0
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  49. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +30 -21
  50. package/corpus/templates/analytics/AGENTS.md +12 -0
  51. package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
  52. package/corpus/templates/analytics/actions/get-session-replay-events.ts +1 -0
  53. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
  54. package/corpus/templates/analytics/actions/list-error-issues.ts +1 -0
  55. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
  56. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -1
  57. package/corpus/templates/analytics/app/global.css +2 -2
  58. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +41 -4
  59. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +52 -203
  60. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +34 -12
  61. package/corpus/templates/analytics/changelog/2026-07-12-analytics-chat-retries-before-showing-a-generic-no-data-mess.md +6 -0
  62. package/corpus/templates/analytics/changelog/2026-07-12-analytics-sessions-search-no-longer-loses-fast-keystrokes-an.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-07-12-chart-selection-respects-chat-state.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-anonymizes-error-reporting-emails.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-07-12-session-replays-show-iframe-content.md +6 -0
  66. package/corpus/templates/analytics/netlify.toml +0 -3
  67. package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +14 -0
  68. package/corpus/templates/analytics/server/lib/error-capture.ts +59 -2
  69. package/corpus/templates/analytics/server/lib/real-data-actions.ts +24 -0
  70. package/corpus/templates/analytics/server/lib/session-replay.ts +2 -9
  71. package/corpus/templates/analytics/server/plugins/agent-chat.ts +39 -6
  72. package/corpus/templates/assets/app/global.css +1 -1
  73. package/corpus/templates/brain/app/global.css +2 -2
  74. package/corpus/templates/brain/changelog/2026-07-12-fix-long-response-progress.md +6 -0
  75. package/corpus/templates/forms/app/components/CloudUpgrade.tsx +1 -1
  76. package/corpus/templates/forms/app/components/ThemeToggle.tsx +22 -6
  77. package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +5 -5
  78. package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +3 -3
  79. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +16 -16
  80. package/corpus/templates/forms/app/global.css +110 -5
  81. package/corpus/templates/forms/app/pages/AskPage.tsx +3 -3
  82. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +134 -52
  83. package/corpus/templates/forms/app/pages/FormFillPage.tsx +44 -11
  84. package/corpus/templates/forms/app/pages/FormsListPage.tsx +42 -24
  85. package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +28 -15
  86. package/corpus/templates/forms/app/pages/ResponsesPage.tsx +38 -15
  87. package/corpus/templates/forms/app/routes/_app.settings.tsx +1 -1
  88. package/corpus/templates/forms/changelog/2026-07-12-form-builder-surfaces-are-calmer-fields-stay-stable-on-hover.md +6 -0
  89. package/corpus/templates/forms/changelog/2026-07-12-smoother-more-tactile-controls-throughout-animated-icon-and-.md +6 -0
  90. package/corpus/templates/mail/app/components/email/EmailThread.tsx +21 -13
  91. package/corpus/templates/mail/app/components/email/email-iframe-document.ts +18 -0
  92. package/dist/agent/durable-background.d.ts +10 -10
  93. package/dist/agent/durable-background.d.ts.map +1 -1
  94. package/dist/agent/durable-background.js +16 -16
  95. package/dist/agent/durable-background.js.map +1 -1
  96. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  97. package/dist/agent/engine/ai-sdk-engine.js +19 -8
  98. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  99. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  100. package/dist/agent/engine/anthropic-engine.js +11 -3
  101. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  102. package/dist/agent/engine/output-tokens.js +6 -6
  103. package/dist/agent/engine/output-tokens.js.map +1 -1
  104. package/dist/agent/production-agent.d.ts +52 -0
  105. package/dist/agent/production-agent.d.ts.map +1 -1
  106. package/dist/agent/production-agent.js +154 -26
  107. package/dist/agent/production-agent.js.map +1 -1
  108. package/dist/agent/run-manager.d.ts +9 -0
  109. package/dist/agent/run-manager.d.ts.map +1 -1
  110. package/dist/agent/run-manager.js +71 -1
  111. package/dist/agent/run-manager.js.map +1 -1
  112. package/dist/agent/run-store.d.ts +9 -0
  113. package/dist/agent/run-store.d.ts.map +1 -1
  114. package/dist/agent/run-store.js +243 -48
  115. package/dist/agent/run-store.js.map +1 -1
  116. package/dist/client/AgentPanel.d.ts.map +1 -1
  117. package/dist/client/AgentPanel.js +6 -2
  118. package/dist/client/AgentPanel.js.map +1 -1
  119. package/dist/client/AssistantChat.d.ts +21 -0
  120. package/dist/client/AssistantChat.d.ts.map +1 -1
  121. package/dist/client/AssistantChat.js +298 -67
  122. package/dist/client/AssistantChat.js.map +1 -1
  123. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  124. package/dist/client/agent-chat-adapter.js +146 -1
  125. package/dist/client/agent-chat-adapter.js.map +1 -1
  126. package/dist/client/chat/message-components.d.ts.map +1 -1
  127. package/dist/client/chat/message-components.js +2 -1
  128. package/dist/client/chat/message-components.js.map +1 -1
  129. package/dist/client/chat/tool-call-display.d.ts +3 -1
  130. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  131. package/dist/client/chat/tool-call-display.js +13 -37
  132. package/dist/client/chat/tool-call-display.js.map +1 -1
  133. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +1 -0
  134. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
  135. package/dist/client/conversation/use-near-bottom-autoscroll.js +6 -0
  136. package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
  137. package/dist/client/extensions/AgentNativeExtensionFrame.d.ts.map +1 -1
  138. package/dist/client/extensions/AgentNativeExtensionFrame.js +2 -1
  139. package/dist/client/extensions/AgentNativeExtensionFrame.js.map +1 -1
  140. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  141. package/dist/client/extensions/EmbeddedExtension.js +2 -1
  142. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  143. package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
  144. package/dist/client/extensions/ExtensionEditor.js +5 -2
  145. package/dist/client/extensions/ExtensionEditor.js.map +1 -1
  146. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  147. package/dist/client/extensions/ExtensionViewer.js +2 -1
  148. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  149. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
  150. package/dist/client/extensions/InlineExtensionFrame.js +2 -1
  151. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
  152. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  153. package/dist/client/extensions/portable-extension.js +2 -0
  154. package/dist/client/extensions/portable-extension.js.map +1 -1
  155. package/dist/client/session-replay.d.ts.map +1 -1
  156. package/dist/client/session-replay.js +308 -29
  157. package/dist/client/session-replay.js.map +1 -1
  158. package/dist/client/sse-event-processor.d.ts +5 -0
  159. package/dist/client/sse-event-processor.d.ts.map +1 -1
  160. package/dist/client/sse-event-processor.js +37 -1
  161. package/dist/client/sse-event-processor.js.map +1 -1
  162. package/dist/collab/routes.d.ts +1 -1
  163. package/dist/collab/struct-routes.d.ts +1 -1
  164. package/dist/demo/actions/toggle-demo-mode.js +1 -1
  165. package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
  166. package/dist/demo/config.js +6 -6
  167. package/dist/demo/config.js.map +1 -1
  168. package/dist/demo/fetch-interceptor.d.ts.map +1 -1
  169. package/dist/demo/fetch-interceptor.js +13 -4
  170. package/dist/demo/fetch-interceptor.js.map +1 -1
  171. package/dist/demo/redact.d.ts +7 -7
  172. package/dist/demo/redact.d.ts.map +1 -1
  173. package/dist/demo/redact.js +8 -145
  174. package/dist/demo/redact.js.map +1 -1
  175. package/dist/extensions/html-shell.d.ts.map +1 -1
  176. package/dist/extensions/html-shell.js +2 -0
  177. package/dist/extensions/html-shell.js.map +1 -1
  178. package/dist/extensions/session-replay-iframe.d.ts +10 -0
  179. package/dist/extensions/session-replay-iframe.d.ts.map +1 -0
  180. package/dist/extensions/session-replay-iframe.js +121 -0
  181. package/dist/extensions/session-replay-iframe.js.map +1 -0
  182. package/dist/progress/routes.d.ts +1 -1
  183. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  184. package/dist/resources/handlers.d.ts +3 -3
  185. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  186. package/dist/scripts/agent-engines/list-agent-engines.js +1 -19
  187. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  188. package/dist/session-replay-iframe-protocol.d.ts +36 -0
  189. package/dist/session-replay-iframe-protocol.d.ts.map +1 -0
  190. package/dist/session-replay-iframe-protocol.js +24 -0
  191. package/dist/session-replay-iframe-protocol.js.map +1 -0
  192. package/dist/shared/reasoning-effort.d.ts +11 -0
  193. package/dist/shared/reasoning-effort.d.ts.map +1 -1
  194. package/dist/shared/reasoning-effort.js +36 -0
  195. package/dist/shared/reasoning-effort.js.map +1 -1
  196. package/dist/styles/agent-conversation.css +26 -0
  197. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  198. package/docs/content/durable-background-runs.mdx +37 -0
  199. package/docs/content/locales/ar-SA/tracking.mdx +2 -0
  200. package/docs/content/locales/de-DE/tracking.mdx +2 -0
  201. package/docs/content/locales/es-ES/tracking.mdx +2 -0
  202. package/docs/content/locales/fr-FR/tracking.mdx +4 -2
  203. package/docs/content/locales/hi-IN/tracking.mdx +2 -0
  204. package/docs/content/locales/ja-JP/tracking.mdx +2 -0
  205. package/docs/content/locales/ko-KR/tracking.mdx +2 -0
  206. package/docs/content/locales/pt-BR/tracking.mdx +2 -0
  207. package/docs/content/locales/zh-CN/tracking.mdx +2 -0
  208. package/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
  209. package/docs/content/locales/zh-TW/tracking.mdx +2 -0
  210. package/docs/content/tracking.mdx +2 -0
  211. package/package.json +2 -2
  212. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  213. package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
  214. package/dist/observability/hosted-model-experiment.d.ts +0 -39
  215. package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
  216. package/dist/observability/hosted-model-experiment.js +0 -90
  217. package/dist/observability/hosted-model-experiment.js.map +0 -1
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The deliberately small authenticated MCP surface for Analytics.
3
+ *
4
+ * Keep this list read-only and incident-focused. In particular, do not add
5
+ * replay-event/blob actions or dashboard/data mutation actions here.
6
+ */
7
+ export const ANALYTICS_CONNECTOR_CATALOG = [
8
+ "list-session-recordings",
9
+ "get-session-replay-summary",
10
+ "get-session-replay-events",
11
+ "query-agent-native-analytics",
12
+ "list-error-issues",
13
+ "get-error-issue",
14
+ ] as const;
@@ -18,6 +18,7 @@ import { fileURLToPath } from "node:url";
18
18
  * reads go through `accessFilter` so an org-scoped key surfaces its issues to
19
19
  * the whole org exactly like session recordings.
20
20
  */
21
+ import { readAppState } from "@agent-native/core/application-state";
21
22
  import { notifyWithDelivery } from "@agent-native/core/notifications";
22
23
  import { recordChange } from "@agent-native/core/server";
23
24
  import { accessFilter } from "@agent-native/core/sharing";
@@ -1076,6 +1077,52 @@ export interface ListErrorIssuesFilters {
1076
1077
  limit?: number;
1077
1078
  }
1078
1079
 
1080
+ export const ERROR_REPORTING_ANONYMOUS_EMAIL = "anonymous@builder.io";
1081
+ const ERROR_REPORTING_EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
1082
+
1083
+ /**
1084
+ * Error reports contain emails in identity fields, messages, stacks, URLs,
1085
+ * tags, extra context, and breadcrumbs. Redact every string at this owning
1086
+ * read seam so demo mode never depends on browser interception timing.
1087
+ */
1088
+ export function anonymizeErrorReportingEmails<T>(value: T): T {
1089
+ if (typeof value === "string") {
1090
+ return value.replace(
1091
+ ERROR_REPORTING_EMAIL_PATTERN,
1092
+ ERROR_REPORTING_ANONYMOUS_EMAIL,
1093
+ ) as T;
1094
+ }
1095
+ if (Array.isArray(value)) {
1096
+ return value.map((item) => anonymizeErrorReportingEmails(item)) as T;
1097
+ }
1098
+ if (value && typeof value === "object") {
1099
+ if (value instanceof Date) return value;
1100
+ const output: Record<string, unknown> = {};
1101
+ for (const [key, entry] of Object.entries(value)) {
1102
+ output[key] = anonymizeErrorReportingEmails(entry);
1103
+ }
1104
+ return output as T;
1105
+ }
1106
+ return value;
1107
+ }
1108
+
1109
+ async function isErrorDemoModeEnabled(): Promise<boolean> {
1110
+ if (process.env.DEMO_MODE === "true") return true;
1111
+ try {
1112
+ const state = await readAppState("demo-mode");
1113
+ return state?.enabled === true;
1114
+ } catch {
1115
+ return false;
1116
+ }
1117
+ }
1118
+
1119
+ function anonymizeErrorReportingEmailsInDemoMode<T>(
1120
+ value: T,
1121
+ enabled: boolean,
1122
+ ): T {
1123
+ return enabled ? anonymizeErrorReportingEmails(value) : value;
1124
+ }
1125
+
1079
1126
  export interface ErrorIssueSummary {
1080
1127
  id: string;
1081
1128
  fingerprint: string;
@@ -1148,6 +1195,7 @@ export async function listErrorIssues(
1148
1195
  scope: ErrorReadScope,
1149
1196
  filters: ListErrorIssuesFilters = {},
1150
1197
  ): Promise<ErrorIssueSummary[]> {
1198
+ const demoModeEnabled = isErrorDemoModeEnabled();
1151
1199
  const db = getDb() as any;
1152
1200
  const limit = Math.min(
1153
1201
  MAX_ISSUE_LIMIT,
@@ -1184,7 +1232,7 @@ export async function listErrorIssues(
1184
1232
  .limit(limit);
1185
1233
 
1186
1234
  const sparklines = await sparklinesForIssues(rows.map((row: any) => row.id));
1187
- return rows.map((row: any) => ({
1235
+ const issues = rows.map((row: any) => ({
1188
1236
  id: row.id,
1189
1237
  fingerprint: row.fingerprint,
1190
1238
  type: row.type,
@@ -1203,6 +1251,7 @@ export async function listErrorIssues(
1203
1251
  template: row.template ?? null,
1204
1252
  sparkline: sparklines.get(row.id) ?? new Array(SPARKLINE_DAYS).fill(0),
1205
1253
  }));
1254
+ return anonymizeErrorReportingEmailsInDemoMode(issues, await demoModeEnabled);
1206
1255
  }
1207
1256
 
1208
1257
  export interface ErrorEventDetail {
@@ -1294,6 +1343,7 @@ export async function getErrorIssue(
1294
1343
  issueId: string,
1295
1344
  options: { eventsLimit?: number } = {},
1296
1345
  ): Promise<ErrorIssueDetail> {
1346
+ const demoModeEnabled = isErrorDemoModeEnabled();
1297
1347
  const db = getDb() as any;
1298
1348
  const [issueRow] = await db
1299
1349
  .select()
@@ -1419,7 +1469,14 @@ export async function getErrorIssue(
1419
1469
  sparkline: sparklines.get(issueId) ?? new Array(SPARKLINE_DAYS).fill(0),
1420
1470
  };
1421
1471
 
1422
- return { issue, events, sessions: Array.from(sessions.values()) };
1472
+ return anonymizeErrorReportingEmailsInDemoMode(
1473
+ {
1474
+ issue,
1475
+ events,
1476
+ sessions: Array.from(sessions.values()),
1477
+ },
1478
+ await demoModeEnabled,
1479
+ );
1423
1480
  }
1424
1481
 
1425
1482
  export interface UpdateErrorIssueInput {
@@ -8,6 +8,7 @@ const INJECTED_CONTEXT_BLOCKS = [
8
8
  "available-agents",
9
9
  "available-jobs",
10
10
  "plan-mode-note",
11
+ "non-analytics-retry",
11
12
  ];
12
13
 
13
14
  export const DATA_QUERY_ACTIONS = new Set([
@@ -231,12 +232,35 @@ export function looksLikeAnalyticsDataRequest(text: string): boolean {
231
232
  const UNSUPPORTED_RESULT_CLAIM =
232
233
  /(?:\b\d[\d,.]*(?:\.\d+)?\s*(?:%|percent|users?|customers?|accounts?|sessions?|events?|deals?|tickets?|issues?|calls?|messages?|signups?|pageviews?)\b|\$\s*\d|\b(?:data|query|results?)\s+(?:shows?|showed|indicates?|returned|found)\b|\b(?:i found|the top|the bottom|highest|lowest|increased|decreased|grew|declined|converted|churned|retained|averaged|total(?:ed)?|count(?:ed)?)\b)/i;
233
234
 
235
+ export const GENERIC_NO_DATA_FALLBACK_MESSAGE =
236
+ "I can't provide a grounded analytics result yet because no real data-source query ran successfully. Tell me which source to use or connect the missing source, and I'll run it before giving numbers or source-record conclusions.";
237
+
238
+ // The first sentence of the canned fallback, lowercased, with the trailing
239
+ // period dropped. Matching this prefix (rather than the whole message) still
240
+ // catches a model paraphrase that continues differently after "successfully".
241
+ const GENERIC_NO_DATA_FALLBACK_FIRST_SENTENCE =
242
+ GENERIC_NO_DATA_FALLBACK_MESSAGE.slice(
243
+ 0,
244
+ GENERIC_NO_DATA_FALLBACK_MESSAGE.indexOf(". ") + 1,
245
+ ).toLowerCase();
246
+
247
+ export function isGenericNoDataFallback(text: string): boolean {
248
+ return text
249
+ .trim()
250
+ .toLowerCase()
251
+ .startsWith(GENERIC_NO_DATA_FALLBACK_FIRST_SENTENCE.slice(0, -1));
252
+ }
253
+
234
254
  const SAFE_NO_DATA_RESPONSE =
235
255
  /\b(?:i can't|i cannot|can't retrieve|cannot retrieve|couldn't retrieve|unable to retrieve|don't have access|do not have access|not configured|missing credentials?|need (?:a|the)? ?data source|need to know which source|which source|which data source|clarify|can you|once (?:that'?s|it is) (?:connected|configured|available)|no data source|without a successful|query failed|source query failed|sql failed|error running|before (?:i|we) can (?:calculate|report|answer|analyze)|i need to query)\b/i;
236
256
 
237
257
  export function isSafeNoDataAnalyticsResponse(text: string): boolean {
238
258
  const trimmed = text.trim();
239
259
  if (!trimmed) return false;
260
+ // This is the guard's own last-resort fallback. If a model emits it before
261
+ // attempting a query, it must go through the retry path instead of being
262
+ // accepted as an explicit unavailable-source or clarification response.
263
+ if (isGenericNoDataFallback(trimmed)) return false;
240
264
  if (UNSUPPORTED_RESULT_CLAIM.test(trimmed)) return false;
241
265
  if (SAFE_NO_DATA_RESPONSE.test(trimmed)) return true;
242
266
  return /\?\s*$/.test(trimmed) && !UNSUPPORTED_RESULT_CLAIM.test(trimmed);
@@ -255,6 +255,7 @@ const RETENTION_DELETE_BATCH_SIZE = 500;
255
255
  const REPLAY_PRIVATE_BLOB_REF_KIND = "agent-native.session-replay.private-blob";
256
256
  const REPLAY_PRIVATE_BLOB_REF_VERSION = 1;
257
257
  const SESSION_DEMO_EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
258
+ const SESSION_DEMO_ANONYMOUS_EMAIL = "anonymous@builder.io";
258
259
  let inlineReplayFallbackWarned = false;
259
260
 
260
261
  function replayError(message: string, statusCode: number): Error {
@@ -318,20 +319,12 @@ function demoBuilderSessionCondition() {
318
319
  }
319
320
 
320
321
  class SessionDemoAnonymizer {
321
- private readonly aliases = new Map<string, string>();
322
-
323
322
  aliasFor(value: unknown): string | null {
324
323
  const raw = replayString(value);
325
324
  if (!raw || raw.match(SESSION_DEMO_EMAIL_PATTERN)?.[0] !== raw) {
326
325
  return null;
327
326
  }
328
- const email = raw.toLowerCase();
329
- let alias = this.aliases.get(email);
330
- if (!alias) {
331
- alias = `anonymized-${this.aliases.size + 1}@builder.io`;
332
- this.aliases.set(email, alias);
333
- }
334
- return alias;
327
+ return SESSION_DEMO_ANONYMOUS_EMAIL;
335
328
  }
336
329
 
337
330
  summarize(row: any, role?: SessionReplayAccessRole): SessionRecordingSummary {
@@ -10,12 +10,15 @@ import {
10
10
  applyAnalyticsPlanModePolicy,
11
11
  INITIAL_TOOL_NAMES,
12
12
  } from "../lib/agent-chat-plan-mode";
13
+ import { ANALYTICS_CONNECTOR_CATALOG } from "../lib/analytics-connector-catalog";
13
14
  import {
14
15
  failedDataQueryAttemptMessage,
16
+ GENERIC_NO_DATA_FALLBACK_MESSAGE,
15
17
  hasExplicitPartialDisclosure,
16
18
  hasFailedCorpusWorkflowEvidence,
17
19
  hasDataQueryAttempt,
18
20
  hasIncompleteDataEvidence,
21
+ isGenericNoDataFallback,
19
22
  isSafeNoDataAnalyticsResponse,
20
23
  hasOverstatedCoverageConfidenceClaim,
21
24
  looksLikeCoverageSensitiveAnalyticsRequest,
@@ -24,16 +27,34 @@ import {
24
27
  needsCorpusWorkflowForCoverageSensitiveRequest,
25
28
  needsSourceRecordBodyWorkflowForCoverageSensitiveRequest,
26
29
  } from "../lib/real-data-actions";
27
- const ANALYTICS_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
28
-
29
30
  export const SIMPLE_TIME_BOUNDED_METRIC_FAST_PATH_GUIDANCE =
30
31
  "SIMPLE TIME-BOUNDED METRIC FAST PATH — When the data dictionary or a known canonical source identifies the metric, run one bounded aggregate. Once it returns a valid result, answer the explicit question immediately with the source, time window, row count, and only necessary caveats. Do not schema-discover, retry, enrich, cross-check, or add breakdowns after that successful result unless the query failed or the result conflicts with the known metric definition. This does not waive the real-data requirement: never answer from a guess, stale value, or unverified result. ";
31
32
 
33
+ export const ANALYTICS_OBSERVABILITY_INCIDENT_GUIDANCE =
34
+ "OBSERVABILITY INCIDENT WORKFLOW — For a named user's session or error question, resolve the user's email from context, then use list-session-recordings with userId and hasErrors=true over a bounded recent window. Use list-error-issues with userId or sessionRecordingId to identify the grouped issue, then get-error-issue for stack, breadcrumbs, occurrences, and linked recordings. When the timeline, page navigation, console diagnostics, failed network requests, or clicks are needed, use create-session-replay-agent-link and follow its bounded diagnostics/context APIs. Prefer these first-party actions over generic SQL; use query-agent-native-analytics only to correlate first-party events. Report the matching evidence and do not claim a root cause without a corroborating error or replay signal. ";
35
+
36
+ export const NON_ANALYTICS_REQUEST_GUIDANCE =
37
+ "NON-ANALYTICS REQUESTS — If the user is not asking for a live metric, source record, or derived analytics claim, answer normally in chat. Greetings, general-knowledge questions, math, writing, coding, and conceptual questions do not need a data-source call. Do not use the no-grounded-data fallback for those requests. ";
38
+
39
+ // Deterministic backstop for the soft NON_ANALYTICS_REQUEST_GUIDANCE prompt
40
+ // above: if a model still parrots the canned no-grounded-data fallback on a
41
+ // non-analytics turn, retry once with this synthetic user message instead of
42
+ // letting the canned sentence reach the user. Wrapped in an injected-context
43
+ // tag (registered in INJECTED_CONTEXT_BLOCKS) so `looksLikeAnalyticsDataRequest`
44
+ // never classifies the guard's own retry turn as a data request and loops.
45
+ export const NON_ANALYTICS_FALLBACK_RETRY_MESSAGE =
46
+ "<non-analytics-retry>\nThe user's latest message is ordinary conversation. Reply to it directly and naturally. Never answer it with the no-grounded-data disclaimer.\n</non-analytics-retry>";
47
+
48
+ export const NON_ANALYTICS_FALLBACK_FINAL_MESSAGE =
49
+ "I got stuck generating a reply to that message. Please try again or rephrase it.";
50
+
32
51
  export function analyticsSourceGuidanceOpening(): string {
33
52
  return (
34
53
  "<data-source-guidance>\n" +
35
54
  "Apply real-data requirements only when presenting analytics results, source records, or derived metrics. Do not call data-source tools for workflow migration, recurring-job setup, UI/code fixes, settings help, conceptual planning, or other non-data tasks unless the user explicitly asks for data. " +
55
+ NON_ANALYTICS_REQUEST_GUIDANCE +
36
56
  SIMPLE_TIME_BOUNDED_METRIC_FAST_PATH_GUIDANCE +
57
+ ANALYTICS_OBSERVABILITY_INCIDENT_GUIDANCE +
37
58
  "SURFACE DIFFERENTIATION — You are the analytics assistant for definitions, deep-dive analysis, and action. For questions about what a metric, model, or table means, use the Data Dictionary and configured schema tools first. For trends, comparisons, anomalies, current data, or anything that requires querying live data, answer directly in chat with the relevant provider query, dashboard analysis, and inline charts when useful. "
38
59
  );
39
60
  }
@@ -124,7 +145,20 @@ export function realDataFinalGuard(
124
145
  return null;
125
146
  }
126
147
  const userText = latestUserText(context.messages ?? []);
127
- if (!looksLikeAnalyticsDataRequest(userText)) return null;
148
+ if (!looksLikeAnalyticsDataRequest(userText)) {
149
+ // Deterministic backstop: the soft NON_ANALYTICS_REQUEST_GUIDANCE prompt
150
+ // sentence is not always enough, and a model occasionally parrots the
151
+ // canned no-grounded-data fallback even for ordinary conversation. Catch
152
+ // that case here instead of letting it reach the user.
153
+ if (isGenericNoDataFallback(context.text)) {
154
+ return {
155
+ retryMessage: NON_ANALYTICS_FALLBACK_RETRY_MESSAGE,
156
+ fallbackMessage: NON_ANALYTICS_FALLBACK_FINAL_MESSAGE,
157
+ maxRetries: 2,
158
+ };
159
+ }
160
+ return null;
161
+ }
128
162
  const incompleteEvidence = hasIncompleteDataEvidence(context.toolResults);
129
163
  if (
130
164
  hasFailedCorpusWorkflowEvidence(context.toolResults) &&
@@ -200,7 +234,7 @@ export function realDataFinalGuard(
200
234
  " If the right response is a clarification, plan, or explicit unavailable/credentials-missing message with no metrics or source-record claims, finalize that directly instead.",
201
235
  fallbackMessage: configuredSources.length
202
236
  ? `I found connected data sources (${configuredSources.join(", ")}), but the model still did not run a real source query. Please retry the request; you do not need to reconnect those sources.`
203
- : "I can't provide a grounded analytics result yet because no real data-source query ran successfully. Tell me which source to use or connect the missing source, and I'll run it before giving numbers or source-record conclusions.",
237
+ : GENERIC_NO_DATA_FALLBACK_MESSAGE,
204
238
  // Some models use separate turns for status, schema discovery, and the
205
239
  // actual query. One corrective turn was enough for Sonnet but caused Luna
206
240
  // to hit the fallback before it reached the query.
@@ -223,8 +257,7 @@ export default createAgentChatPlugin({
223
257
  // Operators deploying to trusted internal environments can set
224
258
  // AGENT_PROD_CODE_EXECUTION=trusted to also enable bash/read/edit/write.
225
259
  codeExecution: { production: "sandboxed" },
226
- durableBackgroundRuns: true,
227
- runSoftTimeoutMs: ANALYTICS_BACKGROUND_RUN_SOFT_TIMEOUT_MS,
260
+ connectorCatalog: [...ANALYTICS_CONNECTOR_CATALOG],
228
261
  resolveOrgId: async (event) => {
229
262
  const ctx = await getOrgContext(event);
230
263
  return ctx.orgId;
@@ -257,7 +257,7 @@
257
257
  [data-agent-empty-state="centered"]
258
258
  .agent-composer-area {
259
259
  width: 100%;
260
- max-width: min(570px, 90%);
260
+ max-width: min(684px, 90%);
261
261
  padding: 0;
262
262
  }
263
263
 
@@ -135,7 +135,7 @@
135
135
  .brain-chat-panel [data-agent-empty-state="centered"] .brain-chat-intro {
136
136
  display: grid;
137
137
  width: 100%;
138
- max-width: min(570px, 90%);
138
+ max-width: min(684px, 90%);
139
139
  gap: 0.5rem;
140
140
  margin: 0 auto 1rem;
141
141
  text-align: center;
@@ -165,7 +165,7 @@
165
165
 
166
166
  .brain-chat-panel [data-agent-empty-state="centered"] .agent-composer-area {
167
167
  width: 100%;
168
- max-width: min(570px, 90%);
168
+ max-width: min(684px, 90%);
169
169
  padding: 0;
170
170
  }
171
171
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Long AI responses now stay visibly in progress and automatically follow new output until you scroll away.
@@ -168,7 +168,7 @@ export function CloudUpgrade({
168
168
  key={p.id}
169
169
  onClick={() => setSelectedProvider(p.id)}
170
170
  className={cn(
171
- "flex flex-col items-start rounded-lg border px-3 py-2.5 text-left min-h-[44px]",
171
+ "flex flex-col items-start rounded-lg border px-3 py-2.5 text-left min-h-[44px] transition-[border-color,background-color,scale] duration-150 ease-out active:scale-[0.96] motion-reduce:active:scale-100",
172
172
  selectedProvider === p.id
173
173
  ? "border-primary bg-primary/5"
174
174
  : "border-border hover:border-primary/30",
@@ -22,13 +22,29 @@ export function ThemeToggle({ className }: { className?: string }) {
22
22
  variant="ghost"
23
23
  size="icon"
24
24
  onClick={() => setTheme(isDark ? "light" : "dark")}
25
- className={cn("text-muted-foreground", className)}
26
- >
27
- {isDark ? (
28
- <IconSun className="h-4 w-4" />
29
- ) : (
30
- <IconMoon className="h-4 w-4" />
25
+ className={cn(
26
+ "relative text-muted-foreground before:absolute before:-inset-1.5",
27
+ className,
31
28
  )}
29
+ >
30
+ <span className="relative inline-flex h-4 w-4 items-center justify-center">
31
+ <IconSun
32
+ className={cn(
33
+ "absolute inset-0 h-4 w-4 transition-[opacity,filter,scale] duration-200 ease-[cubic-bezier(0.2,0,0,1)]",
34
+ isDark
35
+ ? "scale-100 opacity-100 blur-none"
36
+ : "scale-[0.25] opacity-0 blur-[4px]",
37
+ )}
38
+ />
39
+ <IconMoon
40
+ className={cn(
41
+ "absolute inset-0 h-4 w-4 transition-[opacity,filter,scale] duration-200 ease-[cubic-bezier(0.2,0,0,1)]",
42
+ isDark
43
+ ? "scale-[0.25] opacity-0 blur-[4px]"
44
+ : "scale-100 opacity-100 blur-none",
45
+ )}
46
+ />
47
+ </span>
32
48
  </Button>
33
49
  </TooltipTrigger>
34
50
  <TooltipContent>{t("root.toggleTheme")}</TooltipContent>
@@ -70,7 +70,7 @@ export function FieldPropertiesPanel({
70
70
  <Button
71
71
  variant="ghost"
72
72
  size="sm"
73
- className="text-destructive h-7 px-2 text-xs"
73
+ className="h-10 px-3 text-xs text-destructive active:scale-[0.96]"
74
74
  onClick={onDelete}
75
75
  >
76
76
  {t("common.delete")}
@@ -181,13 +181,13 @@ export function FieldPropertiesPanel({
181
181
  <Button
182
182
  variant="ghost"
183
183
  size="sm"
184
- className="h-7 w-7 p-0"
184
+ className="relative h-10 w-10 shrink-0 p-0 transition-transform duration-150 ease-out before:absolute before:-inset-1 active:scale-[0.96] motion-reduce:active:scale-100 sm:h-7 sm:w-7"
185
185
  onClick={() => removeOption(i)}
186
186
  aria-label={t("fieldProperties.removeOption", {
187
187
  option: opt,
188
188
  })}
189
189
  >
190
- <IconX className="h-3 w-3" />
190
+ <IconX className="h-3.5 w-3.5 translate-y-px" />
191
191
  </Button>
192
192
  </div>
193
193
  ))}
@@ -202,11 +202,11 @@ export function FieldPropertiesPanel({
202
202
  <Button
203
203
  variant="ghost"
204
204
  size="sm"
205
- className="h-7 w-7 p-0"
205
+ className="relative h-10 w-10 shrink-0 p-0 transition-transform duration-150 ease-out before:absolute before:-inset-1 active:scale-[0.96] motion-reduce:active:scale-100 sm:h-7 sm:w-7"
206
206
  onClick={addOption}
207
207
  aria-label={t("fieldProperties.addOption")}
208
208
  >
209
- <IconPlus className="h-3 w-3" />
209
+ <IconPlus className="h-3.5 w-3.5 translate-y-px" />
210
210
  </Button>
211
211
  </div>
212
212
  </div>
@@ -204,14 +204,14 @@ export function FieldRenderer({
204
204
  key={star}
205
205
  type="button"
206
206
  onClick={() => !disabled && handleChange(star)}
207
- className="cursor-pointer p-2 sm:p-1.5 disabled:cursor-not-allowed"
207
+ className="flex size-11 cursor-pointer items-center justify-center rounded-md p-2 transition-[color,transform] duration-150 ease-out active:scale-[0.96] motion-reduce:active:scale-100 disabled:cursor-not-allowed sm:size-10 sm:p-1.5"
208
208
  disabled={disabled}
209
209
  aria-label={`${star} star${star !== 1 ? "s" : ""}`}
210
210
  >
211
211
  {(value as number) >= star ? (
212
- <IconStarFilled className="h-7 w-7 sm:h-6 sm:w-6 text-amber-400" />
212
+ <IconStarFilled className="h-7 w-7 text-amber-400 sm:h-6 sm:w-6" />
213
213
  ) : (
214
- <IconStar className="h-7 w-7 sm:h-6 sm:w-6 text-muted-foreground/30" />
214
+ <IconStar className="h-7 w-7 text-muted-foreground/30 sm:h-6 sm:w-6" />
215
215
  )}
216
216
  </button>
217
217
  ))}
@@ -131,7 +131,7 @@ export function Sidebar() {
131
131
 
132
132
  const newFormButton = (
133
133
  <PopoverTrigger asChild>
134
- <button className="flex min-h-[44px] w-full cursor-pointer items-center gap-2.5 rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground">
134
+ <button className="forms-sidebar-nav-item flex min-h-[44px] w-full cursor-pointer items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-muted-foreground active:scale-[0.96] transition-[background-color,box-shadow,color,transform] hover:bg-accent/50 hover:text-foreground">
135
135
  <IconPlus className="h-4 w-4 shrink-0" />
136
136
  <span>{t("sidebar.newForm")}</span>
137
137
  </button>
@@ -143,7 +143,7 @@ export function Sidebar() {
143
143
  side="right"
144
144
  align="start"
145
145
  sideOffset={8}
146
- className="w-80 p-0 rounded-xl"
146
+ className="forms-new-form-popover w-80 rounded-2xl p-0"
147
147
  >
148
148
  <div className="p-4 pb-3">
149
149
  <p className="text-sm font-semibold">{t("sidebar.newForm")}</p>
@@ -168,7 +168,7 @@ export function Sidebar() {
168
168
  <Button
169
169
  variant="link"
170
170
  size="sm"
171
- className="h-auto p-0 text-xs text-muted-foreground"
171
+ className="min-h-10 px-2 text-xs text-muted-foreground active:scale-[0.96] transition-[background-color,color,transform]"
172
172
  onClick={handleSkip}
173
173
  >
174
174
  {t("sidebar.skipPrompt")}
@@ -180,7 +180,7 @@ export function Sidebar() {
180
180
  <Button
181
181
  variant="secondary"
182
182
  size="icon"
183
- className="h-7 w-7"
183
+ className="size-10 rounded-lg transition-[background-color,box-shadow,transform] active:scale-[0.96] motion-reduce:active:scale-100"
184
184
  onClick={handleSubmitPrompt}
185
185
  disabled={!prompt.trim() || promptRun.isActivePrompt(prompt)}
186
186
  aria-label={t("sidebar.sendPrompt")}
@@ -200,7 +200,7 @@ export function Sidebar() {
200
200
  <button
201
201
  type="button"
202
202
  onClick={() => setCollapsed(false)}
203
- className="flex h-10 w-10 items-center justify-center rounded-md text-muted-foreground/55 transition-colors hover:bg-accent/50 hover:text-muted-foreground"
203
+ className="forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg text-muted-foreground/55 transition-[background-color,box-shadow,color,transform] duration-150 ease-out hover:bg-accent/50 hover:text-muted-foreground active:scale-[0.96] motion-reduce:active:scale-100"
204
204
  aria-label={t("sidebar.expandSidebar")}
205
205
  >
206
206
  <IconLayoutSidebarLeftExpand className="h-4 w-4 rtl:-scale-x-100" />
@@ -219,7 +219,7 @@ export function Sidebar() {
219
219
  onClick={navigateHomeChat}
220
220
  aria-label={t("navigation.askForms")}
221
221
  className={cn(
222
- "flex h-10 w-10 items-center justify-center rounded-md transition-colors",
222
+ "forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
223
223
  location.pathname === "/ask" || location.pathname === "/"
224
224
  ? "bg-accent text-accent-foreground"
225
225
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
@@ -239,7 +239,7 @@ export function Sidebar() {
239
239
  to="/forms"
240
240
  aria-label={t("navigation.allForms")}
241
241
  className={cn(
242
- "flex h-10 w-10 items-center justify-center rounded-md transition-colors",
242
+ "forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
243
243
  location.pathname.startsWith("/forms")
244
244
  ? "bg-accent text-accent-foreground"
245
245
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
@@ -260,7 +260,7 @@ export function Sidebar() {
260
260
  <button
261
261
  type="button"
262
262
  aria-label={t("sidebar.newForm")}
263
- className="flex h-10 w-10 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
263
+ className="forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg text-muted-foreground transition-[background-color,box-shadow,color,transform] duration-150 ease-out hover:bg-accent/50 hover:text-foreground active:scale-[0.96] motion-reduce:active:scale-100"
264
264
  >
265
265
  <IconPlus className="h-4 w-4" />
266
266
  </button>
@@ -279,7 +279,7 @@ export function Sidebar() {
279
279
  to="/settings"
280
280
  aria-label={t("navigation.settings")}
281
281
  className={cn(
282
- "flex h-10 w-10 items-center justify-center rounded-md transition-colors",
282
+ "forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
283
283
  location.pathname === "/settings"
284
284
  ? "bg-accent text-accent-foreground"
285
285
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
@@ -310,7 +310,7 @@ export function Sidebar() {
310
310
  <button
311
311
  type="button"
312
312
  aria-label={t("sidebar.openAskFullScreen")}
313
- className="flex min-w-0 items-center gap-2 rounded-md text-base font-semibold tracking-tight text-muted-foreground/80 transition-colors hover:text-foreground/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
313
+ className="flex min-h-10 min-w-0 items-center gap-2 rounded-lg px-2 text-base font-semibold tracking-tight text-muted-foreground/80 active:scale-[0.96] transition-[color,transform] hover:text-foreground/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
314
314
  onClick={toggleLogoView}
315
315
  >
316
316
  <img
@@ -337,7 +337,7 @@ export function Sidebar() {
337
337
  <Button
338
338
  variant="ghost"
339
339
  size="icon"
340
- className="h-8 w-8"
340
+ className="size-10 transition-[background-color,box-shadow,transform] active:scale-[0.96] motion-reduce:active:scale-100"
341
341
  onClick={() => setMobileOpen(false)}
342
342
  >
343
343
  <IconX size={18} />
@@ -349,7 +349,7 @@ export function Sidebar() {
349
349
  <Button
350
350
  variant="ghost"
351
351
  size="icon"
352
- className="h-8 w-8 text-muted-foreground/55 hover:bg-accent/50 hover:text-muted-foreground"
352
+ className="size-10 text-muted-foreground/55 transition-[background-color,box-shadow,color,transform] hover:bg-accent/50 hover:text-muted-foreground active:scale-[0.96] motion-reduce:active:scale-100"
353
353
  onClick={() => setCollapsed(true)}
354
354
  aria-label={t("sidebar.collapseSidebar")}
355
355
  >
@@ -374,7 +374,7 @@ export function Sidebar() {
374
374
  to="/ask"
375
375
  onClick={navigateHomeChat}
376
376
  className={cn(
377
- "flex min-h-[44px] w-full min-w-0 max-w-full items-center gap-2.5 overflow-hidden rounded-md px-3 py-2 text-sm transition-colors hover:text-primary",
377
+ "forms-sidebar-nav-item flex min-h-[44px] w-full min-w-0 max-w-full items-center gap-2.5 overflow-hidden rounded-lg px-3 py-2 text-sm active:scale-[0.96] transition-[background-color,box-shadow,color,transform] hover:text-primary",
378
378
  location.pathname === "/ask" || location.pathname === "/"
379
379
  ? "bg-accent text-accent-foreground"
380
380
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
@@ -390,7 +390,7 @@ export function Sidebar() {
390
390
  to="/forms"
391
391
  onClick={() => isMobile && setMobileOpen(false)}
392
392
  className={cn(
393
- "flex min-h-[44px] w-full min-w-0 max-w-full items-center gap-2.5 overflow-hidden rounded-md px-3 py-2 text-sm transition-colors hover:text-primary",
393
+ "forms-sidebar-nav-item flex min-h-[44px] w-full min-w-0 max-w-full items-center gap-2.5 overflow-hidden rounded-lg px-3 py-2 text-sm active:scale-[0.96] transition-[background-color,box-shadow,color,transform] hover:text-primary",
394
394
  location.pathname.startsWith("/forms")
395
395
  ? "bg-accent text-accent-foreground"
396
396
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
@@ -415,7 +415,7 @@ export function Sidebar() {
415
415
  to="/settings"
416
416
  onClick={() => isMobile && setMobileOpen(false)}
417
417
  className={cn(
418
- "flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm min-h-[44px]",
418
+ "forms-sidebar-nav-item flex min-h-[44px] w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
419
419
  location.pathname === "/settings"
420
420
  ? "bg-accent text-accent-foreground"
421
421
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
@@ -449,7 +449,7 @@ export function Sidebar() {
449
449
  <Button
450
450
  variant="ghost"
451
451
  size="icon"
452
- className="fixed top-2 start-2 z-40 h-10 w-10 md:hidden"
452
+ className="fixed top-2 start-2 z-40 size-10 active:scale-[0.96] transition-[background-color,box-shadow,transform] md:hidden"
453
453
  onClick={() => setMobileOpen(true)}
454
454
  aria-label={t("sidebar.openSidebar")}
455
455
  >