@ai-matrx/messaging 0.14.0 → 0.15.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 +29 -0
- package/dist/index.cjs +30 -5
- 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 +30 -5
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +79 -10
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +13 -0
- package/dist/react.d.ts +13 -0
- package/dist/react.js +79 -10
- package/dist/react.js.map +1 -1
- package/package.json +3 -2
package/dist/react.d.cts
CHANGED
|
@@ -1019,11 +1019,18 @@ interface MessagingSnapshot {
|
|
|
1019
1019
|
} | null;
|
|
1020
1020
|
/** Conversations whose last open failed, keyed by id. Cleared on the next open. */
|
|
1021
1021
|
readonly conversationReadFailures: ReadonlyMap<ConversationId, ConversationReadFailure>;
|
|
1022
|
+
/**
|
|
1023
|
+
* The inbox read FAILED (and no later read succeeded). Without it a failed
|
|
1024
|
+
* first read left `hasLoadedConversations` false forever — a skeleton that
|
|
1025
|
+
* never resolves is a screen telling a lie (RC-B12 round 11).
|
|
1026
|
+
*/
|
|
1027
|
+
readonly conversationsLoadError: string | null;
|
|
1022
1028
|
}
|
|
1023
1029
|
interface MessagingStore {
|
|
1024
1030
|
snapshot(): MessagingSnapshot;
|
|
1025
1031
|
subscribe(listener: (snapshot: MessagingSnapshot) => void): () => void;
|
|
1026
1032
|
setConversations(items: readonly ConversationSummary[], hasMore: boolean): void;
|
|
1033
|
+
setConversationsLoadError(message: string | null): void;
|
|
1027
1034
|
/**
|
|
1028
1035
|
* Switch the archive axis. Clears the list and marks it UNLOADED: the rows
|
|
1029
1036
|
* on screen belong to the old filter, and leaving them there while the new
|
|
@@ -1133,6 +1140,8 @@ interface MessagingEngine {
|
|
|
1133
1140
|
* refreshes the archived count the reveal label prints.
|
|
1134
1141
|
*/
|
|
1135
1142
|
setArchiveFilter(next: MessagingArchiveFilter): Promise<void>;
|
|
1143
|
+
/** Read the inbox again (the failed-read view's Try again). Never throws; a failure lands in the snapshot. */
|
|
1144
|
+
refreshInbox(): Promise<void>;
|
|
1136
1145
|
/** Re-read how many archived conversations exist, for the reveal's label. */
|
|
1137
1146
|
refreshArchivedCount(): Promise<void>;
|
|
1138
1147
|
/** Open a conversation: load its thread and subscribe to its channel. */
|
|
@@ -1376,6 +1385,10 @@ interface UseConversationsResult {
|
|
|
1376
1385
|
hasMore: boolean;
|
|
1377
1386
|
/** True only on the FIRST load; a refresh must not blank the list. */
|
|
1378
1387
|
isInitialLoading: boolean;
|
|
1388
|
+
/** The inbox read failed; the list shows the failure (never a skeleton forever, never "empty"). */
|
|
1389
|
+
loadError: string | null;
|
|
1390
|
+
/** Read the inbox again. */
|
|
1391
|
+
retry: () => void;
|
|
1379
1392
|
totalUnreadConversations: number;
|
|
1380
1393
|
loadMore: () => void;
|
|
1381
1394
|
select: (id: ConversationId) => void;
|
package/dist/react.d.ts
CHANGED
|
@@ -1019,11 +1019,18 @@ interface MessagingSnapshot {
|
|
|
1019
1019
|
} | null;
|
|
1020
1020
|
/** Conversations whose last open failed, keyed by id. Cleared on the next open. */
|
|
1021
1021
|
readonly conversationReadFailures: ReadonlyMap<ConversationId, ConversationReadFailure>;
|
|
1022
|
+
/**
|
|
1023
|
+
* The inbox read FAILED (and no later read succeeded). Without it a failed
|
|
1024
|
+
* first read left `hasLoadedConversations` false forever — a skeleton that
|
|
1025
|
+
* never resolves is a screen telling a lie (RC-B12 round 11).
|
|
1026
|
+
*/
|
|
1027
|
+
readonly conversationsLoadError: string | null;
|
|
1022
1028
|
}
|
|
1023
1029
|
interface MessagingStore {
|
|
1024
1030
|
snapshot(): MessagingSnapshot;
|
|
1025
1031
|
subscribe(listener: (snapshot: MessagingSnapshot) => void): () => void;
|
|
1026
1032
|
setConversations(items: readonly ConversationSummary[], hasMore: boolean): void;
|
|
1033
|
+
setConversationsLoadError(message: string | null): void;
|
|
1027
1034
|
/**
|
|
1028
1035
|
* Switch the archive axis. Clears the list and marks it UNLOADED: the rows
|
|
1029
1036
|
* on screen belong to the old filter, and leaving them there while the new
|
|
@@ -1133,6 +1140,8 @@ interface MessagingEngine {
|
|
|
1133
1140
|
* refreshes the archived count the reveal label prints.
|
|
1134
1141
|
*/
|
|
1135
1142
|
setArchiveFilter(next: MessagingArchiveFilter): Promise<void>;
|
|
1143
|
+
/** Read the inbox again (the failed-read view's Try again). Never throws; a failure lands in the snapshot. */
|
|
1144
|
+
refreshInbox(): Promise<void>;
|
|
1136
1145
|
/** Re-read how many archived conversations exist, for the reveal's label. */
|
|
1137
1146
|
refreshArchivedCount(): Promise<void>;
|
|
1138
1147
|
/** Open a conversation: load its thread and subscribe to its channel. */
|
|
@@ -1376,6 +1385,10 @@ interface UseConversationsResult {
|
|
|
1376
1385
|
hasMore: boolean;
|
|
1377
1386
|
/** True only on the FIRST load; a refresh must not blank the list. */
|
|
1378
1387
|
isInitialLoading: boolean;
|
|
1388
|
+
/** The inbox read failed; the list shows the failure (never a skeleton forever, never "empty"). */
|
|
1389
|
+
loadError: string | null;
|
|
1390
|
+
/** Read the inbox again. */
|
|
1391
|
+
retry: () => void;
|
|
1379
1392
|
totalUnreadConversations: number;
|
|
1380
1393
|
loadMore: () => void;
|
|
1381
1394
|
select: (id: ConversationId) => void;
|
package/dist/react.js
CHANGED
|
@@ -876,6 +876,7 @@ function createMessagingStore() {
|
|
|
876
876
|
let threads = /* @__PURE__ */ new Map();
|
|
877
877
|
let activeConversationId = null;
|
|
878
878
|
let conversationReadFailures = /* @__PURE__ */ new Map();
|
|
879
|
+
let conversationsLoadError = null;
|
|
879
880
|
const listeners = /* @__PURE__ */ new Set();
|
|
880
881
|
let cached = null;
|
|
881
882
|
function snapshot() {
|
|
@@ -889,7 +890,8 @@ function createMessagingStore() {
|
|
|
889
890
|
totalUnreadConversations: conversations.filter((item) => item.unreadCount > 0).length,
|
|
890
891
|
archiveFilter,
|
|
891
892
|
archivedCount,
|
|
892
|
-
conversationReadFailures
|
|
893
|
+
conversationReadFailures,
|
|
894
|
+
conversationsLoadError
|
|
893
895
|
};
|
|
894
896
|
return cached;
|
|
895
897
|
}
|
|
@@ -934,6 +936,12 @@ function createMessagingStore() {
|
|
|
934
936
|
conversations = normalizeConversations(items);
|
|
935
937
|
hasMoreConversations = hasMore;
|
|
936
938
|
hasLoadedConversations = true;
|
|
939
|
+
conversationsLoadError = null;
|
|
940
|
+
emit();
|
|
941
|
+
},
|
|
942
|
+
setConversationsLoadError(message) {
|
|
943
|
+
if (message === conversationsLoadError) return;
|
|
944
|
+
conversationsLoadError = message;
|
|
937
945
|
emit();
|
|
938
946
|
},
|
|
939
947
|
setArchiveFilter(next) {
|
|
@@ -1166,10 +1174,20 @@ function createMessagingEngine(options) {
|
|
|
1166
1174
|
}
|
|
1167
1175
|
async function reloadInbox() {
|
|
1168
1176
|
if (disposed) return;
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1177
|
+
let page;
|
|
1178
|
+
try {
|
|
1179
|
+
page = await repository.listConversations({
|
|
1180
|
+
limit: conversationPageSize,
|
|
1181
|
+
archived: store.snapshot().archiveFilter
|
|
1182
|
+
});
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
if (!disposed) {
|
|
1185
|
+
store.setConversationsLoadError(
|
|
1186
|
+
error instanceof Error ? error.message : typeof error === "string" ? error : "The conversation list could not be read."
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
throw error;
|
|
1190
|
+
}
|
|
1173
1191
|
if (disposed) return;
|
|
1174
1192
|
conversationCursor = page.nextCursor;
|
|
1175
1193
|
store.setConversations(page.items, page.hasMore);
|
|
@@ -1254,6 +1272,13 @@ function createMessagingEngine(options) {
|
|
|
1254
1272
|
}
|
|
1255
1273
|
});
|
|
1256
1274
|
},
|
|
1275
|
+
async refreshInbox() {
|
|
1276
|
+
try {
|
|
1277
|
+
await reloadInbox();
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
reportError(error, "inboxRefresh");
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1257
1282
|
async setArchiveFilter(next) {
|
|
1258
1283
|
if (next === store.snapshot().archiveFilter) return;
|
|
1259
1284
|
store.setArchiveFilter(next);
|
|
@@ -2397,7 +2422,11 @@ function useConversations() {
|
|
|
2397
2422
|
hasMore: snapshot?.hasMoreConversations ?? false,
|
|
2398
2423
|
// "Not loaded yet" is a FACT from the store, never a guess. Showing
|
|
2399
2424
|
// "No conversations yet" before the first read is a screen telling a lie.
|
|
2400
|
-
isInitialLoading: snapshot === null || !snapshot.hasLoadedConversations,
|
|
2425
|
+
isInitialLoading: snapshot === null || !snapshot.hasLoadedConversations && snapshot.conversationsLoadError === null,
|
|
2426
|
+
loadError: snapshot?.conversationsLoadError ?? null,
|
|
2427
|
+
retry: () => {
|
|
2428
|
+
void host?.engine.refreshInbox();
|
|
2429
|
+
},
|
|
2401
2430
|
totalUnreadConversations: snapshot?.totalUnreadConversations ?? 0,
|
|
2402
2431
|
loadMore: () => {
|
|
2403
2432
|
void host?.engine.loadMoreConversations();
|
|
@@ -2984,6 +3013,7 @@ function EmptyState(props) {
|
|
|
2984
3013
|
}
|
|
2985
3014
|
|
|
2986
3015
|
// src/react/components.tsx
|
|
3016
|
+
import { ErrorBox } from "@ai-matrx/design-system";
|
|
2987
3017
|
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2988
3018
|
var AI_LABELS = {
|
|
2989
3019
|
catchUp: "Catch me up",
|
|
@@ -3001,7 +3031,9 @@ function ConversationList(props) {
|
|
|
3001
3031
|
activeConversationId,
|
|
3002
3032
|
archiveFilter,
|
|
3003
3033
|
setArchiveFilter,
|
|
3004
|
-
archivedCount
|
|
3034
|
+
archivedCount,
|
|
3035
|
+
loadError,
|
|
3036
|
+
retry
|
|
3005
3037
|
} = useConversations();
|
|
3006
3038
|
const RowChrome = useMessagingHost()?.wrapConversationRow ?? null;
|
|
3007
3039
|
const [query, setQuery] = useState2("");
|
|
@@ -3066,7 +3098,20 @@ function ConversationList(props) {
|
|
|
3066
3098
|
}
|
|
3067
3099
|
) }) : null,
|
|
3068
3100
|
/* @__PURE__ */ jsxs2("div", { className: "mx-msg__scroll", children: [
|
|
3069
|
-
isInitialLoading ? /* @__PURE__ */ jsx4(ConversationSkeleton, {}) : visible.length === 0 ?
|
|
3101
|
+
isInitialLoading ? /* @__PURE__ */ jsx4(ConversationSkeleton, {}) : loadError !== null && visible.length === 0 ? (
|
|
3102
|
+
// A failed read resolves to the failure — never a skeleton forever,
|
|
3103
|
+
// never "No conversations yet" (RC-B12 round 11). ErrorBox is the
|
|
3104
|
+
// design-system error slot: the host fills it with its menu.
|
|
3105
|
+
/* @__PURE__ */ jsx4(
|
|
3106
|
+
ReadFailureBox,
|
|
3107
|
+
{
|
|
3108
|
+
title: "Couldn't load your conversations",
|
|
3109
|
+
message: loadError,
|
|
3110
|
+
operation: "List your conversations",
|
|
3111
|
+
onRetry: retry
|
|
3112
|
+
}
|
|
3113
|
+
)
|
|
3114
|
+
) : visible.length === 0 ? /* @__PURE__ */ jsx4(
|
|
3070
3115
|
EmptyState,
|
|
3071
3116
|
{
|
|
3072
3117
|
title: query.length > 0 ? "No matches" : showingArchive ? "No archived conversations" : "No conversations yet",
|
|
@@ -3243,10 +3288,11 @@ function ConversationView(props) {
|
|
|
3243
3288
|
body: "It doesn't exist, or you aren't a member of it."
|
|
3244
3289
|
}
|
|
3245
3290
|
) : conversation.error !== null && messages.length === 0 ? /* @__PURE__ */ jsx4(
|
|
3246
|
-
|
|
3291
|
+
ReadFailureBox,
|
|
3247
3292
|
{
|
|
3248
3293
|
title: "Couldn't load this conversation",
|
|
3249
|
-
|
|
3294
|
+
message: conversation.error.message || "Check your connection, then open it again.",
|
|
3295
|
+
operation: "Open this conversation"
|
|
3250
3296
|
}
|
|
3251
3297
|
) : messages.length === 0 ? /* @__PURE__ */ jsx4(EmptyState, { title: "No messages yet", body: "Say something to get started." }) : /* @__PURE__ */ jsxs2("div", { className: "mx-msg__messages", children: [
|
|
3252
3298
|
conversation.hasMoreOlder ? /* @__PURE__ */ jsx4(
|
|
@@ -3577,6 +3623,29 @@ function MessagingInbox(props) {
|
|
|
3577
3623
|
}
|
|
3578
3624
|
);
|
|
3579
3625
|
}
|
|
3626
|
+
function ReadFailureBox({
|
|
3627
|
+
title,
|
|
3628
|
+
message,
|
|
3629
|
+
operation,
|
|
3630
|
+
onRetry
|
|
3631
|
+
}) {
|
|
3632
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mx-msg__empty", role: "alert", children: [
|
|
3633
|
+
/* @__PURE__ */ jsx4(
|
|
3634
|
+
ErrorBox,
|
|
3635
|
+
{
|
|
3636
|
+
as: "div",
|
|
3637
|
+
title,
|
|
3638
|
+
message,
|
|
3639
|
+
operation,
|
|
3640
|
+
origin: "messaging",
|
|
3641
|
+
style: { color: "var(--mx-msg-danger)", fontWeight: 600 },
|
|
3642
|
+
children: title
|
|
3643
|
+
}
|
|
3644
|
+
),
|
|
3645
|
+
/* @__PURE__ */ jsx4("p", { children: message }),
|
|
3646
|
+
onRetry ? /* @__PURE__ */ jsx4("button", { type: "button", className: "mx-msg__button mx-msg__button--ghost", onClick: onRetry, children: "Try again" }) : null
|
|
3647
|
+
] });
|
|
3648
|
+
}
|
|
3580
3649
|
export {
|
|
3581
3650
|
AgentTag,
|
|
3582
3651
|
AlertIcon,
|