@ai-matrx/messaging 0.1.0 → 0.1.2
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 +58 -1
- package/dist/index.cjs +27 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +27 -14
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +44 -19
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +16 -0
- package/dist/react.d.ts +16 -0
- package/dist/react.js +44 -19
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Changelog — `@ai-matrx/messaging`
|
|
2
2
|
|
|
3
|
+
## 0.1.2 — 2026-08-31
|
|
4
|
+
|
|
5
|
+
**Typing, presence and message broadcast work.** The root defect was in `@ai-matrx/realtime`
|
|
6
|
+
(every channel got a unique instance topic, and for broadcast/presence the topic IS the room);
|
|
7
|
+
this package found it, and it is fixed in realtime 0.2.0. These are the consumer-side changes
|
|
8
|
+
that fix came with — both of them real defects this package was carrying.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **One presence publisher per conversation.** One channel carries ONE presence entry per key, so
|
|
13
|
+
`useTypists` and `useOnlineUserIds` now read the roster with `presence: { publish: false }`.
|
|
14
|
+
The composer — which actually knows whether this user is typing — is the single publisher. Two
|
|
15
|
+
publishers overwrote each other on every heartbeat.
|
|
16
|
+
- **One composer per conversation, so REPLY works.** `ConversationView` called `useComposer` and
|
|
17
|
+
also rendered `<Composer>`, which called it again: two independent drafts, two independent
|
|
18
|
+
reply targets — picking "reply" on a message set it on an instance the composer's reply bar
|
|
19
|
+
never read — and two typing publishers. The view now owns the state and renders `ComposerView`;
|
|
20
|
+
the exported `Composer` is unchanged for standalone use.
|
|
21
|
+
- **A broadcast message one field short no longer takes down the thread.** `useMessageAction`
|
|
22
|
+
reads `message.action ?? null` instead of assuming the field is present; an off-shape wire
|
|
23
|
+
payload used to throw `undefined.kind` and unmount the conversation.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- `src/react/realtime-rooms.test.tsx` — renders the real `ConversationView` against a shared,
|
|
28
|
+
topic-deduping fake Supabase client and proves every holder of the conversation topic lands on
|
|
29
|
+
ONE live channel named as declared (0.1.1 produced five), a remote typist reaches the
|
|
30
|
+
indicator, a remote broadcast reaches the thread, and exactly one holder publishes presence.
|
|
31
|
+
All four fail against `@ai-matrx/realtime` 0.1.1.
|
|
32
|
+
|
|
33
|
+
### Consumer action (C28)
|
|
34
|
+
|
|
35
|
+
None. No public API changed. `@ai-matrx/realtime` >= 0.2.0 is required and is picked up
|
|
36
|
+
automatically by the `"latest"` spec.
|
|
37
|
+
|
|
38
|
+
## 0.1.1 — 2026-08-31
|
|
39
|
+
|
|
40
|
+
Caught in review immediately after 0.1.0 published.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **Sending a message no longer costs a conversation-list read.** `onSent` and the inbox
|
|
45
|
+
channel's message handler both called `listConversations` to refresh one row — a full RPC page
|
|
46
|
+
read per sent message, i.e. one per burst at a busy org, and a regression against the origin
|
|
47
|
+
(`matrx-frontend` updated the row optimistically via `updateConversationLastMessage`). The row's
|
|
48
|
+
new preview and sort position are entirely derivable from the message already in hand, so the
|
|
49
|
+
store gained `applyLastMessage`, which also refuses to move a row BACKWARDS on an out-of-order
|
|
50
|
+
arrival, ignores an optimistic (still-sending) message, and never badges the conversation being
|
|
51
|
+
read. Five regression tests.
|
|
52
|
+
- **The inbox no longer flashes "No conversations yet" before it has looked.** An empty list meant
|
|
53
|
+
two different things — "you have none" and "we have not read yet" — and the surface guessed.
|
|
54
|
+
`MessagingSnapshot.hasLoadedConversations` makes it a fact.
|
|
55
|
+
|
|
56
|
+
### Consumer action
|
|
57
|
+
|
|
58
|
+
None. Both fixes are internal to the package; no API changed and no host wiring is affected.
|
|
59
|
+
|
|
3
60
|
## 0.1.0 — 2026-08-31
|
|
4
61
|
|
|
5
62
|
First release. Enterprise, AI-native in-app messaging for Matrx clients, built to the ratified
|
|
@@ -40,7 +97,7 @@ work order at `common-docs/systems/communications/messaging/HANDOFF.md`, on top
|
|
|
40
97
|
insets, 44px touch targets, a 16px composer minimum so iOS Safari does not zoom on focus) and
|
|
41
98
|
`@ai-matrx/messaging/tokens.css` (a complete default palette, light and dark, host-overridable).
|
|
42
99
|
Hardcoded colors in the structural sheet fail the release gate.
|
|
43
|
-
-
|
|
100
|
+
- 118 tests, each named for the failure it prevents, plus a packed-tarball canary that exercises
|
|
44
101
|
ESM + CJS, both entries, the `"use client"` banner per chunk, the `globalThis` slots across
|
|
45
102
|
module graphs, the stylesheets' presence and export-map resolution, and the stability of the
|
|
46
103
|
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
|
-
|
|
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
|
-
|
|
1270
|
-
|
|
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
|
{
|