@ai-matrx/messaging 0.10.4 → 0.11.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.
package/dist/react.js CHANGED
@@ -821,6 +821,17 @@ function projectConversationSummary(row, viewerId, fallbackOrganizationId) {
821
821
  };
822
822
  }
823
823
 
824
+ // src/core/types.ts
825
+ var asConversationId = (value) => value;
826
+ var asMessageId = (value) => value;
827
+ var asUserId = (value) => value;
828
+ var asOrganizationId = (value) => value;
829
+ var asClientMessageId = (value) => value;
830
+ var DEFAULT_MESSAGING_ARCHIVE_FILTER = "active";
831
+ function toMessagingArchiveFilter(value, fallback = DEFAULT_MESSAGING_ARCHIVE_FILTER) {
832
+ return value === "active" || value === "archived" || value === "all" ? value : fallback;
833
+ }
834
+
824
835
  // src/core/store.ts
825
836
  function timeOf(message) {
826
837
  const stamp = message.editedAt ?? message.createdAt;
@@ -851,6 +862,8 @@ function createMessagingStore() {
851
862
  let conversations = [];
852
863
  let hasMoreConversations = false;
853
864
  let hasLoadedConversations = false;
865
+ let archiveFilter = DEFAULT_MESSAGING_ARCHIVE_FILTER;
866
+ let archivedCount = null;
854
867
  let threads = /* @__PURE__ */ new Map();
855
868
  let activeConversationId = null;
856
869
  const listeners = /* @__PURE__ */ new Set();
@@ -863,7 +876,9 @@ function createMessagingStore() {
863
876
  hasLoadedConversations,
864
877
  threads,
865
878
  activeConversationId,
866
- totalUnreadConversations: conversations.filter((item) => item.unreadCount > 0).length
879
+ totalUnreadConversations: conversations.filter((item) => item.unreadCount > 0).length,
880
+ archiveFilter,
881
+ archivedCount
867
882
  };
868
883
  return cached;
869
884
  }
@@ -910,6 +925,18 @@ function createMessagingStore() {
910
925
  hasLoadedConversations = true;
911
926
  emit();
912
927
  },
928
+ setArchiveFilter(next) {
929
+ if (next === archiveFilter) return;
930
+ archiveFilter = next;
931
+ conversations = [];
932
+ hasMoreConversations = false;
933
+ hasLoadedConversations = false;
934
+ emit();
935
+ },
936
+ setArchivedCount(value) {
937
+ archivedCount = value;
938
+ emit();
939
+ },
913
940
  appendConversations(items, hasMore) {
914
941
  const byId = new Map(conversations.map((item) => [item.conversation.id, item]));
915
942
  items.forEach((item) => byId.set(item.conversation.id, item));
@@ -1115,10 +1142,21 @@ function createMessagingEngine(options) {
1115
1142
  openChannels.get(message.conversationId)?.send(MESSAGING_EVENTS.message, message);
1116
1143
  }
1117
1144
  async function reloadInbox() {
1118
- const page = await repository.listConversations({ limit: conversationPageSize });
1145
+ if (disposed) return;
1146
+ const page = await repository.listConversations({
1147
+ limit: conversationPageSize,
1148
+ archived: store.snapshot().archiveFilter
1149
+ });
1150
+ if (disposed) return;
1119
1151
  conversationCursor = page.nextCursor;
1120
1152
  store.setConversations(page.items, page.hasMore);
1121
1153
  }
1154
+ async function reloadArchivedCount() {
1155
+ if (disposed) return;
1156
+ const value = await repository.countArchivedConversations();
1157
+ if (disposed) return;
1158
+ store.setArchivedCount(value);
1159
+ }
1122
1160
  async function backfillConversation(id) {
1123
1161
  const thread = store.snapshot().threads.get(id);
1124
1162
  const since = thread?.latestAt ?? null;
@@ -1145,7 +1183,13 @@ function createMessagingEngine(options) {
1145
1183
  outbox,
1146
1184
  identity,
1147
1185
  async start() {
1186
+ if (options.archiveFilter !== void 0) {
1187
+ store.setArchiveFilter(options.archiveFilter);
1188
+ }
1148
1189
  await reloadInbox();
1190
+ void reloadArchivedCount().catch(
1191
+ (error) => reportError(error, "refreshArchivedCount")
1192
+ );
1149
1193
  if (disposed || inboxChannel !== null) return;
1150
1194
  inboxChannel = manager.open({
1151
1195
  topic: inboxTopic(identity.userId),
@@ -1160,7 +1204,7 @@ function createMessagingEngine(options) {
1160
1204
  const message = projectMessage(row, identity.organizationId);
1161
1205
  const known = store.snapshot().conversations.some((item) => item.conversation.id === message.conversationId);
1162
1206
  if (!known) {
1163
- void reloadInbox();
1207
+ void reloadInbox().catch((error) => reportError(error, "inboxRefresh"));
1164
1208
  return;
1165
1209
  }
1166
1210
  store.ingest(message);
@@ -1176,7 +1220,7 @@ function createMessagingEngine(options) {
1176
1220
  filter: `user_id=eq.${identity.userId}`,
1177
1221
  rowId: (row) => typeof row["id"] === "string" ? row["id"] : void 0,
1178
1222
  onChange: () => {
1179
- void reloadInbox();
1223
+ void reloadInbox().catch((error) => reportError(error, "inboxRefresh"));
1180
1224
  }
1181
1225
  }
1182
1226
  ],
@@ -1187,12 +1231,33 @@ function createMessagingEngine(options) {
1187
1231
  }
1188
1232
  });
1189
1233
  },
1234
+ async setArchiveFilter(next) {
1235
+ if (next === store.snapshot().archiveFilter) return;
1236
+ store.setArchiveFilter(next);
1237
+ conversationCursor = null;
1238
+ try {
1239
+ await reloadInbox();
1240
+ } catch (error) {
1241
+ reportError(error, "setArchiveFilter");
1242
+ }
1243
+ await reloadArchivedCount().catch(
1244
+ (error) => reportError(error, "refreshArchivedCount")
1245
+ );
1246
+ },
1247
+ async refreshArchivedCount() {
1248
+ try {
1249
+ await reloadArchivedCount();
1250
+ } catch (error) {
1251
+ reportError(error, "refreshArchivedCount");
1252
+ }
1253
+ },
1190
1254
  async loadMoreConversations() {
1191
1255
  if (conversationCursor === null) return;
1192
1256
  try {
1193
1257
  const page = await repository.listConversations({
1194
1258
  limit: conversationPageSize,
1195
- cursor: conversationCursor
1259
+ cursor: conversationCursor,
1260
+ archived: store.snapshot().archiveFilter
1196
1261
  });
1197
1262
  conversationCursor = page.nextCursor;
1198
1263
  store.appendConversations(page.items, page.hasMore);
@@ -1456,6 +1521,7 @@ function createMessagingRepository(options) {
1456
1521
  identity,
1457
1522
  async listConversations(args = {}) {
1458
1523
  const limit = args.limit ?? 30;
1524
+ const archived = args.archived ?? DEFAULT_MESSAGING_ARCHIVE_FILTER;
1459
1525
  const operation = "listConversations";
1460
1526
  const rows = await withSessionRetry(
1461
1527
  operation,
@@ -1465,7 +1531,12 @@ function createMessagingRepository(options) {
1465
1531
  p_user_id: identity.userId,
1466
1532
  p_limit: limit + 1,
1467
1533
  p_before_sort_at: args.cursor?.beforeSortAt ?? null,
1468
- p_before_conversation_id: args.cursor?.beforeConversationId ?? null
1534
+ p_before_conversation_id: args.cursor?.beforeConversationId ?? null,
1535
+ // THE ARCHIVED-ITEMS LAW, SERVER-side. `get_dm_conversations_with_details`
1536
+ // used to hardcode `is_archived IS FALSE` with no parameter at all,
1537
+ // so an archived conversation was not hidden — it was unreachable.
1538
+ // The RPC gained `p_archived` on 2026-09-09 (register row R1).
1539
+ p_archived: archived
1469
1540
  },
1470
1541
  operation
1471
1542
  )
@@ -1485,6 +1556,29 @@ function createMessagingRepository(options) {
1485
1556
  nextCursor: hasMore && last !== void 0 ? { beforeSortAt: last.sortAt, beforeConversationId: last.conversation.id } : null
1486
1557
  };
1487
1558
  },
1559
+ async countArchivedConversations(args = {}) {
1560
+ const limit = args.limit ?? 100;
1561
+ const operation = "countArchivedConversations";
1562
+ const rows = await withSessionRetry(
1563
+ operation,
1564
+ () => rpc(
1565
+ RPCS.conversationsWithDetails,
1566
+ {
1567
+ p_user_id: identity.userId,
1568
+ p_limit: limit + 1,
1569
+ p_before_sort_at: null,
1570
+ p_before_conversation_id: null,
1571
+ p_archived: "archived"
1572
+ },
1573
+ operation
1574
+ )
1575
+ );
1576
+ if (rows !== null && !Array.isArray(rows)) {
1577
+ throw invalidResponse(operation, `${RPCS.conversationsWithDetails} did not return rows`);
1578
+ }
1579
+ const found = (rows ?? []).length;
1580
+ return found > limit ? { count: limit, exact: false } : { count: found, exact: true };
1581
+ },
1488
1582
  async getConversation(id) {
1489
1583
  const operation = "getConversation";
1490
1584
  const { data, error } = await withSessionRetry(
@@ -1793,6 +1887,7 @@ function MessagingRuntime(props) {
1793
1887
  onFallback: (message) => report({ level: "warn", message })
1794
1888
  }),
1795
1889
  onDiagnostic: report,
1890
+ ...props.archiveFilter !== void 0 ? { archiveFilter: props.archiveFilter } : {},
1796
1891
  onIncoming: (message) => {
1797
1892
  const snapshot = built?.store.snapshot();
1798
1893
  if (snapshot === void 0) return;
@@ -2023,6 +2118,11 @@ function useConversations() {
2023
2118
  },
2024
2119
  select,
2025
2120
  activeConversationId: snapshot?.activeConversationId ?? null,
2121
+ archiveFilter: snapshot?.archiveFilter ?? DEFAULT_MESSAGING_ARCHIVE_FILTER,
2122
+ setArchiveFilter: (next) => {
2123
+ void host?.engine.setArchiveFilter(next);
2124
+ },
2125
+ archivedCount: snapshot?.archivedCount ?? null,
2026
2126
  startDirect: async (otherUserId) => {
2027
2127
  if (host === null) {
2028
2128
  throw new Error("[@ai-matrx/messaging] startDirect called before the host was ready.");
@@ -2599,9 +2699,37 @@ var AI_LABELS = {
2599
2699
  draftReply: "Draft a reply"
2600
2700
  };
2601
2701
  function ConversationList(props) {
2602
- const { conversations, hasMore, isInitialLoading, loadMore, select, activeConversationId } = useConversations();
2702
+ const {
2703
+ conversations,
2704
+ hasMore,
2705
+ isInitialLoading,
2706
+ loadMore,
2707
+ select,
2708
+ activeConversationId,
2709
+ archiveFilter,
2710
+ setArchiveFilter,
2711
+ archivedCount
2712
+ } = useConversations();
2603
2713
  const RowChrome = useMessagingHost()?.wrapConversationRow ?? null;
2604
2714
  const [query, setQuery] = useState2("");
2715
+ const showingArchive = archiveFilter === "archived";
2716
+ const archiveReveal = useMemo3(() => {
2717
+ if (showingArchive) {
2718
+ return {
2719
+ next: "active",
2720
+ label: "\u2190 Back to active conversations",
2721
+ ariaLabel: "Hide archived conversations"
2722
+ };
2723
+ }
2724
+ if (archivedCount === null) return null;
2725
+ if (archivedCount.count === 0) return null;
2726
+ const printed = archivedCount.exact ? `${archivedCount.count}` : `${archivedCount.count}+`;
2727
+ return {
2728
+ next: "archived",
2729
+ label: `Archived (${printed})`,
2730
+ ariaLabel: "Show archived conversations"
2731
+ };
2732
+ }, [showingArchive, archivedCount]);
2605
2733
  const visible = useMemo3(() => {
2606
2734
  const needle = query.trim().toLowerCase();
2607
2735
  if (needle.length === 0) return conversations;
@@ -2634,12 +2762,22 @@ function ConversationList(props) {
2634
2762
  }
2635
2763
  ) : null
2636
2764
  ] }),
2765
+ archiveReveal !== null ? /* @__PURE__ */ jsx4("div", { className: "mx-msg__list-archive", children: /* @__PURE__ */ jsx4(
2766
+ "button",
2767
+ {
2768
+ type: "button",
2769
+ className: "mx-msg__chip",
2770
+ onClick: () => setArchiveFilter(archiveReveal.next),
2771
+ "aria-label": archiveReveal.ariaLabel,
2772
+ children: archiveReveal.label
2773
+ }
2774
+ ) }) : null,
2637
2775
  /* @__PURE__ */ jsxs2("div", { className: "mx-msg__scroll", children: [
2638
2776
  isInitialLoading ? /* @__PURE__ */ jsx4(ConversationSkeleton, {}) : visible.length === 0 ? /* @__PURE__ */ jsx4(
2639
2777
  EmptyState,
2640
2778
  {
2641
- title: query.length > 0 ? "No matches" : "No conversations yet",
2642
- body: query.length > 0 ? "Try a different name or word." : "Start one and it will appear here."
2779
+ title: query.length > 0 ? "No matches" : showingArchive ? "No archived conversations" : "No conversations yet",
2780
+ body: query.length > 0 ? "Try a different name or word." : showingArchive ? "Archiving a conversation moves it here." : "Start one and it will appear here."
2643
2781
  }
2644
2782
  ) : /* @__PURE__ */ jsx4("ul", { className: "mx-msg__rows", children: visible.map((item) => {
2645
2783
  const row = /* @__PURE__ */ jsx4(
@@ -3134,13 +3272,6 @@ function MessagingInbox(props) {
3134
3272
  }
3135
3273
  );
3136
3274
  }
3137
-
3138
- // src/core/types.ts
3139
- var asConversationId = (value) => value;
3140
- var asMessageId = (value) => value;
3141
- var asUserId = (value) => value;
3142
- var asOrganizationId = (value) => value;
3143
- var asClientMessageId = (value) => value;
3144
3275
  export {
3145
3276
  AgentTag,
3146
3277
  AlertIcon,
@@ -3154,6 +3285,7 @@ export {
3154
3285
  ConversationList,
3155
3286
  ConversationSkeleton,
3156
3287
  ConversationView,
3288
+ DEFAULT_MESSAGING_ARCHIVE_FILTER,
3157
3289
  DeliveryTick,
3158
3290
  DoubleCheckIcon,
3159
3291
  EmptyState,
@@ -3216,6 +3348,7 @@ export {
3216
3348
  resolveActor,
3217
3349
  splitText,
3218
3350
  summarizeText,
3351
+ toMessagingArchiveFilter,
3219
3352
  unreadCutoff,
3220
3353
  useComposer,
3221
3354
  useConversation,