@ai-matrx/messaging 0.1.0 → 0.1.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,27 @@
1
1
  # Changelog — `@ai-matrx/messaging`
2
2
 
3
+ ## 0.1.1 — 2026-08-31
4
+
5
+ Caught in review immediately after 0.1.0 published.
6
+
7
+ ### Fixed
8
+
9
+ - **Sending a message no longer costs a conversation-list read.** `onSent` and the inbox
10
+ channel's message handler both called `listConversations` to refresh one row — a full RPC page
11
+ read per sent message, i.e. one per burst at a busy org, and a regression against the origin
12
+ (`matrx-frontend` updated the row optimistically via `updateConversationLastMessage`). The row's
13
+ new preview and sort position are entirely derivable from the message already in hand, so the
14
+ store gained `applyLastMessage`, which also refuses to move a row BACKWARDS on an out-of-order
15
+ arrival, ignores an optimistic (still-sending) message, and never badges the conversation being
16
+ read. Five regression tests.
17
+ - **The inbox no longer flashes "No conversations yet" before it has looked.** An empty list meant
18
+ two different things — "you have none" and "we have not read yet" — and the surface guessed.
19
+ `MessagingSnapshot.hasLoadedConversations` makes it a fact.
20
+
21
+ ### Consumer action
22
+
23
+ None. Both fixes are internal to the package; no API changed and no host wiring is affected.
24
+
3
25
  ## 0.1.0 — 2026-08-31
4
26
 
5
27
  First release. Enterprise, AI-native in-app messaging for Matrx clients, built to the ratified
@@ -40,7 +62,7 @@ work order at `common-docs/systems/communications/messaging/HANDOFF.md`, on top
40
62
  insets, 44px touch targets, a 16px composer minimum so iOS Safari does not zoom on focus) and
41
63
  `@ai-matrx/messaging/tokens.css` (a complete default palette, light and dark, host-overridable).
42
64
  Hardcoded colors in the structural sheet fail the release gate.
43
- - 113 tests, each named for the failure it prevents, plus a packed-tarball canary that exercises
65
+ - 118 tests, each named for the failure it prevents, plus a packed-tarball canary that exercises
44
66
  ESM + CJS, both entries, the `"use client"` banner per chunk, the `globalThis` slots across
45
67
  module graphs, the stylesheets' presence and export-map resolution, and the stability of the
46
68
  table/RPC names.
package/dist/index.cjs CHANGED
@@ -1111,6 +1111,29 @@ function createMessagingStore() {
1111
1111
  });
1112
1112
  emit();
1113
1113
  },
1114
+ applyLastMessage(message, args = {}) {
1115
+ const existing = conversations.find(
1116
+ (item) => item.conversation.id === message.conversationId
1117
+ );
1118
+ if (existing === void 0) return;
1119
+ if (message.deliveryState === "sending" || message.deletedAt !== null) return;
1120
+ if (existing.lastMessageAt !== null && message.createdAt < existing.lastMessageAt) return;
1121
+ const isActive = activeConversationId === message.conversationId;
1122
+ const shouldCount = args.incrementUnread === true && !isActive;
1123
+ conversations = normalizeConversations(
1124
+ conversations.map(
1125
+ (item) => item.conversation.id === message.conversationId ? {
1126
+ ...item,
1127
+ lastMessageContent: message.content,
1128
+ lastMessageSenderId: message.senderId,
1129
+ lastMessageAt: message.createdAt,
1130
+ sortAt: message.createdAt,
1131
+ unreadCount: shouldCount ? item.unreadCount + 1 : item.unreadCount
1132
+ } : item
1133
+ )
1134
+ );
1135
+ emit();
1136
+ },
1114
1137
  markConversationRead(id) {
1115
1138
  conversations = conversations.map(
1116
1139
  (item) => item.conversation.id === id ? { ...item, unreadCount: 0 } : item
@@ -1181,7 +1204,7 @@ function createMessagingEngine(options) {
1181
1204
  onSent: (entry, message) => {
1182
1205
  store.ingest(message);
1183
1206
  broadcastMessage(message);
1184
- void refreshConversationRow(message.conversationId);
1207
+ store.applyLastMessage(message);
1185
1208
  void entry;
1186
1209
  },
1187
1210
  onChange: (entries) => {
@@ -1207,15 +1230,6 @@ function createMessagingEngine(options) {
1207
1230
  function broadcastMessage(message) {
1208
1231
  openChannels.get(message.conversationId)?.send(MESSAGING_EVENTS.message, message);
1209
1232
  }
1210
- async function refreshConversationRow(id) {
1211
- try {
1212
- const page = await repository.listConversations({ limit: conversationPageSize });
1213
- const row = page.items.find((item) => item.conversation.id === id);
1214
- if (row !== void 0) store.upsertConversation(row);
1215
- } catch (error) {
1216
- reportError(error, "refreshConversationRow");
1217
- }
1218
- }
1219
1233
  async function reloadInbox() {
1220
1234
  const page = await repository.listConversations({ limit: conversationPageSize });
1221
1235
  conversationCursor = page.nextCursor;
@@ -1266,10 +1280,9 @@ function createMessagingEngine(options) {
1266
1280
  return;
1267
1281
  }
1268
1282
  store.ingest(message);
1269
- if (message.senderId !== identity.userId) {
1270
- options.onIncoming?.(message);
1271
- }
1272
- void refreshConversationRow(message.conversationId);
1283
+ const isMine = message.senderId === identity.userId;
1284
+ store.applyLastMessage(message, { incrementUnread: !isMine });
1285
+ if (!isMine) options.onIncoming?.(message);
1273
1286
  }
1274
1287
  },
1275
1288
  {