@ai-matrx/messaging 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog — `@ai-matrx/messaging`
2
2
 
3
+ ## 0.3.0 — 2026-09-07
4
+
5
+ **Three more seams the first adoption needed** — each one a case where the host
6
+ would otherwise have kept a piece of its own messaging UI beside this package's,
7
+ which is the failure this package exists to prevent. Same session as 0.2.0.
8
+
9
+ ### Added
10
+
11
+ - **`wrapMessage` / `wrapConversationRow` — app chrome around a bubble and a row.**
12
+ The reason is the right-click menu: a platform whose every surface answers a right-click with
13
+ copy / export / attach / hand-to-an-agent cannot have messaging be the one surface that does
14
+ not, and that menu needs the app's surface name, entity tokens and clipboard primitive — none
15
+ of which a package can own. The wrapper WRAPS the package's bubble; it cannot replace it.
16
+ - **`renderReference` — the host's own reference renderer.** References are the one part of a
17
+ bubble an app is most likely to already render everywhere else (this platform resolves a
18
+ ```matrx fence through a kind registry into a live chip). Two treatments of one fence in one
19
+ app is the second-renderer defect. Omit it and the package's card is used, unchanged.
20
+ - **`onIncomingMessage` now receives an `IncomingMessageContext`** —
21
+ `{ isActiveConversation, conversation }`. "Do not interrupt someone for the conversation they
22
+ are looking at" is the rule every chat app needs and only this package can answer, and a
23
+ desktop notification names the SENDER, whose display name lives on a conversation the host
24
+ cannot see from above the provider. (The origin surface had `sender_id.substring(0, 8)` and a
25
+ `// Placeholder` comment where the name belonged.)
26
+
27
+ ### Consumer action (C28)
28
+
29
+ - `onIncomingMessage` gains a SECOND argument. Existing one-argument callbacks keep compiling and
30
+ keep working — but if you were suppressing notifications for the open conversation by reading
31
+ your own state, delete that and read `context.isActiveConversation`.
32
+ - Hosts with their own reference chips or per-row context menus: pass `renderReference` /
33
+ `wrapMessage` / `wrapConversationRow` and DELETE the host-side message list.
34
+
3
35
  ## 0.2.0 — 2026-09-07
4
36
 
5
37
  **Both fixes came from the first real adoption** (matrx-frontend, the C9 full-elimination swap).
package/README.md CHANGED
@@ -241,6 +241,27 @@ rule that matters: an unknown kind — or a known kind at a version this build d
241
241
  renders NOTHING. Use `actions` for decisions, `actionRenderers` for surfaces; a kind may have
242
242
  both a handler (for `summarize`, used by previews) and a renderer.
243
243
 
244
+ ### Your app's chrome around our bubbles
245
+
246
+ A platform whose surfaces all answer a right-click, or that already renders references through
247
+ its own registry, hands those in rather than rebuilding the thread:
248
+
249
+ ```tsx
250
+ <MessagingProvider …
251
+ wrapMessage={({ message, children }) => (
252
+ <MessageContextMenu message={message}>{children}</MessageContextMenu>
253
+ )}
254
+ wrapConversationRow={({ conversation, children }) => (
255
+ <ConversationContextMenu conversation={conversation}>{children}</ConversationContextMenu>
256
+ )}
257
+ renderReference={({ reference }) => <AppReferenceChip reference={reference} />}
258
+ />
259
+ ```
260
+
261
+ `wrapMessage` and `wrapConversationRow` WRAP; they never replace. `renderReference` replaces the
262
+ package's card — deliberately, because an app that draws references everywhere else must not
263
+ draw them two ways.
264
+
244
265
  ### Theming
245
266
 
246
267
  Structural CSS ships in the package; the token **contract** is enforced; token **values** are
package/dist/react.cjs CHANGED
@@ -1831,7 +1831,8 @@ function MessagingRuntime(props) {
1831
1831
  identity,
1832
1832
  ...props.resolveSession !== void 0 ? { resolveSession: props.resolveSession } : {}
1833
1833
  });
1834
- return createMessagingEngine({
1834
+ let built = null;
1835
+ built = createMessagingEngine({
1835
1836
  repository,
1836
1837
  manager,
1837
1838
  identity,
@@ -1840,8 +1841,18 @@ function MessagingRuntime(props) {
1840
1841
  onFallback: (message) => report({ level: "warn", message })
1841
1842
  }),
1842
1843
  onDiagnostic: report,
1843
- onIncoming: (message) => incomingRef.current?.(message)
1844
+ onIncoming: (message) => {
1845
+ const snapshot = built?.store.snapshot();
1846
+ if (snapshot === void 0) return;
1847
+ incomingRef.current?.(message, {
1848
+ isActiveConversation: snapshot.activeConversationId === message.conversationId,
1849
+ conversation: snapshot.conversations.find(
1850
+ (item) => item.conversation.id === message.conversationId
1851
+ ) ?? null
1852
+ });
1853
+ }
1844
1854
  });
1855
+ return built;
1845
1856
  }, [ready, manager, userId, organizationId]);
1846
1857
  (0, import_react.useEffect)(() => {
1847
1858
  if (engine === null) return void 0;
@@ -1874,6 +1885,9 @@ function MessagingRuntime(props) {
1874
1885
  });
1875
1886
  return map;
1876
1887
  }, [renderers]);
1888
+ const renderReference = props.renderReference ?? null;
1889
+ const wrapMessage = props.wrapMessage ?? null;
1890
+ const wrapConversationRow = props.wrapConversationRow ?? null;
1877
1891
  const host = (0, import_react.useMemo)(() => {
1878
1892
  if (engine === null) return null;
1879
1893
  return {
@@ -1882,9 +1896,12 @@ function MessagingRuntime(props) {
1882
1896
  ai,
1883
1897
  identity: engine.identity,
1884
1898
  openReference: referenceRef.current ?? null,
1885
- actionRenderers: rendererMap
1899
+ actionRenderers: rendererMap,
1900
+ renderReference,
1901
+ wrapMessage,
1902
+ wrapConversationRow
1886
1903
  };
1887
- }, [engine, actionRegistry, ai, rendererMap]);
1904
+ }, [engine, actionRegistry, ai, rendererMap, renderReference, wrapMessage, wrapConversationRow]);
1888
1905
  const MessagingContext = messagingContext();
1889
1906
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingContext.Provider, { value: host, children });
1890
1907
  }
@@ -2555,6 +2572,7 @@ var AI_LABELS = {
2555
2572
  };
2556
2573
  function ConversationList(props) {
2557
2574
  const { conversations, hasMore, isInitialLoading, loadMore, select, activeConversationId } = useConversations();
2575
+ const RowChrome = useMessagingHost()?.wrapConversationRow ?? null;
2558
2576
  const [query, setQuery] = (0, import_react5.useState)("");
2559
2577
  const visible = (0, import_react5.useMemo)(() => {
2560
2578
  const needle = query.trim().toLowerCase();
@@ -2595,17 +2613,20 @@ function ConversationList(props) {
2595
2613
  title: query.length > 0 ? "No matches" : "No conversations yet",
2596
2614
  body: query.length > 0 ? "Try a different name or word." : "Start one and it will appear here."
2597
2615
  }
2598
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-msg__rows", children: visible.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2599
- ConversationRow,
2600
- {
2601
- summary: item,
2602
- isActive: item.conversation.id === activeConversationId,
2603
- onSelect: () => {
2604
- select(item.conversation.id);
2605
- props.onSelect?.(item.conversation.id);
2616
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "mx-msg__rows", children: visible.map((item) => {
2617
+ const row = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2618
+ ConversationRow,
2619
+ {
2620
+ summary: item,
2621
+ isActive: item.conversation.id === activeConversationId,
2622
+ onSelect: () => {
2623
+ select(item.conversation.id);
2624
+ props.onSelect?.(item.conversation.id);
2625
+ }
2606
2626
  }
2607
- }
2608
- ) }, item.conversation.id)) }),
2627
+ );
2628
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: RowChrome !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowChrome, { conversation: item, children: row }) : row }, item.conversation.id);
2629
+ }) }),
2609
2630
  hasMore && !isInitialLoading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2610
2631
  "button",
2611
2632
  {
@@ -2834,6 +2855,8 @@ function MessageGroupView(props) {
2834
2855
  function MessageBubble(props) {
2835
2856
  const { message, isMine } = props;
2836
2857
  const host = useMessagingHost();
2858
+ const HostReference = host?.renderReference ?? null;
2859
+ const MessageChrome = host?.wrapMessage ?? null;
2837
2860
  if (message.deletedAt !== null) {
2838
2861
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mx-msg__bubble mx-msg__bubble--deleted", children: "Message deleted" });
2839
2862
  }
@@ -2844,10 +2867,16 @@ function MessageBubble(props) {
2844
2867
  message.deliveryState === "sending" ? "mx-msg__bubble--pending" : "",
2845
2868
  message.deliveryState === "failed" ? "mx-msg__bubble--failed" : ""
2846
2869
  ].filter(Boolean).join(" ");
2847
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2870
+ const bubble = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2848
2871
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: classes, children: [
2849
2872
  splitText(message.content).map(
2850
- (segment, index) => segment.type === "text" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: segment.value }, index) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2873
+ (segment, index) => segment.type === "text" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: segment.value }, index) : HostReference !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2874
+ HostReference,
2875
+ {
2876
+ reference: segment.reference
2877
+ },
2878
+ `${segment.reference.entityType}:${segment.reference.entityId}`
2879
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2851
2880
  ReferenceCard,
2852
2881
  {
2853
2882
  reference: segment.reference,
@@ -2856,14 +2885,22 @@ function MessageBubble(props) {
2856
2885
  `${segment.reference.entityType}:${segment.reference.entityId}`
2857
2886
  )
2858
2887
  ),
2859
- message.references.map((reference) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2860
- ReferenceCard,
2861
- {
2862
- reference,
2863
- onOpen: host?.openReference ?? null
2864
- },
2865
- `structured:${reference.entityType}:${reference.entityId}`
2866
- )),
2888
+ message.references.map(
2889
+ (reference) => HostReference !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2890
+ HostReference,
2891
+ {
2892
+ reference
2893
+ },
2894
+ `structured:${reference.entityType}:${reference.entityId}`
2895
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2896
+ ReferenceCard,
2897
+ {
2898
+ reference,
2899
+ onOpen: host?.openReference ?? null
2900
+ },
2901
+ `structured:${reference.entityType}:${reference.entityId}`
2902
+ )
2903
+ ),
2867
2904
  message.action !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MessageActionChips, { message }) : null
2868
2905
  ] }),
2869
2906
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -2899,6 +2936,7 @@ function MessageBubble(props) {
2899
2936
  }
2900
2937
  )
2901
2938
  ] });
2939
+ return MessageChrome !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MessageChrome, { message, isMine, children: bubble }) : bubble;
2902
2940
  }
2903
2941
  function MessageActionChips(props) {
2904
2942
  const host = useRequiredMessagingHost();