@agent-native/core-corpus 0.216.0 → 0.218.0

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 (196) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
  3. package/corpus/templates/analytics/AGENTS.md +1 -1
  4. package/corpus/templates/analytics/CHANGELOG.md +3 -0
  5. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
  6. package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
  7. package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
  8. package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
  9. package/corpus/templates/analytics/actions/view-screen.ts +29 -3
  10. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
  11. package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
  12. package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
  13. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
  14. package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
  15. package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
  16. package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
  17. package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
  19. package/corpus/templates/analytics/server/db/schema.ts +67 -0
  20. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
  21. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
  22. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
  23. package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
  24. package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
  25. package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
  26. package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
  27. package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
  28. package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
  29. package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
  30. package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
  31. package/corpus/templates/analytics/server/plugins/db.ts +46 -0
  32. package/corpus/templates/analytics/shared/output-use.ts +205 -0
  33. package/corpus/templates/analytics/shared/session-events.ts +6 -0
  34. package/corpus/templates/assets/CHANGELOG.md +6 -0
  35. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
  36. package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  37. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
  38. package/corpus/templates/brain/app/routes/sources.tsx +1 -1
  39. package/corpus/templates/brain/vitest.config.ts +7 -0
  40. package/corpus/templates/calendar/CHANGELOG.md +12 -0
  41. package/corpus/templates/calendar/actions/create-event.ts +2 -4
  42. package/corpus/templates/calendar/actions/update-event.ts +35 -6
  43. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
  44. package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
  45. package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
  46. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
  47. package/corpus/templates/clips/CHANGELOG.md +7 -20
  48. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
  49. package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
  50. package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
  51. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
  52. package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
  54. package/corpus/templates/community/qr/package.json +1 -1
  55. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
  56. package/corpus/templates/content/CHANGELOG.md +10 -0
  57. package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
  58. package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
  59. package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
  60. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
  61. package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
  62. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
  63. package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
  64. package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
  65. package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
  66. package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
  67. package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
  68. package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
  69. package/corpus/templates/content/actions/_database-utils.ts +93 -7
  70. package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
  71. package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
  72. package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
  73. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
  74. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
  75. package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
  76. package/corpus/templates/content/actions/delete-document.ts +41 -3
  77. package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
  78. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
  79. package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
  80. package/corpus/templates/content/actions/get-document.ts +2 -0
  81. package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
  82. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
  83. package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
  84. package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
  85. package/corpus/templates/content/actions/update-document.ts +139 -20
  86. package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
  87. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +119 -30
  88. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +56 -13
  89. package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
  90. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
  91. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
  92. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
  93. package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
  94. package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
  95. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
  96. package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
  97. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
  98. package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
  99. package/corpus/templates/content/app/global.css +31 -0
  100. package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
  101. package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
  102. package/corpus/templates/content/app/i18n-data.ts +1 -0
  103. package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
  104. package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
  105. package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
  106. package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
  107. package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
  108. package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
  109. package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
  110. package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
  111. package/corpus/templates/content/parity/matrix.md +42 -42
  112. package/corpus/templates/content/parity/matrix.ts +3 -1
  113. package/corpus/templates/content/server/db/schema.ts +20 -0
  114. package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
  115. package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
  116. package/corpus/templates/content/server/live-copy/binding.ts +31 -0
  117. package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
  118. package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
  119. package/corpus/templates/content/server/live-copy/holds.ts +59 -0
  120. package/corpus/templates/content/server/live-copy/persist.ts +656 -0
  121. package/corpus/templates/content/server/live-copy/switch.ts +136 -0
  122. package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
  123. package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
  124. package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
  125. package/corpus/templates/content/server/live-copy/write.ts +85 -0
  126. package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
  127. package/corpus/templates/content/server/plugins/collab.ts +3 -0
  128. package/corpus/templates/content/server/plugins/db.ts +28 -0
  129. package/corpus/templates/content/shared/api.ts +30 -1
  130. package/corpus/templates/content/shared/database-item-layout.ts +54 -22
  131. package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
  132. package/corpus/templates/content/shared/og-description.ts +258 -36
  133. package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
  134. package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
  135. package/corpus/templates/content/shared/suggestion-text.ts +20 -5
  136. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
  137. package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
  138. package/corpus/templates/design/CHANGELOG.md +5 -0
  139. package/corpus/templates/design/actions/get-getting-started.ts +53 -0
  140. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
  141. package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
  142. package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
  143. package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
  144. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
  145. package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
  146. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
  147. package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
  148. package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
  149. package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
  150. package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
  151. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
  152. package/corpus/templates/design/app/global.css +6 -0
  153. package/corpus/templates/design/app/i18n/en-US.ts +17 -0
  154. package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
  155. package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
  156. package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
  157. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
  158. package/corpus/templates/design/app/pages/Index.tsx +28 -67
  159. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
  160. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
  161. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
  162. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
  163. package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
  164. package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
  165. package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
  166. package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
  167. package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
  168. package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
  169. package/corpus/templates/design/server/lib/getting-started.ts +267 -0
  170. package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
  171. package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
  172. package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
  173. package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
  174. package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
  175. package/corpus/templates/design/shared/design-template-presets.ts +8 -0
  176. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  177. package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
  178. package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  179. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
  180. package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
  181. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  182. package/corpus/templates/plan/CHANGELOG.md +6 -0
  183. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
  184. package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  185. package/corpus/templates/slides/CHANGELOG.md +7 -0
  186. package/corpus/templates/slides/actions/delete-deck.ts +7 -0
  187. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  188. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
  189. package/corpus/templates/slides/app/pages/Index.tsx +7 -2
  190. package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
  191. package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
  192. package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
  193. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
  194. package/package.json +1 -1
  195. package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
  196. package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
@@ -1,3 +1,4 @@
1
+ import { appPath } from "@agent-native/core/client/api-path";
1
2
  import {
2
3
  AppSidebarFooter,
3
4
  AppSidebarHeader,
@@ -96,7 +97,6 @@ function useSettledSyncVersion(version: number): number {
96
97
  import {
97
98
  navigateWithAgentChatViewTransition,
98
99
  useChatThreads,
99
- type ChatThreadSummary,
100
100
  } from "@agent-native/core/client/agent-chat";
101
101
  import {
102
102
  callAction,
@@ -104,13 +104,14 @@ import {
104
104
  useChangeVersions,
105
105
  } from "@agent-native/core/client/hooks";
106
106
  import { useT } from "@agent-native/core/client/i18n";
107
+ import {
108
+ OwnershipChatHistoryRail,
109
+ useOwnershipChatHistory,
110
+ ownDraftRows,
111
+ } from "@agent-native/toolkit/app/chat/ownership-history";
107
112
  import { DevDatabaseLink } from "@agent-native/toolkit/app/db-admin";
108
113
  import { FeedbackButton } from "@agent-native/toolkit/app/feedback";
109
114
  import { OrgSwitcher } from "@agent-native/toolkit/app/org";
110
- import {
111
- ChatHistoryRail,
112
- type ChatHistoryItem,
113
- } from "@agent-native/toolkit/chat-history";
114
115
  import {
115
116
  analyticsAskThreadIdFromPath,
116
117
  analyticsAskThreadPath,
@@ -333,10 +334,6 @@ function applyOrder<T extends { id: string }>(
333
334
  return ordered;
334
335
  }
335
336
 
336
- function isVisibility(value: unknown): value is Visibility {
337
- return value === "private" || value === "org" || value === "public";
338
- }
339
-
340
337
  export function matchesVisibilityFilter(
341
338
  item: { visibility?: Visibility; ownerEmail?: string | null },
342
339
  filter: SidebarVisibilityFilter,
@@ -345,23 +342,6 @@ export function matchesVisibilityFilter(
345
342
  return matchesDashboardVisibilityFilter(item, filter, currentUserEmail);
346
343
  }
347
344
 
348
- export function threadMatchesVisibilityFilter(
349
- thread: ChatThreadSummary,
350
- filter: SidebarVisibilityFilter,
351
- ): boolean {
352
- const runtimeThread = thread as ChatThreadSummary & {
353
- visibility?: unknown;
354
- };
355
- return matchesVisibilityFilter(
356
- {
357
- visibility: isVisibility(runtimeThread.visibility)
358
- ? runtimeThread.visibility
359
- : "private",
360
- },
361
- filter,
362
- );
363
- }
364
-
365
345
  function SidebarSectionSettingsPopover({
366
346
  label,
367
347
  sortMode,
@@ -1368,110 +1348,44 @@ export function prefetchSqlDashboard(
1368
1348
  });
1369
1349
  }
1370
1350
 
1371
- function formatThreadAge(updatedAt: number) {
1372
- const diffMs = Math.max(0, Date.now() - updatedAt);
1373
- const minutes = Math.floor(diffMs / 60_000);
1374
- if (minutes < 1) return "now";
1375
- if (minutes < 60) return `${minutes}m`;
1376
- const hours = Math.floor(minutes / 60);
1377
- if (hours < 24) return `${hours}h`;
1378
- const days = Math.floor(hours / 24);
1379
- if (days < 7) return `${days}d`;
1380
- return new Date(updatedAt).toLocaleDateString([], {
1381
- month: "short",
1382
- day: "numeric",
1383
- });
1384
- }
1385
-
1386
- function threadTitle(thread: ChatThreadSummary, untitledLabel: string) {
1387
- return thread.title || thread.preview || untitledLabel;
1388
- }
1389
-
1390
- function threadUpdatedAt(thread: ChatThreadSummary) {
1391
- return Number.isFinite(thread.updatedAt)
1392
- ? thread.updatedAt
1393
- : Number.isFinite(thread.createdAt)
1394
- ? thread.createdAt
1395
- : 0;
1396
- }
1397
-
1398
- function compareThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
1399
- const aPinned = a.pinnedAt ?? 0;
1400
- const bPinned = b.pinnedAt ?? 0;
1401
- if (aPinned || bPinned) return bPinned - aPinned;
1402
- return threadUpdatedAt(b) - threadUpdatedAt(a);
1403
- }
1404
-
1405
1351
  function AnalyticsChatsSection({
1406
1352
  askThreadId,
1407
1353
  open,
1408
- visibilityFilter,
1409
1354
  }: {
1410
1355
  /** The saved thread open on /ask/:threadId; null on the blank /ask page. */
1411
1356
  askThreadId: string | null;
1412
1357
  open: boolean;
1413
- visibilityFilter: SidebarVisibilityFilter;
1414
1358
  }) {
1415
1359
  const navigate = useNavigate();
1416
1360
  const t = useT();
1417
- const {
1418
- threads,
1419
- isLoading: chatsLoading,
1420
- pinThread,
1421
- archiveThread,
1422
- renameThread,
1423
- refreshThreads,
1424
- } = useChatThreads(undefined, ANALYTICS_CHAT_STORAGE_KEY, undefined, {
1425
- autoCreate: false,
1426
- restoreActiveThread: false,
1427
- });
1428
-
1429
- const visibleThreads = useMemo(
1430
- () =>
1431
- threads
1432
- .filter(
1433
- (thread) =>
1434
- thread.messageCount > 0 &&
1435
- !thread.archivedAt &&
1436
- threadMatchesVisibilityFilter(thread, visibilityFilter),
1437
- )
1438
- .sort(compareThreads)
1439
- .slice(0, 15),
1440
- [threads, visibilityFilter],
1441
- );
1442
- const chatItems = useMemo<ChatHistoryItem[]>(
1443
- () =>
1444
- visibleThreads.map((thread) => ({
1445
- id: thread.id,
1446
- title: threadTitle(thread, t("chat.untitledChat")),
1447
- titleText: threadTitle(thread, t("chat.untitledChat")),
1448
- timestamp:
1449
- thread.id === askThreadId
1450
- ? undefined
1451
- : formatThreadAge(threadUpdatedAt(thread)),
1452
- pinned: Boolean(thread.pinnedAt),
1453
- })),
1454
- [askThreadId, t, visibleThreads],
1455
- );
1361
+ const { threads, pinThread, archiveThread, renameThread, isNewThread } =
1362
+ useChatThreads(undefined, ANALYTICS_CHAT_STORAGE_KEY, undefined, {
1363
+ ownership: "mine",
1364
+ autoCreate: false,
1365
+ restoreActiveThread: false,
1366
+ });
1456
1367
 
1457
- useEffect(() => {
1458
- const refresh = () => refreshThreads();
1459
- const handleRunning = (event: Event) => {
1460
- const detail = (event as CustomEvent).detail as
1461
- | { isRunning?: unknown }
1462
- | undefined;
1463
- if (typeof detail?.isRunning === "boolean") refreshThreads();
1464
- };
1465
-
1466
- window.addEventListener("agent-chat:threads-updated", refresh);
1467
- window.addEventListener("agentNative.chatRunning", handleRunning);
1468
- window.addEventListener("focus", refresh);
1469
- return () => {
1470
- window.removeEventListener("agent-chat:threads-updated", refresh);
1471
- window.removeEventListener("agentNative.chatRunning", handleRunning);
1472
- window.removeEventListener("focus", refresh);
1473
- };
1474
- }, [refreshThreads]);
1368
+ const history = useOwnershipChatHistory({
1369
+ getThreadUrl: (threadId) => appPath(analyticsAskThreadPath(threadId)),
1370
+ activeId: askThreadId,
1371
+ localMineRows: ownDraftRows({ threads, isNewThread }),
1372
+ mutations: {
1373
+ pin: pinThread,
1374
+ rename: renameThread,
1375
+ archive: archiveThread,
1376
+ },
1377
+ onMutationFailed: (kind) => {
1378
+ toast.error(
1379
+ t(
1380
+ kind === "archive"
1381
+ ? "chat.archiveFailed"
1382
+ : kind === "rename"
1383
+ ? "chat.renameFailed"
1384
+ : "agentChat.common.saveFailed",
1385
+ ),
1386
+ );
1387
+ },
1388
+ });
1475
1389
 
1476
1390
  function openThread(threadId: string) {
1477
1391
  navigateWithAgentChatViewTransition(
@@ -1494,18 +1408,10 @@ function AnalyticsChatsSection({
1494
1408
 
1495
1409
  async function handleArchiveThread(threadId: string) {
1496
1410
  const wasActive = threadId === askThreadId;
1497
- const archived = await archiveThread(threadId);
1498
- if (!archived) {
1499
- toast.error(t("chat.archiveFailed"));
1500
- return;
1501
- }
1411
+ const archived = await history.listProps.onDelete!(threadId);
1412
+ if (!archived) return false;
1502
1413
  if (wasActive) handleNewChat();
1503
- }
1504
-
1505
- function handleRenameThread(threadId: string, title: string) {
1506
- void renameThread(threadId, title).then((renamed) => {
1507
- if (!renamed) toast.error(t("chat.renameFailed"));
1508
- });
1414
+ return true;
1509
1415
  }
1510
1416
 
1511
1417
  return (
@@ -1515,43 +1421,15 @@ function AnalyticsChatsSection({
1515
1421
  aria-hidden={!open}
1516
1422
  >
1517
1423
  <div className="ms-4 min-w-0 space-y-0.5">
1518
- {chatsLoading &&
1519
- visibleThreads.length === 0 &&
1520
- Array.from({ length: 3 }).map((_, i) => (
1521
- <div
1522
- key={`chat-skeleton-${i}`}
1523
- className="flex items-center gap-2 px-3 py-1"
1524
- >
1525
- <Skeleton
1526
- className={cn(
1527
- "h-3.5 w-3.5 shrink-0 rounded-sm",
1528
- SIDEBAR_SKELETON_CLASS,
1529
- )}
1530
- />
1531
- <Skeleton
1532
- className={cn("h-3 rounded", SIDEBAR_SKELETON_CLASS)}
1533
- style={{ width: `${60 + ((i * 17) % 30)}%` }}
1534
- />
1535
- </div>
1536
- ))}
1537
- <ChatHistoryRail
1538
- items={chatItems}
1424
+ <OwnershipChatHistoryRail
1425
+ {...history}
1539
1426
  activeId={askThreadId}
1540
1427
  onSelect={(threadId) => openThread(threadId)}
1541
1428
  onNewChat={handleNewChat}
1542
1429
  railLabels={{
1543
1430
  newChat: t("chat.newChat"),
1544
- showMore: t("sidebar.showMore", {
1545
- count: Math.max(0, visibleThreads.length - SIDEBAR_PREVIEW_COUNT),
1546
- }),
1547
- showLess: t("sidebar.showLess"),
1548
1431
  }}
1549
1432
  renameMaxLength={160}
1550
- onTogglePin={(threadId) => {
1551
- const thread = visibleThreads.find((item) => item.id === threadId);
1552
- if (thread) void pinThread(threadId, !thread.pinnedAt);
1553
- }}
1554
- onRename={handleRenameThread}
1555
1433
  onDelete={(threadId) => void handleArchiveThread(threadId)}
1556
1434
  labels={{
1557
1435
  options: (item) =>
@@ -1639,7 +1517,6 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1639
1517
  const [askOpen, setAskOpen] = useState(
1640
1518
  () => getStoredBooleanPreference(ASK_OPEN_KEY) ?? isAskRoute,
1641
1519
  );
1642
- const [askFilter, setAskFilter] = useState<SidebarVisibilityFilter>("all");
1643
1520
  const [dashOpen, setDashOpen] = useState(
1644
1521
  () =>
1645
1522
  getStoredBooleanPreference(DASHBOARDS_OPEN_KEY) ??
@@ -2511,11 +2388,6 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2511
2388
  {t("navigation.ask")}
2512
2389
  </span>
2513
2390
  </Link>
2514
- <SidebarSectionSettingsPopover
2515
- label={t("navigation.ask")}
2516
- visibilityFilter={askFilter}
2517
- onVisibilityFilterChange={setAskFilter}
2518
- />
2519
2391
  <button
2520
2392
  type="button"
2521
2393
  onClick={toggleAskOpen}
@@ -2538,7 +2410,6 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2538
2410
  <AnalyticsChatsSection
2539
2411
  askThreadId={askThreadId}
2540
2412
  open={askOpen}
2541
- visibilityFilter={askFilter}
2542
2413
  />
2543
2414
  </div>
2544
2415
 
@@ -1790,6 +1790,10 @@ export default {
1790
1790
  catalogOpenSessions: "Show sessions with {{event}}",
1791
1791
  catalogAutomatic: "Automatic",
1792
1792
  catalogStoppedFiring: "Stopped firing",
1793
+ catalogMadeIt: "Made it",
1794
+ catalogUsedIt: "Used it",
1795
+ catalogNoUsedIt:
1796
+ "“Made it, didn't use it” can't check outputs from {{apps}} because no “used it” event is defined.",
1793
1797
  catalogPropertyKeys: "Property keys",
1794
1798
  catalogMoreKeys: "+{{count}} more",
1795
1799
  catalogTruncated: "Showing only the {{count}} most recently seen events.",
@@ -1803,6 +1807,29 @@ export default {
1803
1807
  speedCoverageStarting: "Speed is measured for page views from now on.",
1804
1808
  speedFilterNeedsLab:
1805
1809
  "This link has a speed filter. Turn on the Sessions triage Lab in Settings to apply it.",
1810
+ unusedOutputs: "Made it, didn't use it",
1811
+ anyOutcome: "Any session",
1812
+ unusedSlidesDecks: "Unused Slides decks",
1813
+ unusedSlidesRule:
1814
+ "Decks made at least {{days}} days ago that weren't presented past the first slide for {{seconds}} s or more, or exported, within {{days}} days. Opening a deck doesn't count.",
1815
+ unusedFilterNeedsLab:
1816
+ "This link has a “made it, didn't use it” filter. Turn on the Sessions triage Lab in Settings to apply it.",
1817
+ outputCoverageSince:
1818
+ "Outputs tracked since {{date}}; unused ones show {{days}} days after they're made.",
1819
+ outputCoverageStarting:
1820
+ "Outputs are tracked from now on; unused ones show {{days}} days after they're made.",
1821
+ openDeck: "Open deck",
1822
+ moreOutputs: "+{{count}} more",
1823
+ deckCount: "{{count}} decks",
1824
+ deckCountSingular: "{{count}} deck",
1825
+ deckMadeOn: "Deck made {{date}}",
1826
+ outputPresentedOn: "Presented {{date}}",
1827
+ outputExportedOn: "Exported {{date}}",
1828
+ outputUsedOn: "Used {{date}}",
1829
+ outputUnused: "Not used within {{days}} days",
1830
+ outputOpen: "Not used yet; counts as unused after {{date}}",
1831
+ outputUnmeasured: "Not measured: some events failed to save",
1832
+ outputsUnavailable: "Couldn't load outputs.",
1806
1833
  slowRequestCount: "{{count}} slow requests",
1807
1834
  slowRequestCountSingular: "{{count}} slow request",
1808
1835
  speedIncomplete: "Speed data incomplete",
@@ -82,6 +82,9 @@ export default function EventCatalogPage() {
82
82
  const automaticOnlyApps = (data?.apps ?? []).filter(
83
83
  (item) => item.onlyAutomaticEvents,
84
84
  );
85
+ const noUsedItApps = (data?.apps ?? [])
86
+ .filter((item) => item.makesOutputs && !item.usedItEvents.length)
87
+ .map((item) => item.app || t("sessions.unknownApp"));
85
88
  const sessionsHref = `/sessions${
86
89
  range !== "30d" || app
87
90
  ? `?${new URLSearchParams({
@@ -190,7 +193,7 @@ export default function EventCatalogPage() {
190
193
  </Button>
191
194
  </div>
192
195
 
193
- {automaticOnlyApps.length ? (
196
+ {automaticOnlyApps.length || noUsedItApps.length ? (
194
197
  <Alert role="status">
195
198
  <IconInfoCircle />
196
199
  <AlertDescription>
@@ -201,6 +204,13 @@ export default function EventCatalogPage() {
201
204
  })}
202
205
  </p>
203
206
  ))}
207
+ {noUsedItApps.length ? (
208
+ <p>
209
+ {t("sessions.catalogNoUsedIt", {
210
+ apps: noUsedItApps.join(", "),
211
+ })}
212
+ </p>
213
+ ) : null}
204
214
  </AlertDescription>
205
215
  </Alert>
206
216
  ) : null}
@@ -283,6 +293,13 @@ function CatalogRow({
283
293
  {t("sessions.catalogStoppedFiring")}
284
294
  </Badge>
285
295
  ) : null}
296
+ {entry.outputRole ? (
297
+ <Badge variant="outline">
298
+ {entry.outputRole === "made"
299
+ ? t("sessions.catalogMadeIt")
300
+ : t("sessions.catalogUsedIt")}
301
+ </Badge>
302
+ ) : null}
286
303
  </span>
287
304
  {entry.description ? (
288
305
  <span className="line-clamp-2 block text-xs text-muted-foreground">
@@ -128,6 +128,7 @@ import {
128
128
  SessionDevToolsPanel,
129
129
  } from "./SessionDevToolsPanel";
130
130
  import { SessionFrictionPanel } from "./SessionFriction";
131
+ import { SessionOutputsButton } from "./SessionOutputs";
131
132
  import { SessionStartTime } from "./SessionStartTime";
132
133
 
133
134
  export {
@@ -343,7 +344,8 @@ export default function SessionDetailPage() {
343
344
  </div>
344
345
  {recording ? (
345
346
  <TooltipProvider>
346
- <div className="flex shrink-0 items-center gap-2">
347
+ <div className="flex shrink-0 flex-wrap items-center gap-2">
348
+ <SessionOutputsButton recordingId={recording.id} />
347
349
  <CopySessionForAgentButton recordingId={recording.id} />
348
350
  <AskSessionPopover recording={recording} />
349
351
  <AgentToggleButton />
@@ -0,0 +1,197 @@
1
+ import { useActionQuery } from "@agent-native/core/client/hooks";
2
+ import { useT } from "@agent-native/core/client/i18n";
3
+ import { useLab } from "@agent-native/core/client/labs";
4
+ import {
5
+ IconExternalLink,
6
+ IconPackage,
7
+ IconRefresh,
8
+ } from "@tabler/icons-react";
9
+
10
+ import { Button } from "@/components/ui/button";
11
+ import {
12
+ Popover,
13
+ PopoverContent,
14
+ PopoverTrigger,
15
+ } from "@/components/ui/popover";
16
+ import { cn } from "@/lib/utils";
17
+
18
+ import { ANALYTICS_SESSIONS_TRIAGE_LAB } from "../../../shared/labs";
19
+ import {
20
+ MIN_PRESENTATION_MS,
21
+ OUTPUT_USE_WINDOW_DAYS,
22
+ type OutputUseApp,
23
+ type SessionOutput,
24
+ type SessionRecordingOutputs,
25
+ } from "../../../shared/output-use";
26
+
27
+ type T = ReturnType<typeof useT>;
28
+
29
+ export function unusedFilterLabel(app: OutputUseApp, t: T): string {
30
+ switch (app) {
31
+ case "slides":
32
+ return t("sessions.unusedSlidesDecks");
33
+ }
34
+ }
35
+
36
+ export function unusedRuleText(app: OutputUseApp, t: T): string {
37
+ switch (app) {
38
+ case "slides":
39
+ return t("sessions.unusedSlidesRule", {
40
+ days: String(OUTPUT_USE_WINDOW_DAYS),
41
+ seconds: String(MIN_PRESENTATION_MS / 1000),
42
+ });
43
+ }
44
+ }
45
+
46
+ function openOutputLabel(output: SessionOutput, t: T): string {
47
+ switch (output.app) {
48
+ case "slides":
49
+ return t("sessions.openDeck");
50
+ }
51
+ }
52
+
53
+ function outputTitle(output: SessionOutput, t: T): string {
54
+ const date = new Date(output.madeAt).toLocaleDateString();
55
+ switch (output.app) {
56
+ case "slides":
57
+ return t("sessions.deckMadeOn", { date });
58
+ }
59
+ }
60
+
61
+ export function outputStatusText(output: SessionOutput, t: T): string {
62
+ switch (output.status) {
63
+ case "used": {
64
+ const date = output.usedAt
65
+ ? new Date(output.usedAt).toLocaleDateString()
66
+ : "";
67
+ if (output.usedEvent === "presentation_ended") {
68
+ return t("sessions.outputPresentedOn", { date });
69
+ }
70
+ if (output.usedEvent === "deck_exported") {
71
+ return t("sessions.outputExportedOn", { date });
72
+ }
73
+ return t("sessions.outputUsedOn", { date });
74
+ }
75
+ case "unused":
76
+ return t("sessions.outputUnused", {
77
+ days: String(OUTPUT_USE_WINDOW_DAYS),
78
+ });
79
+ case "open":
80
+ return t("sessions.outputOpen", {
81
+ date: new Date(output.useBy).toLocaleDateString(),
82
+ });
83
+ case "unmeasured":
84
+ return t("sessions.outputUnmeasured");
85
+ }
86
+ }
87
+
88
+ function OutputLink({ output }: { output: SessionOutput }) {
89
+ const t = useT();
90
+ if (!output.url) return null;
91
+ return (
92
+ <Button asChild variant="ghost" size="xs" className="shrink-0">
93
+ <a href={output.url} target="_blank" rel="noreferrer">
94
+ {openOutputLabel(output, t)}
95
+ <IconExternalLink />
96
+ </a>
97
+ </Button>
98
+ );
99
+ }
100
+
101
+ // A link cannot nest inside the row's replay link, so this sits beside it.
102
+ export function UnusedOutputLink({
103
+ outputs,
104
+ }: {
105
+ outputs?: SessionOutput[] | null;
106
+ }) {
107
+ const t = useT();
108
+ const unusedOutputs = (outputs ?? []).filter(
109
+ (output) => output.status === "unused",
110
+ );
111
+ const linked = unusedOutputs.find((output) => output.url);
112
+ if (!linked) return null;
113
+ return (
114
+ <div className="flex shrink-0 items-center gap-1 px-3">
115
+ <OutputLink output={linked} />
116
+ {unusedOutputs.length > 1 ? (
117
+ <span className="text-xs text-muted-foreground">
118
+ {t("sessions.moreOutputs", {
119
+ count: String(unusedOutputs.length - 1),
120
+ })}
121
+ </span>
122
+ ) : null}
123
+ </div>
124
+ );
125
+ }
126
+
127
+ /** The outputs a replay's session made, and whether each was used in time. */
128
+ export function SessionOutputsButton({ recordingId }: { recordingId: string }) {
129
+ const t = useT();
130
+ const lab = useLab(ANALYTICS_SESSIONS_TRIAGE_LAB);
131
+ const query = useActionQuery<SessionRecordingOutputs>(
132
+ "list-session-outputs",
133
+ { recordingIds: [recordingId] },
134
+ { enabled: lab, staleTime: 30_000 },
135
+ );
136
+ if (!lab) return null;
137
+ if (query.isError && !query.data) {
138
+ return (
139
+ <Button
140
+ variant="ghost"
141
+ size="sm"
142
+ onClick={() => void query.refetch()}
143
+ disabled={query.isFetching}
144
+ >
145
+ <IconRefresh className={cn(query.isFetching && "animate-spin")} />
146
+ {t("sessions.outputsUnavailable")}
147
+ </Button>
148
+ );
149
+ }
150
+ const outputs = query.data?.outputs[recordingId];
151
+ if (!outputs?.length) return null;
152
+ const omitted = query.data?.omittedOutputs[recordingId] ?? 0;
153
+ const total = outputs.length + omitted;
154
+ return (
155
+ <Popover>
156
+ <PopoverTrigger asChild>
157
+ <Button variant="outline">
158
+ <IconPackage />
159
+ {t(
160
+ total === 1 ? "sessions.deckCountSingular" : "sessions.deckCount",
161
+ { count: String(total) },
162
+ )}
163
+ </Button>
164
+ </PopoverTrigger>
165
+ <PopoverContent align="end" className="w-80">
166
+ <ul className="space-y-3">
167
+ {outputs.map((output) => (
168
+ <li
169
+ key={`${output.app}:${output.outputId}`}
170
+ className="flex items-start justify-between gap-3"
171
+ >
172
+ <span className="min-w-0 text-sm">
173
+ <span className="block truncate font-medium">
174
+ {outputTitle(output, t)}
175
+ </span>
176
+ <span
177
+ className={cn(
178
+ "block text-xs text-muted-foreground",
179
+ output.status === "unused" && "text-foreground",
180
+ )}
181
+ >
182
+ {outputStatusText(output, t)}
183
+ </span>
184
+ </span>
185
+ <OutputLink output={output} />
186
+ </li>
187
+ ))}
188
+ </ul>
189
+ {omitted ? (
190
+ <p className="mt-3 text-xs text-muted-foreground">
191
+ {t("sessions.moreOutputs", { count: String(omitted) })}
192
+ </p>
193
+ ) : null}
194
+ </PopoverContent>
195
+ </Popover>
196
+ );
197
+ }