@ttt-productions/chat-react 0.13.17 → 0.13.19

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.
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import type { ChatNameResolution, ChatNameResolver, ChatPrewarmSenders } from '@ttt-productions/chat-core';
2
+ import type { ChatNameReadFailure, ChatNameResolution, ChatNameResolver, ChatPrewarmSenders } from '@ttt-productions/chat-core';
3
3
  export type UnresolvedChatName = Exclude<ChatNameResolution, {
4
4
  status: 'resolved';
5
5
  }>;
@@ -7,11 +7,13 @@ type ChatNameResolverContextValue = {
7
7
  resolveName: ChatNameResolver;
8
8
  prewarm?: ChatPrewarmSenders;
9
9
  renderUnresolvedName?: (resolution: UnresolvedChatName) => React.ReactNode;
10
+ renderHeldNameFailure?: (failure: ChatNameReadFailure) => React.ReactNode;
10
11
  };
11
12
  export type ChatNameResolverProviderProps = {
12
13
  resolveName: ChatNameResolver;
13
14
  prewarm?: ChatPrewarmSenders;
14
15
  renderUnresolvedName?: (resolution: UnresolvedChatName) => React.ReactNode;
16
+ renderHeldNameFailure?: (failure: ChatNameReadFailure) => React.ReactNode;
15
17
  children: React.ReactNode;
16
18
  };
17
19
  export declare function ChatNameResolverProvider(props: ChatNameResolverProviderProps): React.JSX.Element;
@@ -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,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"}
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,mBAAmB,EACnB,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;IAC3E,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,KAAK,CAAC,SAAS,CAAC;CAC3E,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,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1E,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"}
@@ -3,8 +3,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
4
  const ChatNameResolverContext = React.createContext(null);
5
5
  export function ChatNameResolverProvider(props) {
6
- const { resolveName, prewarm, renderUnresolvedName, children } = props;
7
- const value = React.useMemo(() => ({ resolveName, prewarm, renderUnresolvedName }), [resolveName, prewarm, renderUnresolvedName]);
6
+ const { resolveName, prewarm, renderUnresolvedName, renderHeldNameFailure, children } = props;
7
+ const value = React.useMemo(() => ({ resolveName, prewarm, renderUnresolvedName, renderHeldNameFailure }), [resolveName, prewarm, renderUnresolvedName, renderHeldNameFailure]);
8
8
  return (_jsx(ChatNameResolverContext.Provider, { value: value, children: children }));
9
9
  }
10
10
  export function useChatNameResolver() {
@@ -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;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"]}
1
+ {"version":3,"file":"ChatNameResolverContext.js","sourceRoot":"","sources":["../../src/context/ChatNameResolverContext.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAiB/B,MAAM,uBAAuB,GAAG,KAAK,CAAC,aAAa,CAAsC,IAAI,CAAC,CAAC;AAU/F,MAAM,UAAU,wBAAwB,CAAC,KAAoC;IAC3E,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAC9F,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CACzB,GAAG,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,CAAC,EAC7E,CAAC,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,CAAC,CACpE,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 ChatNameReadFailure,\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 renderHeldNameFailure?: (failure: ChatNameReadFailure) => 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 renderHeldNameFailure?: (failure: ChatNameReadFailure) => React.ReactNode;\n children: React.ReactNode;\n};\n\nexport function ChatNameResolverProvider(props: ChatNameResolverProviderProps) {\n const { resolveName, prewarm, renderUnresolvedName, renderHeldNameFailure, children } = props;\n const value = React.useMemo(\n () => ({ resolveName, prewarm, renderUnresolvedName, renderHeldNameFailure }),\n [resolveName, prewarm, renderUnresolvedName, renderHeldNameFailure],\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"]}
package/dist/types.d.ts CHANGED
@@ -12,7 +12,6 @@ export type ChatCoreConfig = {
12
12
  threadId: string;
13
13
  currentUserId: string;
14
14
  currentUserDisplayName?: string;
15
- isAdmin: boolean;
16
15
  transport?: ChatTransportMode;
17
16
  realtime?: ChatRealtimeTransportConfig;
18
17
  allowed: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,UAAU,CAAC;AAEzD,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,mBAAmB,CAAC;IAChC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,kBAAkB,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,CAAC,EAAE,2BAA2B,CAAC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACrF,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,EAAE,aAAa,KAAK,SAAS,CAAC;AAE9D,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,UAAU,CAAC;AAEzD,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,mBAAmB,CAAC;IAChC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,kBAAkB,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,CAAC,EAAE,2BAA2B,CAAC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACrF,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,EAAE,aAAa,KAAK,SAAS,CAAC;AAE9D,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReactNode } from 'react';\nimport type { ChatMessageV1 } from '@ttt-productions/chat-core';\nimport type { ChatConversationRef } from '@ttt-productions/chat-schemas';\n\nexport type ChatTransportMode = 'firestore' | 'realtime';\n\nexport interface ChatRealtimeTransportConfig {\n channelRef: ChatConversationRef;\n client: unknown;\n}\n\nexport type ChatCoreConfig = {\n chatCollectionPath: string | string[];\n messagesSubcollection?: string;\n threadId: string;\n currentUserId: string;\n currentUserDisplayName?: string;\n isAdmin: boolean;\n transport?: ChatTransportMode;\n realtime?: ChatRealtimeTransportConfig;\n allowed: boolean;\n createdAtField?: string;\n pageSize?: number;\n messageMeta?: (doc: Record<string, unknown>) => Record<string, unknown> | undefined;\n};\n\nexport type MessageRenderer = (m: ChatMessageV1) => ReactNode;\n\nexport type MessageRendererRegistry = Record<string, MessageRenderer>;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReactNode } from 'react';\nimport type { ChatMessageV1 } from '@ttt-productions/chat-core';\nimport type { ChatConversationRef } from '@ttt-productions/chat-schemas';\n\nexport type ChatTransportMode = 'firestore' | 'realtime';\n\nexport interface ChatRealtimeTransportConfig {\n channelRef: ChatConversationRef;\n client: unknown;\n}\n\nexport type ChatCoreConfig = {\n chatCollectionPath: string | string[];\n messagesSubcollection?: string;\n threadId: string;\n currentUserId: string;\n currentUserDisplayName?: string;\n transport?: ChatTransportMode;\n realtime?: ChatRealtimeTransportConfig;\n allowed: boolean;\n createdAtField?: string;\n pageSize?: number;\n messageMeta?: (doc: Record<string, unknown>) => Record<string, unknown> | undefined;\n};\n\nexport type MessageRenderer = (m: ChatMessageV1) => ReactNode;\n\nexport type MessageRendererRegistry = Record<string, MessageRenderer>;\n"]}
@@ -137,12 +137,12 @@ function ChatShellView(props) {
137
137
  return _jsx("div", { className: "p-4 text-sm opacity-70", children: "You don't have access to this thread." });
138
138
  }
139
139
  if (isInitialLoading || initialFailure) {
140
- return (_jsxs(Card, { className: cardClassName, children: [(header || handlers) && (_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4 space-y-0", children: [_jsx("div", { children: header }), _jsx("div", { className: "flex flex-col items-end gap-2", children: _jsx(ThreadActions, { threadId: config.threadId, isAdmin: config.isAdmin, handlers: handlers }) })] })), (() => {
140
+ return (_jsxs(Card, { className: cardClassName, children: [(header || handlers) && (_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4 space-y-0", children: [_jsx("div", { children: header }), _jsx("div", { className: "flex flex-col items-end gap-2", children: _jsx(ThreadActions, { threadId: config.threadId, handlers: handlers }) })] })), (() => {
141
141
  const aboveMessages = renderAboveMessages?.() ?? null;
142
142
  return aboveMessages != null && aboveMessages !== false ? (_jsx("div", { className: "border-b", children: aboveMessages })) : null;
143
143
  })(), _jsx(CardContent, { className: contentClassName, children: initialFailure ? (renderLoadError(initialFailure)) : (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-12 text-sm text-muted-foreground", role: "status", "aria-live": "polite", children: [_jsx(Spinner, { size: "sm" }), _jsx("span", { children: "Opening chat\u2026" })] })) }), renderBelowMessages && _jsx("div", { className: "border-t", children: renderBelowMessages() }), renderFooter ? (_jsx(CardFooter, { className: "border-t", children: renderFooter() })) : (_jsx(CardFooter, { className: "border-t", children: _jsx(Skeleton, { className: "h-10 w-full" }) }))] }));
144
144
  }
145
- return (_jsxs(Card, { className: cardClassName, children: [(header || handlers) && (_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4 space-y-0", children: [_jsx("div", { children: header }), _jsx("div", { className: "flex flex-col items-end gap-2", children: _jsx(ThreadActions, { threadId: config.threadId, isAdmin: config.isAdmin, handlers: handlers }) })] })), _jsx(ChatConnectionBanner, { status: status }), renderAboveMessages && _jsx("div", { className: "border-b", children: renderAboveMessages() }), loadFailure?.scope === 'live' && (_jsx("div", { className: "border-b", children: renderLoadError(loadFailure) })), _jsx(CardContent, { className: contentClassName, children: _jsx(MessageList, { messages: messages, currentUserId: config.currentUserId, isAdmin: config.isAdmin, isFetchingOlder: isFetchingOlder, hasOlder: hasOlder, onLoadOlder: () => fetchOlder(), renderMessage: renderMessage, messageRenderers: messageRenderers, showScrollToBottom: showScrollToBottom, onScrollToBottom: () => setShowScrollToBottom(false), onAtBottomChange: setAtBottom, fillHeight: fillHeight, scrollClassName: scrollClassName, scrollContainer: scrollContainer, handlers: handlers, onSenderClick: onSenderClick, messageGroupKey: messageGroupKey, renderSenderAdornment: renderSenderAdornment, onRetrySend: retrySend, olderLoadErrorRow: olderFailure ? renderLoadError(olderFailure) : null }) }), renderBelowMessages && _jsx("div", { className: "border-t", children: renderBelowMessages() }), renderFooter ? (_jsx(CardFooter, { className: "border-t", children: renderFooter() })) : (_jsxs(CardFooter, { className: "border-t flex-col items-stretch gap-1", children: [_jsx(TypingIndicator, { typing: typing }), _jsx(KeyboardAvoidingView, { padding: true, offset: 8, className: "w-full", children: _jsx(Composer, { onSend: send, onTyping: signalTyping, disabled: composerDisabled, autoFocus: autoFocus, placeholder: composerPlaceholder }) })] }))] }));
145
+ return (_jsxs(Card, { className: cardClassName, children: [(header || handlers) && (_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4 space-y-0", children: [_jsx("div", { children: header }), _jsx("div", { className: "flex flex-col items-end gap-2", children: _jsx(ThreadActions, { threadId: config.threadId, handlers: handlers }) })] })), _jsx(ChatConnectionBanner, { status: status }), renderAboveMessages && _jsx("div", { className: "border-b", children: renderAboveMessages() }), loadFailure?.scope === 'live' && (_jsx("div", { className: "border-b", children: renderLoadError(loadFailure) })), _jsx(CardContent, { className: contentClassName, children: _jsx(MessageList, { messages: messages, currentUserId: config.currentUserId, isFetchingOlder: isFetchingOlder, hasOlder: hasOlder, onLoadOlder: () => fetchOlder(), renderMessage: renderMessage, messageRenderers: messageRenderers, showScrollToBottom: showScrollToBottom, onScrollToBottom: () => setShowScrollToBottom(false), onAtBottomChange: setAtBottom, fillHeight: fillHeight, scrollClassName: scrollClassName, scrollContainer: scrollContainer, handlers: handlers, onSenderClick: onSenderClick, messageGroupKey: messageGroupKey, renderSenderAdornment: renderSenderAdornment, onRetrySend: retrySend, olderLoadErrorRow: olderFailure ? renderLoadError(olderFailure) : null }) }), renderBelowMessages && _jsx("div", { className: "border-t", children: renderBelowMessages() }), renderFooter ? (_jsx(CardFooter, { className: "border-t", children: renderFooter() })) : (_jsxs(CardFooter, { className: "border-t flex-col items-stretch gap-1", children: [_jsx(TypingIndicator, { typing: typing }), _jsx(KeyboardAvoidingView, { padding: true, offset: 8, className: "w-full", children: _jsx(Composer, { onSend: send, onTyping: signalTyping, disabled: composerDisabled, autoFocus: autoFocus, placeholder: composerPlaceholder }) })] }))] }));
146
146
  }
147
147
  function ChatConnectionBanner({ status }) {
148
148
  if (status !== 'reconnecting' && status !== 'closed')
@@ -1 +1 @@
1
- {"version":3,"file":"ChatShell.js","sourceRoot":"","sources":["../../src/ui/ChatShell.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatMessageGroupKey,\n ChatMessageV1,\n ModerationHandlers,\n} from '@ttt-productions/chat-core';\nimport type { ChatCoreConfig, MessageRendererRegistry } from '../types.js';\nimport {\n Card,\n CardHeader,\n CardContent,\n CardFooter,\n Skeleton,\n Spinner,\n} from '@ttt-productions/ui-core/react';\nimport { KeyboardAvoidingView } from '@ttt-productions/mobile-core/react';\nimport { useChatMessages } from '../hooks/useChatMessages.js';\nimport { useRealtimeChatMessages } from '../realtime/useRealtimeChatMessages.js';\nimport type { RealtimeChatClient } from '../realtime/transport.js';\nimport { MessageList, type ChatScrollContainerProps } from './MessageList.js';\nimport { Composer } from './Composer.js';\nimport { ThreadActions } from './menus.js';\n\nexport type ChatLoadFailure = {\n scope: 'initial' | 'live' | 'older';\n error: Error | null;\n retry: () => void;\n retrying: boolean;\n};\n\nexport type ChatShellProps = {\n config: ChatCoreConfig;\n\n renderLoadError: (failure: ChatLoadFailure) => React.ReactNode;\n\n header?: React.ReactNode;\n\n onSend?: (text: string) => Promise<void>;\n\n renderMessage?: (m: any) => React.ReactNode;\n messageRenderers?: MessageRendererRegistry;\n handlers?: ModerationHandlers;\n\n composerPlaceholder?: string;\n autoFocus?: boolean;\n\n renderAboveMessages?: () => React.ReactNode;\n renderBelowMessages?: () => React.ReactNode;\n renderFooter?: () => React.ReactNode;\n\n onSenderClick?: (senderId: string) => void;\n messageGroupKey?: ChatMessageGroupKey;\n renderSenderAdornment?: (m: ChatMessageV1) => React.ReactNode;\n\n composerDisabled?: boolean;\n\n fillHeight?: boolean;\n onReadinessChange?: (ready: boolean) => void;\n scrollClassName?: string;\n scrollContainer?: React.ComponentType<ChatScrollContainerProps>;\n};\n\ntype ResolvedChat = {\n allowed: boolean;\n isInitialLoading: boolean;\n transportCurrent: boolean;\n messages: ChatMessageV1[];\n fetchOlder: () => Promise<void>;\n hasOlder: boolean;\n isFetchingOlder: boolean;\n loadFailure: ChatLoadFailure | null;\n olderFailure: ChatLoadFailure | null;\n send: (text: string) => Promise<void>;\n retrySend?: (clientMessageId: string) => void;\n readAck?: (latestSeq: number, focused: boolean) => boolean;\n status?: string;\n typing?: string[];\n signalTyping?: () => void;\n};\n\n// Hooks must be unconditional, so each transport is a separate component with one data hook.\nexport function ChatShell(props: ChatShellProps) {\n if (props.config.transport === 'realtime') {\n return <RealtimeChatShell {...props} />;\n }\n return <FirestoreChatShell {...props} />;\n}\n\nfunction FirestoreChatShell(props: ChatShellProps) {\n const { config, onSend } = props;\n const r = useChatMessages(config);\n const send = React.useCallback(\n async (text: string) => {\n if (onSend) await onSend(text);\n },\n [onSend],\n );\n const { error, olderError, retry, messages, isFetchingOlder } = r;\n const loadFailure = React.useMemo<ChatLoadFailure | null>(\n () =>\n error\n ? { scope: messages.length > 0 ? 'live' : 'initial', error, retry, retrying: false }\n : null,\n [error, messages.length, retry],\n );\n const olderFailure = React.useMemo<ChatLoadFailure | null>(\n () =>\n olderError ? { scope: 'older', error: olderError, retry, retrying: isFetchingOlder } : null,\n [olderError, retry, isFetchingOlder],\n );\n return (\n <ChatShellView\n {...props}\n resolved={{\n allowed: r.allowed,\n isInitialLoading: r.isInitialLoading,\n transportCurrent: true,\n messages: r.messages,\n fetchOlder: r.fetchOlder,\n hasOlder: r.hasOlder,\n isFetchingOlder: r.isFetchingOlder,\n loadFailure,\n olderFailure,\n send,\n }}\n />\n );\n}\n\nfunction RealtimeChatShell(props: ChatShellProps) {\n const { config, onSend } = props;\n const client = config.realtime?.client as RealtimeChatClient | undefined;\n if (!client) {\n throw new Error(\n \"[ChatShell] transport 'realtime' requires config.realtime.client (createRealtimeChatClient(...))\",\n );\n }\n const r = useRealtimeChatMessages(client);\n const { initialLoadFailed, retry } = r;\n const loadFailure = React.useMemo<ChatLoadFailure | null>(\n () => (initialLoadFailed ? { scope: 'initial', error: null, retry, retrying: false } : null),\n [initialLoadFailed, retry],\n );\n const send = React.useCallback(\n async (text: string) => {\n const ok = r.send(text);\n if (!ok) throw new Error('chat-send-failed');\n if (onSend) await onSend(text);\n },\n [r, onSend],\n );\n return (\n <ChatShellView\n {...props}\n resolved={{\n allowed: config.allowed && r.allowed,\n isInitialLoading: r.isInitialLoading,\n transportCurrent: r.isSynced,\n messages: r.messages,\n fetchOlder: r.fetchOlder,\n hasOlder: r.hasOlder,\n isFetchingOlder: r.isFetchingOlder,\n loadFailure,\n olderFailure: null,\n send,\n retrySend: r.retrySend,\n readAck: r.readAck,\n status: r.status,\n typing: r.typing,\n signalTyping: r.signalTyping,\n }}\n />\n );\n}\n\nfunction ChatShellView(props: ChatShellProps & { resolved: ResolvedChat }) {\n const {\n config,\n header,\n renderMessage,\n messageRenderers,\n handlers,\n composerPlaceholder,\n autoFocus = false,\n renderAboveMessages,\n renderBelowMessages,\n renderFooter,\n onSenderClick,\n messageGroupKey,\n renderSenderAdornment,\n composerDisabled,\n fillHeight = false,\n scrollClassName,\n scrollContainer,\n renderLoadError,\n onReadinessChange,\n resolved,\n } = props;\n\n const {\n allowed,\n isInitialLoading,\n messages,\n fetchOlder,\n hasOlder,\n isFetchingOlder,\n loadFailure,\n olderFailure,\n send,\n retrySend,\n readAck,\n status,\n typing,\n signalTyping,\n } = resolved;\n const initialFailure = loadFailure?.scope === 'initial' ? loadFailure : null;\n\n const ready = allowed && !isInitialLoading && loadFailure === null && resolved.transportCurrent;\n const readinessListener = React.useRef(onReadinessChange);\n React.useEffect(() => {\n readinessListener.current = onReadinessChange;\n });\n React.useEffect(() => {\n readinessListener.current?.(ready);\n }, [ready]);\n React.useEffect(() => () => readinessListener.current?.(false), []);\n\n const [showScrollToBottom, setShowScrollToBottom] = React.useState(false);\n const [atBottom, setAtBottom] = React.useState(true);\n const [focused, setFocused] = React.useState(true);\n const lastAckedRef = React.useRef(0);\n const leaseHeldRef = React.useRef(false);\n\n const latestSeq = React.useMemo(() => {\n let max = 0;\n for (const m of messages) {\n const seq = typeof m.meta?.seq === 'number' ? (m.meta?.seq as number) : 0;\n if (seq > max) max = seq;\n }\n return max;\n }, [messages]);\n\n React.useEffect(() => {\n const compute = () =>\n typeof document === 'undefined' ||\n (document.visibilityState !== 'hidden' &&\n (typeof document.hasFocus !== 'function' || document.hasFocus()));\n setFocused(compute());\n const onChange = () => setFocused(compute());\n window.addEventListener('focus', onChange);\n window.addEventListener('blur', onChange);\n document.addEventListener('visibilitychange', onChange);\n return () => {\n window.removeEventListener('focus', onChange);\n window.removeEventListener('blur', onChange);\n document.removeEventListener('visibilitychange', onChange);\n };\n }, []);\n\n React.useEffect(() => {\n if (!readAck || latestSeq <= 0) return;\n if (focused && atBottom) {\n leaseHeldRef.current = true;\n if (latestSeq > lastAckedRef.current) {\n if (readAck(latestSeq, true)) lastAckedRef.current = latestSeq;\n }\n } else if (leaseHeldRef.current) {\n leaseHeldRef.current = false;\n readAck(latestSeq, false);\n }\n }, [readAck, latestSeq, atBottom, focused]);\n\n const cardClassName = fillHeight ? 'w-full flex flex-col h-full min-h-0' : 'w-full';\n const contentClassName = fillHeight ? 'p-0 flex-1 min-h-0 flex flex-col' : 'p-0';\n\n if (!allowed) {\n return <div className=\"p-4 text-sm opacity-70\">You don&apos;t have access to this thread.</div>;\n }\n\n if (isInitialLoading || initialFailure) {\n return (\n <Card className={cardClassName}>\n {(header || handlers) && (\n <CardHeader className=\"flex flex-row items-start justify-between gap-4 space-y-0\">\n <div>{header}</div>\n <div className=\"flex flex-col items-end gap-2\">\n <ThreadActions\n threadId={config.threadId}\n isAdmin={config.isAdmin}\n handlers={handlers}\n />\n </div>\n </CardHeader>\n )}\n {(() => {\n const aboveMessages = renderAboveMessages?.() ?? null;\n return aboveMessages != null && aboveMessages !== false ? (\n <div className=\"border-b\">{aboveMessages}</div>\n ) : null;\n })()}\n <CardContent className={contentClassName}>\n {initialFailure ? (\n renderLoadError(initialFailure)\n ) : (\n <div\n className=\"flex flex-col items-center justify-center gap-2 py-12 text-sm text-muted-foreground\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Spinner size=\"sm\" />\n <span>Opening chat…</span>\n </div>\n )}\n </CardContent>\n {renderBelowMessages && <div className=\"border-t\">{renderBelowMessages()}</div>}\n {renderFooter ? (\n <CardFooter className=\"border-t\">{renderFooter()}</CardFooter>\n ) : (\n <CardFooter className=\"border-t\">\n <Skeleton className=\"h-10 w-full\" />\n </CardFooter>\n )}\n </Card>\n );\n }\n\n return (\n <Card className={cardClassName}>\n {(header || handlers) && (\n <CardHeader className=\"flex flex-row items-start justify-between gap-4 space-y-0\">\n <div>{header}</div>\n <div className=\"flex flex-col items-end gap-2\">\n <ThreadActions\n threadId={config.threadId}\n isAdmin={config.isAdmin}\n handlers={handlers}\n />\n </div>\n </CardHeader>\n )}\n\n <ChatConnectionBanner status={status} />\n\n {renderAboveMessages && <div className=\"border-b\">{renderAboveMessages()}</div>}\n\n {loadFailure?.scope === 'live' && (\n <div className=\"border-b\">{renderLoadError(loadFailure)}</div>\n )}\n\n <CardContent className={contentClassName}>\n <MessageList\n messages={messages}\n currentUserId={config.currentUserId}\n isAdmin={config.isAdmin}\n isFetchingOlder={isFetchingOlder}\n hasOlder={hasOlder}\n onLoadOlder={() => fetchOlder()}\n renderMessage={renderMessage}\n messageRenderers={messageRenderers}\n showScrollToBottom={showScrollToBottom}\n onScrollToBottom={() => setShowScrollToBottom(false)}\n onAtBottomChange={setAtBottom}\n fillHeight={fillHeight}\n scrollClassName={scrollClassName}\n scrollContainer={scrollContainer}\n handlers={handlers}\n onSenderClick={onSenderClick}\n messageGroupKey={messageGroupKey}\n renderSenderAdornment={renderSenderAdornment}\n onRetrySend={retrySend}\n olderLoadErrorRow={olderFailure ? renderLoadError(olderFailure) : null}\n />\n </CardContent>\n\n {renderBelowMessages && <div className=\"border-t\">{renderBelowMessages()}</div>}\n\n {renderFooter ? (\n <CardFooter className=\"border-t\">{renderFooter()}</CardFooter>\n ) : (\n <CardFooter className=\"border-t flex-col items-stretch gap-1\">\n <TypingIndicator typing={typing} />\n <KeyboardAvoidingView padding offset={8} className=\"w-full\">\n <Composer\n onSend={send}\n onTyping={signalTyping}\n disabled={composerDisabled}\n autoFocus={autoFocus}\n placeholder={composerPlaceholder}\n />\n </KeyboardAvoidingView>\n </CardFooter>\n )}\n </Card>\n );\n}\n\nfunction ChatConnectionBanner({ status }: { status?: string }) {\n if (status !== 'reconnecting' && status !== 'closed') return null;\n return (\n <div\n className=\"px-3 py-1.5 text-xs text-center bg-muted text-muted-foreground border-b\"\n role=\"status\"\n aria-live=\"polite\"\n >\n {status === 'reconnecting'\n ? 'Reconnecting… messages may be delayed.'\n : 'Disconnected — messages may be out of date.'}\n </div>\n );\n}\n\nfunction TypingIndicator({ typing }: { typing?: string[] }) {\n if (!typing || typing.length === 0) return null;\n return (\n <div className=\"text-xs italic text-muted-foreground\" aria-live=\"polite\">\n {typing.length === 1 ? 'Someone is typing…' : 'Several people are typing…'}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"ChatShell.js","sourceRoot":"","sources":["../../src/ui/ChatShell.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatMessageGroupKey,\n ChatMessageV1,\n ModerationHandlers,\n} from '@ttt-productions/chat-core';\nimport type { ChatCoreConfig, MessageRendererRegistry } from '../types.js';\nimport {\n Card,\n CardHeader,\n CardContent,\n CardFooter,\n Skeleton,\n Spinner,\n} from '@ttt-productions/ui-core/react';\nimport { KeyboardAvoidingView } from '@ttt-productions/mobile-core/react';\nimport { useChatMessages } from '../hooks/useChatMessages.js';\nimport { useRealtimeChatMessages } from '../realtime/useRealtimeChatMessages.js';\nimport type { RealtimeChatClient } from '../realtime/transport.js';\nimport { MessageList, type ChatScrollContainerProps } from './MessageList.js';\nimport { Composer } from './Composer.js';\nimport { ThreadActions } from './menus.js';\n\nexport type ChatLoadFailure = {\n scope: 'initial' | 'live' | 'older';\n error: Error | null;\n retry: () => void;\n retrying: boolean;\n};\n\nexport type ChatShellProps = {\n config: ChatCoreConfig;\n\n renderLoadError: (failure: ChatLoadFailure) => React.ReactNode;\n\n header?: React.ReactNode;\n\n onSend?: (text: string) => Promise<void>;\n\n renderMessage?: (m: any) => React.ReactNode;\n messageRenderers?: MessageRendererRegistry;\n handlers?: ModerationHandlers;\n\n composerPlaceholder?: string;\n autoFocus?: boolean;\n\n renderAboveMessages?: () => React.ReactNode;\n renderBelowMessages?: () => React.ReactNode;\n renderFooter?: () => React.ReactNode;\n\n onSenderClick?: (senderId: string) => void;\n messageGroupKey?: ChatMessageGroupKey;\n renderSenderAdornment?: (m: ChatMessageV1) => React.ReactNode;\n\n composerDisabled?: boolean;\n\n fillHeight?: boolean;\n onReadinessChange?: (ready: boolean) => void;\n scrollClassName?: string;\n scrollContainer?: React.ComponentType<ChatScrollContainerProps>;\n};\n\ntype ResolvedChat = {\n allowed: boolean;\n isInitialLoading: boolean;\n transportCurrent: boolean;\n messages: ChatMessageV1[];\n fetchOlder: () => Promise<void>;\n hasOlder: boolean;\n isFetchingOlder: boolean;\n loadFailure: ChatLoadFailure | null;\n olderFailure: ChatLoadFailure | null;\n send: (text: string) => Promise<void>;\n retrySend?: (clientMessageId: string) => void;\n readAck?: (latestSeq: number, focused: boolean) => boolean;\n status?: string;\n typing?: string[];\n signalTyping?: () => void;\n};\n\n// Hooks must be unconditional, so each transport is a separate component with one data hook.\nexport function ChatShell(props: ChatShellProps) {\n if (props.config.transport === 'realtime') {\n return <RealtimeChatShell {...props} />;\n }\n return <FirestoreChatShell {...props} />;\n}\n\nfunction FirestoreChatShell(props: ChatShellProps) {\n const { config, onSend } = props;\n const r = useChatMessages(config);\n const send = React.useCallback(\n async (text: string) => {\n if (onSend) await onSend(text);\n },\n [onSend],\n );\n const { error, olderError, retry, messages, isFetchingOlder } = r;\n const loadFailure = React.useMemo<ChatLoadFailure | null>(\n () =>\n error\n ? { scope: messages.length > 0 ? 'live' : 'initial', error, retry, retrying: false }\n : null,\n [error, messages.length, retry],\n );\n const olderFailure = React.useMemo<ChatLoadFailure | null>(\n () =>\n olderError ? { scope: 'older', error: olderError, retry, retrying: isFetchingOlder } : null,\n [olderError, retry, isFetchingOlder],\n );\n return (\n <ChatShellView\n {...props}\n resolved={{\n allowed: r.allowed,\n isInitialLoading: r.isInitialLoading,\n transportCurrent: true,\n messages: r.messages,\n fetchOlder: r.fetchOlder,\n hasOlder: r.hasOlder,\n isFetchingOlder: r.isFetchingOlder,\n loadFailure,\n olderFailure,\n send,\n }}\n />\n );\n}\n\nfunction RealtimeChatShell(props: ChatShellProps) {\n const { config, onSend } = props;\n const client = config.realtime?.client as RealtimeChatClient | undefined;\n if (!client) {\n throw new Error(\n \"[ChatShell] transport 'realtime' requires config.realtime.client (createRealtimeChatClient(...))\",\n );\n }\n const r = useRealtimeChatMessages(client);\n const { initialLoadFailed, retry } = r;\n const loadFailure = React.useMemo<ChatLoadFailure | null>(\n () => (initialLoadFailed ? { scope: 'initial', error: null, retry, retrying: false } : null),\n [initialLoadFailed, retry],\n );\n const send = React.useCallback(\n async (text: string) => {\n const ok = r.send(text);\n if (!ok) throw new Error('chat-send-failed');\n if (onSend) await onSend(text);\n },\n [r, onSend],\n );\n return (\n <ChatShellView\n {...props}\n resolved={{\n allowed: config.allowed && r.allowed,\n isInitialLoading: r.isInitialLoading,\n transportCurrent: r.isSynced,\n messages: r.messages,\n fetchOlder: r.fetchOlder,\n hasOlder: r.hasOlder,\n isFetchingOlder: r.isFetchingOlder,\n loadFailure,\n olderFailure: null,\n send,\n retrySend: r.retrySend,\n readAck: r.readAck,\n status: r.status,\n typing: r.typing,\n signalTyping: r.signalTyping,\n }}\n />\n );\n}\n\nfunction ChatShellView(props: ChatShellProps & { resolved: ResolvedChat }) {\n const {\n config,\n header,\n renderMessage,\n messageRenderers,\n handlers,\n composerPlaceholder,\n autoFocus = false,\n renderAboveMessages,\n renderBelowMessages,\n renderFooter,\n onSenderClick,\n messageGroupKey,\n renderSenderAdornment,\n composerDisabled,\n fillHeight = false,\n scrollClassName,\n scrollContainer,\n renderLoadError,\n onReadinessChange,\n resolved,\n } = props;\n\n const {\n allowed,\n isInitialLoading,\n messages,\n fetchOlder,\n hasOlder,\n isFetchingOlder,\n loadFailure,\n olderFailure,\n send,\n retrySend,\n readAck,\n status,\n typing,\n signalTyping,\n } = resolved;\n const initialFailure = loadFailure?.scope === 'initial' ? loadFailure : null;\n\n const ready = allowed && !isInitialLoading && loadFailure === null && resolved.transportCurrent;\n const readinessListener = React.useRef(onReadinessChange);\n React.useEffect(() => {\n readinessListener.current = onReadinessChange;\n });\n React.useEffect(() => {\n readinessListener.current?.(ready);\n }, [ready]);\n React.useEffect(() => () => readinessListener.current?.(false), []);\n\n const [showScrollToBottom, setShowScrollToBottom] = React.useState(false);\n const [atBottom, setAtBottom] = React.useState(true);\n const [focused, setFocused] = React.useState(true);\n const lastAckedRef = React.useRef(0);\n const leaseHeldRef = React.useRef(false);\n\n const latestSeq = React.useMemo(() => {\n let max = 0;\n for (const m of messages) {\n const seq = typeof m.meta?.seq === 'number' ? (m.meta?.seq as number) : 0;\n if (seq > max) max = seq;\n }\n return max;\n }, [messages]);\n\n React.useEffect(() => {\n const compute = () =>\n typeof document === 'undefined' ||\n (document.visibilityState !== 'hidden' &&\n (typeof document.hasFocus !== 'function' || document.hasFocus()));\n setFocused(compute());\n const onChange = () => setFocused(compute());\n window.addEventListener('focus', onChange);\n window.addEventListener('blur', onChange);\n document.addEventListener('visibilitychange', onChange);\n return () => {\n window.removeEventListener('focus', onChange);\n window.removeEventListener('blur', onChange);\n document.removeEventListener('visibilitychange', onChange);\n };\n }, []);\n\n React.useEffect(() => {\n if (!readAck || latestSeq <= 0) return;\n if (focused && atBottom) {\n leaseHeldRef.current = true;\n if (latestSeq > lastAckedRef.current) {\n if (readAck(latestSeq, true)) lastAckedRef.current = latestSeq;\n }\n } else if (leaseHeldRef.current) {\n leaseHeldRef.current = false;\n readAck(latestSeq, false);\n }\n }, [readAck, latestSeq, atBottom, focused]);\n\n const cardClassName = fillHeight ? 'w-full flex flex-col h-full min-h-0' : 'w-full';\n const contentClassName = fillHeight ? 'p-0 flex-1 min-h-0 flex flex-col' : 'p-0';\n\n if (!allowed) {\n return <div className=\"p-4 text-sm opacity-70\">You don&apos;t have access to this thread.</div>;\n }\n\n if (isInitialLoading || initialFailure) {\n return (\n <Card className={cardClassName}>\n {(header || handlers) && (\n <CardHeader className=\"flex flex-row items-start justify-between gap-4 space-y-0\">\n <div>{header}</div>\n <div className=\"flex flex-col items-end gap-2\">\n <ThreadActions threadId={config.threadId} handlers={handlers} />\n </div>\n </CardHeader>\n )}\n {(() => {\n const aboveMessages = renderAboveMessages?.() ?? null;\n return aboveMessages != null && aboveMessages !== false ? (\n <div className=\"border-b\">{aboveMessages}</div>\n ) : null;\n })()}\n <CardContent className={contentClassName}>\n {initialFailure ? (\n renderLoadError(initialFailure)\n ) : (\n <div\n className=\"flex flex-col items-center justify-center gap-2 py-12 text-sm text-muted-foreground\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <Spinner size=\"sm\" />\n <span>Opening chat…</span>\n </div>\n )}\n </CardContent>\n {renderBelowMessages && <div className=\"border-t\">{renderBelowMessages()}</div>}\n {renderFooter ? (\n <CardFooter className=\"border-t\">{renderFooter()}</CardFooter>\n ) : (\n <CardFooter className=\"border-t\">\n <Skeleton className=\"h-10 w-full\" />\n </CardFooter>\n )}\n </Card>\n );\n }\n\n return (\n <Card className={cardClassName}>\n {(header || handlers) && (\n <CardHeader className=\"flex flex-row items-start justify-between gap-4 space-y-0\">\n <div>{header}</div>\n <div className=\"flex flex-col items-end gap-2\">\n <ThreadActions threadId={config.threadId} handlers={handlers} />\n </div>\n </CardHeader>\n )}\n\n <ChatConnectionBanner status={status} />\n\n {renderAboveMessages && <div className=\"border-b\">{renderAboveMessages()}</div>}\n\n {loadFailure?.scope === 'live' && (\n <div className=\"border-b\">{renderLoadError(loadFailure)}</div>\n )}\n\n <CardContent className={contentClassName}>\n <MessageList\n messages={messages}\n currentUserId={config.currentUserId}\n isFetchingOlder={isFetchingOlder}\n hasOlder={hasOlder}\n onLoadOlder={() => fetchOlder()}\n renderMessage={renderMessage}\n messageRenderers={messageRenderers}\n showScrollToBottom={showScrollToBottom}\n onScrollToBottom={() => setShowScrollToBottom(false)}\n onAtBottomChange={setAtBottom}\n fillHeight={fillHeight}\n scrollClassName={scrollClassName}\n scrollContainer={scrollContainer}\n handlers={handlers}\n onSenderClick={onSenderClick}\n messageGroupKey={messageGroupKey}\n renderSenderAdornment={renderSenderAdornment}\n onRetrySend={retrySend}\n olderLoadErrorRow={olderFailure ? renderLoadError(olderFailure) : null}\n />\n </CardContent>\n\n {renderBelowMessages && <div className=\"border-t\">{renderBelowMessages()}</div>}\n\n {renderFooter ? (\n <CardFooter className=\"border-t\">{renderFooter()}</CardFooter>\n ) : (\n <CardFooter className=\"border-t flex-col items-stretch gap-1\">\n <TypingIndicator typing={typing} />\n <KeyboardAvoidingView padding offset={8} className=\"w-full\">\n <Composer\n onSend={send}\n onTyping={signalTyping}\n disabled={composerDisabled}\n autoFocus={autoFocus}\n placeholder={composerPlaceholder}\n />\n </KeyboardAvoidingView>\n </CardFooter>\n )}\n </Card>\n );\n}\n\nfunction ChatConnectionBanner({ status }: { status?: string }) {\n if (status !== 'reconnecting' && status !== 'closed') return null;\n return (\n <div\n className=\"px-3 py-1.5 text-xs text-center bg-muted text-muted-foreground border-b\"\n role=\"status\"\n aria-live=\"polite\"\n >\n {status === 'reconnecting'\n ? 'Reconnecting… messages may be delayed.'\n : 'Disconnected — messages may be out of date.'}\n </div>\n );\n}\n\nfunction TypingIndicator({ typing }: { typing?: string[] }) {\n if (!typing || typing.length === 0) return null;\n return (\n <div className=\"text-xs italic text-muted-foreground\" aria-live=\"polite\">\n {typing.length === 1 ? 'Someone is typing…' : 'Several people are typing…'}\n </div>\n );\n}\n"]}
@@ -3,7 +3,6 @@ import type { ChatMessageV1, ModerationHandlers } from '@ttt-productions/chat-co
3
3
  export type MessageItemDefaultProps = {
4
4
  m: ChatMessageV1;
5
5
  currentUserId: string;
6
- isAdmin: boolean;
7
6
  handlers?: ModerationHandlers;
8
7
  isContinuation?: boolean;
9
8
  onSenderClick?: (senderId: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageItemDefault.d.ts","sourceRoot":"","sources":["../../src/ui/MessageItemDefault.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAsCpF,MAAM,MAAM,uBAAuB,GAAG;IACpC,CAAC,EAAE,aAAa,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAE9B,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,+BA2GhE"}
1
+ {"version":3,"file":"MessageItemDefault.d.ts","sourceRoot":"","sources":["../../src/ui/MessageItemDefault.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAsCpF,MAAM,MAAM,uBAAuB,GAAG;IACpC,CAAC,EAAE,aAAa,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAE9B,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,+BA0GhE"}
@@ -26,7 +26,7 @@ function SystemMessage({ m }) {
26
26
  return (_jsxs("div", { className: "chat-system-message", children: [m.text && _jsx("span", { children: m.text }), _jsx("span", { className: "chat-system-message-time", children: new Date(m.createdAt).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) })] }));
27
27
  }
28
28
  export function MessageItemDefault(props) {
29
- const { m, currentUserId, isAdmin, handlers, isContinuation, onSenderClick: senderClick, senderAdornment, onRetrySend, } = props;
29
+ const { m, currentUserId, handlers, isContinuation, onSenderClick: senderClick, senderAdornment, onRetrySend, } = props;
30
30
  const nameResolved = useSenderNameResolution(m.senderId).status === 'resolved';
31
31
  const onSenderClick = nameResolved ? senderClick : undefined;
32
32
  if (m.isSystemMessage) {
@@ -43,6 +43,6 @@ export function MessageItemDefault(props) {
43
43
  if (e.key === 'Enter' || e.key === ' ')
44
44
  onSenderClick(m.senderId);
45
45
  }
46
- : undefined, children: _jsx(SenderName, { senderId: m.senderId }) }), senderAdornment, _jsx("span", { children: "\u00B7" }), _jsx("span", { children: new Date(m.createdAt).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) })] })), m.text && (_jsx("p", { className: "text-sm whitespace-pre-wrap", children: _jsx(MessageText, { text: m.text }) }))] }), sendPending && (_jsxs("div", { className: "chat-send-status mt-0.5", role: "status", "aria-live": "polite", children: [_jsx(Spinner, { size: "xs", className: "shrink-0" }), _jsx("span", { children: "Sending\u2026" })] })), sendFailed && (_jsxs("div", { className: "chat-send-status chat-send-status--failed mt-0.5", role: "alert", children: [_jsx(AlertTriangle, { className: "h-3 w-3 shrink-0" }), _jsx("span", { children: sendFailureCopy(sendFailureCode) }), showRetry && onRetrySend && clientMessageId && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-5 px-1.5 text-[0.6875rem]", onClick: () => onRetrySend(clientMessageId), children: "Retry" }))] })), !isContinuation && (_jsx("div", { className: "mt-1", children: _jsx(MessageActions, { messageId: m.messageId, isAdmin: isAdmin, handlers: handlers }) }))] }));
46
+ : undefined, children: _jsx(SenderName, { senderId: m.senderId }) }), senderAdornment, _jsx("span", { children: "\u00B7" }), _jsx("span", { children: new Date(m.createdAt).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) })] })), m.text && (_jsx("p", { className: "text-sm whitespace-pre-wrap", children: _jsx(MessageText, { text: m.text }) }))] }), sendPending && (_jsxs("div", { className: "chat-send-status mt-0.5", role: "status", "aria-live": "polite", children: [_jsx(Spinner, { size: "xs", className: "shrink-0" }), _jsx("span", { children: "Sending\u2026" })] })), sendFailed && (_jsxs("div", { className: "chat-send-status chat-send-status--failed mt-0.5", role: "alert", children: [_jsx(AlertTriangle, { className: "h-3 w-3 shrink-0" }), _jsx("span", { children: sendFailureCopy(sendFailureCode) }), showRetry && onRetrySend && clientMessageId && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-5 px-1.5 text-[0.6875rem]", onClick: () => onRetrySend(clientMessageId), children: "Retry" }))] })), !isContinuation && (_jsx("div", { className: "mt-1", children: _jsx(MessageActions, { messageId: m.messageId, handlers: handlers }) }))] }));
47
47
  }
48
48
  //# sourceMappingURL=MessageItemDefault.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageItemDefault.js","sourceRoot":"","sources":["../../src/ui/MessageItemDefault.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\nimport type { ChatMessageV1, ModerationHandlers } from '@ttt-productions/chat-core';\nimport { MessageText } from './MessageText.js';\nimport { cn } from '@ttt-productions/ui-core';\nimport { Button, Spinner } from '@ttt-productions/ui-core/react';\nimport { AlertTriangle } from 'lucide-react';\nimport { MessageActions } from './menus.js';\nimport { SenderName } from './SenderName.js';\nimport { useSenderNameResolution } from '../context/ChatNameResolverContext.js';\n\nconst SEND_FAILURE_COPY: Record<string, string> = {\n 'blocked-word': 'Message contains blocked language',\n archived: 'This channel is read-only',\n deleted: 'This channel is read-only',\n 'membership-pending': 'Chat is still preparing',\n 'wordlist-unavailable': 'Chat safety check is temporarily unavailable',\n flood: 'Please wait before sending again',\n 'slow-mode': 'Please wait before sending again',\n};\n\nfunction sendFailureCopy(code: string | null): string {\n return (code && SEND_FAILURE_COPY[code]) || \"Couldn't send\";\n}\n\nfunction isRetryableFailure(meta: Record<string, unknown> | undefined): boolean {\n return meta?.sendRetryable !== false;\n}\n\nfunction SystemMessage({ m }: { m: ChatMessageV1 }) {\n return (\n <div className=\"chat-system-message\">\n {m.text && <span>{m.text}</span>}\n <span className=\"chat-system-message-time\">\n {new Date(m.createdAt).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}\n </span>\n </div>\n );\n}\n\nexport type MessageItemDefaultProps = {\n m: ChatMessageV1;\n currentUserId: string;\n isAdmin: boolean;\n handlers?: ModerationHandlers;\n\n isContinuation?: boolean;\n\n onSenderClick?: (senderId: string) => void;\n senderAdornment?: ReactNode;\n\n onRetrySend?: (clientMessageId: string) => void;\n};\n\nexport function MessageItemDefault(props: MessageItemDefaultProps) {\n const {\n m,\n currentUserId,\n isAdmin,\n handlers,\n isContinuation,\n onSenderClick: senderClick,\n senderAdornment,\n onRetrySend,\n } = props;\n const nameResolved = useSenderNameResolution(m.senderId).status === 'resolved';\n const onSenderClick = nameResolved ? senderClick : undefined;\n\n if (m.isSystemMessage) {\n return <SystemMessage m={m} />;\n }\n\n const mine = m.senderId === currentUserId;\n\n const sendFailed = m.meta?.sendFailed === true;\n const sendPending = m.meta?.optimistic === true && !sendFailed;\n const clientMessageId =\n typeof m.meta?.clientMessageId === 'string' ? m.meta.clientMessageId : null;\n const sendFailureCode =\n typeof m.meta?.sendFailureCode === 'string' ? m.meta.sendFailureCode : null;\n const showRetry = sendFailed && isRetryableFailure(m.meta);\n\n return (\n <div\n className={cn(\n 'flex flex-col w-fit max-w-[85%]',\n mine ? 'ml-auto items-end' : 'mr-auto items-start',\n isContinuation ? 'chat-continuation-gap' : 'chat-group-gap',\n )}\n >\n <div\n className={cn(\n 'chat-bubble',\n mine ? 'chat-bubble--mine' : 'chat-bubble--theirs',\n sendPending && 'chat-bubble--pending',\n sendFailed && 'chat-bubble--failed',\n )}\n >\n {!isContinuation && (\n <div className=\"flex items-center gap-2 text-xs opacity-80 mb-1\">\n <span\n className={cn('font-medium', onSenderClick && 'cursor-pointer hover:underline')}\n onClick={onSenderClick ? () => onSenderClick(m.senderId) : undefined}\n role={onSenderClick ? 'button' : undefined}\n tabIndex={onSenderClick ? 0 : undefined}\n onKeyDown={\n onSenderClick\n ? (e) => {\n if (e.key === 'Enter' || e.key === ' ') onSenderClick(m.senderId);\n }\n : undefined\n }\n >\n <SenderName senderId={m.senderId} />\n </span>\n {senderAdornment}\n <span>·</span>\n <span>\n {new Date(m.createdAt).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}\n </span>\n </div>\n )}\n\n {m.text && (\n <p className=\"text-sm whitespace-pre-wrap\">\n <MessageText text={m.text} />\n </p>\n )}\n </div>\n\n {sendPending && (\n <div className=\"chat-send-status mt-0.5\" role=\"status\" aria-live=\"polite\">\n <Spinner size=\"xs\" className=\"shrink-0\" />\n <span>Sending…</span>\n </div>\n )}\n {sendFailed && (\n <div className=\"chat-send-status chat-send-status--failed mt-0.5\" role=\"alert\">\n <AlertTriangle className=\"h-3 w-3 shrink-0\" />\n <span>{sendFailureCopy(sendFailureCode)}</span>\n {showRetry && onRetrySend && clientMessageId && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-5 px-1.5 text-[0.6875rem]\"\n onClick={() => onRetrySend(clientMessageId)}\n >\n Retry\n </Button>\n )}\n </div>\n )}\n\n {!isContinuation && (\n <div className=\"mt-1\">\n <MessageActions messageId={m.messageId} isAdmin={isAdmin} handlers={handlers} />\n </div>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"MessageItemDefault.js","sourceRoot":"","sources":["../../src/ui/MessageItemDefault.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { ReactNode } from 'react';\nimport type { ChatMessageV1, ModerationHandlers } from '@ttt-productions/chat-core';\nimport { MessageText } from './MessageText.js';\nimport { cn } from '@ttt-productions/ui-core';\nimport { Button, Spinner } from '@ttt-productions/ui-core/react';\nimport { AlertTriangle } from 'lucide-react';\nimport { MessageActions } from './menus.js';\nimport { SenderName } from './SenderName.js';\nimport { useSenderNameResolution } from '../context/ChatNameResolverContext.js';\n\nconst SEND_FAILURE_COPY: Record<string, string> = {\n 'blocked-word': 'Message contains blocked language',\n archived: 'This channel is read-only',\n deleted: 'This channel is read-only',\n 'membership-pending': 'Chat is still preparing',\n 'wordlist-unavailable': 'Chat safety check is temporarily unavailable',\n flood: 'Please wait before sending again',\n 'slow-mode': 'Please wait before sending again',\n};\n\nfunction sendFailureCopy(code: string | null): string {\n return (code && SEND_FAILURE_COPY[code]) || \"Couldn't send\";\n}\n\nfunction isRetryableFailure(meta: Record<string, unknown> | undefined): boolean {\n return meta?.sendRetryable !== false;\n}\n\nfunction SystemMessage({ m }: { m: ChatMessageV1 }) {\n return (\n <div className=\"chat-system-message\">\n {m.text && <span>{m.text}</span>}\n <span className=\"chat-system-message-time\">\n {new Date(m.createdAt).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}\n </span>\n </div>\n );\n}\n\nexport type MessageItemDefaultProps = {\n m: ChatMessageV1;\n currentUserId: string;\n handlers?: ModerationHandlers;\n\n isContinuation?: boolean;\n\n onSenderClick?: (senderId: string) => void;\n senderAdornment?: ReactNode;\n\n onRetrySend?: (clientMessageId: string) => void;\n};\n\nexport function MessageItemDefault(props: MessageItemDefaultProps) {\n const {\n m,\n currentUserId,\n handlers,\n isContinuation,\n onSenderClick: senderClick,\n senderAdornment,\n onRetrySend,\n } = props;\n const nameResolved = useSenderNameResolution(m.senderId).status === 'resolved';\n const onSenderClick = nameResolved ? senderClick : undefined;\n\n if (m.isSystemMessage) {\n return <SystemMessage m={m} />;\n }\n\n const mine = m.senderId === currentUserId;\n\n const sendFailed = m.meta?.sendFailed === true;\n const sendPending = m.meta?.optimistic === true && !sendFailed;\n const clientMessageId =\n typeof m.meta?.clientMessageId === 'string' ? m.meta.clientMessageId : null;\n const sendFailureCode =\n typeof m.meta?.sendFailureCode === 'string' ? m.meta.sendFailureCode : null;\n const showRetry = sendFailed && isRetryableFailure(m.meta);\n\n return (\n <div\n className={cn(\n 'flex flex-col w-fit max-w-[85%]',\n mine ? 'ml-auto items-end' : 'mr-auto items-start',\n isContinuation ? 'chat-continuation-gap' : 'chat-group-gap',\n )}\n >\n <div\n className={cn(\n 'chat-bubble',\n mine ? 'chat-bubble--mine' : 'chat-bubble--theirs',\n sendPending && 'chat-bubble--pending',\n sendFailed && 'chat-bubble--failed',\n )}\n >\n {!isContinuation && (\n <div className=\"flex items-center gap-2 text-xs opacity-80 mb-1\">\n <span\n className={cn('font-medium', onSenderClick && 'cursor-pointer hover:underline')}\n onClick={onSenderClick ? () => onSenderClick(m.senderId) : undefined}\n role={onSenderClick ? 'button' : undefined}\n tabIndex={onSenderClick ? 0 : undefined}\n onKeyDown={\n onSenderClick\n ? (e) => {\n if (e.key === 'Enter' || e.key === ' ') onSenderClick(m.senderId);\n }\n : undefined\n }\n >\n <SenderName senderId={m.senderId} />\n </span>\n {senderAdornment}\n <span>·</span>\n <span>\n {new Date(m.createdAt).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}\n </span>\n </div>\n )}\n\n {m.text && (\n <p className=\"text-sm whitespace-pre-wrap\">\n <MessageText text={m.text} />\n </p>\n )}\n </div>\n\n {sendPending && (\n <div className=\"chat-send-status mt-0.5\" role=\"status\" aria-live=\"polite\">\n <Spinner size=\"xs\" className=\"shrink-0\" />\n <span>Sending…</span>\n </div>\n )}\n {sendFailed && (\n <div className=\"chat-send-status chat-send-status--failed mt-0.5\" role=\"alert\">\n <AlertTriangle className=\"h-3 w-3 shrink-0\" />\n <span>{sendFailureCopy(sendFailureCode)}</span>\n {showRetry && onRetrySend && clientMessageId && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-5 px-1.5 text-[0.6875rem]\"\n onClick={() => onRetrySend(clientMessageId)}\n >\n Retry\n </Button>\n )}\n </div>\n )}\n\n {!isContinuation && (\n <div className=\"mt-1\">\n <MessageActions messageId={m.messageId} handlers={handlers} />\n </div>\n )}\n </div>\n );\n}\n"]}
@@ -10,7 +10,6 @@ export type ChatScrollContainerProps = {
10
10
  export declare function MessageList(props: {
11
11
  messages: ChatMessageV1[];
12
12
  currentUserId: string;
13
- isAdmin: boolean;
14
13
  isFetchingOlder?: boolean;
15
14
  hasOlder?: boolean;
16
15
  onLoadOlder?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAI3D,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;IAC/C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAUF,wBAAgB,WAAW,CAAC,KAAK,EAAE;IACjC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IAEjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAEzB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAC;IACtD,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAE3C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,wBAAwB,CAAC,CAAC;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,qBAAqB,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,iBAAiB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACrC,qBAuMA"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAI3D,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;IAC/C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAUF,wBAAgB,WAAW,CAAC,KAAK,EAAE;IACjC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IAEtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAEzB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAC;IACtD,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAE3C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,wBAAwB,CAAC,CAAC;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,qBAAqB,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,iBAAiB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACrC,qBAqMA"}
@@ -8,7 +8,7 @@ function DefaultScrollContainer({ ref, className, onScroll, children }) {
8
8
  return (_jsx("div", { ref: ref, className: className, onScroll: onScroll, children: children }));
9
9
  }
10
10
  export function MessageList(props) {
11
- const { messages, currentUserId, isAdmin, isFetchingOlder, hasOlder, onLoadOlder, renderMessage, messageRenderers, showScrollToBottom, onScrollToBottom, onAtBottomChange, scrollClassName, scrollContainer: ScrollContainer = DefaultScrollContainer, fillHeight = false, handlers, onSenderClick, messageGroupKey, renderSenderAdornment, onRetrySend, olderLoadErrorRow, } = props;
11
+ const { messages, currentUserId, isFetchingOlder, hasOlder, onLoadOlder, renderMessage, messageRenderers, showScrollToBottom, onScrollToBottom, onAtBottomChange, scrollClassName, scrollContainer: ScrollContainer = DefaultScrollContainer, fillHeight = false, handlers, onSenderClick, messageGroupKey, renderSenderAdornment, onRetrySend, olderLoadErrorRow, } = props;
12
12
  const olderLoadFailed = olderLoadErrorRow != null;
13
13
  const scrollClass = scrollClassName ?? (fillHeight ? 'flex-1 min-h-0' : 'h-[400px]');
14
14
  const outerClass = fillHeight ? 'relative flex flex-col flex-1 min-h-0' : 'relative';
@@ -101,7 +101,7 @@ export function MessageList(props) {
101
101
  const prevMsg = idx > 0 ? messages[idx - 1] : undefined;
102
102
  const continuation = showDay ? false : isContinuation(prevMsg, m, messageGroupKey);
103
103
  const byType = m.type && messageRenderers?.[m.type] ? messageRenderers[m.type](m) : null;
104
- const body = renderMessage?.(m) ?? byType ?? (_jsx(MessageItemDefault, { m: m, currentUserId: currentUserId, isAdmin: isAdmin, handlers: handlers, isContinuation: continuation, onSenderClick: onSenderClick, senderAdornment: renderSenderAdornment?.(m), onRetrySend: onRetrySend }));
104
+ const body = renderMessage?.(m) ?? byType ?? (_jsx(MessageItemDefault, { m: m, currentUserId: currentUserId, handlers: handlers, isContinuation: continuation, onSenderClick: onSenderClick, senderAdornment: renderSenderAdornment?.(m), onRetrySend: onRetrySend }));
105
105
  return (_jsxs(React.Fragment, { children: [showDay && (_jsx("div", { className: "my-2 flex justify-center", children: _jsx("div", { className: "chat-date-separator", children: new Date(m.createdAt).toLocaleDateString() }) })), body] }, m.messageId));
106
106
  }) }))] }), (unseenCount > 0 || showScrollToBottom) && (_jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": unseenCount > 0 ? `${unseenCount} new messages, scroll to latest` : 'Scroll to latest', className: "absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full elevation-raised gap-1", onClick: () => {
107
107
  scrollToBottom();
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.js","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatMessageGroupKey,\n ChatMessageV1,\n ModerationHandlers,\n} from '@ttt-productions/chat-core';\nimport { isContinuation } from '@ttt-productions/chat-core';\nimport type { MessageRendererRegistry } from '../types.js';\nimport { Button, Spinner } from '@ttt-productions/ui-core/react';\nimport { MessageItemDefault } from './MessageItemDefault.js';\n\nexport type ChatScrollContainerProps = {\n ref: React.Ref<HTMLDivElement>;\n className: string;\n onScroll: React.UIEventHandler<HTMLDivElement>;\n children: React.ReactNode;\n};\n\nfunction DefaultScrollContainer({ ref, className, onScroll, children }: ChatScrollContainerProps) {\n return (\n <div ref={ref} className={className} onScroll={onScroll}>\n {children}\n </div>\n );\n}\n\nexport function MessageList(props: {\n messages: ChatMessageV1[];\n currentUserId: string;\n isAdmin: boolean;\n\n isFetchingOlder?: boolean;\n hasOlder?: boolean;\n onLoadOlder?: () => void;\n\n renderMessage?: (m: ChatMessageV1) => React.ReactNode;\n messageRenderers?: MessageRendererRegistry;\n\n showScrollToBottom?: boolean;\n onScrollToBottom?: () => void;\n onAtBottomChange?: (atBottom: boolean) => void;\n scrollClassName?: string;\n scrollContainer?: React.ComponentType<ChatScrollContainerProps>;\n fillHeight?: boolean;\n\n handlers?: ModerationHandlers;\n onSenderClick?: (senderId: string) => void;\n messageGroupKey?: ChatMessageGroupKey;\n renderSenderAdornment?: (m: ChatMessageV1) => React.ReactNode;\n onRetrySend?: (clientMessageId: string) => void;\n olderLoadErrorRow?: React.ReactNode;\n}) {\n const {\n messages,\n currentUserId,\n isAdmin,\n isFetchingOlder,\n hasOlder,\n onLoadOlder,\n renderMessage,\n messageRenderers,\n showScrollToBottom,\n onScrollToBottom,\n onAtBottomChange,\n scrollClassName,\n scrollContainer: ScrollContainer = DefaultScrollContainer,\n fillHeight = false,\n handlers,\n onSenderClick,\n messageGroupKey,\n renderSenderAdornment,\n onRetrySend,\n olderLoadErrorRow,\n } = props;\n const olderLoadFailed = olderLoadErrorRow != null;\n\n const scrollClass = scrollClassName ?? (fillHeight ? 'flex-1 min-h-0' : 'h-[400px]');\n const outerClass = fillHeight ? 'relative flex flex-col flex-1 min-h-0' : 'relative';\n\n const scrollRef = React.useRef<HTMLDivElement>(null);\n const topSentinelRef = React.useRef<HTMLDivElement>(null);\n\n const isAtBottomRef = React.useRef(true);\n const prevScrollHeightRef = React.useRef<number | null>(null);\n const prevCountRef = React.useRef(0);\n\n const [unseenCount, setUnseenCount] = React.useState(0);\n\n const scrollToBottom = React.useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n el.scrollTop = el.scrollHeight;\n isAtBottomRef.current = true;\n setUnseenCount(0);\n onAtBottomChange?.(true);\n }, [onAtBottomChange]);\n\n React.useEffect(() => {\n const root = scrollRef.current;\n const target = topSentinelRef.current;\n if (!root || !target) return;\n if (typeof IntersectionObserver === 'undefined') return;\n\n const io = new IntersectionObserver(\n (entries) => {\n const e = entries[0];\n if (!e?.isIntersecting) return;\n if (!hasOlder || isFetchingOlder || olderLoadFailed) return;\n\n prevScrollHeightRef.current = root.scrollHeight;\n onLoadOlder?.();\n },\n { root, threshold: 0 },\n );\n\n io.observe(target);\n return () => io.disconnect();\n }, [hasOlder, isFetchingOlder, olderLoadFailed, onLoadOlder]);\n\n // Scroll management assigns scrollTop (never a smooth scrollTo), so a scrolling parent such as an\n // accordion never jumps.\n React.useLayoutEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n\n const count = messages.length;\n const prev = prevCountRef.current;\n\n if (prev === 0 && count > 0) {\n requestAnimationFrame(() => {\n const savedScrollY = window.scrollY;\n el.scrollTop = el.scrollHeight;\n // Restore page scroll if the browser moved it\n if (window.scrollY !== savedScrollY) {\n window.scrollTo({ top: savedScrollY, behavior: 'instant' as ScrollBehavior });\n }\n });\n isAtBottomRef.current = true;\n } else if (count > prev && prevScrollHeightRef.current != null) {\n const diff = el.scrollHeight - prevScrollHeightRef.current;\n el.scrollTop += diff;\n prevScrollHeightRef.current = null;\n } else if (count > prev && isAtBottomRef.current) {\n requestAnimationFrame(() => {\n const savedScrollY = window.scrollY;\n el.scrollTop = el.scrollHeight;\n if (window.scrollY !== savedScrollY) {\n window.scrollTo({ top: savedScrollY, behavior: 'instant' as ScrollBehavior });\n }\n });\n } else if (count > prev) {\n setUnseenCount((c) => c + (count - prev));\n }\n\n prevCountRef.current = count;\n }, [messages]);\n\n const onScroll = (e: React.UIEvent<HTMLDivElement>) => {\n const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;\n const atBottom = scrollHeight - (scrollTop + clientHeight) < 50;\n if (atBottom !== isAtBottomRef.current) onAtBottomChange?.(atBottom);\n isAtBottomRef.current = atBottom;\n if (atBottom && unseenCount !== 0) setUnseenCount(0);\n };\n\n let lastDay: string | null = null;\n\n return (\n <div className={outerClass}>\n <ScrollContainer\n ref={scrollRef}\n className={`${scrollClass} overflow-y-auto p-4`}\n onScroll={onScroll}\n >\n {olderLoadFailed ? (\n <div className=\"mb-2\">{olderLoadErrorRow}</div>\n ) : isFetchingOlder ? (\n <div className=\"flex justify-center mb-2\">\n <Spinner size=\"xs\" label=\"Loading older messages\" />\n </div>\n ) : null}\n\n <div ref={topSentinelRef} />\n\n {messages.length === 0 ? (\n <div className=\"h-full flex items-center justify-center text-sm opacity-70\">\n No messages yet\n </div>\n ) : (\n <div className=\"flex flex-col\">\n {messages.map((m, idx) => {\n const day = new Date(m.createdAt).toDateString();\n const showDay = day !== lastDay;\n lastDay = day;\n\n const prevMsg = idx > 0 ? messages[idx - 1] : undefined;\n const continuation = showDay ? false : isContinuation(prevMsg, m, messageGroupKey);\n\n const byType =\n m.type && messageRenderers?.[m.type] ? messageRenderers[m.type]!(m) : null;\n\n const body = renderMessage?.(m) ?? byType ?? (\n <MessageItemDefault\n m={m}\n currentUserId={currentUserId}\n isAdmin={isAdmin}\n handlers={handlers}\n isContinuation={continuation}\n onSenderClick={onSenderClick}\n senderAdornment={renderSenderAdornment?.(m)}\n onRetrySend={onRetrySend}\n />\n );\n\n return (\n <React.Fragment key={m.messageId}>\n {showDay && (\n <div className=\"my-2 flex justify-center\">\n <div className=\"chat-date-separator\">\n {new Date(m.createdAt).toLocaleDateString()}\n </div>\n </div>\n )}\n {body}\n </React.Fragment>\n );\n })}\n </div>\n )}\n </ScrollContainer>\n\n {(unseenCount > 0 || showScrollToBottom) && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={\n unseenCount > 0 ? `${unseenCount} new messages, scroll to latest` : 'Scroll to latest'\n }\n className=\"absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full elevation-raised gap-1\"\n onClick={() => {\n scrollToBottom();\n onScrollToBottom?.();\n }}\n >\n {unseenCount > 0 ? `${unseenCount} new message${unseenCount > 1 ? 's' : ''} ↓` : '↓'}\n </Button>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"MessageList.js","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatMessageGroupKey,\n ChatMessageV1,\n ModerationHandlers,\n} from '@ttt-productions/chat-core';\nimport { isContinuation } from '@ttt-productions/chat-core';\nimport type { MessageRendererRegistry } from '../types.js';\nimport { Button, Spinner } from '@ttt-productions/ui-core/react';\nimport { MessageItemDefault } from './MessageItemDefault.js';\n\nexport type ChatScrollContainerProps = {\n ref: React.Ref<HTMLDivElement>;\n className: string;\n onScroll: React.UIEventHandler<HTMLDivElement>;\n children: React.ReactNode;\n};\n\nfunction DefaultScrollContainer({ ref, className, onScroll, children }: ChatScrollContainerProps) {\n return (\n <div ref={ref} className={className} onScroll={onScroll}>\n {children}\n </div>\n );\n}\n\nexport function MessageList(props: {\n messages: ChatMessageV1[];\n currentUserId: string;\n\n isFetchingOlder?: boolean;\n hasOlder?: boolean;\n onLoadOlder?: () => void;\n\n renderMessage?: (m: ChatMessageV1) => React.ReactNode;\n messageRenderers?: MessageRendererRegistry;\n\n showScrollToBottom?: boolean;\n onScrollToBottom?: () => void;\n onAtBottomChange?: (atBottom: boolean) => void;\n scrollClassName?: string;\n scrollContainer?: React.ComponentType<ChatScrollContainerProps>;\n fillHeight?: boolean;\n\n handlers?: ModerationHandlers;\n onSenderClick?: (senderId: string) => void;\n messageGroupKey?: ChatMessageGroupKey;\n renderSenderAdornment?: (m: ChatMessageV1) => React.ReactNode;\n onRetrySend?: (clientMessageId: string) => void;\n olderLoadErrorRow?: React.ReactNode;\n}) {\n const {\n messages,\n currentUserId,\n isFetchingOlder,\n hasOlder,\n onLoadOlder,\n renderMessage,\n messageRenderers,\n showScrollToBottom,\n onScrollToBottom,\n onAtBottomChange,\n scrollClassName,\n scrollContainer: ScrollContainer = DefaultScrollContainer,\n fillHeight = false,\n handlers,\n onSenderClick,\n messageGroupKey,\n renderSenderAdornment,\n onRetrySend,\n olderLoadErrorRow,\n } = props;\n const olderLoadFailed = olderLoadErrorRow != null;\n\n const scrollClass = scrollClassName ?? (fillHeight ? 'flex-1 min-h-0' : 'h-[400px]');\n const outerClass = fillHeight ? 'relative flex flex-col flex-1 min-h-0' : 'relative';\n\n const scrollRef = React.useRef<HTMLDivElement>(null);\n const topSentinelRef = React.useRef<HTMLDivElement>(null);\n\n const isAtBottomRef = React.useRef(true);\n const prevScrollHeightRef = React.useRef<number | null>(null);\n const prevCountRef = React.useRef(0);\n\n const [unseenCount, setUnseenCount] = React.useState(0);\n\n const scrollToBottom = React.useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n el.scrollTop = el.scrollHeight;\n isAtBottomRef.current = true;\n setUnseenCount(0);\n onAtBottomChange?.(true);\n }, [onAtBottomChange]);\n\n React.useEffect(() => {\n const root = scrollRef.current;\n const target = topSentinelRef.current;\n if (!root || !target) return;\n if (typeof IntersectionObserver === 'undefined') return;\n\n const io = new IntersectionObserver(\n (entries) => {\n const e = entries[0];\n if (!e?.isIntersecting) return;\n if (!hasOlder || isFetchingOlder || olderLoadFailed) return;\n\n prevScrollHeightRef.current = root.scrollHeight;\n onLoadOlder?.();\n },\n { root, threshold: 0 },\n );\n\n io.observe(target);\n return () => io.disconnect();\n }, [hasOlder, isFetchingOlder, olderLoadFailed, onLoadOlder]);\n\n // Scroll management assigns scrollTop (never a smooth scrollTo), so a scrolling parent such as an\n // accordion never jumps.\n React.useLayoutEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n\n const count = messages.length;\n const prev = prevCountRef.current;\n\n if (prev === 0 && count > 0) {\n requestAnimationFrame(() => {\n const savedScrollY = window.scrollY;\n el.scrollTop = el.scrollHeight;\n // Restore page scroll if the browser moved it\n if (window.scrollY !== savedScrollY) {\n window.scrollTo({ top: savedScrollY, behavior: 'instant' as ScrollBehavior });\n }\n });\n isAtBottomRef.current = true;\n } else if (count > prev && prevScrollHeightRef.current != null) {\n const diff = el.scrollHeight - prevScrollHeightRef.current;\n el.scrollTop += diff;\n prevScrollHeightRef.current = null;\n } else if (count > prev && isAtBottomRef.current) {\n requestAnimationFrame(() => {\n const savedScrollY = window.scrollY;\n el.scrollTop = el.scrollHeight;\n if (window.scrollY !== savedScrollY) {\n window.scrollTo({ top: savedScrollY, behavior: 'instant' as ScrollBehavior });\n }\n });\n } else if (count > prev) {\n setUnseenCount((c) => c + (count - prev));\n }\n\n prevCountRef.current = count;\n }, [messages]);\n\n const onScroll = (e: React.UIEvent<HTMLDivElement>) => {\n const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;\n const atBottom = scrollHeight - (scrollTop + clientHeight) < 50;\n if (atBottom !== isAtBottomRef.current) onAtBottomChange?.(atBottom);\n isAtBottomRef.current = atBottom;\n if (atBottom && unseenCount !== 0) setUnseenCount(0);\n };\n\n let lastDay: string | null = null;\n\n return (\n <div className={outerClass}>\n <ScrollContainer\n ref={scrollRef}\n className={`${scrollClass} overflow-y-auto p-4`}\n onScroll={onScroll}\n >\n {olderLoadFailed ? (\n <div className=\"mb-2\">{olderLoadErrorRow}</div>\n ) : isFetchingOlder ? (\n <div className=\"flex justify-center mb-2\">\n <Spinner size=\"xs\" label=\"Loading older messages\" />\n </div>\n ) : null}\n\n <div ref={topSentinelRef} />\n\n {messages.length === 0 ? (\n <div className=\"h-full flex items-center justify-center text-sm opacity-70\">\n No messages yet\n </div>\n ) : (\n <div className=\"flex flex-col\">\n {messages.map((m, idx) => {\n const day = new Date(m.createdAt).toDateString();\n const showDay = day !== lastDay;\n lastDay = day;\n\n const prevMsg = idx > 0 ? messages[idx - 1] : undefined;\n const continuation = showDay ? false : isContinuation(prevMsg, m, messageGroupKey);\n\n const byType =\n m.type && messageRenderers?.[m.type] ? messageRenderers[m.type]!(m) : null;\n\n const body = renderMessage?.(m) ?? byType ?? (\n <MessageItemDefault\n m={m}\n currentUserId={currentUserId}\n handlers={handlers}\n isContinuation={continuation}\n onSenderClick={onSenderClick}\n senderAdornment={renderSenderAdornment?.(m)}\n onRetrySend={onRetrySend}\n />\n );\n\n return (\n <React.Fragment key={m.messageId}>\n {showDay && (\n <div className=\"my-2 flex justify-center\">\n <div className=\"chat-date-separator\">\n {new Date(m.createdAt).toLocaleDateString()}\n </div>\n </div>\n )}\n {body}\n </React.Fragment>\n );\n })}\n </div>\n )}\n </ScrollContainer>\n\n {(unseenCount > 0 || showScrollToBottom) && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={\n unseenCount > 0 ? `${unseenCount} new messages, scroll to latest` : 'Scroll to latest'\n }\n className=\"absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full elevation-raised gap-1\"\n onClick={() => {\n scrollToBottom();\n onScrollToBottom?.();\n }}\n >\n {unseenCount > 0 ? `${unseenCount} new message${unseenCount > 1 ? 's' : ''} ↓` : '↓'}\n </Button>\n )}\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"SenderName.d.ts","sourceRoot":"","sources":["../../src/ui/SenderName.tsx"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BAK5D"}
1
+ {"version":3,"file":"SenderName.d.ts","sourceRoot":"","sources":["../../src/ui/SenderName.tsx"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BAY5D"}
@@ -1,11 +1,12 @@
1
1
  'use client';
2
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useChatNameResolver } from '../context/ChatNameResolverContext.js';
4
4
  export function SenderName({ senderId }) {
5
- const { resolveName, renderUnresolvedName } = useChatNameResolver();
5
+ const { resolveName, renderUnresolvedName, renderHeldNameFailure } = useChatNameResolver();
6
6
  const resolution = resolveName(senderId);
7
- if (resolution.status === 'resolved')
8
- return _jsx(_Fragment, { children: resolution.name });
7
+ if (resolution.status === 'resolved') {
8
+ return (_jsxs(_Fragment, { children: [resolution.name, resolution.failure ? (renderHeldNameFailure?.(resolution.failure) ?? null) : null] }));
9
+ }
9
10
  return _jsx(_Fragment, { children: renderUnresolvedName?.(resolution) ?? null });
10
11
  }
11
12
  //# sourceMappingURL=SenderName.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SenderName.js","sourceRoot":"","sources":["../../src/ui/SenderName.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAE5E,MAAM,UAAU,UAAU,CAAC,EAAE,QAAQ,EAAwB;IAC3D,MAAM,EAAE,WAAW,EAAE,oBAAoB,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACpE,MAAM,UAAU,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,UAAU,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,4BAAG,UAAU,CAAC,IAAI,GAAI,CAAC;IACpE,OAAO,4BAAG,oBAAoB,EAAE,CAAC,UAAU,CAAC,IAAI,IAAI,GAAI,CAAC;AAC3D,CAAC","sourcesContent":["'use client';\n\nimport { useChatNameResolver } from '../context/ChatNameResolverContext.js';\n\nexport function SenderName({ senderId }: { senderId: string }) {\n const { resolveName, renderUnresolvedName } = useChatNameResolver();\n const resolution = resolveName(senderId);\n if (resolution.status === 'resolved') return <>{resolution.name}</>;\n return <>{renderUnresolvedName?.(resolution) ?? null}</>;\n}\n"]}
1
+ {"version":3,"file":"SenderName.js","sourceRoot":"","sources":["../../src/ui/SenderName.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAE5E,MAAM,UAAU,UAAU,CAAC,EAAE,QAAQ,EAAwB;IAC3D,MAAM,EAAE,WAAW,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,GAAG,mBAAmB,EAAE,CAAC;IAC3F,MAAM,UAAU,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,UAAU,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACrC,OAAO,CACL,8BACG,UAAU,CAAC,IAAI,EACf,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,qBAAqB,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IACjF,CACJ,CAAC;IACJ,CAAC;IACD,OAAO,4BAAG,oBAAoB,EAAE,CAAC,UAAU,CAAC,IAAI,IAAI,GAAI,CAAC;AAC3D,CAAC","sourcesContent":["'use client';\n\nimport { useChatNameResolver } from '../context/ChatNameResolverContext.js';\n\nexport function SenderName({ senderId }: { senderId: string }) {\n const { resolveName, renderUnresolvedName, renderHeldNameFailure } = useChatNameResolver();\n const resolution = resolveName(senderId);\n if (resolution.status === 'resolved') {\n return (\n <>\n {resolution.name}\n {resolution.failure ? (renderHeldNameFailure?.(resolution.failure) ?? null) : null}\n </>\n );\n }\n return <>{renderUnresolvedName?.(resolution) ?? null}</>;\n}\n"]}
@@ -1,12 +1,10 @@
1
1
  import type { ModerationHandlers } from '@ttt-productions/chat-core';
2
2
  export declare function MessageActions(props: {
3
3
  messageId: string;
4
- isAdmin: boolean;
5
4
  handlers?: ModerationHandlers;
6
5
  }): import("react").JSX.Element;
7
6
  export declare function ThreadActions(props: {
8
7
  threadId: string;
9
- isAdmin: boolean;
10
8
  handlers?: ModerationHandlers;
11
9
  }): import("react").JSX.Element;
12
10
  //# sourceMappingURL=menus.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"menus.d.ts","sourceRoot":"","sources":["../../src/ui/menus.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAiCrE,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,+BAaA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,+BAeA"}
1
+ {"version":3,"file":"menus.d.ts","sourceRoot":"","sources":["../../src/ui/menus.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAiCrE,wBAAgB,cAAc,CAAC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;CAAE,+BASzF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;CAAE,+BAWvF"}
package/dist/ui/menus.js CHANGED
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { Button, useAsyncAction } from '@ttt-productions/ui-core/react';
4
4
  // Rethrown on a timer so the rejection reaches the global error channel, as an unawaited one would.
5
5
  function reraise(error) {
@@ -15,15 +15,13 @@ function ModerationActionButton(props) {
15
15
  return (_jsx(Button, { type: "button", variant: "link", size: "sm", className: "h-auto p-0 text-xs opacity-70 hover:opacity-100", onClick: () => void action.run(), pending: action.pending, children: label }));
16
16
  }
17
17
  export function MessageActions(props) {
18
- const { messageId, isAdmin, handlers } = props;
18
+ const { messageId, handlers } = props;
19
19
  const onReport = handlers?.onReportMessage;
20
- const onDelete = handlers?.onDeleteMessage;
21
- return (_jsxs("div", { className: "flex items-center gap-2", children: [onReport && _jsx(ModerationActionButton, { label: "Report", onAction: () => onReport(messageId) }), isAdmin && onDelete && (_jsx(ModerationActionButton, { label: "Delete", onAction: () => onDelete(messageId) }))] }));
20
+ return (_jsx("div", { className: "flex items-center gap-2", children: onReport && _jsx(ModerationActionButton, { label: "Report", onAction: () => onReport(messageId) }) }));
22
21
  }
23
22
  export function ThreadActions(props) {
24
- const { threadId, isAdmin, handlers } = props;
23
+ const { threadId, handlers } = props;
25
24
  const onReport = handlers?.onReportThread;
26
- const onDelete = handlers?.onDeleteThread;
27
- return (_jsxs("div", { className: "flex items-center gap-3", children: [onReport && (_jsx(ModerationActionButton, { label: "Report thread", onAction: () => onReport(threadId) })), isAdmin && onDelete && (_jsx(ModerationActionButton, { label: "Delete thread", onAction: () => onDelete(threadId) }))] }));
25
+ return (_jsx("div", { className: "flex items-center gap-3", children: onReport && (_jsx(ModerationActionButton, { label: "Report thread", onAction: () => onReport(threadId) })) }));
28
26
  }
29
27
  //# sourceMappingURL=menus.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"menus.js","sourceRoot":"","sources":["../../src/ui/menus.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAExE,oGAAoG;AACpG,SAAS,OAAO,CAAC,KAAc;IAC7B,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,sBAAsB,CAAC,KAA8D;IAC5F,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAC3B,KAAK,IAAI,EAAE;QACT,MAAM,QAAQ,EAAE,CAAC;IACnB,CAAC,EACD,EAAE,OAAO,EAAE,OAAO,EAAE,CACrB,CAAC;IAEF,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,MAAM,EACd,IAAI,EAAC,IAAI,EACT,SAAS,EAAC,iDAAiD,EAC3D,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,GAAG,EAAE,EAChC,OAAO,EAAE,MAAM,CAAC,OAAO,YAEtB,KAAK,GACC,CACV,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAI9B;IACC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAC/C,MAAM,QAAQ,GAAG,QAAQ,EAAE,eAAe,CAAC;IAC3C,MAAM,QAAQ,GAAG,QAAQ,EAAE,eAAe,CAAC;IAE3C,OAAO,CACL,eAAK,SAAS,EAAC,yBAAyB,aACrC,QAAQ,IAAI,KAAC,sBAAsB,IAAC,KAAK,EAAC,QAAQ,EAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAI,EAC1F,OAAO,IAAI,QAAQ,IAAI,CACtB,KAAC,sBAAsB,IAAC,KAAK,EAAC,QAAQ,EAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAI,CAC/E,IACG,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAI7B;IACC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAC9C,MAAM,QAAQ,GAAG,QAAQ,EAAE,cAAc,CAAC;IAC1C,MAAM,QAAQ,GAAG,QAAQ,EAAE,cAAc,CAAC;IAE1C,OAAO,CACL,eAAK,SAAS,EAAC,yBAAyB,aACrC,QAAQ,IAAI,CACX,KAAC,sBAAsB,IAAC,KAAK,EAAC,eAAe,EAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAI,CACrF,EACA,OAAO,IAAI,QAAQ,IAAI,CACtB,KAAC,sBAAsB,IAAC,KAAK,EAAC,eAAe,EAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAI,CACrF,IACG,CACP,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport type { ModerationHandlers } from '@ttt-productions/chat-core';\nimport { Button, useAsyncAction } from '@ttt-productions/ui-core/react';\n\n// Rethrown on a timer so the rejection reaches the global error channel, as an unawaited one would.\nfunction reraise(error: unknown) {\n setTimeout(() => {\n throw error;\n });\n}\n\nfunction ModerationActionButton(props: { label: string; onAction: () => void | Promise<void> }) {\n const { label, onAction } = props;\n const action = useAsyncAction(\n async () => {\n await onAction();\n },\n { onError: reraise },\n );\n\n return (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"h-auto p-0 text-xs opacity-70 hover:opacity-100\"\n onClick={() => void action.run()}\n pending={action.pending}\n >\n {label}\n </Button>\n );\n}\n\nexport function MessageActions(props: {\n messageId: string;\n isAdmin: boolean;\n handlers?: ModerationHandlers;\n}) {\n const { messageId, isAdmin, handlers } = props;\n const onReport = handlers?.onReportMessage;\n const onDelete = handlers?.onDeleteMessage;\n\n return (\n <div className=\"flex items-center gap-2\">\n {onReport && <ModerationActionButton label=\"Report\" onAction={() => onReport(messageId)} />}\n {isAdmin && onDelete && (\n <ModerationActionButton label=\"Delete\" onAction={() => onDelete(messageId)} />\n )}\n </div>\n );\n}\n\nexport function ThreadActions(props: {\n threadId: string;\n isAdmin: boolean;\n handlers?: ModerationHandlers;\n}) {\n const { threadId, isAdmin, handlers } = props;\n const onReport = handlers?.onReportThread;\n const onDelete = handlers?.onDeleteThread;\n\n return (\n <div className=\"flex items-center gap-3\">\n {onReport && (\n <ModerationActionButton label=\"Report thread\" onAction={() => onReport(threadId)} />\n )}\n {isAdmin && onDelete && (\n <ModerationActionButton label=\"Delete thread\" onAction={() => onDelete(threadId)} />\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"menus.js","sourceRoot":"","sources":["../../src/ui/menus.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAExE,oGAAoG;AACpG,SAAS,OAAO,CAAC,KAAc;IAC7B,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,sBAAsB,CAAC,KAA8D;IAC5F,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAC3B,KAAK,IAAI,EAAE;QACT,MAAM,QAAQ,EAAE,CAAC;IACnB,CAAC,EACD,EAAE,OAAO,EAAE,OAAO,EAAE,CACrB,CAAC;IAEF,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,MAAM,EACd,IAAI,EAAC,IAAI,EACT,SAAS,EAAC,iDAAiD,EAC3D,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,GAAG,EAAE,EAChC,OAAO,EAAE,MAAM,CAAC,OAAO,YAEtB,KAAK,GACC,CACV,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA2D;IACxF,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACtC,MAAM,QAAQ,GAAG,QAAQ,EAAE,eAAe,CAAC;IAE3C,OAAO,CACL,cAAK,SAAS,EAAC,yBAAyB,YACrC,QAAQ,IAAI,KAAC,sBAAsB,IAAC,KAAK,EAAC,QAAQ,EAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAI,GACvF,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA0D;IACtF,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACrC,MAAM,QAAQ,GAAG,QAAQ,EAAE,cAAc,CAAC;IAE1C,OAAO,CACL,cAAK,SAAS,EAAC,yBAAyB,YACrC,QAAQ,IAAI,CACX,KAAC,sBAAsB,IAAC,KAAK,EAAC,eAAe,EAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAI,CACrF,GACG,CACP,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport type { ModerationHandlers } from '@ttt-productions/chat-core';\nimport { Button, useAsyncAction } from '@ttt-productions/ui-core/react';\n\n// Rethrown on a timer so the rejection reaches the global error channel, as an unawaited one would.\nfunction reraise(error: unknown) {\n setTimeout(() => {\n throw error;\n });\n}\n\nfunction ModerationActionButton(props: { label: string; onAction: () => void | Promise<void> }) {\n const { label, onAction } = props;\n const action = useAsyncAction(\n async () => {\n await onAction();\n },\n { onError: reraise },\n );\n\n return (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"h-auto p-0 text-xs opacity-70 hover:opacity-100\"\n onClick={() => void action.run()}\n pending={action.pending}\n >\n {label}\n </Button>\n );\n}\n\nexport function MessageActions(props: { messageId: string; handlers?: ModerationHandlers }) {\n const { messageId, handlers } = props;\n const onReport = handlers?.onReportMessage;\n\n return (\n <div className=\"flex items-center gap-2\">\n {onReport && <ModerationActionButton label=\"Report\" onAction={() => onReport(messageId)} />}\n </div>\n );\n}\n\nexport function ThreadActions(props: { threadId: string; handlers?: ModerationHandlers }) {\n const { threadId, handlers } = props;\n const onReport = handlers?.onReportThread;\n\n return (\n <div className=\"flex items-center gap-3\">\n {onReport && (\n <ModerationActionButton label=\"Report thread\" onAction={() => onReport(threadId)} />\n )}\n </div>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/chat-react",
3
- "version": "0.13.17",
3
+ "version": "0.13.19",
4
4
  "description": "Chat React UI, hooks, and Firebase-client adapter for TTT Productions apps (realtime newest window + infinite older pagination)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@tanstack/react-query": ">=5.0.0",
36
- "@ttt-productions/query-core": "^0.14.23",
36
+ "@ttt-productions/query-core": "^0.14.25",
37
37
  "firebase": ">=12.0.0",
38
38
  "lucide-react": "^1.30.0",
39
39
  "react": ">=19.0.0",
@@ -60,16 +60,16 @@
60
60
  }
61
61
  },
62
62
  "dependencies": {
63
- "@ttt-productions/chat-core": "^0.11.5",
64
- "@ttt-productions/chat-schemas": "^0.10.8",
63
+ "@ttt-productions/chat-core": "^0.11.7",
64
+ "@ttt-productions/chat-schemas": "^0.10.10",
65
65
  "@ttt-productions/realtime-core": "^0.1.5",
66
66
  "@ttt-productions/ui-core": "^0.11.35",
67
- "@ttt-productions/upload-ui": "^0.7.18",
67
+ "@ttt-productions/upload-ui": "^0.7.19",
68
68
  "@ttt-productions/mobile-core": "^0.9.6",
69
- "@ttt-productions/firebase-helpers": "^0.12.15"
69
+ "@ttt-productions/firebase-helpers": "^0.12.16"
70
70
  },
71
71
  "devDependencies": {
72
- "@ttt-productions/query-core": "^0.14.23",
72
+ "@ttt-productions/query-core": "^0.14.25",
73
73
  "@types/react": "^19.0.0",
74
74
  "@types/react-dom": "^19.0.0",
75
75
  "lucide-react": "^1.30.0",