@agent-native/core 0.169.0 → 0.169.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/bigquery-table-info.ts +3 -2
  3. package/corpus/templates/analytics/actions/search-bigquery-schema.ts +130 -4
  4. package/corpus/templates/analytics/agent-native.config.ts +1 -0
  5. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +36 -23
  6. package/corpus/templates/analytics/app/lib/dashboard-visibility.ts +41 -0
  7. package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +34 -9
  8. package/corpus/templates/analytics/changelog/2026-08-21-dashboard-filters-keep-other-users-dashboards-out-of-mine.md +6 -0
  9. package/corpus/templates/analytics/server/lib/real-data-actions.ts +14 -3
  10. package/corpus/templates/analytics/server/plugins/agent-chat.ts +33 -3
  11. package/corpus/templates/brain/server/lib/brain.ts +27 -2
  12. package/corpus/templates/clips/actions/stop-meeting-recording.ts +87 -6
  13. package/corpus/templates/clips/app/components/library/search-bar.tsx +1 -1
  14. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +3 -4
  15. package/corpus/templates/design/app/components/design/EditPanel.tsx +15 -47
  16. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -3
  17. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +0 -7
  18. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +2 -4
  19. package/corpus/templates/design/app/i18n-data.ts +10 -10
  20. package/corpus/templates/design/app/pages/DesignEditor.tsx +22 -22
  21. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +39 -8
  22. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
  23. package/corpus/templates/design/app/pages/Index.tsx +1 -1
  24. package/corpus/templates/design/app/pages/design-editor/preview-navigation.ts +25 -0
  25. package/corpus/templates/mail/app/hooks/use-emails.ts +13 -3
  26. package/dist/agent/engine/credential-errors.d.ts +1 -1
  27. package/dist/agent/engine/credential-errors.js +2 -2
  28. package/dist/client/AssistantChat.js +47 -15
  29. package/dist/client/ConnectBuilderCard.d.ts +3 -1
  30. package/dist/client/ConnectBuilderCard.js +6 -3
  31. package/dist/client/chat/message-components.d.ts +7 -0
  32. package/dist/client/chat/message-components.js +1 -1
  33. package/dist/client/chat/run-recovery.js +5 -2
  34. package/dist/client/chat/tool-call-display.js +9 -3
  35. package/dist/client/error-format.js +12 -0
  36. package/dist/client/navigation/index.d.ts +1 -0
  37. package/dist/client/navigation/index.js +1 -0
  38. package/dist/client/route-state.js +6 -0
  39. package/dist/client/workspace-app-navigation.d.ts +10 -0
  40. package/dist/client/workspace-app-navigation.js +38 -0
  41. package/dist/collab/struct-routes.d.ts +1 -1
  42. package/dist/deploy/build.d.ts +13 -0
  43. package/dist/deploy/build.js +51 -0
  44. package/dist/localization/core-messages/ar-SA.js +1 -1
  45. package/dist/localization/core-messages/de-DE.js +1 -1
  46. package/dist/localization/core-messages/en-US.d.ts +1 -1
  47. package/dist/localization/core-messages/en-US.js +1 -1
  48. package/dist/localization/core-messages/es-ES.js +1 -1
  49. package/dist/localization/core-messages/fr-FR.js +1 -1
  50. package/dist/localization/core-messages/hi-IN.js +1 -1
  51. package/dist/localization/core-messages/ja-JP.js +1 -1
  52. package/dist/localization/core-messages/ko-KR.js +1 -1
  53. package/dist/localization/core-messages/pt-BR.js +1 -1
  54. package/dist/localization/core-messages/zh-CN.js +1 -1
  55. package/dist/localization/core-messages/zh-TW.js +1 -1
  56. package/dist/localization/core-messages.d.ts +1 -1
  57. package/dist/notifications/routes.d.ts +1 -1
  58. package/dist/observability/routes.d.ts +3 -3
  59. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  60. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  61. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  62. package/dist/resources/handlers.d.ts +1 -1
  63. package/dist/scripts/db/check-scoping.js +9 -6
  64. package/dist/secrets/routes.d.ts +6 -6
  65. package/dist/server/builder-browser.d.ts +4 -0
  66. package/dist/server/builder-browser.js +23 -1
  67. package/dist/server/core-routes-plugin.js +13 -1
  68. package/dist/server/realtime-token.d.ts +1 -1
  69. package/dist/server/transcribe-voice.d.ts +1 -1
  70. package/package.json +3 -3
  71. /package/corpus/templates/dispatch/app/routes/{apps.$appId.tsx → apps.$appId.$.tsx} +0 -0
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8522
34
+ - template files: 8525
@@ -9,12 +9,12 @@ Use these sources of truth instead:
9
9
 
10
10
  - \`list-data-dictionary\` for approved metric, table, column, join, and caveat definitions configured by this organization.
11
11
  - \`data-source-status --key bigquery\` to confirm whether BigQuery credentials and a project are configured.
12
- - \`search-bigquery-schema\` to list configured datasets/tables or inspect exact table columns before writing SQL.
12
+ - \`search-bigquery-schema\` to search the configured project by dataset, table, or column term, or inspect exact table columns before writing SQL.
13
13
  - The configured warehouse's \`INFORMATION_SCHEMA.COLUMNS\` tables when you need to discover actual datasets, tables, and columns.
14
14
  - \`@app_events\` as an optional placeholder for a default application events table in query-metrics examples. It defaults to \`<BIGQUERY_PROJECT_ID>.analytics.events_partitioned\` and can be overridden with \`ANALYTICS_BIGQUERY_EVENTS_TABLE\`; it is not the BigQuery connection status or a table catalog.
15
15
  - \`@project\` as the configured BigQuery project placeholder for fully-qualified table references.
16
16
 
17
- If no data-dictionary entry exists and schema discovery is unavailable, ask the user for the source table or metric definition before writing SQL. Never invent table names, column names, joins, or numbers.
17
+ If no data-dictionary entry exists, search the configured project before asking about business meaning. Ask the user for a metric definition only after the configured schema and query actions prove that the source or metric is unavailable. Never ask the user to provide internal dataset, table, or column names that the schema action can discover, and never invent table names, column names, joins, or numbers.
18
18
 
19
19
  Preferred schema discovery:
20
20
 
@@ -22,6 +22,7 @@ Preferred schema discovery:
22
22
  pnpm action search-bigquery-schema
23
23
  pnpm action search-bigquery-schema --dataset=<dataset>
24
24
  pnpm action search-bigquery-schema --table=<dataset.table>
25
+ pnpm action search-bigquery-schema --search=<metric-or-column-term>
25
26
  \`\`\`
26
27
 
27
28
  Example schema discovery query:
@@ -35,6 +35,15 @@ interface TableListResponse {
35
35
  }>;
36
36
  }
37
37
 
38
+ interface BigQueryTableSummary {
39
+ projectId?: string;
40
+ datasetId?: string;
41
+ tableId?: string;
42
+ type?: string;
43
+ friendlyName?: string;
44
+ labels?: Record<string, string>;
45
+ }
46
+
38
47
  interface TableMetadata {
39
48
  tableReference?: {
40
49
  projectId?: string;
@@ -54,6 +63,9 @@ interface TableMetadata {
54
63
 
55
64
  const PROJECT_RE = /^[A-Za-z][A-Za-z0-9-]{4,61}[A-Za-z0-9]$/;
56
65
  const ID_RE = /^[A-Za-z0-9_]+$/;
66
+ const GLOBAL_SEARCH_DATASET_LIMIT = 100;
67
+ const GLOBAL_SEARCH_TABLE_LIMIT = 250;
68
+ const GLOBAL_SEARCH_METADATA_BATCH_SIZE = 20;
57
69
 
58
70
  function assertIdentifier(
59
71
  label: string,
@@ -183,7 +195,12 @@ function compactTable(meta: TableMetadata, includeColumns: boolean) {
183
195
  }
184
196
 
185
197
  function matchesSearch(meta: TableMetadata, search: string): boolean {
186
- const q = search.toLowerCase();
198
+ const terms = search
199
+ .toLowerCase()
200
+ .replace(/[._-]+/g, " ")
201
+ .split(/\s+/)
202
+ .filter(Boolean);
203
+ if (!terms.length) return true;
187
204
  const ref = meta.tableReference ?? {};
188
205
  const haystack = [
189
206
  ref.projectId,
@@ -199,8 +216,9 @@ function matchesSearch(meta: TableMetadata, search: string): boolean {
199
216
  ]
200
217
  .filter(Boolean)
201
218
  .join(" ")
202
- .toLowerCase();
203
- return haystack.includes(q);
219
+ .toLowerCase()
220
+ .replace(/[._-]+/g, " ");
221
+ return terms.every((term) => haystack.includes(term));
204
222
  }
205
223
 
206
224
  async function listDatasets(projectId: string, limit: number, search: string) {
@@ -245,6 +263,111 @@ async function listTables(projectId: string, datasetId: string, limit: number) {
245
263
  }));
246
264
  }
247
265
 
266
+ async function searchAcrossDatasets(
267
+ projectId: string,
268
+ search: string,
269
+ limit: number,
270
+ ) {
271
+ const datasets = await listDatasets(
272
+ projectId,
273
+ GLOBAL_SEARCH_DATASET_LIMIT + 1,
274
+ "",
275
+ );
276
+ const scannableDatasets = datasets.slice(0, GLOBAL_SEARCH_DATASET_LIMIT);
277
+ const tables: BigQueryTableSummary[] = [];
278
+ const datasetCount = scannableDatasets.filter(
279
+ (dataset) => typeof dataset.datasetId === "string" && dataset.datasetId,
280
+ ).length;
281
+ let datasetsScanned = 0;
282
+ let truncated = datasets.length > GLOBAL_SEARCH_DATASET_LIMIT;
283
+
284
+ for (const dataset of scannableDatasets) {
285
+ const datasetId = dataset.datasetId;
286
+ if (!datasetId) continue;
287
+ if (tables.length >= GLOBAL_SEARCH_TABLE_LIMIT) {
288
+ truncated = true;
289
+ break;
290
+ }
291
+
292
+ datasetsScanned += 1;
293
+ const remaining = GLOBAL_SEARCH_TABLE_LIMIT - tables.length;
294
+ const listed = await listTables(
295
+ projectId,
296
+ datasetId,
297
+ Math.min(remaining, GLOBAL_SEARCH_TABLE_LIMIT),
298
+ );
299
+ tables.push(...listed);
300
+ }
301
+
302
+ if (datasetsScanned < datasetCount) truncated = true;
303
+
304
+ // Inspect table metadata in bounded batches so a search can match columns,
305
+ // not only table names, without issuing an unbounded burst of requests.
306
+ const matches: ReturnType<typeof compactTable>[] = [];
307
+ const errors: Array<{
308
+ projectId?: string;
309
+ datasetId?: string;
310
+ tableId?: string;
311
+ error: string;
312
+ }> = [];
313
+
314
+ for (
315
+ let offset = 0;
316
+ offset < tables.length;
317
+ offset += GLOBAL_SEARCH_METADATA_BATCH_SIZE
318
+ ) {
319
+ const batch = tables.slice(
320
+ offset,
321
+ offset + GLOBAL_SEARCH_METADATA_BATCH_SIZE,
322
+ );
323
+ const settled = await Promise.allSettled(
324
+ batch.map(async (table) => {
325
+ if (!table.datasetId || !table.tableId) return null;
326
+ const metadata = await getTableMetadata(
327
+ projectId,
328
+ table.datasetId,
329
+ table.tableId,
330
+ );
331
+ return matchesSearch(metadata, search)
332
+ ? compactTable(metadata, true)
333
+ : null;
334
+ }),
335
+ );
336
+
337
+ settled.forEach((result, index) => {
338
+ const table = batch[index];
339
+ if (result.status === "fulfilled") {
340
+ if (result.value) matches.push(result.value);
341
+ return;
342
+ }
343
+ errors.push({
344
+ projectId: table?.projectId ?? projectId,
345
+ datasetId: table?.datasetId,
346
+ tableId: table?.tableId,
347
+ error:
348
+ result.reason instanceof Error
349
+ ? result.reason.message
350
+ : String(result.reason),
351
+ });
352
+ });
353
+ }
354
+
355
+ return {
356
+ mode: "table-search",
357
+ projectId,
358
+ search,
359
+ datasetsScanned,
360
+ tablesScanned: tables.length,
361
+ truncated,
362
+ tables: matches.slice(0, limit),
363
+ ...(errors.length
364
+ ? { errors: errors.slice(0, 12), errorCount: errors.length }
365
+ : {}),
366
+ nextStep:
367
+ "Use table=dataset.table for full metadata. Global search is bounded; pass a returned dataset/table reference for a complete inspection.",
368
+ };
369
+ }
370
+
248
371
  async function getTableMetadata(
249
372
  projectId: string,
250
373
  datasetId: string,
@@ -256,7 +379,7 @@ async function getTableMetadata(
256
379
 
257
380
  export default defineAction({
258
381
  description:
259
- "Search or describe BigQuery metadata for the configured warehouse. Use before writing SQL when the data dictionary does not already name the dataset, table, and columns. With no args, lists datasets. With dataset, lists tables. With dataset + table or a dataset.table value, returns columns for that table.",
382
+ "Search or describe BigQuery metadata for the configured warehouse. Use before writing SQL when the data dictionary does not already name the dataset, table, and columns. With no args, lists datasets. With search and no dataset, searches accessible tables and columns across the configured project so the user does not need to provide internal table names. With dataset, lists tables. With dataset + table or a dataset.table value, returns columns for that table.",
260
383
  schema: z.object({
261
384
  dataset: z
262
385
  .string()
@@ -307,6 +430,9 @@ export default defineAction({
307
430
  }
308
431
 
309
432
  if (!args.dataset) {
433
+ if (search) {
434
+ return searchAcrossDatasets(configuredProjectId, search, limit);
435
+ }
310
436
  return {
311
437
  mode: "datasets",
312
438
  projectId: configuredProjectId,
@@ -1,5 +1,6 @@
1
1
  import { defineAgentNativeConfig } from "@agent-native/core/config";
2
2
 
3
3
  export default defineAgentNativeConfig({
4
+ changelog: { enabled: true },
4
5
  harness: true,
5
6
  });
@@ -46,6 +46,11 @@ import { toast } from "sonner";
46
46
 
47
47
  import { useAuth } from "@/components/auth/AuthProvider";
48
48
  import { ANALYTICS_CHAT_STORAGE_KEY } from "@/lib/chat-handoff";
49
+ import {
50
+ matchesDashboardVisibilityFilter,
51
+ type DashboardVisibility,
52
+ type DashboardVisibilityFilter,
53
+ } from "@/lib/dashboard-visibility";
49
54
  import { cn, shortcutModifierLabel } from "@/lib/utils";
50
55
  import {
51
56
  dashboards,
@@ -63,6 +68,7 @@ type SidebarDashboard = {
63
68
  source: "static" | "sql" | "analysis";
64
69
  resourceId?: string;
65
70
  visibility?: Visibility;
71
+ ownerEmail?: string | null;
66
72
  /** Id of the dashboard this one nests under in the sidebar, if any. */
67
73
  parentId?: string;
68
74
  };
@@ -162,7 +168,7 @@ const SIDEBAR_SKELETON_CLASS =
162
168
  "bg-sidebar-foreground/12 dark:bg-sidebar-foreground/10";
163
169
 
164
170
  type SidebarSortMode = "most-used" | "alphabetical" | "manual";
165
- type SidebarVisibilityFilter = "all" | "private" | "shared";
171
+ type SidebarVisibilityFilter = DashboardVisibilityFilter;
166
172
 
167
173
  import {
168
174
  DndContext,
@@ -258,14 +264,11 @@ function isVisibility(value: unknown): value is Visibility {
258
264
  }
259
265
 
260
266
  export function matchesVisibilityFilter(
261
- item: { visibility?: Visibility },
267
+ item: { visibility?: Visibility; ownerEmail?: string | null },
262
268
  filter: SidebarVisibilityFilter,
269
+ currentUserEmail?: string | null,
263
270
  ): boolean {
264
- if (filter === "all") return true;
265
- if (filter === "private") {
266
- return item.visibility !== "org" && item.visibility !== "public";
267
- }
268
- return item.visibility === "org" || item.visibility === "public";
271
+ return matchesDashboardVisibilityFilter(item, filter, currentUserEmail);
269
272
  }
270
273
 
271
274
  export function threadMatchesVisibilityFilter(
@@ -447,7 +450,7 @@ function SidebarSectionSettingsPopover({
447
450
 
448
451
  // --- Visibility types and helpers ---
449
452
 
450
- type Visibility = "private" | "org" | "public";
453
+ type Visibility = DashboardVisibility;
451
454
 
452
455
  // --- Shared sortable row (used by both dashboards and analyses) ---
453
456
 
@@ -1143,6 +1146,7 @@ type SqlDashboardListItem = {
1143
1146
  id: string;
1144
1147
  name: string;
1145
1148
  visibility?: Visibility;
1149
+ ownerEmail?: string | null;
1146
1150
  parentId?: string;
1147
1151
  };
1148
1152
 
@@ -1160,18 +1164,25 @@ async function fetchSqlDashboards(
1160
1164
  d.visibility === "org" ||
1161
1165
  d.visibility === "public"),
1162
1166
  )
1163
- .map((d: any) => ({
1164
- id: d.id,
1165
- name:
1166
- typeof d.name === "string" && d.name.trim().length > 0
1167
- ? d.name
1168
- : t("sidebar.untitledDashboard"),
1169
- visibility: d.visibility as Visibility,
1170
- parentId:
1171
- typeof d.parentId === "string" && d.parentId.trim().length > 0
1172
- ? d.parentId
1173
- : undefined,
1174
- }));
1167
+ .map((d: any) => {
1168
+ const ownerEmail =
1169
+ typeof d.ownerEmail === "string" && d.ownerEmail.trim().length > 0
1170
+ ? d.ownerEmail
1171
+ : undefined;
1172
+ return {
1173
+ id: d.id,
1174
+ name:
1175
+ typeof d.name === "string" && d.name.trim().length > 0
1176
+ ? d.name
1177
+ : t("sidebar.untitledDashboard"),
1178
+ visibility: d.visibility as Visibility,
1179
+ ...(ownerEmail ? { ownerEmail } : {}),
1180
+ parentId:
1181
+ typeof d.parentId === "string" && d.parentId.trim().length > 0
1182
+ ? d.parentId
1183
+ : undefined,
1184
+ };
1185
+ });
1175
1186
  }
1176
1187
 
1177
1188
  async function fetchSidebarAnalyses(t: (key: string) => string): Promise<
@@ -1525,7 +1536,8 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1525
1536
  activeDashboardId !== null,
1526
1537
  );
1527
1538
  const [dashShowAll, setDashShowAll] = useState(false);
1528
- const [dashFilter, setDashFilter] = useState<SidebarVisibilityFilter>("all");
1539
+ const [dashFilter, setDashFilter] =
1540
+ useState<SidebarVisibilityFilter>("private");
1529
1541
  const [dashboardSortMode, setDashboardSortModeState] =
1530
1542
  useState<SidebarSortMode>(() => getStoredSortMode(DASHBOARD_SORT_MODE_KEY));
1531
1543
  const { data: popularity, isReady: popularityReady } = usePopularity();
@@ -1749,6 +1761,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1749
1761
  name: d.name,
1750
1762
  source: "sql",
1751
1763
  visibility: d.visibility,
1764
+ ownerEmail: d.ownerEmail,
1752
1765
  parentId: d.parentId,
1753
1766
  }));
1754
1767
  const analysisItems: SidebarDashboard[] = analysesList.map((a) => ({
@@ -1802,9 +1815,9 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1802
1815
  const filteredDashboards = useMemo(
1803
1816
  () =>
1804
1817
  visibleDashboards.filter((dashboard) =>
1805
- matchesVisibilityFilter(dashboard, dashFilter),
1818
+ matchesVisibilityFilter(dashboard, dashFilter, auth?.email),
1806
1819
  ),
1807
- [visibleDashboards, dashFilter],
1820
+ [auth?.email, visibleDashboards, dashFilter],
1808
1821
  );
1809
1822
 
1810
1823
  // Group dashboards that declare a parentId beneath their parent. Nesting is
@@ -0,0 +1,41 @@
1
+ export type DashboardVisibility = "private" | "org" | "public";
2
+ export type DashboardVisibilityFilter = "all" | "private" | "shared";
3
+
4
+ export type DashboardVisibilityItem = {
5
+ visibility?: DashboardVisibility;
6
+ ownerEmail?: string | null;
7
+ };
8
+
9
+ function normalizeEmail(email: string | null | undefined): string | null {
10
+ const normalized = email?.trim().toLowerCase();
11
+ return normalized ? normalized : null;
12
+ }
13
+
14
+ export function isDashboardMine(
15
+ item: DashboardVisibilityItem,
16
+ currentUserEmail?: string | null,
17
+ ): boolean {
18
+ if (item.visibility === "org" || item.visibility === "public") {
19
+ return false;
20
+ }
21
+
22
+ if ("ownerEmail" in item) {
23
+ const ownerEmail = normalizeEmail(item.ownerEmail);
24
+ const viewerEmail = normalizeEmail(currentUserEmail);
25
+ return Boolean(ownerEmail && viewerEmail && ownerEmail === viewerEmail);
26
+ }
27
+
28
+ // Older dashboard-shaped items do not always carry ownership metadata.
29
+ return true;
30
+ }
31
+
32
+ export function matchesDashboardVisibilityFilter(
33
+ item: DashboardVisibilityItem,
34
+ filter: DashboardVisibilityFilter,
35
+ currentUserEmail?: string | null,
36
+ ): boolean {
37
+ if (filter === "all") return true;
38
+ return filter === "private"
39
+ ? isDashboardMine(item, currentUserEmail)
40
+ : !isDashboardMine(item, currentUserEmail);
41
+ }
@@ -56,6 +56,10 @@ import {
56
56
  SelectValue,
57
57
  } from "@/components/ui/select";
58
58
  import { Skeleton } from "@/components/ui/skeleton";
59
+ import {
60
+ isDashboardMine,
61
+ type DashboardVisibility,
62
+ } from "@/lib/dashboard-visibility";
59
63
  import { dashboardCacheScope } from "@/lib/prefetch-keys";
60
64
 
61
65
  import { NewDashboardDialog } from "../components/layout/NewDashboardDialog";
@@ -65,7 +69,8 @@ type Dashboard = {
65
69
  id: string;
66
70
  name?: string;
67
71
  title?: string;
68
- visibility?: string;
72
+ visibility?: DashboardVisibility;
73
+ ownerEmail?: string | null;
69
74
  folderId?: string | null;
70
75
  };
71
76
  type Folder = {
@@ -125,10 +130,15 @@ export default function DashboardOverview() {
125
130
  ) {
126
131
  continue;
127
132
  }
133
+ const ownerEmail =
134
+ typeof dashboard.ownerEmail === "string"
135
+ ? dashboard.ownerEmail
136
+ : undefined;
128
137
  validDashboards.push({
129
138
  id: dashboard.id,
130
139
  name: dashboard.name,
131
140
  visibility: dashboard.visibility,
141
+ ...(ownerEmail ? { ownerEmail } : {}),
132
142
  folderId: dashboard.folderId ?? null,
133
143
  });
134
144
  }
@@ -394,6 +404,7 @@ export default function DashboardOverview() {
394
404
  folders={folders}
395
405
  grouped={grouped}
396
406
  onMove={moveDashboard}
407
+ currentUserEmail={auth?.email}
397
408
  t={t}
398
409
  />
399
410
  ))}
@@ -428,6 +439,7 @@ function DashboardScopeSection({
428
439
  folders,
429
440
  grouped,
430
441
  onMove,
442
+ currentUserEmail,
431
443
  t,
432
444
  }: {
433
445
  scope: Scope;
@@ -435,11 +447,12 @@ function DashboardScopeSection({
435
447
  folders: Folder[];
436
448
  grouped: Map<string, Dashboard[]>;
437
449
  onMove: (dashboard: Dashboard, folderId: string | null) => void;
450
+ currentUserEmail?: string | null;
438
451
  t: ReturnType<typeof useT>;
439
452
  }) {
440
453
  const scopeFolders = folders.filter((folder) => folder.scope === scope);
441
454
  const scopeDashboards = dashboards.filter((dashboard) =>
442
- isDashboardInScope(dashboard, scope),
455
+ isDashboardInScope(dashboard, scope, currentUserEmail),
443
456
  );
444
457
  const Icon = scope === "personal" ? IconLock : IconUsers;
445
458
 
@@ -476,6 +489,7 @@ function DashboardScopeSection({
476
489
  folders={folders}
477
490
  scope={scope}
478
491
  onMove={onMove}
492
+ currentUserEmail={currentUserEmail}
479
493
  t={t}
480
494
  />
481
495
  ))}
@@ -485,6 +499,7 @@ function DashboardScopeSection({
485
499
  folders={folders}
486
500
  scope={scope}
487
501
  onMove={onMove}
502
+ currentUserEmail={currentUserEmail}
488
503
  t={t}
489
504
  hideWhenEmpty
490
505
  />
@@ -500,6 +515,7 @@ function FolderSection({
500
515
  folders,
501
516
  scope,
502
517
  onMove,
518
+ currentUserEmail,
503
519
  t,
504
520
  hideWhenEmpty = false,
505
521
  }: {
@@ -508,11 +524,12 @@ function FolderSection({
508
524
  folders: Folder[];
509
525
  scope: Scope;
510
526
  onMove: (dashboard: Dashboard, folderId: string | null) => void;
527
+ currentUserEmail?: string | null;
511
528
  t: ReturnType<typeof useT>;
512
529
  hideWhenEmpty?: boolean;
513
530
  }) {
514
531
  const scopedDashboards = dashboards.filter((dashboard) =>
515
- isDashboardInScope(dashboard, scope),
532
+ isDashboardInScope(dashboard, scope, currentUserEmail),
516
533
  );
517
534
  if (hideWhenEmpty && scopedDashboards.length === 0) return null;
518
535
 
@@ -540,6 +557,7 @@ function FolderSection({
540
557
  folders={folders}
541
558
  currentFolderId={folder?.id ?? null}
542
559
  onMove={onMove}
560
+ currentUserEmail={currentUserEmail}
543
561
  t={t}
544
562
  />
545
563
  ))}
@@ -554,23 +572,26 @@ function DashboardRow({
554
572
  folders,
555
573
  currentFolderId,
556
574
  onMove,
575
+ currentUserEmail,
557
576
  t,
558
577
  }: {
559
578
  dashboard: Dashboard;
560
579
  folders: Folder[];
561
580
  currentFolderId: string | null;
562
581
  onMove: (dashboard: Dashboard, folderId: string | null) => void;
582
+ currentUserEmail?: string | null;
563
583
  t: ReturnType<typeof useT>;
564
584
  }) {
565
585
  const name =
566
586
  dashboard.name || dashboard.title || t("sidebar.untitledDashboard");
587
+ const mine = isDashboardMine(dashboard, currentUserEmail);
567
588
  const candidateFolders = folders.filter((folder) => {
568
- if (dashboard.visibility === "private") {
589
+ if (mine) {
569
590
  return folder.scope === "personal";
570
591
  }
571
592
  return dashboard.visibility === "org" && folder.scope === "shared";
572
593
  });
573
- const canOrganize = dashboard.visibility !== "public";
594
+ const canOrganize = mine || dashboard.visibility === "org";
574
595
 
575
596
  return (
576
597
  <div className="group flex items-center gap-3 px-4 py-3 transition-colors hover:bg-muted/30">
@@ -586,7 +607,7 @@ function DashboardRow({
586
607
  {name}
587
608
  </p>
588
609
  <p className="truncate text-xs text-muted-foreground">
589
- {dashboard.visibility === "private"
610
+ {mine
590
611
  ? t("dashboardOverview.personal")
591
612
  : t("dashboardOverview.shared")}
592
613
  </p>
@@ -635,8 +656,12 @@ function DashboardRow({
635
656
  );
636
657
  }
637
658
 
638
- function isDashboardInScope(dashboard: Dashboard, scope: Scope): boolean {
659
+ function isDashboardInScope(
660
+ dashboard: Dashboard,
661
+ scope: Scope,
662
+ currentUserEmail?: string | null,
663
+ ): boolean {
639
664
  return scope === "personal"
640
- ? dashboard.visibility === "private"
641
- : dashboard.visibility === "org" || dashboard.visibility === "public";
665
+ ? isDashboardMine(dashboard, currentUserEmail)
666
+ : !isDashboardMine(dashboard, currentUserEmail);
642
667
  }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-21
4
+ ---
5
+
6
+ Dashboard filters keep other users' dashboards out of Mine
@@ -286,10 +286,13 @@ function looksLikeWorkflowOrAutomationRequest(lower: string): boolean {
286
286
  }
287
287
 
288
288
  const ANALYTICS_RESULT_TERMS =
289
- /\b(conversion|conversions|funnel|revenue|payment|payments|traffic|pageviews?|signups?|events?|active users?|sessions?|retention|churn|pipeline|deals?|calls?|transcripts?|sentiment|themes?|objections?|cohorts?|segments?|accounts?|customers?|tickets?|issues?|leads?|opportunities|mrr|arr|ctr|cvr|cac|ltv)\b/;
289
+ /\b(conversion|conversions|funnel|revenue|payment|payments|traffic|pageviews?|signups?|events?|active users?|sessions?|retention|churn|pipeline|deals?|calls?|transcripts?|sentiment|themes?|objections?|cohorts?|segments?|accounts?|customers?|tickets?|issues?|leads?|opportunities|usage|adoption|ai credits?|credit consumption|credits? consumed|allowance|quota|mrr|arr|ctr|cvr|cac|ltv)\b/;
290
+
291
+ const ANALYTICS_DOMAIN_ENTITY_TERMS =
292
+ /\b(?:branch(?:es)?|branch creation|created by|creator identity)\b/;
290
293
 
291
294
  const ANALYTICS_INTENT_TERMS =
292
- /\b(analy[sz]e|measure|calculate|query|report|summari[sz]e|break ?down|compare|rank|segment|forecast|trend|count|total|average|median|percent(?:age)?|rate|top|bottom|highest|lowest|how many|how much|what (?:is|are|was|were)|which|why)\b/;
295
+ /\b(analy[sz]e|measure|calculate|query|report|summari[sz]e|break ?down|compare|rank|segment|forecast|trend|count|total|average|median|percent(?:age)?|rate|top|bottom|highest|lowest|pull|fetch|retrieve|export|show|how many|how much|what (?:is|are|was|were)|which|why)\b/;
293
296
 
294
297
  const SOURCE_SEARCH_INTENT_TERMS =
295
298
  /\b(find|surface|search|scan|grep|review|inspect|check|look through|go find)\b/;
@@ -308,7 +311,7 @@ const ARTIFACT_DATA_INTENT =
308
311
  // a live provider data call. They should be answered from the data dictionary,
309
312
  // schema introspection tools, or the agent's knowledge of configured sources.
310
313
  const METADATA_ONLY_TERMS =
311
- /\b(what (?:tables?|columns?|fields?|sources?|datasets?|metrics?|schema) (?:are|is|exist|available|do (?:we|you|i) have)|which (?:sources?|tables?|providers?|integrations?) (?:are|is) (?:connected|configured|available|set up)|list (?:the )?(?:tables?|columns?|fields?|sources?|datasets?|schemas?)|show (?:me )?(?:available|the) (?:sources?|tables?|schemas?)|what does .+ (?:mean|measure|represent|track)|how is .+ (?:defined|calculated|computed|measured)|definition of|describe (?:the )?(?:\w+\s+)?(?:table|column|schema|metric|field)|list (?:the )?columns?\s+in|what (?:is|are) (?:the )?(?:data (?:dictionary|schema)|available (?:sources?|tables?))|what (?:source|provider|table) (?:has|stores|contains))\b/;
314
+ /\b(what (?:tables?|columns?|fields?|sources?|datasets?|metrics?|schema) (?:are|is|exist|available|do (?:we|you|i) have)|which (?:sources?|tables?|providers?|integrations?) (?:are|is) (?:connected|configured|available|set up)|list (?:the )?(?:tables?|columns?|fields?|sources?|datasets?|schemas?)|show (?:me )?(?:available|the) (?:data )?(?:sources?|tables?|schemas?)|what does .+ (?:mean|measure|represent|track)|how is .+ (?:defined|calculated|computed|measured)|definition of|describe (?:the )?(?:\w+\s+)?(?:table|column|schema|metric|field)|list (?:the )?columns?\s+in|what (?:is|are) (?:the )?(?:data (?:dictionary|schema)|available (?:data )?(?:sources?|tables?))|what (?:source|provider|table) (?:has|stores|contains))\b/;
312
315
 
313
316
  export function looksLikeAnalyticsDataRequest(text: string): boolean {
314
317
  const requestText = stripInjectedAnalyticsGuardContext(text);
@@ -350,6 +353,14 @@ export function looksLikeAnalyticsDataRequest(text: string): boolean {
350
353
  if (METADATA_ONLY_TERMS.test(lower)) return false;
351
354
 
352
355
  if (ANALYTICS_RESULT_TERMS.test(lower)) return true;
356
+ if (
357
+ ANALYTICS_DOMAIN_ENTITY_TERMS.test(lower) &&
358
+ (ANALYTICS_INTENT_TERMS.test(lower) ||
359
+ SOURCE_SEARCH_INTENT_TERMS.test(lower) ||
360
+ /\bdata\b/.test(lower))
361
+ ) {
362
+ return true;
363
+ }
353
364
  if (
354
365
  ANALYTICS_INTENT_TERMS.test(lower) &&
355
366
  /\b(data|source|table|sql)\b/.test(lower)