@ai-matrx/messaging 0.10.5 → 0.11.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/dist/react.d.cts CHANGED
@@ -138,6 +138,39 @@ interface Message {
138
138
  /** Set only while this message is in the outbox and its last send failed. */
139
139
  readonly failureReason?: string | undefined;
140
140
  }
141
+ /**
142
+ * THE ARCHIVED-ITEMS LAW's three states (Arman, 2026-09-09 —
143
+ * `common-docs/policies/archived-items.md`):
144
+ *
145
+ * > "everything should have an archive filter, and the default should always
146
+ * > hide archived, but seeing archived items should be one or two clicks
147
+ * > away … this is a system wide decision for every single item everywhere in
148
+ * > our system, for every single table and every single page."
149
+ *
150
+ * | value | what the list shows |
151
+ * |------------|--------------------------|
152
+ * | `active` | only un-archived rows |
153
+ * | `archived` | only archived rows |
154
+ * | `all` | every row |
155
+ *
156
+ * The same three words the rest of the platform uses — `ArchivedFilter` in
157
+ * matrx-frontend's `lib/entity-list`, `p_archived` on the `agx_*`/`wfx_*` and
158
+ * DM list RPCs, `ArchiveFilter` in `@ai-matrx/design-system`. A person meets
159
+ * ONE control, not one per app.
160
+ *
161
+ * 🚨 Three states, never a boolean. A boolean cannot say "archived only",
162
+ * which is exactly the view someone looking for a conversation they archived
163
+ * last month needs.
164
+ */
165
+ type MessagingArchiveFilter = "active" | "archived" | "all";
166
+ /** The platform default: a list hides archived rows until asked. */
167
+ declare const DEFAULT_MESSAGING_ARCHIVE_FILTER: MessagingArchiveFilter;
168
+ /**
169
+ * Narrow an untrusted value (a stored preference, a URL param, a host knob) to
170
+ * the tri-state. Anything unrecognised falls back to the default rather than
171
+ * silently widening a list to archived conversations.
172
+ */
173
+ declare function toMessagingArchiveFilter(value: unknown, fallback?: MessagingArchiveFilter): MessagingArchiveFilter;
141
174
  interface ConversationSummary {
142
175
  readonly conversation: Conversation;
143
176
  readonly participants: readonly UserSummary[];
@@ -667,7 +700,30 @@ interface MessagingRepository {
667
700
  listConversations(args?: {
668
701
  limit?: number;
669
702
  cursor?: ConversationCursor | null;
703
+ /**
704
+ * THE ARCHIVED-ITEMS LAW. Omitted means `active` — archived conversations
705
+ * are hidden until a caller asks for them. This is a REQUEST to the reader
706
+ * (the RPC's `p_archived`), never a client-side sieve, so `hasMore` and the
707
+ * cursor describe the rows the caller actually asked for.
708
+ */
709
+ archived?: MessagingArchiveFilter;
670
710
  }): Promise<Page<ConversationSummary, ConversationCursor>>;
711
+ /**
712
+ * How many ARCHIVED conversations the caller has — the number a reveal
713
+ * control prints ("Archived (12)").
714
+ *
715
+ * 🚨 It is capped, and it says so. A count is read by listing archived rows
716
+ * up to `limit`; if that many come back the result is `exact: false` and the
717
+ * UI must render "12+", never a confident "12". A screen never lies: a cap
718
+ * silently presented as a total is the same defect as a badge counting rows
719
+ * the list hides.
720
+ */
721
+ countArchivedConversations(args?: {
722
+ limit?: number;
723
+ }): Promise<{
724
+ count: number;
725
+ exact: boolean;
726
+ }>;
671
727
  getConversation(id: ConversationId): Promise<Conversation>;
672
728
  listMessages(conversationId: ConversationId, args?: {
673
729
  limit?: number;
@@ -754,11 +810,38 @@ interface MessagingSnapshot {
754
810
  readonly activeConversationId: ConversationId | null;
755
811
  /** Conversations WITH unread, not total unread messages (the origin's semantics). */
756
812
  readonly totalUnreadConversations: number;
813
+ /**
814
+ * THE ARCHIVED-ITEMS LAW's state for this inbox. `conversations` above is
815
+ * ALWAYS the rows this filter asked the server for — never a superset a
816
+ * surface is expected to sieve, so counts and pagination cannot disagree
817
+ * with what renders.
818
+ */
819
+ readonly archiveFilter: MessagingArchiveFilter;
820
+ /**
821
+ * How many archived conversations exist, for the reveal control's label, or
822
+ * `null` before the first count lands. `exact: false` means the count hit its
823
+ * cap and the label must read "N+" — a capped number shown as a total is the
824
+ * same lie as a badge counting hidden rows.
825
+ */
826
+ readonly archivedCount: {
827
+ readonly count: number;
828
+ readonly exact: boolean;
829
+ } | null;
757
830
  }
758
831
  interface MessagingStore {
759
832
  snapshot(): MessagingSnapshot;
760
833
  subscribe(listener: (snapshot: MessagingSnapshot) => void): () => void;
761
834
  setConversations(items: readonly ConversationSummary[], hasMore: boolean): void;
835
+ /**
836
+ * Switch the archive axis. Clears the list and marks it UNLOADED: the rows
837
+ * on screen belong to the old filter, and leaving them there while the new
838
+ * page is in flight shows active conversations under an "Archived" heading.
839
+ */
840
+ setArchiveFilter(next: MessagingArchiveFilter): void;
841
+ setArchivedCount(value: {
842
+ count: number;
843
+ exact: boolean;
844
+ } | null): void;
762
845
  appendConversations(items: readonly ConversationSummary[], hasMore: boolean): void;
763
846
  upsertConversation(item: ConversationSummary): void;
764
847
  removeConversation(id: ConversationId): void;
@@ -833,6 +916,15 @@ interface MessagingEngineOptions {
833
916
  onIncoming?: ((message: Message) => void) | undefined;
834
917
  conversationPageSize?: number | undefined;
835
918
  messagePageSize?: number | undefined;
919
+ /**
920
+ * 🚨 OPINIONS BECOME KNOBS. THE ARCHIVED-ITEMS LAW's clause 6 says the
921
+ * filter's initial state is an org/user-configurable setting, not code
922
+ * taste. The PLATFORM default is `active` (hide archived) and a host that
923
+ * passes nothing gets exactly that; a host wires this to whatever its own
924
+ * setting resolves to. The person can always change it from the control on
925
+ * the list.
926
+ */
927
+ archiveFilter?: MessagingArchiveFilter | undefined;
836
928
  }
837
929
  interface MessagingEngine {
838
930
  readonly store: MessagingStore;
@@ -841,6 +933,14 @@ interface MessagingEngine {
841
933
  /** Load page one of the inbox and start the inbox channel. */
842
934
  start(): Promise<void>;
843
935
  loadMoreConversations(): Promise<void>;
936
+ /**
937
+ * THE ARCHIVED-ITEMS LAW's control. Re-reads page one under the new state
938
+ * (the filter is a REQUEST to the server, never a client-side sieve) and
939
+ * refreshes the archived count the reveal label prints.
940
+ */
941
+ setArchiveFilter(next: MessagingArchiveFilter): Promise<void>;
942
+ /** Re-read how many archived conversations exist, for the reveal's label. */
943
+ refreshArchivedCount(): Promise<void>;
844
944
  /** Open a conversation: load its thread and subscribe to its channel. */
845
945
  openConversation(id: ConversationId): Promise<void>;
846
946
  closeConversation(id: ConversationId): void;
@@ -1037,6 +1137,15 @@ interface MessagingProviderProps {
1037
1137
  resolveSession?: SessionResolver | undefined;
1038
1138
  /** Override outbox persistence. Defaults to localStorage, then memory. */
1039
1139
  outboxStorage?: OutboxStorage | undefined;
1140
+ /**
1141
+ * 🚨 OPINIONS BECOME KNOBS. THE ARCHIVED-ITEMS LAW (Arman, 2026-09-09 —
1142
+ * `common-docs/policies/archived-items.md`) clause 6: the archive filter's
1143
+ * initial state is an org/user setting, not code taste. The PLATFORM default
1144
+ * is `active` — archived conversations hidden — and a host that passes
1145
+ * nothing gets exactly that. Pass whatever your own setting resolves to; the
1146
+ * person can still flip it from the control on the list.
1147
+ */
1148
+ archiveFilter?: MessagingArchiveFilter | undefined;
1040
1149
  children: ReactNode;
1041
1150
  }
1042
1151
  declare function MessagingProvider(props: MessagingProviderProps): ReactNode;
@@ -1061,6 +1170,22 @@ interface UseConversationsResult {
1061
1170
  select: (id: ConversationId) => void;
1062
1171
  activeConversationId: ConversationId | null;
1063
1172
  startDirect: (otherUserId: UserId) => Promise<ConversationId>;
1173
+ /**
1174
+ * THE ARCHIVED-ITEMS LAW's state. `conversations` above is ALWAYS the rows
1175
+ * this filter asked the SERVER for — never a superset to sieve — so a count
1176
+ * taken over it can never disagree with what renders.
1177
+ */
1178
+ archiveFilter: MessagingArchiveFilter;
1179
+ setArchiveFilter: (next: MessagingArchiveFilter) => void;
1180
+ /**
1181
+ * How many archived conversations exist, for a reveal label, or `null`
1182
+ * before the first count lands. `exact: false` means the count hit its cap
1183
+ * and the label must read "N+".
1184
+ */
1185
+ archivedCount: {
1186
+ count: number;
1187
+ exact: boolean;
1188
+ } | null;
1064
1189
  }
1065
1190
  declare function useConversations(): UseConversationsResult;
1066
1191
  interface UseConversationResult {
@@ -1538,4 +1663,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1538
1663
  /** Serialize picked references into a fence the platform's other readers accept. */
1539
1664
  declare function composeFence(references: readonly MatrxReference[]): string;
1540
1665
 
1541
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, AgentTag, type AiCallArgs, type AiCapability, type AiResult, AlertIcon, type Attachment, Avatar, BotIcon, CheckIcon, ChevronLeftIcon, type ClientMessageId, ClockIcon, CloseIcon, Composer, type ComposerInputRenderProps, type Conversation, type ConversationCursor, type ConversationId, ConversationList, type ConversationListProps, type ConversationRowWrapperProps, ConversationSkeleton, type ConversationSummary, type ConversationThread, type ConversationType, ConversationView, type ConversationViewProps, type DeliveryState, DeliveryTick, DoubleCheckIcon, type DraftMessage, EmptyState, type EngineDiagnostic, type IncomingMessageContext, type JsonObject, type JsonValue, LinkIcon, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, MessageActionChips, type MessageActionRenderProps, type MessageActionRenderer, MessageBubble, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessageWrapperProps, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingHost, type MessagingIdentity, MessagingInbox, type MessagingInboxProps, MessagingProvider, type MessagingProviderProps, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, PaperclipIcon, type Participant, type ParticipantRole, PlusIcon, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, ReferenceCard, type ReferenceRenderer, ReplyIcon, type RepositoryOptions, type SchemaLike, SearchIcon, SendIcon, type SessionResolver, SparklesIcon, type SupabaseLike, TABLES, type TextSegment, TypingDots, type UseComposerResult, type UseConversationResult, type UseConversationsResult, type UseMessageActionResult, type UseMessagingAiResult, type UseTypistsResult, type UserId, type UserSummary, UsersIcon, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, unreadCutoff, useComposer, useConversation, useConversations, useMessageAction, useMessagingAi, useMessagingHost, useMessagingSnapshot, useOnlineUserIds, useRequiredMessagingHost, useTypists };
1666
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, AgentTag, type AiCallArgs, type AiCapability, type AiResult, AlertIcon, type Attachment, Avatar, BotIcon, CheckIcon, ChevronLeftIcon, type ClientMessageId, ClockIcon, CloseIcon, Composer, type ComposerInputRenderProps, type Conversation, type ConversationCursor, type ConversationId, ConversationList, type ConversationListProps, type ConversationRowWrapperProps, ConversationSkeleton, type ConversationSummary, type ConversationThread, type ConversationType, ConversationView, type ConversationViewProps, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, DeliveryTick, DoubleCheckIcon, type DraftMessage, EmptyState, type EngineDiagnostic, type IncomingMessageContext, type JsonObject, type JsonValue, LinkIcon, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, MessageActionChips, type MessageActionRenderProps, type MessageActionRenderer, MessageBubble, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessageWrapperProps, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingHost, type MessagingIdentity, MessagingInbox, type MessagingInboxProps, MessagingProvider, type MessagingProviderProps, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, PaperclipIcon, type Participant, type ParticipantRole, PlusIcon, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, ReferenceCard, type ReferenceRenderer, ReplyIcon, type RepositoryOptions, type SchemaLike, SearchIcon, SendIcon, type SessionResolver, SparklesIcon, type SupabaseLike, TABLES, type TextSegment, TypingDots, type UseComposerResult, type UseConversationResult, type UseConversationsResult, type UseMessageActionResult, type UseMessagingAiResult, type UseTypistsResult, type UserId, type UserSummary, UsersIcon, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff, useComposer, useConversation, useConversations, useMessageAction, useMessagingAi, useMessagingHost, useMessagingSnapshot, useOnlineUserIds, useRequiredMessagingHost, useTypists };
package/dist/react.d.ts CHANGED
@@ -138,6 +138,39 @@ interface Message {
138
138
  /** Set only while this message is in the outbox and its last send failed. */
139
139
  readonly failureReason?: string | undefined;
140
140
  }
141
+ /**
142
+ * THE ARCHIVED-ITEMS LAW's three states (Arman, 2026-09-09 —
143
+ * `common-docs/policies/archived-items.md`):
144
+ *
145
+ * > "everything should have an archive filter, and the default should always
146
+ * > hide archived, but seeing archived items should be one or two clicks
147
+ * > away … this is a system wide decision for every single item everywhere in
148
+ * > our system, for every single table and every single page."
149
+ *
150
+ * | value | what the list shows |
151
+ * |------------|--------------------------|
152
+ * | `active` | only un-archived rows |
153
+ * | `archived` | only archived rows |
154
+ * | `all` | every row |
155
+ *
156
+ * The same three words the rest of the platform uses — `ArchivedFilter` in
157
+ * matrx-frontend's `lib/entity-list`, `p_archived` on the `agx_*`/`wfx_*` and
158
+ * DM list RPCs, `ArchiveFilter` in `@ai-matrx/design-system`. A person meets
159
+ * ONE control, not one per app.
160
+ *
161
+ * 🚨 Three states, never a boolean. A boolean cannot say "archived only",
162
+ * which is exactly the view someone looking for a conversation they archived
163
+ * last month needs.
164
+ */
165
+ type MessagingArchiveFilter = "active" | "archived" | "all";
166
+ /** The platform default: a list hides archived rows until asked. */
167
+ declare const DEFAULT_MESSAGING_ARCHIVE_FILTER: MessagingArchiveFilter;
168
+ /**
169
+ * Narrow an untrusted value (a stored preference, a URL param, a host knob) to
170
+ * the tri-state. Anything unrecognised falls back to the default rather than
171
+ * silently widening a list to archived conversations.
172
+ */
173
+ declare function toMessagingArchiveFilter(value: unknown, fallback?: MessagingArchiveFilter): MessagingArchiveFilter;
141
174
  interface ConversationSummary {
142
175
  readonly conversation: Conversation;
143
176
  readonly participants: readonly UserSummary[];
@@ -667,7 +700,30 @@ interface MessagingRepository {
667
700
  listConversations(args?: {
668
701
  limit?: number;
669
702
  cursor?: ConversationCursor | null;
703
+ /**
704
+ * THE ARCHIVED-ITEMS LAW. Omitted means `active` — archived conversations
705
+ * are hidden until a caller asks for them. This is a REQUEST to the reader
706
+ * (the RPC's `p_archived`), never a client-side sieve, so `hasMore` and the
707
+ * cursor describe the rows the caller actually asked for.
708
+ */
709
+ archived?: MessagingArchiveFilter;
670
710
  }): Promise<Page<ConversationSummary, ConversationCursor>>;
711
+ /**
712
+ * How many ARCHIVED conversations the caller has — the number a reveal
713
+ * control prints ("Archived (12)").
714
+ *
715
+ * 🚨 It is capped, and it says so. A count is read by listing archived rows
716
+ * up to `limit`; if that many come back the result is `exact: false` and the
717
+ * UI must render "12+", never a confident "12". A screen never lies: a cap
718
+ * silently presented as a total is the same defect as a badge counting rows
719
+ * the list hides.
720
+ */
721
+ countArchivedConversations(args?: {
722
+ limit?: number;
723
+ }): Promise<{
724
+ count: number;
725
+ exact: boolean;
726
+ }>;
671
727
  getConversation(id: ConversationId): Promise<Conversation>;
672
728
  listMessages(conversationId: ConversationId, args?: {
673
729
  limit?: number;
@@ -754,11 +810,38 @@ interface MessagingSnapshot {
754
810
  readonly activeConversationId: ConversationId | null;
755
811
  /** Conversations WITH unread, not total unread messages (the origin's semantics). */
756
812
  readonly totalUnreadConversations: number;
813
+ /**
814
+ * THE ARCHIVED-ITEMS LAW's state for this inbox. `conversations` above is
815
+ * ALWAYS the rows this filter asked the server for — never a superset a
816
+ * surface is expected to sieve, so counts and pagination cannot disagree
817
+ * with what renders.
818
+ */
819
+ readonly archiveFilter: MessagingArchiveFilter;
820
+ /**
821
+ * How many archived conversations exist, for the reveal control's label, or
822
+ * `null` before the first count lands. `exact: false` means the count hit its
823
+ * cap and the label must read "N+" — a capped number shown as a total is the
824
+ * same lie as a badge counting hidden rows.
825
+ */
826
+ readonly archivedCount: {
827
+ readonly count: number;
828
+ readonly exact: boolean;
829
+ } | null;
757
830
  }
758
831
  interface MessagingStore {
759
832
  snapshot(): MessagingSnapshot;
760
833
  subscribe(listener: (snapshot: MessagingSnapshot) => void): () => void;
761
834
  setConversations(items: readonly ConversationSummary[], hasMore: boolean): void;
835
+ /**
836
+ * Switch the archive axis. Clears the list and marks it UNLOADED: the rows
837
+ * on screen belong to the old filter, and leaving them there while the new
838
+ * page is in flight shows active conversations under an "Archived" heading.
839
+ */
840
+ setArchiveFilter(next: MessagingArchiveFilter): void;
841
+ setArchivedCount(value: {
842
+ count: number;
843
+ exact: boolean;
844
+ } | null): void;
762
845
  appendConversations(items: readonly ConversationSummary[], hasMore: boolean): void;
763
846
  upsertConversation(item: ConversationSummary): void;
764
847
  removeConversation(id: ConversationId): void;
@@ -833,6 +916,15 @@ interface MessagingEngineOptions {
833
916
  onIncoming?: ((message: Message) => void) | undefined;
834
917
  conversationPageSize?: number | undefined;
835
918
  messagePageSize?: number | undefined;
919
+ /**
920
+ * 🚨 OPINIONS BECOME KNOBS. THE ARCHIVED-ITEMS LAW's clause 6 says the
921
+ * filter's initial state is an org/user-configurable setting, not code
922
+ * taste. The PLATFORM default is `active` (hide archived) and a host that
923
+ * passes nothing gets exactly that; a host wires this to whatever its own
924
+ * setting resolves to. The person can always change it from the control on
925
+ * the list.
926
+ */
927
+ archiveFilter?: MessagingArchiveFilter | undefined;
836
928
  }
837
929
  interface MessagingEngine {
838
930
  readonly store: MessagingStore;
@@ -841,6 +933,14 @@ interface MessagingEngine {
841
933
  /** Load page one of the inbox and start the inbox channel. */
842
934
  start(): Promise<void>;
843
935
  loadMoreConversations(): Promise<void>;
936
+ /**
937
+ * THE ARCHIVED-ITEMS LAW's control. Re-reads page one under the new state
938
+ * (the filter is a REQUEST to the server, never a client-side sieve) and
939
+ * refreshes the archived count the reveal label prints.
940
+ */
941
+ setArchiveFilter(next: MessagingArchiveFilter): Promise<void>;
942
+ /** Re-read how many archived conversations exist, for the reveal's label. */
943
+ refreshArchivedCount(): Promise<void>;
844
944
  /** Open a conversation: load its thread and subscribe to its channel. */
845
945
  openConversation(id: ConversationId): Promise<void>;
846
946
  closeConversation(id: ConversationId): void;
@@ -1037,6 +1137,15 @@ interface MessagingProviderProps {
1037
1137
  resolveSession?: SessionResolver | undefined;
1038
1138
  /** Override outbox persistence. Defaults to localStorage, then memory. */
1039
1139
  outboxStorage?: OutboxStorage | undefined;
1140
+ /**
1141
+ * 🚨 OPINIONS BECOME KNOBS. THE ARCHIVED-ITEMS LAW (Arman, 2026-09-09 —
1142
+ * `common-docs/policies/archived-items.md`) clause 6: the archive filter's
1143
+ * initial state is an org/user setting, not code taste. The PLATFORM default
1144
+ * is `active` — archived conversations hidden — and a host that passes
1145
+ * nothing gets exactly that. Pass whatever your own setting resolves to; the
1146
+ * person can still flip it from the control on the list.
1147
+ */
1148
+ archiveFilter?: MessagingArchiveFilter | undefined;
1040
1149
  children: ReactNode;
1041
1150
  }
1042
1151
  declare function MessagingProvider(props: MessagingProviderProps): ReactNode;
@@ -1061,6 +1170,22 @@ interface UseConversationsResult {
1061
1170
  select: (id: ConversationId) => void;
1062
1171
  activeConversationId: ConversationId | null;
1063
1172
  startDirect: (otherUserId: UserId) => Promise<ConversationId>;
1173
+ /**
1174
+ * THE ARCHIVED-ITEMS LAW's state. `conversations` above is ALWAYS the rows
1175
+ * this filter asked the SERVER for — never a superset to sieve — so a count
1176
+ * taken over it can never disagree with what renders.
1177
+ */
1178
+ archiveFilter: MessagingArchiveFilter;
1179
+ setArchiveFilter: (next: MessagingArchiveFilter) => void;
1180
+ /**
1181
+ * How many archived conversations exist, for a reveal label, or `null`
1182
+ * before the first count lands. `exact: false` means the count hit its cap
1183
+ * and the label must read "N+".
1184
+ */
1185
+ archivedCount: {
1186
+ count: number;
1187
+ exact: boolean;
1188
+ } | null;
1064
1189
  }
1065
1190
  declare function useConversations(): UseConversationsResult;
1066
1191
  interface UseConversationResult {
@@ -1538,4 +1663,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1538
1663
  /** Serialize picked references into a fence the platform's other readers accept. */
1539
1664
  declare function composeFence(references: readonly MatrxReference[]): string;
1540
1665
 
1541
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, AgentTag, type AiCallArgs, type AiCapability, type AiResult, AlertIcon, type Attachment, Avatar, BotIcon, CheckIcon, ChevronLeftIcon, type ClientMessageId, ClockIcon, CloseIcon, Composer, type ComposerInputRenderProps, type Conversation, type ConversationCursor, type ConversationId, ConversationList, type ConversationListProps, type ConversationRowWrapperProps, ConversationSkeleton, type ConversationSummary, type ConversationThread, type ConversationType, ConversationView, type ConversationViewProps, type DeliveryState, DeliveryTick, DoubleCheckIcon, type DraftMessage, EmptyState, type EngineDiagnostic, type IncomingMessageContext, type JsonObject, type JsonValue, LinkIcon, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, MessageActionChips, type MessageActionRenderProps, type MessageActionRenderer, MessageBubble, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessageWrapperProps, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingHost, type MessagingIdentity, MessagingInbox, type MessagingInboxProps, MessagingProvider, type MessagingProviderProps, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, PaperclipIcon, type Participant, type ParticipantRole, PlusIcon, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, ReferenceCard, type ReferenceRenderer, ReplyIcon, type RepositoryOptions, type SchemaLike, SearchIcon, SendIcon, type SessionResolver, SparklesIcon, type SupabaseLike, TABLES, type TextSegment, TypingDots, type UseComposerResult, type UseConversationResult, type UseConversationsResult, type UseMessageActionResult, type UseMessagingAiResult, type UseTypistsResult, type UserId, type UserSummary, UsersIcon, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, unreadCutoff, useComposer, useConversation, useConversations, useMessageAction, useMessagingAi, useMessagingHost, useMessagingSnapshot, useOnlineUserIds, useRequiredMessagingHost, useTypists };
1666
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, AgentTag, type AiCallArgs, type AiCapability, type AiResult, AlertIcon, type Attachment, Avatar, BotIcon, CheckIcon, ChevronLeftIcon, type ClientMessageId, ClockIcon, CloseIcon, Composer, type ComposerInputRenderProps, type Conversation, type ConversationCursor, type ConversationId, ConversationList, type ConversationListProps, type ConversationRowWrapperProps, ConversationSkeleton, type ConversationSummary, type ConversationThread, type ConversationType, ConversationView, type ConversationViewProps, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, DeliveryTick, DoubleCheckIcon, type DraftMessage, EmptyState, type EngineDiagnostic, type IncomingMessageContext, type JsonObject, type JsonValue, LinkIcon, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, MessageActionChips, type MessageActionRenderProps, type MessageActionRenderer, MessageBubble, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessageWrapperProps, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingHost, type MessagingIdentity, MessagingInbox, type MessagingInboxProps, MessagingProvider, type MessagingProviderProps, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, PaperclipIcon, type Participant, type ParticipantRole, PlusIcon, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, ReferenceCard, type ReferenceRenderer, ReplyIcon, type RepositoryOptions, type SchemaLike, SearchIcon, SendIcon, type SessionResolver, SparklesIcon, type SupabaseLike, TABLES, type TextSegment, TypingDots, type UseComposerResult, type UseConversationResult, type UseConversationsResult, type UseMessageActionResult, type UseMessagingAiResult, type UseTypistsResult, type UserId, type UserSummary, UsersIcon, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff, useComposer, useConversation, useConversations, useMessageAction, useMessagingAi, useMessagingHost, useMessagingSnapshot, useOnlineUserIds, useRequiredMessagingHost, useTypists };
package/dist/react.js CHANGED
@@ -821,6 +821,17 @@ function projectConversationSummary(row, viewerId, fallbackOrganizationId) {
821
821
  };
822
822
  }
823
823
 
824
+ // src/core/types.ts
825
+ var asConversationId = (value) => value;
826
+ var asMessageId = (value) => value;
827
+ var asUserId = (value) => value;
828
+ var asOrganizationId = (value) => value;
829
+ var asClientMessageId = (value) => value;
830
+ var DEFAULT_MESSAGING_ARCHIVE_FILTER = "active";
831
+ function toMessagingArchiveFilter(value, fallback = DEFAULT_MESSAGING_ARCHIVE_FILTER) {
832
+ return value === "active" || value === "archived" || value === "all" ? value : fallback;
833
+ }
834
+
824
835
  // src/core/store.ts
825
836
  function timeOf(message) {
826
837
  const stamp = message.editedAt ?? message.createdAt;
@@ -851,6 +862,8 @@ function createMessagingStore() {
851
862
  let conversations = [];
852
863
  let hasMoreConversations = false;
853
864
  let hasLoadedConversations = false;
865
+ let archiveFilter = DEFAULT_MESSAGING_ARCHIVE_FILTER;
866
+ let archivedCount = null;
854
867
  let threads = /* @__PURE__ */ new Map();
855
868
  let activeConversationId = null;
856
869
  const listeners = /* @__PURE__ */ new Set();
@@ -863,7 +876,9 @@ function createMessagingStore() {
863
876
  hasLoadedConversations,
864
877
  threads,
865
878
  activeConversationId,
866
- totalUnreadConversations: conversations.filter((item) => item.unreadCount > 0).length
879
+ totalUnreadConversations: conversations.filter((item) => item.unreadCount > 0).length,
880
+ archiveFilter,
881
+ archivedCount
867
882
  };
868
883
  return cached;
869
884
  }
@@ -910,6 +925,18 @@ function createMessagingStore() {
910
925
  hasLoadedConversations = true;
911
926
  emit();
912
927
  },
928
+ setArchiveFilter(next) {
929
+ if (next === archiveFilter) return;
930
+ archiveFilter = next;
931
+ conversations = [];
932
+ hasMoreConversations = false;
933
+ hasLoadedConversations = false;
934
+ emit();
935
+ },
936
+ setArchivedCount(value) {
937
+ archivedCount = value;
938
+ emit();
939
+ },
913
940
  appendConversations(items, hasMore) {
914
941
  const byId = new Map(conversations.map((item) => [item.conversation.id, item]));
915
942
  items.forEach((item) => byId.set(item.conversation.id, item));
@@ -1116,11 +1143,20 @@ function createMessagingEngine(options) {
1116
1143
  }
1117
1144
  async function reloadInbox() {
1118
1145
  if (disposed) return;
1119
- const page = await repository.listConversations({ limit: conversationPageSize });
1146
+ const page = await repository.listConversations({
1147
+ limit: conversationPageSize,
1148
+ archived: store.snapshot().archiveFilter
1149
+ });
1120
1150
  if (disposed) return;
1121
1151
  conversationCursor = page.nextCursor;
1122
1152
  store.setConversations(page.items, page.hasMore);
1123
1153
  }
1154
+ async function reloadArchivedCount() {
1155
+ if (disposed) return;
1156
+ const value = await repository.countArchivedConversations();
1157
+ if (disposed) return;
1158
+ store.setArchivedCount(value);
1159
+ }
1124
1160
  async function backfillConversation(id) {
1125
1161
  const thread = store.snapshot().threads.get(id);
1126
1162
  const since = thread?.latestAt ?? null;
@@ -1147,7 +1183,13 @@ function createMessagingEngine(options) {
1147
1183
  outbox,
1148
1184
  identity,
1149
1185
  async start() {
1186
+ if (options.archiveFilter !== void 0) {
1187
+ store.setArchiveFilter(options.archiveFilter);
1188
+ }
1150
1189
  await reloadInbox();
1190
+ void reloadArchivedCount().catch(
1191
+ (error) => reportError(error, "refreshArchivedCount")
1192
+ );
1151
1193
  if (disposed || inboxChannel !== null) return;
1152
1194
  inboxChannel = manager.open({
1153
1195
  topic: inboxTopic(identity.userId),
@@ -1189,12 +1231,33 @@ function createMessagingEngine(options) {
1189
1231
  }
1190
1232
  });
1191
1233
  },
1234
+ async setArchiveFilter(next) {
1235
+ if (next === store.snapshot().archiveFilter) return;
1236
+ store.setArchiveFilter(next);
1237
+ conversationCursor = null;
1238
+ try {
1239
+ await reloadInbox();
1240
+ } catch (error) {
1241
+ reportError(error, "setArchiveFilter");
1242
+ }
1243
+ await reloadArchivedCount().catch(
1244
+ (error) => reportError(error, "refreshArchivedCount")
1245
+ );
1246
+ },
1247
+ async refreshArchivedCount() {
1248
+ try {
1249
+ await reloadArchivedCount();
1250
+ } catch (error) {
1251
+ reportError(error, "refreshArchivedCount");
1252
+ }
1253
+ },
1192
1254
  async loadMoreConversations() {
1193
1255
  if (conversationCursor === null) return;
1194
1256
  try {
1195
1257
  const page = await repository.listConversations({
1196
1258
  limit: conversationPageSize,
1197
- cursor: conversationCursor
1259
+ cursor: conversationCursor,
1260
+ archived: store.snapshot().archiveFilter
1198
1261
  });
1199
1262
  conversationCursor = page.nextCursor;
1200
1263
  store.appendConversations(page.items, page.hasMore);
@@ -1458,6 +1521,7 @@ function createMessagingRepository(options) {
1458
1521
  identity,
1459
1522
  async listConversations(args = {}) {
1460
1523
  const limit = args.limit ?? 30;
1524
+ const archived = args.archived ?? DEFAULT_MESSAGING_ARCHIVE_FILTER;
1461
1525
  const operation = "listConversations";
1462
1526
  const rows = await withSessionRetry(
1463
1527
  operation,
@@ -1467,7 +1531,12 @@ function createMessagingRepository(options) {
1467
1531
  p_user_id: identity.userId,
1468
1532
  p_limit: limit + 1,
1469
1533
  p_before_sort_at: args.cursor?.beforeSortAt ?? null,
1470
- p_before_conversation_id: args.cursor?.beforeConversationId ?? null
1534
+ p_before_conversation_id: args.cursor?.beforeConversationId ?? null,
1535
+ // THE ARCHIVED-ITEMS LAW, SERVER-side. `get_dm_conversations_with_details`
1536
+ // used to hardcode `is_archived IS FALSE` with no parameter at all,
1537
+ // so an archived conversation was not hidden — it was unreachable.
1538
+ // The RPC gained `p_archived` on 2026-09-09 (register row R1).
1539
+ p_archived: archived
1471
1540
  },
1472
1541
  operation
1473
1542
  )
@@ -1487,6 +1556,29 @@ function createMessagingRepository(options) {
1487
1556
  nextCursor: hasMore && last !== void 0 ? { beforeSortAt: last.sortAt, beforeConversationId: last.conversation.id } : null
1488
1557
  };
1489
1558
  },
1559
+ async countArchivedConversations(args = {}) {
1560
+ const limit = args.limit ?? 100;
1561
+ const operation = "countArchivedConversations";
1562
+ const rows = await withSessionRetry(
1563
+ operation,
1564
+ () => rpc(
1565
+ RPCS.conversationsWithDetails,
1566
+ {
1567
+ p_user_id: identity.userId,
1568
+ p_limit: limit + 1,
1569
+ p_before_sort_at: null,
1570
+ p_before_conversation_id: null,
1571
+ p_archived: "archived"
1572
+ },
1573
+ operation
1574
+ )
1575
+ );
1576
+ if (rows !== null && !Array.isArray(rows)) {
1577
+ throw invalidResponse(operation, `${RPCS.conversationsWithDetails} did not return rows`);
1578
+ }
1579
+ const found = (rows ?? []).length;
1580
+ return found > limit ? { count: limit, exact: false } : { count: found, exact: true };
1581
+ },
1490
1582
  async getConversation(id) {
1491
1583
  const operation = "getConversation";
1492
1584
  const { data, error } = await withSessionRetry(
@@ -1795,6 +1887,7 @@ function MessagingRuntime(props) {
1795
1887
  onFallback: (message) => report({ level: "warn", message })
1796
1888
  }),
1797
1889
  onDiagnostic: report,
1890
+ ...props.archiveFilter !== void 0 ? { archiveFilter: props.archiveFilter } : {},
1798
1891
  onIncoming: (message) => {
1799
1892
  const snapshot = built?.store.snapshot();
1800
1893
  if (snapshot === void 0) return;
@@ -2025,6 +2118,11 @@ function useConversations() {
2025
2118
  },
2026
2119
  select,
2027
2120
  activeConversationId: snapshot?.activeConversationId ?? null,
2121
+ archiveFilter: snapshot?.archiveFilter ?? DEFAULT_MESSAGING_ARCHIVE_FILTER,
2122
+ setArchiveFilter: (next) => {
2123
+ void host?.engine.setArchiveFilter(next);
2124
+ },
2125
+ archivedCount: snapshot?.archivedCount ?? null,
2028
2126
  startDirect: async (otherUserId) => {
2029
2127
  if (host === null) {
2030
2128
  throw new Error("[@ai-matrx/messaging] startDirect called before the host was ready.");
@@ -2601,9 +2699,37 @@ var AI_LABELS = {
2601
2699
  draftReply: "Draft a reply"
2602
2700
  };
2603
2701
  function ConversationList(props) {
2604
- const { conversations, hasMore, isInitialLoading, loadMore, select, activeConversationId } = useConversations();
2702
+ const {
2703
+ conversations,
2704
+ hasMore,
2705
+ isInitialLoading,
2706
+ loadMore,
2707
+ select,
2708
+ activeConversationId,
2709
+ archiveFilter,
2710
+ setArchiveFilter,
2711
+ archivedCount
2712
+ } = useConversations();
2605
2713
  const RowChrome = useMessagingHost()?.wrapConversationRow ?? null;
2606
2714
  const [query, setQuery] = useState2("");
2715
+ const showingArchive = archiveFilter === "archived";
2716
+ const archiveReveal = useMemo3(() => {
2717
+ if (showingArchive) {
2718
+ return {
2719
+ next: "active",
2720
+ label: "\u2190 Back to active conversations",
2721
+ ariaLabel: "Hide archived conversations"
2722
+ };
2723
+ }
2724
+ if (archivedCount === null) return null;
2725
+ if (archivedCount.count === 0) return null;
2726
+ const printed = archivedCount.exact ? `${archivedCount.count}` : `${archivedCount.count}+`;
2727
+ return {
2728
+ next: "archived",
2729
+ label: `Archived (${printed})`,
2730
+ ariaLabel: "Show archived conversations"
2731
+ };
2732
+ }, [showingArchive, archivedCount]);
2607
2733
  const visible = useMemo3(() => {
2608
2734
  const needle = query.trim().toLowerCase();
2609
2735
  if (needle.length === 0) return conversations;
@@ -2636,12 +2762,22 @@ function ConversationList(props) {
2636
2762
  }
2637
2763
  ) : null
2638
2764
  ] }),
2765
+ archiveReveal !== null ? /* @__PURE__ */ jsx4("div", { className: "mx-msg__list-archive", children: /* @__PURE__ */ jsx4(
2766
+ "button",
2767
+ {
2768
+ type: "button",
2769
+ className: "mx-msg__chip",
2770
+ onClick: () => setArchiveFilter(archiveReveal.next),
2771
+ "aria-label": archiveReveal.ariaLabel,
2772
+ children: archiveReveal.label
2773
+ }
2774
+ ) }) : null,
2639
2775
  /* @__PURE__ */ jsxs2("div", { className: "mx-msg__scroll", children: [
2640
2776
  isInitialLoading ? /* @__PURE__ */ jsx4(ConversationSkeleton, {}) : visible.length === 0 ? /* @__PURE__ */ jsx4(
2641
2777
  EmptyState,
2642
2778
  {
2643
- title: query.length > 0 ? "No matches" : "No conversations yet",
2644
- body: query.length > 0 ? "Try a different name or word." : "Start one and it will appear here."
2779
+ title: query.length > 0 ? "No matches" : showingArchive ? "No archived conversations" : "No conversations yet",
2780
+ body: query.length > 0 ? "Try a different name or word." : showingArchive ? "Archiving a conversation moves it here." : "Start one and it will appear here."
2645
2781
  }
2646
2782
  ) : /* @__PURE__ */ jsx4("ul", { className: "mx-msg__rows", children: visible.map((item) => {
2647
2783
  const row = /* @__PURE__ */ jsx4(
@@ -3136,13 +3272,6 @@ function MessagingInbox(props) {
3136
3272
  }
3137
3273
  );
3138
3274
  }
3139
-
3140
- // src/core/types.ts
3141
- var asConversationId = (value) => value;
3142
- var asMessageId = (value) => value;
3143
- var asUserId = (value) => value;
3144
- var asOrganizationId = (value) => value;
3145
- var asClientMessageId = (value) => value;
3146
3275
  export {
3147
3276
  AgentTag,
3148
3277
  AlertIcon,
@@ -3156,6 +3285,7 @@ export {
3156
3285
  ConversationList,
3157
3286
  ConversationSkeleton,
3158
3287
  ConversationView,
3288
+ DEFAULT_MESSAGING_ARCHIVE_FILTER,
3159
3289
  DeliveryTick,
3160
3290
  DoubleCheckIcon,
3161
3291
  EmptyState,
@@ -3218,6 +3348,7 @@ export {
3218
3348
  resolveActor,
3219
3349
  splitText,
3220
3350
  summarizeText,
3351
+ toMessagingArchiveFilter,
3221
3352
  unreadCutoff,
3222
3353
  useComposer,
3223
3354
  useConversation,