@ttt-productions/chat-core 0.10.1 → 0.10.2

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 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC","sourcesContent":["// Chat-domain limits and operational constants.\n\n/**\n * Maximum chat message text length (characters). Consumer apps may override\n * by passing a custom value through `ChatCoreConfig`; this constant is the\n * package default and is also what chat-core's own internal schemas use.\n */\nexport const MAX_CHAT_MESSAGE_LENGTH = 4000;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"grouping.js","sourceRoot":"","sources":["../src/grouping.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAC5B,IAA+B,EAC/B,GAAkB;IAElB,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IACjD,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAC7C,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,aAAa,GAAG,IAAI,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"grouping.js","sourceRoot":"","sources":["../src/grouping.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAC5B,IAA+B,EAC/B,GAAkB;IAElB,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IACjD,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAC7C,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,aAAa,GAAG,IAAI,CAAC;AACrD,CAAC","sourcesContent":["import type { ChatMessageV1 } from \"./types.js\";\nimport { GROUP_GAP_SEC } from \"./types.js\";\n\n/**\n * Determine if `msg` is a \"continuation\" of `prev` for the purpose of\n * visually grouping consecutive messages from the same sender in the\n * message list.\n *\n * A message is a continuation when ALL of the following hold:\n * - `prev` exists.\n * - Neither `prev` nor `msg` is a system message.\n * - `prev` and `msg` have the same `senderId`.\n * - `msg.createdAt - prev.createdAt` is in the inclusive range\n * `[0, GROUP_GAP_SEC * 1000]` milliseconds. Negative gaps\n * (out-of-order messages) are treated as not-a-continuation.\n *\n * Pure function. No side effects. Safe to call from server and client.\n */\nexport function isContinuation(\n prev: ChatMessageV1 | undefined,\n msg: ChatMessageV1,\n): boolean {\n if (!prev) return false;\n if (msg.isSystemMessage || prev.isSystemMessage) return false;\n if (msg.senderId !== prev.senderId) return false;\n const gapMs = msg.createdAt - prev.createdAt;\n return gapMs >= 0 && gapMs <= GROUP_GAP_SEC * 1000;\n}\n"]}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,8EAA8E;AAC9E,uDAAuD;AACvD,EAAE;AACF,8EAA8E;AAC9E,iEAAiE;AACjE,iCAAiC;AAEjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAqB/C,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,8EAA8E;AAC9E,uDAAuD;AACvD,EAAE;AACF,8EAA8E;AAC9E,iEAAiE;AACjE,iCAAiC;AAEjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAqB/C,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["// Pure chat-core barrel. No React, no Firebase (client or admin) reachable from\n// here — safe for Cloud Functions, scripts, and future native/TV clients that\n// only need the parser, contracts, and grouping logic.\n//\n// The chat React UI, hooks, Firestore-client adapter config, and render types\n// live in @ttt-productions/chat-react. Pure chat schemas live in\n// @ttt-productions/chat-schemas.\n\nexport { MAX_CHAT_MESSAGE_LENGTH } from \"./constants.js\";\n\nexport { GROUP_GAP_SEC } from \"./types.js\";\nexport { isContinuation } from \"./grouping.js\";\nexport type {\n ChatAttachment,\n ChatId,\n ChatThreadV1,\n ChatMessageV1,\n ChatAccessMode,\n SendAttachmentInput,\n SendAttachmentFn,\n ModerationHandlers,\n ChatNameResolver,\n ChatPrewarmSenders,\n} from \"./types.js\";\n\nexport type {\n MentionRef,\n ParsedSegment,\n MentionProvider,\n RecentMentionsAdapter,\n MentionAnchor,\n} from \"./mentions/types.js\";\nexport { parseMentionTokens, formatMentionToken } from \"./mentions/parser.js\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/mentions/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,QAAQ,GAAG,gDAAgD,CAAC;AAElE;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY;IAEZ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,MAAM,QAAQ,GAAgC,EAAE,CAAC;IACjD,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;IACvB,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC;QAClD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;QAEjC,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,GAAG,GAAsB;YAC7B,IAAI,EAAE,OAAgB;YACtB,EAAE,EAAE,KAAK;YACT,WAAW,EAAE,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK;SACtE,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;QAExC,SAAS,GAAG,GAAG,CAAC;IAClB,CAAC;IAED,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAuB,GAAsB;IAC7E,OAAO,KAAK,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,WAAW,GAAG,CAAC;AACvD,CAAC"}
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/mentions/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,QAAQ,GAAG,gDAAgD,CAAC;AAElE;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY;IAEZ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,MAAM,QAAQ,GAAgC,EAAE,CAAC;IACjD,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;IACvB,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC;QAClD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;QAEjC,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,GAAG,GAAsB;YAC7B,IAAI,EAAE,OAAgB;YACtB,EAAE,EAAE,KAAK;YACT,WAAW,EAAE,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK;SACtE,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;QAExC,SAAS,GAAG,GAAG,CAAC;IAClB,CAAC;IAED,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAuB,GAAsB;IAC7E,OAAO,KAAK,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,WAAW,GAAG,CAAC;AACvD,CAAC","sourcesContent":["/**\n * Mention token parser.\n *\n * Token grammar:\n * @[kind:id] — minimal form; displayText defaults to id\n * @[kind:id|displayText] — full form\n *\n * kind : [A-Za-z0-9-]+\n * id : [^\\]|]+\n * displayText : [^\\]]* (may be empty; defaults to id when omitted)\n *\n * Anything not matching the grammar is preserved verbatim as text segments.\n * Malformed tokens (unterminated, illegal characters) are NOT parsed — they\n * pass through as plain text so the user always sees what they typed.\n */\n\nimport type { MentionRef, ParsedSegment } from './types.js';\n\nconst TOKEN_RE = /@\\[([A-Za-z0-9-]+):([^\\]|]+)(?:\\|([^\\]]*))?\\]/g;\n\n/**\n * Parse a string of message text into an ordered array of text and mention\n * segments. Always returns at least one segment for non-empty input. For an\n * empty string, returns an empty array.\n *\n * The parser is intentionally permissive in `TKind` — it returns mentions\n * with `kind` typed as `TKind`, but does NOT validate that the parsed kind\n * is a member of the consumer's union. Validation, if needed, is the\n * consumer's job; the wire token is the source of truth.\n */\nexport function parseMentionTokens<TKind extends string = string>(\n text: string,\n): Array<ParsedSegment<TKind>> {\n if (text.length === 0) return [];\n\n const segments: Array<ParsedSegment<TKind>> = [];\n let lastIndex = 0;\n\n TOKEN_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = TOKEN_RE.exec(text)) !== null) {\n const [whole, kindRaw, idRaw, displayRaw] = match;\n const start = match.index;\n const end = start + whole.length;\n\n if (start > lastIndex) {\n segments.push({ type: 'text', text: text.slice(lastIndex, start) });\n }\n\n const ref: MentionRef<TKind> = {\n kind: kindRaw as TKind,\n id: idRaw,\n displayText: displayRaw && displayRaw.length > 0 ? displayRaw : idRaw,\n };\n segments.push({ type: 'mention', ref });\n\n lastIndex = end;\n }\n\n if (lastIndex < text.length) {\n segments.push({ type: 'text', text: text.slice(lastIndex) });\n }\n\n return segments;\n}\n\n/**\n * Build a wire token from a MentionRef. Inverse of the parser's per-token\n * branch — same grammar.\n */\nexport function formatMentionToken<TKind extends string>(ref: MentionRef<TKind>): string {\n return `@[${ref.kind}:${ref.id}|${ref.displayText}]`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/mentions/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/mentions/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG","sourcesContent":["/**\n * Generic mention system types.\n *\n * chat-core owns the mention infrastructure (parsing, autocomplete UI, keyboard\n * behavior, composer insertion, message text rendering) but knows nothing about\n * what a \"user\" or \"entity\" is. The consumer parameterizes `TKind` to its own\n * union and supplies one `MentionProvider` per kind.\n *\n * Wire format — mentions are encoded inline in the message's `text` field as\n * `@[kind:id|displayText]` tokens. The renderer parses them out at display\n * time. No schema change to ChatMessage.\n *\n * These are the PURE mention contracts. The React-coupled render type for\n * autocomplete rows (`renderResult`) lives in @ttt-productions/chat-react.\n */\n\n/**\n * Resolved mention — the value that ends up in the wire token after the user\n * selects an autocomplete result.\n *\n * `kind` is the discriminator supplied by the consumer (e.g. `'member'`, `'tag'`); `id` is the\n * consumer-chosen stable identifier; `displayText` is the user-facing label\n * (typically a display name or title).\n */\nexport type MentionRef<TKind extends string = string> = {\n kind: TKind;\n id: string;\n displayText: string;\n};\n\n/**\n * Single segment in parsed message text. Either a literal text run or a\n * resolved mention. The renderer walks an ordered array of these.\n */\nexport type ParsedSegment<TKind extends string = string> =\n | { type: 'text'; text: string }\n | { type: 'mention'; ref: MentionRef<TKind> };\n\n/**\n * Provider supplying autocomplete results for one mention kind.\n *\n * - `kind` — the discriminator. Must match `MentionRef.kind` for results\n * returned by `search`.\n * - `label` — human-readable label for the section / tab.\n * - `search` — async query. Receives the current query string (after the\n * trigger character), the consumer-supplied `context`, and an optional\n * `signal` for cancellation. Returns up to whatever limit the provider\n * chooses; the chat UI renders all returned results.\n *\n * `TContext` is consumer-owned — pass anything you need inside `search`\n * (Firestore db, current user id, surface-specific filters). The mention\n * system never inspects it.\n *\n * Optional per-row custom rendering (`renderResult`) is a React concern and is\n * layered on top of this pure provider by `RenderableMentionProvider` in\n * @ttt-productions/chat-react.\n */\nexport type MentionProvider<TKind extends string = string, TContext = unknown> = {\n kind: TKind;\n label: string;\n search: (args: {\n query: string;\n context: TContext;\n signal?: AbortSignal;\n }) => Promise<MentionRef<TKind>[]>;\n};\n\n/**\n * Optional recent-mentions adapter. When provided, the autocomplete UI shows\n * recent picks when the user opens the dropdown without typing a query\n * (or after typing fewer than the configured minimum characters).\n *\n * `getRecent` MAY be async — the autocomplete renders a spinner while it\n * resolves. `recordUse` is fired after a successful selection so consumers\n * can persist usage.\n */\nexport type RecentMentionsAdapter<TKind extends string = string> = {\n getRecent: () => MentionRef<TKind>[] | Promise<MentionRef<TKind>[]>;\n recordUse?: (ref: MentionRef<TKind>) => void | Promise<void>;\n};\n\n/**\n * Anchor describing the position of an inserted mention inside the composer's\n * text buffer. Tracked in component state so positionally-collocated mentions\n * with the same display string disambiguate on send.\n *\n * - `start` / `end` — character offsets into the current textarea value.\n * - `ref` — the resolved MentionRef.\n */\nexport type MentionAnchor<TKind extends string = string> = {\n start: number;\n end: number;\n ref: MentionRef<TKind>;\n};\n"]}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA0GA,+CAA+C;AAC/C,kDAAkD;AAClD,+CAA+C;AAE/C,iEAAiE;AACjE,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA0GA,+CAA+C;AAC/C,kDAAkD;AAClD,+CAA+C;AAE/C,iEAAiE;AACjE,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC","sourcesContent":["import type { ChatAttachment, ReplyTo } from \"@ttt-productions/chat-schemas\";\n\n// ============================================\n// CHAT ATTACHMENT\n// ============================================\n\n// Re-exported from @ttt-productions/chat-schemas, the Tier 0 source of truth.\n// chat-schemas is server-safe and can be consumed by both chat-core (pure) and\n// backend code without forcing the chat UI dep graph on backend callers.\n// An attachment carries a lifecycle `status` (pending -> ready | failed): a\n// pending/failed placeholder is visible only to the sender (rule-enforced);\n// other participants see it once it is `ready` (or absent = ready/legacy).\nexport type { ChatAttachment };\n\n// ============================================\n// THREAD & MESSAGE\n// ============================================\n\nexport type ChatId = string;\n\nexport type ChatThreadV1 = {\n allowedUserIds: string[];\n participantUserIds?: string[];\n createdAt: number; // millis\n lastMessageAt: number; // millis\n status?: string; // opaque to chat-core\n meta?: Record<string, unknown>; // opaque\n};\n\nexport type ChatMessageV1 = {\n messageId: string;\n threadId: string;\n createdAt: number; // millis\n senderId: string;\n text?: string;\n type?: string; // optional for renderer registry\n attachment?: ChatAttachment; // single, not array\n replyTo?: ReplyTo;\n isSystemMessage?: boolean;\n /** Moderation tombstone flag on the stored message (backend-written); consumers\n * render a tombstone instead of the content when true. */\n hidden?: boolean;\n meta?: Record<string, unknown>;\n};\n\n// ============================================\n// ACCESS\n// ============================================\n\n/**\n * Access mode controls how chat decides whether the current user can\n * read/write a thread.\n *\n * - \"firestore-rules\" — trust Firestore rules. canAccessThread returns true\n * for any signed-in user; if rules deny, onSnapshot will surface\n * permission-denied. Use this when access depends on data the client doesn't\n * reliably know up-front (entity membership, invite participation, etc.).\n *\n * - \"explicit-allowlist\" — access is enforced client-side via\n * threadAllowedUserIds. The list is required in this mode. Use this when the\n * consumer already knows the participants (admin/support threads).\n *\n * Admins (`isAdmin: true`) bypass both modes.\n */\nexport type ChatAccessMode = \"firestore-rules\" | \"explicit-allowlist\";\n\n// ============================================\n// ATTACHMENT REGISTRATION CALLBACK\n// ============================================\n\n/**\n * Called by the composer after the file has been uploaded to Storage. The\n * consumer wires this to a backend callable that writes the pendingMedia doc\n * (caption text + reply pointer) AND a placeholder chat message doc with\n * `attachment.status: 'pending'`. The media processor later flips that same\n * message doc to `ready` (sets `url`) or `failed`.\n *\n * The placeholder bubble therefore arrives through the normal Firestore\n * listener — the composer does not need a separate optimistic local state.\n * While pending/failed the doc is sender-only (rule-enforced).\n */\nexport type SendAttachmentInput = {\n text: string;\n attachment: {\n attachmentId: string; // pendingMedia doc ID\n storagePath: string; // uploads/chat-attachment/{uid}/{attachmentId}\n originalFileName: string;\n type: ChatAttachment[\"type\"];\n size: number;\n };\n replyTo?: ChatMessageV1[\"replyTo\"];\n};\n\nexport type SendAttachmentFn = (input: SendAttachmentInput) => Promise<void>;\n\n// ============================================\n// MODERATION\n// ============================================\n\nexport type ModerationHandlers = {\n onReportMessage?: (messageId: string, reason?: string) => void | Promise<void>;\n onReportThread?: (threadId: string, reason?: string) => void | Promise<void>;\n onDeleteMessage?: (messageId: string) => void | Promise<void>; // admin only (gated)\n onDeleteThread?: (threadId: string) => void | Promise<void>; // admin only (gated)\n};\n\n// ============================================\n// MESSAGE GROUPING (internal, exported for tests)\n// ============================================\n\n/** Max seconds between messages to be grouped as continuation */\nexport const GROUP_GAP_SEC = 120;\n\n// ============================================\n// NAME RESOLUTION\n// ============================================\n\n/**\n * Resolves a senderId to a display name synchronously from app-side cache.\n * Returns null if the sender is unknown or the cache hasn't loaded yet —\n * the chat UI will render a stable fallback (\"User\") in that case.\n */\nexport type ChatNameResolver = (senderId: string) => string | null;\n\n/**\n * Optional pre-warm callback. The chat UI calls this with the deduped list of\n * senderIds visible in the current message page so the consuming app can\n * batch-fetch names into its cache. Implementations should be idempotent —\n * the same id list will be passed across re-renders.\n */\nexport type ChatPrewarmSenders = (senderIds: string[]) => void;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/chat-core",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Pure chat contracts, mention parser/serializer, and grouping helpers for TTT Productions apps (no React, no Firebase)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "prepublishOnly": "npm run clean && npm run build"
29
29
  },
30
30
  "dependencies": {
31
- "@ttt-productions/chat-schemas": "^0.9.0"
31
+ "@ttt-productions/chat-schemas": "^0.9.2"
32
32
  },
33
33
  "devDependencies": {
34
34
  "typescript": "^5.8.3"