-
Notifications
You must be signed in to change notification settings - Fork 87
navigation simplification #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Updated `ChatHome` to utilize `useChatId` for fetching the chat ID directly, removing the need for props. - Simplified `ChatPageRouter` to render `ChatPage` directly without conditional checks for chat type. - Replaced `ChatPage` implementation to handle chat ID and session validation, ensuring proper access control for anonymous users. - Enhanced shared chat handling in `SharedChatPageRoute` with prefetching for improved performance. - Introduced `useIsSharedRoute` hook to streamline shared route detection across components. - Updated chat ID provider to manage provisional and persisted chat states more effectively, including new methods for marking and confirming chat IDs.
… Management - Removed the `ChatPageRouter` component, directly rendering `ChatPage` within the routing structure for simplicity. - Introduced `ProjectPage` and `ProjectChatPage` components to streamline project-related chat functionalities. - Implemented a new `useChatSystemInitialState` hook to centralize initial state management for chat messages and tools. - Updated `TextMessagePart` to simplify rendering logic and improve performance. - Enhanced project page routing to ensure proper data fetching and error handling.
- Replaced `useQuery` with `useSuspenseQuery` for improved data fetching in `ProjectPage`. - Simplified project ID extraction from parameters and streamlined error handling for project loading. - Enhanced readability and maintainability of the component by reducing unnecessary loading states.
- Updated `DeleteChatDialog` to utilize `isPersisted` instead of `type` for determining chat state during deletion. - Simplified `ChatIdProvider` by removing the `type` property from the context, focusing on `id` and `isPersisted` for clarity. - Enhanced state management for chat IDs, improving the overall structure and maintainability of the components.
- Introduced `resolveChatId` function to streamline chat ID extraction from URL paths, enhancing clarity and maintainability. - Updated `ChatIdProvider` to utilize the new `resolveChatId` function, improving state management for provisional and confirmed chat IDs. - Modified test cases in `resolve-chat-id.test.ts` to reflect changes in the chat ID type and persistence logic, ensuring accurate validation of chat ID resolution.
|
You have run out of free Bugbot PR reviews for this billing cycle. This will reset on January 5. To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughReplaces client-side chat routers with direct page components, centralizes chat ID semantics around an Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45–60 minutes Areas to inspect closely:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (4)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (6)
app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx (1)
16-18: Consider validating projectId before use.While
projectIdshould always be present in the/project/[projectId]/chat/[chatId]route, the optional typing{ projectId?: string }means TypeScript sees it as potentially undefined. For defensive coding and better type safety, consider adding a validation check:const params = useParams<{ projectId?: string }>(); -const projectId = params.projectId; + +const projectId = params.projectId; +if (!projectId) { + return notFound(); +}This ensures type narrowing and handles edge cases explicitly, though the current code may work if
ChatSystemacceptsundefinedforprojectId.Also applies to: 66-66
components/multimodal-input.tsx (1)
86-86: Clarify the intent of callinguseChatId()without using its return value.The hook is invoked but its return value is discarded. If this is meant as a context guard (to ensure the component is within
ChatIdProvider), consider adding a brief comment explaining the intent. Otherwise, if the return value should be used, destructure the needed values.- useChatId(); + // Ensure component is within ChatIdProvider context + useChatId();components/data-stream-handler.tsx (1)
168-171: Consider tracking this TODO or removing the dead code path.The conditional block checks for
data-chatConfirmedbut the actual action is commented out. While the TODO explains the reason, this leaves a no-op code path. Consider either:
- Removing this block entirely until the dependency is resolved, or
- Opening an issue to track re-enabling this feature
Would you like me to help open an issue to track re-enabling the
confirmChatIdfunctionality once chat threads are independent of thegetChatByIdquery cache?app/(chat)/chat/[id]/page.tsx (1)
1-3: Consider adding aSuspensefallback to avoid blank UI during fetch.
<Suspense>withoutfallbackwill render nothing while the subtree suspends; a small skeleton/loader would improve perceived performance. Also worth sanity-checking thatchatIdprefetched fromparamsalways matches theuseChatId()-resolved ID used byChatPage.Also applies to: 18-20
providers/message-tree-provider.tsx (1)
42-110: Avoid recomputing andJSON.stringify-comparing query keys inside the cache subscriber.
You already computequeryKey(Line 61-64); the handler can close over it and compare by reference/hash instead of serializing on every cache event.- const handleCacheUpdate = (event: { + const handleCacheUpdate = (event: { type: string; query: { queryKey?: unknown; state: { data?: unknown }; }; }) => { if (event.type !== "updated" || !event.query.queryKey) { return; } - - const eventQueryKey = event.query.queryKey; - const currentQueryKey = isShared - ? trpc.chat.getPublicChatMessages.queryKey({ chatId: id }) - : trpc.chat.getChatMessages.queryKey({ chatId: id }); - - if (JSON.stringify(eventQueryKey) === JSON.stringify(currentQueryKey)) { + const eventQueryKey = event.query.queryKey; + if (JSON.stringify(eventQueryKey) === JSON.stringify(queryKey)) { const newData = event.query.state.data as ChatMessage[] | undefined; if (newData) { setAllMessages(newData); } } };If you want to fully remove
JSON.stringify, consider comparing via query hashes (query.queryHash) orhashKey(queryKey)from TanStack Query internals—depending on what your event payload exposes.components/header-breadcrumb.tsx (1)
27-57: Query enablement logic matchesisShared/isPersisted; consider a non-null loading state if breadcrumb flicker matters.
Right nowif (!(chat || publicChat)) return null;will hide the breadcrumb while the enabled query is fetching. If that causes layout shift, consider rendering a placeholder until data arrives.Also applies to: 86-90
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (27)
app/(chat)/api/chat/route.ts(1 hunks)app/(chat)/chat-home.tsx(1 hunks)app/(chat)/chat-page-router.tsx(0 hunks)app/(chat)/chat/[id]/chat-page.tsx(2 hunks)app/(chat)/chat/[id]/page.tsx(2 hunks)app/(chat)/page.tsx(1 hunks)app/(chat)/project/[projectId]/chat/[chatId]/page.tsx(2 hunks)app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx(2 hunks)app/(chat)/project/[projectId]/page.tsx(2 hunks)app/(chat)/project/[projectId]/project-page.tsx(1 hunks)app/(chat)/share/[id]/page.tsx(1 hunks)components/data-stream-handler.tsx(3 hunks)components/delete-chat-dialog.tsx(2 hunks)components/header-breadcrumb.tsx(3 hunks)components/message-parts.tsx(1 hunks)components/multimodal-input.tsx(2 hunks)components/part/text-message-part.tsx(1 hunks)components/sidebar-chat-item.tsx(1 hunks)hooks/chat-sync-hooks.ts(6 hunks)hooks/use-chat-system-initial-state.ts(1 hunks)hooks/use-is-shared-route.ts(1 hunks)lib/ai/core-chat-agent.ts(0 hunks)lib/ai/types.ts(1 hunks)providers/chat-id-provider.tsx(1 hunks)providers/message-tree-provider.tsx(5 hunks)providers/resolve-chat-id.test.ts(8 hunks)providers/resolve-chat-id.ts(2 hunks)
💤 Files with no reviewable changes (2)
- lib/ai/core-chat-agent.ts
- app/(chat)/chat-page-router.tsx
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{css,tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Use Tailwind 4 for styling
Files:
lib/ai/types.tsapp/(chat)/api/chat/route.tsapp/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxhooks/use-is-shared-route.tscomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/resolve-chat-id.tsproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxhooks/use-chat-system-initial-state.tshooks/chat-sync-hooks.tscomponents/data-stream-handler.tsxcomponents/message-parts.tsxproviders/resolve-chat-id.test.tsapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Use Shadcn UI components from
components/uifolder with config incomponents.jsonCo-locate small form schemas with the component; extract only when reused across modules
Files:
lib/ai/types.tsapp/(chat)/api/chat/route.tsapp/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxhooks/use-is-shared-route.tscomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/resolve-chat-id.tsproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxhooks/use-chat-system-initial-state.tshooks/chat-sync-hooks.tscomponents/data-stream-handler.tsxcomponents/message-parts.tsxproviders/resolve-chat-id.test.tsapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Use Vercel AI SDK for AI framework and frontend-backend interactions
**/*.{ts,tsx}: Avoidanyat all cost in TypeScript. The types should work or they indicate a problem.
Never useas "any"oras unknown asto solve/avoid type errors in TypeScript. The types should work or they indicate a problem.
Avoid usingasto cast to a specific type in TypeScript. The types should work or they indicate a problem.
Always use inline interfaces with function parameters in TypeScript
Files:
lib/ai/types.tsapp/(chat)/api/chat/route.tsapp/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxhooks/use-is-shared-route.tscomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/resolve-chat-id.tsproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxhooks/use-chat-system-initial-state.tshooks/chat-sync-hooks.tscomponents/data-stream-handler.tsxcomponents/message-parts.tsxproviders/resolve-chat-id.test.tsapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
Always use direct imports with named exports instead of barrel files
**/*.{ts,tsx,js,jsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Files:
lib/ai/types.tsapp/(chat)/api/chat/route.tsapp/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxhooks/use-is-shared-route.tscomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/resolve-chat-id.tsproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxhooks/use-chat-system-initial-state.tshooks/chat-sync-hooks.tscomponents/data-stream-handler.tsxcomponents/message-parts.tsxproviders/resolve-chat-id.test.tsapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{ts,tsx,js,jsx,json,jsonc,html,vue,svelte,astro,css,yaml,yml,graphql,gql,md,mdx,grit}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Files:
lib/ai/types.tsapp/(chat)/api/chat/route.tsapp/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxhooks/use-is-shared-route.tscomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/resolve-chat-id.tsproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxhooks/use-chat-system-initial-state.tshooks/chat-sync-hooks.tscomponents/data-stream-handler.tsxcomponents/message-parts.tsxproviders/resolve-chat-id.test.tsapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
app/(chat)/api/chat/route.ts
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Handle AI interactions through the endpoint
app/(chat)/api/chat/route.tsfor message creation
Files:
app/(chat)/api/chat/route.ts
app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Use Next.js with app directory structure
Files:
app/(chat)/api/chat/route.tsapp/(chat)/project/[projectId]/page.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/trpc-patterns.mdc)
**/*.tsx: In TRPC frontend components, use theuseTRPC()hook from@/trpc/reactto access TRPC client instance
In TRPC frontend components, importuseMutation,useQuery,useQueryClientfrom@tanstack/react-query
In TRPC frontend queries, use the pattern:useQuery({ ...trpc.router.procedure.queryOptions(), enabled: !!condition })
In TRPC frontend mutations, invalidate related queries usingqueryClient.invalidateQueries()in theonSuccesscallback
In TRPC frontend components, handle loading and error states in the UI based on query/mutation states
**/*.tsx: Use React function components with hooks over class components
Mark interactive components with "use client" at the top of the file; keep non-interactive logic in server components or libraries
Use named exports for all components; no default exports
Follow the standard rules of hooks; no conditional or looped hooks
Prefer deriving values from props/form state instead of duplicating them in useState
Keep useEffect minimal and side effect focused; avoid using it for basic data derivation
Use react-hook-form + Zod for all non-trivial forms
Prefer shadcn Form primitives (Form, FormField, FormItem, FormLabel, FormControl, FormMessage) for form layout and error handling
Use the shared cn utility for conditional classes
Prefer smaller composed components over deeply nested JSX in a single component
Always use useConfig() from @/components/config-provider to access site configuration in client components with "use client"
Never import siteConfig directly in client components—use ConfigProvider instead as it receives a serialized version that decouples client code from server secrets
Files:
app/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxcomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxcomponents/data-stream-handler.tsxcomponents/message-parts.tsxapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{tsx,jsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props in React
Don't define components inside other components
AvoiddangerouslySetInnerHTMLunless absolutely necessary
Use proper image components (e.g., Next.js<Image>) over<img>tags
Use Next.js<Image>component for images
Usenext/heador App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead ofReact.forwardRefin React 19+
Files:
app/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxcomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxcomponents/data-stream-handler.tsxcomponents/message-parts.tsxapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{tsx,jsx,vue,svelte,astro,html}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Files:
app/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxcomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxcomponents/data-stream-handler.tsxcomponents/message-parts.tsxapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{tsx,jsx,html,vue,svelte,astro}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Add
rel="noopener"when usingtarget="_blank"on links
Files:
app/(chat)/project/[projectId]/page.tsxcomponents/sidebar-chat-item.tsxapp/(chat)/chat-home.tsxapp/(chat)/project/[projectId]/project-page.tsxcomponents/part/text-message-part.tsxcomponents/delete-chat-dialog.tsxproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxcomponents/multimodal-input.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxproviders/chat-id-provider.tsxcomponents/data-stream-handler.tsxcomponents/message-parts.tsxapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{test,spec}.{ts,tsx,js,jsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
providers/resolve-chat-id.test.ts
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-29T10:39:51.188Z
Learning: Applies to app/(chat)/api/chat/route.ts : Handle AI interactions through the endpoint `app/(chat)/api/chat/route.ts` for message creation
📚 Learning: 2025-11-29T10:39:51.188Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-29T10:39:51.188Z
Learning: Applies to app/(chat)/api/chat/route.ts : Handle AI interactions through the endpoint `app/(chat)/api/chat/route.ts` for message creation
Applied to files:
lib/ai/types.tsapp/(chat)/api/chat/route.tscomponents/sidebar-chat-item.tsxproviders/message-tree-provider.tsxapp/(chat)/chat/[id]/chat-page.tsxapp/(chat)/page.tsxcomponents/header-breadcrumb.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxhooks/use-chat-system-initial-state.tshooks/chat-sync-hooks.tsproviders/resolve-chat-id.test.tsapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
📚 Learning: 2025-11-29T10:40:31.537Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-29T10:40:31.537Z
Learning: Applies to **/*.{tsx,jsx} : Use Server Components for async data fetching instead of async Client Components in Next.js
Applied to files:
app/(chat)/project/[projectId]/page.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
📚 Learning: 2025-11-29T10:40:00.793Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/trpc-patterns.mdc:0-0
Timestamp: 2025-11-29T10:40:00.793Z
Learning: Applies to **/*.tsx : In TRPC frontend components, use the `useTRPC()` hook from `@/trpc/react` to access TRPC client instance
Applied to files:
app/(chat)/project/[projectId]/page.tsxhooks/use-is-shared-route.tscomponents/header-breadcrumb.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxhooks/chat-sync-hooks.tsapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
📚 Learning: 2025-11-29T10:40:00.793Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/trpc-patterns.mdc:0-0
Timestamp: 2025-11-29T10:40:00.793Z
Learning: Create feature-specific TRPC routers using the naming convention `trpc/routers/featureName.router.ts`
Applied to files:
app/(chat)/project/[projectId]/page.tsxapp/(chat)/share/[id]/page.tsx
📚 Learning: 2025-11-29T10:40:00.793Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/trpc-patterns.mdc:0-0
Timestamp: 2025-11-29T10:40:00.793Z
Learning: All TRPC routers must be registered in the main router at `trpc/routers/_app.ts`
Applied to files:
app/(chat)/project/[projectId]/page.tsx
📚 Learning: 2025-11-29T10:40:00.793Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/trpc-patterns.mdc:0-0
Timestamp: 2025-11-29T10:40:00.793Z
Learning: Applies to **/*.tsx : In TRPC frontend queries, use the pattern: `useQuery({ ...trpc.router.procedure.queryOptions(), enabled: !!condition })`
Applied to files:
app/(chat)/project/[projectId]/page.tsxhooks/chat-sync-hooks.ts
📚 Learning: 2025-12-09T12:49:45.050Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/react.md:0-0
Timestamp: 2025-12-09T12:49:45.050Z
Learning: Applies to **/*.tsx : Use React function components with hooks over class components
Applied to files:
app/(chat)/chat-home.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/page.tsx
📚 Learning: 2025-11-29T10:39:51.188Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-29T10:39:51.188Z
Learning: Applies to app/**/*.{ts,tsx} : Use Next.js with app directory structure
Applied to files:
app/(chat)/project/[projectId]/project-page.tsx
📚 Learning: 2025-11-29T10:40:00.793Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/trpc-patterns.mdc:0-0
Timestamp: 2025-11-29T10:40:00.793Z
Learning: Applies to **/*.tsx : In TRPC frontend components, import `useMutation`, `useQuery`, `useQueryClient` from `tanstack/react-query`
Applied to files:
app/(chat)/project/[projectId]/project-page.tsxproviders/message-tree-provider.tsxcomponents/header-breadcrumb.tsxapp/(chat)/share/[id]/page.tsxapp/(chat)/chat/[id]/page.tsxapp/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsxhooks/chat-sync-hooks.ts
📚 Learning: 2025-11-29T10:40:00.793Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/trpc-patterns.mdc:0-0
Timestamp: 2025-11-29T10:40:00.793Z
Learning: Applies to **/*.tsx : In TRPC frontend mutations, invalidate related queries using `queryClient.invalidateQueries()` in the `onSuccess` callback
Applied to files:
providers/message-tree-provider.tsxhooks/chat-sync-hooks.ts
📚 Learning: 2025-12-09T12:49:45.050Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/react.md:0-0
Timestamp: 2025-12-09T12:49:45.050Z
Learning: Applies to **/*.tsx : Mark interactive components with "use client" at the top of the file; keep non-interactive logic in server components or libraries
Applied to files:
components/header-breadcrumb.tsx
📚 Learning: 2025-11-29T10:40:00.793Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/trpc-patterns.mdc:0-0
Timestamp: 2025-11-29T10:40:00.793Z
Learning: In TRPC backend mutations, verify user ownership and permissions before executing the mutation logic
Applied to files:
hooks/chat-sync-hooks.ts
📚 Learning: 2025-11-29T10:40:00.793Z
Learnt from: CR
Repo: FranciscoMoretti/sparka PR: 0
File: .cursor/rules/trpc-patterns.mdc:0-0
Timestamp: 2025-11-29T10:40:00.793Z
Learning: Applies to **/*.tsx : In TRPC frontend components, handle loading and error states in the UI based on query/mutation states
Applied to files:
hooks/chat-sync-hooks.ts
🧬 Code graph analysis (19)
app/(chat)/api/chat/route.ts (1)
lib/utils.ts (1)
generateUUID(89-91)
app/(chat)/project/[projectId]/page.tsx (1)
app/(chat)/project/[projectId]/project-page.tsx (1)
ProjectPage(9-31)
app/(chat)/chat-home.tsx (1)
providers/chat-id-provider.tsx (1)
useChatId(86-92)
app/(chat)/project/[projectId]/project-page.tsx (3)
providers/chat-id-provider.tsx (1)
useChatId(86-92)trpc/server.tsx (1)
trpc(16-20)lib/db/schema.ts (1)
project(57-75)
components/part/text-message-part.tsx (1)
components/ai-elements/response.tsx (1)
Response(9-20)
components/delete-chat-dialog.tsx (1)
providers/chat-id-provider.tsx (1)
useChatId(86-92)
providers/message-tree-provider.tsx (3)
providers/chat-id-provider.tsx (1)
useChatId(86-92)hooks/use-is-shared-route.ts (1)
useIsSharedRoute(7-10)trpc/server.tsx (1)
trpc(16-20)
app/(chat)/page.tsx (1)
app/(chat)/chat-home.tsx (1)
ChatHome(8-23)
components/header-breadcrumb.tsx (4)
providers/chat-id-provider.tsx (1)
useChatId(86-92)hooks/use-is-shared-route.ts (1)
useIsSharedRoute(7-10)hooks/chat-sync-hooks.ts (1)
useGetChatById(444-453)hooks/use-shared-chat.ts (1)
usePublicChat(4-14)
components/multimodal-input.tsx (1)
providers/chat-id-provider.tsx (1)
useChatId(86-92)
app/(chat)/share/[id]/page.tsx (2)
trpc/server.tsx (3)
prefetch(31-40)trpc(16-20)HydrateClient(22-29)app/(chat)/share/[id]/shared-chat-page.tsx (1)
SharedChatPage(9-76)
app/(chat)/chat/[id]/page.tsx (1)
app/(chat)/chat/[id]/chat-page.tsx (1)
ChatPage(35-49)
app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx (1)
providers/chat-id-provider.tsx (1)
useChatId(86-92)
providers/chat-id-provider.tsx (2)
lib/utils.ts (1)
generateUUID(89-91)providers/resolve-chat-id.ts (1)
resolveChatId(29-82)
hooks/use-chat-system-initial-state.ts (2)
lib/ai/types.ts (2)
ChatMessage(111-116)UiToolName(49-49)lib/thread-utils.ts (1)
getDefaultThread(73-80)
hooks/chat-sync-hooks.ts (3)
providers/chat-id-provider.tsx (1)
useChatId(86-92)hooks/use-is-shared-route.ts (1)
useIsSharedRoute(7-10)providers/session-provider.tsx (1)
useSession(39-45)
components/message-parts.tsx (2)
components/part/text-message-part.tsx (1)
TextMessagePart(6-12)lib/db/schema.ts (1)
part(145-220)
providers/resolve-chat-id.test.ts (1)
providers/resolve-chat-id.ts (1)
resolveChatId(29-82)
app/(chat)/project/[projectId]/chat/[chatId]/page.tsx (1)
app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx (1)
ProjectChatPage(14-69)
🔇 Additional comments (24)
components/sidebar-chat-item.tsx (1)
96-97: Navigation simplification looks good.The refactored approach consistently uses
router.push(chatHref)for all chat item clicks. ThechatHrefis correctly typed as a union of both supported routes (/chat/:idand/project/:projectId/chat/:id) and properly constructed based on the chat type.Using
preventDefaultwithrouter.pushon a Link component is a valid Next.js pattern when you need custom navigation behavior and side effects (like closing the mobile menu). The implementation correctly:
- Prevents default link behavior
- Preserves special click handling for middle-click and ctrl-click to open in new tab
- Closes the mobile sidebar after navigation
- Maintains accessibility with the Link href attribute
app/(chat)/project/[projectId]/chat/[chatId]/page.tsx (1)
2-2: LGTM! Component rename is consistent.The rename from
ProjectChatPageRoutertoProjectChatPagealigns with the broader refactor to simplify routing terminology. The prefetching and hydration logic remain intact.Also applies to: 18-18
app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx (1)
14-14: LGTM! Component rename improves clarity.The rename from
ProjectChatPageRoutertoProjectChatPageis consistent with the PR's routing simplification objectives.app/(chat)/project/[projectId]/project-page.tsx (1)
9-9: LGTM! Component rename and simplified error handling.The rename from
ProjectPageRoutertoProjectPageand the simplified error condition from a complex check to!projectboth improve code clarity.Also applies to: 18-20
app/(chat)/project/[projectId]/page.tsx (1)
2-2: LGTM! Consistent component rename.The import and usage update from
ProjectPageRoutertoProjectPageis consistent with the component rename inproject-page.tsx. Prefetching logic is preserved correctly.Also applies to: 17-17
lib/ai/types.ts (1)
104-106: LGTM!The new
chatConfirmedtype is well-defined and aligns with the broader chat confirmation flow introduced in this PR.hooks/use-is-shared-route.ts (1)
1-10: LGTM!Clean implementation of the shared route detection hook. The regex correctly matches
/share/{id}paths with optional trailing segments, and theBoolean()wrapper safely handles thenullpathname case.app/(chat)/chat-home.tsx (1)
8-9: LGTM!The transition from prop-based
idto hook-based retrieval viauseChatId()aligns well with the centralized chat ID management pattern introduced in this PR.app/(chat)/page.tsx (1)
1-4: LGTM!Clean simplification of the page entry point. The routing logic is now properly encapsulated in
ChatHome.app/(chat)/share/[id]/page.tsx (1)
4-19: LGTM!Well-structured async Server Component that properly prefetches TRPC queries before rendering the client component. The
HydrateClientwrapper correctly provides the hydrated query state toSharedChatPage. This follows the recommended Next.js pattern for Server Components handling async data fetching.app/(chat)/chat/[id]/chat-page.tsx (1)
35-48: LGTM!Clean separation of concerns between access control (
ChatPage) and content rendering (ChatPageContent). The guard logic correctly:
- Waits for session loading to complete before redirecting (
!isPending)- Redirects anonymous users from persisted chats
- Returns
notFound()for non-persisted chat IDs on this routehooks/use-chat-system-initial-state.ts (1)
7-56: Nice extraction; consider tightening the tool-part shape checks (and confirmfindLastsupport).
Ifparts/outputare not strongly typed, adding a small type guard (or narrowingpart.outputbefore reading.format) will prevent silent runtimeundefinedassumptions. Also confirm your TS/target runtime supportsArray.prototype.findLastin all environments you ship to.app/(chat)/api/chat/route.ts (1)
472-480: Earlydata-chatConfirmedemission looks correct; verify resume/duplication handling on the client.
Given resumable streams, this event may be observed more than once; ensure the client treats it idempotently (e.g.,confirmChatId(chatId)is safe to call repeatedly) and that anonymous flows don’t rely on it. Based on learnings, AI interactions are correctly handled viaapp/(chat)/api/chat/route.ts.components/delete-chat-dialog.tsx (1)
30-62: Good alignment with newisPersistedsemantics.
The “refresh provisional + navigate home” behavior now triggers only when the deleted chat is the active persisted chat, which matches the intended routing simplification.providers/resolve-chat-id.test.ts (1)
8-110: Tests look consistent with the newresolveChatIddiscriminants (type+isPersisted).
Coverage across share/project/chat/root paths is clear and matches the updated semantics.hooks/chat-sync-hooks.ts (4)
34-43: LGTM - Clean refactor to isPersisted flag.The transition from type-based checking to the
isPersistedflag withisSharedroute detection is a cleaner separation of concerns. The enabled condition correctly requires:
- A valid chatId
- The chat to be persisted (safe to query)
- Either shared route access OR authenticated user
410-420: LGTM - Proper conditional query selection.The
useDocumentshook correctly:
- Uses
isSharedto select between public and private document queries- Gates the query with
(isShared || !!session?.user)matching the pattern inuseGetChatMessagesQueryOptions
26-31: LGTM - Empty string fallback is safe.Using
projectId ?? ""is appropriate since the query is gated by!!projectIdin theenabledcondition (line 30), ensuring the query won't execute with an empty string.
304-340: No race condition exists with the current implementation.The concern about
confirmChatIdbeing called before query cache invalidation is unfounded. React state updates (setConfirmedChatId) are synchronous, soisPersistedis set totrueimmediately whenconfirmChatId(chatId)executes on line 327. By the timeqc.invalidateQueriesruns (even the non-awaited call on line 328), the state is already updated and any query checkingisPersistedwill see the correct value.The TODO comment about moving this to
data-stream-handleris an optimization concern, not a bug fix—it's about timing the confirmation earlier in the data flow, not about preventing a race condition.providers/resolve-chat-id.ts (4)
1-19: Well-designed discriminated union types.The type structure with
isPersistedas a literal type discriminant (falsefor provisional,truefor chat) is clean and enables TypeScript's type narrowing. This eliminates the previous "shared" type while maintaining clear semantics:
ProvisionalChatId: temporary client-side ID, not yet persistedPersistedChatId: server-confirmed ID, safe to query
46-63: LGTM - Project route resolution logic is correct.The pattern correctly handles:
/project/:projectId/chat/:chatId→ persisted chat/project/:projectId→ provisional (new chat in project context)The regex
PROJECT_ROUTE_PATTERNat line 22 properly captures both cases with an optional chat segment.
65-81: LGTM - Chat route and fallback resolution.The remaining branches correctly handle:
/chat/:id→ persisted chat (type: "chat", isPersisted: true)- Default/root → provisional (type: "provisional", isPersisted: false)
The
provisionalIdparameter ensures a consistent ID is used for new/provisional chats.
36-44: No further action needed. The migration fromtype: "shared"totype: "chat"for shared routes is complete and properly verified:
resolveChatIdonly returnstype: "chat"ortype: "provisional"(no "shared" type exists)- All shared route consumers already use
useIsSharedRoute()for access pattern determination- The consumer in
chat-id-provider.tsxonly accessesisPersisted, never checking the type field- No remaining references to
type === "shared"exist in the codebasecomponents/part/text-message-part.tsx (1)
6-11: VerifyResponse/Streamdownprop contract formode+isAnimating.
SinceResponseforwards props toStreamdown, make suremodeandisAnimatingare valid/typed props (and won’t end up as unknown DOM attributes ifStreamdownultimately renders a host element).
| export function ProjectPageRouter() { | ||
| const pathname = usePathname(); | ||
| export function ProjectPage() { | ||
| const { projectId } = useParams<{ projectId: string }>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type assertion issue: projectId should be validated, not asserted.
The required typing useParams<{ projectId: string }>() acts as a type assertion without runtime validation, which violates the coding guideline: "Avoid using as to cast to a specific type in TypeScript. The types should work or they indicate a problem."
Additionally, this is inconsistent with project-chat-page.tsx (line 16), which correctly uses optional typing { projectId?: string }.
Apply this diff to use defensive typing with runtime validation:
-const { projectId } = useParams<{ projectId: string }>();
+const params = useParams<{ projectId?: string }>();
+const projectId = params.projectId;
+
+if (!projectId) {
+ return notFound();
+}This provides proper type narrowing and explicit error handling.
As per coding guidelines: types should work without assertions.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/(chat)/project/[projectId]/project-page.tsx around line 10, the call
useParams<{ projectId: string }>() is an unsafe type assertion; change it to
useParams<{ projectId?: string }>() (or omit the generic) and perform runtime
validation immediately after retrieving params: check that projectId is a
non-empty string, handle the invalid case explicitly (e.g., throw a clear error,
return a NotFound/redirect component, or render an error UI) so types align with
runtime behavior and match project-chat-page.tsx's approach.
| if (isTextUIPart(part)) { | ||
| return <TextMessagePart messageId={messageId} partIdx={partIdx} />; | ||
| return <TextMessagePart isLoading={isLoading} text={part.text} />; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "message-parts.tsx" -type fRepository: FranciscoMoretti/sparka
Length of output: 97
🏁 Script executed:
wc -l components/message-parts.tsxRepository: FranciscoMoretti/sparka
Length of output: 99
🏁 Script executed:
sed -n '110,140p' components/message-parts.tsxRepository: FranciscoMoretti/sparka
Length of output: 753
🏁 Script executed:
# Find TextMessagePart component definition
fd -t f -e tsx -e ts | head -20 | xargs grep -l "TextMessagePart" | grep -v node_modulesRepository: FranciscoMoretti/sparka
Length of output: 49
🏁 Script executed:
# Find isTextUIPart definition
rg "isTextUIPart" -A 3 -B 1Repository: FranciscoMoretti/sparka
Length of output: 559
🏁 Script executed:
# Check the type definition for part.text
rg "type.*Part\|interface.*Part" -A 5 | head -50Repository: FranciscoMoretti/sparka
Length of output: 49
🏁 Script executed:
sed -n '100,176p' components/message-parts.tsxRepository: FranciscoMoretti/sparka
Length of output: 1830
🏁 Script executed:
# Find TextMessagePart component
rg "function TextMessagePart|const TextMessagePart|export.*TextMessagePart" -A 10Repository: FranciscoMoretti/sparka
Length of output: 553
🏁 Script executed:
# Look for isLoading usage and how it's determined
rg "isLoading" components/message-parts.tsx -B 2 -A 2Repository: FranciscoMoretti/sparka
Length of output: 888
🏁 Script executed:
# Check what happens at the end of PureMessagePart - does it return anything?
sed -n '140,160p' components/message-parts.tsxRepository: FranciscoMoretti/sparka
Length of output: 521
🏁 Script executed:
# Find useMessagePartByPartIdx implementation
rg "useMessagePartByPartIdx" -A 10Repository: FranciscoMoretti/sparka
Length of output: 4308
🏁 Script executed:
# Look for imports from ai package and type definitions
head -50 components/message-parts.tsxRepository: FranciscoMoretti/sparka
Length of output: 1472
🏁 Script executed:
# Check if there's a types file or interface definitions for parts
fd -t f -e ts -e tsx | xargs grep -l "TextUIPart\|ReasoningUIPart" | head -5Repository: FranciscoMoretti/sparka
Length of output: 95
🏁 Script executed:
# Look at the store/state management for message parts
rg "const part = useMessagePartByPartIdx" -B 5 -A 15Repository: FranciscoMoretti/sparka
Length of output: 1896
🌐 Web query:
Vercel AI SDK TextUIPart ReasoningUIPart type definitions text property
💡 Result:
Short answer: In Vercel AI SDK v5 UI types, both TextUIPart and ReasoningUIPart expose a text property of type string. Typical definitions:
- TextUIPart: { type: 'text'; text: string }.[1]
- ReasoningUIPart: { type: 'reasoning'; text: string; providerMetadata?: Record<string, any> }.[1][2]
(Streaming stream parts carrying deltas use a separate stream shape whose value chunks are appended to the part's text property.)[2]
Sources:
[1] Vercel AI SDK v5 internals — UIMessage parts (TextUIPart / ReasoningUIPart).
[2] Vercel AI SDK v5 internals — streaming UIMessage stream parts (text/reasoning stream parts).
🌐 Web query:
Vercel AI SDK streaming edge cases undefined text property
💡 Result:
Short answer: this is a known race/edge-case in the AI SDK where a "finish-step" UI stream chunk (or a merged stream chunk) can arrive without a message-specific id, so the front-end parser tries to read message.text from an undefined message and throws "Cannot read properties of undefined (reading 'text')". The problem appears when merging multiple UIMessageStreams / multiplexing streams or when finish/step events are emitted out-of-order. See the open issue discussion. [1]
Workarounds until a fix/patch:
- Ensure every text-delta/finish chunk you emit has an id: when converting model → UI streams use generateMessageId that always returns an id for that stream. (Avoid emitting finish-step chunks without id.) [1]
- If you merge streams, avoid merging streams that may emit finish-step events without message ids; instead merge only fully-qualified UI streams or serialize writes so finish-step is emitted after id-bound chunks. [1]
- Defensive parsing on the client: guard access to message properties (optional chaining / null checks) in your readUIMessageStream/process-ui-message-stream wrapper so unknown chunks don’t crash the parser. Example: if (!msg) return; or use msg?.text. [1][2]
- As a temporary filter, drop/ignore finish-step chunks that lack an id on the server before sending them to the client (but note this may affect onFinish callbacks/status updates). [1]
- Where applicable, call modelStream.text (await final text) instead of relying on finish-step for downstream logic that expects a stable summary. [1]
References:
- Race condition / undefined text error discussion (issue describing TypeError when merging UIMessageStreams). [1]
- Migration / createUIMessageStream error reports showing the same "reading 'text' of undefined" failure and examples of setups that trigger it. [2]
Guard part.text to handle AI SDK streaming edge-cases.
The Vercel AI SDK can emit finish-step chunks without proper message IDs in certain streaming scenarios (especially when merging UIMessageStreams), which may cause part.text to be undefined at render time. TextMessagePart expects a string, so guard with nullish coalescing:
if (isTextUIPart(part)) {
- return <TextMessagePart isLoading={isLoading} text={part.text} />;
+ return <TextMessagePart isLoading={isLoading} text={part.text ?? ""} />;
}
+ return null;Additionally, PureMessagePart currently has no explicit return, falling through to undefined for unknown parts. Returning null makes the intent clearer.
📝 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 (isTextUIPart(part)) { | |
| return <TextMessagePart messageId={messageId} partIdx={partIdx} />; | |
| return <TextMessagePart isLoading={isLoading} text={part.text} />; | |
| } | |
| if (isTextUIPart(part)) { | |
| return <TextMessagePart isLoading={isLoading} text={part.text ?? ""} />; | |
| } | |
| return null; |
🤖 Prompt for AI Agents
In components/message-parts.tsx around lines 124 to 126, guard the
possibly-undefined part.text emitted by the Vercel AI SDK during streaming by
passing a safe string (e.g., use nullish coalescing on part.text) into
TextMessagePart so it always receives a string, and update PureMessagePart to
explicitly return null for unknown/unsupported part types instead of falling
through to undefined; implement these two small changes to ensure stable
rendering in streaming edge-cases.
| const [provisionalChatId, setProvisionalChatId] = useState<string>(() => | ||
| generateUUID() | ||
| ); | ||
| const [confirmedChatId, setConfirmedChatId] = useState<string | null>(null); | ||
|
|
||
| const { id, type } = useMemo(() => { | ||
| const result = resolveChatId({ | ||
| const { id, isPersisted } = useMemo(() => { | ||
| const fromPathname = resolveChatId({ | ||
| pathname, | ||
| provisionalId: provisionalChatIdRef.current, | ||
| provisionalId: provisionalChatId, | ||
| }); | ||
|
|
||
| // When the provisional chat was persisted, regenerate the ID for future new chats | ||
| if (result.shouldRefreshProvisionalId) { | ||
| provisionalChatIdRef.current = generateUUID(); | ||
| /** | ||
| * Precedence (no navigation, no router writes): | ||
| * 1) If the URL already points at a chat (/chat/:id, /project/:p/chat/:id, /share/:id), | ||
| * that ID wins and is always persisted. | ||
| * 2) Otherwise, if the server confirms a persisted chat id during the current interaction, | ||
| * use that as the active id (still without navigating). | ||
| * 3) Otherwise, fall back to the provisional id (not persisted). | ||
| */ | ||
| if (fromPathname.isPersisted) { | ||
| return { id: fromPathname.id, isPersisted: true }; | ||
| } | ||
| if (confirmedChatId) { | ||
| return { id: confirmedChatId, isPersisted: true }; | ||
| } | ||
| return { | ||
| id: fromPathname.id, | ||
| isPersisted: false, | ||
| }; | ||
| }, [pathname, provisionalChatId, confirmedChatId]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug risk: confirmedChatId can override future provisional routes after navigation.
As written, once confirmedChatId is set, any non-persisted pathname will prefer it—even if the user navigates to a different provisional context—potentially fetching the wrong chat. Consider scoping confirmation to the current provisional ID.
type ChatIdContextType = {
id: string;
/**
* True when the current chatId is known to be persisted server-side (i.e. safe to query).
* For existing chats, this is true immediately. For provisional chats, this flips when the
* server confirms it saved the first user message.
*/
isPersisted: boolean;
confirmChatId: (chatId: string) => void;
refreshChatID: () => void;
};
export function ChatIdProvider({ children }: { children: ReactNode }) {
const pathname = usePathname();
const [provisionalChatId, setProvisionalChatId] = useState<string>(() =>
generateUUID()
);
- const [confirmedChatId, setConfirmedChatId] = useState<string | null>(null);
+ const [confirmed, setConfirmed] = useState<{
+ provisionalId: string;
+ chatId: string;
+ } | null>(null);
const { id, isPersisted } = useMemo(() => {
const fromPathname = resolveChatId({
pathname,
provisionalId: provisionalChatId,
});
if (fromPathname.isPersisted) {
return { id: fromPathname.id, isPersisted: true };
}
- if (confirmedChatId) {
- return { id: confirmedChatId, isPersisted: true };
+ if (confirmed && confirmed.provisionalId === fromPathname.id) {
+ return { id: confirmed.chatId, isPersisted: true };
}
return {
id: fromPathname.id,
isPersisted: false,
};
- }, [pathname, provisionalChatId, confirmedChatId]);
+ }, [pathname, provisionalChatId, confirmed]);
const confirmChatId = useCallback((chatId: string) => {
- setConfirmedChatId(chatId);
- }, []);
+ setConfirmed({ provisionalId: provisionalChatId, chatId });
+ }, [provisionalChatId]);
const refreshChatID = useCallback(() => {
setProvisionalChatId(generateUUID());
- setConfirmedChatId(null);
+ setConfirmed(null);
}, []);Also applies to: 62-69
🤖 Prompt for AI Agents
In providers/chat-id-provider.tsx around lines 31 to 60 (and similarly 62 to
69), confirmedChatId is applied unconditionally which lets a previously
confirmed ID override new provisional IDs after navigation; restrict
confirmation to the current provisional context by associating the confirmed
chat with the provisionalId that produced it (e.g., store confirmed as
{provisionalId, confirmedId}) or only accept confirmedChatId when it equals the
current provisionalChatId; update the useMemo and any follow-up checks to verify
provisionalId matches before returning isPersisted true so navigations to a
different provisional route won't be overridden.
- Updated parameter extraction in `ProjectPage` and `ProjectChatPage` to utilize `ParamsOf` for better type safety and clarity. - Enhanced project ID handling in `ProjectPage` to ensure correct usage of the project object. - Added a return statement in `PureMessagePart` to prevent potential rendering issues.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Refactor
Tests
✏️ Tip: You can customize this high-level summary in your review settings.