@ai-matrx/messaging 0.1.3 → 0.2.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,48 @@
1
1
  # Changelog — `@ai-matrx/messaging`
2
2
 
3
+ ## 0.2.0 — 2026-09-07
4
+
5
+ **Both fixes came from the first real adoption** (matrx-frontend, the C9 full-elimination swap).
6
+ Each is a case of the package forcing the host to keep something it should not have had to keep.
7
+
8
+ ### Fixed
9
+
10
+ - **The provider no longer builds a SECOND realtime manager under a host that already has one.**
11
+ `<MessagingProvider>` mounted `<RealtimeProvider>` unconditionally. An app that wires
12
+ `@ai-matrx/realtime` has exactly ONE provider on purpose — the manager owns the write ledger
13
+ that makes an optimistic write's own echo recognizable — so a second manager beneath it gave
14
+ this package its own ledger and its own channels: our sends looked remote to the host's
15
+ realtime consumers, and both sides subscribed to the same rooms twice. The provider now asks
16
+ `useIsRealtimeProviderMounted()` (`@ai-matrx/realtime` 0.3.0) and rides the host's manager when
17
+ there is one; an app that wires none still gets a provider for free, so R7 is unchanged.
18
+ Probing `useRealtimeManager() !== null` would NOT have worked — it is also null while a mounted
19
+ host provider waits for its Supabase client.
20
+ - **The React context moved into the `globalSlot`.** Module-scope `createContext` splits in two
21
+ under dual ESM/CJS evaluation, exactly like every other piece of module-level state this
22
+ package already slots.
23
+
24
+ ### Added
25
+
26
+ - **`actionRenderers` — a host surface for an action kind whose answer is a CARD, not a chip.**
27
+ Chips answer a question ("Approve" / "Decline"); a shared-resource card, a link out to a
28
+ report, an app-icon reminder are app-shaped surfaces only the host can draw. Without this seam
29
+ the first consumer had to keep its own message renderer beside this package's — a package plus
30
+ a surviving custom copy, which is the failure this package exists to prevent. The
31
+ forward-compatibility rule stays the PACKAGE's: an unknown kind, or a known kind at a version
32
+ the renderer does not list, renders nothing.
33
+ Types: `MessageActionRenderer`, `MessageActionRenderProps` from `@ai-matrx/messaging/react`.
34
+ - `src/react/host-integration.test.tsx` — four tests: the host's manager is the one messaging
35
+ uses (fails against 0.1.3), a provider-less app still gets one, a host renderer draws inside
36
+ the bubble, and an unlisted version renders nothing.
37
+
38
+ ### Consumer action (C28)
39
+
40
+ - **Requires `@ai-matrx/realtime` >= 0.3.0**, picked up automatically by the `"latest"` spec.
41
+ - Nothing to change in existing wiring. If your app mounts its own `<RealtimeProvider>`, messaging
42
+ now rides it — that is the fix, and it is what you wanted.
43
+ - Hosts with action kinds that render a card rather than a decision: pass them as
44
+ `actionRenderers={[{ kind, versions, render }]}` and DELETE the host-side bubble renderer.
45
+
3
46
  ## 0.1.3
4
47
 
5
48
  Automatic changed-only republish (docs/metadata drift since the last tag — see
package/README.md CHANGED
@@ -184,6 +184,19 @@ that fails when pressed.
184
184
  Omit it and reference cards render as labeled, non-interactive cards with a title explaining why —
185
185
  honest, never a button that does nothing.
186
186
 
187
+ ### Mounting it in an app that already wires realtime
188
+
189
+ Nothing to do. `<MessagingProvider>` rides the host's `<RealtimeProvider>` when one is mounted
190
+ above it, and mounts its own only when there is none. That is not a convenience — an app has ONE
191
+ realtime manager on purpose (it owns the write ledger that makes an optimistic write's own echo
192
+ recognizable), and a second one under it makes each manager's writes look remote to the other.
193
+
194
+ ```tsx
195
+ <RealtimeProvider client={supabase} actorId={user.id}> {/* the app's ONE mount */}
196
+ <MessagingProvider client={supabase} userId={user.id} organizationId={org.id}>
197
+
198
+ ```
199
+
187
200
  ### Registering an actionable message kind
188
201
 
189
202
  ```tsx
@@ -209,6 +222,25 @@ honest, never a button that does nothing.
209
222
  This is the seam `@ai-matrx/meet` uses: a call invitation is just a message with a structured
210
223
  payload.
211
224
 
225
+ ### An action kind whose answer is a card, not a chip
226
+
227
+ Chips answer a question. A shared-resource card, a link out to a report, a reminder with an app
228
+ icon are app-shaped surfaces only the host can draw — so the host supplies the surface and the
229
+ package still owns the gate:
230
+
231
+ ```tsx
232
+ <MessagingProvider … actionRenderers={[{
233
+ kind: "resource_shared",
234
+ versions: [1],
235
+ render: ({ payload, isOwn }) => <ResourceSharedCard payload={payload} isOwn={isOwn} />,
236
+ }]}>
237
+ ```
238
+
239
+ A kind with a renderer draws it instead of chips. Everything else is unchanged, including the
240
+ rule that matters: an unknown kind — or a known kind at a version this build does not list —
241
+ renders NOTHING. Use `actions` for decisions, `actionRenderers` for surfaces; a kind may have
242
+ both a handler (for `summarize`, used by previews) and a renderer.
243
+
212
244
  ### Theming
213
245
 
214
246
  Structural CSS ships in the package; the token **contract** is enforced; token **values** are
package/dist/react.cjs CHANGED
@@ -1780,7 +1780,12 @@ function createMessagingRepository(options) {
1780
1780
 
1781
1781
  // src/react/provider.tsx
1782
1782
  var import_jsx_runtime = require("react/jsx-runtime");
1783
- var MessagingContext = (0, import_react.createContext)(null);
1783
+ function messagingContext() {
1784
+ return globalSlot(
1785
+ "react-context",
1786
+ () => (0, import_react.createContext)(null)
1787
+ );
1788
+ }
1784
1789
  function defaultDiagnostics(event) {
1785
1790
  const line = `[@ai-matrx/messaging] ${event.message}${event.remedy !== void 0 ? `
1786
1791
  \u2192 ${event.remedy}` : ""}`;
@@ -1791,6 +1796,8 @@ function defaultDiagnostics(event) {
1791
1796
  function MessagingProvider(props) {
1792
1797
  const { client, userId, organizationId } = props;
1793
1798
  const ready = client != null && typeof userId === "string" && userId.length > 0 && typeof organizationId === "string" && organizationId.length > 0;
1799
+ const hostProvidesRealtime = (0, import_react2.useIsRealtimeProviderMounted)();
1800
+ if (hostProvidesRealtime) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingRuntime, { ...props });
1794
1801
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.RealtimeProvider, { client: ready ? client : null, actorId: userId ?? void 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingRuntime, { ...props }) });
1795
1802
  }
1796
1803
  function MessagingRuntime(props) {
@@ -1859,6 +1866,14 @@ function MessagingRuntime(props) {
1859
1866
  agents
1860
1867
  });
1861
1868
  }, [transport, agents, ready, organizationId]);
1869
+ const renderers = props.actionRenderers;
1870
+ const rendererMap = (0, import_react.useMemo)(() => {
1871
+ const map = /* @__PURE__ */ new Map();
1872
+ (renderers ?? []).forEach((renderer) => {
1873
+ map.set(renderer.kind, renderer);
1874
+ });
1875
+ return map;
1876
+ }, [renderers]);
1862
1877
  const host = (0, import_react.useMemo)(() => {
1863
1878
  if (engine === null) return null;
1864
1879
  return {
@@ -1866,13 +1881,15 @@ function MessagingRuntime(props) {
1866
1881
  actions: actionRegistry,
1867
1882
  ai,
1868
1883
  identity: engine.identity,
1869
- openReference: referenceRef.current ?? null
1884
+ openReference: referenceRef.current ?? null,
1885
+ actionRenderers: rendererMap
1870
1886
  };
1871
- }, [engine, actionRegistry, ai]);
1887
+ }, [engine, actionRegistry, ai, rendererMap]);
1888
+ const MessagingContext = messagingContext();
1872
1889
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagingContext.Provider, { value: host, children });
1873
1890
  }
1874
1891
  function useMessagingHost() {
1875
- return (0, import_react.useContext)(MessagingContext);
1892
+ return (0, import_react.useContext)(messagingContext());
1876
1893
  }
1877
1894
  function useRequiredMessagingHost() {
1878
1895
  const host = useMessagingHost();
@@ -2884,7 +2901,22 @@ function MessageBubble(props) {
2884
2901
  ] });
2885
2902
  }
2886
2903
  function MessageActionChips(props) {
2904
+ const host = useRequiredMessagingHost();
2887
2905
  const action = useMessageAction(props.message);
2906
+ const declared = props.message.action;
2907
+ const renderer = declared === null ? void 0 : host.actionRenderers.get(declared.kind);
2908
+ if (declared !== null && renderer !== void 0) {
2909
+ if (!renderer.versions.includes(declared.version)) return null;
2910
+ const Render = renderer.render;
2911
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2912
+ Render,
2913
+ {
2914
+ message: props.message,
2915
+ payload: declared.payload,
2916
+ isOwn: props.message.senderId === host.identity.userId
2917
+ }
2918
+ );
2919
+ }
2888
2920
  if (action.receipt !== null) {
2889
2921
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "mx-msg__receipt", children: [
2890
2922
  action.receipt.label,