Repository navigation
feat(react): align support hooks with client query - #14
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a generic in-memory store system and domain stores (conversations, messages, website) to @cossistant/core, integrates them into CossistantClient with optimistic and realtime flows, and migrates React/Next hooks and provider code to use store-backed querying; includes tests and API/event schema additions. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor UI
participant ReactHook as useSendMessage
participant Client as CossistantClient
participant Stores as {conversations|messages}
participant REST as API
UI->>ReactHook: mutate({ conversationId?, bodyMd, defaultMessages? })
alt No conversationId (new)
ReactHook->>Client: initiateConversation({ defaultMessages })
Client->>Stores: conversationsStore.ingestConversation(pending)
Client-->>ReactHook: { conversationId, initialMessages? }
end
ReactHook->>Client: sendMessage({ conversationId, bodyMd, createIfPending: true })
Client->>Stores: messagesStore.ingestMessage(optimistic)
Client->>REST: POST /messages
alt Success
REST-->>Client: { message, conversation? }
Client->>Stores: messagesStore.finalizeMessage(...)
Client->>Stores: conversationsStore.ingestConversation(updated?)
ReactHook-->>UI: { conversationId, messageId, conversation? }
else Failure
Client->>Stores: messagesStore.removeMessage(optimisticId)
ReactHook-->>UI: throw Error
end
sequenceDiagram
autonumber
actor WS as Realtime
participant Client as CossistantClient
participant Stores as {messages|conversations}
note over WS,Client: MESSAGE_CREATED / CONVERSATION_* events
WS-->>Client: RealtimeEvent
Client->>Client: handleRealtimeEvent(event)
alt MESSAGE_CREATED
Client->>Stores: messagesStore.ingestRealtime(event)
else CONVERSATION_SEEN / TYPING / EVENT_CREATED
Client->>Stores: conversationsStore.ingestConversation/merge(...)
end
sequenceDiagram
autonumber
actor UI
participant Hook as useWebsiteStore
participant Client as CossistantClient
participant Store as websiteStore
participant REST as API
UI->>Hook: mount()
Hook->>Store: select state
alt state.status == idle
Hook->>Client: fetchWebsite()
Client->>Store: setLoading()
Client->>REST: GET /website
alt Success
REST-->>Client: website
Client->>Store: setWebsite(website)
else Failure
Client->>Store: setError(err)
end
else Cached
Hook-->>UI: website from store
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/react/src/support/pages/home.tsx (1)
111-139: Defensive check for optionalquickOptions
quickOptions.lengthwill throw ifquickOptionsis undefined. Use a nullish-safe length.- {quickOptions.length > 0 && ( + {(quickOptions?.length ?? 0) > 0 && (packages/core/src/rest-client.ts (1)
27-27: Add missingnanoiddependency to core package
The utils module in packages/core/src/utils.ts importscustomAlphabetfromnanoid, causing build/tests to fail with “Cannot find package 'nanoid'”. Add the following under dependencies inpackages/core/package.jsonto restore the build:--- a/packages/core/package.json +++ b/packages/core/package.json @@ -5,6 +5,9 @@ "license": "MIT", "type": "module", "exports": { … }, + "dependencies": { + "nanoid": "^5.0.7" + } }(Optional long-term) Replace
nanoidwith a Web Crypto–based ID generator to remove this dependency.
🧹 Nitpick comments (27)
packages/react/src/hooks/use-store-selector.ts (1)
1-29: Stabilize the subscribe/getSnapshot callbacks passed touseSyncExternalStoreBoth
subscribeand the inline() => store.getState()closures are re-created every render.useSyncExternalStoretreats changes in either callback as a signal to tear down and recreate the subscription, so we end up unsubscribing and resubscribing on every render even when nothing changed. This adds avoidable churn to the store listeners. Capture them inuseCallback(or hoist stable references) so the identities stay fixed across renders.-import { useRef, useSyncExternalStore } from "react"; +import { useCallback, useRef, useSyncExternalStore } from "react"; @@ - const subscribe = (onStoreChange: () => void) => - store.subscribe(() => { - onStoreChange(); - }); - - const snapshot = useSyncExternalStore(subscribe, () => store.getState(), () => store.getState()); + const subscribe = useCallback( + (onStoreChange: () => void) => + store.subscribe(() => { + onStoreChange(); + }), + [store] + ); + const getSnapshot = useCallback(() => store.getState(), [store]); + + const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);packages/core/LLM_GUIDE.md (1)
7-26: Add a language hint to the fenced directory treemarkdownlint (MD040) complains about the bare triple backticks here. Annotating the block (e.g., with
text) keeps the lint job green without changing the rendered output.-``` +```text packages/core/ ├── package.json # workspace metadata @@ -``` +```Based on static analysis hints
packages/react/src/support/hooks/index.ts (2)
2-6: Preferexport type { ... }for type-only re-exportsAlign with our TS guideline: split value and type exports for clearer ESM tree-shaking.
Apply:
-export { - type ConversationEventItem, - type GroupedMessage, - type UseGroupedMessagesProps, - useGroupedMessages, -} from "../../hooks/use-grouped-messages"; +export type { + ConversationEventItem, + GroupedMessage, + UseGroupedMessagesProps, +} from "../../hooks/use-grouped-messages"; +export { useGroupedMessages } from "../../hooks/use-grouped-messages";
8-8: Re-export the hook’s public types alongside the hookIf these types are part of the public surface, re-export them here for discoverability (and also at the package entrypoint).
-export { useWebsite } from "./use-website"; +export { useWebsite } from "./use-website"; +export type { UseWebsiteOptions, UseWebsiteResult } from "./use-website";packages/react/src/support/pages/home.tsx (2)
45-61: Remove console logs (repo guideline: no console)Drop debug logs or guard behind a dev-only logger to satisfy lint rules.
- console.log( - "[HomePage] Conversations:", - conversations?.map((c) => ({ - id: c.id, - status: c.status, - updatedAt: c.updatedAt, - })) - ); + // debug: inspect conversations via devtools or a scoped logger if needed ... - console.log( - "[HomePage] Last open conversation:", - _lastOpenConversation?.id - );
1-3: Minor: remove unused type-only import
import type React from "react";isn’t used with the automatic JSX runtime.-import type React from "react";packages/core/src/rest-client.ts (1)
227-237: Cache resolved visitor ID to avoid repeated storage lookupsSet
this.visitorIdwhen deriving from storage.- getCurrentVisitorId(): string | null { - if (this.visitorId) { - return this.visitorId; - } - if (!this.websiteId) { - return null; - } - return getVisitorId(this.websiteId) ?? null; - } + getCurrentVisitorId(): string | null { + if (this.visitorId) return this.visitorId; + if (!this.websiteId) return null; + const stored = getVisitorId(this.websiteId); + if (stored) this.visitorId = stored; + return stored ?? null; + }packages/react/src/support/hooks/use-website.ts (2)
1-12: Add JSDoc for public hook and typesDocument purpose, options, defaults, and error states to meet our React package docs guideline.
+/** + * useWebsite + * Returns website data from the core website store and keeps it refreshed via useClientQuery. + * @param options.refetchInterval - ms or false to disable (default: undefined -> store default) + * @param options.refetchOnWindowFocus - default true + * @returns {UseWebsiteResult} website, status, isLoading, error, refresh() + */
1-8: Optional: re-export types instead of aliasingYou can re-export the store types to avoid indirection.
-export type UseWebsiteOptions = UseWebsiteStoreOptions; -export type UseWebsiteResult = UseWebsiteStoreResult; +export type { UseWebsiteStoreOptions as UseWebsiteOptions, UseWebsiteStoreResult as UseWebsiteResult } from "../../hooks/use-website-store";packages/core/src/store/website-store.test.ts (2)
106-127: Add a concurrency test for in‑flight request de‑duplicationYou validate cache after the first resolve; also assert coalescing when two fetchWebsite calls start before the first resolves.
Example test to append:
+ it("de-duplicates concurrent in-flight fetches", async () => { + const website = createWebsite({ id: "site-concurrent" }); + let resolver!: (v: unknown) => void; + let fetchCount = 0; + + // @ts-expect-error override private for test + client.restClient = { + getWebsite: () => + new Promise<PublicWebsiteResponse>((resolve) => { + fetchCount += 1; + resolver = resolve; + }), + }; + + const p1 = client.fetchWebsite(); + const p2 = client.fetchWebsite(); + expect(fetchCount).toBe(1); + + resolver(website); + const [r1, r2] = await Promise.all([p1, p2]); + expect(r1).toEqual(website); + expect(r2).toEqual(website); + });
129-154: Good error → recovery flow; consider asserting request handle resetAfter a failure and forced refetch, also assert websiteRequest was cleared to avoid stale handles.
Append:
+ // @ts-expect-error private field inspection for test + expect(client.websiteRequest).toBeNull();packages/react/src/hooks/use-website-store.ts (2)
46-49: Expose refetchOnMount in options and thread it throughLet consumers opt-in to mount refetch even when state isn’t idle (e.g., after an error).
export type UseWebsiteStoreOptions = { refetchInterval?: number | false; refetchOnWindowFocus?: boolean; + refetchOnMount?: boolean; }; ... - refetchOnMount: state.status === "idle", + refetchOnMount: options.refetchOnMount ?? state.status === "idle",Also applies to: 75-79
63-103: Add minimal JSDoc for public hook and typesPer guidelines, document purpose, params, return shape, and edge cases (client=null).
As per coding guidelines
packages/core/src/store/create-store.ts (1)
20-26: Isolate subscriber failures.One listener throwing will currently prevent others from receiving updates. Wrap callbacks to avoid cascading failures.
- for (const listener of listeners) { - listener(state); - } + for (const listener of listeners) { + try { + listener(state); + } catch { + // Swallow to isolate subscribers; consider surfacing via a diagnostic hook if needed. + } + }packages/react/src/hooks/use-client-query.ts (2)
107-110: Optionally refetch on client instance change.Switching clients usually implies new config/context; consider a refetch when
clientchanges (skip on first mount).useEffect(() => { fetchIdRef.current += 1; -}, [client]); +}, [client]); + +useEffect(() => { + if (!client || !enabled) return; + if (!hasFetchedRef.current) return; // avoid double-fetch on initial mount + void execute(latestArgsRef.current).catch(() => {}); +}, [client, enabled, execute]);
44-51: Stable key hashing (optional).
JSON.stringifycan flap when object key order differs. If yourqueryKeyincludes objects, prefer a stable hash.-function toHash(key: ReadonlyArray<unknown> | string): string { +function toHash(key: ReadonlyArray<unknown> | string): string { if (typeof key === "string") { return key; } - - return JSON.stringify(key); + const stable = (v: unknown): unknown => + v && typeof v === "object" && !Array.isArray(v) + ? Object.keys(v as Record<string, unknown>) + .sort() + .reduce<Record<string, unknown>>((acc, k) => { + acc[k] = stable((v as any)[k]); + return acc; + }, {}) + : Array.isArray(v) + ? (v as unknown[]).map(stable) + : v; + return JSON.stringify(stable(key)); }packages/core/src/store/conversations-store.ts (2)
161-179: Consider reordering on single-conversation updates.ingestConversation appends new IDs but doesn’t move updated ones to the front, which can leave “recently active” threads buried. Recommend moving the conversation to the head when it’s new or changed.
Apply:
function applyConversation( state: ConversationsState, conversation: Conversation ): ConversationsState { const previous = state.byId[conversation.id]; - const sameConversation = previous ? isSameConversation(previous, conversation) : false; - const byId = sameConversation ? state.byId : { ...state.byId, [conversation.id]: conversation }; - const hasId = state.ids.includes(conversation.id); - const ids = hasId ? state.ids : [...state.ids, conversation.id]; + const changed = previous ? !isSameConversation(previous, conversation) : true; + const byId = changed ? { ...state.byId, [conversation.id]: conversation } : state.byId; + const hasId = state.ids.includes(conversation.id); + let ids = state.ids; + if (!hasId) { + // New: place at front + ids = [conversation.id, ...state.ids]; + } else if (changed) { + // Updated: move to front + const rest = state.ids.filter((id) => id !== conversation.id); + ids = [conversation.id, ...rest]; + } if (byId === state.byId && ids === state.ids) { return state; } return { byId, ids, pagination: state.pagination, }; }
94-101: Minor perf: avoid O(n×m) includes on first-page merge.Use a Set for incoming IDs.
- const rest = existing.filter((id) => !incoming.includes(id)); - const next = [...incoming, ...rest]; + const incomingSet = new Set(incoming); + const rest = existing.filter((id) => !incomingSet.has(id)); + const next = [...incoming, ...rest];packages/react/src/hooks/use-conversations-store.ts (1)
50-59: Reduce duplication by reusing core selector.You can rely on core’s getConversations to keep derivation logic consistent across packages.
-import { useSupport } from "../provider"; +import { useSupport } from "../provider"; +import { getConversations } from "@cossistant/core"; @@ - return useStoreSelector( - client.conversationsStore, - (state: ConversationsState) => ({ - conversations: state.ids - .map((id) => state.byId[id]) - .filter((conversation): conversation is Conversation => conversation !== undefined), - pagination: state.pagination, - }), - areSelectionsEqual - ); + return useStoreSelector( + client.conversationsStore, + (state: ConversationsState) => ({ + conversations: getConversations(client.conversationsStore), + pagination: state.pagination, + }), + areSelectionsEqual + );packages/react/src/hooks/use-conversation-messages.ts (2)
20-25: Avoid re-renders from new object snapshots in fallback store.
getStatereturns a fresh object each time. Return a frozen singleton to keep referential stability.-const EMPTY_MESSAGES_STORE = { - getState: (): MessagesState => ({ conversations: {} }), +const EMPTY_MESSAGES_STATE: MessagesState = Object.freeze({ conversations: {} }); +const EMPTY_MESSAGES_STORE = { + getState: (): MessagesState => EMPTY_MESSAGES_STATE, subscribe: (_listener: (state: MessagesState) => void) => () => { // noop }, };
111-113: Expose refetching state or clarify isLoading semantics.
isLoadingreflects only the initial load; subsequent refetches won’t toggle it. Consider returning anisRefetchingflag (pass throughqueryLoading) or rename toisInitialLoadingto avoid confusion.packages/core/src/store/messages-store.ts (1)
20-22: Deterministic sort with tiebreakers.Add secondary keys to avoid flicker when timestamps collide.
-function sortMessages(messages: Message[]): Message[] { - return [...messages].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); -} +function sortMessages(messages: Message[]): Message[] { + return [...messages].sort((a, b) => { + const c = a.createdAt.getTime() - b.createdAt.getTime(); + if (c !== 0) return c; + const u = a.updatedAt.getTime() - b.updatedAt.getTime(); + if (u !== 0) return u; + return a.id.localeCompare(b.id); + }); +}packages/react/src/support/hooks/use-conversations.ts (2)
87-95: Expose isFetching to reflect background refetches; keep isLoading for initial-only.Current isLoading becomes false after first load even while refetching, hiding in-flight states from consumers.
Apply this diff to add isFetching without breaking initial-loading semantics:
export type UseConversationsResult = { conversations: ListConversationsResponse["conversations"]; pagination: ConversationPagination | null; isLoading: boolean; + isFetching: boolean; error: Error | null; refetch: ( args?: Partial<ListConversationsRequest> ) => Promise<ListConversationsResponse | undefined>; }; - const { refetch: queryRefetch, isLoading: queryLoading, error } = useClientQuery< + const { refetch: queryRefetch, isLoading: queryIsLoading, error } = useClientQuery< ListConversationsResponse, Partial<ListConversationsRequest> >({ client, queryKey: [ "conversations", clientKey, requestDefaults.limit ?? null, requestDefaults.page ?? null, requestDefaults.status ?? null, requestDefaults.orderBy ?? null, requestDefaults.order ?? null, ], queryFn: (instance, args) => instance.listConversations({ ...requestDefaults, ...args, }), enabled: Boolean(client && enabled), refetchInterval, refetchOnWindowFocus, refetchOnMount: selection.conversations.length === 0, initialArgs: requestDefaults, }); const isInitialLoad = selection.conversations.length === 0; - const isLoading = isInitialLoad ? queryLoading : false; + const isLoading = isInitialLoad ? queryIsLoading : false; return { conversations: selection.conversations, pagination: selection.pagination, isLoading, + isFetching: queryIsLoading, error, refetch, };Also applies to: 148-172, 182-191
97-105: Add concise JSDoc for the public hook and overloads.Documents purpose, options, provider dependency, and return shape for consumers.
packages/core/src/client.ts (3)
308-316: Update conversation.lastMessage after successful send.Non‑pending path finalizes the message but leaves conversationsStore.lastMessage stale, so lists won’t reflect the latest message.
Apply this diff:
try { const response = await this.restClient.sendMessage(payload); this.messagesStore.finalizeMessage(rest.conversationId, optimisticId, response.message); + const existing = this.conversationsStore.getState().byId[rest.conversationId]; + if (existing) { + this.conversationsStore.ingestConversation({ + ...existing, + lastMessage: response.message, + updatedAt: response.message.createdAt, + }); + } return response; } catch (error) {
319-327: Propagate realtime MESSAGE_CREATED to conversationsStore (lastMessage/updatedAt).Currently only messagesStore is updated; conversation lists can get stale.
Apply this diff:
handleRealtimeEvent(event: RealtimeEvent): void { switch (event.type) { case "MESSAGE_CREATED": this.messagesStore.ingestRealtime(event); + { + const msg = (event as any).data?.message as Message | undefined; + if (msg) { + const existing = this.conversationsStore.getState().byId[msg.conversationId]; + if (existing) { + this.conversationsStore.ingestConversation({ + ...existing, + lastMessage: msg, + updatedAt: msg.createdAt, + }); + } + } + } break; default: break; } }
127-129: Consider caching in getWebsite() instead of forcing network.getWebsite always forces a fetch; many callers expect a cached read with internal refresh via fetchWebsite. Optional, but reduces redundant calls.
- async getWebsite(): Promise<PublicWebsiteResponse> { - return this.fetchWebsite({ force: true }); - } + async getWebsite(): Promise<PublicWebsiteResponse> { + return this.fetchWebsite(); + }Also applies to: 90-126
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (26)
packages/core/LLM_GUIDE.md(1 hunks)packages/core/src/client.ts(4 hunks)packages/core/src/index.ts(1 hunks)packages/core/src/rest-client.ts(1 hunks)packages/core/src/store/conversations-store.test.ts(1 hunks)packages/core/src/store/conversations-store.ts(1 hunks)packages/core/src/store/create-store.ts(1 hunks)packages/core/src/store/messages-store.test.ts(1 hunks)packages/core/src/store/messages-store.ts(1 hunks)packages/core/src/store/website-store.test.ts(1 hunks)packages/core/src/store/website-store.ts(1 hunks)packages/react/src/hooks/index.ts(1 hunks)packages/react/src/hooks/use-client-query.ts(1 hunks)packages/react/src/hooks/use-conversation-messages.ts(1 hunks)packages/react/src/hooks/use-conversations-store.ts(1 hunks)packages/react/src/hooks/use-store-selector.ts(1 hunks)packages/react/src/hooks/use-website-store.ts(1 hunks)packages/react/src/index.ts(1 hunks)packages/react/src/provider.tsx(2 hunks)packages/react/src/support/hooks/index.ts(1 hunks)packages/react/src/support/hooks/use-conversation.ts(1 hunks)packages/react/src/support/hooks/use-conversations.ts(1 hunks)packages/react/src/support/hooks/use-website-data.ts(0 hunks)packages/react/src/support/hooks/use-website.ts(1 hunks)packages/react/src/support/pages/conversation-history.tsx(1 hunks)packages/react/src/support/pages/home.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- packages/react/src/support/hooks/use-website-data.ts
🧰 Additional context used
📓 Path-based instructions (15)
packages/react/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Develop headless React components (including and primitives) under packages/react/src/
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/react/src/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/store/create-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/core/src/rest-client.tspackages/core/src/store/conversations-store.tspackages/core/src/store/messages-store.test.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/core/src/store/website-store.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.tspackages/core/src/index.tspackages/core/src/client.tspackages/react/src/index.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't use theaccessKeyattribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/store/create-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/core/src/rest-client.tspackages/core/src/store/conversations-store.tspackages/core/src/store/messages-store.test.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/core/src/store/website-store.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.tspackages/core/src/index.tspackages/core/src/client.tspackages/react/src/index.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/store/create-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/core/src/rest-client.tspackages/core/src/store/conversations-store.tspackages/core/src/store/messages-store.test.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/core/src/store/website-store.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/hooks/use-website-store.tspackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.tspackages/core/src/index.tspackages/core/src/client.tspackages/react/src/index.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class members
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/store/create-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/core/src/rest-client.tspackages/core/src/store/conversations-store.tspackages/core/src/store/messages-store.test.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/core/src/store/website-store.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.tspackages/core/src/index.tspackages/core/src/client.tspackages/react/src/index.ts
**/*.{js,tsx,ts,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Use arrow functions instead of function expressions
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/store/create-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/core/src/rest-client.tspackages/core/src/store/conversations-store.tspackages/core/src/store/messages-store.test.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/core/src/store/website-store.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.tspackages/core/src/index.tspackages/core/src/client.tspackages/react/src/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace characters
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/store/create-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/core/src/rest-client.tspackages/core/src/store/conversations-store.tspackages/core/src/store/messages-store.test.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/core/src/store/website-store.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.tspackages/core/src/index.tspackages/core/src/client.tspackages/react/src/index.ts
**/*.{js,ts,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't redeclare variables, functions, classes, and types in the same scope
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/store/create-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/core/src/rest-client.tspackages/core/src/store/conversations-store.tspackages/core/src/store/messages-store.test.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/core/src/store/website-store.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.tspackages/core/src/index.tspackages/core/src/client.tspackages/react/src/index.ts
packages/react/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react-package-rules.mdc)
packages/react/**/*.{ts,tsx}: Components must render with no built-in styles (headless, unstyled)
Components must accept className, style, and asChild props to enable external styling/control
Do not use emotion, styled-components, or style-prop APIs; styling is via Tailwind/CSS through className/style
Use useRenderElement to create elements and encapsulate render logic (supporting intrinsic tags and render prop overrides)
Components with internal state must be controllable via props (e.g., value/defaultValue/onChange or isOpen/onOpenChange)
Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function child
May reference Base UI logic internally, but never expose Base UI or MUI APIs; do not leak getRootProps/getInputProps; prefer useRenderElement
Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic components
Provide a render prop API: render?: (props, state) => ReactElement, and allow className to be string or (state) => string
Support developer-owned rendering: components should not impose visual structure and should allow parent-driven markup via render prop/asChild
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/react/src/index.ts
packages/**
📄 CodeRabbit inference engine (AGENTS.md)
Put shared contracts and utilities in packages/* (e.g., core, types, transactional)
Files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/store/create-store.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/index.tspackages/core/src/rest-client.tspackages/core/src/store/conversations-store.tspackages/core/src/store/messages-store.test.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/pages/home.tsxpackages/core/LLM_GUIDE.mdpackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/index.tspackages/core/src/store/website-store.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.tspackages/react/src/support/pages/conversation-history.tsxpackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.tspackages/core/src/index.tspackages/core/src/client.tspackages/react/src/index.ts
**/*.{test.ts,test.tsx,spec.ts,spec.tsx,test.js,test.jsx,spec.js,spec.jsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
Include tests: unit tests for components, integration tests for composed components, accessibility tests, and error boundary tests
Files:
packages/core/src/store/messages-store.test.tspackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{test,spec}.{js,jsx,ts,tsx}: Don't nest describe() blocks too deeply in test files
Don't use callbacks in asynchronous tests and hooks
Don't have duplicate hooks in describe blocks
Don't use export or module.exports in test files
Don't use focused tests
Ensure the assertion function (e.g., expect) is placed inside an it() function call
Don't use disabled tests
Files:
packages/core/src/store/messages-store.test.tspackages/core/src/store/website-store.test.tspackages/core/src/store/conversations-store.test.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.tsx: Use the Compound Components pattern with namespaced exports (e.g., FormWizard.Root, FormWizard.Step) so pieces compose under a single parent
Ensure accessibility: keyboard navigation support
Ensure accessibility: include appropriate ARIA attributes
Ensure accessibility: use semantic markup for screen readers
Maintain proper focus management in interactive components
Structure components with forwardRef and typed props/refs as shown (ComponentRef, HTMLAttributes, ReactNode)
Root component acts as main context provider, manages global state, and handles initialization
Child components must be used within Root, read context via hooks, support asChild, and follow consistent props
Props ordering: list required props first; optional props have clear defaults; support common HTML attributes; expose className
Support composition with an asChild prop using Radix UI Slot to allow element replacement
Styling: support className, use data-* attributes for state, and allow style customization
Event handling: support standard DOM events, add custom events when needed, and manage propagation correctly
Base component pattern: forwardRef, choose Slot or intrinsic element via asChild, pass ref/className and spread props
Files:
packages/react/src/support/pages/home.tsxpackages/react/src/provider.tsxpackages/react/src/support/pages/conversation-history.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Ensure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include the words "image", "picture", or "photo" in img alt prop
Don't use an explicit role that's the same as the element's implicit role
Static elements with click handlers must have a valid role attribute
Always include a <title> element within SVG elements
Provide meaningful alt text for elements that require alt text
Ensure anchors have content accessible to screen readers
Assign tabIndex to non-interactive elements only when using aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Ensure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Elements with interactive roles and handlers must be focusable
Heading elements must have content accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
Prefer semantic elements instead of role attributes in JSX
Ensure all anchors are valid and navigable
Ensure all aria-* properties are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Don't use unnecessary fragmen...Files:
packages/react/src/support/pages/home.tsxpackages/react/src/provider.tsxpackages/react/src/support/pages/conversation-history.tsx**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx,html}: Ensure the preconnect attribute is used when using Google Fonts
Don't use target="_blank" without rel="noopener"
Use a recommended display strategy with Google FontsFiles:
packages/react/src/support/pages/home.tsxpackages/react/src/provider.tsxpackages/react/src/support/pages/conversation-history.tsx🧠 Learnings (2)
📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to apps/web/src/hooks/use*.{ts,tsx} : Name React hooks with a use* prefix and place them in apps/web/src/hooksApplied to files:
packages/react/src/support/hooks/use-website.tspackages/react/src/hooks/index.tspackages/react/src/support/hooks/index.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-website-store.ts📚 Learning: 2025-09-17T16:59:53.676Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/react-package-rules.mdc:0-0 Timestamp: 2025-09-17T16:59:53.676Z Learning: Applies to packages/react/**/*.{ts,tsx} : Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function childApplied to files:
packages/react/src/hooks/index.tspackages/react/src/support/hooks/index.ts🧬 Code graph analysis (19)
packages/react/src/support/hooks/use-website.ts (2)
packages/react/src/hooks/use-website-store.ts (3)
UseWebsiteStoreOptions(46-49)UseWebsiteStoreResult(38-44)useWebsiteStore(63-103)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-conversations-store.ts (4)
packages/core/src/index.ts (2)
ConversationPagination(16-16)ConversationsState(17-17)packages/core/src/store/conversations-store.ts (2)
ConversationPagination(5-5)ConversationsState(7-11)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-30)packages/react/src/hooks/use-client-query.ts (1)
packages/core/src/client.ts (1)
CossistantClient(62-333)packages/core/src/rest-client.ts (1)
packages/core/src/visitor-tracker.ts (2)
setVisitorId(64-83)getVisitorId(36-59)packages/core/src/store/conversations-store.ts (2)
packages/types/src/api/conversation.ts (1)
ListConversationsResponse(109-111)packages/core/src/store/create-store.ts (2)
Store(7-12)createStore(14-68)packages/core/src/store/messages-store.test.ts (5)
packages/types/src/realtime-events.ts (1)
RealtimeEvent(60-64)packages/core/src/store/messages-store.ts (1)
createMessagesStore(188-210)packages/core/src/client.ts (1)
CossistantClient(62-333)packages/types/src/api/message.ts (1)
SendMessageRequest(37-37)packages/types/src/api/conversation.ts (1)
CreateConversationRequestBody(35-37)packages/core/src/store/messages-store.ts (4)
packages/types/src/realtime-events.ts (1)
RealtimeEvent(60-64)packages/core/src/store/create-store.ts (2)
Store(7-12)createStore(14-68)packages/core/src/client.ts (1)
getConversationMessages(220-230)packages/core/src/rest-client.ts (1)
getConversationMessages(514-574)packages/react/src/hooks/use-store-selector.ts (1)
packages/core/src/store/create-store.ts (1)
subscribe(49-54)packages/react/src/hooks/use-conversation-messages.ts (4)
packages/core/src/store/messages-store.ts (2)
ConversationMessagesState(6-10)MessagesState(12-14)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-30)packages/react/src/hooks/use-client-query.ts (1)
useClientQuery(52-249)packages/react/src/support/pages/home.tsx (2)
packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/support/hooks/use-conversations.ts (1)
useConversations(102-192)packages/react/src/support/hooks/use-conversation.ts (5)
packages/core/src/store/conversations-store.ts (1)
ConversationsState(7-11)packages/types/src/api/conversation.ts (2)
GetConversationResponse(135-137)GetConversationRequest(123-125)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-30)packages/react/src/hooks/use-client-query.ts (1)
useClientQuery(52-249)packages/core/src/store/website-store.ts (1)
packages/core/src/store/create-store.ts (2)
Store(7-12)createStore(14-68)packages/react/src/support/hooks/use-conversations.ts (6)
packages/types/src/api/conversation.ts (2)
ListConversationsResponse(109-111)ListConversationsRequest(90-92)packages/core/src/store/conversations-store.ts (2)
ConversationPagination(5-5)ConversationsState(7-11)packages/core/src/client.ts (1)
CossistantClient(62-333)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-30)packages/react/src/hooks/use-client-query.ts (1)
useClientQuery(52-249)packages/react/src/provider.tsx (2)
packages/react/src/hooks/use-rest-client.ts (1)
useClient(18-69)packages/react/src/hooks/use-website-store.ts (1)
useWebsiteStore(63-103)packages/react/src/hooks/use-website-store.ts (4)
packages/core/src/store/website-store.ts (2)
WebsiteState(10-14)WebsiteStore(34-39)packages/core/src/client.ts (1)
CossistantClient(62-333)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-30)packages/react/src/hooks/use-client-query.ts (1)
useClientQuery(52-249)packages/react/src/support/pages/conversation-history.tsx (2)
packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/support/hooks/use-conversations.ts (1)
useConversations(102-192)packages/core/src/store/website-store.test.ts (2)
packages/core/src/store/website-store.ts (2)
createWebsiteStore(41-91)WebsiteState(10-14)packages/core/src/client.ts (1)
CossistantClient(62-333)packages/core/src/store/conversations-store.test.ts (3)
packages/types/src/api/conversation.ts (1)
ListConversationsResponse(109-111)packages/core/src/store/conversations-store.ts (3)
createConversationsStore(187-201)getConversations(203-210)getConversationById(212-217)packages/core/src/client.ts (1)
CossistantClient(62-333)packages/core/src/client.ts (8)
packages/types/src/schemas.ts (2)
Conversation(58-58)Message(25-25)packages/core/src/rest-client.ts (2)
CossistantRestClient(43-623)request(149-173)packages/core/src/store/conversations-store.ts (2)
ConversationsStore(182-185)createConversationsStore(187-201)packages/core/src/store/messages-store.ts (2)
MessagesStore(180-186)createMessagesStore(188-210)packages/core/src/store/website-store.ts (3)
WebsiteStore(34-39)createWebsiteStore(41-91)WebsiteState(10-14)packages/core/src/utils.ts (2)
generateConversationId(13-16)generateMessageId(18-20)packages/types/src/enums.ts (6)
ConversationStatus(17-21)ConversationStatus(23-24)MessageType(1-5)MessageType(7-7)MessageVisibility(33-36)MessageVisibility(74-75)packages/types/src/realtime-events.ts (1)
RealtimeEvent(60-64)🪛 markdownlint-cli2 (0.18.1)
packages/core/LLM_GUIDE.md
7-7: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
| const pending = this.pendingConversations.get(rest.conversationId); | ||
|
|
||
| if (pending && createIfPending !== false) { | ||
| try { | ||
| const response = await this.restClient.createConversation({ | ||
| conversationId: rest.conversationId, | ||
| defaultMessages: [...pending.defaultMessages, optimisticMessage], | ||
| }); | ||
|
|
||
| this.conversationsStore.ingestConversation(response.conversation); | ||
| this.messagesStore.removeMessage(rest.conversationId, optimisticId); | ||
| this.messagesStore.ingestPage(rest.conversationId, { | ||
| messages: response.initialMessages, | ||
| hasNextPage: false, | ||
| nextCursor: undefined, | ||
| }); | ||
|
|
||
| this.pendingConversations.delete(rest.conversationId); | ||
|
|
||
| return { | ||
| message: response.initialMessages[response.initialMessages.length - 1] ?? | ||
| response.initialMessages[0], | ||
| conversation: response.conversation, | ||
| initialMessages: response.initialMessages, | ||
| wasConversationCreated: true, | ||
| } satisfies SendMessageResponse & { | ||
| conversation: Conversation; | ||
| initialMessages: Message[]; | ||
| wasConversationCreated: true; | ||
| }; | ||
| } catch (error) { | ||
| this.messagesStore.removeMessage(rest.conversationId, optimisticId); | ||
| throw error; | ||
| } | ||
| } |
There was a problem hiding this comment.
Deduplicate concurrent pending-conversation creations to avoid double POSTs and message loss.
Two rapid sends on a pending conversation can race, causing a second createConversation call to fail and the optimistic message to be dropped.
Apply this diff to gate by an in‑flight map:
@@
export class CossistantClient {
@@
- private pendingConversations = new Map<string, PendingConversation>();
+ private pendingConversations = new Map<string, PendingConversation>();
+ private creatingConversations = new Map<string, Promise<CreateConversationResponseBody>>();
@@
- if (pending && createIfPending !== false) {
+ if (pending && createIfPending !== false) {
try {
- const response = await this.restClient.createConversation({
- conversationId: rest.conversationId,
- defaultMessages: [...pending.defaultMessages, optimisticMessage],
- });
+ let request = this.creatingConversations.get(rest.conversationId);
+ if (!request) {
+ request = this.restClient.createConversation({
+ conversationId: rest.conversationId,
+ defaultMessages: [...pending.defaultMessages, optimisticMessage],
+ });
+ this.creatingConversations.set(rest.conversationId, request);
+ }
+ const response = await request.finally(() => {
+ this.creatingConversations.delete(rest.conversationId);
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const pending = this.pendingConversations.get(rest.conversationId); | |
| if (pending && createIfPending !== false) { | |
| try { | |
| const response = await this.restClient.createConversation({ | |
| conversationId: rest.conversationId, | |
| defaultMessages: [...pending.defaultMessages, optimisticMessage], | |
| }); | |
| this.conversationsStore.ingestConversation(response.conversation); | |
| this.messagesStore.removeMessage(rest.conversationId, optimisticId); | |
| this.messagesStore.ingestPage(rest.conversationId, { | |
| messages: response.initialMessages, | |
| hasNextPage: false, | |
| nextCursor: undefined, | |
| }); | |
| this.pendingConversations.delete(rest.conversationId); | |
| return { | |
| message: response.initialMessages[response.initialMessages.length - 1] ?? | |
| response.initialMessages[0], | |
| conversation: response.conversation, | |
| initialMessages: response.initialMessages, | |
| wasConversationCreated: true, | |
| } satisfies SendMessageResponse & { | |
| conversation: Conversation; | |
| initialMessages: Message[]; | |
| wasConversationCreated: true; | |
| }; | |
| } catch (error) { | |
| this.messagesStore.removeMessage(rest.conversationId, optimisticId); | |
| throw error; | |
| } | |
| } | |
| // In packages/core/src/client.ts | |
| export class CossistantClient { | |
| private pendingConversations = new Map<string, PendingConversation>(); | |
| private creatingConversations = new Map<string, Promise<CreateConversationResponseBody>>(); | |
| // … other members … | |
| private async sendMessage(rest: SendMessageRequest, createIfPending = true): Promise<SendMessageResponse> { | |
| const pending = this.pendingConversations.get(rest.conversationId); | |
| if (pending && createIfPending !== false) { | |
| try { | |
| let request = this.creatingConversations.get(rest.conversationId); | |
| if (!request) { | |
| request = this.restClient.createConversation({ | |
| conversationId: rest.conversationId, | |
| defaultMessages: [...pending.defaultMessages, optimisticMessage], | |
| }); | |
| this.creatingConversations.set(rest.conversationId, request); | |
| } | |
| const response = await request.finally(() => { | |
| this.creatingConversations.delete(rest.conversationId); | |
| }); | |
| this.conversationsStore.ingestConversation(response.conversation); | |
| this.messagesStore.removeMessage(rest.conversationId, optimisticId); | |
| this.messagesStore.ingestPage(rest.conversationId, { | |
| messages: response.initialMessages, | |
| hasNextPage: false, | |
| nextCursor: undefined, | |
| }); | |
| this.pendingConversations.delete(rest.conversationId); | |
| return { | |
| message: response.initialMessages[response.initialMessages.length - 1] ?? | |
| response.initialMessages[0], | |
| conversation: response.conversation, | |
| initialMessages: response.initialMessages, | |
| wasConversationCreated: true, | |
| } satisfies SendMessageResponse & { | |
| conversation: Conversation; | |
| initialMessages: Message[]; | |
| wasConversationCreated: true; | |
| }; | |
| } catch (error) { | |
| this.messagesStore.removeMessage(rest.conversationId, optimisticId); | |
| throw error; | |
| } | |
| } | |
| // … fallback for non-pending or createIfPending === false … | |
| } | |
| } |
🤖 Prompt for AI Agents
packages/core/src/client.ts around lines 263-297: concurrent sends for the same
pending conversation can trigger duplicate createConversation POSTs and drop
optimistic messages; add an in-flight map keyed by conversationId to deduplicate
and await an existing create promise instead of issuing a second POST. On entry,
check the inFlightCreates map for rest.conversationId and if present await that
promise and then return the same response handling (ingest conversation/messages
and remove optimistic message as done currently); if not present, create a
Promise for the createConversation call, store it in the map before calling
restClient.createConversation, and ensure you remove the map entry in both
success and catch paths so subsequent attempts can proceed; preserve existing
messageStore and conversationsStore updates and rethrow errors after cleaning
up.
| function isSameMessage(a: Message, b: Message): boolean { | ||
| return ( | ||
| a.id === b.id && | ||
| a.bodyMd === b.bodyMd && | ||
| a.updatedAt.getTime() === b.updatedAt.getTime() && | ||
| a.createdAt.getTime() === b.createdAt.getTime() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Broaden equality check to catch visibility/deletion changes.
isSameMessage ignores fields like visibility and deletedAt. If the backend doesn’t bump updatedAt for those, changes will be missed.
function isSameMessage(a: Message, b: Message): boolean {
return (
a.id === b.id &&
a.bodyMd === b.bodyMd &&
a.updatedAt.getTime() === b.updatedAt.getTime() &&
- a.createdAt.getTime() === b.createdAt.getTime()
+ a.createdAt.getTime() === b.createdAt.getTime() &&
+ a.visibility === b.visibility &&
+ (a.deletedAt?.getTime() ?? -1) === (b.deletedAt?.getTime() ?? -1)
);
}🤖 Prompt for AI Agents
In packages/core/src/store/messages-store.ts around lines 24 to 31,
isSameMessage currently compares id, bodyMd, createdAt and updatedAt but omits
visibility and deletedAt so changes to those fields will be missed; update the
equality check to also compare message.visibility (exact equality) and
message.deletedAt (treat null/undefined consistently and compare .getTime() for
Date values), ensuring you handle cases where deletedAt can be undefined/null
before calling getTime, and update any relevant tests or callers accordingly.
| function mergeMessages(existing: Message[], incoming: Message[]): Message[] { | ||
| if (incoming.length === 0) { | ||
| return existing; | ||
| } | ||
|
|
||
| const byId = new Map<string, Message>(); | ||
| for (const message of existing) { | ||
| byId.set(message.id, message); | ||
| } | ||
|
|
||
| let changed = false; | ||
| for (const message of incoming) { | ||
| const previous = byId.get(message.id); | ||
| if (!previous || !isSameMessage(previous, message)) { | ||
| changed = true; | ||
| } | ||
| byId.set(message.id, message); | ||
| } | ||
|
|
||
| if (!changed && byId.size === existing.length) { | ||
| let orderStable = true; | ||
| for (const message of existing) { | ||
| if (byId.get(message.id) !== message) { | ||
| orderStable = false; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (orderStable) { | ||
| return existing; | ||
| } | ||
| } | ||
|
|
||
| return sortMessages(Array.from(byId.values())); | ||
| } |
There was a problem hiding this comment.
Preserve object identity for unchanged messages to prevent needless updates.
Even when content is equal, you replace map entries with the incoming object, breaking referential checks and causing avoidable re-renders/store writes.
for (const message of incoming) {
const previous = byId.get(message.id);
- if (!previous || !isSameMessage(previous, message)) {
- changed = true;
- }
- byId.set(message.id, message);
+ if (!previous) {
+ changed = true;
+ byId.set(message.id, message);
+ } else if (!isSameMessage(previous, message)) {
+ changed = true;
+ byId.set(message.id, message);
+ } else {
+ // Keep previous reference to maintain structural sharing.
+ byId.set(message.id, previous);
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function mergeMessages(existing: Message[], incoming: Message[]): Message[] { | |
| if (incoming.length === 0) { | |
| return existing; | |
| } | |
| const byId = new Map<string, Message>(); | |
| for (const message of existing) { | |
| byId.set(message.id, message); | |
| } | |
| let changed = false; | |
| for (const message of incoming) { | |
| const previous = byId.get(message.id); | |
| if (!previous || !isSameMessage(previous, message)) { | |
| changed = true; | |
| } | |
| byId.set(message.id, message); | |
| } | |
| if (!changed && byId.size === existing.length) { | |
| let orderStable = true; | |
| for (const message of existing) { | |
| if (byId.get(message.id) !== message) { | |
| orderStable = false; | |
| break; | |
| } | |
| } | |
| if (orderStable) { | |
| return existing; | |
| } | |
| } | |
| return sortMessages(Array.from(byId.values())); | |
| } | |
| function mergeMessages(existing: Message[], incoming: Message[]): Message[] { | |
| if (incoming.length === 0) { | |
| return existing; | |
| } | |
| const byId = new Map<string, Message>(); | |
| for (const message of existing) { | |
| byId.set(message.id, message); | |
| } | |
| let changed = false; | |
| for (const message of incoming) { | |
| const previous = byId.get(message.id); | |
| if (!previous) { | |
| changed = true; | |
| byId.set(message.id, message); | |
| } else if (!isSameMessage(previous, message)) { | |
| changed = true; | |
| byId.set(message.id, message); | |
| } else { | |
| // Keep previous reference to maintain structural sharing. | |
| byId.set(message.id, previous); | |
| } | |
| } | |
| if (!changed && byId.size === existing.length) { | |
| let orderStable = true; | |
| for (const message of existing) { | |
| if (byId.get(message.id) !== message) { | |
| orderStable = false; | |
| break; | |
| } | |
| } | |
| if (orderStable) { | |
| return existing; | |
| } | |
| } | |
| return sortMessages(Array.from(byId.values())); | |
| } |
🤖 Prompt for AI Agents
In packages/core/src/store/messages-store.ts around lines 33 to 67, the
mergeMessages routine currently replaces existing Message objects with incoming
ones even when isSameMessage(previous, message) returns true, breaking object
identity and causing unnecessary updates; change the loop that iterates incoming
messages so that if previous exists and isSameMessage(previous, message) is true
you keep the previous object in the map (byId.set(message.id, previous)) and
only replace/set with the incoming message when there is no previous or messages
differ, preserving referential equality and leaving the existing array instance
early when no changes are detected.
| reset() { | ||
| store.setState(() => INITIAL_STATE); | ||
| }, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Reset should restore to the caller’s initial state, not the module constant.
If a custom initialState is passed to createWebsiteStore, reset() currently reverts to INITIAL_STATE, not the provided baseline.
Apply this diff to snapshot the caller’s initial state and use it for reset:
export function createWebsiteStore(initialState: WebsiteState = INITIAL_STATE): WebsiteStore {
- const store = createStore<WebsiteState>(initialState);
+ const store = createStore<WebsiteState>(initialState);
+ const initialSnapshot: WebsiteState = { ...initialState };
return {
...store,
setLoading() {
@@
},
reset() {
- store.setState(() => INITIAL_STATE);
+ store.setState(() => initialSnapshot);
},
};
}🤖 Prompt for AI Agents
In packages/core/src/store/website-store.ts around lines 87-91, reset()
currently sets state to the module-level INITIAL_STATE instead of the
caller-provided initialState; capture the provided initialState when
createWebsiteStore is called (e.g., const baseline = initialState ??
INITIAL_STATE and deep/shallow clone it to avoid external mutation) and change
reset() to restore that baseline clone via store.setState(() =>
cloneOfBaseline). Ensure baseline is computed once at creation and used
immutably for subsequent resets.
| keyChangedRef.current = false; | ||
| hasFetchedRef.current = true; | ||
| void execute(latestArgsRef.current); | ||
| }, [client, enabled, execute, keyHash, refetchOnMount]); |
There was a problem hiding this comment.
Avoid unhandled promise rejections from internal fetches.
execute() rethrows; internal calls aren’t awaited, which can surface unhandled rejections. Catch them where invoked.
Apply these changes:
- void execute(latestArgsRef.current);
+ void execute(latestArgsRef.current).catch(() => {});- const timer = window.setInterval(() => {
- void execute(latestArgsRef.current);
- }, refetchInterval);
+ const timer = window.setInterval(() => {
+ void execute(latestArgsRef.current).catch(() => {});
+ }, refetchInterval);- const handleRefetch = () => {
+ const handleRefetch = () => {
if (!client || !enabled) {
return;
}
-
- void execute(latestArgsRef.current);
+ void execute(latestArgsRef.current).catch(() => {});
};Also applies to: 191-197, 209-216
🤖 Prompt for AI Agents
In packages/react/src/hooks/use-client-query.ts around lines 173-176 (and
likewise for the other internal calls at 191-197 and 209-216), internal calls to
execute(...) are invoked without handling rejections which can cause unhandled
promise rejections; update those invocations to catch and handle any rejection
from execute — e.g. call execute(latestArgsRef.current).catch(err => {/* handle
or log error */}) or use void execute(...).catch(err => {/* handle or log error
*/}) so every internal fire-and-forget invocation has a .catch that at minimum
logs the error.
| const EMPTY_MESSAGES_STORE = { | ||
| getState: (): MessagesState => ({ conversations: {} }), | ||
| subscribe: () => () => { | ||
| // noop | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Fix subscribe signature on EMPTY_MESSAGES_STORE (type/runtime bug).
useStoreSelector expects subscribe(listener) => unsubscribe. This fallback takes no listener, which will fail type-checking and suppress updates.
Apply:
-const EMPTY_MESSAGES_STORE = {
- getState: (): MessagesState => ({ conversations: {} }),
- subscribe: () => () => {
- // noop
- },
-};
+const EMPTY_MESSAGES_STORE = {
+ getState: (): MessagesState => ({ conversations: {} }),
+ subscribe: (_listener: (state: MessagesState) => void) => () => {
+ // noop
+ },
+};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const EMPTY_MESSAGES_STORE = { | |
| getState: (): MessagesState => ({ conversations: {} }), | |
| subscribe: () => () => { | |
| // noop | |
| }, | |
| }; | |
| const EMPTY_MESSAGES_STORE = { | |
| getState: (): MessagesState => ({ conversations: {} }), | |
| subscribe: (_listener: (state: MessagesState) => void) => () => { | |
| // noop | |
| }, | |
| }; |
🤖 Prompt for AI Agents
In packages/react/src/hooks/use-conversation-messages.ts around lines 20 to 25,
the EMPTY_MESSAGES_STORE.subscribe currently takes no arguments which breaks
useStoreSelector's expected subscribe(listener) => unsubscribe signature; change
subscribe to accept a listener parameter (e.g. subscribe: (listener: () => void)
=> () => void) and return an unsubscribe function that is a noop, ensuring the
type matches the store subscriber type so type-checking and runtime updates work
correctly.
| const query = useClientQuery<PublicWebsiteResponse, { force?: boolean }>({ | ||
| client, | ||
| queryKey: ["website", client], | ||
| queryFn: (instance, params) => instance.fetchWebsite(params ?? {}), | ||
| enabled: Boolean(client), | ||
| refetchInterval: options.refetchInterval ?? false, | ||
| refetchOnWindowFocus: options.refetchOnWindowFocus ?? true, | ||
| refetchOnMount: state.status === "idle", | ||
| initialData: state.website ?? undefined, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
queryKey includes a non-serializable object; verify hashing or switch to a stable scalar key
If useClientQuery hashes keys via JSON.stringify (or similar), including the client object can throw (circular) or collapse to “[object Object]”, breaking cache segregation across client/context. Prefer a stable scalar key (e.g., ["website", clientContextId]) or enhance hashing to handle object identity.
Please confirm how keys are hashed:
If serialization is used, consider:
- Expose a public accessor on CossistantClient to return a stable context key (e.g., websiteId:visitorId), then:
queryKey: ["website", client.getContextKey()] - Or change useClientQuery to support object identity keys (WeakMap-based) rather than stringification.
I can draft a small utility for stable key derivation once we pick an approach.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -nP 'function\s+useClientQuery|hash|queryKey' packages/react/src/hooks/use-client-query.ts -n -C3Length of output: 737
Replace client object in queryKey with a stable scalar
useClientQuery’s toHash calls JSON.stringify(key), so including the CossistantClient instance will either throw on circular refs or serialize to {}, collapsing all client keys and breaking cache isolation. Switch to a stable scalar identifier instead of the object, for example:
queryKey: ["website", client.getContextKey()]🤖 Prompt for AI Agents
In packages/react/src/hooks/use-website-store.ts around lines 70 to 79, the
queryKey currently includes the CossistantClient object which causes
JSON.stringify() to fail or collapse keys; replace the object with a stable
scalar identifier (e.g. call a client.getContextKey() or client.id property) so
the key becomes ["website", clientContextKey]; ensure the chosen scalar is
stable across renders and uniquely identifies the client context and update any
type usages accordingly.
| const EMPTY_STORE = { | ||
| getState: (): ConversationsState => EMPTY_STATE, | ||
| subscribe: () => () => { | ||
| // noop | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Type mismatch in EMPTY_STORE.subscribe.
subscribe should accept a listener argument; current signature may fail TS checks when unioned with real store.
Apply:
-const EMPTY_STORE = {
- getState: (): ConversationsState => EMPTY_STATE,
- subscribe: () => () => {
- // noop
- },
-};
+const EMPTY_STORE = {
+ getState: (): ConversationsState => EMPTY_STATE,
+ subscribe: (_listener: (state: ConversationsState) => void) => () => {
+ // noop
+ },
+};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const EMPTY_STORE = { | |
| getState: (): ConversationsState => EMPTY_STATE, | |
| subscribe: () => () => { | |
| // noop | |
| }, | |
| }; | |
| const EMPTY_STORE = { | |
| getState: (): ConversationsState => EMPTY_STATE, | |
| subscribe: (_listener: (state: ConversationsState) => void) => () => { | |
| // noop | |
| }, | |
| }; |
🤖 Prompt for AI Agents
In packages/react/src/support/hooks/use-conversation.ts around lines 13 to 18,
EMPTY_STORE.subscribe currently has no listener parameter which causes a
TypeScript mismatch when this stub is unioned with the real store; change
subscribe to accept a listener argument typed as () => void and return an
unsubscribe function typed as () => void (i.e., subscribe: (listener: () =>
void) => () => void { return () => {} }), ensuring the signature matches the
real store's subscribe and satisfies TS checks.
| const clientKey = useMemo(() => { | ||
| if (!client) { | ||
| return "no-client"; | ||
| } | ||
| const configuration = client.getConfiguration(); | ||
| return `${configuration.publicKey ?? "anon"}|${configuration.apiUrl ?? ""}`; | ||
| }, [client]); | ||
|
|
There was a problem hiding this comment.
Fix stale query key when client config changes.
clientKey is memoized only on the client instance, so updates via client.updateConfiguration(...) won’t change the cache key, leading to stale queries/refetch gaps.
Apply this diff to compute clientKey per render:
- const clientKey = useMemo(() => {
- if (!client) {
- return "no-client";
- }
- const configuration = client.getConfiguration();
- return `${configuration.publicKey ?? "anon"}|${configuration.apiUrl ?? ""}`;
- }, [client]);
+ const configuration = client?.getConfiguration();
+ const clientKey = configuration
+ ? `${configuration.publicKey ?? "anon"}|${configuration.apiUrl ?? ""}`
+ : "no-client";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const clientKey = useMemo(() => { | |
| if (!client) { | |
| return "no-client"; | |
| } | |
| const configuration = client.getConfiguration(); | |
| return `${configuration.publicKey ?? "anon"}|${configuration.apiUrl ?? ""}`; | |
| }, [client]); | |
| const configuration = client?.getConfiguration(); | |
| const clientKey = configuration | |
| ? `${configuration.publicKey ?? "anon"}|${configuration.apiUrl ?? ""}` | |
| : "no-client"; |
🤖 Prompt for AI Agents
In packages/react/src/support/hooks/use-conversations.ts around lines 140 to
147, clientKey is memoized only on the client instance so updates to
client.updateConfiguration(...) won’t change the key; compute clientKey on every
render instead of using useMemo so configuration changes are reflected
immediately: remove the useMemo wrapper and compute the same logic synchronously
each render (return "no-client" when client is falsy, otherwise read
client.getConfiguration() and build the `${publicKey ?? "anon"}|${apiUrl ?? ""}`
string).
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/react/src/support/pages/conversation.tsx (1)
101-141: Avoid sending null conversationId to the APIPassing
nullcan break serializers or violate API types. Preferundefinedwhen creating a new conversation.Apply:
- sendMessage.mutate({ - conversationId: realConversationId, + sendMessage.mutate({ + conversationId: hasRealConversation ? realConversationId! : undefined,packages/types/src/realtime-events.ts (1)
2-2: Remove unused imports.
MessageTypeandMessageVisibilityaren’t used.-import { MessageType, MessageVisibility } from "./enums";packages/react/src/support/pages/home.tsx (1)
44-61: Remove console logs and avoid side effects insideuseMemo.
console.logis disallowed anduseMemoshould be pure. Drop logs or move them behind a dev-onlyuseEffect.Apply this diff:
- console.log( - "[HomePage] Conversations:", - conversations?.map((c) => ({ - id: c.id, - status: c.status, - updatedAt: c.updatedAt, - })) - ); - const _lastOpenConversation = conversations?.find( (conversation) => conversation.status === "open" ); - - console.log( - "[HomePage] Last open conversation:", - _lastOpenConversation?.id - );As per coding guidelines.
🧹 Nitpick comments (19)
apps/web/src/components/conversation/index.tsx (3)
41-53: Drop unused fields from useMultimodalInput destructuringclearFiles, reset, isValid, canSubmit (and after the change below, addFiles/removeFile) are unused; they’ll trip linters and add noise.
Apply:
- const { - message, - files, - isSubmitting, - error, - setMessage, - addFiles, - removeFile, - clearFiles, - submit, - reset, - isValid, - canSubmit, - } = useMultimodalInput({ + const { + message, + files, + isSubmitting, + error, + setMessage, + submit, + } = useMultimodalInput({
133-146: Memoize onFetchMoreIfNeeded to avoid prop churn on MessagesListTiny perf/GC win when passed down every render.
Apply:
- const onFetchMoreIfNeeded = async () => { + const onFetchMoreIfNeeded = useCallback(async () => { const promises: Promise<unknown>[] = []; @@ - await Promise.all(promises); - }; + await Promise.all(promises); + }, [ + hasNextPageMessages, + fetchNextPageMessages, + hasNextPageEvents, + fetchNextPageEvents, + ]);Also add the missing import:
-import { useEffect, useMemo, useRef } from "react"; +import { useCallback, useEffect, useMemo, useRef } from "react";
89-131: Mark-as-read effect: add a simple in-flight guardRapid message bursts can trigger overlapping markRead calls. Guarding avoids redundant mutations.
Minimal pattern:
const markReadInFlight = useRef(false); useEffect(() => { // ...existing checks above if (markReadInFlight.current) return; markReadInFlight.current = true; markRead() .then(() => { lastMarkedMessageIdRef.current = lastMessage.id; }) .finally(() => { markReadInFlight.current = false; }); }, [/* existing deps */]);Alternatively, if useConversationActions exposes pendingAction/isAnyPending (per its docs), gate on that instead. Please confirm availability.
biome.jsonc (1)
32-35: Don’t disable rules that your guidelines requireYou turned off
complexity.noVoid, but your guidelines explicitly say “Don’t use void operators.” Keep this rule enabled; consider warning on cognitive complexity instead of disabling.- "complexity": { - "noVoid": "off", - "noExcessiveCognitiveComplexity": "off" - }, + "complexity": { + "noVoid": "error", + "noExcessiveCognitiveComplexity": "warn" + },As per coding guidelines.
packages/react/src/support/pages/conversation.tsx (1)
185-185: Replace console.error with structured loggingDirect console usage is disallowed by your rules; route through a logger or dev‑only debug utility.
- console.error("Failed to mark conversation as seen:", markSeenError); + // TODO: replace with structured logger (e.g., supportClient.logger.error) + if (process.env.NODE_ENV !== "production") { + // eslint-disable-next-line no-console + console.error("Failed to mark conversation as seen:", markSeenError); + }As per coding guidelines.
apps/api/src/utils/conversation-realtime.ts (1)
67-69: Use exhaustive type checks for actor mappingPrefer a typed
assertUnreachablefor compile‑time exhaustiveness and better error detail.-function mapActor(actor: ConversationRealtimeActor) { +function assertUnreachable(x: never): never { + throw new Error(`Unknown actor: ${JSON.stringify(x)}`); +} + +function mapActor(actor: ConversationRealtimeActor) { switch (actor.type) { /* ...cases... */ - default: - throw new Error("Unknown actor type"); + default: + return assertUnreachable(actor as never); } }packages/react/src/hooks/use-store-selector.ts (3)
1-1: Add useDebugValue for DevTools visibilityExpose the selected slice in React DevTools for easier debugging.
-import { useRef, useSyncExternalStore } from "react"; +import { useDebugValue, useRef, useSyncExternalStore } from "react";
29-36: Avoid treating undefined as a sentinel valueIf a selector intentionally returns
undefined, the current check reassigns on every render. Track initialization separately.- if ( - selectionRef.current === undefined || - !isEqual(selectionRef.current, selected) - ) { - selectionRef.current = selected; - } + const hasSelectionRef = useRef(false); + if (!hasSelectionRef.current || !isEqual(selectionRef.current as TSelected, selected)) { + selectionRef.current = selected; + hasSelectionRef.current = true; + }
10-14: Add concise JSDoc with usage and cautionsDocument the API and comparator semantics for consumers.
export function useStoreSelector<TState, TSelected>( store: BasicStore<TState>, selector: (state: TState) => TSelected, isEqual: (previous: TSelected, next: TSelected) => boolean = Object.is ): TSelected { + /** + * Selects a memoized slice from an external store with anti-tearing guarantees via useSyncExternalStore. + * - Provide a stable selector when possible. + * - Supply a custom comparator for structural equality to reduce re-renders. + */packages/react/src/hooks/use-client-query.ts (3)
205-239: Skip registering focus/visibility listeners when disabled or no client.Avoid unnecessary listeners when
clientis null orenabledis false.useEffect(() => { - if ( - !refetchOnWindowFocus || - typeof window === "undefined" || - typeof document === "undefined" - ) { + if ( + !(client && enabled) || + !refetchOnWindowFocus || + typeof window === "undefined" || + typeof document === "undefined" + ) { return; }
188-195: Align types with guard (null check).
refetchIntervalis typednumber | false, but you also guard fornull. Either extend the type tonumber | false | nullor drop thenullcheck. I suggest removing the redundantnullbranch.- if ( - refetchInterval === false || - refetchInterval === null || - refetchInterval <= 0 - ) { + if (refetchInterval === false || refetchInterval <= 0) { return; }
107-110: Remove the extra fetchId bump on mount.This increment isn’t needed;
executealready managesfetchIdRef. Dropping it reduces cognitive overhead without changing behavior.-useEffect(() => { - fetchIdRef.current += 1; -}, []);packages/types/src/realtime-events.ts (2)
34-42: Use a datetime string for lastSeenAt to match API schemas.Align with API (
z.date()in conversation response) by using an ISO datetime string here.CONVERSATION_SEEN: z.object({ conversationId: z.string(), websiteId: z.string(), organizationId: z.string(), visitorId: z.string().nullable(), userId: z.string().nullable(), aiAgentId: z.string().nullable(), - lastSeenAt: z.string(), + lastSeenAt: z.string().datetime(), }),
68-89: Tighten message type/visibility validation (optional).If you have literal unions/constants for message
typeandvisibility, preferz.enum([...])overz.string()for stronger validation (and drop the unused enum imports). If not available, keepingz.string()is acceptable.packages/react/src/hooks/use-conversations-store.ts (1)
14-44: Deduplicate the equality comparator.This
areSelectionsEqualmirrors logic used elsewhere (e.g., support hooks). Extract a shared comparator (e.g., areConversationsSelectionEqual) to avoid divergence.As per coding guidelines.
packages/core/src/store/messages-store.ts (1)
230-235: Optional: return a default empty shape.Returning
undefinedforces callers to null-check. Consider returning an empty conversation state for ergonomics.-export function getConversationMessages( +export function getConversationMessages( store: Store<MessagesState>, conversationId: string -): ConversationMessagesState | undefined { - return store.getState().conversations[conversationId]; +): ConversationMessagesState { + return ( + store.getState().conversations[conversationId] ?? { + messages: [], + hasNextPage: false, + nextCursor: undefined, + } + ); }As per coding guidelines.
packages/core/src/store/conversations-store.ts (1)
94-101: Minor perf nit: avoid O(n·m) includes on large pages.When
page <= 1, buildingrestwithincoming.includes(id)is quadratic. Use a Set forincoming.- if (page <= 1) { - const rest = existing.filter((id) => !incoming.includes(id)); + if (page <= 1) { + const incomingSet = new Set(incoming); + const rest = existing.filter((id) => !incomingSet.has(id)); const next = [...incoming, ...rest];packages/react/src/support/hooks/use-conversations.ts (1)
176-185: Include all request filters in the query key to avoid stale caches.Only a subset of fields from
requestDefaultsare included, which can miss additional filters and prevent refetches when those change.Minimal change: append the whole
requestDefaultsobject to the key (youruseClientQueryhashes keys stably).queryKey: [ "conversations", clientKey, requestDefaults.limit ?? null, requestDefaults.page ?? null, requestDefaults.status ?? null, requestDefaults.orderBy ?? null, requestDefaults.order ?? null, + requestDefaults, ],packages/core/src/client.ts (1)
335-343: Update conversation lastMessage/updatedAt on realtime MESSAGE_CREATED.Keep conversations list fresh when realtime events arrive.
handleRealtimeEvent(event: RealtimeEvent): void { switch (event.type) { case "MESSAGE_CREATED": - this.messagesStore.ingestRealtime(event); - break; + this.messagesStore.ingestRealtime(event); + { + const message = (event as any).data?.message as Message | undefined; + if (message) { + const existing = + this.conversationsStore.getState().byId[message.conversationId]; + if (existing) { + this.conversationsStore.ingestConversation({ + ...existing, + lastMessage: message, + updatedAt: new Date(), + }); + } + } + } + break; default: break; } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (47)
apps/api/scripts/migrate.ts(0 hunks)apps/api/src/db/cache/bun-redis-cache.ts(0 hunks)apps/api/src/rest/routers/conversation.ts(2 hunks)apps/api/src/rest/routers/visitor.ts(0 hunks)apps/api/src/trpc/routers/conversation.ts(2 hunks)apps/api/src/utils/conversation-realtime.ts(2 hunks)apps/api/src/ws/realtime-pubsub.ts(0 hunks)apps/api/src/ws/router.test.ts(1 hunks)apps/web/src/components/conversation/index.tsx(1 hunks)apps/web/src/components/conversation/messages/group.tsx(2 hunks)apps/web/src/components/conversation/messages/list.tsx(2 hunks)apps/web/src/components/conversation/messages/message.tsx(1 hunks)apps/web/src/components/conversation/messages/read-indicator.tsx(2 hunks)apps/web/src/components/ui/avatar.tsx(1 hunks)apps/web/src/lib/metadata.ts(0 hunks)apps/web/src/lib/trpc/server.tsx(0 hunks)biome.jsonc(1 hunks)packages/core/src/client.ts(5 hunks)packages/core/src/index.ts(1 hunks)packages/core/src/rest-client.ts(3 hunks)packages/core/src/store/conversations-store.test.ts(1 hunks)packages/core/src/store/conversations-store.ts(1 hunks)packages/core/src/store/create-store.ts(1 hunks)packages/core/src/store/messages-store.test.ts(1 hunks)packages/core/src/store/messages-store.ts(1 hunks)packages/core/src/store/website-store.test.ts(1 hunks)packages/core/src/store/website-store.ts(1 hunks)packages/react/src/hooks/index.ts(1 hunks)packages/react/src/hooks/use-client-query.ts(1 hunks)packages/react/src/hooks/use-conversation-messages.ts(1 hunks)packages/react/src/hooks/use-conversations-store.ts(1 hunks)packages/react/src/hooks/use-grouped-messages.ts(0 hunks)packages/react/src/hooks/use-store-selector.ts(1 hunks)packages/react/src/hooks/use-website-store.ts(1 hunks)packages/react/src/index.ts(1 hunks)packages/react/src/primitives/multimodal-input.tsx(0 hunks)packages/react/src/provider.tsx(2 hunks)packages/react/src/support/hooks/index.ts(1 hunks)packages/react/src/support/hooks/use-conversation.ts(1 hunks)packages/react/src/support/hooks/use-conversations.ts(1 hunks)packages/react/src/support/hooks/use-website.ts(1 hunks)packages/react/src/support/pages/conversation-history.tsx(1 hunks)packages/react/src/support/pages/conversation.tsx(2 hunks)packages/react/src/support/pages/home.tsx(1 hunks)packages/react/src/utils/message-converter.ts(0 hunks)packages/types/src/api/conversation.ts(1 hunks)packages/types/src/realtime-events.ts(1 hunks)
💤 Files with no reviewable changes (9)
- apps/api/src/db/cache/bun-redis-cache.ts
- packages/react/src/utils/message-converter.ts
- apps/api/src/rest/routers/visitor.ts
- apps/api/scripts/migrate.ts
- apps/web/src/lib/metadata.ts
- apps/api/src/ws/realtime-pubsub.ts
- apps/web/src/lib/trpc/server.tsx
- packages/react/src/primitives/multimodal-input.tsx
- packages/react/src/hooks/use-grouped-messages.ts
✅ Files skipped from review due to trivial changes (6)
- apps/api/src/ws/router.test.ts
- apps/api/src/trpc/routers/conversation.ts
- apps/web/src/components/ui/avatar.tsx
- apps/web/src/components/conversation/messages/list.tsx
- apps/web/src/components/conversation/messages/read-indicator.tsx
- apps/web/src/components/conversation/messages/group.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/react/src/hooks/index.ts
- packages/react/src/support/hooks/index.ts
- packages/react/src/support/hooks/use-website.ts
- packages/react/src/hooks/use-website-store.ts
- packages/react/src/provider.tsx
- packages/core/src/store/website-store.ts
- packages/core/src/store/messages-store.test.ts
- packages/react/src/support/pages/conversation-history.tsx
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...
Files:
packages/core/src/store/conversations-store.test.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/types/src/realtime-events.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-conversations-store.tsapps/api/src/rest/routers/conversation.tspackages/core/src/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/core/src/client.tsapps/web/src/components/conversation/messages/message.tsxpackages/core/src/rest-client.tspackages/react/src/hooks/use-store-selector.tspackages/core/src/store/create-store.tspackages/react/src/support/hooks/use-conversation.tspackages/types/src/api/conversation.tspackages/react/src/support/hooks/use-conversations.tsapps/api/src/utils/conversation-realtime.tspackages/react/src/hooks/use-client-query.tspackages/core/src/store/conversations-store.tspackages/core/src/store/website-store.test.tspackages/react/src/index.tsapps/web/src/components/conversation/index.tsx
**/*.{test.ts,test.tsx,spec.ts,spec.tsx,test.js,test.jsx,spec.js,spec.jsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
Include tests: unit tests for components, integration tests for composed components, accessibility tests, and error boundary tests
Files:
packages/core/src/store/conversations-store.test.tspackages/core/src/store/website-store.test.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't use theaccessKeyattribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==
Files:
packages/core/src/store/conversations-store.test.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/types/src/realtime-events.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-conversations-store.tsapps/api/src/rest/routers/conversation.tspackages/core/src/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/core/src/client.tsapps/web/src/components/conversation/messages/message.tsxpackages/core/src/rest-client.tspackages/react/src/hooks/use-store-selector.tspackages/core/src/store/create-store.tspackages/react/src/support/hooks/use-conversation.tspackages/types/src/api/conversation.tspackages/react/src/support/hooks/use-conversations.tsapps/api/src/utils/conversation-realtime.tspackages/react/src/hooks/use-client-query.tspackages/core/src/store/conversations-store.tspackages/core/src/store/website-store.test.tspackages/react/src/index.tsapps/web/src/components/conversation/index.tsx
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...
Files:
packages/core/src/store/conversations-store.test.tspackages/types/src/realtime-events.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-conversations-store.tsapps/api/src/rest/routers/conversation.tspackages/core/src/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/core/src/client.tspackages/core/src/rest-client.tspackages/react/src/hooks/use-store-selector.tspackages/core/src/store/create-store.tspackages/react/src/support/hooks/use-conversation.tspackages/types/src/api/conversation.tspackages/react/src/support/hooks/use-conversations.tsapps/api/src/utils/conversation-realtime.tspackages/react/src/hooks/use-client-query.tspackages/core/src/store/conversations-store.tspackages/core/src/store/website-store.test.tspackages/react/src/index.ts
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{test,spec}.{js,jsx,ts,tsx}: Don't nest describe() blocks too deeply in test files
Don't use callbacks in asynchronous tests and hooks
Don't have duplicate hooks in describe blocks
Don't use export or module.exports in test files
Don't use focused tests
Ensure the assertion function (e.g., expect) is placed inside an it() function call
Don't use disabled tests
Files:
packages/core/src/store/conversations-store.test.tspackages/core/src/store/website-store.test.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class members
Files:
packages/core/src/store/conversations-store.test.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/types/src/realtime-events.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-conversations-store.tsapps/api/src/rest/routers/conversation.tspackages/core/src/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/core/src/client.tsapps/web/src/components/conversation/messages/message.tsxpackages/core/src/rest-client.tspackages/react/src/hooks/use-store-selector.tspackages/core/src/store/create-store.tspackages/react/src/support/hooks/use-conversation.tspackages/types/src/api/conversation.tspackages/react/src/support/hooks/use-conversations.tsapps/api/src/utils/conversation-realtime.tspackages/react/src/hooks/use-client-query.tspackages/core/src/store/conversations-store.tspackages/core/src/store/website-store.test.tspackages/react/src/index.tsapps/web/src/components/conversation/index.tsx
**/*.{js,tsx,ts,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Use arrow functions instead of function expressions
Files:
packages/core/src/store/conversations-store.test.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/types/src/realtime-events.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-conversations-store.tsapps/api/src/rest/routers/conversation.tspackages/core/src/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/core/src/client.tsapps/web/src/components/conversation/messages/message.tsxpackages/core/src/rest-client.tspackages/react/src/hooks/use-store-selector.tspackages/core/src/store/create-store.tspackages/react/src/support/hooks/use-conversation.tspackages/types/src/api/conversation.tspackages/react/src/support/hooks/use-conversations.tsapps/api/src/utils/conversation-realtime.tspackages/react/src/hooks/use-client-query.tspackages/core/src/store/conversations-store.tspackages/core/src/store/website-store.test.tspackages/react/src/index.tsapps/web/src/components/conversation/index.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace characters
Files:
packages/core/src/store/conversations-store.test.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/types/src/realtime-events.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-conversations-store.tsapps/api/src/rest/routers/conversation.tspackages/core/src/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/core/src/client.tsapps/web/src/components/conversation/messages/message.tsxpackages/core/src/rest-client.tspackages/react/src/hooks/use-store-selector.tspackages/core/src/store/create-store.tspackages/react/src/support/hooks/use-conversation.tspackages/types/src/api/conversation.tspackages/react/src/support/hooks/use-conversations.tsapps/api/src/utils/conversation-realtime.tspackages/react/src/hooks/use-client-query.tspackages/core/src/store/conversations-store.tspackages/core/src/store/website-store.test.tspackages/react/src/index.tsapps/web/src/components/conversation/index.tsx
**/*.{js,ts,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't redeclare variables, functions, classes, and types in the same scope
Files:
packages/core/src/store/conversations-store.test.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/types/src/realtime-events.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-conversations-store.tsapps/api/src/rest/routers/conversation.tspackages/core/src/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/core/src/client.tsapps/web/src/components/conversation/messages/message.tsxpackages/core/src/rest-client.tspackages/react/src/hooks/use-store-selector.tspackages/core/src/store/create-store.tspackages/react/src/support/hooks/use-conversation.tspackages/types/src/api/conversation.tspackages/react/src/support/hooks/use-conversations.tsapps/api/src/utils/conversation-realtime.tspackages/react/src/hooks/use-client-query.tspackages/core/src/store/conversations-store.tspackages/core/src/store/website-store.test.tspackages/react/src/index.tsapps/web/src/components/conversation/index.tsx
packages/**
📄 CodeRabbit inference engine (AGENTS.md)
Put shared contracts and utilities in packages/* (e.g., core, types, transactional)
Files:
packages/core/src/store/conversations-store.test.tspackages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/types/src/realtime-events.tspackages/core/src/store/messages-store.tspackages/react/src/hooks/use-conversations-store.tspackages/core/src/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/core/src/client.tspackages/core/src/rest-client.tspackages/react/src/hooks/use-store-selector.tspackages/core/src/store/create-store.tspackages/react/src/support/hooks/use-conversation.tspackages/types/src/api/conversation.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/hooks/use-client-query.tspackages/core/src/store/conversations-store.tspackages/core/src/store/website-store.test.tspackages/react/src/index.ts
packages/react/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Develop headless React components (including and primitives) under packages/react/src/
Files:
packages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations-store.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/hooks/use-client-query.tspackages/react/src/index.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.tsx: Use the Compound Components pattern with namespaced exports (e.g., FormWizard.Root, FormWizard.Step) so pieces compose under a single parent
Ensure accessibility: keyboard navigation support
Ensure accessibility: include appropriate ARIA attributes
Ensure accessibility: use semantic markup for screen readers
Maintain proper focus management in interactive components
Structure components with forwardRef and typed props/refs as shown (ComponentRef, HTMLAttributes, ReactNode)
Root component acts as main context provider, manages global state, and handles initialization
Child components must be used within Root, read context via hooks, support asChild, and follow consistent props
Props ordering: list required props first; optional props have clear defaults; support common HTML attributes; expose className
Support composition with an asChild prop using Radix UI Slot to allow element replacement
Styling: support className, use data-* attributes for state, and allow style customization
Event handling: support standard DOM events, add custom events when needed, and manage propagation correctly
Base component pattern: forwardRef, choose Slot or intrinsic element via asChild, pass ref/className and spread props
Files:
packages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxapps/web/src/components/conversation/messages/message.tsxapps/web/src/components/conversation/index.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Ensure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include the words "image", "picture", or "photo" in img alt prop
Don't use an explicit role that's the same as the element's implicit role
Static elements with click handlers must have a valid role attribute
Always include a <title> element within SVG elements
Provide meaningful alt text for elements that require alt text
Ensure anchors have content accessible to screen readers
Assign tabIndex to non-interactive elements only when using aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Ensure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Elements with interactive roles and handlers must be focusable
Heading elements must have content accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
Prefer semantic elements instead of role attributes in JSX
Ensure all anchors are valid and navigable
Ensure all aria-* properties are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Don't use unnecessary fragmen...Files:
packages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxapps/web/src/components/conversation/messages/message.tsxapps/web/src/components/conversation/index.tsx**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx,html}: Ensure the preconnect attribute is used when using Google Fonts
Don't use target="_blank" without rel="noopener"
Use a recommended display strategy with Google FontsFiles:
packages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxapps/web/src/components/conversation/messages/message.tsxapps/web/src/components/conversation/index.tsxpackages/react/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react-package-rules.mdc)
packages/react/**/*.{ts,tsx}: Components must render with no built-in styles (headless, unstyled)
Components must accept className, style, and asChild props to enable external styling/control
Do not use emotion, styled-components, or style-prop APIs; styling is via Tailwind/CSS through className/style
Use useRenderElement to create elements and encapsulate render logic (supporting intrinsic tags and render prop overrides)
Components with internal state must be controllable via props (e.g., value/defaultValue/onChange or isOpen/onOpenChange)
Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function child
May reference Base UI logic internally, but never expose Base UI or MUI APIs; do not leak getRootProps/getInputProps; prefer useRenderElement
Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic components
Provide a render prop API: render?: (props, state) => ReactElement, and allow className to be string or (state) => string
Support developer-owned rendering: components should not impose visual structure and should allow parent-driven markup via render prop/asChildFiles:
packages/react/src/support/pages/home.tsxpackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations-store.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/use-store-selector.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/use-conversations.tspackages/react/src/hooks/use-client-query.tspackages/react/src/index.tspackages/types/src/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
packages/types/src/**/*.ts: Keep shared TypeScript types and Zod schemas in packages/types/src/ and update them alongside schema changes
Ensure Zod schemas mirror any API/database shape changes for end-to-end type safetyFiles:
packages/types/src/realtime-events.tspackages/types/src/api/conversation.tspackages/types/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Surface shared DTOs in packages/types
Files:
packages/types/src/realtime-events.tspackages/types/src/api/conversation.tsapps/web/src/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Put reusable React components under apps/web/src/components
Files:
apps/web/src/components/conversation/messages/message.tsxapps/web/src/components/conversation/index.tsxapps/web/src/{app,components}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Use snake_case filenames for React components and layouts in the web app
Files:
apps/web/src/components/conversation/messages/message.tsxapps/web/src/components/conversation/index.tsx🧠 Learnings (5)
📚 Learning: 2025-09-17T16:59:53.676Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/react-package-rules.mdc:0-0 Timestamp: 2025-09-17T16:59:53.676Z Learning: Applies to packages/react/**/*.{ts,tsx} : Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic componentsApplied to files:
apps/web/src/components/conversation/messages/message.tsx📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{js,ts} : Ensure Promise-like statements are handled (awaited, returned, or chained) appropriatelyApplied to files:
packages/react/src/hooks/use-client-query.ts📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{js,ts} : Don't use async functions as Promise executorsApplied to files:
packages/react/src/hooks/use-client-query.ts📚 Learning: 2025-09-17T09:00:28.751Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: CLAUDE.md:0-0 Timestamp: 2025-09-17T09:00:28.751Z Learning: Applies to packages/react/src/**/*.{ts,tsx} : Develop headless React components (including <Support /> and primitives) under packages/react/src/Applied to files:
packages/react/src/index.ts📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to **/*.{ts,tsx} : Prefer explicit return types on exported APIs in TypeScriptApplied to files:
packages/react/src/index.ts🧬 Code graph analysis (16)
packages/core/src/store/conversations-store.test.ts (2)
packages/core/src/store/conversations-store.ts (3)
createConversationsStore(195-209)getConversations(211-220)getConversationById(222-227)packages/core/src/client.ts (1)
CossistantClient(66-349)packages/react/src/support/pages/home.tsx (2)
packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/support/hooks/use-conversations.ts (1)
useConversations(111-215)packages/core/src/store/messages-store.ts (4)
packages/types/src/realtime-events.ts (1)
RealtimeEvent(94-98)packages/core/src/store/create-store.ts (2)
Store(7-12)createStore(14-69)packages/core/src/client.ts (1)
getConversationMessages(232-242)packages/core/src/rest-client.ts (1)
getConversationMessages(514-574)packages/react/src/hooks/use-conversations-store.ts (3)
packages/core/src/store/conversations-store.ts (2)
ConversationPagination(5-5)ConversationsState(7-11)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-37)apps/api/src/rest/routers/conversation.ts (7)
packages/types/src/api/conversation.ts (8)
createConversationRequestSchema(5-33)createConversationResponseSchema(39-46)listConversationsRequestSchema(52-88)listConversationsResponseSchema(94-107)getConversationRequestSchema(113-121)getConversationResponseSchema(127-133)markConversationSeenRequestSchema(139-153)markConversationSeenResponseSchema(159-171)apps/api/src/utils/validate.ts (3)
safelyExtractRequestData(56-107)validateResponse(6-20)safelyExtractRequestQuery(120-162)apps/api/src/db/queries/visitor.ts (1)
getVisitor(104-133)apps/api/src/db/queries/conversation.ts (3)
upsertConversation(37-66)listConversations(68-174)getConversationByIdWithLastMessage(176-219)apps/api/src/utils/message.ts (1)
createMessage(79-141)apps/api/src/db/mutations/conversation.ts (1)
markConversationAsSeenByVisitor(341-372)apps/api/src/utils/conversation-realtime.ts (1)
emitConversationSeenEvent(72-101)packages/react/src/hooks/use-conversation-messages.ts (4)
packages/core/src/store/messages-store.ts (2)
ConversationMessagesState(6-10)MessagesState(12-14)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/use-client-query.ts (1)
useClientQuery(52-254)packages/core/src/client.ts (7)
packages/core/src/rest-client.ts (2)
CossistantRestClient(43-623)request(149-173)packages/core/src/store/conversations-store.ts (2)
ConversationsStore(190-193)createConversationsStore(195-209)packages/core/src/store/messages-store.ts (2)
MessagesStore(188-198)createMessagesStore(200-228)packages/core/src/store/website-store.ts (3)
WebsiteStore(39-44)createWebsiteStore(46-105)WebsiteState(10-14)packages/core/src/utils.ts (2)
generateConversationId(13-16)generateMessageId(18-20)packages/types/src/enums.ts (6)
ConversationStatus(17-21)ConversationStatus(23-24)MessageType(1-5)MessageType(7-7)MessageVisibility(33-36)MessageVisibility(74-75)packages/types/src/realtime-events.ts (1)
RealtimeEvent(94-98)apps/web/src/components/conversation/messages/message.tsx (2)
packages/react/src/primitives/message.tsx (4)
MessageProps(19-27)Message(33-81)MessageContent(157-207)MessageTimestamp(224-267)packages/react/src/support/components/message.tsx (2)
MessageProps(10-14)Message(16-77)packages/core/src/rest-client.ts (1)
packages/core/src/visitor-tracker.ts (1)
getVisitorId(36-59)packages/react/src/hooks/use-store-selector.ts (1)
packages/core/src/store/create-store.ts (1)
subscribe(50-55)packages/react/src/support/hooks/use-conversation.ts (5)
packages/core/src/store/conversations-store.ts (1)
ConversationsState(7-11)packages/types/src/api/conversation.ts (2)
GetConversationResponse(135-137)GetConversationRequest(123-125)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/use-client-query.ts (1)
useClientQuery(52-254)packages/react/src/support/hooks/use-conversations.ts (5)
packages/core/src/store/conversations-store.ts (2)
ConversationPagination(5-5)ConversationsState(7-11)packages/core/src/client.ts (1)
CossistantClient(66-349)packages/react/src/provider.tsx (1)
useSupport(227-244)packages/react/src/hooks/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/use-client-query.ts (1)
useClientQuery(52-254)packages/react/src/hooks/use-client-query.ts (1)
packages/core/src/client.ts (1)
CossistantClient(66-349)packages/core/src/store/conversations-store.ts (2)
packages/types/src/api/conversation.ts (1)
ListConversationsResponse(109-111)packages/core/src/store/create-store.ts (2)
Store(7-12)createStore(14-69)packages/core/src/store/website-store.test.ts (2)
packages/core/src/store/website-store.ts (2)
createWebsiteStore(46-105)WebsiteState(10-14)packages/core/src/client.ts (1)
CossistantClient(66-349)apps/web/src/components/conversation/index.tsx (11)
apps/web/src/hooks/use-send-conversation-message.ts (1)
useSendConversationMessage(34-153)apps/web/src/contexts/website.tsx (1)
useWebsiteMembers(128-135)apps/web/src/contexts/inboxes/index.tsx (2)
useInboxes(113-125)ConversationHeader(12-13)apps/web/src/data/use-conversation-actions.ts (1)
useConversationActions(102-705)apps/web/src/data/use-conversation-messages.tsx (1)
useConversationMessages(17-68)apps/web/src/data/use-conversation-events.tsx (1)
useConversationEvents(16-66)apps/web/src/data/use-visitor.tsx (1)
useVisitor(6-37)apps/web/src/components/ui/layout/index.tsx (1)
Page(49-66)apps/web/src/components/conversation/messages/list.tsx (1)
MessagesList(33-120)apps/web/src/components/conversation/multimodal-input/index.tsx (1)
MultimodalInput(28-180)apps/web/src/components/ui/layout/sidebars/visitor/visitor-sidebar.tsx (1)
VisitorSidebar(16-99)⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
| const { db, website, organization, body, visitorIdHeader } = | ||
| await safelyExtractRequestData(c, markConversationSeenRequestSchema); | ||
|
|
||
| const params = getConversationRequestSchema.parse({ | ||
| conversationId: c.req.param("conversationId"), | ||
| }); | ||
| const params = getConversationRequestSchema.parse({ | ||
| conversationId: c.req.param("conversationId"), | ||
| }); | ||
|
|
||
| const [visitor, conversationRecord] = await Promise.all([ | ||
| getVisitor(db, { | ||
| visitorId: body.visitorId || visitorIdHeader, | ||
| externalVisitorId: body.externalVisitorId, | ||
| }), | ||
| getConversationByIdWithLastMessage(db, { | ||
| organizationId: organization.id, | ||
| websiteId: website.id, | ||
| conversationId: params.conversationId, | ||
| }), | ||
| ]); | ||
| const [visitor, conversationRecord] = await Promise.all([ | ||
| getVisitor(db, { | ||
| visitorId: body.visitorId || visitorIdHeader, | ||
| externalVisitorId: body.externalVisitorId, | ||
| }), |
There was a problem hiding this comment.
Empty POST body now triggers 400 on the “seen” route
markConversationSeen documents the JSON body as optional, but the new call to safelyExtractRequestData(c, markConversationSeenRequestSchema) always forces a JSON parse. With the Fetch API (and Hono’s Request.json), posting with no body — e.g. fetch("/seen", { method: "POST" }) — throws SyntaxError: Unexpected end of JSON input, so we now return the helper’s 400 “Invalid JSON” error for perfectly valid requests. That’s a regression for any client relying solely on the X-Visitor-Id header.
Guard the parse so we only invoke the schema path when a body is actually present, and fall back to an empty object otherwise:
- const { db, website, organization, body, visitorIdHeader } =
- await safelyExtractRequestData(c, markConversationSeenRequestSchema);
+ const hasBody = c.req.raw.body !== null;
+
+ const {
+ db,
+ website,
+ organization,
+ body,
+ visitorIdHeader,
+ } = hasBody
+ ? await safelyExtractRequestData(c, markConversationSeenRequestSchema)
+ : await safelyExtractRequestData(c);
+
+ const payload =
+ hasBody && body
+ ? body
+ : markConversationSeenRequestSchema.parse({});
@@
- getVisitor(db, {
- visitorId: body.visitorId || visitorIdHeader,
- externalVisitorId: body.externalVisitorId,
+ getVisitor(db, {
+ visitorId: payload.visitorId || visitorIdHeader,
+ externalVisitorId: payload.externalVisitorId,That preserves validation when JSON is supplied while keeping the route usable without a body.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { db, website, organization, body, visitorIdHeader } = | |
| await safelyExtractRequestData(c, markConversationSeenRequestSchema); | |
| const params = getConversationRequestSchema.parse({ | |
| conversationId: c.req.param("conversationId"), | |
| }); | |
| const params = getConversationRequestSchema.parse({ | |
| conversationId: c.req.param("conversationId"), | |
| }); | |
| const [visitor, conversationRecord] = await Promise.all([ | |
| getVisitor(db, { | |
| visitorId: body.visitorId || visitorIdHeader, | |
| externalVisitorId: body.externalVisitorId, | |
| }), | |
| getConversationByIdWithLastMessage(db, { | |
| organizationId: organization.id, | |
| websiteId: website.id, | |
| conversationId: params.conversationId, | |
| }), | |
| ]); | |
| const [visitor, conversationRecord] = await Promise.all([ | |
| getVisitor(db, { | |
| visitorId: body.visitorId || visitorIdHeader, | |
| externalVisitorId: body.externalVisitorId, | |
| }), | |
| // Determine whether a JSON body was actually sent | |
| const hasBody = c.req.raw.body !== null; | |
| // Extract request data, validating only if a body is present | |
| const { | |
| db, | |
| website, | |
| organization, | |
| body, | |
| visitorIdHeader, | |
| } = hasBody | |
| ? await safelyExtractRequestData(c, markConversationSeenRequestSchema) | |
| : await safelyExtractRequestData(c); | |
| // Normalize to a payload object, falling back to an empty schema-validated object | |
| const payload = | |
| hasBody && body | |
| ? body | |
| : markConversationSeenRequestSchema.parse({}); | |
| const params = getConversationRequestSchema.parse({ | |
| conversationId: c.req.param("conversationId"), | |
| }); | |
| const [visitor, conversationRecord] = await Promise.all([ | |
| getVisitor(db, { | |
| visitorId: payload.visitorId || visitorIdHeader, | |
| externalVisitorId: payload.externalVisitorId, | |
| }), | |
| // … | |
| ]); |
🤖 Prompt for AI Agents
In apps/api/src/rest/routers/conversation.ts around lines 535–546, avoid calling
safelyExtractRequestData unconditionally because an empty POST body causes
Request.json() to throw; instead, first check the incoming request body (e.g.,
read raw text or inspect Content-Length/header) and if it is empty use an empty
object for body and still extract visitorIdHeader from headers, otherwise call
safelyExtractRequestData(c, markConversationSeenRequestSchema) to validate/parse
the JSON; update subsequent code to use the resulting { db, website,
organization, body, visitorIdHeader } so validation still occurs when JSON is
provided but empty POSTs relying on headers succeed without a 400.
| const seenData = selectedConversation?.seenData ?? []; | ||
|
|
||
| console.log("seenData", seenData); | ||
|
|
||
| return ( | ||
| <> | ||
| <Page className="relative px-[1px] py-0"> | ||
| <div className="pointer-events-none absolute inset-x-0 top-0 z-0 h-14 bg-gradient-to-b from-co-background/50 to-transparent dark:from-co-background-100/80" /> | ||
| <ConversationHeader /> | ||
| <MessagesList | ||
| availableAIAgents={[]} | ||
| currentUserId={currentUserId} | ||
| events={events} | ||
| messages={messages} | ||
| onFetchMoreIfNeeded={onFetchMoreIfNeeded} | ||
| seenData={selectedConversation?.seenData ?? []} |
There was a problem hiding this comment.
Remove debug log and use the computed seenData variable
Avoid console noise and pass the prepared value consistently.
Apply:
const seenData = selectedConversation?.seenData ?? [];
-
- console.log("seenData", seenData);
@@
- seenData={selectedConversation?.seenData ?? []}
+ seenData={seenData}As per coding guidelines (observability hygiene).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const seenData = selectedConversation?.seenData ?? []; | |
| console.log("seenData", seenData); | |
| return ( | |
| <> | |
| <Page className="relative px-[1px] py-0"> | |
| <div className="pointer-events-none absolute inset-x-0 top-0 z-0 h-14 bg-gradient-to-b from-co-background/50 to-transparent dark:from-co-background-100/80" /> | |
| <ConversationHeader /> | |
| <MessagesList | |
| availableAIAgents={[]} | |
| currentUserId={currentUserId} | |
| events={events} | |
| messages={messages} | |
| onFetchMoreIfNeeded={onFetchMoreIfNeeded} | |
| seenData={selectedConversation?.seenData ?? []} | |
| const seenData = selectedConversation?.seenData ?? []; | |
| return ( | |
| <> | |
| <Page className="relative px-[1px] py-0"> | |
| <div className="pointer-events-none absolute inset-x-0 top-0 z-0 h-14 bg-gradient-to-b from-co-background/50 to-transparent dark:from-co-background-100/80" /> | |
| <ConversationHeader /> | |
| <MessagesList | |
| availableAIAgents={[]} | |
| currentUserId={currentUserId} | |
| events={events} | |
| messages={messages} | |
| onFetchMoreIfNeeded={onFetchMoreIfNeeded} | |
| seenData={seenData} |
🤖 Prompt for AI Agents
In apps/web/src/components/conversation/index.tsx around lines 151 to 166,
there's a leftover console.log("seenData", seenData) and the MessagesList prop
uses selectedConversation?.seenData ?? [] instead of the computed seenData
variable; remove the console.log and replace the inline expression with the
seenData variable so the prepared value is passed consistently and there is no
debug noise.
| allowedFileTypes={["image/*", "application/pdf", "text/*"]} | ||
| error={error} | ||
| files={files} | ||
| isSubmitting={isSubmitting} | ||
| maxFileSize={10 * 1024 * 1024} | ||
| maxFiles={2} | ||
| onChange={setMessage} | ||
| onFileSelect={addFiles} | ||
| onRemoveFile={removeFile} |
There was a problem hiding this comment.
Hide file attachments UI until sending files is supported
Backend path throws “File attachments are not supported yet.” Presenting attach UI creates a broken path.
Apply:
- maxFiles={2}
+ maxFiles={0}
onChange={setMessage}
- onFileSelect={addFiles}
- onRemoveFile={removeFile}
onSubmit={submit}This pairs with removing addFiles/removeFile from the destructuring above.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| allowedFileTypes={["image/*", "application/pdf", "text/*"]} | |
| error={error} | |
| files={files} | |
| isSubmitting={isSubmitting} | |
| maxFileSize={10 * 1024 * 1024} | |
| maxFiles={2} | |
| onChange={setMessage} | |
| onFileSelect={addFiles} | |
| onRemoveFile={removeFile} | |
| allowedFileTypes={["image/*", "application/pdf", "text/*"]} | |
| error={error} | |
| files={files} | |
| isSubmitting={isSubmitting} | |
| maxFileSize={10 * 1024 * 1024} | |
| maxFiles={0} | |
| onChange={setMessage} | |
| onSubmit={submit} |
🤖 Prompt for AI Agents
In apps/web/src/components/conversation/index.tsx around lines 171-179, the file
attachment UI is being rendered even though the backend returns "File
attachments are not supported yet." Remove or hide the attachment-related UI by
not rendering the attachment component (or wrapping it in a feature-flag/boolean
that is currently false) and remove addFiles and removeFile from the props/state
destructuring earlier in the file so they are not referenced; keep other props
(allowedFileTypes, error, files, isSubmitting, maxFileSize, maxFiles, onChange)
only if needed, or remove all attachment-related props entirely until backend
support is added.
| export function Message({ | ||
| message, | ||
| isLast = false, | ||
| isSentByViewer = false, | ||
| message, | ||
| isLast = false, | ||
| isSentByViewer = false, | ||
| }: MessageProps) { | ||
| return ( | ||
| <PrimitiveMessage message={message}> | ||
| {({ isAI, timestamp }) => ( | ||
| <div | ||
| className={cn( | ||
| "flex w-full gap-2", | ||
| isSentByViewer && "flex-row-reverse", | ||
| !isSentByViewer && "flex-row" | ||
| )} | ||
| > | ||
| <div | ||
| className={cn( | ||
| "flex w-full flex-1 flex-col gap-3", | ||
| isSentByViewer && "items-end" | ||
| )} | ||
| > | ||
| <MessageContent | ||
| bodyMd={message.bodyMd} | ||
| className={cn( | ||
| "block max-w-full rounded-lg px-3 py-2 text-sm md:w-max md:max-w-[420px]", | ||
| { | ||
| "bg-co-background-300 text-foreground dark:bg-co-background-600": | ||
| !isSentByViewer, | ||
| "bg-primary text-primary-foreground": isSentByViewer, | ||
| "rounded-br-[2px]": isLast && isSentByViewer, | ||
| "rounded-bl-[2px]": isLast && !isSentByViewer, | ||
| } | ||
| )} | ||
| renderMarkdown | ||
| /> | ||
| {isLast && ( | ||
| <MessageTimestamp | ||
| className="px-1 text-muted-foreground text-xs" | ||
| timestamp={timestamp} | ||
| > | ||
| {() => ( | ||
| <> | ||
| {timestamp.toLocaleTimeString([], { | ||
| hour: "2-digit", | ||
| minute: "2-digit", | ||
| })} | ||
| {isAI && " • AI agent"} | ||
| </> | ||
| )} | ||
| </MessageTimestamp> | ||
| )} | ||
| </div> | ||
| </div> | ||
| )} | ||
| </PrimitiveMessage> | ||
| ); | ||
| return ( | ||
| <PrimitiveMessage message={message}> | ||
| {({ isAI, timestamp }) => ( | ||
| <div | ||
| className={cn( | ||
| "flex w-full gap-2", | ||
| isSentByViewer && "flex-row-reverse", | ||
| !isSentByViewer && "flex-row" | ||
| )} | ||
| > | ||
| <div | ||
| className={cn( | ||
| "flex w-full flex-1 flex-col gap-3", | ||
| isSentByViewer && "items-end" | ||
| )} | ||
| > | ||
| <MessageContent | ||
| bodyMd={message.bodyMd} | ||
| className={cn( | ||
| "block max-w-full rounded-lg px-3 py-2 text-sm md:w-max md:max-w-[420px]", | ||
| { | ||
| "bg-co-background-300 text-foreground dark:bg-co-background-600": | ||
| !isSentByViewer, | ||
| "bg-primary text-primary-foreground": isSentByViewer, | ||
| "rounded-br-[2px]": isLast && isSentByViewer, | ||
| "rounded-bl-[2px]": isLast && !isSentByViewer, | ||
| } | ||
| )} | ||
| renderMarkdown | ||
| /> | ||
| {isLast && ( | ||
| <MessageTimestamp | ||
| className="px-1 text-muted-foreground text-xs" | ||
| timestamp={timestamp} | ||
| > | ||
| {() => ( | ||
| <> | ||
| {timestamp.toLocaleTimeString([], { | ||
| hour: "2-digit", | ||
| minute: "2-digit", | ||
| })} | ||
| {isAI && " • AI agent"} | ||
| </> | ||
| )} | ||
| </MessageTimestamp> | ||
| )} | ||
| </div> | ||
| </div> | ||
| )} | ||
| </PrimitiveMessage> | ||
| ); |
There was a problem hiding this comment.
Infer isSentByViewer from render‑props when not provided
Without a fallback to the message sender, viewer messages can render on the wrong side when isSentByViewer isn’t passed. Use the isVisitor flag from PrimitiveMessage to derive a sane default.
Apply:
-export function Message({
- message,
- isLast = false,
- isSentByViewer = false,
-}: MessageProps) {
+export function Message({ message, isLast = false, isSentByViewer }: MessageProps) {
return (
<PrimitiveMessage message={message}>
- {({ isAI, timestamp }) => (
+ {({ isAI, isVisitor, timestamp }) => {
+ const isSentByViewerFinal = isSentByViewer ?? isVisitor;
+ return (
<div
className={cn(
"flex w-full gap-2",
- isSentByViewer && "flex-row-reverse",
- !isSentByViewer && "flex-row"
+ isSentByViewerFinal && "flex-row-reverse",
+ !isSentByViewerFinal && "flex-row"
)}
>
<div
className={cn(
"flex w-full flex-1 flex-col gap-3",
- isSentByViewer && "items-end"
+ isSentByViewerFinal && "items-end"
)}
>
<MessageContent
bodyMd={message.bodyMd}
className={cn(
"block max-w-full rounded-lg px-3 py-2 text-sm md:w-max md:max-w-[420px]",
{
"bg-co-background-300 text-foreground dark:bg-co-background-600":
- !isSentByViewer,
- "bg-primary text-primary-foreground": isSentByViewer,
- "rounded-br-[2px]": isLast && isSentByViewer,
- "rounded-bl-[2px]": isLast && !isSentByViewer,
+ !isSentByViewerFinal,
+ "bg-primary text-primary-foreground": isSentByViewerFinal,
+ "rounded-br-[2px]": isLast && isSentByViewerFinal,
+ "rounded-bl-[2px]": isLast && !isSentByViewerFinal,
}
)}
renderMarkdown
/>
{isLast && (
<MessageTimestamp
className="px-1 text-muted-foreground text-xs"
timestamp={timestamp}
>
- {() => (
+ {() => (
<>
{timestamp.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
{isAI && " • AI agent"}
</>
)}
</MessageTimestamp>
)}
</div>
</div>
- )}
+ )}}
</PrimitiveMessage>
);Based on learnings.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function Message({ | |
| message, | |
| isLast = false, | |
| isSentByViewer = false, | |
| message, | |
| isLast = false, | |
| isSentByViewer = false, | |
| }: MessageProps) { | |
| return ( | |
| <PrimitiveMessage message={message}> | |
| {({ isAI, timestamp }) => ( | |
| <div | |
| className={cn( | |
| "flex w-full gap-2", | |
| isSentByViewer && "flex-row-reverse", | |
| !isSentByViewer && "flex-row" | |
| )} | |
| > | |
| <div | |
| className={cn( | |
| "flex w-full flex-1 flex-col gap-3", | |
| isSentByViewer && "items-end" | |
| )} | |
| > | |
| <MessageContent | |
| bodyMd={message.bodyMd} | |
| className={cn( | |
| "block max-w-full rounded-lg px-3 py-2 text-sm md:w-max md:max-w-[420px]", | |
| { | |
| "bg-co-background-300 text-foreground dark:bg-co-background-600": | |
| !isSentByViewer, | |
| "bg-primary text-primary-foreground": isSentByViewer, | |
| "rounded-br-[2px]": isLast && isSentByViewer, | |
| "rounded-bl-[2px]": isLast && !isSentByViewer, | |
| } | |
| )} | |
| renderMarkdown | |
| /> | |
| {isLast && ( | |
| <MessageTimestamp | |
| className="px-1 text-muted-foreground text-xs" | |
| timestamp={timestamp} | |
| > | |
| {() => ( | |
| <> | |
| {timestamp.toLocaleTimeString([], { | |
| hour: "2-digit", | |
| minute: "2-digit", | |
| })} | |
| {isAI && " • AI agent"} | |
| </> | |
| )} | |
| </MessageTimestamp> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| </PrimitiveMessage> | |
| ); | |
| return ( | |
| <PrimitiveMessage message={message}> | |
| {({ isAI, timestamp }) => ( | |
| <div | |
| className={cn( | |
| "flex w-full gap-2", | |
| isSentByViewer && "flex-row-reverse", | |
| !isSentByViewer && "flex-row" | |
| )} | |
| > | |
| <div | |
| className={cn( | |
| "flex w-full flex-1 flex-col gap-3", | |
| isSentByViewer && "items-end" | |
| )} | |
| > | |
| <MessageContent | |
| bodyMd={message.bodyMd} | |
| className={cn( | |
| "block max-w-full rounded-lg px-3 py-2 text-sm md:w-max md:max-w-[420px]", | |
| { | |
| "bg-co-background-300 text-foreground dark:bg-co-background-600": | |
| !isSentByViewer, | |
| "bg-primary text-primary-foreground": isSentByViewer, | |
| "rounded-br-[2px]": isLast && isSentByViewer, | |
| "rounded-bl-[2px]": isLast && !isSentByViewer, | |
| } | |
| )} | |
| renderMarkdown | |
| /> | |
| {isLast && ( | |
| <MessageTimestamp | |
| className="px-1 text-muted-foreground text-xs" | |
| timestamp={timestamp} | |
| > | |
| {() => ( | |
| <> | |
| {timestamp.toLocaleTimeString([], { | |
| hour: "2-digit", | |
| minute: "2-digit", | |
| })} | |
| {isAI && " • AI agent"} | |
| </> | |
| )} | |
| </MessageTimestamp> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| </PrimitiveMessage> | |
| ); | |
| export function Message({ message, isLast = false, isSentByViewer }: MessageProps) { | |
| return ( | |
| <PrimitiveMessage message={message}> | |
| {({ isAI, isVisitor, timestamp }) => { | |
| const isSentByViewerFinal = isSentByViewer ?? isVisitor; | |
| return ( | |
| <div | |
| className={cn( | |
| "flex w-full gap-2", | |
| isSentByViewerFinal && "flex-row-reverse", | |
| !isSentByViewerFinal && "flex-row" | |
| )} | |
| > | |
| <div | |
| className={cn( | |
| "flex w-full flex-1 flex-col gap-3", | |
| isSentByViewerFinal && "items-end" | |
| )} | |
| > | |
| <MessageContent | |
| bodyMd={message.bodyMd} | |
| className={cn( | |
| "block max-w-full rounded-lg px-3 py-2 text-sm md:w-max md:max-w-[420px]", | |
| { | |
| "bg-co-background-300 text-foreground dark:bg-co-background-600": | |
| !isSentByViewerFinal, | |
| "bg-primary text-primary-foreground": isSentByViewerFinal, | |
| "rounded-br-[2px]": isLast && isSentByViewerFinal, | |
| "rounded-bl-[2px]": isLast && !isSentByViewerFinal, | |
| } | |
| )} | |
| renderMarkdown | |
| /> | |
| {isLast && ( | |
| <MessageTimestamp | |
| className="px-1 text-muted-foreground text-xs" | |
| timestamp={timestamp} | |
| > | |
| {() => ( | |
| <> | |
| {timestamp.toLocaleTimeString([], { | |
| hour: "2-digit", | |
| minute: "2-digit", | |
| })} | |
| {isAI && " • AI agent"} | |
| </> | |
| )} | |
| </MessageTimestamp> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| }} | |
| </PrimitiveMessage> | |
| ); | |
| } |
🤖 Prompt for AI Agents
In apps/web/src/components/conversation/messages/message.tsx around lines 16 to
71, the UI currently relies solely on the isSentByViewer prop so when it’s
omitted messages can appear on the wrong side; use the PrimitiveMessage
render-props isVisitor flag as a fallback. Inside the PrimitiveMessage child,
destructure isVisitor from the render props and compute a local sentByViewer =
isSentByViewer ?? isVisitor (or similar), then replace all uses of
isSentByViewer in the JSX with that local variable so the component defaults to
the message sender when the prop is not provided.
| isSameMessage(a.lastMessage ?? undefined, b.lastMessage ?? undefined) | ||
| ); |
There was a problem hiding this comment.
Drop unnecessary nullish coalescing to undefined.
Passing undefined widens the type and can violate Conversation["lastMessage"] if it is Message | null. Just pass the fields directly.
- isSameMessage(a.lastMessage ?? undefined, b.lastMessage ?? undefined)
+ isSameMessage(a.lastMessage, b.lastMessage)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| isSameMessage(a.lastMessage ?? undefined, b.lastMessage ?? undefined) | |
| ); | |
| isSameMessage(a.lastMessage, b.lastMessage) | |
| ); |
🤖 Prompt for AI Agents
In packages/core/src/store/conversations-store.ts around lines 60 to 61, the
code uses unnecessary nullish coalescing to undefined when calling
isSameMessage(a.lastMessage ?? undefined, b.lastMessage ?? undefined); remove
the "?? undefined" so you pass a.lastMessage and b.lastMessage directly, which
preserves the original types (e.g., Message | null) and avoids widening the type
to include undefined.
| export function useConversations( | ||
| clientOrOptions?: CossistantClient | null | UseConversationsOptions, | ||
| maybeOptions?: UseConversationsOptions | ||
| ): UseConversationsResult { | ||
| const isEnabled = !!client && params.enabled !== false; | ||
|
|
||
| const { data, isLoading, error } = useQuery({ | ||
| queryKey: QUERY_KEYS.conversations(), | ||
| queryFn: async () => { | ||
| if (!client) { | ||
| throw new Error("No client available"); | ||
| } | ||
|
|
||
| return client.listConversations({ | ||
| limit: params.limit || 10, | ||
| orderBy: "createdAt", | ||
| order: "asc", | ||
| }); | ||
| }, | ||
| enabled: isEnabled, | ||
| staleTime: 5 * 60 * 1000, | ||
| const { client } = useSupport(); | ||
| let resolvedOptions: UseConversationsOptions; | ||
|
|
||
| if (isClientLike(clientOrOptions) || clientOrOptions === null) { | ||
| resolvedOptions = { ...DEFAULT_OPTIONS, ...(maybeOptions ?? {}) }; | ||
| } else { | ||
| resolvedOptions = { ...DEFAULT_OPTIONS, ...(clientOrOptions ?? {}) }; | ||
| } | ||
|
|
||
| const store = client?.conversationsStore ?? EMPTY_STORE; | ||
|
|
||
| const selection = useStoreSelector( |
There was a problem hiding this comment.
Explicit client argument is ignored; respect the overload and use an effectiveClient.
Currently the hook always uses the provider client and never the explicit client passed via the overload. This breaks the public API and makes tests/usages that pass a client impossible.
Apply this diff to resolve the effective client and use it throughout:
@@
-export function useConversations(
+export function useConversations(
clientOrOptions?: CossistantClient | null | UseConversationsOptions,
maybeOptions?: UseConversationsOptions
): UseConversationsResult {
- const { client } = useSupport();
- let resolvedOptions: UseConversationsOptions;
+ const support = useSupport();
+ const providerClient = support.client;
+ let resolvedOptions: UseConversationsOptions;
+ const effectiveClient: CossistantClient | null =
+ isClientLike(clientOrOptions) || clientOrOptions === null
+ ? (clientOrOptions as CossistantClient | null)
+ : providerClient;
@@
- if (isClientLike(clientOrOptions) || clientOrOptions === null) {
+ if (isClientLike(clientOrOptions) || clientOrOptions === null) {
resolvedOptions = { ...DEFAULT_OPTIONS, ...(maybeOptions ?? {}) };
} else {
resolvedOptions = { ...DEFAULT_OPTIONS, ...(clientOrOptions ?? {}) };
}
@@
- const store = client?.conversationsStore ?? EMPTY_STORE;
+ const store = effectiveClient?.conversationsStore ?? EMPTY_STORE;
@@
- const {
+ const {
refetch: queryRefetch,
isLoading: queryLoading,
error,
} = useClientQuery<
ListConversationsResponse,
Partial<ListConversationsRequest>
>({
- client,
+ client: effectiveClient,
@@
- enabled: Boolean(client && enabled),
+ enabled: Boolean(effectiveClient && enabled),
refetchInterval,
refetchOnWindowFocus,
refetchOnMount: selection.conversations.length === 0,
initialArgs: requestDefaults,
});Also applies to: 124-141, 167-195, 197-204
🤖 Prompt for AI Agents
In packages/react/src/support/hooks/use-conversations.ts around lines 111 to 126
(and also apply the same change at 124-141, 167-195, 197-204), the hook ignores
an explicit client passed via the overload and always uses the provider client;
resolve an effectiveClient variable that picks the explicit client when
clientOrOptions is a client-like value (or null handling as intended) and
otherwise falls back to the provider client, then use that effectiveClient
everywhere the code currently uses client (including obtaining the
conversationsStore, selectors, and any operations) so the overload is respected
and explicit clients work in tests/usages.
| React.useEffect(() => { | ||
| lastSeenMessageIdRef.current = null; | ||
| markSeenInFlightRef.current = false; | ||
| }, [realConversationId]); | ||
| }, []); | ||
|
|
There was a problem hiding this comment.
Reset seen tracking when conversation changes, not only on mount
Mount‑only reset can leak the last seen id across conversation switches.
Apply:
- React.useEffect(() => {
- lastSeenMessageIdRef.current = null;
- markSeenInFlightRef.current = false;
- }, []);
+ React.useEffect(() => {
+ lastSeenMessageIdRef.current = null;
+ markSeenInFlightRef.current = false;
+ }, [realConversationId]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| React.useEffect(() => { | |
| lastSeenMessageIdRef.current = null; | |
| markSeenInFlightRef.current = false; | |
| }, [realConversationId]); | |
| }, []); | |
| // packages/react/src/support/pages/conversation.tsx | |
| // … somewhere above you have something like: | |
| // const realConversationId = …; | |
| React.useEffect(() => { | |
| lastSeenMessageIdRef.current = null; | |
| markSeenInFlightRef.current = false; | |
| }, [realConversationId]); |
🤖 Prompt for AI Agents
In packages/react/src/support/pages/conversation.tsx around lines 153-157, the
useEffect currently only runs on mount so lastSeenMessageIdRef and
markSeenInFlightRef can leak across conversation switches; update the effect to
depend on the active conversation identifier (e.g., conversation?.id or
conversationId prop) instead of an empty array so the refs are reset whenever
the conversation changes, keeping the same reset logic inside the effect.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
packages/react/src/support/components/support-content.tsx (1)
12-24: Exposestyle(and optionallyasChild) to meet headless component contract.Per package guidelines, components should accept
classNameandstyle(and oftenasChild). Consider this minimal tweak.As per coding guidelines.
type SupportContentProps = { className?: string; + style?: React.CSSProperties; position?: "top" | "bottom"; align?: "right" | "left"; mode?: "floating" | "responsive"; }; export const SupportContent: React.FC<SupportContentProps> = ({ className, + style, position = "bottom", align = "right", mode = "floating", }) => { @@ - <motion.div + <motion.div animate={{ opacity: 1 }} className={containerClasses} + style={style} initial={{ opacity: 0 }}packages/react/src/hooks/utils/use-client-query.ts (2)
44-51: Harden key hashing against circular/non‑serializable values.
JSON.stringifywill throw on circular structures; add a safe fallback to prevent runtime errors during re‑keys.-function toHash(key: readonly unknown[] | string): string { +function toHash(key: readonly unknown[] | string): string { if (typeof key === "string") { return key; } - - return JSON.stringify(key); + try { + return JSON.stringify(key); + } catch { + // Fallback: best‑effort stable-ish string + return Array.isArray(key) ? key.map((v) => String(v)).join("|") : String(key); + } }
111-160: De‑dupe overlapping fetches to avoid interval/focus storms.Multiple triggers can start concurrent requests; only latest wins but the rest still hit the backend. Track an in‑flight promise and return it.
export function useClientQuery<TData, TArgs = void>( @@ const latestArgsRef = useRef<TArgs | undefined>(initialArgs); const fetchIdRef = useRef(0); + const inFlightRef = useRef<Promise<TData> | null>(null); @@ const execute = useCallback( async (args?: TArgs, ignoreEnabled = false): Promise<TData | undefined> => { if (!(client && (enabled || ignoreEnabled))) { return stateRef.current.data; } + // Return existing request if one is in-flight + if (inFlightRef.current) { + return inFlightRef.current.then( + (v) => v, + () => stateRef.current.data + ); + } + const fetchId = fetchIdRef.current + 1; fetchIdRef.current = fetchId; latestArgsRef.current = args; @@ try { - const result = await queryFn(client, args); + const promise = queryFn(client, args); + inFlightRef.current = promise; + const result = await promise; if (!isMountedRef.current || fetchId !== fetchIdRef.current) { return stateRef.current.data; } setState({ data: result, error: null, status: "success" }); hasFetchedRef.current = true; return result; } catch (error: unknown) { if (!isMountedRef.current || fetchId !== fetchIdRef.current) { return stateRef.current.data; } const normalized = toError(error); setState((current) => ({ data: current.data, error: normalized, status: "error", })); throw normalized; + } finally { + inFlightRef.current = null; } }, [client, enabled, queryFn] );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
packages/next/src/hooks/use-rest-client.ts(1 hunks)packages/react/src/hooks/index.ts(1 hunks)packages/react/src/hooks/store/use-conversations-store.ts(1 hunks)packages/react/src/hooks/store/use-store-selector.ts(1 hunks)packages/react/src/hooks/store/use-website-store.ts(1 hunks)packages/react/src/hooks/use-conversation-messages.ts(1 hunks)packages/react/src/hooks/use-conversations.ts(1 hunks)packages/react/src/hooks/utils/use-client-query.ts(1 hunks)packages/react/src/hooks/utils/use-default-messages.ts(1 hunks)packages/react/src/hooks/utils/use-grouped-messages.ts(0 hunks)packages/react/src/hooks/utils/use-realtime-support.ts(1 hunks)packages/react/src/index.ts(1 hunks)packages/react/src/provider.tsx(2 hunks)packages/react/src/realtime/index.ts(1 hunks)packages/react/src/realtime/support-provider.tsx(2 hunks)packages/react/src/support/components/support-content.tsx(1 hunks)packages/react/src/support/hooks/index.ts(1 hunks)packages/react/src/support/hooks/use-conversation.ts(1 hunks)packages/react/src/support/hooks/use-messages.ts(0 hunks)packages/react/src/support/hooks/use-website.ts(1 hunks)packages/react/src/support/pages/conversation.tsx(4 hunks)
💤 Files with no reviewable changes (2)
- packages/react/src/hooks/utils/use-grouped-messages.ts
- packages/react/src/support/hooks/use-messages.ts
✅ Files skipped from review due to trivial changes (1)
- packages/next/src/hooks/use-rest-client.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/react/src/support/hooks/use-website.ts
🧰 Additional context used
📓 Path-based instructions (13)
packages/react/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Develop headless React components (including and primitives) under packages/react/src/
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.tsx: Use the Compound Components pattern with namespaced exports (e.g., FormWizard.Root, FormWizard.Step) so pieces compose under a single parent
Ensure accessibility: keyboard navigation support
Ensure accessibility: include appropriate ARIA attributes
Ensure accessibility: use semantic markup for screen readers
Maintain proper focus management in interactive components
Structure components with forwardRef and typed props/refs as shown (ComponentRef, HTMLAttributes, ReactNode)
Root component acts as main context provider, manages global state, and handles initialization
Child components must be used within Root, read context via hooks, support asChild, and follow consistent props
Props ordering: list required props first; optional props have clear defaults; support common HTML attributes; expose className
Support composition with an asChild prop using Radix UI Slot to allow element replacement
Styling: support className, use data-* attributes for state, and allow style customization
Event handling: support standard DOM events, add custom events when needed, and manage propagation correctly
Base component pattern: forwardRef, choose Slot or intrinsic element via asChild, pass ref/className and spread props
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/support/components/support-content.tsxpackages/react/src/provider.tsxpackages/react/src/realtime/support-provider.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't use theaccessKeyattribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Ensure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include the words "image", "picture", or "photo" in img alt prop
Don't use an explicit role that's the same as the element's implicit role
Static elements with click handlers must have a valid role attribute
Always include a <title> element within SVG elements
Provide meaningful alt text for elements that require alt text
Ensure anchors have content accessible to screen readers
Assign tabIndex to non-interactive elements only when using aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Ensure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Elements with interactive roles and handlers must be focusable
Heading elements must have content accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
Prefer semantic elements instead of role attributes in JSX
Ensure all anchors are valid and navigable
Ensure all aria-* properties are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Don't use unnecessary fragmen...Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/support/components/support-content.tsxpackages/react/src/provider.tsxpackages/react/src/realtime/support-provider.tsx**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class membersFiles:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.ts**/*.{js,tsx,ts,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Use arrow functions instead of function expressions
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.ts**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace charactersFiles:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.ts**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx,html}: Ensure the preconnect attribute is used when using Google Fonts
Don't use target="_blank" without rel="noopener"
Use a recommended display strategy with Google FontsFiles:
packages/react/src/support/pages/conversation.tsxpackages/react/src/support/components/support-content.tsxpackages/react/src/provider.tsxpackages/react/src/realtime/support-provider.tsx**/*.{js,ts,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't redeclare variables, functions, classes, and types in the same scope
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.tspackages/react/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react-package-rules.mdc)
packages/react/**/*.{ts,tsx}: Components must render with no built-in styles (headless, unstyled)
Components must accept className, style, and asChild props to enable external styling/control
Do not use emotion, styled-components, or style-prop APIs; styling is via Tailwind/CSS through className/style
Use useRenderElement to create elements and encapsulate render logic (supporting intrinsic tags and render prop overrides)
Components with internal state must be controllable via props (e.g., value/defaultValue/onChange or isOpen/onOpenChange)
Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function child
May reference Base UI logic internally, but never expose Base UI or MUI APIs; do not leak getRootProps/getInputProps; prefer useRenderElement
Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic components
Provide a render prop API: render?: (props, state) => ReactElement, and allow className to be string or (state) => string
Support developer-owned rendering: components should not impose visual structure and should allow parent-driven markup via render prop/asChildFiles:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.tspackages/**
📄 CodeRabbit inference engine (AGENTS.md)
Put shared contracts and utilities in packages/* (e.g., core, types, transactional)
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/support/components/support-content.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/provider.tsxpackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/realtime/support-provider.tsxpackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.ts**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...Files:
packages/react/src/hooks/use-conversations.tspackages/react/src/hooks/utils/use-default-messages.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/hooks/store/use-conversations-store.tspackages/react/src/hooks/store/use-store-selector.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/utils/use-realtime-support.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/realtime/index.tspackages/react/src/index.ts🧠 Learnings (9)
📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to apps/web/src/hooks/use*.{ts,tsx} : Name React hooks with a use* prefix and place them in apps/web/src/hooksApplied to files:
packages/react/src/support/components/support-content.tsxpackages/react/src/support/hooks/index.tspackages/react/src/hooks/index.ts📚 Learning: 2025-09-17T09:00:28.751Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: CLAUDE.md:0-0 Timestamp: 2025-09-17T09:00:28.751Z Learning: Applies to packages/react/src/**/*.{ts,tsx} : Develop headless React components (including <Support /> and primitives) under packages/react/src/Applied to files:
packages/react/src/support/components/support-content.tsxpackages/react/src/index.ts📚 Learning: 2025-09-17T16:59:53.676Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/react-package-rules.mdc:0-0 Timestamp: 2025-09-17T16:59:53.676Z Learning: Applies to packages/react/**/*.{ts,tsx} : Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function childApplied to files:
packages/react/src/support/components/support-content.tsxpackages/react/src/hooks/index.ts📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.{ts,tsx} : Manage shared component state via React ContextApplied to files:
packages/react/src/realtime/support-provider.tsx📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{jsx,tsx} : Ensure all dependencies are correctly specified in React hooksApplied to files:
packages/react/src/hooks/index.ts📚 Learning: 2025-09-17T16:59:53.676Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/react-package-rules.mdc:0-0 Timestamp: 2025-09-17T16:59:53.676Z Learning: Applies to packages/react/**/*.{ts,tsx} : Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic componentsApplied to files:
packages/react/src/hooks/index.ts📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to apps/api/src/ws/**/*.{ts,tsx} : Place WebSocket handlers under apps/api/src/wsApplied to files:
packages/react/src/hooks/utils/use-realtime-support.ts📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to **/*.{ts,tsx} : Prefer explicit return types on exported APIs in TypeScriptApplied to files:
packages/react/src/index.ts📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.{ts,tsx} : Export type definitions for the public APIApplied to files:
packages/react/src/index.ts🧬 Code graph analysis (11)
packages/react/src/support/pages/conversation.tsx (2)
packages/react/src/hooks/use-conversation-messages.ts (1)
useConversationMessages(46-127)packages/react/src/index.ts (1)
useConversationMessages(6-6)packages/react/src/hooks/use-conversations.ts (5)
packages/types/src/api/conversation.ts (2)
ListConversationsResponse(109-111)ListConversationsRequest(90-92)packages/core/src/client.ts (1)
CossistantClient(66-349)packages/react/src/provider.tsx (1)
useSupport(226-243)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(52-254)packages/react/src/hooks/store/use-website-store.ts (2)
packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(52-254)packages/react/src/provider.tsx (1)
packages/react/src/hooks/store/use-website-store.ts (1)
useWebsiteStore(67-112)packages/react/src/support/hooks/use-conversation.ts (5)
packages/core/src/store/conversations-store.ts (1)
ConversationsState(7-11)packages/types/src/api/conversation.ts (2)
GetConversationResponse(135-137)GetConversationRequest(123-125)packages/react/src/provider.tsx (1)
useSupport(226-243)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(52-254)packages/react/src/hooks/store/use-conversations-store.ts (2)
packages/react/src/provider.tsx (1)
useSupport(226-243)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/realtime/support-provider.tsx (3)
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/registry.ts (1)
handlers(10-13)packages/react/src/realtime/index.ts (1)
RealtimeEventHandlersMap(26-28)apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.ts (1)
RealtimeEventHandlersMap(28-29)packages/react/src/hooks/store/use-store-selector.ts (1)
packages/core/src/store/create-store.ts (1)
subscribe(50-55)packages/react/src/hooks/use-conversation-messages.ts (4)
packages/core/src/store/messages-store.ts (2)
ConversationMessagesState(6-10)MessagesState(12-14)packages/react/src/provider.tsx (1)
useSupport(226-243)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(52-254)packages/react/src/hooks/utils/use-client-query.ts (1)
packages/react/src/index.ts (1)
useClientQuery(21-21)packages/react/src/realtime/index.ts (2)
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.ts (4)
RealtimeEventHandlerContext(19-20)RealtimeEventHandlerParams(22-23)RealtimeEventHandler(25-26)RealtimeEventHandlersMap(28-29)apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/registry.ts (2)
createRealtimeEventDispatcher(15-22)handlers(10-13)⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
🔇 Additional comments (8)
packages/react/src/support/components/support-content.tsx (1)
5-5: Path realignment LGTM.The updated utils path for
useMultimodalInputmatches the hook reorg.packages/react/src/realtime/index.ts (1)
175-180: Subscription effect is clean and minimal.
useRealtimeEventscorrectly subscribes/unsubscribes using the memoized dispatcher.packages/react/src/hooks/utils/use-default-messages.ts (1)
1-1: Core’snanoiddependency is already declared
The core package.json lists"nanoid": "^5.1.5"under dependencies (packages/core/package.json:32), so the “Cannot find package 'nanoid'” error points to an install or workspace‐linking issue rather than a missing dep. Reinstall your node_modules or verify your monorepo setup.Likely an incorrect or invalid review comment.
packages/react/src/hooks/utils/use-realtime-support.ts (1)
3-3: Import path and export verified
useWebSocketis correctly defined and exported inpackages/react/src/support/context/websocket.tsx, and no circular imports were introduced by the move.packages/react/src/hooks/store/use-website-store.ts (1)
74-83: Fix the non-serializable query key before this ships
useClientQuerystill hashes keys viaJSON.stringify(seetoHashinuse-client-query.ts). Passing the liveCossistantClientinstance intoqueryKeywill either throw (TypeError: Converting circular structure to JSON) or collapse every client to the same cache bucket ({}), so subsequent renders will reuse/evict the wrong website data. Please replace the object with a stable scalar identifier (e.g., derive a string from the client’s public key / API origin, or expose a dedicatedclient.getIdentityKey()and use that here) before merging.packages/react/src/support/pages/conversation.tsx (1)
117-120: Reset seen refs when conversation switchesThis effect still only runs once, so the seen-tracking refs leak between different conversations. The stale IDs break the “mark as seen” flow after navigating. Please restore the dependency on
realConversationId.- }, []); + }, [realConversationId]);packages/react/src/support/hooks/use-conversation.ts (1)
16-21: Match EMPTY_STORE.subscribe to the BasicStore signature.The fallback store drops the listener parameter, so it can't be treated as a BasicStore.
useStoreSelectorpasses a callback and relies on it firing; with the current signature we lose type safety and break updates whenever the real store is missing. Please accept the listener argument and return a noop unsubscribe.const EMPTY_STORE = { getState: (): ConversationsState => EMPTY_STATE, - subscribe: () => () => { + subscribe: (_listener: () => void) => () => { // noop }, };packages/react/src/hooks/use-conversation-messages.ts (1)
20-25: Restore the EMPTY_MESSAGES_STORE.subscribe listener parameter.This fallback store no longer satisfies the BasicStore shape—
useStoreSelectorpasses a listener, but this stub ignores the argument, breaking type compatibility and preventing updates from ever propagating when the real store is absent. Please align the signature with the real store.const EMPTY_MESSAGES_STORE = { getState: (): MessagesState => ({ conversations: {} }), - subscribe: () => () => { + subscribe: (_listener: () => void) => () => { // noop }, };
| const { client } = useSupport(); | ||
| let resolvedOptions: UseConversationsOptions; | ||
|
|
||
| if (isClientLike(clientOrOptions) || clientOrOptions === null) { | ||
| resolvedOptions = { ...DEFAULT_OPTIONS, ...(maybeOptions ?? {}) }; | ||
| } else { | ||
| resolvedOptions = { ...DEFAULT_OPTIONS, ...(clientOrOptions ?? {}) }; | ||
| } | ||
|
|
||
| const store = client?.conversationsStore ?? EMPTY_STORE; | ||
|
|
||
| const selection = useStoreSelector( | ||
| store, | ||
| (state): ConversationsSelection => ({ | ||
| conversations: state.ids | ||
| .map((id) => state.byId[id]) | ||
| .filter( | ||
| ( | ||
| conversation | ||
| ): conversation is ListConversationsResponse["conversations"][number] => | ||
| Boolean(conversation) | ||
| ), | ||
| pagination: state.pagination, | ||
| }), | ||
| areSelectionsEqual | ||
| ); |
There was a problem hiding this comment.
Honor the explicit client parameter
The overload useConversations(client, options) no longer works: clientOrOptions is ignored and we always pull client from context. Passing a client outside the provider now throws and even inside the provider the argument is a no-op. Wire the provided client through before falling back to context.
- const { client } = useSupport();
- let resolvedOptions: UseConversationsOptions;
-
- if (isClientLike(clientOrOptions) || clientOrOptions === null) {
- resolvedOptions = { ...DEFAULT_OPTIONS, ...(maybeOptions ?? {}) };
- } else {
- resolvedOptions = { ...DEFAULT_OPTIONS, ...(clientOrOptions ?? {}) };
- }
-
- const store = client?.conversationsStore ?? EMPTY_STORE;
+ const support = useSupport();
+ const contextClient = support.client;
+ const client =
+ isClientLike(clientOrOptions) || clientOrOptions === null
+ ? clientOrOptions ?? contextClient
+ : contextClient;
+
+ let resolvedOptions: UseConversationsOptions;
+ if (isClientLike(clientOrOptions) || clientOrOptions === null) {
+ resolvedOptions = { ...DEFAULT_OPTIONS, ...(maybeOptions ?? {}) };
+ } else {
+ resolvedOptions = { ...DEFAULT_OPTIONS, ...(clientOrOptions ?? {}) };
+ }
+
+ const store = client?.conversationsStore ?? EMPTY_STORE;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { client } = useSupport(); | |
| let resolvedOptions: UseConversationsOptions; | |
| if (isClientLike(clientOrOptions) || clientOrOptions === null) { | |
| resolvedOptions = { ...DEFAULT_OPTIONS, ...(maybeOptions ?? {}) }; | |
| } else { | |
| resolvedOptions = { ...DEFAULT_OPTIONS, ...(clientOrOptions ?? {}) }; | |
| } | |
| const store = client?.conversationsStore ?? EMPTY_STORE; | |
| const selection = useStoreSelector( | |
| store, | |
| (state): ConversationsSelection => ({ | |
| conversations: state.ids | |
| .map((id) => state.byId[id]) | |
| .filter( | |
| ( | |
| conversation | |
| ): conversation is ListConversationsResponse["conversations"][number] => | |
| Boolean(conversation) | |
| ), | |
| pagination: state.pagination, | |
| }), | |
| areSelectionsEqual | |
| ); | |
| const support = useSupport(); | |
| const contextClient = support.client; | |
| const client = | |
| isClientLike(clientOrOptions) || clientOrOptions === null | |
| ? clientOrOptions ?? contextClient | |
| : contextClient; | |
| let resolvedOptions: UseConversationsOptions; | |
| if (isClientLike(clientOrOptions) || clientOrOptions === null) { | |
| resolvedOptions = { ...DEFAULT_OPTIONS, ...(maybeOptions ?? {}) }; | |
| } else { | |
| resolvedOptions = { ...DEFAULT_OPTIONS, ...(clientOrOptions ?? {}) }; | |
| } | |
| const store = client?.conversationsStore ?? EMPTY_STORE; | |
| const selection = useStoreSelector( | |
| store, | |
| (state): ConversationsSelection => ({ | |
| conversations: state.ids | |
| .map((id) => state.byId[id]) | |
| .filter( | |
| ( | |
| conversation | |
| ): conversation is ListConversationsResponse["conversations"][number] => | |
| Boolean(conversation) | |
| ), | |
| pagination: state.pagination, | |
| }), | |
| areSelectionsEqual | |
| ); |
🤖 Prompt for AI Agents
In packages/react/src/hooks/use-conversations.ts around lines 115 to 140, the
function always uses the client from context (const { client } = useSupport())
which ignores an explicitly passed client; update the logic to prefer a provided
client when clientOrOptions is client-like (use that client for store and any
client access) and only fall back to the context client otherwise; keep the
existing resolvedOptions logic but ensure store is derived from the chosen
client (providedClient ?? contextClient) so passing useConversations(client,
options) works again.
| type UseClientQueryOptions<TData, TArgs> = { | ||
| client: CossistantClient | null; | ||
| queryKey: readonly unknown[] | string; | ||
| queryFn: QueryFn<TData, TArgs>; | ||
| enabled?: boolean; | ||
| refetchInterval?: number | false; | ||
| refetchOnWindowFocus?: boolean; | ||
| refetchOnMount?: boolean; | ||
| initialData?: TData; | ||
| initialArgs?: TArgs; | ||
| }; | ||
|
|
||
| type UseClientQueryResult<TData, TArgs> = { | ||
| data: TData | undefined; | ||
| error: Error | null; | ||
| isLoading: boolean; | ||
| refetch: (args?: TArgs) => Promise<TData | undefined>; | ||
| }; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Export hook option/result types for public API re-exports.
PR description mentions re‑exporting these types; they’re currently internal. Exporting aligns with our TS API guidelines and avoids broken re-exports.
As per coding guidelines.
-type UseClientQueryOptions<TData, TArgs> = {
+export type UseClientQueryOptions<TData, TArgs> = {
client: CossistantClient | null;
queryKey: readonly unknown[] | string;
queryFn: QueryFn<TData, TArgs>;
enabled?: boolean;
refetchInterval?: number | false;
refetchOnWindowFocus?: boolean;
refetchOnMount?: boolean;
initialData?: TData;
initialArgs?: TArgs;
};
-type UseClientQueryResult<TData, TArgs> = {
+export type UseClientQueryResult<TData, TArgs> = {
data: TData | undefined;
error: Error | null;
isLoading: boolean;
refetch: (args?: TArgs) => Promise<TData | undefined>;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| type UseClientQueryOptions<TData, TArgs> = { | |
| client: CossistantClient | null; | |
| queryKey: readonly unknown[] | string; | |
| queryFn: QueryFn<TData, TArgs>; | |
| enabled?: boolean; | |
| refetchInterval?: number | false; | |
| refetchOnWindowFocus?: boolean; | |
| refetchOnMount?: boolean; | |
| initialData?: TData; | |
| initialArgs?: TArgs; | |
| }; | |
| type UseClientQueryResult<TData, TArgs> = { | |
| data: TData | undefined; | |
| error: Error | null; | |
| isLoading: boolean; | |
| refetch: (args?: TArgs) => Promise<TData | undefined>; | |
| }; | |
| export type UseClientQueryOptions<TData, TArgs> = { | |
| client: CossistantClient | null; | |
| queryKey: readonly unknown[] | string; | |
| queryFn: QueryFn<TData, TArgs>; | |
| enabled?: boolean; | |
| refetchInterval?: number | false; | |
| refetchOnWindowFocus?: boolean; | |
| refetchOnMount?: boolean; | |
| initialData?: TData; | |
| initialArgs?: TArgs; | |
| }; | |
| export type UseClientQueryResult<TData, TArgs> = { | |
| data: TData | undefined; | |
| error: Error | null; | |
| isLoading: boolean; | |
| refetch: (args?: TArgs) => Promise<TData | undefined>; | |
| }; |
🤖 Prompt for AI Agents
In packages/react/src/hooks/utils/use-client-query.ts around lines 17 to 35, the
hook option/result types are declared but not exported; export both
UseClientQueryOptions and UseClientQueryResult so they become part of the public
API and can be re-exported by the package. Update the type declarations to be
exported (export type ...) and ensure any related imports/exports in the package
index re-export these types if required by the PR.
| export type RealtimeEventHandlerContext<TContext = EmptyObject> = {} & TContext; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
Context type change—verify downstreams no longer depend on queryClient.
Since RealtimeEventHandlerContext dropped QueryClient, confirm all consumers (apps/web and others) pass the new context shape and don’t read queryClient.
Run:
🏁 Script executed:
#!/bin/bash
rg -nP '\bRealtimeEventHandlerContext<|context\.\s*queryClient\b' --glob '!**/node_modules/**'Length of output: 148
🏁 Script executed:
#!/bin/bash
# Search for usages of the updated context type
rg -nP '\bRealtimeEventHandlerContext\b' --glob '*.ts' --glob '*.tsx'
# Search for any remaining direct access to queryClient on the context object
rg -nP 'context\.\s*queryClient\b' --glob '*.ts' --glob '*.tsx'Length of output: 1399
Remove direct context.queryClient references
- apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.ts lines 189 and 206 still call
context.queryClient; refactor these handlers to align with the newRealtimeEventHandlerContext(e.g. injectQueryClientviaTContextor update to the revised API).
🤖 Prompt for AI Agents
In packages/react/src/realtime/index.ts around lines 9 to 10, the
RealtimeEventHandlerContext type was simplified to "{} & TContext" which removed
any built-in queryClient; update the conversation-seen handlers at
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.ts
(lines ~189 and ~206) to stop referencing context.queryClient directly: either
extend the RealtimeEventHandlerContext with a QueryClient in the TContext when
wiring handlers (inject QueryClient into TContext) or change those handler
functions to accept QueryClient via parameters or a factory closure; implement
the chosen approach consistently and update types/signatures so handlers access
the QueryClient from the injected TContext (or param) instead of
context.queryClient.
| for (const handler of eventHandlers) { | ||
| try { | ||
| handler({ | ||
| event, | ||
| context, | ||
| }); | ||
| } catch (error) { | ||
| if (onError) { | ||
| onError(error, event.type); | ||
| } else { | ||
| console.error("[Realtime] Event handler failed", { | ||
| error, | ||
| eventType: event.type, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this comment.
Async handler errors are not caught; remove console usage and route through onError.
try/catch won’t catch rejections from async handlers; current fallback logs via console.error (forbidden by guidelines). Wrap handler calls in Promise.resolve(...).catch(...) and rely solely on onError.
As per coding guidelines.
- for (const handler of eventHandlers) {
- try {
- handler({
- event,
- context,
- });
- } catch (error) {
- if (onError) {
- onError(error, event.type);
- } else {
- console.error("[Realtime] Event handler failed", {
- error,
- eventType: event.type,
- });
- }
- }
- }
+ for (const handler of eventHandlers) {
+ // Normalize sync/async handlers and catch rejections
+ void Promise.resolve(
+ handler({
+ event,
+ context,
+ })
+ ).catch((error) => {
+ onError?.(error, event.type);
+ });
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (const handler of eventHandlers) { | |
| try { | |
| handler({ | |
| event, | |
| context, | |
| }); | |
| } catch (error) { | |
| if (onError) { | |
| onError(error, event.type); | |
| } else { | |
| console.error("[Realtime] Event handler failed", { | |
| error, | |
| eventType: event.type, | |
| }); | |
| } | |
| } | |
| } | |
| }; | |
| for (const handler of eventHandlers) { | |
| // Normalize sync/async handlers and catch rejections | |
| void Promise.resolve( | |
| handler({ | |
| event, | |
| context, | |
| }) | |
| ).catch((error) => { | |
| onError?.(error, event.type); | |
| }); | |
| } |
🤖 Prompt for AI Agents
In packages/react/src/realtime/index.ts around lines 57 to 74, the current
try/catch won't catch rejections from async event handlers and falls back to
console.error (forbidden). Replace the synchronous try/catch with
Promise.resolve(handler({ event, context })).catch(err => { if (onError)
onError(err, event.type); }); and remove the console.error fallback so all async
and sync handler errors are routed exclusively through onError to avoid
unhandled rejections and forbidden console usage.
| try { | ||
| message = mapEventToMessage(params); | ||
| } catch (error) { | ||
| if (onTransformError) { | ||
| onTransformError(error, params); | ||
| } else { | ||
| console.error("[Realtime] Failed to transform MESSAGE_CREATED event", { | ||
| error, | ||
| eventType: params.event.type, | ||
| }); | ||
| } | ||
| return; | ||
| } |
There was a problem hiding this comment.
Same issue in transform error path; don’t log to console.
Route transform failures through onTransformError only; drop console logging.
As per coding guidelines.
- } catch (error) {
- if (onTransformError) {
- onTransformError(error, params);
- } else {
- console.error("[Realtime] Failed to transform MESSAGE_CREATED event", {
- error,
- eventType: params.event.type,
- });
- }
- return;
- }
+ } catch (error) {
+ onTransformError?.(error, params);
+ return;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| message = mapEventToMessage(params); | |
| } catch (error) { | |
| if (onTransformError) { | |
| onTransformError(error, params); | |
| } else { | |
| console.error("[Realtime] Failed to transform MESSAGE_CREATED event", { | |
| error, | |
| eventType: params.event.type, | |
| }); | |
| } | |
| return; | |
| } | |
| try { | |
| message = mapEventToMessage(params); | |
| } catch (error) { | |
| onTransformError?.(error, params); | |
| return; | |
| } |
🤖 Prompt for AI Agents
In packages/react/src/realtime/index.ts around lines 121 to 133, the transform
error path currently falls back to console.error when onTransformError is not
provided; change this so transform failures are only routed through
onTransformError and remove the console logging. Modify the catch block to call
onTransformError(error, params) if present, and otherwise silently return (no
console.error), ensuring no direct console logging occurs in this path per
guidelines.
| const messagesQuery = useConversationMessages(conversationId); | ||
|
|
There was a problem hiding this comment.
Prevent getConversationMessages calls for the pending conversation placeholder
conversationId is still the sentinel PENDING_CONVERSATION_ID until the server conversation exists, but the new hook fires immediately and invokes getConversationMessages with that fake id. The request will 404 and leave the hook stuck in an error state. Gate the query on hasRealConversation so we only fetch when the real id is available.
- const messagesQuery = useConversationMessages(conversationId);
+ const messagesQuery = useConversationMessages(conversationId, {
+ enabled: hasRealConversation,
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const messagesQuery = useConversationMessages(conversationId); | |
| ─ const messagesQuery = useConversationMessages(conversationId); | |
| const messagesQuery = useConversationMessages(conversationId, { | |
| enabled: hasRealConversation, | |
| }); |
🤖 Prompt for AI Agents
In packages/react/src/support/pages/conversation.tsx around lines 58-59, the
hook useConversationMessages is being called with the sentinel
PENDING_CONVERSATION_ID which triggers a 404; change the call so it is gated by
hasRealConversation — e.g. call useConversationMessages(hasRealConversation ?
conversationId : undefined, { enabled: hasRealConversation }) or pass an options
object with enabled: hasRealConversation so the query only runs once a real
conversationId exists.
…minate-react-query-usage refactor(react): simplify hooks around support client context
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/react/src/support/pages/conversation.tsx (2)
110-112: Surface send errors in the UICurrently, send failures are only logged; include
sendMessage.errorso the input can display it.- const actualError = error || messagesQuery.error; + const actualError = error || messagesQuery.error || sendMessage.error;
70-95: Files passed to mutate are ignored downstream
useSendMessagedoesn’t accept/forwardfiles; attachments won’t be sent. Either removefileshere or plumb them through the hook and payload builder.Proposed minimal plumbing (outside this file):
- packages/react/src/hooks/use-send-message.ts
- async (payload: SendMessageOptions): Promise<SendMessageResult | null> => { + async (payload: SendMessageOptions): Promise<SendMessageResult | null> => { const { conversationId: providedConversationId, message, + files, defaultMessages = [], visitorId, onSuccess, onError, } = payload; @@ - const messagePayload = buildMessagePayload( - message, - conversationId, - visitorId ?? null - ); + const messagePayload = buildMessagePayload( + message, + conversationId, + visitorId ?? null, + files ?? [] + );
- Ensure
SendMessageOptionsincludesfiles?: File[]and updatebuildMessagePayloadsignature to accept and embed files.
🧹 Nitpick comments (5)
packages/react/src/hooks/store/use-website-store.ts (2)
21-24: Exposeenabledoption for parity with other hooksAdd
enabled?: booleanand wire it touseClientQuery(defaults to true). Improves control/consistency with hooks likeuseConversationMessages.export type UseWebsiteStoreOptions = { + enabled?: boolean; refetchInterval?: number | false; refetchOnWindowFocus?: boolean; };Also applies to: 50-50
39-44: Consider readingclientfrom context for API consistencyOther hooks (
useConversation,useConversationMessages) source the client fromuseSupport(). Makingclientoptional here (fallback to context) would unify the API surface.packages/react/src/support/pages/conversation.tsx (1)
91-93: Remove console usage; route errors to UI/logging infraProject guideline forbids
console. Drop logs and rely onactualError(above) or a logger service.As per coding guidelines
- onError: (_error) => { - console.error("Failed to send message:", _error); - }, + onError: () => {},- console.error("Failed to mark conversation as seen:", markSeenError); + // no-op (error surfaced via store/hooks if needed)Also applies to: 149-149
packages/react/src/hooks/use-conversation.ts (1)
1-8: Stabilizerefetchwith useCallbackNot mandatory, but keeps identity stable for consumers.
+import { useCallback } from "react"; @@ - const refetch = (args?: GetConversationRequest) => { + const refetch = useCallback((args?: GetConversationRequest) => { if (!conversationId) { return Promise.resolve(undefined); } return queryRefetch({ conversationId, ...args, }); - }; + }, [conversationId, queryRefetch]);Also applies to: 62-71
packages/react/src/hooks/utils/use-rest-client.ts (1)
12-17: Documented error handling doesn't match implementationThe JSDoc promises we surface configuration issues via the
errorfield, but the hook throws instead and always returns{ error: null }. Either wire the catch branch to populateerror(and stop throwing), or update the return type/docs to droperror.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
packages/react/src/hooks/index.ts(1 hunks)packages/react/src/hooks/store/use-website-store.ts(1 hunks)packages/react/src/hooks/use-conversation-messages.ts(1 hunks)packages/react/src/hooks/use-conversation.ts(1 hunks)packages/react/src/hooks/use-conversations.ts(1 hunks)packages/react/src/hooks/use-create-conversation.ts(1 hunks)packages/react/src/hooks/use-send-message.ts(1 hunks)packages/react/src/hooks/use-visitor.ts(1 hunks)packages/react/src/hooks/utils/use-client-query.ts(1 hunks)packages/react/src/hooks/utils/use-rest-client.ts(1 hunks)packages/react/src/index.ts(1 hunks)packages/react/src/provider.tsx(3 hunks)packages/react/src/support/hooks/index.ts(1 hunks)packages/react/src/support/hooks/use-conversation.ts(1 hunks)packages/react/src/support/hooks/use-create-conversation.ts(1 hunks)packages/react/src/support/hooks/use-send-message.ts(1 hunks)packages/react/src/support/pages/conversation.tsx(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/react/src/hooks/index.ts
- packages/react/src/support/hooks/use-conversation.ts
🧰 Additional context used
📓 Path-based instructions (13)
packages/react/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Develop headless React components (including and primitives) under packages/react/src/
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't use theaccessKeyattribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class members
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
**/*.{js,tsx,ts,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Use arrow functions instead of function expressions
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace characters
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
**/*.{js,ts,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't redeclare variables, functions, classes, and types in the same scope
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
packages/react/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react-package-rules.mdc)
packages/react/**/*.{ts,tsx}: Components must render with no built-in styles (headless, unstyled)
Components must accept className, style, and asChild props to enable external styling/control
Do not use emotion, styled-components, or style-prop APIs; styling is via Tailwind/CSS through className/style
Use useRenderElement to create elements and encapsulate render logic (supporting intrinsic tags and render prop overrides)
Components with internal state must be controllable via props (e.g., value/defaultValue/onChange or isOpen/onOpenChange)
Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function child
May reference Base UI logic internally, but never expose Base UI or MUI APIs; do not leak getRootProps/getInputProps; prefer useRenderElement
Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic components
Provide a render prop API: render?: (props, state) => ReactElement, and allow className to be string or (state) => string
Support developer-owned rendering: components should not impose visual structure and should allow parent-driven markup via render prop/asChild
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
packages/**
📄 CodeRabbit inference engine (AGENTS.md)
Put shared contracts and utilities in packages/* (e.g., core, types, transactional)
Files:
packages/react/src/hooks/use-conversation.tspackages/react/src/support/hooks/use-create-conversation.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/support/hooks/index.tspackages/react/src/hooks/use-visitor.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/index.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/hooks/use-send-message.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/use-conversations.tspackages/react/src/hooks/use-create-conversation.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/utils/use-client-query.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.tsx: Use the Compound Components pattern with namespaced exports (e.g., FormWizard.Root, FormWizard.Step) so pieces compose under a single parent
Ensure accessibility: keyboard navigation support
Ensure accessibility: include appropriate ARIA attributes
Ensure accessibility: use semantic markup for screen readers
Maintain proper focus management in interactive components
Structure components with forwardRef and typed props/refs as shown (ComponentRef, HTMLAttributes, ReactNode)
Root component acts as main context provider, manages global state, and handles initialization
Child components must be used within Root, read context via hooks, support asChild, and follow consistent props
Props ordering: list required props first; optional props have clear defaults; support common HTML attributes; expose className
Support composition with an asChild prop using Radix UI Slot to allow element replacement
Styling: support className, use data-* attributes for state, and allow style customization
Event handling: support standard DOM events, add custom events when needed, and manage propagation correctly
Base component pattern: forwardRef, choose Slot or intrinsic element via asChild, pass ref/className and spread props
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/provider.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Ensure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include the words "image", "picture", or "photo" in img alt prop
Don't use an explicit role that's the same as the element's implicit role
Static elements with click handlers must have a valid role attribute
Always include a <title> element within SVG elements
Provide meaningful alt text for elements that require alt text
Ensure anchors have content accessible to screen readers
Assign tabIndex to non-interactive elements only when using aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Ensure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Elements with interactive roles and handlers must be focusable
Heading elements must have content accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
Prefer semantic elements instead of role attributes in JSX
Ensure all anchors are valid and navigable
Ensure all aria-* properties are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Don't use unnecessary fragmen...Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/provider.tsx**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx,html}: Ensure the preconnect attribute is used when using Google Fonts
Don't use target="_blank" without rel="noopener"
Use a recommended display strategy with Google FontsFiles:
packages/react/src/support/pages/conversation.tsxpackages/react/src/provider.tsx🧠 Learnings (5)
📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to apps/web/src/hooks/use*.{ts,tsx} : Name React hooks with a use* prefix and place them in apps/web/src/hooksApplied to files:
packages/react/src/support/hooks/index.tspackages/react/src/support/hooks/use-send-message.ts📚 Learning: 2025-09-17T09:00:28.751Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: CLAUDE.md:0-0 Timestamp: 2025-09-17T09:00:28.751Z Learning: Applies to packages/react/src/**/*.{ts,tsx} : Develop headless React components (including <Support /> and primitives) under packages/react/src/Applied to files:
packages/react/src/index.ts📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to **/*.{ts,tsx} : Prefer explicit return types on exported APIs in TypeScriptApplied to files:
packages/react/src/index.tspackages/react/src/hooks/utils/use-client-query.ts📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.{ts,tsx} : Export type definitions for the public APIApplied to files:
packages/react/src/hooks/utils/use-client-query.ts📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{ts,tsx} : Use export type for typesApplied to files:
packages/react/src/hooks/utils/use-client-query.ts🧬 Code graph analysis (10)
packages/react/src/hooks/use-conversation.ts (4)
packages/types/src/api/conversation.ts (2)
GetConversationResponse(135-137)GetConversationRequest(123-125)packages/react/src/provider.tsx (1)
useSupport(221-238)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(38-219)packages/react/src/hooks/store/use-website-store.ts (2)
packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(38-219)packages/react/src/hooks/use-visitor.ts (2)
packages/react/src/provider.tsx (1)
useSupport(221-238)packages/types/src/api/visitor.ts (2)
VisitorMetadata(12-12)VisitorResponse(353-353)packages/react/src/support/pages/conversation.tsx (2)
packages/react/src/hooks/use-conversation-messages.ts (1)
useConversationMessages(36-112)packages/react/src/hooks/use-send-message.ts (1)
useSendMessage(75-183)packages/react/src/hooks/use-send-message.ts (4)
packages/types/src/api/conversation.ts (1)
CreateConversationResponseBody(48-50)packages/core/src/client.ts (1)
CossistantClient(66-349)packages/types/src/enums.ts (4)
MessageType(1-5)MessageType(7-7)MessageVisibility(33-36)MessageVisibility(74-75)packages/react/src/provider.tsx (1)
useSupport(221-238)packages/react/src/hooks/use-conversation-messages.ts (4)
packages/core/src/store/messages-store.ts (1)
ConversationMessagesState(6-10)packages/react/src/provider.tsx (1)
useSupport(221-238)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(38-219)packages/react/src/provider.tsx (3)
packages/core/src/client.ts (1)
CossistantClient(66-349)packages/react/src/hooks/utils/use-rest-client.ts (1)
useClient(18-51)packages/react/src/hooks/store/use-website-store.ts (1)
useWebsiteStore(38-79)packages/react/src/hooks/use-conversations.ts (3)
packages/react/src/provider.tsx (1)
useSupport(221-238)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(38-219)packages/react/src/hooks/use-create-conversation.ts (3)
packages/core/src/client.ts (1)
CossistantClient(66-349)packages/types/src/api/conversation.ts (2)
CreateConversationResponseBody(48-50)CreateConversationRequestBody(35-37)packages/react/src/provider.tsx (1)
useSupport(221-238)packages/react/src/hooks/utils/use-rest-client.ts (2)
packages/react/src/index.ts (1)
useClient(27-27)packages/next/src/hooks/use-rest-client.ts (1)
useClient(3-3)⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
packages/react/src/hooks/utils/use-client-query.ts (1)
9-26: Export the public hook types to avoid build breakage.
UseClientQueryOptionsandUseClientQueryResultare consumed via downstream re-exports (per the PR summary), but they aren’t actually exported here anymore. That makes the package emitModule has no exported membererrors during type generation/build, so the new hook surface can’t be used. Please restore the exports.-type UseClientQueryOptions<TData, TArgs> = { +export type UseClientQueryOptions<TData, TArgs> = { @@ -}; - -type UseClientQueryResult<TData, TArgs> = { +}; + +export type UseClientQueryResult<TData, TArgs> = {packages/react/src/support/pages/conversation.tsx (2)
117-121: Reset “seen” tracking when the conversation changes, not only on mountPrevents leaking
lastSeenMessageIdRefacross conversation switches.- React.useEffect(() => { + React.useEffect(() => { lastSeenMessageIdRef.current = null; markSeenInFlightRef.current = false; - }, []); + }, [realConversationId]);
58-59: Gate messages query until a real conversation id existsAvoid calling the API with the sentinel
PENDING_CONVERSATION_ID, which 404s and poisons error state.- const messagesQuery = useConversationMessages(conversationId); + const messagesQuery = useConversationMessages(conversationId, { + enabled: hasRealConversation, + });
| const query = useClientQuery<PublicWebsiteResponse, { force?: boolean }>({ | ||
| client, | ||
| queryFn: (instance, params) => instance.fetchWebsite(params ?? {}), | ||
| enabled: true, | ||
| refetchInterval: options.refetchInterval ?? false, | ||
| refetchOnWindowFocus: options.refetchOnWindowFocus ?? true, | ||
| refetchOnMount: state.status === "idle", | ||
| initialData: state.website ?? undefined, | ||
| }); |
There was a problem hiding this comment.
Fix type mismatch and refresh return; return website, not the response envelope
useClientQuery is typed as PublicWebsiteResponse but initialData is state.website and refresh() returns a website. Align TData to the website shape and extract response.website from fetchWebsite. Also prefer the store variable over client.websiteStore in refresh.
- const query = useClientQuery<PublicWebsiteResponse, { force?: boolean }>({
- client,
- queryFn: (instance, params) => instance.fetchWebsite(params ?? {}),
- enabled: true,
- refetchInterval: options.refetchInterval ?? false,
- refetchOnWindowFocus: options.refetchOnWindowFocus ?? true,
- refetchOnMount: state.status === "idle",
- initialData: state.website ?? undefined,
- });
+ const query = useClientQuery<WebsiteState["website"], { force?: boolean }>({
+ client,
+ queryFn: async (instance, params) => {
+ const res = await instance.fetchWebsite(params ?? {});
+ return res.website; // adapt if fetchWebsite already returns the website
+ },
+ enabled: options.enabled ?? true,
+ refetchInterval: options.refetchInterval ?? false,
+ refetchOnWindowFocus: options.refetchOnWindowFocus ?? true,
+ refetchOnMount: state.status === "idle",
+ initialData: state.website ?? undefined,
+ });
@@
- const refresh = () => {
- return query
- .refetch({ force: true })
- .then((result) => result ?? client.websiteStore.getState().website)
- .catch(() => client.websiteStore.getState().website)
- .then((website) => website ?? null);
- };
+ const refresh = () => {
+ return query
+ .refetch({ force: true })
+ .then((website) => website ?? store.getState().website)
+ .catch(() => store.getState().website)
+ .then((website) => website ?? null);
+ };Also applies to: 64-70
🤖 Prompt for AI Agents
In packages/react/src/hooks/store/use-website-store.ts around lines 47-55 (and
similarly adjust lines 64-70), the query is typed as PublicWebsiteResponse while
initialData and refresh expect the Website shape; change the query generic to
the Website type (not PublicWebsiteResponse), update queryFn to return
instance.fetchWebsite(...).website (extract the website from the response), and
set initialData to state.website as before; in the refresh function use the
local store variable (not client.websiteStore) to perform updates and return the
website object (not the response envelope). Ensure types/signatures are updated
accordingly so refresh() and the query return the Website type.
| const isInitialLoad = selection.messages.length === 0; | ||
| const isLoading = isInitialLoad ? queryLoading : false; |
There was a problem hiding this comment.
Don’t mask ongoing loads once messages exist.
Right now isLoading is forced to false whenever the store already holds any messages. When fetchNextPage() or a manual refetch() runs, useClientQuery correctly flips queryLoading to true, but your wrapper hides that by zeroing it out. Consumers lose any signal that pagination or refresh is in flight, so spinners/loaders never render after the first page—this is a user-facing regression.
Let the hook surface the actual loading flag from useClientQuery (and, if needed, track an additional isInitialLoading separately), e.g.:
- const isInitialLoad = selection.messages.length === 0;
- const isLoading = isInitialLoad ? queryLoading : false;
+ const isLoading = queryLoading;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const isInitialLoad = selection.messages.length === 0; | |
| const isLoading = isInitialLoad ? queryLoading : false; | |
| - const isInitialLoad = selection.messages.length === 0; | |
| const isLoading = queryLoading; |
🤖 Prompt for AI Agents
In packages/react/src/hooks/use-conversation-messages.ts around lines 100 to
101, the hook is masking ongoing loads by forcing isLoading to false when
messages already exist; change it to surface the real queryLoading from
useClientQuery (i.e., set isLoading = queryLoading) so pagination/refetch
loading states are preserved for consumers, and if you need to distinguish the
first load keep the existing isInitialLoad = selection.messages.length === 0 and
expose an additional isInitialLoading flag (isInitialLoading = isInitialLoad &&
queryLoading) rather than overriding queryLoading.
| const store = client.conversationsStore; | ||
|
|
||
| const conversation = useStoreSelector(store, (state) => { |
There was a problem hiding this comment.
Add explicit store-availability guard
Throws a clear error instead of failing inside useStoreSelector when the store isn’t present.
- const store = client.conversationsStore;
+ const store = client.conversationsStore ?? ((): typeof client.conversationsStore => {
+ throw new Error("Conversations store is not available on the client instance");
+ })();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const store = client.conversationsStore; | |
| const conversation = useStoreSelector(store, (state) => { | |
| const store = client.conversationsStore ?? ((): typeof client.conversationsStore => { | |
| throw new Error("Conversations store is not available on the client instance"); | |
| })(); | |
| const conversation = useStoreSelector(store, (state) => { |
🤖 Prompt for AI Agents
In packages/react/src/hooks/use-conversation.ts around lines 29 to 31, the code
assumes client.conversationsStore exists and may fail inside useStoreSelector
when it's missing; add an explicit guard after obtaining const store =
client.conversationsStore and if store is null/undefined throw a clear,
descriptive error (e.g., "conversationsStore is not available on client") before
calling useStoreSelector so consumers get a helpful message instead of an
internal failure.
| if (website) { | ||
| // @ts-expect-error internal priming: safe in our library context | ||
| client.restClient?.setWebsiteContext?.(website.id, website.visitor?.id); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Call the public client API instead of a private field
CossistantClient already exposes setWebsiteContext, so we can avoid poking the private restClient field and the accompanying @ts-expect-error. Relying on internals is brittle and will break as soon as that implementation detail changes.
- if (website) {
- // @ts-expect-error internal priming: safe in our library context
- client.restClient?.setWebsiteContext?.(website.id, website.visitor?.id);
- }
+ if (website) {
+ client.setWebsiteContext(website.id, website.visitor?.id);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (website) { | |
| // @ts-expect-error internal priming: safe in our library context | |
| client.restClient?.setWebsiteContext?.(website.id, website.visitor?.id); | |
| } | |
| if (website) { | |
| - // @ts-expect-error internal priming: safe in our library context | |
| client.setWebsiteContext(website.id, website.visitor?.id); | |
| } |
🤖 Prompt for AI Agents
In packages/react/src/provider.tsx around lines 98 to 101, avoid accessing the
private restClient field and remove the @ts-expect-error: call the public API on
CossistantClient instead. Replace
client.restClient?.setWebsiteContext?.(website.id, website.visitor?.id) with a
call to client.setWebsiteContext(website.id, website.visitor?.id) (guarding with
an existence check if needed), and remove the @ts-expect-error comment so we
rely only on the public method.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/react/src/realtime/index.ts (1)
135-137: Don’t treat valid falsy messages as absent
if (!message)drops0,"", orfalse. Check nullish only.- if (!message) { + if (message == null) { return; }packages/react/src/support/pages/conversation.tsx (2)
69-76: Don’t pass null as conversationId to useSendMessage
conversationIdis optional; pass it only when real to satisfy types and intent.- sendMessage.mutate({ - conversationId: realConversationId, + sendMessage.mutate({ + ...(hasRealConversation ? { conversationId } : {}), message: message.trim(), files, defaultMessages, visitorId: visitor?.id,
109-111: Surface sendMessage errors in the UICurrently only parent/error or messagesQuery.error show. Include
sendMessage.errorso failed sends are visible.- const actualError = error || messagesQuery.error; + const actualError = error || messagesQuery.error || sendMessage.error;
🧹 Nitpick comments (18)
packages/react/src/realtime/index.ts (1)
9-9: Redundant intersection; simplify context alias
{} & TContext≡TContext. Use a direct alias.-export type RealtimeEventHandlerContext<TContext = EmptyObject> = {} & TContext; +export type RealtimeEventHandlerContext<TContext = EmptyObject> = TContext;packages/react/src/support/pages/conversation.tsx (1)
91-91: Avoid using console in production codeRoute through an app logger or propagate via state/UI. If needed, guard for dev-only logging.
Example:
- console.error("Failed to send message:", _error); + // TODO: integrate with app logger or error boundary + if (process.env.NODE_ENV !== "production") { + // eslint-disable-next-line no-console + console.error("Failed to send message:", _error); + }Apply similarly for the "mark conversation as seen" catch.
Also applies to: 148-148
packages/react/src/hooks/store/use-website-store.ts (3)
6-6: Remove unused PublicWebsiteResponse importType will be dropped after aligning query generics to Website shape.
67-69: Refine loading state to avoid spinner when website is already presentTreat idle as loading only when the store has no website yet.
- const isLoading = - query.isLoading || state.status === "loading" || state.status === "idle"; + const isLoading = + query.isLoading || + state.status === "loading" || + (state.status === "idle" && !state.website);
70-76: Use the local store reference and return Website from refresh()Avoid reaching back into
client.websiteStoreand keep return type consistent.- const refresh = () => { - return query - .refetch({ force: true }) - .then((result) => result ?? client.websiteStore.getState().website) - .catch(() => client.websiteStore.getState().website) - .then((website) => website ?? null); - }; + const refresh = () => { + return query + .refetch({ force: true }) + .then((website) => website ?? store.getState().website) + .catch(() => store.getState().website) + .then((website) => website ?? null); + };packages/react/src/hooks/use-conversation.ts (1)
42-60: Avoid unnecessary fetch when conversation is already in the store
useClientQuerywill still fetch on mount ifinitialDatais undefined. Gate viaenabled: … && !conversationto skip the initial request when cache has the data.- } = useClientQuery<GetConversationResponse, GetConversationRequest>({ + } = useClientQuery<GetConversationResponse, GetConversationRequest>({ client, queryFn: (instance) => { if (!request) { throw new Error("Conversation ID is required"); } return instance.getConversation(request); }, - enabled: Boolean(conversationId && (options.enabled ?? true)), + enabled: Boolean(conversationId && (options.enabled ?? true) && !conversation), refetchInterval: options.refetchInterval ?? false, refetchOnWindowFocus: options.refetchOnWindowFocus ?? true, refetchOnMount: !conversation, initialArgs: request, dependencies: [conversationId ?? "null"], });packages/react/src/hooks/utils/use-client-query.ts (3)
4-8: Also export QueryFn for better DX.Consumers will often type their query functions; exporting this alias improves ergonomics.
Apply:
-type QueryFn<TData, TArgs> = ( +export type QueryFn<TData, TArgs> = ( client: CossistantClient, args?: TArgs | undefined ) => Promise<TData>;As per coding guidelines (export type for types).
131-139: Rename misleading variable: it refetches on every dependency change.
shouldFetchInitiallyis true on subsequent runs too. Rename for clarity.Apply:
- const shouldFetchInitially = hasMountedRef.current - ? true - : refetchOnMount || !hasFetchedRef.current; + const shouldFetch = hasMountedRef.current + ? true + : refetchOnMount || !hasFetchedRef.current; @@ - if (!shouldFetchInitially) { + if (!shouldFetch) { return; }
38-41: Add minimal JSDoc for the exported hook and types.Public API should be documented (purpose, options, return).
Apply:
+/** + * Client-driven query hook with optional auto-refetch on interval/window focus. + * Avoids React Query keys; uses a provided queryFn and client instance. + */ export function useClientQuery<TData, TArgs = void>( options: UseClientQueryOptions<TData, TArgs> ): UseClientQueryResult<TData, TArgs> {+/** Options for useClientQuery. Pass a stable `dependencies` array to control refetch triggers. */ export type UseClientQueryOptions<TData, TArgs> = {+/** Result of useClientQuery. `refetch` bypasses `enabled`. */ export type UseClientQueryResult<TData, TArgs> = {As per coding guidelines.
Also applies to: 9-19, 21-26
packages/react/src/provider.tsx (4)
170-175: Add explicit return type to exported component.Public APIs should have explicit return types.
Apply:
-export function SupportProvider({ +export function SupportProvider({ children, @@ -}: Props) { +}: Props): JSX.Element {As per coding guidelines (prefer explicit return types on exported APIs).
221-229: Add explicit return type to exported hook.Annotate return type for
useSupport.Apply:
-export function useSupport() { +export type UseSupportResult = CossistantContextValue & { + availableHumanAgents: NonNullable<CossistantContextValue["website"]>["availableHumanAgents"] | []; + availableAIAgents: NonNullable<CossistantContextValue["website"]>["availableAIAgents"] | []; + visitor: CossistantContextValue["website"] extends infer W + ? W extends { visitor?: infer V } ? V | undefined : undefined + : undefined; +}; + +export function useSupport(): UseSupportResult {As per coding guidelines.
224-226: Tighten the error message.Capitalize brand for consistency.
Apply:
- "useSupport must be used within a cossistant SupportProvider" + "useSupport must be used within a Cossistant SupportProvider"
12-12: Dead prop:defaultOpenisn’t used.Either wire it up or drop from
Propsto avoid API confusion.Would you like me to wire it to a future UI state or remove it now?
packages/react/src/hooks/utils/use-rest-client.ts (1)
7-10: Return type still exposeserrorbut hook now throws; simplify API.
erroris always null and the JSDoc claims errors are returned, which is no longer true. Either restore non-throwing behavior or removeerrorfrom the API; recommend removal.-export type UseClientResult = { - client: CossistantClient; - error: Error | null; -}; +export type UseClientResult = { + client: CossistantClient; +}; @@ - return { client, error: null }; + return { client };Optionally update the JSDoc to note that the hook throws on misconfiguration.
Also applies to: 50-51
packages/react/src/hooks/use-conversation-messages.ts (1)
73-76: Guard queries when conversationId is empty.If an empty string slips through, we'll issue a fetch with an invalid id. Gate
enabledon a non-empty id.- enabled: options.enabled ?? true, + enabled: (options.enabled ?? true) && conversationId !== "",packages/react/src/index.ts (1)
45-47: Prefer curated named exports overexport *for stability.Wildcard re-exports from "./provider" and "./realtime" make the root API surface volatile and can cause accidental symbol collisions as the modules evolve. Consider switching to explicit named exports (types via
export type, values viaexport) for a stable, documented surface.packages/react/src/hooks/index.ts (2)
7-12: Expose utils via named exports (avoidexport *) to keep the public API intentional.Wildcard re-exports can unintentionally grow the API surface and hinder type-only re-exports. Prefer named exports for utils.
Apply this diff:
- export * from "./utils/use-client-query"; - export * from "./utils/use-default-messages"; - export * from "./utils/use-grouped-messages"; - export * from "./utils/use-multimodal-input"; - export * from "./utils/use-realtime-support"; - export * from "./utils/use-rest-client"; + export { useClientQuery } from "./utils/use-client-query"; + export { useDefaultMessages } from "./utils/use-default-messages"; + export { useGroupedMessages } from "./utils/use-grouped-messages"; + export { useMultimodalInput } from "./utils/use-multimodal-input"; + export { useRealtimeSupport } from "./utils/use-realtime-support"; + export { useClient } from "./utils/use-rest-client";
1-5: Optional: mirror curated, type‑safe re‑exports for conversation hooks.To align with root index style and “export type” guidance, consider switching these to explicit
export type { … }+export { … }per module.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
packages/react/src/hooks/index.ts(1 hunks)packages/react/src/hooks/store/use-website-store.ts(1 hunks)packages/react/src/hooks/use-conversation-messages.ts(1 hunks)packages/react/src/hooks/use-conversation.ts(1 hunks)packages/react/src/hooks/use-conversations.ts(1 hunks)packages/react/src/hooks/use-create-conversation.ts(1 hunks)packages/react/src/hooks/use-send-message.ts(1 hunks)packages/react/src/hooks/use-visitor.ts(0 hunks)packages/react/src/hooks/utils/use-client-query.ts(1 hunks)packages/react/src/hooks/utils/use-rest-client.ts(3 hunks)packages/react/src/index.ts(1 hunks)packages/react/src/provider.tsx(3 hunks)packages/react/src/realtime/index.ts(1 hunks)packages/react/src/support/hooks/index.ts(1 hunks)packages/react/src/support/hooks/use-conversation.ts(1 hunks)packages/react/src/support/hooks/use-create-conversation.ts(1 hunks)packages/react/src/support/hooks/use-send-message.ts(1 hunks)packages/react/src/support/pages/conversation.tsx(4 hunks)
💤 Files with no reviewable changes (1)
- packages/react/src/hooks/use-visitor.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/react/src/support/hooks/use-create-conversation.ts
- packages/react/src/hooks/use-create-conversation.ts
- packages/react/src/hooks/use-conversations.ts
- packages/react/src/hooks/use-send-message.ts
🧰 Additional context used
📓 Path-based instructions (13)
packages/react/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Develop headless React components (including and primitives) under packages/react/src/
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't use theaccessKeyattribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class members
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
**/*.{js,tsx,ts,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Use arrow functions instead of function expressions
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace characters
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
**/*.{js,ts,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't redeclare variables, functions, classes, and types in the same scope
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
packages/react/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react-package-rules.mdc)
packages/react/**/*.{ts,tsx}: Components must render with no built-in styles (headless, unstyled)
Components must accept className, style, and asChild props to enable external styling/control
Do not use emotion, styled-components, or style-prop APIs; styling is via Tailwind/CSS through className/style
Use useRenderElement to create elements and encapsulate render logic (supporting intrinsic tags and render prop overrides)
Components with internal state must be controllable via props (e.g., value/defaultValue/onChange or isOpen/onOpenChange)
Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function child
May reference Base UI logic internally, but never expose Base UI or MUI APIs; do not leak getRootProps/getInputProps; prefer useRenderElement
Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic components
Provide a render prop API: render?: (props, state) => ReactElement, and allow className to be string or (state) => string
Support developer-owned rendering: components should not impose visual structure and should allow parent-driven markup via render prop/asChild
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
packages/**
📄 CodeRabbit inference engine (AGENTS.md)
Put shared contracts and utilities in packages/* (e.g., core, types, transactional)
Files:
packages/react/src/realtime/index.tspackages/react/src/support/hooks/use-conversation.tspackages/react/src/support/pages/conversation.tsxpackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/use-conversation.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/hooks/utils/use-client-query.tspackages/react/src/hooks/use-conversation-messages.tspackages/react/src/support/hooks/index.tspackages/react/src/index.tspackages/react/src/hooks/utils/use-rest-client.tspackages/react/src/hooks/store/use-website-store.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.tsx: Use the Compound Components pattern with namespaced exports (e.g., FormWizard.Root, FormWizard.Step) so pieces compose under a single parent
Ensure accessibility: keyboard navigation support
Ensure accessibility: include appropriate ARIA attributes
Ensure accessibility: use semantic markup for screen readers
Maintain proper focus management in interactive components
Structure components with forwardRef and typed props/refs as shown (ComponentRef, HTMLAttributes, ReactNode)
Root component acts as main context provider, manages global state, and handles initialization
Child components must be used within Root, read context via hooks, support asChild, and follow consistent props
Props ordering: list required props first; optional props have clear defaults; support common HTML attributes; expose className
Support composition with an asChild prop using Radix UI Slot to allow element replacement
Styling: support className, use data-* attributes for state, and allow style customization
Event handling: support standard DOM events, add custom events when needed, and manage propagation correctly
Base component pattern: forwardRef, choose Slot or intrinsic element via asChild, pass ref/className and spread props
Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/provider.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Ensure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include the words "image", "picture", or "photo" in img alt prop
Don't use an explicit role that's the same as the element's implicit role
Static elements with click handlers must have a valid role attribute
Always include a <title> element within SVG elements
Provide meaningful alt text for elements that require alt text
Ensure anchors have content accessible to screen readers
Assign tabIndex to non-interactive elements only when using aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Ensure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Elements with interactive roles and handlers must be focusable
Heading elements must have content accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
Prefer semantic elements instead of role attributes in JSX
Ensure all anchors are valid and navigable
Ensure all aria-* properties are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Don't use unnecessary fragmen...Files:
packages/react/src/support/pages/conversation.tsxpackages/react/src/provider.tsx**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx,html}: Ensure the preconnect attribute is used when using Google Fonts
Don't use target="_blank" without rel="noopener"
Use a recommended display strategy with Google FontsFiles:
packages/react/src/support/pages/conversation.tsxpackages/react/src/provider.tsx🧠 Learnings (8)
📚 Learning: 2025-09-17T09:06:53.619Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.619Z Learning: Applies to **/*.{js,ts} : Don't use unnecessary catch clausesApplied to files:
packages/react/src/realtime/index.ts📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{js,ts} : Ensure Promise-like statements are handled (awaited, returned, or chained) appropriatelyApplied to files:
packages/react/src/realtime/index.ts📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to apps/web/src/hooks/use*.{ts,tsx} : Name React hooks with a use* prefix and place them in apps/web/src/hooksApplied to files:
packages/react/src/support/hooks/use-conversation.tspackages/react/src/support/hooks/use-send-message.tspackages/react/src/hooks/index.ts📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{js,ts} : Don't have unused importsApplied to files:
packages/react/src/provider.tsx📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to **/*.{ts,tsx} : Prefer explicit return types on exported APIs in TypeScriptApplied to files:
packages/react/src/hooks/utils/use-client-query.tspackages/react/src/index.ts📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.{ts,tsx} : Export type definitions for the public APIApplied to files:
packages/react/src/hooks/utils/use-client-query.tspackages/react/src/index.ts📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{ts,tsx} : Use export type for typesApplied to files:
packages/react/src/hooks/utils/use-client-query.ts📚 Learning: 2025-09-17T09:00:28.751Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: CLAUDE.md:0-0 Timestamp: 2025-09-17T09:00:28.751Z Learning: Applies to packages/react/src/**/*.{ts,tsx} : Develop headless React components (including <Support /> and primitives) under packages/react/src/Applied to files:
packages/react/src/index.ts🧬 Code graph analysis (7)
packages/react/src/realtime/index.ts (2)
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.ts (1)
RealtimeEventHandlerContext(19-20)apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.ts (1)
RealtimeEventHandlerContext(4-4)packages/react/src/support/pages/conversation.tsx (4)
packages/react/src/hooks/use-conversation-messages.ts (1)
useConversationMessages(36-112)packages/core/src/client.ts (1)
sendMessage(244-333)packages/core/src/rest-client.ts (1)
sendMessage(576-622)packages/react/src/hooks/use-send-message.ts (1)
useSendMessage(77-185)packages/react/src/hooks/use-conversation.ts (4)
packages/types/src/api/conversation.ts (2)
GetConversationResponse(135-137)GetConversationRequest(123-125)packages/react/src/provider.tsx (1)
useSupport(221-238)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(38-219)packages/react/src/provider.tsx (3)
packages/core/src/client.ts (1)
CossistantClient(66-349)packages/react/src/hooks/utils/use-rest-client.ts (1)
useClient(18-51)packages/react/src/hooks/store/use-website-store.ts (1)
useWebsiteStore(42-85)packages/react/src/hooks/utils/use-client-query.ts (1)
packages/react/src/index.ts (1)
useClientQuery(38-38)packages/react/src/hooks/use-conversation-messages.ts (4)
packages/core/src/store/messages-store.ts (1)
ConversationMessagesState(6-10)packages/react/src/provider.tsx (1)
useSupport(221-238)packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(38-219)packages/react/src/hooks/store/use-website-store.ts (2)
packages/react/src/hooks/store/use-store-selector.ts (1)
useStoreSelector(10-37)packages/react/src/hooks/utils/use-client-query.ts (1)
useClientQuery(38-219)🔇 Additional comments (20)
packages/react/src/realtime/index.ts (3)
9-17: Context shape changed—verify no remainingcontext.queryClientusagesDropping QueryClient from the base context risks stale consumers.
#!/usr/bin/env bash # Find remaining references to the old context/queryClient rg -nP '\bRealtimeEventHandlerContext\b|context\.\s*queryClient\b' -g '!**/node_modules/**'
57-74: Async handler rejections not caught; remove console usageCurrent try/catch misses async rejections and violates “Don’t use console”.
- for (const handler of eventHandlers) { - try { - handler({ - event, - context, - }); - } catch (error) { - if (onError) { - onError(error, event.type); - } else { - console.error("[Realtime] Event handler failed", { - error, - eventType: event.type, - }); - } - } - } + for (const handler of eventHandlers) { + void Promise.resolve( + handler({ event, context }) + ).catch((error) => { + onError?.(error, event.type); + }); + }As per coding guidelines.
121-133: Route transform failures viaonTransformErroronly; no consoleAvoid console logging in the transform error path.
- } catch (error) { - if (onTransformError) { - onTransformError(error, params); - } else { - console.error("[Realtime] Failed to transform MESSAGE_CREATED event", { - error, - eventType: params.event.type, - }); - } - return; - } + } catch (error) { + onTransformError?.(error, params); + return; + }As per coding guidelines.
packages/react/src/support/hooks/use-conversation.ts (1)
1-6: Re-export looks goodCentralizing the hook API reduces duplication and keeps a single source of truth.
packages/react/src/support/pages/conversation.tsx (2)
116-120: Reset seen-tracking when the conversation changes (not only on mount)Ensure refs don't leak across conversation switches.
- }, []); + }, [realConversationId]);
57-57: Gate messages query until a real conversation ID existsAvoid 404s and error state when
conversationIdis the pending sentinel.- const messagesQuery = useConversationMessages(conversationId); + const messagesQuery = useConversationMessages(conversationId, { + enabled: hasRealConversation, + });packages/react/src/hooks/use-conversation.ts (1)
29-31: Guard against missing conversationsStore on the clientThrow a clear runtime error instead of failing inside
useStoreSelector.- const store = client.conversationsStore; + const store = + client.conversationsStore ?? + ((): typeof client.conversationsStore => { + throw new Error( + "Conversations store is not available on the client instance" + ); + })();packages/react/src/hooks/store/use-website-store.ts (2)
1-1: Repo test failure: “Cannot find package 'nanoid' …/packages/core/src/utils.ts”Add
nanoidto the workspace (or core package) and ensure the import path matches runtime (browser vs node). Prefernanoid/non-securefor client-only usage.
53-61: Fix data type mismatch: query returns Website, not the response envelopeAlign
useClientQuerygenerics and extractwebsiteinqueryFn. This also makesinitialDatacorrect.- const query = useClientQuery<PublicWebsiteResponse, { force?: boolean }>({ + const query = useClientQuery<WebsiteState["website"], { force?: boolean }>({ client, - queryFn: (instance, params) => instance.fetchWebsite(params ?? {}), + queryFn: async (instance, params) => { + const res = await instance.fetchWebsite(params ?? {}); + return res.website; + }, enabled: true, refetchInterval: options.refetchInterval ?? false, refetchOnWindowFocus: options.refetchOnWindowFocus ?? true, refetchOnMount: state.status === "idle", initialData: state.website ?? undefined, });packages/react/src/provider.tsx (3)
6-6: Remove unused importuseConversations.It’s only referenced in commented code and will fail lint/biome.
Apply:
-import { useConversations } from "./hooks/use-conversations";
98-101: Call public API instead of privaterestClient.Use
client.setWebsiteContext(...)to avoid touching internals and the@ts-expect-error.Apply:
- if (website) { - // @ts-expect-error internal priming: safe in our library context - client.restClient?.setWebsiteContext?.(website.id, website.visitor?.id); - } + if (website) { + client.setWebsiteContext(website.id, website.visitor?.id); + }As per coding guidelines (avoid private internals, clear runtime errors).
1-3: Remove the “add nanoid” suggestion—core already declares"nanoid": "^5.1.5"
Tests still report “Cannot find package 'nanoid'” because nanoid v5 is ESM-only and may not resolve under your test runner. Instead of adding the dependency, adjust your Jest/ts-jest configuration to support ESM imports (or pin to a CJS-compatible nanoid v3/4 or usenanoid/non-secure).Likely an incorrect or invalid review comment.
packages/react/src/hooks/utils/use-client-query.ts (1)
9-19: Export the public option/result types.These types are used by consumers and mentioned in the PR summary as re-exported. Without
export, downstream imports will fail.Apply:
-type UseClientQueryOptions<TData, TArgs> = { +export type UseClientQueryOptions<TData, TArgs> = { client: CossistantClient; queryFn: QueryFn<TData, TArgs>; enabled?: boolean; refetchInterval?: number | false; refetchOnWindowFocus?: boolean; refetchOnMount?: boolean; initialData?: TData; initialArgs?: TArgs; dependencies?: readonly unknown[]; }; -type UseClientQueryResult<TData, TArgs> = { +export type UseClientQueryResult<TData, TArgs> = { data: TData | undefined; error: Error | null; isLoading: boolean; refetch: (args?: TArgs) => Promise<TData | undefined>; };As per coding guidelines (export type for public API).
Also applies to: 21-26
packages/react/src/support/hooks/use-send-message.ts (1)
1-6: Re-export confirmed; upstream exports exist. All three types—SendMessageOptions,SendMessageResult,UseSendMessageResult—are defined withexport typeinpackages/react/src/hooks/use-send-message.ts, so the barrel remains type-safe.packages/react/src/support/hooks/index.ts (1)
1-20: LGTM on centralized re-exports.Paths and type/value exports look consistent. Please confirm these are mirrored in packages/react/src/index.ts so consumers have a single stable entrypoint.
packages/react/src/hooks/use-conversation-messages.ts (1)
100-101: Don’t mask ongoing loads; surface real loading state.This forces
isLoadingto false after the first page, hiding pagination/refresh loads. Use the query’s loading and, if needed, expose a separateisInitialLoading.- const isInitialLoad = selection.messages.length === 0; - const isLoading = isInitialLoad ? queryLoading : false; + const isInitialLoad = selection.messages.length === 0; + const isLoading = queryLoading; + const isInitialLoading = isInitialLoad && queryLoading;Also add the field to the type and return value:
-export type UseConversationMessagesResult = ConversationMessagesState & { +export type UseConversationMessagesResult = ConversationMessagesState & { isLoading: boolean; + isInitialLoading: boolean; error: Error | null; @@ return { messages: selection.messages, hasNextPage: selection.hasNextPage, nextCursor: selection.nextCursor, isLoading, + isInitialLoading, error, refetch, fetchNextPage, };packages/react/src/index.ts (4)
2-37: Good: type‑only re‑exports are correctly marked.Using
export type { ... }is consistent with our TS guidelines and keeps value/type namespaces clean.
48-53: Remove wildcard re-export from "./support" to prevent duplicate exports and leaking deprecated APIs.
export * from "./support"will re‑exportuseWebsiteand its types, which you also explicitly export on Lines 49–53. This risks duplicate export errors and re‑exposes deprecated/legacy hooks that the PR intends to phase out.Apply this diff:
- export * from "./support"; + // Avoid re-exporting all of "./support" to keep the public API curated.
1-1: Remove missing ‘nanoid’ dependency suggestion: Thenanoidpackage is already declared in packages/core/package.json (v^5.1.5); no changes needed here.Likely an incorrect or invalid review comment.
44-44: No conflictinguseClientexports in the React package. Verified that only the implementation inpackages/react/src/hooks/utils/use-rest-client.tsand its single re-export inpackages/react/src/index.tsexist—no other barrels or provider files exportuseClient.
| const keyFromEnv = | ||
| process.env.NEXT_PUBLIC_COSSISSTANT_KEY || | ||
| process.env.COSSISSTANT_PUBLIC_KEY; | ||
| const keyToUse = publicKey ?? keyFromEnv; |
There was a problem hiding this comment.
Env var name typo breaks key resolution (throws even when env is set).
"COSSISSTANT" (triple s) is misspelled in both env var lookups. This prevents picking up correctly set keys.
Apply:
- const keyFromEnv =
- process.env.NEXT_PUBLIC_COSSISSTANT_KEY ||
- process.env.COSSISSTANT_PUBLIC_KEY;
+ const keyFromEnv =
+ // correct names
+ process.env.NEXT_PUBLIC_COSSISTANT_KEY ??
+ process.env.COSSISTANT_PUBLIC_KEY ??
+ // legacy misspelling for back-compat
+ process.env.NEXT_PUBLIC_COSSISSTANT_KEY ??
+ process.env.COSSISSTANT_PUBLIC_KEY;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/react/src/hooks/utils/use-rest-client.ts around lines 24 to 27, the
environment variable lookups use the misspelled "COSSISSTANT" (triple s); change
both occurrences to the correct "COSSISTANT" spelling so the code reads
process.env.NEXT_PUBLIC_COSSISTANT_KEY || process.env.COSSISTANT_PUBLIC_KEY (or
keep whichever project convention you use for NEXT_PUBLIC_ vs non-public),
update any other occurrences in the repo, and run the app/tests to verify the
key resolution works.
| if (!keyToUse) { | ||
| const envKey = | ||
| process.env.NEXT_PUBLIC_COSSISSTANT_KEY || | ||
| process.env.COSSISSTANT_PUBLIC_KEY; | ||
|
|
||
| if (!envKey) { | ||
| setError( | ||
| new Error( | ||
| "Public key is required. Please provide it as a prop or set NEXT_PUBLIC_COSSISSTANT_KEY environment variable." | ||
| ) | ||
| ); | ||
| return null; | ||
| } | ||
| keyToUse = envKey; | ||
| throw new Error( | ||
| "Public key is required. Please provide it as a prop or set NEXT_PUBLIC_COSSISSTANT_KEY environment variable." | ||
| ); | ||
| } |
There was a problem hiding this comment.
Error message references the wrong env var name.
Align the guidance with the corrected env var(s).
- throw new Error(
- "Public key is required. Please provide it as a prop or set NEXT_PUBLIC_COSSISSTANT_KEY environment variable."
- );
+ throw new Error(
+ "Public key is required. Pass publicKey or set NEXT_PUBLIC_COSSISTANT_KEY (or COSSISTANT_PUBLIC_KEY)."
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!keyToUse) { | |
| const envKey = | |
| process.env.NEXT_PUBLIC_COSSISSTANT_KEY || | |
| process.env.COSSISSTANT_PUBLIC_KEY; | |
| if (!envKey) { | |
| setError( | |
| new Error( | |
| "Public key is required. Please provide it as a prop or set NEXT_PUBLIC_COSSISSTANT_KEY environment variable." | |
| ) | |
| ); | |
| return null; | |
| } | |
| keyToUse = envKey; | |
| throw new Error( | |
| "Public key is required. Please provide it as a prop or set NEXT_PUBLIC_COSSISSTANT_KEY environment variable." | |
| ); | |
| } | |
| if (!keyToUse) { | |
| throw new Error( | |
| "Public key is required. Pass publicKey or set NEXT_PUBLIC_COSSISTANT_KEY (or COSSISTANT_PUBLIC_KEY)." | |
| ); | |
| } |
🤖 Prompt for AI Agents
In packages/react/src/hooks/utils/use-rest-client.ts around lines 29 to 33, the
thrown Error message references the wrong environment variable name; update the
message to reference the actual env var(s) used by the codebase (match the key
name(s) read from process.env, e.g. NEXT_PUBLIC_COSSISTANT_KEY) so the guidance
is correct and actionable — replace the incorrect NEXT_PUBLIC_COSSISSTANT_KEY
text with the correct env var name(s) and keep the rest of the message intact.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/react/src/primitives/message-list.tsx (1)
60-143: Forwarded callback refs crash the auto-scroll logicWhen a consumer passes a callback ref,
refis a function. Casting it toMutableRefObjectkeeps it as a function at runtime, so the laterscrollRef.current.scrollTo(...)call throws.forwardRefmust support both object and callback refs, so this is a correctness bug. Please merge the internal ref with the forwarded ref instead of assuming an object ref.- const internalRef = React.useRef<HTMLDivElement>(null); - const scrollRef = - (ref as React.MutableRefObject<HTMLDivElement>) || internalRef; + const internalRef = React.useRef<HTMLDivElement>(null); + const assignRef = React.useCallback( + (node: HTMLDivElement | null) => { + internalRef.current = node; + + if (typeof ref === "function") { + ref(node); + } else if (ref) { + (ref as React.MutableRefObject<HTMLDivElement | null>).current = node; + } + }, + [ref], + ); @@ - React.useEffect(() => { - if (autoScroll && scrollRef.current) { + React.useEffect(() => { + const element = internalRef.current; + + if (autoScroll && element) { @@ - scrollRef.current.scrollTo({ - top: scrollRef.current.scrollHeight, + element.scrollTo({ + top: element.scrollHeight, @@ - }, [messages.length, events.length, autoScroll, scrollRef]); + }, [messages.length, events.length, autoScroll]); @@ - ref: scrollRef, + ref: assignRef,packages/react/src/hooks/use-grouped-messages.ts (1)
249-260: Fix unread boundary detection inhasUnreadAfter. Line 258 currently returnsmessageIndex < lastReadIndex, so a viewer who has already read past a message getstrue, while a viewer who still has unread messages (theirlastReadIndexis behind the target) getsfalse. This inverts the intended logic and hides unread indicators exactly when unread messages exist. Re-check the messages that follow the given ID and bail as soon as you find one the viewer hasn’t seen.- hasUnreadAfter: (messageId: string, userId: string): boolean => { - const lastRead = lastReadMessageMap.get(userId); - if (!lastRead) { - return true; // All messages are unread - } - - // Find the indices - const messageIndex = messages.findIndex((m) => m.id === messageId); - const lastReadIndex = messages.findIndex((m) => m.id === lastRead); - - return messageIndex < lastReadIndex; - }, + hasUnreadAfter: (messageId: string, userId: string): boolean => { + const startIndex = messages.findIndex((message) => message.id === messageId); + if (startIndex === -1) { + return false; + } + + for (let index = startIndex + 1; index < messages.length; index++) { + const seenBy = seenByMap.get(messages[index].id); + if (!seenBy?.has(userId)) { + return true; + } + } + + return false; + },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (49)
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.ts(1 hunks)apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.ts(1 hunks)apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.ts(1 hunks)apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx(1 hunks)apps/web/src/app/playground/page.tsx(1 hunks)packages/core/src/client.ts(5 hunks)packages/core/src/store/messages-store.test.ts(1 hunks)packages/core/src/store/messages-store.ts(1 hunks)packages/next/src/hooks/index.ts(1 hunks)packages/next/src/hooks/use-client-query.ts(1 hunks)packages/next/src/hooks/use-conversation-messages.ts(1 hunks)packages/next/src/hooks/use-conversation.ts(1 hunks)packages/next/src/hooks/use-conversations.ts(1 hunks)packages/next/src/hooks/use-create-conversation.ts(1 hunks)packages/next/src/hooks/use-grouped-messages.ts(1 hunks)packages/next/src/hooks/use-multimodal-input.ts(1 hunks)packages/next/src/hooks/use-realtime-support.ts(1 hunks)packages/next/src/hooks/use-rest-client.ts(1 hunks)packages/next/src/hooks/use-send-message.ts(1 hunks)packages/next/src/index.ts(1 hunks)packages/next/src/provider.tsx(1 hunks)packages/next/src/support/index.tsx(1 hunks)packages/react/src/config.tsx(2 hunks)packages/react/src/hooks/index.ts(1 hunks)packages/react/src/hooks/store/use-website-store.ts(1 hunks)packages/react/src/hooks/use-client-query.ts(1 hunks)packages/react/src/hooks/use-conversation-messages.ts(1 hunks)packages/react/src/hooks/use-conversation.ts(1 hunks)packages/react/src/hooks/use-conversations.ts(1 hunks)packages/react/src/hooks/use-default-messages.ts(2 hunks)packages/react/src/hooks/use-grouped-messages.ts(2 hunks)packages/react/src/hooks/use-rest-client.ts(3 hunks)packages/react/src/index.ts(1 hunks)packages/react/src/primitives/avatar/avatar.tsx(1 hunks)packages/react/src/primitives/avatar/fallback.tsx(1 hunks)packages/react/src/primitives/avatar/image.tsx(1 hunks)packages/react/src/primitives/bubble.tsx(1 hunks)packages/react/src/primitives/button.tsx(1 hunks)packages/react/src/primitives/message-group.tsx(6 hunks)packages/react/src/primitives/message-list.tsx(4 hunks)packages/react/src/primitives/message.tsx(3 hunks)packages/react/src/primitives/multimodal-input.tsx(2 hunks)packages/react/src/primitives/window.tsx(1 hunks)packages/react/src/provider.tsx(6 hunks)packages/react/src/realtime/support-provider.tsx(1 hunks)packages/react/src/support/components/cossistant-branding.tsx(1 hunks)packages/react/src/support/hooks/index.ts(1 hunks)packages/react/src/support/index.tsx(2 hunks)packages/react/src/support/pages/conversation.tsx(4 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/next/src/support/index.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/core/src/store/messages-store.ts
- packages/react/src/support/hooks/index.ts
- packages/react/src/hooks/use-conversations.ts
- packages/react/src/support/pages/conversation.tsx
- packages/react/src/hooks/use-conversation-messages.ts
- packages/react/src/primitives/multimodal-input.tsx
- packages/react/src/hooks/use-conversation.ts
🧰 Additional context used
📓 Path-based instructions (17)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...
Files:
packages/next/src/hooks/use-client-query.tspackages/next/src/provider.tsxpackages/next/src/hooks/index.tspackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/next/src/hooks/use-conversations.tspackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/next/src/hooks/use-multimodal-input.tspackages/next/src/index.tspackages/react/src/primitives/avatar/fallback.tsxpackages/next/src/hooks/use-conversation-messages.tspackages/react/src/support/components/cossistant-branding.tsxpackages/next/src/hooks/use-grouped-messages.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.tspackages/react/src/hooks/use-grouped-messages.tspackages/next/src/hooks/use-rest-client.tspackages/react/src/provider.tsxpackages/next/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/next/src/hooks/use-create-conversation.tspackages/next/src/hooks/use-send-message.tspackages/core/src/client.tspackages/react/src/primitives/avatar/image.tsxpackages/next/src/hooks/use-realtime-support.tspackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.tspackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/core/src/store/messages-store.test.tspackages/react/src/index.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't use theaccessKeyattribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==
Files:
packages/next/src/hooks/use-client-query.tspackages/next/src/provider.tsxpackages/next/src/hooks/index.tspackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/next/src/hooks/use-conversations.tspackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/next/src/hooks/use-multimodal-input.tspackages/next/src/index.tspackages/react/src/primitives/avatar/fallback.tsxpackages/next/src/hooks/use-conversation-messages.tspackages/react/src/support/components/cossistant-branding.tsxpackages/next/src/hooks/use-grouped-messages.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.tspackages/react/src/hooks/use-grouped-messages.tspackages/next/src/hooks/use-rest-client.tspackages/react/src/provider.tsxpackages/next/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/next/src/hooks/use-create-conversation.tspackages/next/src/hooks/use-send-message.tspackages/core/src/client.tspackages/react/src/primitives/avatar/image.tsxpackages/next/src/hooks/use-realtime-support.tspackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.tspackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/core/src/store/messages-store.test.tspackages/react/src/index.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...
Files:
packages/next/src/hooks/use-client-query.tspackages/next/src/hooks/index.tspackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/store/use-website-store.tspackages/next/src/hooks/use-conversations.tspackages/next/src/hooks/use-multimodal-input.tspackages/next/src/index.tspackages/next/src/hooks/use-conversation-messages.tspackages/next/src/hooks/use-grouped-messages.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.tspackages/react/src/hooks/use-grouped-messages.tspackages/next/src/hooks/use-rest-client.tspackages/next/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/next/src/hooks/use-create-conversation.tspackages/next/src/hooks/use-send-message.tspackages/core/src/client.tspackages/next/src/hooks/use-realtime-support.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.tspackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/core/src/store/messages-store.test.tspackages/react/src/index.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class members
Files:
packages/next/src/hooks/use-client-query.tspackages/next/src/provider.tsxpackages/next/src/hooks/index.tspackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/next/src/hooks/use-conversations.tspackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/next/src/hooks/use-multimodal-input.tspackages/next/src/index.tspackages/react/src/primitives/avatar/fallback.tsxpackages/next/src/hooks/use-conversation-messages.tspackages/react/src/support/components/cossistant-branding.tsxpackages/next/src/hooks/use-grouped-messages.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.tspackages/react/src/hooks/use-grouped-messages.tspackages/next/src/hooks/use-rest-client.tspackages/react/src/provider.tsxpackages/next/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/next/src/hooks/use-create-conversation.tspackages/next/src/hooks/use-send-message.tspackages/core/src/client.tspackages/react/src/primitives/avatar/image.tsxpackages/next/src/hooks/use-realtime-support.tspackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.tspackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/core/src/store/messages-store.test.tspackages/react/src/index.ts
**/*.{js,tsx,ts,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Use arrow functions instead of function expressions
Files:
packages/next/src/hooks/use-client-query.tspackages/next/src/provider.tsxpackages/next/src/hooks/index.tspackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/next/src/hooks/use-conversations.tspackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/next/src/hooks/use-multimodal-input.tspackages/next/src/index.tspackages/react/src/primitives/avatar/fallback.tsxpackages/next/src/hooks/use-conversation-messages.tspackages/react/src/support/components/cossistant-branding.tsxpackages/next/src/hooks/use-grouped-messages.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.tspackages/react/src/hooks/use-grouped-messages.tspackages/next/src/hooks/use-rest-client.tspackages/react/src/provider.tsxpackages/next/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/next/src/hooks/use-create-conversation.tspackages/next/src/hooks/use-send-message.tspackages/core/src/client.tspackages/react/src/primitives/avatar/image.tsxpackages/next/src/hooks/use-realtime-support.tspackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.tspackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/core/src/store/messages-store.test.tspackages/react/src/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace characters
Files:
packages/next/src/hooks/use-client-query.tspackages/next/src/provider.tsxpackages/next/src/hooks/index.tspackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/next/src/hooks/use-conversations.tspackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/next/src/hooks/use-multimodal-input.tspackages/next/src/index.tspackages/react/src/primitives/avatar/fallback.tsxpackages/next/src/hooks/use-conversation-messages.tspackages/react/src/support/components/cossistant-branding.tsxpackages/next/src/hooks/use-grouped-messages.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.tspackages/react/src/hooks/use-grouped-messages.tspackages/next/src/hooks/use-rest-client.tspackages/react/src/provider.tsxpackages/next/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/next/src/hooks/use-create-conversation.tspackages/next/src/hooks/use-send-message.tspackages/core/src/client.tspackages/react/src/primitives/avatar/image.tsxpackages/next/src/hooks/use-realtime-support.tspackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.tspackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/core/src/store/messages-store.test.tspackages/react/src/index.ts
**/*.{js,ts,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't redeclare variables, functions, classes, and types in the same scope
Files:
packages/next/src/hooks/use-client-query.tspackages/next/src/provider.tsxpackages/next/src/hooks/index.tspackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/next/src/hooks/use-conversations.tspackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/next/src/hooks/use-multimodal-input.tspackages/next/src/index.tspackages/react/src/primitives/avatar/fallback.tsxpackages/next/src/hooks/use-conversation-messages.tspackages/react/src/support/components/cossistant-branding.tsxpackages/next/src/hooks/use-grouped-messages.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.tspackages/react/src/hooks/use-grouped-messages.tspackages/next/src/hooks/use-rest-client.tspackages/react/src/provider.tsxpackages/next/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/next/src/hooks/use-create-conversation.tspackages/next/src/hooks/use-send-message.tspackages/core/src/client.tspackages/react/src/primitives/avatar/image.tsxpackages/next/src/hooks/use-realtime-support.tspackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.tspackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/core/src/store/messages-store.test.tspackages/react/src/index.ts
packages/**
📄 CodeRabbit inference engine (AGENTS.md)
Put shared contracts and utilities in packages/* (e.g., core, types, transactional)
Files:
packages/next/src/hooks/use-client-query.tspackages/next/src/provider.tsxpackages/next/src/hooks/index.tspackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/next/src/hooks/use-conversations.tspackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/next/src/hooks/use-multimodal-input.tspackages/next/src/index.tspackages/react/src/primitives/avatar/fallback.tsxpackages/next/src/hooks/use-conversation-messages.tspackages/react/src/support/components/cossistant-branding.tsxpackages/next/src/hooks/use-grouped-messages.tspackages/react/src/hooks/use-grouped-messages.tspackages/next/src/hooks/use-rest-client.tspackages/react/src/provider.tsxpackages/next/src/hooks/use-conversation.tspackages/react/src/hooks/index.tspackages/next/src/hooks/use-create-conversation.tspackages/next/src/hooks/use-send-message.tspackages/core/src/client.tspackages/react/src/primitives/avatar/image.tsxpackages/next/src/hooks/use-realtime-support.tspackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxpackages/react/src/primitives/bubble.tsxpackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/core/src/store/messages-store.test.tspackages/react/src/index.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
**/*.tsx: Use the Compound Components pattern with namespaced exports (e.g., FormWizard.Root, FormWizard.Step) so pieces compose under a single parent
Ensure accessibility: keyboard navigation support
Ensure accessibility: include appropriate ARIA attributes
Ensure accessibility: use semantic markup for screen readers
Maintain proper focus management in interactive components
Structure components with forwardRef and typed props/refs as shown (ComponentRef, HTMLAttributes, ReactNode)
Root component acts as main context provider, manages global state, and handles initialization
Child components must be used within Root, read context via hooks, support asChild, and follow consistent props
Props ordering: list required props first; optional props have clear defaults; support common HTML attributes; expose className
Support composition with an asChild prop using Radix UI Slot to allow element replacement
Styling: support className, use data-* attributes for state, and allow style customization
Event handling: support standard DOM events, add custom events when needed, and manage propagation correctly
Base component pattern: forwardRef, choose Slot or intrinsic element via asChild, pass ref/className and spread props
Files:
packages/next/src/provider.tsxpackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/primitives/window.tsxpackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/react/src/primitives/avatar/fallback.tsxpackages/react/src/support/components/cossistant-branding.tsxpackages/react/src/provider.tsxpackages/react/src/primitives/avatar/image.tsxpackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Ensure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include the words "image", "picture", or "photo" in img alt prop
Don't use an explicit role that's the same as the element's implicit role
Static elements with click handlers must have a valid role attribute
Always include a <title> element within SVG elements
Provide meaningful alt text for elements that require alt text
Ensure anchors have content accessible to screen readers
Assign tabIndex to non-interactive elements only when using aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Ensure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Elements with interactive roles and handlers must be focusable
Heading elements must have content accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
Prefer semantic elements instead of role attributes in JSX
Ensure all anchors are valid and navigable
Ensure all aria-* properties are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Don't use unnecessary fragmen...Files:
packages/next/src/provider.tsxpackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/primitives/window.tsxpackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/react/src/primitives/avatar/fallback.tsxpackages/react/src/support/components/cossistant-branding.tsxpackages/react/src/provider.tsxpackages/react/src/primitives/avatar/image.tsxpackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsx**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx,html}: Ensure the preconnect attribute is used when using Google Fonts
Don't use target="_blank" without rel="noopener"
Use a recommended display strategy with Google FontsFiles:
packages/next/src/provider.tsxpackages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxpackages/react/src/primitives/window.tsxpackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/react/src/primitives/avatar/fallback.tsxpackages/react/src/support/components/cossistant-branding.tsxpackages/react/src/provider.tsxpackages/react/src/primitives/avatar/image.tsxpackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsxpackages/react/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Develop headless React components (including and primitives) under packages/react/src/
Files:
packages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/react/src/primitives/avatar/fallback.tsxpackages/react/src/support/components/cossistant-branding.tsxpackages/react/src/hooks/use-grouped-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/primitives/avatar/image.tsxpackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxpackages/react/src/primitives/bubble.tsxpackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/react/src/index.tspackages/react/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react-package-rules.mdc)
packages/react/**/*.{ts,tsx}: Components must render with no built-in styles (headless, unstyled)
Components must accept className, style, and asChild props to enable external styling/control
Do not use emotion, styled-components, or style-prop APIs; styling is via Tailwind/CSS through className/style
Use useRenderElement to create elements and encapsulate render logic (supporting intrinsic tags and render prop overrides)
Components with internal state must be controllable via props (e.g., value/defaultValue/onChange or isOpen/onOpenChange)
Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function child
May reference Base UI logic internally, but never expose Base UI or MUI APIs; do not leak getRootProps/getInputProps; prefer useRenderElement
Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic components
Provide a render prop API: render?: (props, state) => ReactElement, and allow className to be string or (state) => string
Support developer-owned rendering: components should not impose visual structure and should allow parent-driven markup via render prop/asChildFiles:
packages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/hooks/use-client-query.tspackages/react/src/hooks/store/use-website-store.tspackages/react/src/primitives/window.tsxpackages/react/src/support/index.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/react/src/primitives/avatar/fallback.tsxpackages/react/src/support/components/cossistant-branding.tsxpackages/react/src/hooks/use-grouped-messages.tspackages/react/src/provider.tsxpackages/react/src/hooks/index.tspackages/react/src/primitives/avatar/image.tsxpackages/react/src/primitives/message.tsxpackages/react/src/config.tsxpackages/react/src/realtime/support-provider.tsxpackages/react/src/primitives/bubble.tsxpackages/react/src/hooks/use-default-messages.tspackages/react/src/hooks/use-rest-client.tspackages/react/src/index.tsapps/web/src/app/**
📄 CodeRabbit inference engine (AGENTS.md)
Place Next.js App Router pages/layouts/routes under apps/web/src/app
Files:
apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.tsapps/web/src/app/playground/page.tsxapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.tsapps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.tsapps/web/src/{app,components}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Use snake_case filenames for React components and layouts in the web app
Files:
apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsxapps/web/src/app/playground/page.tsx**/*.{test.ts,test.tsx,spec.ts,spec.tsx,test.js,test.jsx,spec.js,spec.jsx}
📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)
Include tests: unit tests for components, integration tests for composed components, accessibility tests, and error boundary tests
Files:
packages/core/src/store/messages-store.test.ts**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{test,spec}.{js,jsx,ts,tsx}: Don't nest describe() blocks too deeply in test files
Don't use callbacks in asynchronous tests and hooks
Don't have duplicate hooks in describe blocks
Don't use export or module.exports in test files
Don't use focused tests
Ensure the assertion function (e.g., expect) is placed inside an it() function call
Don't use disabled testsFiles:
packages/core/src/store/messages-store.test.ts🧠 Learnings (20)
📚 Learning: 2025-09-17T16:59:53.676Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/react-package-rules.mdc:0-0 Timestamp: 2025-09-17T16:59:53.676Z Learning: Applies to packages/react/**/*.{ts,tsx} : Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function childApplied to files:
packages/next/src/hooks/use-client-query.tspackages/next/src/provider.tsxpackages/react/src/primitives/window.tsxpackages/next/src/hooks/use-multimodal-input.tspackages/react/src/hooks/index.ts📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.{ts,tsx} : Manage shared component state via React ContextApplied to files:
packages/next/src/provider.tsx📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.tsx : Root component acts as main context provider, manages global state, and handles initializationApplied to files:
packages/next/src/provider.tsx📚 Learning: 2025-09-17T16:59:53.676Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/react-package-rules.mdc:0-0 Timestamp: 2025-09-17T16:59:53.676Z Learning: Applies to packages/react/**/*.{ts,tsx} : Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic componentsApplied to files:
packages/react/src/primitives/message-list.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/primitives/message.tsxpackages/react/src/primitives/bubble.tsx📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.tsx : Structure components with forwardRef and typed props/refs as shown (ComponentRef, HTMLAttributes, ReactNode)Applied to files:
packages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/primitives/window.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/react/src/primitives/avatar/fallback.tsxpackages/react/src/primitives/avatar/image.tsxpackages/react/src/primitives/message.tsxpackages/react/src/primitives/bubble.tsx📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.{ts,tsx} : Provide proper type definitions for all props and refsApplied to files:
packages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message.tsxpackages/react/src/config.tsx📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.tsx : Base component pattern: forwardRef, choose Slot or intrinsic element via asChild, pass ref/className and spread propsApplied to files:
packages/react/src/primitives/message-list.tsxpackages/react/src/primitives/button.tsxpackages/react/src/primitives/message-group.tsxpackages/react/src/primitives/avatar/avatar.tsxpackages/react/src/primitives/message.tsxpackages/react/src/primitives/bubble.tsx📚 Learning: 2025-09-17T16:59:53.676Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/react-package-rules.mdc:0-0 Timestamp: 2025-09-17T16:59:53.676Z Learning: Applies to packages/react/**/*.{ts,tsx} : Support developer-owned rendering: components should not impose visual structure and should allow parent-driven markup via render prop/asChildApplied to files:
packages/react/src/primitives/message-list.tsx📚 Learning: 2025-09-17T09:06:53.619Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.619Z Learning: Applies to **/*.{jsx,tsx} : Always include a type attribute for button elementsApplied to files:
packages/react/src/primitives/button.tsx📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{js,ts} : Ensure Promise-like statements are handled (awaited, returned, or chained) appropriatelyApplied to files:
packages/react/src/hooks/use-client-query.ts📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{js,ts} : Don't use async functions as Promise executorsApplied to files:
packages/react/src/hooks/use-client-query.ts📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to **/*.{ts,tsx} : Prefer explicit return types on exported APIs in TypeScriptApplied to files:
packages/react/src/hooks/use-client-query.tspackages/react/src/index.ts📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.{ts,tsx} : Export type definitions for the public APIApplied to files:
packages/react/src/hooks/use-client-query.ts📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{ts,tsx} : Use export type for typesApplied to files:
packages/react/src/hooks/use-client-query.ts📚 Learning: 2025-09-17T09:00:28.751Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: CLAUDE.md:0-0 Timestamp: 2025-09-17T09:00:28.751Z Learning: Support React/Next.js/Remix applications with a ready-to-use <Support /> component and primitive headless componentsApplied to files:
packages/react/src/primitives/window.tsxpackages/react/src/support/index.tsxpackages/react/src/provider.tsxpackages/react/src/config.tsxapps/web/src/app/playground/page.tsxpackages/react/src/primitives/bubble.tsx📚 Learning: 2025-09-17T09:00:28.751Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: CLAUDE.md:0-0 Timestamp: 2025-09-17T09:00:28.751Z Learning: Applies to packages/react/src/**/*.{ts,tsx} : Develop headless React components (including <Support /> and primitives) under packages/react/src/Applied to files:
packages/react/src/support/index.tsxpackages/react/src/config.tsxapps/web/src/app/playground/page.tsxpackages/react/src/index.ts📚 Learning: 2025-09-17T09:06:53.620Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.620Z Learning: Applies to **/*.{js,ts} : Don't have unused importsApplied to files:
packages/react/src/provider.tsx📚 Learning: 2025-09-20T07:23:05.629Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: AGENTS.md:0-0 Timestamp: 2025-09-20T07:23:05.629Z Learning: Applies to apps/web/src/hooks/use*.{ts,tsx} : Name React hooks with a use* prefix and place them in apps/web/src/hooksApplied to files:
packages/react/src/hooks/index.ts📚 Learning: 2025-09-17T09:06:53.619Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-09-17T09:06:53.619Z Learning: Applies to **/*.{ts,tsx} : Don't use primitive type aliases or misleading typesApplied to files:
packages/react/src/primitives/message.tsx📚 Learning: 2025-09-17T09:01:10.563Z
Learnt from: CR PR: cossistantcom/cossistant#0 File: .cursor/rules/global-rules.mdc:0-0 Timestamp: 2025-09-17T09:01:10.563Z Learning: Applies to **/*.tsx : Child components must be used within Root, read context via hooks, support asChild, and follow consistent propsApplied to files:
packages/react/src/primitives/bubble.tsx🧬 Code graph analysis (19)
packages/react/src/primitives/message-list.tsx (1)
packages/react/src/support/components/message-list.tsx (1)
MessageListProps(18-28)packages/react/src/primitives/button.tsx (1)
packages/react/src/support/components/button.tsx (1)
ButtonProps(36-37)packages/react/src/primitives/message-group.tsx (1)
packages/react/src/support/components/message-group.tsx (1)
MessageGroupProps(20-25)packages/react/src/hooks/use-client-query.ts (1)
packages/core/src/client.ts (1)
CossistantClient(66-367)apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx (1)
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.ts (1)
DashboardRealtimeContext(15-19)packages/react/src/hooks/store/use-website-store.ts (2)
packages/core/src/client.ts (1)
CossistantClient(66-367)packages/react/src/hooks/use-client-query.ts (1)
useClientQuery(38-219)packages/react/src/support/index.tsx (1)
packages/next/src/support/index.tsx (1)
SupportProps(4-4)packages/react/src/primitives/avatar/fallback.tsx (1)
packages/react/src/primitives/avatar/index.parts.ts (1)
AvatarFallbackProps(3-3)apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.ts (3)
packages/types/src/trpc/conversation.ts (1)
ConversationHeader(98-98)apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/types.ts (1)
RealtimeEventHandler(27-28)apps/web/src/data/conversation-header-cache.ts (1)
updateConversationHeaderInCache(14-62)packages/react/src/hooks/use-grouped-messages.ts (3)
packages/types/src/schemas.ts (1)
ConversationSeen(102-102)packages/next/src/hooks/use-grouped-messages.ts (1)
UseGroupedMessagesProps(7-7)packages/react/src/support/hooks/index.ts (1)
UseGroupedMessagesProps(10-10)packages/react/src/provider.tsx (3)
packages/core/src/client.ts (1)
CossistantClient(66-367)packages/react/src/hooks/use-rest-client.ts (1)
useClient(18-51)packages/react/src/hooks/store/use-website-store.ts (1)
useWebsiteStore(42-85)packages/core/src/client.ts (8)
packages/types/src/schemas.ts (2)
Conversation(58-58)Message(25-25)packages/core/src/rest-client.ts (2)
CossistantRestClient(43-623)request(149-173)packages/core/src/store/conversations-store.ts (2)
ConversationsStore(190-193)createConversationsStore(195-209)packages/core/src/store/messages-store.ts (2)
MessagesStore(188-198)createMessagesStore(200-229)packages/core/src/store/website-store.ts (3)
WebsiteStore(39-44)createWebsiteStore(46-105)WebsiteState(10-14)packages/core/src/utils.ts (2)
generateConversationId(13-16)generateMessageId(18-20)packages/types/src/enums.ts (6)
ConversationStatus(17-21)ConversationStatus(23-24)MessageType(1-5)MessageType(7-7)MessageVisibility(33-36)MessageVisibility(74-75)packages/types/src/realtime-events.ts (1)
RealtimeEvent(94-98)packages/react/src/primitives/avatar/image.tsx (1)
packages/react/src/primitives/avatar/index.parts.ts (1)
AvatarImageProps(5-5)packages/react/src/primitives/message.tsx (2)
apps/web/src/components/conversation/messages/message.tsx (1)
MessageProps(10-14)packages/react/src/support/components/message.tsx (1)
MessageProps(10-14)packages/react/src/config.tsx (1)
packages/react/src/support/store/support-store.ts (1)
SupportConfig(24-35)packages/react/src/realtime/support-provider.tsx (4)
packages/core/src/client.ts (1)
CossistantClient(66-367)packages/react/src/realtime/index.ts (2)
RealtimeEventHandlerContext(9-9)RealtimeEventHandlersMap(26-28)packages/react/src/provider.tsx (1)
useSupport(220-237)packages/react/src/hooks/use-realtime-support.ts (1)
useRealtimeSupport(23-47)apps/web/src/app/playground/page.tsx (6)
packages/react/src/provider.tsx (1)
DefaultMessage(26-30)packages/types/src/enums.ts (2)
SenderType(9-13)SenderType(15-15)packages/react/src/hooks/use-realtime-support.ts (1)
useRealtimeSupport(23-47)packages/react/src/support/index.tsx (2)
useSupportConfig(64-64)Support(26-59)packages/react/src/support/store/support-store.ts (1)
useSupportConfig(173-182)apps/web/src/components/ui/logo.tsx (1)
LogoText(26-38)packages/react/src/hooks/use-rest-client.ts (1)
packages/core/src/client.ts (1)
CossistantClient(66-367)packages/core/src/store/messages-store.test.ts (5)
packages/types/src/realtime-events.ts (1)
RealtimeEvent(94-98)packages/core/src/store/messages-store.ts (1)
createMessagesStore(200-229)packages/core/src/client.ts (1)
CossistantClient(66-367)packages/types/src/api/message.ts (1)
SendMessageRequest(37-37)packages/types/src/api/conversation.ts (1)
CreateConversationRequestBody(35-37)🪛 GitHub Actions: PR Tests
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.ts
[error] 1-1: Formatter would have reformatted file. Biome/Prettier detected style differences and would print updated content.
apps/web/src/app/playground/page.tsx
[error] 1-1: Formatter would have reformatted file. Biome/Prettier detected style differences and would print updated content.
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.ts
[error] 1-1: Formatter would have reformatted file. Biome/Prettier detected style differences and would print updated content.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
| const input = (maybeInput as QueryKeyInput).input; | ||
| if (!input || typeof input !== "object") { | ||
| return null; | ||
| } | ||
|
|
||
| return input; | ||
| return input; | ||
| } |
There was a problem hiding this comment.
Do not skip cache updates when the TRPC input is undefined.
trpc-react-query emits keys like [["conversation","listConversationsHeaders"], { input: undefined, … }] when callers rely on server defaults. The new guard treats this as “no input” and bails out, so every dashboard view that omits websiteSlug will stop receiving CONVERSATION_SEEN updates and unread counts/last-seen stamps will stall. Please treat null/undefined as an empty input instead of aborting.
- const input = (maybeInput as QueryKeyInput).input;
- if (!input || typeof input !== "object") {
- return null;
- }
-
- return input;
+ const input = (maybeInput as QueryKeyInput).input;
+ if (input == null) {
+ return {};
+ }
+ if (typeof input !== "object") {
+ return null;
+ }
+ return input;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const input = (maybeInput as QueryKeyInput).input; | |
| if (!input || typeof input !== "object") { | |
| return null; | |
| } | |
| return input; | |
| return input; | |
| } | |
| const input = (maybeInput as QueryKeyInput).input; | |
| if (input == null) { | |
| return {}; | |
| } | |
| if (typeof input !== "object") { | |
| return null; | |
| } | |
| return input; | |
| } |
🤖 Prompt for AI Agents
In
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/conversation-seen.ts
around lines 28 to 34, the guard currently treats missing TRPC input as "no
input" and returns null, which skips cache updates when callers pass undefined;
instead, treat null/undefined as an empty input object so updates still apply.
Change the logic to return an empty object ({}) when input is null or undefined
(while keeping the existing object-type check for other non-object inputs),
ensuring the function returns a usable input for cache keys rather than
aborting.
| RealtimeEventHandler, | ||
| RealtimeEventHandlerContext, | ||
| RealtimeEventHandlerParams, | ||
| } from "./types"; |
There was a problem hiding this comment.
Restore Biome-compliant indentation to unblock CI.
Biome defaults to tab indentation for TypeScript, so the leading spaces on the exported types trigger the formatter check that’s failing in CI. Please re-run the formatter (e.g., pnpm biome format apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.ts) or adjust the indentation back to tabs so the pipeline passes. (biomejs.dev)
Apply this diff:
export type {
- RealtimeEventHandler,
- RealtimeEventHandlerContext,
- RealtimeEventHandlerParams,
+ RealtimeEventHandler,
+ RealtimeEventHandlerContext,
+ RealtimeEventHandlerParams,
} from "./types";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| RealtimeEventHandler, | |
| RealtimeEventHandlerContext, | |
| RealtimeEventHandlerParams, | |
| } from "./types"; | |
| // apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.ts | |
| export type { | |
| RealtimeEventHandler, | |
| RealtimeEventHandlerContext, | |
| RealtimeEventHandlerParams, | |
| } from "./types"; |
🤖 Prompt for AI Agents
In apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.ts around
lines 3 to 6, the exported type imports have leading spaces instead of tabs
which breaks the Biome formatter; replace the leading spaces with tab
indentation for those lines (or run the project formatter) so the file uses
Biome's default tab indentation—e.g., convert the leading spaces to tabs for the
RealtimeEventHandler, RealtimeEventHandlerContext, and
RealtimeEventHandlerParams exports and then run `pnpm biome format
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/index.ts` (or `pnpm
format`) to apply and verify the fix.
| "use client"; | ||
|
|
||
| import { useRealtimeSupport } from "@cossistant/react/hooks/use-realtime-support"; | ||
| import type { DefaultMessage } from "@cossistant/react/provider"; | ||
| import Support, { useSupportConfig } from "@cossistant/react/support"; | ||
| import type { DefaultMessage } from "@cossistant/next"; | ||
| import { useRealtimeSupport } from "@cossistant/next/hooks/use-realtime-support"; | ||
| import { Support, useSupportConfig } from "@cossistant/next/support"; | ||
| import { SenderType } from "@cossistant/types"; | ||
| import { motion } from "motion/react"; | ||
| import Image from "next/image"; | ||
| import { Logo, LogoText } from "@/components/ui/logo"; | ||
| import { LogoText } from "@/components/ui/logo"; | ||
|
|
||
| function PlaygroundPropDisplay({ | ||
| name, | ||
| value, | ||
| name, | ||
| value, | ||
| }: { | ||
| name: string; | ||
| value: string; | ||
| name: string; | ||
| value: string; | ||
| }) { | ||
| return ( | ||
| <div className="flex items-center justify-between gap-4 font-medium uppercase"> | ||
| <h2 className="text-primary/80 text-sm">{name}</h2> | ||
| <div className="h-[1px] flex-1 bg-primary/10" /> | ||
| <p className="font-mono text-sm">{value}</p> | ||
| </div> | ||
| ); | ||
| return ( | ||
| <div className="flex items-center justify-between gap-4 font-medium uppercase"> | ||
| <h2 className="text-primary/80 text-sm">{name}</h2> | ||
| <div className="h-[1px] flex-1 bg-primary/10" /> | ||
| <p className="font-mono text-sm">{value}</p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const DEFAULT_MESSAGES: DefaultMessage[] = [ | ||
| { | ||
| content: "Hi 👋 I'm Anthony, founder of Cossistant", | ||
| senderType: SenderType.TEAM_MEMBER, | ||
| }, | ||
| { | ||
| content: "How can I help you?", | ||
| senderType: SenderType.TEAM_MEMBER, | ||
| }, | ||
| { | ||
| content: "Hi 👋 I'm Anthony, founder of Cossistant", | ||
| senderType: SenderType.TEAM_MEMBER, | ||
| }, | ||
| { | ||
| content: "How can I help you?", | ||
| senderType: SenderType.TEAM_MEMBER, | ||
| }, | ||
| ]; | ||
|
|
||
| const QUICK_OPTIONS = ["How to install Cossistant?", "Pricing"]; | ||
|
|
||
| export default function Playground() { | ||
| const { isConnected } = useRealtimeSupport(); | ||
| const { mode, size, isOpen } = useSupportConfig(); | ||
| const { isConnected } = useRealtimeSupport(); | ||
| const { mode, size, isOpen } = useSupportConfig(); | ||
|
|
||
| return ( | ||
| <div className="relative flex min-h-screen items-center bg-background p-20"> | ||
| <motion.div | ||
| animate={{ opacity: 1 }} | ||
| initial={{ opacity: 0 }} | ||
| transition={{ duration: 3 }} | ||
| > | ||
| <Image | ||
| alt="Playground" | ||
| className="absolute inset-0 z-0 object-cover opacity-10 grayscale-100" | ||
| height={2400} | ||
| src="https://cdn.cossistant.com/neom-t3dlLOhdEzs-unsplash.jpg" | ||
| width={1600} | ||
| /> | ||
| </motion.div> | ||
| <div className="z-10 h-fit w-full border border-primary/10 border-dashed p-4 md:w-1/3"> | ||
| <h1 className="flex items-center gap-2 font-f37-stout text-xl"> | ||
| <LogoText /> | ||
| <span className="font-medium text-primary/40">dev playground</span> | ||
| </h1> | ||
| <div className="mt-10 flex flex-col gap-2"> | ||
| <PlaygroundPropDisplay | ||
| name="Websocket healthy" | ||
| value={isConnected.toString()} | ||
| /> | ||
| <PlaygroundPropDisplay name="Opened" value={isOpen.toString()} /> | ||
| <PlaygroundPropDisplay name="Mode" value={mode} /> | ||
| <PlaygroundPropDisplay name="Size" value={size} /> | ||
| </div> | ||
| </div> | ||
| <Support | ||
| defaultMessages={DEFAULT_MESSAGES} | ||
| defaultOpen | ||
| quickOptions={QUICK_OPTIONS} | ||
| /> | ||
| </div> | ||
| ); | ||
| return ( | ||
| <div className="relative flex min-h-screen items-center bg-background p-20"> | ||
| <motion.div | ||
| animate={{ opacity: 1 }} | ||
| initial={{ opacity: 0 }} | ||
| transition={{ duration: 3 }} | ||
| > | ||
| <Image | ||
| alt="Playground" | ||
| className="absolute inset-0 z-0 object-cover opacity-10 grayscale-100" | ||
| height={2400} | ||
| src="https://cdn.cossistant.com/neom-t3dlLOhdEzs-unsplash.jpg" | ||
| width={1600} | ||
| /> | ||
| </motion.div> | ||
| <div className="z-10 h-fit w-full border border-primary/10 border-dashed p-4 md:w-1/3"> | ||
| <h1 className="flex items-center gap-2 font-f37-stout text-xl"> | ||
| <LogoText /> | ||
| <span className="font-medium text-primary/40">dev playground</span> | ||
| </h1> | ||
| <div className="mt-10 flex flex-col gap-2"> | ||
| <PlaygroundPropDisplay | ||
| name="Websocket healthy" | ||
| value={isConnected.toString()} | ||
| /> | ||
| <PlaygroundPropDisplay name="Opened" value={isOpen.toString()} /> | ||
| <PlaygroundPropDisplay name="Mode" value={mode} /> | ||
| <PlaygroundPropDisplay name="Size" value={size} /> | ||
| </div> | ||
| </div> | ||
| <Support | ||
| defaultMessages={DEFAULT_MESSAGES} | ||
| defaultOpen | ||
| quickOptions={QUICK_OPTIONS} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Run Biome formatter to fix pipeline failure. Biome flagged this file during CI (Formatter would have reformatted file), so please re-run the formatter (e.g., pnpm biome format apps/web/src/app/playground/page.tsx) to align with the enforced style and unblock the build.
🧰 Tools
🪛 GitHub Actions: PR Tests
[error] 1-1: Formatter would have reformatted file. Biome/Prettier detected style differences and would print updated content.
🤖 Prompt for AI Agents
In apps/web/src/app/playground/page.tsx around lines 1 to 81, the file fails CI
because Biome formatting differs from the committed file; run the Biome
formatter to update the file. Fix by running the project formatter command
(e.g., pnpm biome format apps/web/src/app/playground/page.tsx or pnpm biome
format .) then review the changes, stage and commit the formatted file so the
pipeline passes.
| import type { Conversation, Message, RealtimeEvent } from "@cossistant/types"; | ||
| import type { CreateConversationRequestBody } from "@cossistant/types/api/conversation"; | ||
| import type { SendMessageRequest } from "@cossistant/types/api/message"; | ||
| import { CossistantClient } from "../client"; |
There was a problem hiding this comment.
Blocker: missing nanoid dependency breaks the suite
Instantiating CossistantClient pulls in packages/core/src/utils.ts, which now imports nanoid. The package isn’t listed in packages/core dependencies, so bun test fails with Cannot find package 'nanoid'. Please add the runtime dependency (and types if needed) to packages/core/package.json or stop requiring it before we can land this PR.
🤖 Prompt for AI Agents
In packages/core/src/store/messages-store.test.ts around line 5, running tests
fails because CossistantClient imports packages/core/src/utils.ts which now
requires the 'nanoid' package but packages/core/package.json does not list it;
add "nanoid" to the dependencies of packages/core/package.json (and add
@types/nanoid to devDependencies if TypeScript needs types) or remove/guard the
import in utils.ts so tests no longer require nanoid at runtime before landing
this PR.
| type UseDefaultMessagesParams = { | ||
| conversationId: string; | ||
| }; |
There was a problem hiding this comment.
Export the params type alongside the public hook
Since useDefaultMessages is part of the public React surface, its parameter contract needs to be exported for consumers to reuse and compose typings. Please switch the local alias to an exported type. citecoding_guidelines
-type UseDefaultMessagesParams = {
+export type UseDefaultMessagesParams = {
conversationId: string;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| type UseDefaultMessagesParams = { | |
| conversationId: string; | |
| }; | |
| export type UseDefaultMessagesParams = { | |
| conversationId: string; | |
| }; |
🤖 Prompt for AI Agents
In packages/react/src/hooks/use-default-messages.ts around lines 11 to 13, the
params type for the public hook is currently declared locally; change it to an
exported type so consumers can reuse it by replacing the local alias with
"export type UseDefaultMessagesParams = { conversationId: string }" (and update
any local references/imports if needed).
| export { useClientQuery } from "./hooks/use-client-query"; | ||
| export type { |
There was a problem hiding this comment.
Re-export useClientQuery option/result types.
The PR summary promises these types, but the index only exports the hook. Please add the matching type re-export so consumers can grab them from the package root. As per coding guidelines.
-export { useClientQuery } from "./hooks/use-client-query";
+export { useClientQuery } from "./hooks/use-client-query";
+export type {
+ UseClientQueryOptions,
+ UseClientQueryResult,
+} from "./hooks/use-client-query";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export { useClientQuery } from "./hooks/use-client-query"; | |
| export type { | |
| export { useClientQuery } from "./hooks/use-client-query"; | |
| export type { | |
| UseClientQueryOptions, | |
| UseClientQueryResult, | |
| } from "./hooks/use-client-query"; |
🤖 Prompt for AI Agents
In packages/react/src/index.ts around lines 11-12, the package re-exports the
hook but not its option/result types promised in the PR; add a type re-export
for the hook's option and result types by exporting them from
"./hooks/use-client-query" (e.g. export type { UseClientQueryOptions,
UseClientQueryResult } from "./hooks/use-client-query") so consumers can import
these types from the package root.
| type CossistantLogoProps = { | ||
| className?: string; | ||
| }; | ||
|
|
||
| export function CossistantLogo({ className }: Props) { | ||
| export function CossistantLogo({ className }: CossistantLogoProps) { |
There was a problem hiding this comment.
Export the new props type.
CossistantLogoProps is intended to be part of the public surface, but it's currently module-private. Any index barrel trying to re-export it (or downstream consumers importing it) will hit a TypeScript error that the symbol doesn't exist. Please export the alias directly from this module.
-type CossistantLogoProps = {
+export type CossistantLogoProps = {
className?: string;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| type CossistantLogoProps = { | |
| className?: string; | |
| }; | |
| export function CossistantLogo({ className }: Props) { | |
| export function CossistantLogo({ className }: CossistantLogoProps) { | |
| ++ b/packages/react/src/support/components/cossistant-branding.tsx | |
| @@ | |
| export type CossistantLogoProps = { | |
| className?: string; | |
| }; | |
| export function CossistantLogo({ className }: CossistantLogoProps) { |
🤖 Prompt for AI Agents
In packages/react/src/support/components/cossistant-branding.tsx around lines 3
to 7, the type alias CossistantLogoProps is declared but not exported, causing
downstream consumers and index barrels to fail to re-export or import it; export
the type by changing the declaration to an exported type (e.g., export type
CossistantLogoProps = { className?: string } ) and keep the component signature
as-is so consumers can import both the component and its props from this module.
| wasConversationCreated: true; | ||
| }; | ||
| } catch (error) { | ||
| this.messagesStore.removeMessage(rest.conversationId, optimisticId); |
There was a problem hiding this comment.
Bug: Unvalidated Empty Array Causes Undefined Message
In the sendMessage method, when creating a new conversation, the code attempts to extract a message from response.initialMessages without validating if the array is empty. If the server returns an empty initialMessages array (which can happen if no default messages are provided), the message field in the return object becomes undefined. This violates the expected return type and can lead to runtime errors.
Summary
useClientQuerywhile reading from the core storesTesting
https://chatgpt.com/codex/tasks/task_e_68d7b2078dc8832b92878d5c653b9461
Summary by CodeRabbit
New Features
Documentation
Tests
Refactor
Chores
Note
Introduce core client stores with optimistic/realtime updates and refactor React/Next hooks and provider to fetch via client + stores, simplifying consumers.
conversations,messages, andwebsitestores with immutable reducers, selectors, and tests; hydrate via REST and ingest realtimeMESSAGE_CREATED.CossistantClientnow hydrates stores (list/get/create, messages, website fetch with caching), supports optimisticsendMessageandinitiateConversationfor pending convos.useClientQuery,useWebsiteStore,useConversations,useConversation,useConversationMessages,useCreateConversation,useSendMessage; unify over core client + stores; re-export types.useClientnow throws on missing key and returns non-null client.providerentry and hook facades.CONVERSATION_SEENhandler; fix imports; small UI adjustments.Written by Cursor Bugbot for commit 0ce55e7. This will update automatically on new commits. Configure here.