Skip to content

Conversation

@FranciscoMoretti
Copy link
Owner

@FranciscoMoretti FranciscoMoretti commented Dec 13, 2025

  • Refactor Chat Components for Improved State Management and Routing
  • Refactor Chat and Project Components for Enhanced Structure and State Management
  • log
  • Refactor ProjectPage to Use Suspense for Data Fetching
  • Refactor Chat ID Management in DeleteChatDialog and ChatIdProvider
  • Refactor Chat ID Handling in ChatIdProvider and Tests

Summary by CodeRabbit

Release Notes

New Features

  • Persistent chat confirmation and shared-route handling to keep chats consistent across sessions.

Bug Fixes

  • Improved chat ID confirmation and persistence behavior for non-anonymous interactions.

Refactor

  • Streamlined chat routing and navigation; simplified component composition and message rendering for more predictable UI.
  • Centralized shared vs. persisted chat logic and adjusted data-fetch behavior accordingly.

Tests

  • Updated tests to reflect new persisted/shared semantics.

✏️ Tip: You can customize this high-level summary in your review settings.

- 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.
@cursor
Copy link

cursor bot commented Dec 13, 2025

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.

@vercel
Copy link

vercel bot commented Dec 13, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
sparka Ready Ready Preview, Comment Dec 14, 2025 8:48am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Replaces client-side chat routers with direct page components, centralizes chat ID semantics around an isPersisted flag with server-confirmation, updates many components/hooks to use the new provider APIs, and emits a transient initial data-chatConfirmed event on non-anonymous chat streams.

Changes

Cohort / File(s) Summary
Route pages / entry points
app/(chat)/page.tsx, app/(chat)/chat/[id]/page.tsx, app/(chat)/project/[projectId]/page.tsx, app/(chat)/share/[id]/page.tsx
Replaced router components with direct page components (e.g., ChatHome, ChatPage, ProjectChatPage, SharedChatPage), added HydrateClient/Suspense where applicable, and added TRPC prefetching for shared route.
Removed router component
app/(chat)/chat-page-router.tsx
Deleted the client-side router component that performed type-based conditional rendering.
Chat page refactor
app/(chat)/chat/[id]/chat-page.tsx, app/(chat)/chat-home.tsx
ChatPage and ChatHome now obtain chat id from provider hooks (no id prop). ChatPage split into wrapper and internal content; initial state moved to useChatSystemInitialState.
Project pages refactor
app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx, app/(chat)/project/[projectId]/page.tsx, app/(chat)/project/[projectId]/project-page.tsx
Renamed router components to direct page components, replaced pathname regex parsing with useParams() for projectId, simplified error handling.
Streaming endpoint
app/(chat)/api/chat/route.ts
Emits an initial transient SSE data-chatConfirmed message (UUID + chatId) on non-anonymous streaming paths before starting the core chat agent.
Chat ID provider & resolution
providers/chat-id-provider.tsx, providers/resolve-chat-id.ts, providers/resolve-chat-id.test.ts
Introduced isPersisted discriminated union (ProvisionalChatId
Message / message-part changes
components/message-parts.tsx, components/part/text-message-part.tsx
Simplified TextMessagePart to accept { text, isLoading } directly; removed store-based part lookup; added fallback null for unmatched cases.
UI components adapted to new ID semantics
components/delete-chat-dialog.tsx, components/header-breadcrumb.tsx, components/sidebar-chat-item.tsx, components/multimodal-input.tsx
Replaced type-based logic with useChatId()/isPersisted and useIsSharedRoute(); simplified navigation and gating conditions.
Providers / message tree updates
providers/message-tree-provider.tsx, providers/message-tree-provider.*
Replaced type checks with isPersisted and isShared (via useIsSharedRoute); updated cache key selection and effect dependencies.
Hooks added / updated
hooks/use-chat-system-initial-state.ts, hooks/use-is-shared-route.ts, hooks/chat-sync-hooks.ts
Added useChatSystemInitialState (normalizes messages and infers initial tool), useIsSharedRoute (pathname regex), and updated chat-sync hooks to use isPersisted, isShared, and call confirmChatId() on save where appropriate.
Custom UI types & minor cleanup
lib/ai/types.ts, lib/ai/core-chat-agent.ts
Added chatConfirmed to CustomUIDataTypes; removed a debug console.log in core agent.
Data stream handler (no-op changes)
components/data-stream-handler.tsx
Added commented/disabled branch and commented import for handling data-chatConfirmed; no active behavior change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

Areas to inspect closely:

  • providers/resolve-chat-id.ts — new discriminated unions and route-resolution changes.
  • providers/chat-id-provider.tsxconfirmChatId() state transitions and isPersisted logic.
  • app/(chat)/api/chat/route.ts — timing and format of initial data-chatConfirmed SSE emission.
  • hooks/chat-sync-hooks.ts — enabled conditions, confirmChatId() usage, and cache invalidation.
  • components/part/text-message-part.tsx and components/message-parts.tsx — interface changes for parts rendering.

Possibly related PRs

Poem

🐰
I hopped through routes both new and old,
Confirmed the IDs in my little hold.
Provisional no more, persisted and bright,
Streams now whisper "chatConfirmed" at first light.
Hooray — a carrot for every tidy flight! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title 'navigation simplification' is too vague and generic. It does not clearly convey the specific, substantial refactoring work described in the objectives (routing architecture changes, state management improvements, component restructuring). Consider a more descriptive title that captures the primary change, such as 'Refactor routing architecture to use route params instead of router component pattern' or 'Simplify chat routing with isPersisted state flag.'
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a356c1 and cc6121f.

📒 Files selected for processing (4)
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx (2 hunks)
  • app/(chat)/project/[projectId]/project-page.tsx (2 hunks)
  • components/message-parts.tsx (2 hunks)
  • hooks/use-is-shared-route.ts (1 hunks)

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 projectId should 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 ChatSystem accepts undefined for projectId.

Also applies to: 66-66

components/multimodal-input.tsx (1)

86-86: Clarify the intent of calling useChatId() 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-chatConfirmed but the actual action is commented out. While the TODO explains the reason, this leaves a no-op code path. Consider either:

  1. Removing this block entirely until the dependency is resolved, or
  2. Opening an issue to track re-enabling this feature

Would you like me to help open an issue to track re-enabling the confirmChatId functionality once chat threads are independent of the getChatById query cache?

app/(chat)/chat/[id]/page.tsx (1)

1-3: Consider adding a Suspense fallback to avoid blank UI during fetch.
<Suspense> without fallback will render nothing while the subtree suspends; a small skeleton/loader would improve perceived performance. Also worth sanity-checking that chatId prefetched from params always matches the useChatId()-resolved ID used by ChatPage.

Also applies to: 18-20

providers/message-tree-provider.tsx (1)

42-110: Avoid recomputing and JSON.stringify-comparing query keys inside the cache subscriber.
You already compute queryKey (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) or hashKey(queryKey) from TanStack Query internals—depending on what your event payload exposes.

components/header-breadcrumb.tsx (1)

27-57: Query enablement logic matches isShared/isPersisted; consider a non-null loading state if breadcrumb flicker matters.
Right now if (!(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

📥 Commits

Reviewing files that changed from the base of the PR and between 8bfb11e and 3a356c1.

📒 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.ts
  • app/(chat)/api/chat/route.ts
  • app/(chat)/project/[projectId]/page.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • hooks/use-is-shared-route.ts
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/resolve-chat-id.ts
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • hooks/use-chat-system-initial-state.ts
  • hooks/chat-sync-hooks.ts
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • providers/resolve-chat-id.test.ts
  • app/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Use Shadcn UI components from components/ui folder with config in components.json

Co-locate small form schemas with the component; extract only when reused across modules

Files:

  • lib/ai/types.ts
  • app/(chat)/api/chat/route.ts
  • app/(chat)/project/[projectId]/page.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • hooks/use-is-shared-route.ts
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/resolve-chat-id.ts
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • hooks/use-chat-system-initial-state.ts
  • hooks/chat-sync-hooks.ts
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • providers/resolve-chat-id.test.ts
  • app/(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}: Avoid any at all cost in TypeScript. The types should work or they indicate a problem.
Never use as "any" or as unknown as to solve/avoid type errors in TypeScript. The types should work or they indicate a problem.
Avoid using as to 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.ts
  • app/(chat)/api/chat/route.ts
  • app/(chat)/project/[projectId]/page.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • hooks/use-is-shared-route.ts
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/resolve-chat-id.ts
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • hooks/use-chat-system-initial-state.ts
  • hooks/chat-sync-hooks.ts
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • providers/resolve-chat-id.test.ts
  • app/(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
Prefer unknown over any when 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
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax 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
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks 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 use eval() or assign directly to document.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.ts
  • app/(chat)/api/chat/route.ts
  • app/(chat)/project/[projectId]/page.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • hooks/use-is-shared-route.ts
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/resolve-chat-id.ts
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • hooks/use-chat-system-initial-state.ts
  • hooks/chat-sync-hooks.ts
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • providers/resolve-chat-id.test.ts
  • app/(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.ts
  • app/(chat)/api/chat/route.ts
  • app/(chat)/project/[projectId]/page.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • hooks/use-is-shared-route.ts
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/resolve-chat-id.ts
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • hooks/use-chat-system-initial-state.ts
  • hooks/chat-sync-hooks.ts
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • providers/resolve-chat-id.test.ts
  • app/(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.ts for 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.ts
  • app/(chat)/project/[projectId]/page.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/trpc-patterns.mdc)

**/*.tsx: In TRPC frontend components, use the useTRPC() hook from @/trpc/react to access TRPC client instance
In TRPC frontend components, import useMutation, useQuery, useQueryClient from @tanstack/react-query
In TRPC frontend queries, use the pattern: useQuery({ ...trpc.router.procedure.queryOptions(), enabled: !!condition })
In TRPC frontend mutations, invalidate related queries using queryClient.invalidateQueries() in the onSuccess callback
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.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • app/(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 the key prop 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
Avoid dangerouslySetInnerHTML unless absolutely necessary
Use proper image components (e.g., Next.js <Image>) over <img> tags
Use Next.js <Image> component for images
Use next/head or 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 of React.forwardRef in React 19+

Files:

  • app/(chat)/project/[projectId]/page.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • app/(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.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/page.tsx
**/*.{tsx,jsx,html,vue,svelte,astro}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

Add rel="noopener" when using target="_blank" on links

Files:

  • app/(chat)/project/[projectId]/page.tsx
  • components/sidebar-chat-item.tsx
  • app/(chat)/chat-home.tsx
  • app/(chat)/project/[projectId]/project-page.tsx
  • components/part/text-message-part.tsx
  • components/delete-chat-dialog.tsx
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • components/multimodal-input.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • providers/chat-id-provider.tsx
  • components/data-stream-handler.tsx
  • components/message-parts.tsx
  • app/(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 inside it() or test() blocks
Avoid done callbacks in async tests - use async/await instead
Don't use .only or .skip in committed code
Keep test suites reasonably flat - avoid excessive describe nesting

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.ts
  • app/(chat)/api/chat/route.ts
  • components/sidebar-chat-item.tsx
  • providers/message-tree-provider.tsx
  • app/(chat)/chat/[id]/chat-page.tsx
  • app/(chat)/page.tsx
  • components/header-breadcrumb.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • hooks/use-chat-system-initial-state.ts
  • hooks/chat-sync-hooks.ts
  • providers/resolve-chat-id.test.ts
  • app/(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.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • app/(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.tsx
  • hooks/use-is-shared-route.ts
  • components/header-breadcrumb.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • hooks/chat-sync-hooks.ts
  • app/(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.tsx
  • app/(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.tsx
  • hooks/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.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(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.tsx
  • providers/message-tree-provider.tsx
  • components/header-breadcrumb.tsx
  • app/(chat)/share/[id]/page.tsx
  • app/(chat)/chat/[id]/page.tsx
  • app/(chat)/project/[projectId]/chat/[chatId]/project-chat-page.tsx
  • 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 mutations, invalidate related queries using `queryClient.invalidateQueries()` in the `onSuccess` callback

Applied to files:

  • providers/message-tree-provider.tsx
  • hooks/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. The chatHref is correctly typed as a union of both supported routes (/chat/:id and /project/:projectId/chat/:id) and properly constructed based on the chat type.

Using preventDefault with router.push on 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 ProjectChatPageRouter to ProjectChatPage aligns 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 ProjectChatPageRouter to ProjectChatPage is 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 ProjectPageRouter to ProjectPage and the simplified error condition from a complex check to !project both 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 ProjectPageRouter to ProjectPage is consistent with the component rename in project-page.tsx. Prefetching logic is preserved correctly.

Also applies to: 17-17

lib/ai/types.ts (1)

104-106: LGTM!

The new chatConfirmed type 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 the Boolean() wrapper safely handles the null pathname case.

app/(chat)/chat-home.tsx (1)

8-9: LGTM!

The transition from prop-based id to hook-based retrieval via useChatId() 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 HydrateClient wrapper correctly provides the hydrated query state to SharedChatPage. 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:

  1. Waits for session loading to complete before redirecting (!isPending)
  2. Redirects anonymous users from persisted chats
  3. Returns notFound() for non-persisted chat IDs on this route
hooks/use-chat-system-initial-state.ts (1)

7-56: Nice extraction; consider tightening the tool-part shape checks (and confirm findLast support).
If parts/output are not strongly typed, adding a small type guard (or narrowing part.output before reading .format) will prevent silent runtime undefined assumptions. Also confirm your TS/target runtime supports Array.prototype.findLast in all environments you ship to.

app/(chat)/api/chat/route.ts (1)

472-480: Early data-chatConfirmed emission 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 via app/(chat)/api/chat/route.ts.

components/delete-chat-dialog.tsx (1)

30-62: Good alignment with new isPersisted semantics.
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 new resolveChatId discriminants (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 isPersisted flag with isShared route detection is a cleaner separation of concerns. The enabled condition correctly requires:

  1. A valid chatId
  2. The chat to be persisted (safe to query)
  3. Either shared route access OR authenticated user

410-420: LGTM - Proper conditional query selection.

The useDocuments hook correctly:

  1. Uses isShared to select between public and private document queries
  2. Gates the query with (isShared || !!session?.user) matching the pattern in useGetChatMessagesQueryOptions

26-31: LGTM - Empty string fallback is safe.

Using projectId ?? "" is appropriate since the query is gated by !!projectId in the enabled condition (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 confirmChatId being called before query cache invalidation is unfounded. React state updates (setConfirmedChatId) are synchronous, so isPersisted is set to true immediately when confirmChatId(chatId) executes on line 327. By the time qc.invalidateQueries runs (even the non-awaited call on line 328), the state is already updated and any query checking isPersisted will see the correct value.

The TODO comment about moving this to data-stream-handler is 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 isPersisted as a literal type discriminant (false for provisional, true for 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 persisted
  • PersistedChatId: 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_PATTERN at 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 provisionalId parameter ensures a consistent ID is used for new/provisional chats.


36-44: No further action needed. The migration from type: "shared" to type: "chat" for shared routes is complete and properly verified:

  • resolveChatId only returns type: "chat" or type: "provisional" (no "shared" type exists)
  • All shared route consumers already use useIsSharedRoute() for access pattern determination
  • The consumer in chat-id-provider.tsx only accesses isPersisted, never checking the type field
  • No remaining references to type === "shared" exist in the codebase
components/part/text-message-part.tsx (1)

6-11: Verify Response/Streamdown prop contract for mode + isAnimating.
Since Response forwards props to Streamdown, make sure mode and isAnimating are valid/typed props (and won’t end up as unknown DOM attributes if Streamdown ultimately renders a host element).

export function ProjectPageRouter() {
const pathname = usePathname();
export function ProjectPage() {
const { projectId } = useParams<{ projectId: string }>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 124 to 126
if (isTextUIPart(part)) {
return <TextMessagePart messageId={messageId} partIdx={partIdx} />;
return <TextMessagePart isLoading={isLoading} text={part.text} />;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "message-parts.tsx" -type f

Repository: FranciscoMoretti/sparka

Length of output: 97


🏁 Script executed:

wc -l components/message-parts.tsx

Repository: FranciscoMoretti/sparka

Length of output: 99


🏁 Script executed:

sed -n '110,140p' components/message-parts.tsx

Repository: 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_modules

Repository: FranciscoMoretti/sparka

Length of output: 49


🏁 Script executed:

# Find isTextUIPart definition
rg "isTextUIPart" -A 3 -B 1

Repository: FranciscoMoretti/sparka

Length of output: 559


🏁 Script executed:

# Check the type definition for part.text
rg "type.*Part\|interface.*Part" -A 5 | head -50

Repository: FranciscoMoretti/sparka

Length of output: 49


🏁 Script executed:

sed -n '100,176p' components/message-parts.tsx

Repository: FranciscoMoretti/sparka

Length of output: 1830


🏁 Script executed:

# Find TextMessagePart component
rg "function TextMessagePart|const TextMessagePart|export.*TextMessagePart" -A 10

Repository: 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 2

Repository: 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.tsx

Repository: FranciscoMoretti/sparka

Length of output: 521


🏁 Script executed:

# Find useMessagePartByPartIdx implementation
rg "useMessagePartByPartIdx" -A 10

Repository: FranciscoMoretti/sparka

Length of output: 4308


🏁 Script executed:

# Look for imports from ai package and type definitions
head -50 components/message-parts.tsx

Repository: 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 -5

Repository: FranciscoMoretti/sparka

Length of output: 95


🏁 Script executed:

# Look at the store/state management for message parts
rg "const part = useMessagePartByPartIdx" -B 5 -A 15

Repository: 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.

Suggested change
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.

Comment on lines +31 to +60
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]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.
@FranciscoMoretti FranciscoMoretti merged commit 6606156 into main Dec 14, 2025
1 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants