@ai-matrx/messaging 0.0.0 → 0.1.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 +78 -0
- package/LICENSE +21 -0
- package/README.md +287 -2
- package/dist/index.cjs +1880 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +995 -0
- package/dist/index.d.ts +995 -0
- package/dist/index.js +1862 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +2990 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +1273 -0
- package/dist/react.d.ts +1273 -0
- package/dist/react.js +2980 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +745 -0
- package/dist/tokens.css +119 -0
- package/package.json +100 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Changelog — `@ai-matrx/messaging`
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-08-31
|
|
4
|
+
|
|
5
|
+
First release. Enterprise, AI-native in-app messaging for Matrx clients, built to the ratified
|
|
6
|
+
work order at `common-docs/systems/communications/messaging/HANDOFF.md`, on top of
|
|
7
|
+
`@ai-matrx/realtime` (D5: zero hand-rolled `.channel(` in this package).
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Core (`@ai-matrx/messaging`)** — framework-free; the only runtime dependencies are
|
|
12
|
+
`@ai-matrx/realtime` and `@ai-matrx/agents`.
|
|
13
|
+
- `createMessagingRepository` — THE data contract, and the only file in the package holding a
|
|
14
|
+
table or RPC name. Atomic direct-conversation creation through
|
|
15
|
+
`dm_get_or_create_direct_conversation` (read-then-insert is structurally absent), keyset
|
|
16
|
+
pagination terminated by a unique column, explicit `organization_id` on every write, a single
|
|
17
|
+
session retry that never touches a permission denial, and an in-flight-deduped profile cache.
|
|
18
|
+
- `createMessagingStore` — the one door every incoming message uses. Collapses the optimistic
|
|
19
|
+
bubble onto its confirmed row by client key, drops out-of-order updates, orders by
|
|
20
|
+
`(created_at, id)`, and forces the active conversation's unread count to zero.
|
|
21
|
+
- `createOutbox` — a message you typed is never lost: persisted before the send, one
|
|
22
|
+
idempotency key across every attempt, strict order, durable across a reload, and a bounded
|
|
23
|
+
failure that keeps the message with a reason and a retry door.
|
|
24
|
+
- `createMessagingEngine` — channel wiring with a backfill door on every recovery path,
|
|
25
|
+
optimistic send, read receipts, and conversation lifecycle.
|
|
26
|
+
- `createActionRegistry` — actionable messages with idempotence enforced by the registry, and
|
|
27
|
+
forward compatibility on unknown kinds and versions. The seam `@ai-matrx/meet` extends.
|
|
28
|
+
- `createMessagingAi` — catch me up, summarize, extract action items, draft a reply, executed
|
|
29
|
+
through the platform's agent system. Machine content travels as named variables, never
|
|
30
|
+
`user_input`.
|
|
31
|
+
- Matrx references (`splitText`, `summarizeText`, `extractReferences`, `composeFence`),
|
|
32
|
+
effective-actor resolution, the ingress-boundary projection layer, the typed error
|
|
33
|
+
classification, formatting/grouping helpers, and the two channel namespaces.
|
|
34
|
+
- **React (`@ai-matrx/messaging/react`)** — the complete product, not a kernel:
|
|
35
|
+
`MessagingProvider`, `MessagingInbox`, `ConversationList`, `ConversationView`, `Composer`,
|
|
36
|
+
`MessageBubble`, `MessageActionChips`, the parts (`Avatar`, `ReferenceCard`, `DeliveryTick`,
|
|
37
|
+
skeletons, empty states), fifteen inlined SVG icons (C19 — no icon-library dependency), and the
|
|
38
|
+
hooks for hosts that compose their own page layout.
|
|
39
|
+
- **Styling** — `@ai-matrx/messaging/styles.css` (structural: layout, motion, `100dvh`, safe-area
|
|
40
|
+
insets, 44px touch targets, a 16px composer minimum so iOS Safari does not zoom on focus) and
|
|
41
|
+
`@ai-matrx/messaging/tokens.css` (a complete default palette, light and dark, host-overridable).
|
|
42
|
+
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
|
|
44
|
+
ESM + CJS, both entries, the `"use client"` banner per chunk, the `globalThis` slots across
|
|
45
|
+
module graphs, the stylesheets' presence and export-map resolution, and the stability of the
|
|
46
|
+
table/RPC names.
|
|
47
|
+
|
|
48
|
+
### Deliberately not in this release
|
|
49
|
+
|
|
50
|
+
Recorded in `FEATURE.md` § Deliberate omissions and in the work order, not quietly dropped:
|
|
51
|
+
reactions, per-message read receipts, edit history and pinning (all need a DB change — the
|
|
52
|
+
canonical schema has no columns for them); attachment UPLOAD (reads and durable-ref rendering
|
|
53
|
+
ship; the upload path belongs on `@ai-matrx/data/files`); a separate thread surface; moderation
|
|
54
|
+
affordances; and a `/native` entry (C25 — built at the first real Expo consumer, inside this
|
|
55
|
+
package).
|
|
56
|
+
|
|
57
|
+
### Consumer action
|
|
58
|
+
|
|
59
|
+
None required — this is a first release with no existing consumers.
|
|
60
|
+
|
|
61
|
+
Adopting it in a repo that has its own messaging feature (matrx-frontend is the intended first
|
|
62
|
+
consumer):
|
|
63
|
+
|
|
64
|
+
- Wrap the messaging route in `<MessagingProvider client={supabase} userId={…}
|
|
65
|
+
organizationId={…}>` and render `<MessagingInbox />`. Import `tokens.css` then `styles.css`.
|
|
66
|
+
- **Delete, do not wrap** (C9): the host's own realtime subscription and channel bookkeeping, its
|
|
67
|
+
message dedup and echo suppression, its optimistic-send reconciliation, its typing and presence
|
|
68
|
+
timers, its conversation-list cache and in-flight dedup, and its `find-then-insert`
|
|
69
|
+
direct-conversation path. A second copy beside the package is the named failure.
|
|
70
|
+
- Re-register the host's actionable-message kinds as `ActionHandler`s on the provider. Each
|
|
71
|
+
handler must re-resolve its authorizing row server-side; the payload is the request, never the
|
|
72
|
+
permission.
|
|
73
|
+
- Wire `onOpenReference` to the app's router, or reference cards render inert-but-labeled.
|
|
74
|
+
- Pass `transport` + `agents` to light up the AI actions; without them they do not render.
|
|
75
|
+
- `resolveSession` enables the single retry on a missing session — pass it, or a read during
|
|
76
|
+
sign-in reports `session-unavailable` instead of recovering.
|
|
77
|
+
- This package supersedes host code that talks to `communication.dm_*` directly. Anything still
|
|
78
|
+
reading those tables outside the package should be moved onto it or deleted.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AI Matrix Engine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,288 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@ai-matrx/messaging`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Enterprise, AI-native in-app messaging for every Matrx client: direct, group, and org-scoped
|
|
4
|
+
conversations, with agents as first-class participants — delivered as a drop-in package with the
|
|
5
|
+
polish of iMessage and the power of Slack.
|
|
6
|
+
|
|
7
|
+
One provider, one component, under an hour to full production messaging including the AI.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @ai-matrx/messaging
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Built on [`@ai-matrx/realtime`](../realtime/README.md). There is **zero hand-rolled `.channel(`
|
|
14
|
+
code in this package**, and there must be none in yours.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Why this package exists
|
|
19
|
+
|
|
20
|
+
Messaging is the feature everyone assumes is easy and nobody ships correctly twice. The hard
|
|
21
|
+
parts are not the bubbles; they are the eight ways a message can be lost, duplicated, reverted,
|
|
22
|
+
or attributed to the wrong person. Every one of those has a name in this README and a regression
|
|
23
|
+
test beside it.
|
|
24
|
+
|
|
25
|
+
We are deliberately **inflexible**. There is no provider abstraction, no transport adapter, no
|
|
26
|
+
"bring your own store". The package does exactly what we want it to do, and that is the product.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## The doctrine
|
|
31
|
+
|
|
32
|
+
### 1. A message can arrive FOUR ways. It renders once.
|
|
33
|
+
|
|
34
|
+
The optimistic bubble, the insert's own response, the broadcast, and the Postgres Changes row —
|
|
35
|
+
in any order. Identity is `id` **or** `clientMessageId`, and the confirmed row *collapses onto*
|
|
36
|
+
the optimistic one in place.
|
|
37
|
+
|
|
38
|
+
The tempting shortcut — matching on `(sender, content)` — silently merges two genuinely different
|
|
39
|
+
"ok" messages into one. The client-minted idempotency key exists so identity never has to be
|
|
40
|
+
guessed. It is minted once per queued message and reused by **every** send attempt, which is also
|
|
41
|
+
what makes a retry safe when the previous attempt actually reached the database.
|
|
42
|
+
|
|
43
|
+
### 2. Older never overwrites newer.
|
|
44
|
+
|
|
45
|
+
An out-of-order UPDATE carrying an older `edited_at` than the copy already held is **dropped**.
|
|
46
|
+
Without this, an edit visibly reverts itself a second later and then re-applies.
|
|
47
|
+
|
|
48
|
+
The one exception is deliberate: a persisted row always supersedes an optimistic one, even when
|
|
49
|
+
the optimistic timestamp reads later. That timestamp is a guess from the sending device's clock,
|
|
50
|
+
and a skewed clock must not strand a bubble as permanently "sending".
|
|
51
|
+
|
|
52
|
+
### 3. Reconnect means RE-READ. Realtime has no replay.
|
|
53
|
+
|
|
54
|
+
Every channel this package opens declares an `onBackfill` door, and those doors re-read from the
|
|
55
|
+
database — the conversation channel from its own high-water mark, so it fetches what it missed
|
|
56
|
+
rather than everything. A reconnect without a re-read leaves a permanently wrong thread that
|
|
57
|
+
looks perfectly healthy. That is the most expensive bug in messaging, and it is structurally
|
|
58
|
+
impossible here.
|
|
59
|
+
|
|
60
|
+
### 4. A message you typed is NEVER lost.
|
|
61
|
+
|
|
62
|
+
The outbox persists the draft **before** the network call, not after it succeeds. Guarantees:
|
|
63
|
+
|
|
64
|
+
- **Durable across a reload.** A tab closed mid-send comes back with the message still queued.
|
|
65
|
+
The default storage is `localStorage`; when it is unavailable the package falls back to memory
|
|
66
|
+
and **says so** rather than pretending to be durable.
|
|
67
|
+
- **In order.** Message 2 never overtakes a retrying message 1.
|
|
68
|
+
- **Bounded and honest.** Attempts back off; after the last one the entry stays in the queue,
|
|
69
|
+
marked `failed`, **with a reason** and a retry door. Never silently dropped, never retrying
|
|
70
|
+
forever.
|
|
71
|
+
- **Visible.** A queued message renders as an optimistic bubble immediately. An invisible queue
|
|
72
|
+
is a lost message from the user's side.
|
|
73
|
+
|
|
74
|
+
### 5. Direct conversations are created ATOMICALLY, in the database.
|
|
75
|
+
|
|
76
|
+
Via `dm_get_or_create_direct_conversation`, which advisory-locks the unordered user pair. The
|
|
77
|
+
banned pattern is read-then-insert: two tabs, a double-click, or two batched system
|
|
78
|
+
notifications race it into two "direct" conversations for one pair, forever.
|
|
79
|
+
|
|
80
|
+
### 6. Authorization is the database's. Org is explicit.
|
|
81
|
+
|
|
82
|
+
RLS plus auth-checked SECURITY DEFINER RPCs. There is not one permission branch in this package —
|
|
83
|
+
an RPC that says no becomes a `forbidden` error, never a client-side re-decision. And every
|
|
84
|
+
conversation and message write carries an explicit `organization_id`; the package refuses the
|
|
85
|
+
write rather than letting an unscoped row reach the database.
|
|
86
|
+
|
|
87
|
+
### 7. A missing session is a lifecycle moment, not an error.
|
|
88
|
+
|
|
89
|
+
A read that lands between sign-in and token arrival retries **once** after the host resolves a
|
|
90
|
+
session, then reports `session-unavailable` as a *warning*. A permission denial is never retried —
|
|
91
|
+
retrying one is how a request storm is built. (The incident: a reader mounted before the access
|
|
92
|
+
token existed and the app captured **909 errors in 0.6 seconds**.) Profile and list reads also
|
|
93
|
+
share one in-flight promise per key, so N concurrent readers make one request — and a failure is
|
|
94
|
+
never cached, or one wobble leaves the surface broken until reload.
|
|
95
|
+
|
|
96
|
+
### 8. Pagination is keyset, terminated by a unique column.
|
|
97
|
+
|
|
98
|
+
`(created_at, id)`, never `created_at` alone — which duplicates or skips rows whenever two share a
|
|
99
|
+
millisecond, i.e. every page in a large org. The package also asks for one row more than the page
|
|
100
|
+
so `hasMore` is a fact rather than a guess from "we got a full page".
|
|
101
|
+
|
|
102
|
+
### 9. An agent never wears a colleague's face.
|
|
103
|
+
|
|
104
|
+
`sender_id` is the audit principal and never changes. When an agent acted through a human's
|
|
105
|
+
session, the *rendered* author is the agent — and it names the human it acted for. An unnamed
|
|
106
|
+
agent is labeled "Agent"; falling back to the human's name and avatar would put an automated
|
|
107
|
+
message under a real person's byline.
|
|
108
|
+
|
|
109
|
+
### 10. Everything a message names must OPEN.
|
|
110
|
+
|
|
111
|
+
Matrx references arrive two ways — structured in `metadata.references`, or as a ` ```matrx `
|
|
112
|
+
fence in the content. Both become live cards. A fence **never** reaches the reader as a code
|
|
113
|
+
block of JSON, and never reaches an inbox preview or a desktop notification as raw JSON either;
|
|
114
|
+
one collapse function serves both. A reference with no resolvable identity is dropped rather than
|
|
115
|
+
drawn as a card that cannot open.
|
|
116
|
+
|
|
117
|
+
### 11. An action this build cannot honor renders NOTHING.
|
|
118
|
+
|
|
119
|
+
Actionable messages go through a registry, not a `switch`. An unknown `kind` — or a known kind at
|
|
120
|
+
an unknown `version` — renders no chips and executes nothing, which is what lets a new sender ship
|
|
121
|
+
before every reader has caught up. Idempotence is enforced *by the registry*: concurrent
|
|
122
|
+
executions of `(kind, messageId, actorId)` share one call, and a settled action returns its stored
|
|
123
|
+
receipt without touching the server. **The payload is not authorization** — a handler must
|
|
124
|
+
re-resolve the durable, caller-authorized row before it writes.
|
|
125
|
+
|
|
126
|
+
### 12. Nothing structured rides `user_input`.
|
|
127
|
+
|
|
128
|
+
`user_input` is what a human typed. Transcripts, rosters, and cutoffs travel as named
|
|
129
|
+
**variables**. The server stores and frames `user_input` as the turn's human utterance, so
|
|
130
|
+
smuggling a transcript through it silently corrupts every conversation it touches. The one
|
|
131
|
+
`user_input` this package ever sends is the instruction a person typed into "draft a reply".
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Using it
|
|
136
|
+
|
|
137
|
+
### The one-hour integration
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { MessagingProvider, MessagingInbox } from "@ai-matrx/messaging/react";
|
|
141
|
+
import "@ai-matrx/messaging/tokens.css"; // defaults — override with your brand
|
|
142
|
+
import "@ai-matrx/messaging/styles.css"; // structural, required
|
|
143
|
+
|
|
144
|
+
export default function MessagesPage() {
|
|
145
|
+
return (
|
|
146
|
+
<MessagingProvider
|
|
147
|
+
client={supabase}
|
|
148
|
+
userId={user.id}
|
|
149
|
+
organizationId={org.id}
|
|
150
|
+
>
|
|
151
|
+
<MessagingInbox />
|
|
152
|
+
</MessagingProvider>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
That is realtime, presence, typing, optimistic send, the offline outbox, references, actionable
|
|
158
|
+
messages, keyset pagination, and the whole polished UI. There is nothing else to wire.
|
|
159
|
+
|
|
160
|
+
### Adding the AI
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
<MessagingProvider
|
|
164
|
+
client={supabase}
|
|
165
|
+
userId={user.id}
|
|
166
|
+
organizationId={org.id}
|
|
167
|
+
transport={matrxTransport} // from @ai-matrx/agents/matrx
|
|
168
|
+
agents={{
|
|
169
|
+
catchUp: "…", summarize: "…", actionItems: "…", draftReply: "…",
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Agent definitions live in the **database**, never in this package — the id is the only part a
|
|
175
|
+
host injects. A capability with no agent id **does not render at all**; it is never a dead button
|
|
176
|
+
that fails when pressed.
|
|
177
|
+
|
|
178
|
+
### Making references open
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
<MessagingProvider … onOpenReference={(ref) => router.push(`/${ref.entityType}/${ref.entityId}`)}>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Omit it and reference cards render as labeled, non-interactive cards with a title explaining why —
|
|
185
|
+
honest, never a button that does nothing.
|
|
186
|
+
|
|
187
|
+
### Registering an actionable message kind
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
<MessagingProvider … actions={[{
|
|
191
|
+
kind: "access_request",
|
|
192
|
+
versions: [1],
|
|
193
|
+
choices: () => [
|
|
194
|
+
{ id: "approve", label: "Approve", tone: "primary" },
|
|
195
|
+
{ id: "decline", label: "Decline", tone: "danger" },
|
|
196
|
+
],
|
|
197
|
+
execute: async (payload, ctx) => {
|
|
198
|
+
// Re-resolve the authorized request row here. The payload is the REQUEST,
|
|
199
|
+
// never the permission.
|
|
200
|
+
const outcome = await decideAccessRequest(payload.requestId, ctx.choice);
|
|
201
|
+
return { kind: "access_request", messageId: ctx.messageId, actorId: ctx.actorId,
|
|
202
|
+
outcome: outcome.alreadyAnswered ? "already" : "applied",
|
|
203
|
+
label: outcome.alreadyAnswered ? "Already answered" : "Approved",
|
|
204
|
+
settledAt: new Date().toISOString() };
|
|
205
|
+
},
|
|
206
|
+
}]}>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This is the seam `@ai-matrx/meet` uses: a call invitation is just a message with a structured
|
|
210
|
+
payload.
|
|
211
|
+
|
|
212
|
+
### Theming
|
|
213
|
+
|
|
214
|
+
Structural CSS ships in the package; the token **contract** is enforced; token **values** are
|
|
215
|
+
yours. Import `tokens.css` for a complete working default, then override the names you care
|
|
216
|
+
about:
|
|
217
|
+
|
|
218
|
+
```css
|
|
219
|
+
:root {
|
|
220
|
+
--mx-msg-accent: #7c3aed;
|
|
221
|
+
--mx-msg-bubble-mine-bg: var(--mx-msg-accent);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Hardcoded colors inside package components are banned, and the tarball gate fails the build if a
|
|
226
|
+
hex value appears in `styles.css`. Dark values are defined for both `prefers-color-scheme` and an
|
|
227
|
+
explicit `[data-theme="dark"]`, so a host toggle wins in either direction.
|
|
228
|
+
|
|
229
|
+
### Composing your own layout
|
|
230
|
+
|
|
231
|
+
`<MessagingInbox />` is the product. If you want the list in a sidebar and the thread on a route:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
<ConversationList onSelect={(id) => router.push(`/messages/${id}`)} />
|
|
235
|
+
<ConversationView conversationId={id} />
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Hooks — `useConversations`, `useConversation`, `useComposer`, `useTypists`, `useOnlineUserIds`,
|
|
239
|
+
`useMessagingAi`, `useMessageAction` — are exported for hosts that compose their own pages. They
|
|
240
|
+
are **not** an invitation to rebuild the product out of parts.
|
|
241
|
+
|
|
242
|
+
### Outside React
|
|
243
|
+
|
|
244
|
+
The default entry is framework-free — safe from Redux middleware, a service module, a worker, or
|
|
245
|
+
a node test:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { createMessagingEngine, createMessagingRepository } from "@ai-matrx/messaging";
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## The database contract
|
|
254
|
+
|
|
255
|
+
One canonical schema, in Matrx Main, under the platform's DB conventions:
|
|
256
|
+
|
|
257
|
+
| | |
|
|
258
|
+
|---|---|
|
|
259
|
+
| Schema | `communication` (never `public`) |
|
|
260
|
+
| Tables | `dm_conversations`, `dm_conversation_participants`, `dm_messages` |
|
|
261
|
+
| RPCs | `dm_get_or_create_direct_conversation`, `get_dm_conversations_with_details`, `get_dm_user_info`, `get_dm_unread_count`, `is_dm_participant` |
|
|
262
|
+
|
|
263
|
+
Every name lives in exactly one file — `src/core/repository.ts` — and the packed-tarball canary
|
|
264
|
+
asserts them, so a silent rename fails the build instead of the app.
|
|
265
|
+
|
|
266
|
+
`matrx-dm` grew a parallel schema in its own project (`public.conversations` / `messages` /
|
|
267
|
+
`message_reactions`). Per the work order's R8 exactly one canonical schema survives, and it is
|
|
268
|
+
this one — the one with explicit org, versioning, soft deletes, and auth-checked RPCs.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Entry points
|
|
273
|
+
|
|
274
|
+
| Entry | Contents | `"use client"` |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `@ai-matrx/messaging` | Framework-free core: repository, store, outbox, engine, action registry, references, AI, formatting | **No** — a stamped pure module becomes a client reference inside an RSC |
|
|
277
|
+
| `@ai-matrx/messaging/react` | Provider, hooks, and the complete product UI. Re-exports the core, so a React app needs one specifier | Yes |
|
|
278
|
+
| `@ai-matrx/messaging/styles.css` | Structural CSS. Required | — |
|
|
279
|
+
| `@ai-matrx/messaging/tokens.css` | Default token values. Import, then override | — |
|
|
280
|
+
|
|
281
|
+
There is deliberately **no `/native` entry**. Per C25, platform bindings are built at the first
|
|
282
|
+
real consumer and always inside the package; a React Native UI that no Expo app exercises is
|
|
283
|
+
untested theory that rots. The core is already platform-agnostic (it is proven under a `node`
|
|
284
|
+
test environment with no DOM shim), so `/native` is a UI layer away when the mobile app adopts it.
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT.
|