@ttt-productions/chat-react 0.13.16 → 0.13.17

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.
Files changed (84) hide show
  1. package/README.md +3 -3
  2. package/dist/context/ChatNameResolverContext.d.ts +0 -23
  3. package/dist/context/ChatNameResolverContext.d.ts.map +1 -1
  4. package/dist/context/ChatNameResolverContext.js +0 -16
  5. package/dist/context/ChatNameResolverContext.js.map +1 -1
  6. package/dist/hooks/useChatMessages.d.ts +0 -4
  7. package/dist/hooks/useChatMessages.d.ts.map +1 -1
  8. package/dist/hooks/useChatMessages.js +0 -10
  9. package/dist/hooks/useChatMessages.js.map +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +0 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/realtime/channel-client.d.ts +0 -248
  14. package/dist/realtime/channel-client.d.ts.map +1 -1
  15. package/dist/realtime/channel-client.js +5 -432
  16. package/dist/realtime/channel-client.js.map +1 -1
  17. package/dist/realtime/diagnostics.d.ts +0 -62
  18. package/dist/realtime/diagnostics.d.ts.map +1 -1
  19. package/dist/realtime/diagnostics.js +4 -89
  20. package/dist/realtime/diagnostics.js.map +1 -1
  21. package/dist/realtime/inbox-client.d.ts +0 -71
  22. package/dist/realtime/inbox-client.d.ts.map +1 -1
  23. package/dist/realtime/inbox-client.js +0 -98
  24. package/dist/realtime/inbox-client.js.map +1 -1
  25. package/dist/realtime/initial-load-budget.d.ts +1 -16
  26. package/dist/realtime/initial-load-budget.d.ts.map +1 -1
  27. package/dist/realtime/initial-load-budget.js +1 -20
  28. package/dist/realtime/initial-load-budget.js.map +1 -1
  29. package/dist/realtime/map.d.ts +0 -26
  30. package/dist/realtime/map.d.ts.map +1 -1
  31. package/dist/realtime/map.js +0 -33
  32. package/dist/realtime/map.js.map +1 -1
  33. package/dist/realtime/shared.d.ts +0 -78
  34. package/dist/realtime/shared.d.ts.map +1 -1
  35. package/dist/realtime/shared.js +2 -76
  36. package/dist/realtime/shared.js.map +1 -1
  37. package/dist/realtime/socket.d.ts +0 -15
  38. package/dist/realtime/socket.d.ts.map +1 -1
  39. package/dist/realtime/socket.js +0 -4
  40. package/dist/realtime/socket.js.map +1 -1
  41. package/dist/realtime/transport.d.ts +0 -23
  42. package/dist/realtime/transport.d.ts.map +1 -1
  43. package/dist/realtime/transport.js +0 -13
  44. package/dist/realtime/transport.js.map +1 -1
  45. package/dist/realtime/useRealtimeChatMessages.d.ts +0 -17
  46. package/dist/realtime/useRealtimeChatMessages.d.ts.map +1 -1
  47. package/dist/realtime/useRealtimeChatMessages.js +0 -22
  48. package/dist/realtime/useRealtimeChatMessages.js.map +1 -1
  49. package/dist/realtime/wire.d.ts +0 -56
  50. package/dist/realtime/wire.d.ts.map +1 -1
  51. package/dist/realtime/wire.js +0 -16
  52. package/dist/realtime/wire.js.map +1 -1
  53. package/dist/types.d.ts +0 -38
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js +0 -3
  56. package/dist/types.js.map +1 -1
  57. package/dist/ui/ChatShell.d.ts +0 -42
  58. package/dist/ui/ChatShell.d.ts.map +1 -1
  59. package/dist/ui/ChatShell.js +2 -39
  60. package/dist/ui/ChatShell.js.map +1 -1
  61. package/dist/ui/Composer.d.ts +0 -13
  62. package/dist/ui/Composer.d.ts.map +1 -1
  63. package/dist/ui/Composer.js +0 -13
  64. package/dist/ui/Composer.js.map +1 -1
  65. package/dist/ui/MessageItemDefault.d.ts +0 -4
  66. package/dist/ui/MessageItemDefault.d.ts.map +1 -1
  67. package/dist/ui/MessageItemDefault.js +0 -32
  68. package/dist/ui/MessageItemDefault.js.map +1 -1
  69. package/dist/ui/MessageList.d.ts +0 -23
  70. package/dist/ui/MessageList.d.ts.map +1 -1
  71. package/dist/ui/MessageList.js +2 -12
  72. package/dist/ui/MessageList.js.map +1 -1
  73. package/dist/ui/MessageText.d.ts +0 -13
  74. package/dist/ui/MessageText.d.ts.map +1 -1
  75. package/dist/ui/MessageText.js.map +1 -1
  76. package/dist/ui/SenderName.d.ts +0 -5
  77. package/dist/ui/SenderName.d.ts.map +1 -1
  78. package/dist/ui/SenderName.js +0 -5
  79. package/dist/ui/SenderName.js.map +1 -1
  80. package/dist/ui/menus.d.ts.map +1 -1
  81. package/dist/ui/menus.js +1 -3
  82. package/dist/ui/menus.js.map +1 -1
  83. package/package.json +10 -10
  84. package/src/styles/chat.css +2 -15
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @ttt-productions/chat-react
2
2
 
3
- Chat **React UI** for TTT Productions apps: chat shell, composer, message list,
3
+ Chat **React UI** for consuming apps: chat shell, composer, message list,
4
4
  the name-resolver context, and the realtime-newest-window + infinite-older
5
- hooks. Chat is PLAIN-TEXT-only — a conversation's files live in the consuming
6
- app's Conversation Files surface, never in the message timeline, and message text
5
+ hooks. Chat is PLAIN-TEXT-only — files an app attaches to a conversation are the
6
+ app's own surface, never in the message timeline, and message text
7
7
  carries no token grammar (no mentions, no autocomplete, no chips).
8
8
 
9
9
  Built on the pure [`@ttt-productions/chat-core`](../chat-core) package
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
2
  import type { ChatNameResolution, ChatNameResolver, ChatPrewarmSenders } from '@ttt-productions/chat-core';
3
- /** A sender name the app could not resolve: pending, unavailable, or failed. */
4
3
  export type UnresolvedChatName = Exclude<ChatNameResolution, {
5
4
  status: 'resolved';
6
5
  }>;
@@ -12,34 +11,12 @@ type ChatNameResolverContextValue = {
12
11
  export type ChatNameResolverProviderProps = {
13
12
  resolveName: ChatNameResolver;
14
13
  prewarm?: ChatPrewarmSenders;
15
- /**
16
- * Renders a sender whose name is not resolved — the app's own placeholder while the
17
- * read is pending, its unavailable state when there is no person to name, and its
18
- * load-error state (with `retry`) when the read failed. The chat UI never writes a
19
- * stand-in name of its own; without this slot an unresolved sender renders nothing.
20
- */
21
14
  renderUnresolvedName?: (resolution: UnresolvedChatName) => React.ReactNode;
22
15
  children: React.ReactNode;
23
16
  };
24
- /**
25
- * Wraps any chat-react consumer (typically the whole app shell) and provides the name
26
- * resolver. Required for any tree that renders <ChatShell>, <MessageList>, or
27
- * <MessageItemDefault>.
28
- */
29
17
  export declare function ChatNameResolverProvider(props: ChatNameResolverProviderProps): React.JSX.Element;
30
- /**
31
- * Strict hook — throws if no provider is wrapped. Used inside this package's own
32
- * renderers so a missing setup surfaces on first render instead of rendering a sender
33
- * with no name.
34
- */
35
18
  export declare function useChatNameResolver(): ChatNameResolverContextValue;
36
- /**
37
- * Optional variant — returns null instead of throwing when no provider is wrapped.
38
- * Used internally by the message hooks so headless callers (tests, data-only consumers
39
- * that render no UI) can still use them.
40
- */
41
19
  export declare function useOptionalChatNameResolver(): ChatNameResolverContextValue | null;
42
- /** What the app knows about a sender's name right now. Re-renders as the app's cache settles. */
43
20
  export declare function useSenderNameResolution(senderId: string): ChatNameResolution;
44
21
  export {};
45
22
  //# sourceMappingURL=ChatNameResolverContext.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatNameResolverContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatNameResolverContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AAEpC,gFAAgF;AAChF,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AAErF,KAAK,4BAA4B,GAAG;IAClC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;CAC5E,CAAC;AAIF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,WAAW,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC3E,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,6BAA6B,qBAS5E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,4BAA4B,CAQlE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,IAAI,4BAA4B,GAAG,IAAI,CAEjF;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAG5E"}
1
+ {"version":3,"file":"ChatNameResolverContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatNameResolverContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AAErF,KAAK,4BAA4B,GAAG;IAClC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;CAC5E,CAAC;AAIF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,WAAW,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC3E,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,6BAA6B,qBAS5E;AAED,wBAAgB,mBAAmB,IAAI,4BAA4B,CAQlE;AAED,wBAAgB,2BAA2B,IAAI,4BAA4B,GAAG,IAAI,CAEjF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAG5E"}
@@ -2,21 +2,11 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
4
  const ChatNameResolverContext = React.createContext(null);
5
- /**
6
- * Wraps any chat-react consumer (typically the whole app shell) and provides the name
7
- * resolver. Required for any tree that renders <ChatShell>, <MessageList>, or
8
- * <MessageItemDefault>.
9
- */
10
5
  export function ChatNameResolverProvider(props) {
11
6
  const { resolveName, prewarm, renderUnresolvedName, children } = props;
12
7
  const value = React.useMemo(() => ({ resolveName, prewarm, renderUnresolvedName }), [resolveName, prewarm, renderUnresolvedName]);
13
8
  return (_jsx(ChatNameResolverContext.Provider, { value: value, children: children }));
14
9
  }
15
- /**
16
- * Strict hook — throws if no provider is wrapped. Used inside this package's own
17
- * renderers so a missing setup surfaces on first render instead of rendering a sender
18
- * with no name.
19
- */
20
10
  export function useChatNameResolver() {
21
11
  const ctx = React.useContext(ChatNameResolverContext);
22
12
  if (!ctx) {
@@ -24,15 +14,9 @@ export function useChatNameResolver() {
24
14
  }
25
15
  return ctx;
26
16
  }
27
- /**
28
- * Optional variant — returns null instead of throwing when no provider is wrapped.
29
- * Used internally by the message hooks so headless callers (tests, data-only consumers
30
- * that render no UI) can still use them.
31
- */
32
17
  export function useOptionalChatNameResolver() {
33
18
  return React.useContext(ChatNameResolverContext);
34
19
  }
35
- /** What the app knows about a sender's name right now. Re-renders as the app's cache settles. */
36
20
  export function useSenderNameResolution(senderId) {
37
21
  const { resolveName } = useChatNameResolver();
38
22
  return resolveName(senderId);
@@ -1 +1 @@
1
- {"version":3,"file":"ChatNameResolverContext.js","sourceRoot":"","sources":["../../src/context/ChatNameResolverContext.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAgB/B,MAAM,uBAAuB,GAAG,KAAK,CAAC,aAAa,CAAsC,IAAI,CAAC,CAAC;AAe/F;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAoC;IAC3E,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACvE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CACzB,GAAG,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,EACtD,CAAC,WAAW,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAC7C,CAAC;IACF,OAAO,CACL,KAAC,uBAAuB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAoC,CAC9F,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;IACtD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,8GAA8G,CAC/G,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,KAAK,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;AACnD,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,MAAM,EAAE,WAAW,EAAE,GAAG,mBAAmB,EAAE,CAAC;IAC9C,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatNameResolution,\n ChatNameResolver,\n ChatPrewarmSenders,\n} from '@ttt-productions/chat-core';\n\n/** A sender name the app could not resolve: pending, unavailable, or failed. */\nexport type UnresolvedChatName = Exclude<ChatNameResolution, { status: 'resolved' }>;\n\ntype ChatNameResolverContextValue = {\n resolveName: ChatNameResolver;\n prewarm?: ChatPrewarmSenders;\n renderUnresolvedName?: (resolution: UnresolvedChatName) => React.ReactNode;\n};\n\nconst ChatNameResolverContext = React.createContext<ChatNameResolverContextValue | null>(null);\n\nexport type ChatNameResolverProviderProps = {\n resolveName: ChatNameResolver;\n prewarm?: ChatPrewarmSenders;\n /**\n * Renders a sender whose name is not resolved — the app's own placeholder while the\n * read is pending, its unavailable state when there is no person to name, and its\n * load-error state (with `retry`) when the read failed. The chat UI never writes a\n * stand-in name of its own; without this slot an unresolved sender renders nothing.\n */\n renderUnresolvedName?: (resolution: UnresolvedChatName) => React.ReactNode;\n children: React.ReactNode;\n};\n\n/**\n * Wraps any chat-react consumer (typically the whole app shell) and provides the name\n * resolver. Required for any tree that renders <ChatShell>, <MessageList>, or\n * <MessageItemDefault>.\n */\nexport function ChatNameResolverProvider(props: ChatNameResolverProviderProps) {\n const { resolveName, prewarm, renderUnresolvedName, children } = props;\n const value = React.useMemo(\n () => ({ resolveName, prewarm, renderUnresolvedName }),\n [resolveName, prewarm, renderUnresolvedName],\n );\n return (\n <ChatNameResolverContext.Provider value={value}>{children}</ChatNameResolverContext.Provider>\n );\n}\n\n/**\n * Strict hook — throws if no provider is wrapped. Used inside this package's own\n * renderers so a missing setup surfaces on first render instead of rendering a sender\n * with no name.\n */\nexport function useChatNameResolver(): ChatNameResolverContextValue {\n const ctx = React.useContext(ChatNameResolverContext);\n if (!ctx) {\n throw new Error(\n '[chat-react] ChatNameResolverProvider is required. Wrap your ChatShell tree with <ChatNameResolverProvider>.',\n );\n }\n return ctx;\n}\n\n/**\n * Optional variant — returns null instead of throwing when no provider is wrapped.\n * Used internally by the message hooks so headless callers (tests, data-only consumers\n * that render no UI) can still use them.\n */\nexport function useOptionalChatNameResolver(): ChatNameResolverContextValue | null {\n return React.useContext(ChatNameResolverContext);\n}\n\n/** What the app knows about a sender's name right now. Re-renders as the app's cache settles. */\nexport function useSenderNameResolution(senderId: string): ChatNameResolution {\n const { resolveName } = useChatNameResolver();\n return resolveName(senderId);\n}\n"]}
1
+ {"version":3,"file":"ChatNameResolverContext.js","sourceRoot":"","sources":["../../src/context/ChatNameResolverContext.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,MAAM,uBAAuB,GAAG,KAAK,CAAC,aAAa,CAAsC,IAAI,CAAC,CAAC;AAS/F,MAAM,UAAU,wBAAwB,CAAC,KAAoC;IAC3E,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACvE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CACzB,GAAG,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,EACtD,CAAC,WAAW,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAC7C,CAAC;IACF,OAAO,CACL,KAAC,uBAAuB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAoC,CAC9F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;IACtD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,8GAA8G,CAC/G,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,KAAK,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,MAAM,EAAE,WAAW,EAAE,GAAG,mBAAmB,EAAE,CAAC;IAC9C,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatNameResolution,\n ChatNameResolver,\n ChatPrewarmSenders,\n} from '@ttt-productions/chat-core';\n\nexport type UnresolvedChatName = Exclude<ChatNameResolution, { status: 'resolved' }>;\n\ntype ChatNameResolverContextValue = {\n resolveName: ChatNameResolver;\n prewarm?: ChatPrewarmSenders;\n renderUnresolvedName?: (resolution: UnresolvedChatName) => React.ReactNode;\n};\n\nconst ChatNameResolverContext = React.createContext<ChatNameResolverContextValue | null>(null);\n\nexport type ChatNameResolverProviderProps = {\n resolveName: ChatNameResolver;\n prewarm?: ChatPrewarmSenders;\n renderUnresolvedName?: (resolution: UnresolvedChatName) => React.ReactNode;\n children: React.ReactNode;\n};\n\nexport function ChatNameResolverProvider(props: ChatNameResolverProviderProps) {\n const { resolveName, prewarm, renderUnresolvedName, children } = props;\n const value = React.useMemo(\n () => ({ resolveName, prewarm, renderUnresolvedName }),\n [resolveName, prewarm, renderUnresolvedName],\n );\n return (\n <ChatNameResolverContext.Provider value={value}>{children}</ChatNameResolverContext.Provider>\n );\n}\n\nexport function useChatNameResolver(): ChatNameResolverContextValue {\n const ctx = React.useContext(ChatNameResolverContext);\n if (!ctx) {\n throw new Error(\n '[chat-react] ChatNameResolverProvider is required. Wrap your ChatShell tree with <ChatNameResolverProvider>.',\n );\n }\n return ctx;\n}\n\nexport function useOptionalChatNameResolver(): ChatNameResolverContextValue | null {\n return React.useContext(ChatNameResolverContext);\n}\n\nexport function useSenderNameResolution(senderId: string): ChatNameResolution {\n const { resolveName } = useChatNameResolver();\n return resolveName(senderId);\n}\n"]}
@@ -8,13 +8,9 @@ export type UseChatMessagesResult = {
8
8
  fetchOlder: () => Promise<void>;
9
9
  hasOlder: boolean;
10
10
  isFetchingOlder: boolean;
11
- /** The live listener's state: `connecting` while it (re)subscribes, `error` once it failed. */
12
11
  sourceState: FirestoreSourceState;
13
- /** The live listener's failure; the messages already shown stay in `messages`. */
14
12
  error: Error | null;
15
- /** The last older-page read failed; the list stops asking for older pages until `retry`. */
16
13
  olderError: Error | null;
17
- /** Re-run what failed: resubscribe a failed listener, re-read a failed older page. */
18
14
  retry: () => void;
19
15
  };
20
16
  export declare function useChatMessages(config: ChatCoreConfig): UseChatMessagesResult;
@@ -1 +1 @@
1
- {"version":3,"file":"useChatMessages.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOlD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,+FAA+F;IAC/F,WAAW,EAAE,oBAAoB,CAAC;IAClC,kFAAkF;IAClF,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,4FAA4F;IAC5F,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,sFAAsF;IACtF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAmCF,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,qBAAqB,CA6F7E"}
1
+ {"version":3,"file":"useChatMessages.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMlD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,oBAAoB,CAAC;IAClC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AA8BF,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,qBAAqB,CAyF7E"}
@@ -4,7 +4,6 @@ import { toMillis } from '@ttt-productions/firebase-helpers';
4
4
  import { useFirestoreLiveInfinite } from '@ttt-productions/query-core/react';
5
5
  import { messagesColPath } from '../firestore/queries.js';
6
6
  import { useOptionalChatNameResolver } from '../context/ChatNameResolverContext.js';
7
- // Safety limit to prevent memory exhaustion
8
7
  const MAX_PAGE_SIZE = 100;
9
8
  function mapMsg(data, threadId, messageMeta) {
10
9
  const d = data;
@@ -12,9 +11,6 @@ function mapMsg(data, threadId, messageMeta) {
12
11
  if (!createdAt) {
13
12
  console.error('[useChatMessages] Message missing valid createdAt:', data.id, d.createdAt);
14
13
  }
15
- // A stored `replyTo` (if any legacy doc still carries one) is deliberately NOT
16
- // mapped: chat has no reply-authoring affordance, so the UI has no reply
17
- // renderer to feed.
18
14
  const appMeta = messageMeta?.(data);
19
15
  return {
20
16
  messageId: data.id,
@@ -25,8 +21,6 @@ function mapMsg(data, threadId, messageMeta) {
25
21
  type: d.type,
26
22
  isSystemMessage: d.isSystemMessage ?? undefined,
27
23
  referencedUids: Array.isArray(d.referencedUids) ? d.referencedUids : undefined,
28
- // Moderation tombstone flag (backend-written) — carried through so renderers
29
- // can tombstone the content.
30
24
  hidden: d.hidden ?? undefined,
31
25
  meta: appMeta ? { ...d.meta, ...appMeta } : d.meta,
32
26
  };
@@ -37,9 +31,7 @@ export function useChatMessages(config) {
37
31
  if (requestedPageSize > MAX_PAGE_SIZE) {
38
32
  console.warn(`[useChatMessages] Requested pageSize ${requestedPageSize} exceeds maximum ${MAX_PAGE_SIZE}. Using ${MAX_PAGE_SIZE}.`);
39
33
  }
40
- // Stable key segment for the message collection path (string | string[]).
41
34
  const pathKey = React.useMemo(() => JSON.stringify(Array.isArray(chatCollectionPath) ? chatCollectionPath : [chatCollectionPath]), [chatCollectionPath]);
42
- // Slash-joined collection path for the generic hook (handles nested paths).
43
35
  const collectionPath = React.useMemo(() => messagesColPath(chatCollectionPath, threadId, messagesSubcollection).join('/'), [chatCollectionPath, threadId, messagesSubcollection]);
44
36
  const { items, isInitialLoading, fetchOlder, hasOlder, isFetchingOlder, sourceState, error, olderError, retry, } = useFirestoreLiveInfinite({
45
37
  collectionPath,
@@ -59,8 +51,6 @@ export function useChatMessages(config) {
59
51
  select: (data) => mapMsg(data, threadId, messageMeta),
60
52
  });
61
53
  const messages = React.useMemo(() => (allowed ? items : []), [allowed, items]);
62
- // Pre-warm name cache for all visible senders.
63
- // Optional resolver: silent no-op if no provider is wrapped.
64
54
  const resolverCtx = useOptionalChatNameResolver();
65
55
  const senderIds = React.useMemo(() => {
66
56
  const set = new Set();
@@ -1 +1 @@
1
- {"version":3,"file":"useChatMessages.js","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAI7E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AAEpF,4CAA4C;AAC5C,MAAM,aAAa,GAAG,GAAG,CAAC;AAmB1B,SAAS,MAAM,CACb,IAAmC,EACnC,QAAgB,EAChB,WAA0C;IAE1C,MAAM,CAAC,GAAG,IAAW,CAAC;IAEtB,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAExC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,oDAAoD,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5F,CAAC;IAED,+EAA+E;IAC/E,yEAAyE;IACzE,oBAAoB;IACpB,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,EAAE;QAClB,QAAQ;QACR,SAAS,EAAE,SAAS,IAAI,CAAC;QACzB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE;QAClB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,eAAe,EAAE,CAAC,CAAC,eAAe,IAAI,SAAS;QAC/C,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;QAC9E,6EAA6E;QAC7E,6BAA6B;QAC7B,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;QAC7B,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;KACnD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAsB;IACpD,MAAM,EACJ,kBAAkB,EAClB,qBAAqB,GAAG,UAAU,EAClC,QAAQ,EACR,QAAQ,EAAE,iBAAiB,GAAG,EAAE,EAChC,aAAa,EACb,OAAO,EACP,cAAc,GAAG,WAAW,EAC5B,WAAW,GACZ,GAAG,MAAM,CAAC;IAEX,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IAE5D,IAAI,iBAAiB,GAAG,aAAa,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CACV,wCAAwC,iBAAiB,oBAAoB,aAAa,WAAW,aAAa,GAAG,CACtH,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAC3B,GAAG,EAAE,CACH,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,EAC/F,CAAC,kBAAkB,CAAC,CACrB,CAAC;IAEF,4EAA4E;IAC5E,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAClC,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EACpF,CAAC,kBAAkB,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CACtD,CAAC;IAEF,MAAM,EACJ,KAAK,EACL,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,eAAe,EACf,WAAW,EACX,KAAK,EACL,UAAU,EACV,KAAK,GACN,GAAG,wBAAwB,CAAgB;QAC1C,cAAc;QACd,QAAQ,EAAE;YACR,WAAW;YACX,UAAU;YACV,OAAO;YACP,QAAQ;YACR,qBAAqB;YACrB,QAAQ;YACR,aAAa;SACd;QACD,YAAY,EAAE,cAAc;QAC5B,QAAQ;QACR,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC;KACtD,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAE/E,+CAA+C;IAC/C,6DAA6D;IAC7D,MAAM,WAAW,GAAG,2BAA2B,EAAE,CAAC;IAClD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACpB,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,cAAc,IAAI,EAAE;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,WAAW,EAAE,OAAO,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjD,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;IAE7B,OAAO;QACL,OAAO;QACP,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK;QACpD,QAAQ;QACR,UAAU;QACV,QAAQ;QACR,eAAe;QACf,WAAW;QACX,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QAC7B,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;QACvC,KAAK;KACN,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { DocumentData } from 'firebase/firestore';\nimport { toMillis } from '@ttt-productions/firebase-helpers';\nimport { useFirestoreLiveInfinite } from '@ttt-productions/query-core/react';\nimport type { FirestoreSourceState } from '@ttt-productions/query-core/types';\nimport type { ChatMessageV1 } from '@ttt-productions/chat-core';\nimport type { ChatCoreConfig } from '../types.js';\nimport { messagesColPath } from '../firestore/queries.js';\nimport { useOptionalChatNameResolver } from '../context/ChatNameResolverContext.js';\n\n// Safety limit to prevent memory exhaustion\nconst MAX_PAGE_SIZE = 100;\n\nexport type UseChatMessagesResult = {\n allowed: boolean;\n isInitialLoading: boolean;\n messages: ChatMessageV1[];\n fetchOlder: () => Promise<void>;\n hasOlder: boolean;\n isFetchingOlder: boolean;\n /** The live listener's state: `connecting` while it (re)subscribes, `error` once it failed. */\n sourceState: FirestoreSourceState;\n /** The live listener's failure; the messages already shown stay in `messages`. */\n error: Error | null;\n /** The last older-page read failed; the list stops asking for older pages until `retry`. */\n olderError: Error | null;\n /** Re-run what failed: resubscribe a failed listener, re-read a failed older page. */\n retry: () => void;\n};\n\nfunction mapMsg(\n data: DocumentData & { id: string },\n threadId: string,\n messageMeta: ChatCoreConfig['messageMeta'],\n): ChatMessageV1 {\n const d = data as any;\n\n const createdAt = toMillis(d.createdAt);\n\n if (!createdAt) {\n console.error('[useChatMessages] Message missing valid createdAt:', data.id, d.createdAt);\n }\n\n // A stored `replyTo` (if any legacy doc still carries one) is deliberately NOT\n // mapped: chat has no reply-authoring affordance, so the UI has no reply\n // renderer to feed.\n const appMeta = messageMeta?.(data);\n return {\n messageId: data.id,\n threadId,\n createdAt: createdAt || 0,\n senderId: d.senderId,\n text: d.text ?? '',\n type: d.type,\n isSystemMessage: d.isSystemMessage ?? undefined,\n referencedUids: Array.isArray(d.referencedUids) ? d.referencedUids : undefined,\n // Moderation tombstone flag (backend-written) — carried through so renderers\n // can tombstone the content.\n hidden: d.hidden ?? undefined,\n meta: appMeta ? { ...d.meta, ...appMeta } : d.meta,\n };\n}\n\nexport function useChatMessages(config: ChatCoreConfig): UseChatMessagesResult {\n const {\n chatCollectionPath,\n messagesSubcollection = 'messages',\n threadId,\n pageSize: requestedPageSize = 20,\n currentUserId,\n allowed,\n createdAtField = 'createdAt',\n messageMeta,\n } = config;\n\n const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);\n\n if (requestedPageSize > MAX_PAGE_SIZE) {\n console.warn(\n `[useChatMessages] Requested pageSize ${requestedPageSize} exceeds maximum ${MAX_PAGE_SIZE}. Using ${MAX_PAGE_SIZE}.`,\n );\n }\n\n // Stable key segment for the message collection path (string | string[]).\n const pathKey = React.useMemo(\n () =>\n JSON.stringify(Array.isArray(chatCollectionPath) ? chatCollectionPath : [chatCollectionPath]),\n [chatCollectionPath],\n );\n\n // Slash-joined collection path for the generic hook (handles nested paths).\n const collectionPath = React.useMemo(\n () => messagesColPath(chatCollectionPath, threadId, messagesSubcollection).join('/'),\n [chatCollectionPath, threadId, messagesSubcollection],\n );\n\n const {\n items,\n isInitialLoading,\n fetchOlder,\n hasOlder,\n isFetchingOlder,\n sourceState,\n error,\n olderError,\n retry,\n } = useFirestoreLiveInfinite<ChatMessageV1>({\n collectionPath,\n queryKey: [\n 'chat-core',\n 'messages',\n pathKey,\n threadId,\n messagesSubcollection,\n pageSize,\n currentUserId,\n ],\n orderByField: createdAtField,\n pageSize,\n enabled: allowed,\n sort: 'asc',\n select: (data) => mapMsg(data, threadId, messageMeta),\n });\n\n const messages = React.useMemo(() => (allowed ? items : []), [allowed, items]);\n\n // Pre-warm name cache for all visible senders.\n // Optional resolver: silent no-op if no provider is wrapped.\n const resolverCtx = useOptionalChatNameResolver();\n const senderIds = React.useMemo(() => {\n const set = new Set<string>();\n for (const m of messages) {\n set.add(m.senderId);\n for (const uid of m.referencedUids ?? []) set.add(uid);\n }\n return Array.from(set);\n }, [messages]);\n\n React.useEffect(() => {\n if (resolverCtx?.prewarm && senderIds.length > 0) {\n resolverCtx.prewarm(senderIds);\n }\n }, [resolverCtx, senderIds]);\n\n return {\n allowed,\n isInitialLoading: allowed ? isInitialLoading : false,\n messages,\n fetchOlder,\n hasOlder,\n isFetchingOlder,\n sourceState,\n error: allowed ? error : null,\n olderError: allowed ? olderError : null,\n retry,\n };\n}\n"]}
1
+ {"version":3,"file":"useChatMessages.js","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAI7E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AAEpF,MAAM,aAAa,GAAG,GAAG,CAAC;AAe1B,SAAS,MAAM,CACb,IAAmC,EACnC,QAAgB,EAChB,WAA0C;IAE1C,MAAM,CAAC,GAAG,IAAW,CAAC;IAEtB,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAExC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,oDAAoD,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,EAAE;QAClB,QAAQ;QACR,SAAS,EAAE,SAAS,IAAI,CAAC;QACzB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE;QAClB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,eAAe,EAAE,CAAC,CAAC,eAAe,IAAI,SAAS;QAC/C,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;QAC9E,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;QAC7B,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;KACnD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAsB;IACpD,MAAM,EACJ,kBAAkB,EAClB,qBAAqB,GAAG,UAAU,EAClC,QAAQ,EACR,QAAQ,EAAE,iBAAiB,GAAG,EAAE,EAChC,aAAa,EACb,OAAO,EACP,cAAc,GAAG,WAAW,EAC5B,WAAW,GACZ,GAAG,MAAM,CAAC;IAEX,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IAE5D,IAAI,iBAAiB,GAAG,aAAa,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CACV,wCAAwC,iBAAiB,oBAAoB,aAAa,WAAW,aAAa,GAAG,CACtH,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAC3B,GAAG,EAAE,CACH,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,EAC/F,CAAC,kBAAkB,CAAC,CACrB,CAAC;IAEF,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAClC,GAAG,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EACpF,CAAC,kBAAkB,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CACtD,CAAC;IAEF,MAAM,EACJ,KAAK,EACL,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,eAAe,EACf,WAAW,EACX,KAAK,EACL,UAAU,EACV,KAAK,GACN,GAAG,wBAAwB,CAAgB;QAC1C,cAAc;QACd,QAAQ,EAAE;YACR,WAAW;YACX,UAAU;YACV,OAAO;YACP,QAAQ;YACR,qBAAqB;YACrB,QAAQ;YACR,aAAa;SACd;QACD,YAAY,EAAE,cAAc;QAC5B,QAAQ;QACR,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC;KACtD,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAE/E,MAAM,WAAW,GAAG,2BAA2B,EAAE,CAAC;IAClD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACpB,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,cAAc,IAAI,EAAE;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,WAAW,EAAE,OAAO,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjD,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACjC,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;IAE7B,OAAO;QACL,OAAO;QACP,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK;QACpD,QAAQ;QACR,UAAU;QACV,QAAQ;QACR,eAAe;QACf,WAAW;QACX,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QAC7B,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;QACvC,KAAK;KACN,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { DocumentData } from 'firebase/firestore';\nimport { toMillis } from '@ttt-productions/firebase-helpers';\nimport { useFirestoreLiveInfinite } from '@ttt-productions/query-core/react';\nimport type { FirestoreSourceState } from '@ttt-productions/query-core/types';\nimport type { ChatMessageV1 } from '@ttt-productions/chat-core';\nimport type { ChatCoreConfig } from '../types.js';\nimport { messagesColPath } from '../firestore/queries.js';\nimport { useOptionalChatNameResolver } from '../context/ChatNameResolverContext.js';\n\nconst MAX_PAGE_SIZE = 100;\n\nexport type UseChatMessagesResult = {\n allowed: boolean;\n isInitialLoading: boolean;\n messages: ChatMessageV1[];\n fetchOlder: () => Promise<void>;\n hasOlder: boolean;\n isFetchingOlder: boolean;\n sourceState: FirestoreSourceState;\n error: Error | null;\n olderError: Error | null;\n retry: () => void;\n};\n\nfunction mapMsg(\n data: DocumentData & { id: string },\n threadId: string,\n messageMeta: ChatCoreConfig['messageMeta'],\n): ChatMessageV1 {\n const d = data as any;\n\n const createdAt = toMillis(d.createdAt);\n\n if (!createdAt) {\n console.error('[useChatMessages] Message missing valid createdAt:', data.id, d.createdAt);\n }\n\n const appMeta = messageMeta?.(data);\n return {\n messageId: data.id,\n threadId,\n createdAt: createdAt || 0,\n senderId: d.senderId,\n text: d.text ?? '',\n type: d.type,\n isSystemMessage: d.isSystemMessage ?? undefined,\n referencedUids: Array.isArray(d.referencedUids) ? d.referencedUids : undefined,\n hidden: d.hidden ?? undefined,\n meta: appMeta ? { ...d.meta, ...appMeta } : d.meta,\n };\n}\n\nexport function useChatMessages(config: ChatCoreConfig): UseChatMessagesResult {\n const {\n chatCollectionPath,\n messagesSubcollection = 'messages',\n threadId,\n pageSize: requestedPageSize = 20,\n currentUserId,\n allowed,\n createdAtField = 'createdAt',\n messageMeta,\n } = config;\n\n const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);\n\n if (requestedPageSize > MAX_PAGE_SIZE) {\n console.warn(\n `[useChatMessages] Requested pageSize ${requestedPageSize} exceeds maximum ${MAX_PAGE_SIZE}. Using ${MAX_PAGE_SIZE}.`,\n );\n }\n\n const pathKey = React.useMemo(\n () =>\n JSON.stringify(Array.isArray(chatCollectionPath) ? chatCollectionPath : [chatCollectionPath]),\n [chatCollectionPath],\n );\n\n const collectionPath = React.useMemo(\n () => messagesColPath(chatCollectionPath, threadId, messagesSubcollection).join('/'),\n [chatCollectionPath, threadId, messagesSubcollection],\n );\n\n const {\n items,\n isInitialLoading,\n fetchOlder,\n hasOlder,\n isFetchingOlder,\n sourceState,\n error,\n olderError,\n retry,\n } = useFirestoreLiveInfinite<ChatMessageV1>({\n collectionPath,\n queryKey: [\n 'chat-core',\n 'messages',\n pathKey,\n threadId,\n messagesSubcollection,\n pageSize,\n currentUserId,\n ],\n orderByField: createdAtField,\n pageSize,\n enabled: allowed,\n sort: 'asc',\n select: (data) => mapMsg(data, threadId, messageMeta),\n });\n\n const messages = React.useMemo(() => (allowed ? items : []), [allowed, items]);\n\n const resolverCtx = useOptionalChatNameResolver();\n const senderIds = React.useMemo(() => {\n const set = new Set<string>();\n for (const m of messages) {\n set.add(m.senderId);\n for (const uid of m.referencedUids ?? []) set.add(uid);\n }\n return Array.from(set);\n }, [messages]);\n\n React.useEffect(() => {\n if (resolverCtx?.prewarm && senderIds.length > 0) {\n resolverCtx.prewarm(senderIds);\n }\n }, [resolverCtx, senderIds]);\n\n return {\n allowed,\n isInitialLoading: allowed ? isInitialLoading : false,\n messages,\n fetchOlder,\n hasOlder,\n isFetchingOlder,\n sourceState,\n error: allowed ? error : null,\n olderError: allowed ? olderError : null,\n retry,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,EACf,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,6BAA6B,EAC7B,kBAAkB,GACnB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAK7D,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,mCAAmC,GACpC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,YAAY,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,GAC9B,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3F,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1F,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAU5B,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,YAAY,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,EACf,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,6BAA6B,EAC7B,kBAAkB,GACnB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,mCAAmC,GACpC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,YAAY,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,6BAA6B,GAC9B,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3F,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1F,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,YAAY,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -2,9 +2,6 @@
2
2
  export { ChatNameResolverProvider, useChatNameResolver, useOptionalChatNameResolver, useSenderNameResolution, } from './context/ChatNameResolverContext.js';
3
3
  export { SenderName } from './ui/SenderName.js';
4
4
  export { useChatMessages } from './hooks/useChatMessages.js';
5
- // Realtime (Cloudflare Durable Object) transport — the client half of the chat
6
- // worker wire protocol. Opt-in via `ChatCoreConfig.transport === 'realtime'`; the
7
- // firestore transport stays the unchanged default. See docs/packages/chat-react.md.
8
5
  export { createRealtimeChatClient, createInboxClient } from './realtime/transport.js';
9
6
  export { ChannelClient } from './realtime/channel-client.js';
10
7
  export { InboxClient } from './realtime/inbox-client.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAsBb,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,+EAA+E;AAC/E,kFAAkF;AAClF,oFAAoF;AACpF,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,mCAAmC,GACpC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAgC1E,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAyB,MAAM,qBAAqB,CAAC","sourcesContent":["'use client';\n\n// @ttt-productions/chat-react — chat React UI, hooks, the Firebase-client\n// adapter config types, and React render types. Depends on the pure\n// @ttt-productions/chat-core for contracts, parser, and grouping.\n//\n// Apps import chat CSS via \"@ttt-productions/chat-react/styles\".\n\n// React-coupled config + render types (previously on chat-core root).\nexport type {\n ChatCoreConfig,\n ChatTransportMode,\n ChatRealtimeTransportConfig,\n MessageRenderer,\n MessageRendererRegistry,\n} from './types.js';\n\n// Name-resolver context.\nexport type {\n ChatNameResolverProviderProps,\n UnresolvedChatName,\n} from './context/ChatNameResolverContext.js';\nexport {\n ChatNameResolverProvider,\n useChatNameResolver,\n useOptionalChatNameResolver,\n useSenderNameResolution,\n} from './context/ChatNameResolverContext.js';\nexport { SenderName } from './ui/SenderName.js';\n\nexport type { UseChatMessagesResult } from './hooks/useChatMessages.js';\nexport { useChatMessages } from './hooks/useChatMessages.js';\n\n// Realtime (Cloudflare Durable Object) transport — the client half of the chat\n// worker wire protocol. Opt-in via `ChatCoreConfig.transport === 'realtime'`; the\n// firestore transport stays the unchanged default. See docs/packages/chat-react.md.\nexport { createRealtimeChatClient, createInboxClient } from './realtime/transport.js';\nexport { ChannelClient } from './realtime/channel-client.js';\nexport { InboxClient } from './realtime/inbox-client.js';\nexport { useRealtimeChatMessages } from './realtime/useRealtimeChatMessages.js';\nexport { browserSocketFactory } from './realtime/socket.js';\nexport {\n ChatAccessDeniedError,\n isChatAccessDeniedError,\n CHAT_INITIAL_LOAD_FAILURE_BUDGET_MS,\n} from './realtime/shared.js';\nexport { buildFrame, parseFrame } from './realtime/wire.js';\nexport { CHAT_CLIENT_DIAGNOSTIC_EVENTS } from './realtime/diagnostics.js';\nexport type { RealtimeChatClient, RealtimeTransportConfig } from './realtime/transport.js';\nexport type {\n ChatClientDiagnosticsOption,\n ChatClientDiagnosticsSink,\n ChatClientDiagnosticEventName,\n} from './realtime/diagnostics.js';\nexport type { ChannelClientState, ChannelClientConfig } from './realtime/channel-client.js';\nexport type {\n InboxClientState,\n InboxClientConfig,\n ChatMarkReadOutcome,\n} from './realtime/inbox-client.js';\nexport type { UseRealtimeChatMessagesResult } from './realtime/useRealtimeChatMessages.js';\nexport type { GrantProvider, TransportTimers, RealtimeStatus } from './realtime/shared.js';\nexport type { RealtimeSocket, SocketFactory, SocketHandlers } from './realtime/socket.js';\nexport type {\n WireMessageRow,\n WireInboxSnapshot,\n WireRegistryEntry,\n ServerFrame,\n} from './realtime/wire.js';\n\n// TODO (re-export when a consumer needs them): the moderation-overlay helpers\n// `RevisionKind`, `ModerationOverlay`, `applyModerationOverlay`, `overlayFromRow`,\n// and `MODERATION_REDACTED_TEXT` live in `./realtime` but are intentionally NOT\n// re-exported from this package root yet — the consuming app's renderer reads moderation\n// state off `message.meta` (the realtime client applies the overlay internally), so\n// nothing imports them from here today. Add them to the block above when a consumer\n// genuinely needs to apply the overlay directly. (Comment-only; rides the next publish.)\n\nexport type { ChatShellProps, ChatLoadFailure } from './ui/ChatShell.js';\nexport { ChatShell } from './ui/ChatShell.js';\nexport { MessageList } from './ui/MessageList.js';\nexport type { ChatScrollContainerProps } from './ui/MessageList.js';\nexport type { ComposerProps } from './ui/Composer.js';\nexport { Composer } from './ui/Composer.js';\nexport type { MessageItemDefaultProps } from './ui/MessageItemDefault.js';\nexport { MessageItemDefault } from './ui/MessageItemDefault.js';\nexport { MessageActions, ThreadActions } from './ui/menus.js';\nexport { MessageText, type MessageTextProps } from './ui/MessageText.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAcb,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,mCAAmC,GACpC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAwB1E,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAyB,MAAM,qBAAqB,CAAC","sourcesContent":["'use client';\n\nexport type {\n ChatCoreConfig,\n ChatTransportMode,\n ChatRealtimeTransportConfig,\n MessageRenderer,\n MessageRendererRegistry,\n} from './types.js';\n\nexport type {\n ChatNameResolverProviderProps,\n UnresolvedChatName,\n} from './context/ChatNameResolverContext.js';\nexport {\n ChatNameResolverProvider,\n useChatNameResolver,\n useOptionalChatNameResolver,\n useSenderNameResolution,\n} from './context/ChatNameResolverContext.js';\nexport { SenderName } from './ui/SenderName.js';\n\nexport type { UseChatMessagesResult } from './hooks/useChatMessages.js';\nexport { useChatMessages } from './hooks/useChatMessages.js';\n\nexport { createRealtimeChatClient, createInboxClient } from './realtime/transport.js';\nexport { ChannelClient } from './realtime/channel-client.js';\nexport { InboxClient } from './realtime/inbox-client.js';\nexport { useRealtimeChatMessages } from './realtime/useRealtimeChatMessages.js';\nexport { browserSocketFactory } from './realtime/socket.js';\nexport {\n ChatAccessDeniedError,\n isChatAccessDeniedError,\n CHAT_INITIAL_LOAD_FAILURE_BUDGET_MS,\n} from './realtime/shared.js';\nexport { buildFrame, parseFrame } from './realtime/wire.js';\nexport { CHAT_CLIENT_DIAGNOSTIC_EVENTS } from './realtime/diagnostics.js';\nexport type { RealtimeChatClient, RealtimeTransportConfig } from './realtime/transport.js';\nexport type {\n ChatClientDiagnosticsOption,\n ChatClientDiagnosticsSink,\n ChatClientDiagnosticEventName,\n} from './realtime/diagnostics.js';\nexport type { ChannelClientState, ChannelClientConfig } from './realtime/channel-client.js';\nexport type {\n InboxClientState,\n InboxClientConfig,\n ChatMarkReadOutcome,\n} from './realtime/inbox-client.js';\nexport type { UseRealtimeChatMessagesResult } from './realtime/useRealtimeChatMessages.js';\nexport type { GrantProvider, TransportTimers, RealtimeStatus } from './realtime/shared.js';\nexport type { RealtimeSocket, SocketFactory, SocketHandlers } from './realtime/socket.js';\nexport type {\n WireMessageRow,\n WireInboxSnapshot,\n WireRegistryEntry,\n ServerFrame,\n} from './realtime/wire.js';\n\nexport type { ChatShellProps, ChatLoadFailure } from './ui/ChatShell.js';\nexport { ChatShell } from './ui/ChatShell.js';\nexport { MessageList } from './ui/MessageList.js';\nexport type { ChatScrollContainerProps } from './ui/MessageList.js';\nexport type { ComposerProps } from './ui/Composer.js';\nexport { Composer } from './ui/Composer.js';\nexport type { MessageItemDefaultProps } from './ui/MessageItemDefault.js';\nexport { MessageItemDefault } from './ui/MessageItemDefault.js';\nexport { MessageActions, ThreadActions } from './ui/menus.js';\nexport { MessageText, type MessageTextProps } from './ui/MessageText.js';\n"]}
@@ -4,75 +4,29 @@ import type { GrantProvider, TransportTimers, RealtimeStatus } from './shared.js
4
4
  import { type ChatClientDiagnosticsOption } from './diagnostics.js';
5
5
  export interface ChannelClientState {
6
6
  status: RealtimeStatus;
7
- /** Ordered ascending by seq; optimistic messages (no seq yet) sort to the tail. */
8
7
  messages: ChatMessageV1[];
9
- /** uids currently typing (cleared after ~TYPING_COALESCE_MS of silence). */
10
8
  typing: string[];
11
- /** uids the DO reports online (presence). Empty until presence-subscribe. */
12
9
  presence: string[];
13
- /** True while an older-history page request is in flight. */
14
10
  isFetchingOlder: boolean;
15
- /** False once a history page returns fewer than the requested limit (reached the head). */
16
11
  hasOlder: boolean;
17
- /** The last structured error code the DO sent (e.g. 'blocked-word', 'too-long', flood reason). */
18
12
  lastErrorCode: string | null;
19
- /**
20
- * True once the FIRST authoritative chat data has been applied — a non-resync
21
- * resume snapshot, OR the first history page (INCLUDING an empty one). Drives the
22
- * hook's `isInitialLoading` so the shell shows an honest "Opening chat…" state
23
- * while the first data is loading, and a real empty-chat surface only after an
24
- * authoritative empty result. NEVER inferred from `messages.length === 0` (an
25
- * empty first history page IS a successful load). Once true it stays true — a
26
- * later disconnect/reconnect keeps loaded messages mounted, not a re-loader.
27
- * A grant retry / socket open before any snapshot leaves it false.
28
- */
29
13
  hasLoadedInitialData: boolean;
30
- /**
31
- * True once the first open has failed for good enough to say so: attempts have kept
32
- * failing for {@link CHAT_INITIAL_LOAD_FAILURE_BUDGET_MS} without any initial data, or
33
- * reconnecting stopped (gave up, or the socket was revoked) before any. The client keeps
34
- * retrying in the background; the first authoritative data, or a manual `retryNow()`,
35
- * clears it. Never true once initial data has loaded — a later outage keeps the loaded
36
- * messages under the reconnect banner instead.
37
- */
38
14
  initialLoadFailed: boolean;
39
- /**
40
- * True while the channel is CURRENT: the socket is open, initial data has loaded, and the
41
- * authoritative state has been re-applied since this socket opened — a non-resync resume
42
- * snapshot, or the history page answering a resync. Cleared the moment the socket closes,
43
- * reconnects, or is denied, and false again on every new socket until its resume lands, so a
44
- * consumer that must act on the conversation's current terms (an agreement, an offer) waits for
45
- * it. Unlike `hasLoadedInitialData` it is never sticky.
46
- */
47
15
  isSynced: boolean;
48
16
  }
49
17
  export interface ChannelClientConfig {
50
- /** WebSocket endpoint base, e.g. `wss://chat.example.com`. The client appends `/channel`. */
51
18
  endpoint: string;
52
- /** The logical thread id the UI keys on (used as ChatMessageV1.threadId). */
53
19
  threadId: string;
54
- /** The authenticated uid (sender identity for optimistic echoes + auth-switch guard). */
55
20
  currentUserId: string;
56
- /** Mints/refreshes a channel grant. Called on connect and once on a 4401 re-grant. */
57
21
  grantProvider: GrantProvider;
58
22
  socketFactory: SocketFactory;
59
23
  timers?: TransportTimers;
60
- /** Reconnect backoff tuning (forwarded to realtime-core). */
61
24
  reconnect?: {
62
25
  baseDelayMs?: number;
63
26
  maxDelayMs?: number;
64
27
  maxAttempts?: number | null;
65
28
  random?: () => number;
66
29
  };
67
- /**
68
- * OPT-IN structured client diagnostics (default OFF — absent/`false` changes
69
- * nothing and costs one nullish check per decision point). `true` emits one
70
- * `console.debug` line per decision with a stable `chat_client_*` event name
71
- * and a compact, content-free JSON payload; a function routes those entries
72
- * into the app's own logger instead. Never logs message text, captions,
73
- * filenames, URLs, tokens, or previews — ids, seqs, states, counts, and codes
74
- * only. See `./diagnostics.ts`.
75
- */
76
30
  diagnostics?: ChatClientDiagnosticsOption;
77
31
  }
78
32
  export declare class ChannelClient {
@@ -82,280 +36,78 @@ export declare class ChannelClient {
82
36
  private socket;
83
37
  private state;
84
38
  private readonly listeners;
85
- /** Resume cursor: highest seq we've durably applied. Sent on resume after reconnect. */
86
39
  private resumeSeq;
87
- /** Last read-ack the UI requested; re-sent on reconnect so a drop during a closed
88
- * socket window is recovered (M2). */
89
40
  private lastReadAck;
90
- /** True after a `4401` re-grant has been attempted for the CURRENT connect cycle (once-only). */
91
41
  private reauthAttempted;
92
42
  private presenceSubscribed;
93
43
  private heartbeatTimer;
94
- /**
95
- * When the CURRENT socket last proved it was alive: set on open, refreshed by
96
- * every successfully parsed inbound frame (`heartbeat-ack` included — that IS
97
- * the DO's liveness answer). Null whenever no socket is live, so the watchdog
98
- * can never judge a socket that does not exist. See {@link INBOUND_SILENCE_LIMIT_MS}.
99
- */
100
44
  private lastInboundAt;
101
45
  private historyRequests;
102
46
  private typingTimers;
103
47
  private lastTypingSentAt;
104
48
  private reconnectTimer;
105
- /** Single scheduled re-send for the membership-pending window (never stacks). */
106
49
  private membershipRetryTimer;
107
50
  private closedByUs;
108
- /**
109
- * The current lifecycle's number, advanced by every `connect()` and `close()`. A grant
110
- * that resolves, or a timer that fires, for an older lifecycle is dropped — so a
111
- * close → connect while an earlier grant is still minting can never open a second
112
- * socket or touch the new lifecycle's state (FRONTEND-108).
113
- */
114
51
  private lifecycle;
115
- /** Reports the first open as failed once its attempts have failed for the budget. */
116
52
  private readonly initialLoad;
117
- /**
118
- * Per-seq moderation overlay (the max-revision wins). A `revision` frame writes
119
- * here keyed by `messageSeq` so it applies whether it arrives BEFORE its base
120
- * row (re-applied when the row later renders) or AFTER (re-renders the present
121
- * row in place). Merged with the row's own inline `moderationKind` so a row that
122
- * arrives already-moderated from the DO AND one covered by a live frame both
123
- * render the moderated state. */
124
53
  private overlays;
125
- /**
126
- * The ORIGINAL (un-overlaid) mapped message per seq. A `restore` revision must
127
- * revert a previously-redacted message to its original text, so re-renders start
128
- * from this cache rather than the (possibly already-blanked) message in state. */
129
54
  private originalRows;
130
- /**
131
- * Un-acked sends keyed by clientMessageId. A send is added here when its frame is
132
- * written and removed when its `ack` (or the canonical `message` echo carrying the
133
- * same clientMessageId) arrives. On every reconnect resume they are RE-SENT with
134
- * the SAME clientMessageId — the DO dedups (re-acks without re-broadcast), so a
135
- * send that lost its socket before the ack is delivered exactly once. A pending
136
- * send that exceeds the attempt/age cap is flipped to a visible failed state
137
- * (retry affordance) instead of staying a ghost. */
138
55
  private pendingSends;
139
- /**
140
- * The structured diagnostics emitter, or null when diagnostics are off (the
141
- * default). Every call site uses `this.diag?.(...)` so the off path constructs
142
- * no payload at all.
143
- */
144
56
  private readonly diag;
145
- /** Monotonic socket-open attempt counter (diagnostics correlation only). */
146
57
  private connectAttempt;
147
- /** Why the CURRENT connect attempt is happening (diagnostics only). */
148
58
  private reconnectCause;
149
- /** The last close code observed, carried into the next attempt's cause line. */
150
59
  private lastCloseCode;
151
60
  constructor(config: ChannelClientConfig);
152
61
  getState(): ChannelClientState;
153
62
  subscribe(fn: (s: ChannelClientState) => void): () => void;
154
63
  private setState;
155
64
  connect(): Promise<void>;
156
- /**
157
- * Retry now (the first-open failure's Retry). While a reconnect is waiting out its
158
- * backoff, the wait is cancelled and a socket opens at once; a lifecycle that stopped
159
- * (reconnect gave up, or the socket was revoked) starts again; an attempt already in
160
- * flight is left to finish. Either way the first-open failure clears, so the opening state
161
- * shows while the retry runs. The spent budget stays spent: a retry that fails is reported
162
- * again at once, and one that hangs a full budget later.
163
- */
164
65
  retryNow(): void;
165
66
  private startLifecycle;
166
- /** The first open has failed for long enough to say so (see `initialLoadFailed`). */
167
67
  private reportInitialLoadFailed;
168
- /** The first authoritative data is applied; it ends any first-open failure. */
169
68
  private initialDataLoaded;
170
69
  private openSocket;
171
70
  private onOpen;
172
- /**
173
- * On reconnect, re-send each un-acked pending send with the SAME clientMessageId
174
- * (the DO dedups). A send that has exceeded the attempt or age cap is flipped to a
175
- * visible failed state instead of being resent forever — the ghost becomes a
176
- * retryable failure the UI can surface.
177
- */
178
71
  private resendPendingSends;
179
- /**
180
- * Flip an un-acked optimistic row to a visible failed state. The pending entry is
181
- * RETAINED (marked `failed`) so retrySend() can re-queue it with the SAME
182
- * clientMessageId; the row is marked `meta.sendFailed` (plus the correlated
183
- * `sendFailureCode`/`sendRetryable` when a `send-rejected` frame supplied them) so
184
- * the default renderer shows the right reason + retry affordance rather than an
185
- * indistinguishable "sent" bubble. A late ack/echo still reconciles + clears it.
186
- *
187
- * `code`/`retryable` are present for a correlated rejection (terminal) or a
188
- * budget-exhausted server-retryable send; absent for a blind reconnect give-up
189
- * (which keeps the neutral `send-failed` code).
190
- */
191
72
  private failPendingSend;
192
- /** Clear a pending send's per-send retry timer (idempotent). */
193
73
  private clearSendRetryTimer;
194
- /**
195
- * Stop tracking a send (acked / reconciled by an echo / history row): cancel its
196
- * per-send retry timer first so a late reconcile never leaves a stray timer that
197
- * would resend after the send already landed, then drop it from the map.
198
- */
199
74
  private clearPendingSend;
200
- /** One timer for the membership-pending re-send window; a newer frame resets it. */
201
75
  private scheduleMembershipPendingRetry;
202
- /**
203
- * Handle a CORRELATED `send-rejected` frame (the target protocol): parse the
204
- * untrusted payload with the shared schema, correlate to EXACTLY ONE pending send
205
- * by clientMessageId, and either fail just that bubble (terminal) or schedule a
206
- * single per-send resend (retryable).
207
- *
208
- * A frame that fails validation is NOT simply dropped when it still names one of
209
- * OUR pending sends: unlike an unknown frame type, an id-correlated rejection is
210
- * the server explicitly telling us this send did not land — swallowing it would
211
- * strand the bubble on "Sending…" forever (no further frame, no reconnect, no
212
- * budget check ever runs). A future/unknown rejection code (version skew during a
213
- * rollout) therefore fails that send CLOSED with the neutral copy + Retry. A
214
- * malformed frame with no recognizable pending id is dropped (forward-compat
215
- * noise), never surfaced as an error.
216
- */
217
76
  private applySendRejected;
218
- /**
219
- * Schedule EXACTLY ONE per-send resend of a correlated retryable rejection (never
220
- * stacks — a newer rejection clears the prior timer first). When it fires it
221
- * re-sends the SAME clientMessageId (the DO dedups) unless the wall-clock budget
222
- * has elapsed, in which case the send fails with its recorded reason.
223
- */
224
77
  private scheduleSendRetry;
225
- /** Clamp a server `retryAfterMs` hint into a sane resend delay (defaulting when absent). */
226
78
  private clampRetryDelay;
227
- /**
228
- * Flip EVERY un-acked pending send to the visible failed state. Called on the
229
- * terminal close paths (4403 REVOKED, reconnect give-up) — without this, a send
230
- * in flight when the socket dies for good shows "Sending…" until unmount.
231
- */
232
79
  private failAllPendingSends;
233
- /**
234
- * Retry a FAILED (or still-pending) send, RE-USING its original clientMessageId
235
- * so the DO's send-idempotency dedups against any copy that actually landed.
236
- * Resets the attempt/age budget, clears the row's `sendFailed` marker (back to
237
- * the normal pending/optimistic look), and re-sends immediately when the socket
238
- * is open — otherwise the entry rides the next reconnect's automatic resend.
239
- * Returns false when the clientMessageId is not a tracked pending send (already
240
- * acked/reconciled, or never sent from this client).
241
- */
242
80
  retrySend(clientMessageId: string): boolean;
243
81
  private onError;
244
82
  private onClose;
245
83
  private scheduleReconnect;
246
- /**
247
- * Terminal access denial surfaced by the grant provider (ChatAccessDeniedError).
248
- * A reconnect would just re-deny at the next mint, so stop for good — the same
249
- * terminal posture as a 4403 REVOKED close: mark closed-by-us, close the reconnect
250
- * controller, and flip every un-acked pending send to the visible failed state.
251
- * `access-denied` is set AFTER failAllPendingSends so its 'send-failed' code does
252
- * not win as the surfaced error; the React hook maps 'access-denied' to
253
- * `allowed: false` so the shell renders its existing no-access surface.
254
- */
255
84
  private denyAccessTerminally;
256
85
  private onMessage;
257
- /**
258
- * Apply a live moderation `revision` frame: record the overlay keyed by seq
259
- * (max-`messageRevision` wins — a stale/older frame is ignored) and re-render the
260
- * matching message in place if it is already loaded. If the base row hasn't
261
- * arrived yet the overlay is retained and re-applied when the row renders, so a
262
- * frame that races ahead of its message never lets the original content show.
263
- */
264
86
  private applyRevisionFrame;
265
- /**
266
- * The effective overlay for a seq = the max-`messageRevision` between the row's
267
- * own inline overlay (from the DO history/delta merge) and any received `revision`
268
- * frame stored in `this.overlays`. Either source may be ahead of the other.
269
- */
270
87
  private effectiveOverlay;
271
- /** Map a DO row to a UI message with its effective moderation overlay applied. */
272
88
  private renderRow;
273
- /**
274
- * Apply the Contract C resume snapshot. The DO always returns the authoritative
275
- * tail + read cursor and, per the pinned contract, EITHER a contiguous delta
276
- * (`resync:false`) OR a resync directive (`resync:true`).
277
- *
278
- * - `resync === false`: apply the contiguous `delta` (the ≤500-message backlog
279
- * after our cursor, oldest-first) through the same render/merge path as a live
280
- * `message`, then advance `resumeSeq` to `lastMessageSeq`. This heals ANY gap
281
- * within the backlog — including moderation `revision`s the DO baked into the
282
- * delta rows — with no history fetch, so a >50-message reconnect gap is no
283
- * longer a permanent hole.
284
- * - `resync === true`: the gap is beyond the backlog (or we sent no cursor). Drop
285
- * the local tail (messages + overlays + original-row cache) and re-page history
286
- * from the top via a cursorless `requestHistory(null)`.
287
- *
288
- * A pre-contract DO (no `resync`/`delta`) is treated as `resync:true` ⇒ the safe
289
- * re-page path, so a stale worker never strands the client. An empty delta with
290
- * `resync:false` (already caught up) is a no-op beyond advancing the cursor.
291
- */
292
89
  private applyChannelSnapshot;
293
- /**
294
- * Apply one server row (a live `message` frame or a resume-delta row) and record
295
- * the decision the client made about it.
296
- *
297
- * Diagnostics bounding: a LIVE frame always emits one `chat_client_frame_applied`
298
- * line; a DELTA row emits one only when it is materially interesting (carries a
299
- * moderation kind, or reconciles one of our optimistic sends) — a 500-row backlog
300
- * is otherwise summarized by `chat_client_resume_result` rather than spamming 500
301
- * lines.
302
- */
303
90
  private applyMessage;
304
- /**
305
- * The pre-merge view of a row's seq: whether we already hold that seq, and whether
306
- * this row reconciles a live optimistic send.
307
- * Diagnostics-only — never called when diagnostics are off.
308
- */
309
91
  private priorRowFacts;
310
- /**
311
- * Merge a server message: reconcile an optimistic echo by clientMessageId (swap
312
- * the `optimistic:` row for the canonical seq row), or insert/replace by seq.
313
- * Keeps the list sorted ascending by seq with optimistic rows at the tail.
314
- */
315
92
  private mergeMessage;
316
93
  private applyAck;
317
94
  private applyHistoryPage;
318
95
  private applyTyping;
319
96
  private sendFrame;
320
- /**
321
- * Optimistic send: render the echo immediately keyed by clientMessageId, then
322
- * emit the `send` frame. The server `ack`/`message` reconciles on the seq.
323
- */
324
97
  send(args: {
325
98
  clientMessageId: string;
326
99
  text: string;
327
100
  }): boolean;
328
- /**
329
- * Explicit read ack (delivery != read). `focused` => the channel is open + at latest.
330
- * Remembers the cursor (re-sent on reconnect) and returns whether the frame was
331
- * actually sent over an open socket, so the caller only advances its local
332
- * already-acked marker on a real send (M2).
333
- */
334
101
  readAck(readSeq: number, focused: boolean): boolean;
335
- /** Coalesced typing signal: at most one frame per TYPING_COALESCE_MS. */
336
102
  typing(): void;
337
103
  presenceSubscribe(): void;
338
104
  presenceUnsubscribe(): void;
339
- /** Fetch the page of history older than the oldest loaded message (epoch-aware by seq). */
340
105
  fetchOlder(): void;
341
106
  private requestHistory;
342
107
  private startHeartbeat;
343
108
  private stopHeartbeat;
344
- /** True when the live socket has produced no parseable frame for too long. */
345
109
  private isInboundSilent;
346
- /**
347
- * The channel DO auto-acks every heartbeat, so silence past
348
- * {@link INBOUND_SILENCE_LIMIT_MS} means this socket is dead even though no
349
- * `onclose` ever fired (the half-open-connection case that otherwise strands
350
- * the UI on a socket that will never deliver another message).
351
- *
352
- * Close it LOCALLY and deliberately WITHOUT setting `closedByUs`, so the close
353
- * lands on `onClose`'s ordinary transient path — which already owns the
354
- * reconnect scheduling, the backoff, and the resume-cursor recovery. Nothing
355
- * about reconnect policy is duplicated here.
356
- */
357
110
  private closeDeadSocket;
358
- /** Permanently close this client (auth-user switch / unmount). Idempotent. */
359
111
  close(): void;
360
112
  }
361
113
  //# sourceMappingURL=channel-client.d.ts.map