@agent-native/core-corpus 0.217.0 → 0.219.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 (81) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +5 -3
  3. package/corpus/templates/analytics/CHANGELOG.md +2 -0
  4. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
  5. package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  6. package/corpus/templates/assets/CHANGELOG.md +6 -0
  7. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
  8. package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  9. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
  10. package/corpus/templates/brain/app/routes/sources.tsx +1 -1
  11. package/corpus/templates/brain/vitest.config.ts +7 -0
  12. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  13. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
  14. package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
  15. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
  16. package/corpus/templates/clips/CHANGELOG.md +7 -20
  17. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
  18. package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
  19. package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
  20. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
  21. package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
  22. package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
  23. package/corpus/templates/community/qr/package.json +1 -1
  24. package/corpus/templates/content/CHANGELOG.md +7 -0
  25. package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
  26. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +770 -179
  27. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +75 -27
  28. package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
  29. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
  30. package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
  31. package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
  32. package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
  33. package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
  34. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
  35. package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
  36. package/corpus/templates/content/app/global.css +31 -0
  37. package/corpus/templates/content/app/i18n-data.ts +8 -0
  38. package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
  39. package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
  40. package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
  41. package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
  42. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
  43. package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
  44. package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
  45. package/corpus/templates/content/shared/suggestion-text.ts +20 -5
  46. package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
  47. package/corpus/templates/design/CHANGELOG.md +8 -0
  48. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
  49. package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
  50. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
  51. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  52. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
  53. package/corpus/templates/design/app/pages/Index.tsx +18 -64
  54. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
  55. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
  56. package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
  57. package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
  58. package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
  59. package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
  60. package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
  61. package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
  62. package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
  63. package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
  64. package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
  65. package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
  66. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  67. package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
  68. package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  69. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
  70. package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
  71. package/corpus/templates/plan/CHANGELOG.md +6 -0
  72. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
  73. package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  74. package/corpus/templates/slides/CHANGELOG.md +1 -0
  75. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  76. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
  77. package/corpus/templates/slides/app/pages/Index.tsx +7 -2
  78. package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
  79. package/package.json +1 -1
  80. package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
  81. package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
package/corpus/README.md CHANGED
@@ -32,4 +32,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core-corpus/corpu
32
32
 
33
33
  ## Generated Counts
34
34
 
35
- - template files: 14560
35
+ - template files: 14579
@@ -45,9 +45,11 @@ actions do not expose, inspect the provider catalog/docs and call
45
45
  endpoint, call `provider-api-request` (provider `hubspot`, e.g.
46
46
  `/crm/v3/properties/deals`) via `provider-api-docs` **without `stageAs`**.
47
47
  Property definitions are read-only control metadata, not analysis rows; never
48
- stage them. `stageAs` writes result rows to shared staged-dataset storage,
49
- which can be at capacity and may evict existing datasets. Do not clear or
50
- evict existing staged datasets to make room. If a metadata request cannot be
48
+ stage them. `stageAs` writes result rows to staged-dataset storage, which
49
+ has a per-user quota and an app-wide limit. Going over either removes the
50
+ least recently written datasets (the user's own for the per-user quota, any
51
+ user's for the app-wide limit), and a write too large to fit even then fails.
52
+ Do not clear existing staged datasets to make room. If a metadata request cannot be
51
53
  read without staging, narrow the request or return only the required property
52
54
  names, labels, and types through a read-only path.
53
55
  - `provider-api-request` with `provider: "hubspot"` — arbitrary HubSpot HTTP
@@ -74,6 +74,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
74
74
 
75
75
  ### Added
76
76
 
77
+ - Owned and shared chats appear in separate history sections, with each shared chat's owner shown as an avatar and a Copy link action on every chat.
77
78
  - In Edit mode, rename or delete a multi-select filter's group from its dropdown while that group is selected.
78
79
  - The Content dashboard can show signups per blog article, collected once a day behind a BigQuery cost cap that a dry run measures first
79
80
  - Click a table row to see its numbers broken down by app, or another column, in a side panel.
@@ -96,6 +97,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
96
97
 
97
98
  ### Fixed
98
99
 
100
+ - The activation funnel counts visitors who decline analytics cookies and then sign up in the same visit
99
101
  - A key-date marker opens its card on hover even where a chart's hovered point sits on top of it.
100
102
  - Alert polls enforce a query byte limit and report evaluations that exceed it as errors.
101
103
  - All-time dashboard panels keep their historical label when you edit a title or description
@@ -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
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-10-16
4
+ ---
5
+
6
+ Owned and shared chats appear in separate history sections, with each shared chat's owner shown as an avatar and a Copy link action on every chat.
@@ -9,6 +9,12 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
9
9
 
10
10
  - Apps no longer keep server functions and databases warm by default; set `AGENT_NATIVE_ENABLE_KEEP_WARM=1` to opt in.
11
11
 
12
+ ## 2026-10-16
13
+
14
+ ### Added
15
+
16
+ - Owned and shared chats appear in separate history sections, with each shared chat's owner shown as an avatar and a Copy link action on every chat.
17
+
12
18
  ## 2026-10-09
13
19
 
14
20
  ### Improved
@@ -1,11 +1,16 @@
1
1
  import {
2
2
  navigateWithAgentChatViewTransition,
3
3
  useChatThreads,
4
- type ChatThreadSummary,
5
4
  } from "@agent-native/core/client/agent-chat";
5
+ import { appPath } from "@agent-native/core/client/api-path";
6
6
  import { useActionQuery } from "@agent-native/core/client/hooks";
7
7
  import { useT } from "@agent-native/core/client/i18n";
8
8
  import { focusAgentChat } from "@agent-native/toolkit/app/chat";
9
+ import {
10
+ OwnershipChatHistoryRail,
11
+ useOwnershipChatHistory,
12
+ ownDraftRows,
13
+ } from "@agent-native/toolkit/app/chat/ownership-history";
9
14
  import { DevDatabaseLink } from "@agent-native/toolkit/app/db-admin";
10
15
  import { FeedbackButton } from "@agent-native/toolkit/app/feedback";
11
16
  import { OrgSwitcher } from "@agent-native/toolkit/app/org";
@@ -14,10 +19,6 @@ import {
14
19
  AppSidebar,
15
20
  AppSidebarNavItem,
16
21
  } from "@agent-native/toolkit/app/shared";
17
- import {
18
- ChatHistoryRail,
19
- type ChatHistoryItem,
20
- } from "@agent-native/toolkit/chat-history";
21
22
  import {
22
23
  IconClipboardList,
23
24
  IconLayoutGrid,
@@ -26,7 +27,7 @@ import {
26
27
  IconShare3,
27
28
  IconTemplate,
28
29
  } from "@tabler/icons-react";
29
- import { useEffect, useMemo, useState } from "react";
30
+ import { useEffect, useState } from "react";
30
31
  import { useLocation, useNavigate } from "react-router";
31
32
  import { toast } from "sonner";
32
33
 
@@ -53,40 +54,6 @@ const auditNavItem = {
53
54
  const COLLAPSE_KEY = "assets.sidebar.collapsed";
54
55
  const ASSETS_ACTIVE_THREAD_KEY = `agent-chat-active-thread:${ASSETS_CHAT_STORAGE_KEY}`;
55
56
 
56
- function formatThreadAge(updatedAt: number) {
57
- const diffMs = Math.max(0, Date.now() - updatedAt);
58
- const minutes = Math.floor(diffMs / 60_000);
59
- if (minutes < 1) return "now";
60
- if (minutes < 60) return `${minutes}m`;
61
- const hours = Math.floor(minutes / 60);
62
- if (hours < 24) return `${hours}h`;
63
- const days = Math.floor(hours / 24);
64
- if (days < 7) return `${days}d`;
65
- return new Date(updatedAt).toLocaleDateString([], {
66
- month: "short",
67
- day: "numeric",
68
- });
69
- }
70
-
71
- function threadTitle(thread: ChatThreadSummary) {
72
- return thread.title || thread.preview || "Untitled chat";
73
- }
74
-
75
- function threadUpdatedAt(thread: ChatThreadSummary) {
76
- return Number.isFinite(thread.updatedAt)
77
- ? thread.updatedAt
78
- : Number.isFinite(thread.createdAt)
79
- ? thread.createdAt
80
- : 0;
81
- }
82
-
83
- function compareThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
84
- const aPinned = a.pinnedAt ?? 0;
85
- const bPinned = b.pinnedAt ?? 0;
86
- if (aPinned || bPinned) return bPinned - aPinned;
87
- return threadUpdatedAt(b) - threadUpdatedAt(a);
88
- }
89
-
90
57
  function persistedActiveThreadId() {
91
58
  try {
92
59
  return localStorage.getItem(ASSETS_ACTIVE_THREAD_KEY);
@@ -129,55 +96,37 @@ function AssetsChatsSection({ open }: { open: boolean }) {
129
96
  archiveThread,
130
97
  renameThread,
131
98
  createThreadShareLink,
132
- refreshThreads,
99
+ isNewThread,
133
100
  } = useChatThreads(undefined, ASSETS_CHAT_STORAGE_KEY, undefined, {
101
+ ownership: "mine",
134
102
  autoCreate: false,
135
103
  restoreActiveThread: false,
136
104
  });
137
105
 
138
- const visibleThreads = useMemo(
139
- () =>
140
- threads
141
- .filter((thread) => thread.messageCount > 0 && !thread.archivedAt)
142
- .sort(compareThreads),
143
- [threads],
144
- );
145
106
  const displayedActiveThreadId =
146
107
  threadIdFromPath(location.pathname) ??
147
108
  (location.pathname === "/home" ? null : activeThreadId);
148
- const chatItems = useMemo<ChatHistoryItem[]>(
149
- () =>
150
- visibleThreads.map((thread) => ({
151
- id: thread.id,
152
- title: threadTitle(thread),
153
- titleText: threadTitle(thread),
154
- timestamp:
155
- thread.id === displayedActiveThreadId
156
- ? undefined
157
- : formatThreadAge(threadUpdatedAt(thread)),
158
- pinned: Boolean(thread.pinnedAt),
159
- })),
160
- [displayedActiveThreadId, visibleThreads],
161
- );
162
-
163
- useEffect(() => {
164
- const refresh = () => refreshThreads();
165
- const handleRunning = (event: Event) => {
166
- const detail = (event as CustomEvent).detail as
167
- | { isRunning?: unknown }
168
- | undefined;
169
- if (typeof detail?.isRunning === "boolean") refreshThreads();
170
- };
171
-
172
- window.addEventListener("agent-chat:threads-updated", refresh);
173
- window.addEventListener("agentNative.chatRunning", handleRunning);
174
- window.addEventListener("focus", refresh);
175
- return () => {
176
- window.removeEventListener("agent-chat:threads-updated", refresh);
177
- window.removeEventListener("agentNative.chatRunning", handleRunning);
178
- window.removeEventListener("focus", refresh);
179
- };
180
- }, [refreshThreads]);
109
+ const history = useOwnershipChatHistory({
110
+ getThreadUrl: (threadId) => appPath(chatThreadPath(threadId)),
111
+ activeId: displayedActiveThreadId,
112
+ localMineRows: ownDraftRows({ threads, isNewThread }),
113
+ mutations: {
114
+ pin: pinThread,
115
+ rename: renameThread,
116
+ archive: archiveThread,
117
+ },
118
+ onMutationFailed: (kind) => {
119
+ toast.error(
120
+ t(
121
+ kind === "archive"
122
+ ? "chat.archiveFailed"
123
+ : kind === "rename"
124
+ ? "chat.renameFailed"
125
+ : "agentChat.common.saveFailed",
126
+ ),
127
+ );
128
+ },
129
+ });
181
130
 
182
131
  function openThread(threadId: string, options?: { isNew?: boolean }) {
183
132
  switchThread(threadId);
@@ -203,14 +152,12 @@ function AssetsChatsSection({ open }: { open: boolean }) {
203
152
  async function handleArchiveThread(threadId: string) {
204
153
  const wasActive =
205
154
  threadId === activeThreadId || threadId === persistedActiveThreadId();
206
- const archived = await archiveThread(threadId);
207
- if (!archived) {
208
- toast.error(t("chat.archiveFailed"));
209
- return;
210
- }
155
+ const archived = await history.listProps.onDelete!(threadId);
156
+ if (!archived) return false;
211
157
  if (wasActive) {
212
158
  await handleNewChat();
213
159
  }
160
+ return true;
214
161
  }
215
162
 
216
163
  async function handleCopyShareLink(threadId: string) {
@@ -233,12 +180,6 @@ function AssetsChatsSection({ open }: { open: boolean }) {
233
180
  }
234
181
  }
235
182
 
236
- function handleRenameThread(threadId: string, title: string) {
237
- void renameThread(threadId, title).then((renamed) => {
238
- if (!renamed) toast.error(t("chat.renameFailed"));
239
- });
240
- }
241
-
242
183
  return (
243
184
  <div
244
185
  className="an-chat-history-rail__collapse"
@@ -246,39 +187,32 @@ function AssetsChatsSection({ open }: { open: boolean }) {
246
187
  aria-hidden={!open}
247
188
  >
248
189
  <div className="ms-4">
249
- <ChatHistoryRail
250
- items={chatItems}
190
+ <OwnershipChatHistoryRail
191
+ {...history}
251
192
  activeId={displayedActiveThreadId}
252
193
  onSelect={openThread}
253
194
  onNewChat={() => void handleNewChat()}
254
195
  railLabels={{
255
196
  newChat: t("chat.newChat"),
256
- showMore: t("chat.chats"),
257
- showLess: t("chat.chats"),
258
- }}
259
- previewCount={5}
260
- expandedCount={15}
261
- onTogglePin={(threadId) => {
262
- const thread = visibleThreads.find((item) => item.id === threadId);
263
- if (thread) void pinThread(threadId, !thread.pinnedAt);
264
197
  }}
265
- onRename={handleRenameThread}
266
198
  renameMaxLength={160}
267
199
  onDelete={(threadId) => void handleArchiveThread(threadId)}
268
- renderAdditionalRowActions={(item, closeMenu) => (
269
- <button
270
- type="button"
271
- role="menuitem"
272
- className="an-chat-history-row__menu-item"
273
- onClick={() => {
274
- closeMenu();
275
- void handleCopyShareLink(item.id);
276
- }}
277
- >
278
- <IconShare3 size={13} strokeWidth={1.8} />
279
- <span>{t("chat.copyShareLink")}</span>
280
- </button>
281
- )}
200
+ renderAdditionalRowActions={(item, closeMenu) =>
201
+ item.readOnly ? null : (
202
+ <button
203
+ type="button"
204
+ role="menuitem"
205
+ className="an-chat-history-row__menu-item"
206
+ onClick={() => {
207
+ closeMenu();
208
+ void handleCopyShareLink(item.id);
209
+ }}
210
+ >
211
+ <IconShare3 size={13} strokeWidth={1.8} />
212
+ <span>{t("chat.copyShareLink")}</span>
213
+ </button>
214
+ )
215
+ }
282
216
  labels={{
283
217
  options: (item) =>
284
218
  t("chat.optionsFor", { title: item.titleText ?? "" }),
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-10-16
4
+ ---
5
+
6
+ Owned and shared chats appear in separate history sections, with each shared chat's owner shown as an avatar and a Copy link action on every chat.