@ai-matrx/messaging 0.12.5 → 0.13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog — `@ai-matrx/messaging`
2
2
 
3
+ ## 0.13.1
4
+
5
+ ### Fixed
6
+
7
+ - **Opening an unavailable conversation no longer loops.** 0.13.0 cleared a
8
+ conversation's read failure at the START of every `openConversation`, so a
9
+ second holder (the page and its thread pane both calling `useConversation`)
10
+ flipped `unavailable` back to false while its read was in flight. A host that
11
+ swaps the thread for an access surface on that flag remounted the thread,
12
+ which re-opened, which cleared it again — "Maximum update depth exceeded" on
13
+ matrx-frontend `/messages/<id>`. The failure is now replaced only by the next
14
+ answer.
15
+
16
+ ### Consumer action
17
+
18
+ - None — adopt the release.
19
+
20
+ ## 0.13.0
21
+
22
+ ### Added
23
+
24
+ - **A conversation that cannot be read now says so.** Opening a conversation the
25
+ caller may not read (a refused message read) used to leave the thread absent
26
+ forever — an endless spinner — and a zero-row read of a conversation that does
27
+ not exist or is hidden by RLS rendered "No messages yet". The engine now
28
+ records a per-conversation read failure in the store
29
+ (`snapshot.conversationReadFailures`, type `ConversationReadFailure`); on a
30
+ zero-row read it asks the conversation row itself (`getConversation(id)`)
31
+ before calling the thread empty, and opens no realtime channel for a
32
+ conversation the user cannot read.
33
+ - `useConversation(id)` gains `error: MessagingError | null` and
34
+ `unavailable: boolean` (additive). `isLoading` is now false once the read has
35
+ failed. `<ConversationView>` renders "Conversation unavailable" /
36
+ "Couldn't load this conversation" instead of the spinner or the empty state.
37
+
38
+ ### Consumer action
39
+
40
+ - Hosts with an access surface: when `useConversation(id).unavailable` is true,
41
+ render it instead of the thread. `error.cause` is the raw database error
42
+ (`42501`, `PGRST116`) for hosts that classify access failures themselves
43
+ (matrx-frontend: `<AccessGate token="dm_conversation" id error={error.cause} />`).
44
+ - No action otherwise — existing fields are unchanged.
45
+
3
46
  ## 0.12.5
4
47
 
5
48
  Automatic changed-only republish (docs/metadata drift since the last tag — see
package/dist/index.cjs CHANGED
@@ -1044,6 +1044,7 @@ function createMessagingStore() {
1044
1044
  let archivedCount = null;
1045
1045
  let threads = /* @__PURE__ */ new Map();
1046
1046
  let activeConversationId = null;
1047
+ let conversationReadFailures = /* @__PURE__ */ new Map();
1047
1048
  const listeners = /* @__PURE__ */ new Set();
1048
1049
  let cached = null;
1049
1050
  function snapshot() {
@@ -1056,7 +1057,8 @@ function createMessagingStore() {
1056
1057
  activeConversationId,
1057
1058
  totalUnreadConversations: conversations.filter((item) => item.unreadCount > 0).length,
1058
1059
  archiveFilter,
1059
- archivedCount
1060
+ archivedCount,
1061
+ conversationReadFailures
1060
1062
  };
1061
1063
  return cached;
1062
1064
  }
@@ -1140,6 +1142,14 @@ function createMessagingStore() {
1140
1142
  conversations = normalizeConversations(conversations);
1141
1143
  emit();
1142
1144
  },
1145
+ setConversationReadFailure(id, failure) {
1146
+ if (failure === null && !conversationReadFailures.has(id)) return;
1147
+ const next = new Map(conversationReadFailures);
1148
+ if (failure === null) next.delete(id);
1149
+ else next.set(id, failure);
1150
+ conversationReadFailures = next;
1151
+ emit();
1152
+ },
1143
1153
  setThread(id, messages, args = {}) {
1144
1154
  writeThread({
1145
1155
  conversationId: id,
@@ -1447,9 +1457,24 @@ function createMessagingEngine(options) {
1447
1457
  store.setActiveConversation(id);
1448
1458
  try {
1449
1459
  const page = await repository.listMessages(id, { limit: messagePageSize });
1460
+ if (page.items.length === 0) {
1461
+ await repository.getConversation(id);
1462
+ }
1463
+ store.setConversationReadFailure(id, null);
1450
1464
  store.setThread(id, page.items, { hasMoreOlder: page.hasMore });
1451
1465
  } catch (error) {
1452
- reportError(error, "openConversation");
1466
+ const normalized = normalizeMessagingError(error, "openConversation");
1467
+ const unavailable = normalized.code === "not-found" || normalized.code === "forbidden";
1468
+ store.setConversationReadFailure(id, { error: normalized, unavailable });
1469
+ if (unavailable) {
1470
+ report({
1471
+ level: "warn",
1472
+ message: normalized.message,
1473
+ remedy: normalized.remedy
1474
+ });
1475
+ return;
1476
+ }
1477
+ reportError(normalized, "openConversation");
1453
1478
  }
1454
1479
  if (openChannels.has(id) || disposed) return;
1455
1480
  const handle = manager.open({