-
Notifications
You must be signed in to change notification settings - Fork 51
Daily branch 2025 09 01 #18
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughAdds token-count enforcement for paste and submit, exports MAX_TOKENS and countInputTokens, implements per-message feedback UI/hook and Convex persistence/schema, centralizes ownership checks in messages, adds an interactive setup script and env example reorder, updates README and dependencies, and refines rate-limit/Toaster/upgrade UI. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant CI as ChatInput
participant T as token-utils
participant Toast as Toaster
rect rgb(249,250,255)
note right of CI #DDEBF7: Paste validation
U->>CI: Paste text
CI->>T: countInputTokens(pasteText)
T-->>CI: tokenCount
alt tokenCount > MAX_TOKENS
CI->>Toast: show error toast ("Paste too long")
CI-->>U: prevent paste
else
CI-->>U: allow paste (handlePasteEvent)
end
end
sequenceDiagram
autonumber
participant U as User
participant H as useChatHandlers
participant T as token-utils
participant Toast as Toaster
participant API as Chat API
rect rgb(245,255,245)
note right of H #E6F4EA: Submit validation
U->>H: Submit message (+files)
H->>T: countInputTokens(input, uploadedFiles)
T-->>H: totalTokens
alt totalTokens > MAX_TOKENS
H->>Toast: show error toast ("Message is too long")
H-->>U: abort submit
else
H->>API: create/send chat/message (includes userId)
API-->>H: ack/stream
H-->>U: success
end
end
sequenceDiagram
autonumber
participant Dev as Developer
participant S as scripts/setup.ts
participant C as Convex CLI
participant FS as Filesystem
Dev->>S: pnpm run setup
S->>Dev: prompt for API keys & validate
S->>C: run convex dev --configure=new --project=NAME
C-->>S: generates .env.local (with NEXT_PUBLIC_CONVEX_URL)
S->>FS: write consolidated .env.local (keys + WorkOS cookie + comments)
S-->>Dev: print next steps
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (9)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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 (8)
.env.local.example (1)
55-55: Standardize base URL (no trailing slash) + fix EOF newline.Avoid double slashes when composing paths and satisfy dotenv-linter EndingBlankLine.
-# NEXT_PUBLIC_BASE_URL=http://localhost:3000/ +# NEXT_PUBLIC_BASE_URL=http://localhost:3000 +README.md (1)
42-52: Avoid duplicate installs in the flow.Right now users run
pnpm installand thenpnpm run setup, which runs install again. Either remove the “Install dependencies” step from the README or drop the install from the setup script in package.json.I’ve proposed a package.json change below to remove the second install and pin tsx; alternatively, keep README and keep install inside setup—choose one.
package.json (1)
11-13: Make setup faster and deterministic: use local tsx, not npx, and drop the extra install.This avoids a second install and pins the runner.
- "format:check": "prettier --check .", - "setup": "pnpm install && npx tsx scripts/setup.ts" + "format:check": "prettier --check .", + "setup": "pnpm tsx scripts/setup.ts"Also add tsx to devDependencies:
"devDependencies": { @@ - "typescript": "^5" + "typescript": "^5", + "tsx": "^4" }scripts/setup.ts (1)
77-83: Validate WorkOS Client ID.Prompt currently accepts empty/invalid IDs; add a simple format check.
async function getWorkOSClientId(): Promise<string> { console.log(`\n${chalk.bold("Getting WorkOS Client ID")}`); console.log( 'You can find your WorkOS Client ID in the dashboard under the "Quick start" section: https://dashboard.workos.com/get-started', ); - return await question("Enter your WorkOS Client ID: "); + const id = await question("Enter your WorkOS Client ID: "); + if (!id.trim().startsWith("client_")) { + console.log(chalk.red("Invalid WorkOS Client ID format")); + console.log('Client IDs should start with "client_"'); + return await getWorkOSClientId(); + } + return id.trim(); }app/components/ChatInput.tsx (1)
90-112: Prevent “paste passes, submit blocks” by counting existing input + files; add text/plain fallbackRight now you only validate the pasted text in isolation. Consider validating the combined size of current textarea content + pasted text + uploaded files to avoid surprising users at submit time. Also add a fallback for "text/plain" to improve cross-browser support. Update deps so the listener sees fresh files.
useEffect(() => { const handlePaste = async (e: ClipboardEvent) => { // Only handle paste if the textarea is focused if (textareaRef.current === document.activeElement) { - // Check if pasting text content - const clipboardData = e.clipboardData; + // Check if pasting text content + const clipboardData = e.clipboardData; if (clipboardData) { - const pastedText = clipboardData.getData("text"); + const pastedText = + clipboardData.getData("text") || clipboardData.getData("text/plain"); if (pastedText) { - // Check token limit for the pasted text only - const tokenCount = countInputTokens(pastedText, []); - if (tokenCount > MAX_TOKENS) { + // Validate combined size: current input + pasted text + files + const currentInput = textareaRef.current?.value ?? ""; + const combinedTokenCount = countInputTokens( + currentInput + pastedText, + uploadedFiles, + ); + if (combinedTokenCount > MAX_TOKENS) { e.preventDefault(); - toast.error("Content is too long to paste", { - description: `The content you're trying to paste is too large (${tokenCount.toLocaleString()} tokens). Please copy a smaller amount.`, + toast.error("Content is too long to paste", { + description: `Pasting this would exceed the limit (${combinedTokenCount.toLocaleString()} tokens including existing input${ + uploadedFiles.length ? " and files" : "" + }). Please paste less.`, }); return; } } } const filesProcessed = await handlePasteEvent(e); // If files were processed, the event.preventDefault() is already called // in handlePasteEvent, so no additional action needed here } }; document.addEventListener("paste", handlePaste); return () => { document.removeEventListener("paste", handlePaste); }; - }, [handlePasteEvent]); + }, [handlePasteEvent, uploadedFiles]);Also applies to: 119-123
lib/token-utils.ts (2)
4-4: Exporting MAX_TOKENS is good; consider single source of truth configLooks good. Consider reading this from a shared config/env to keep client and server aligned (e.g., NEXT_PUBLIC_MAX_INPUT_TOKENS) if model/context changes.
126-139: Guard against malformed file token metadata (negative/NaN)Clamp file token contributions to non-negative to avoid accidental underflow from bad metadata.
export const countInputTokens = ( input: string, uploadedFiles: Array<{ tokens?: number }> = [], ): number => { const textTokens = countTokens(input); - const fileTokens = uploadedFiles.reduce( - (total, file) => total + (file.tokens || 0), - 0, - ); + const fileTokens = uploadedFiles.reduce((total, file) => { + const t = Number.isFinite(file.tokens) ? (file.tokens as number) : 0; + return total + Math.max(0, t); + }, 0); return textTokens + fileTokens; };app/hooks/useChatHandlers.ts (1)
66-74: Avoid false positives: count tokens on trimmed input and only valid filesUse the same valid-file criteria you later apply when sending to prevent over-counting (e.g., files not fully uploaded). Also match the actual text sent by counting on trimmed input.
- const tokenCount = countInputTokens(input, uploadedFiles); - if (tokenCount > MAX_TOKENS) { - const hasFiles = uploadedFiles.length > 0; + const trimmed = input.trim(); + const filesForCounting = uploadedFiles.filter( + (f) => f.uploaded && f.url && f.fileId, + ); + const tokenCount = countInputTokens(trimmed, filesForCounting); + if (tokenCount > MAX_TOKENS) { + const hasFiles = filesForCounting.length > 0; toast.error("Message is too long", { description: `Your message is too large (${tokenCount.toLocaleString()} tokens). Please make it shorter${ - hasFiles ? " or remove some files" : "" + hasFiles ? " or remove some files" : "" }.`, }); return; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
.env.local.example(2 hunks)README.md(1 hunks)app/components/ChatInput.tsx(2 hunks)app/hooks/useChatHandlers.ts(2 hunks)lib/token-utils.ts(2 hunks)package.json(3 hunks)scripts/setup.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
**/*.{ts,tsx}: Use Id helper type from ./_generated/dataModel to type document IDs (e.g., Id<'users'>) instead of string
When defining Record types, specify key and value types matching validators (e.g., Record<Id<'users'>, string>)
Be strict with types for document IDs; prefer Id<'table'> over string in function args and variables
Useas constfor string literals in discriminated unions
Declare arrays with explicit generic type: const arr: Array = [...]
Declare records with explicit generic types: const record: Record<KeyType, ValueType> = {...}
Files:
scripts/setup.tsapp/hooks/useChatHandlers.tsapp/components/ChatInput.tsxlib/token-utils.ts
package.json
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
Add @types/node to devDependencies when using Node.js built-in modules
Files:
package.json
🧠 Learnings (1)
📚 Learning: 2025-08-27T12:38:06.615Z
Learnt from: RostyslavManko
PR: hackerai-tech/hackerai#14
File: convex/chats.ts:4-24
Timestamp: 2025-08-27T12:38:06.615Z
Learning: Convex functions with serviceKey parameters are designed for backend service-to-service communication and don't require ctx.auth.getUserIdentity() checks. The serviceKey validation against process.env.CONVEX_SERVICE_ROLE_KEY provides sufficient authentication for these backend operations.
Applied to files:
.env.local.example
🧬 Code graph analysis (2)
app/hooks/useChatHandlers.ts (1)
lib/token-utils.ts (2)
countInputTokens(129-139)MAX_TOKENS(4-4)
app/components/ChatInput.tsx (1)
lib/token-utils.ts (2)
countInputTokens(129-139)MAX_TOKENS(4-4)
🪛 dotenv-linter (3.3.0)
.env.local.example
[warning] 55-55: [EndingBlankLine] No blank line at the end of the file
(EndingBlankLine)
🔇 Additional comments (5)
.env.local.example (1)
8-10: Clarify Service Role Key usage (server-to-server only)."for securing public functions" is misleading for Convex. Service Role Keys are for server-to-server calls (e.g., Next.js API routes) and must never be exposed client-side.
Apply:
-# Convex Service Role Key (Required for securing public functions) -# Generate a secure random string and add it to your Convex environment variables +# Convex Service Role Key (server-to-server only; DO NOT expose to the client) +# Used when calling Convex internal functions from Next.js API routes or server components. Optional for local dev. # CONVEX_SERVICE_ROLE_KEY=README.md (1)
26-29: Clarify if both OpenRouter and OpenAI are required or if either is sufficient.If either provider is acceptable, say “OpenRouter or OpenAI” and mark the other optional to reduce friction.
Suggested wording: “You’ll need an OpenRouter or OpenAI account (at least one), plus Convex and WorkOS accounts.”
package.json (1)
65-68: Good: @types/node present as required by guidelines.Meets the “add @types/node when using Node built-ins” rule.
app/components/ChatInput.tsx (1)
27-29: LGTM: shared token utils + toast importCentralizing token constants and using a common counter is the right move for consistency.
app/hooks/useChatHandlers.ts (1)
7-9: LGTM: shared token utils + toast importConsistent usage across paste and submit paths.
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: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
package.json (1)
15-64: Fix peer dependency mismatches and lock toolchain
- Unmet peers:
openai@5.16.0,@browserbasehq/stagehand@1.14.0, andzod-to-json-schema@3.24.6requirezod@^3.xbutzod@4.1.5is installed; add apnpm.overridesentry or align versions.chalk@5.xis ESM-only—replace anyrequire('chalk')withimport chalk from 'chalk'.- Pin Node engine and sync
packageManagerversion for CI consistency."private": true, "engines": { "node": ">=18.18.0" }, - "packageManager": "pnpm@9", + "packageManager": "pnpm@10.15.0",
🧹 Nitpick comments (1)
package.json (1)
11-12: Prefer pnpm dlx for setup script and confirm ESM chalk import
- In package.json, replace
"setup": "pnpm install && npx tsx scripts/setup.ts"
with
"setup": "pnpm install && pnpm dlx tsx scripts/setup.ts".scripts/setup.tsalready usesimport chalk from "chalk"and contains norequire('chalk').
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (1)
package.json(3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
package.json
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
Add @types/node to devDependencies when using Node.js built-in modules
Files:
package.json
🔇 Additional comments (1)
package.json (1)
72-72: eslint-config-next matches Next version — LGTM.Version parity with next 15.5.2 is correct.
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: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/rate-limit.ts (1)
59-71: Bug: error.type mismatches UI check ("rate_limit" vs "rate_limit:chat")MessageErrorState gates the Upgrade button on
error.type === "rate_limit", but here you throw"rate_limit:chat". The button and rate-limit copy won’t render. Fix either here or in the UI.Option A (normalize type here):
- throw new ChatSDKError("rate_limit:chat", cause); + throw new ChatSDKError("rate_limit", cause);and in the catch:
- "rate_limit:chat", + "rate_limit",Option B (preferred for flexibility): keep
"rate_limit:chat"and update the UI tostartsWith("rate_limit")(see suggested UI diff in MessageErrorState).
🧹 Nitpick comments (4)
lib/rate-limit.ts (2)
52-57: Polish copy: avoid comma splice and keep CTA only for free tierSmall UX nit: split the sentence to avoid a comma splice; keep the CTA for free users only (as you did).
- cause = `You've reached the current usage cap for HackerAI, please try again after ${timeString}.`; + cause = `You've reached the current usage cap for HackerAI. Please try again after ${timeString}.`; ... - cause = `You've reached the current usage cap for HackerAI, please try again after ${timeString}.\n\nUpgrade to Pro for higher usage limits and more features.`; + cause = `You've reached the current usage cap for HackerAI. Please try again after ${timeString}.\n\nUpgrade to Pro for higher usage limits and more features.`;
6-9: Type userId with Id<'users'> per guidelinesPer repo TS guidelines, prefer
Id<'users'>overstringfor document IDs.-export const checkRateLimit = async ( - userId: string, +import type { Id } from "./_generated/dataModel"; +export const checkRateLimit = async ( + userId: Id<'users'>, isPro: boolean, ): Promise<void> => {app/components/MessageErrorState.tsx (2)
38-46: A11y: indicate progress state on Upgrade buttonExpose loading state to assistive tech.
- <Button + <Button variant="default" size="sm" onClick={handleUpgrade} - disabled={upgradeLoading} + disabled={upgradeLoading} + aria-busy={upgradeLoading} + aria-live="polite" > {upgradeLoading ? "Loading..." : "Upgrade"} </Button>
16-17: Surface upgrade errors to users
useUpgradeexposesupgradeError; consider showing it under the buttons.- const { handleUpgrade, upgradeLoading } = useUpgrade(); + const { handleUpgrade, upgradeLoading, upgradeError } = useUpgrade();Add below the buttons block:
{upgradeError && ( <p className="text-destructive text-xs mt-2" aria-live="polite"> {upgradeError} </p> )}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (2)
app/components/MessageErrorState.tsx(3 hunks)lib/rate-limit.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
**/*.{ts,tsx}: Use Id helper type from ./_generated/dataModel to type document IDs (e.g., Id<'users'>) instead of string
When defining Record types, specify key and value types matching validators (e.g., Record<Id<'users'>, string>)
Be strict with types for document IDs; prefer Id<'table'> over string in function args and variables
Useas constfor string literals in discriminated unions
Declare arrays with explicit generic type: const arr: Array = [...]
Declare records with explicit generic types: const record: Record<KeyType, ValueType> = {...}
Files:
lib/rate-limit.tsapp/components/MessageErrorState.tsx
🧬 Code graph analysis (1)
app/components/MessageErrorState.tsx (2)
app/hooks/useUpgrade.ts (1)
useUpgrade(5-72)app/contexts/GlobalState.tsx (1)
useGlobalState(297-303)
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: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
convex/chats.ts (2)
93-93: Return type should be Doc Id, not string.
ctx.db.insert("chats", …)returnsId<"chats">. The return validator must bev.id("chats").Apply:
- returns: v.string(), + returns: v.id("chats"),
194-198: Missing return validator on query (violates Convex function contract).All Convex functions must declare
returns. Add a validator for the paginate result.Apply:
export const getUserChats = query({ args: { paginationOpts: paginationOptsValidator, }, + returns: v.object({ + page: v.array( + v.object({ + _id: v.id("chats"), + _creationTime: v.number(), + id: v.string(), + title: v.string(), + user_id: v.string(), + finish_reason: v.optional(v.string()), + todos: v.optional( + v.array( + v.object({ + id: v.string(), + content: v.string(), + status: v.union( + v.literal("pending"), + v.literal("in_progress"), + v.literal("completed"), + v.literal("cancelled"), + ), + }), + ), + ), + update_time: v.number(), + }), + ), + isDone: v.boolean(), + continueCursor: v.string(), + }),
🧹 Nitpick comments (3)
convex/chats.ts (2)
70-75: Prefer.unique()over.first()when querying by unique index.This better documents intent and throws if the index isn’t actually unique.
Apply (illustrative for each location):
- .first(); + .unique();Also applies to: 145-152, 253-260
247-251: Same-file internal call: add explicit return type to avoid TS circularity.Annotate the
ctx.runQuerycall’s return to satisfy the guideline.Apply:
- await ctx.runQuery(internal.chats.verifyChatOwnership, { + await (ctx.runQuery(internal.chats.verifyChatOwnership, { chatId: args.chatId, userId: user.subject, - }); + }) as Promise<null>);app/api/chat/route.ts (1)
70-72: Rate-limit moved post-initial-save — confirm desired side effects.This will create/update chats and persist the user message before rejecting on rate limit. If that’s unintended, pre-check rate limit right after resolving
{ userId, isPro }and again post-truncation if needed.Potential alternative:
- // Handle initial chat ... - const { isNewChat } = await handleInitialChatAndUserMessage({...}); - - // Check rate limit for the user - await checkRateLimit(userId, isPro); + // Early cheap guard before any DB writes + await checkRateLimit(userId, isPro); + + // Handle initial chat ... + const { isNewChat } = await handleInitialChatAndUserMessage({...}); + + // Optional: re-check here if truncation/flow affects accounting semantics + // await checkRateLimit(userId, isPro);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
app/api/chat/route.ts(1 hunks)convex/chats.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
**/*.{ts,tsx}: Use Id helper type from ./_generated/dataModel to type document IDs (e.g., Id<'users'>) instead of string
When defining Record types, specify key and value types matching validators (e.g., Record<Id<'users'>, string>)
Be strict with types for document IDs; prefer Id<'table'> over string in function args and variables
Useas constfor string literals in discriminated unions
Declare arrays with explicit generic type: const arr: Array = [...]
Declare records with explicit generic types: const record: Record<KeyType, ValueType> = {...}
Files:
app/api/chat/route.tsconvex/chats.ts
convex/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
convex/**/*.ts: Always use the new Convex function syntax (query/mutation/action objects with args/returns/handler) when defining Convex functions
When a function returns null, include returns: v.null() and return null explicitly
Use internalQuery/internalMutation/internalAction for private functions callable only by other Convex functions; do not expose sensitive logic via public query/mutation/action
Use query/mutation/action only for public API functions
Do not try to register functions via the api or internal objects
Always include argument and return validators for all Convex functions (query/internalQuery/mutation/internalMutation/action/internalAction)
In JS implementations, functions without an explicit return value implicitly return null
Use ctx.runQuery from queries/mutations/actions to call a query
Use ctx.runMutation from mutations/actions to call a mutation
Use ctx.runAction from actions to call an action
Only call an action from another action when crossing runtimes (e.g., V8 to Node); otherwise extract shared helper code
Minimize calls from actions to queries/mutations to avoid race conditions from splitting transactions
Pass FunctionReference values (from api/internal) to ctx.runQuery/ctx.runMutation/ctx.runAction; do not pass function implementations
When calling a function in the same file via ctx.run*, add an explicit return type annotation at the call site to avoid TS circularity
Use the generated api object for public functions and internal object for internal functions from convex/_generated/api.ts
Respect file-based routing for function references: e.g., convex/example.ts export f -> api.example.f; nested paths map to dot-separated namespaces
For paginated queries use paginationOptsValidator in args and .paginate(args.paginationOpts) on a query
v.bigint() is deprecated; use v.int64() for signed 64-bit integers
Use v.record(keys, values) for record-like data; v.map() and v.set() are not supported
For full-text search, use withSearchIndex("ind...
Files:
convex/chats.ts
🧠 Learnings (1)
📚 Learning: 2025-08-29T13:33:09.901Z
Learnt from: CR
PR: hackerai-tech/hackerai#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-08-29T13:33:09.901Z
Learning: Applies to convex/**/*.ts : In JS implementations, functions without an explicit return value implicitly return null
Applied to files:
convex/chats.ts
🧬 Code graph analysis (1)
app/api/chat/route.ts (1)
lib/rate-limit.ts (1)
checkRateLimit(6-73)
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: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
convex/chats.ts (1)
171-205: Add a returns validator and standardize empty pagination shape (continueCursor should be null).This query lacks a returns validator (required by our Convex guidelines) and returns an empty string for continueCursor in empty results. Align with paginate semantics and our other APIs by validating the response and using null for no cursor.
Apply:
export const getUserChats = query({ args: { paginationOpts: paginationOptsValidator, }, + returns: v.object({ + page: v.array(v.any()), + isDone: v.boolean(), + continueCursor: v.union(v.string(), v.null()), + pageStatus: v.optional(v.union(v.string(), v.null())), + splitCursor: v.optional(v.union(v.string(), v.null())), + }), handler: async (ctx, args) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) { return { page: [], isDone: true, - continueCursor: "", + continueCursor: null, }; } @@ console.error("Failed to get user chats:", error); return { page: [], isDone: true, - continueCursor: "", + continueCursor: null, }; } }, });convex/feedback.ts (1)
4-55: Verify chat ownership via internal query; don’t rely solely on message.user_id (backfill-safe).Older messages may not have user_id set; the current check would incorrectly block feedback. Use centralized ownership verification on the parent chat and only gate on message.user_id when it exists.
-import { mutation } from "./_generated/server"; +import { mutation } from "./_generated/server"; +import { internal } from "./_generated/api"; @@ - } else if (message.user_id !== user.subject) { - throw new Error( - "Unauthorized: User not allowed to give feedback for this message", - ); - } + } else if (message.user_id && message.user_id !== user.subject) { + throw new Error( + "Unauthorized: User not allowed to give feedback for this message", + ); + } else { + // Ensure the requester owns the chat for this message (covers legacy rows without user_id) + await ctx.runQuery(internal.messages.verifyChatOwnership, { + chatId: message.chat_id, + userId: user.subject, + }); + }Optional: If feedback is only intended for assistant messages, enforce it:
if (message.role !== "assistant") { throw new Error("Unsupported: Feedback is only allowed on assistant messages"); }convex/messages.ts (3)
126-137: Don’t swallow authorization errors; only treat “Chat not found” as empty.Right now any error (including unauthorized) returns empty results, masking auth issues. Distinguish not-found vs unauthorized.
- try { - await ctx.runQuery(internal.messages.verifyChatOwnership, { - chatId: args.chatId, - userId: user.subject, - }); - } catch (error) { - // Chat doesn't exist yet - return empty results (will be created on first message) - return { - page: [], - isDone: true, - continueCursor: "", - }; - } + try { + await ctx.runQuery(internal.messages.verifyChatOwnership, { + chatId: args.chatId, + userId: user.subject, + }); + } catch (error) { + if (error instanceof Error && error.message.includes("Chat not found")) { + return { + page: [], + isDone: true, + continueCursor: null, + }; + } + throw error; // Bubble Unauthorized and others + }
254-261: Avoid .filter(); add an index for (chat_id, role, _creationTime) and query it.Filters aren’t supported per our rules; this should be index-backed. Add a composite index and use it here.
Proposed code (requires schema update):
- const lastAssistantMessage = await ctx.db - .query("messages") - .withIndex("by_chat_id", (q) => q.eq("chat_id", args.chatId)) - .filter((q) => q.eq(q.field("role"), "assistant")) - .order("desc") - .first(); + const lastAssistantMessage = await ctx.db + .query("messages") + .withIndex("by_chat_role_created", (q) => + q.eq("chat_id", args.chatId).eq("role", "assistant") + ) + .order("desc") + .first();Schema addition (convex/schema.ts):
messages: defineTable({ // ... }).index("by_chat_role_created", ["chat_id", "role", "_creationTime"]);Want me to push the schema change in this PR?
352-359: Range query on _creationTime must be index-backed.Using gt("_creationTime", …) on an index that doesn’t include _creationTime will fail. Add a composite index (chat_id, _creationTime) and query it.
- const messages = await ctx.db - .query("messages") - .withIndex("by_chat_id", (q) => - q - .eq("chat_id", message.chat_id) - .gt("_creationTime", message._creationTime), - ) - .collect(); + const messages = await ctx.db + .query("messages") + .withIndex("by_chat_created", (q) => + q.eq("chat_id", message.chat_id).gt("_creationTime", message._creationTime) + ) + .collect();Schema addition:
messages: defineTable({ // ... }).index("by_chat_created", ["chat_id", "_creationTime"]);
🧹 Nitpick comments (16)
components/ui/sonner.tsx (3)
11-24: Memoize position/offset to avoid prop churn and unnecessary re-renders
getPositionProps()recreates new objects each render, which can trigger downstream re-layout/reposition inSonner. Memoize and add an explicit type for clarity.Apply:
- const getPositionProps = () => { - if (isMobile) { - return { - position: "top-center" as const, - offset: { top: 20 }, - }; - } - return { - position: "bottom-right" as const, - offset: { bottom: 140, right: 50 }, - }; - }; - - const positionProps = getPositionProps(); + const positionProps = useMemo<Pick<ToasterProps, "position" | "offset">>( + () => + isMobile + ? { position: "top-center" as const, offset: { top: 20 } } + : { position: "bottom-right" as const, offset: { bottom: 140, right: 50 } }, + [isMobile], + );Add import:
import { useMemo } from "react";Also applies to: 26-26
32-33: Confirm override intent for position/offsetWith the current spread order, any
position/offsetpassed via{...props}will override your computed values. If you want your responsive placement to win, move{...props}before them.className="toaster group" - position={positionProps.position} - offset={positionProps.offset} + {...props} + position={positionProps.position} + offset={positionProps.offset} @@ - {...props}Also applies to: 45-45
16-18: Use safe-area insets on mobile (components/ui/sonner.tsx lines 16–18 and 21–23)
Replaceoffset: { top: 20 },with
offset: { top: "calc(env(safe-area-inset-top) + 20px)" },to avoid collisions with iOS notches;
Toastersupports CSS length strings foroffsetcite12lib/utils/file-transform-utils.ts (1)
145-147: Good escalation to error; add timeout and size guard to prevent stalls/OOM on large PDFsRecommend adding an AbortController-based timeout and a max-bytes check in convertFileToBase64. Also include status/url in the error for quicker triage.
// Drop-in replacement for lines 16-30 (outside the selected range) // Adds: 15s timeout + 20MB size cap + richer errors async function convertFileToBase64(fileUrl: string): Promise<string | null> { if (!fileUrl) return null; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 15_000); try { const response = await fetch(fileUrl, { signal: controller.signal }); if (!response.ok) { console.error("convertFileToBase64: non-OK response", { url: fileUrl, status: response.status, statusText: response.statusText, }); return null; } const contentLength = response.headers.get("content-length"); const maxBytes = 20 * 1024 * 1024; // 20MB if (contentLength && Number(contentLength) > maxBytes) { console.error("convertFileToBase64: file too large", { url: fileUrl, bytes: Number(contentLength), }); return null; } const arrayBuffer = await response.arrayBuffer(); if (arrayBuffer.byteLength > maxBytes) { console.error("convertFileToBase64: file too large after fetch", { url: fileUrl, bytes: arrayBuffer.byteLength, }); return null; } return Buffer.from(arrayBuffer).toString("base64"); } catch (error) { console.error("Failed to convert file to base64", { url: fileUrl, error }); return null; } finally { clearTimeout(timeout); } }convex/schema.ts (3)
32-32: Prefer strong foreign keys for user_id if a users table existsIf you have a users table, model user_id as v.id("users") to enforce referential integrity; otherwise keep string if it’s an external auth ID.
- user_id: v.optional(v.string()), + user_id: v.optional(v.id("users")),
36-36: Index feedback_id for fast lookups and joinsIf you read messages by feedback_id (or join from feedback -> message), add an index.
}) .index("by_message_id", ["id"]) - .index("by_chat_id", ["chat_id"]), + .index("by_chat_id", ["chat_id"]) + .index("by_feedback_id", ["feedback_id"]),Also applies to: 39-41
52-55: Feedback table: consider ownership/audit fields and query indexesAdd user_id (or message_id) if you need ownership checks or reverse lookups, plus an index by type if filtering.
feedback: defineTable({ feedback_type: v.union(v.literal("positive"), v.literal("negative")), feedback_details: v.optional(v.string()), - }), + // Optional but recommended if you enforce ownership/analytics: + // user_id: v.id("users"), + // message_id: v.id("messages"), + }) + // Optional indexes based on usage: + // .index("by_feedback_type", ["feedback_type"]),types/chat.ts (1)
59-66: Align metadata enum with shared constants; keep it optional at usage sitesUsing a shared constant reduces drift; ensure downstream treats metadata as optional.
const FEEDBACK_TYPES = ["positive", "negative"] as const; export const messageMetadataSchema = z.object({ feedbackType: z.enum(FEEDBACK_TYPES), }); export type MessageMetadata = z.infer<typeof messageMetadataSchema>; export type ChatMessage = UIMessage<MessageMetadata>; // metadata remains optional on UIMessageapp/components/FeedbackInput.tsx (2)
14-26: Guard against double submit.Skip if already submitting to prevent rapid Enter presses from enqueuing multiple requests.
const handleSend = async () => { - if (!details.trim()) return; + if (isSubmitting || !details.trim()) return;
33-42: Optional: Don’t trigger send/cancel while submitting.Prevent key handlers during isSubmitting to avoid racey UX.
const handleKeyDown = (e: React.KeyboardEvent) => { + if (isSubmitting) return; if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend();convex/messages.ts (1)
187-193: Use null for empty continueCursor to match validator.Minor consistency with the declared returns type and our paginate usage.
return { page: [], isDone: true, - continueCursor: "", + continueCursor: null, };app/components/chat.tsx (1)
118-123: Avoid unsafe cast; prefer stronger typing at the source.Casting
messages as ChatMessage[]hides potential mismatches. IfuseChatsupports generics, parameterize it to returnChatMessage[]; otherwise, changenormalizeMessagesto accept the exactUIMessage<MessageMetadata>type you get fromuseChatto remove the assertion.lib/utils/message-processor.ts (1)
51-53: LGTM on narrowing to ChatMessage.Signature aligns with the rest of the PR. Minor nit: update the JSDoc “UI messages” wording to “ChatMessage[]” for consistency.
- * @param messages - Array of UI messages to normalize + * @param messages - ChatMessage[] to normalizeapp/components/MessageActions.tsx (2)
54-59: Memoize the feedback handler to reduce re-renders.Wrap
handleFeedbackinuseCallbackand import it. Keeps child props stable when lists are large.-import { useState } from "react"; +import { useState, useCallback } from "react"; @@ -const handleFeedback = (type: "positive" | "negative") => { - if (onFeedback) { - onFeedback(type); - } -}; +const handleFeedback = useCallback( + (type: "positive" | "negative") => { + onFeedback?.(type); + }, + [onFeedback], +);
108-169: Improve a11y for feedback buttons.Add
aria-pressedto reflect current selection; assistive tech will announce the toggle state.-<button +<button type="button" onClick={() => handleFeedback("positive")} + aria-pressed={existingFeedback === "positive"} @@ -<button +<button type="button" onClick={() => handleFeedback("negative")} + aria-pressed={existingFeedback === "negative" || isAwaitingFeedbackDetails}app/hooks/useFeedback.ts (1)
74-96: Optional: reflect “details saved” in metadata (if modeled).If
MessageMetadataincludes feedback details, update local state after submit so UI can reflect it immediately.await createFeedback({ feedback_type: "negative", feedback_details: details, message_id: feedbackInputMessageId, }); +// Optionally persist details locally: +setMessages((prev) => + prev.map((m) => + m.id === feedbackInputMessageId + ? { + ...m, + metadata: { ...(m.metadata ?? {}), feedbackType: "negative", feedbackDetails: details }, + } + : m, + ), +);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
convex/_generated/api.d.tsis excluded by!**/_generated/**
📒 Files selected for processing (16)
app/api/chat/route.ts(2 hunks)app/components/FeedbackInput.tsx(1 hunks)app/components/MessageActions.tsx(5 hunks)app/components/Messages.tsx(4 hunks)app/components/chat.tsx(2 hunks)app/hooks/useFeedback.ts(1 hunks)components/ui/sonner.tsx(1 hunks)convex/chats.ts(2 hunks)convex/feedback.ts(1 hunks)convex/messages.ts(10 hunks)convex/schema.ts(2 hunks)lib/db/actions.ts(4 hunks)lib/utils.ts(1 hunks)lib/utils/file-transform-utils.ts(1 hunks)lib/utils/message-processor.ts(3 hunks)types/chat.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/api/chat/route.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
**/*.{ts,tsx}: Use Id helper type from ./_generated/dataModel to type document IDs (e.g., Id<'users'>) instead of string
When defining Record types, specify key and value types matching validators (e.g., Record<Id<'users'>, string>)
Be strict with types for document IDs; prefer Id<'table'> over string in function args and variables
Useas constfor string literals in discriminated unions
Declare arrays with explicit generic type: const arr: Array = [...]
Declare records with explicit generic types: const record: Record<KeyType, ValueType> = {...}
Files:
components/ui/sonner.tsxlib/utils/file-transform-utils.tsapp/components/Messages.tsxapp/components/chat.tsxconvex/schema.tsconvex/feedback.tsapp/hooks/useFeedback.tslib/utils.tslib/utils/message-processor.tslib/db/actions.tsapp/components/FeedbackInput.tsxtypes/chat.tsconvex/chats.tsapp/components/MessageActions.tsxconvex/messages.ts
convex/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
convex/**/*.ts: Always use the new Convex function syntax (query/mutation/action objects with args/returns/handler) when defining Convex functions
When a function returns null, include returns: v.null() and return null explicitly
Use internalQuery/internalMutation/internalAction for private functions callable only by other Convex functions; do not expose sensitive logic via public query/mutation/action
Use query/mutation/action only for public API functions
Do not try to register functions via the api or internal objects
Always include argument and return validators for all Convex functions (query/internalQuery/mutation/internalMutation/action/internalAction)
In JS implementations, functions without an explicit return value implicitly return null
Use ctx.runQuery from queries/mutations/actions to call a query
Use ctx.runMutation from mutations/actions to call a mutation
Use ctx.runAction from actions to call an action
Only call an action from another action when crossing runtimes (e.g., V8 to Node); otherwise extract shared helper code
Minimize calls from actions to queries/mutations to avoid race conditions from splitting transactions
Pass FunctionReference values (from api/internal) to ctx.runQuery/ctx.runMutation/ctx.runAction; do not pass function implementations
When calling a function in the same file via ctx.run*, add an explicit return type annotation at the call site to avoid TS circularity
Use the generated api object for public functions and internal object for internal functions from convex/_generated/api.ts
Respect file-based routing for function references: e.g., convex/example.ts export f -> api.example.f; nested paths map to dot-separated namespaces
For paginated queries use paginationOptsValidator in args and .paginate(args.paginationOpts) on a query
v.bigint() is deprecated; use v.int64() for signed 64-bit integers
Use v.record(keys, values) for record-like data; v.map() and v.set() are not supported
For full-text search, use withSearchIndex("ind...
Files:
convex/schema.tsconvex/feedback.tsconvex/chats.tsconvex/messages.ts
convex/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
convex/schema.ts: Define the Convex schema in convex/schema.ts
Import schema definition functions (defineSchema, defineTable) from convex/server
Understand system fields: _id uses v.id(tableName) and _creationTime uses v.number(); they’re auto-added to all documents
Include all indexed fields in the index name (e.g., by_field1_and_field2 for ["field1","field2"])
Query indexes in the same column order as defined; create separate indexes for alternate orders
Files:
convex/schema.ts
🧠 Learnings (3)
📚 Learning: 2025-08-29T13:33:09.937Z
Learnt from: CR
PR: hackerai-tech/hackerai#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-08-29T13:33:09.937Z
Learning: Applies to convex/schema.ts : Define the Convex schema in convex/schema.ts
Applied to files:
convex/schema.ts
📚 Learning: 2025-08-29T13:33:09.937Z
Learnt from: CR
PR: hackerai-tech/hackerai#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-08-29T13:33:09.937Z
Learning: Applies to convex/**/*.ts : Use internalQuery/internalMutation/internalAction for private functions callable only by other Convex functions; do not expose sensitive logic via public query/mutation/action
Applied to files:
convex/chats.tsconvex/messages.ts
📚 Learning: 2025-08-29T13:33:09.937Z
Learnt from: CR
PR: hackerai-tech/hackerai#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-08-29T13:33:09.937Z
Learning: Applies to convex/**/*.ts : Use query/mutation/action only for public API functions
Applied to files:
convex/chats.ts
🧬 Code graph analysis (8)
components/ui/sonner.tsx (1)
hooks/use-mobile.ts (1)
useIsMobile(5-21)
app/components/Messages.tsx (3)
types/chat.ts (1)
ChatMessage(65-65)app/hooks/useFeedback.ts (1)
useFeedback(12-109)app/components/FeedbackInput.tsx (1)
FeedbackInput(10-82)
app/components/chat.tsx (2)
lib/utils/message-processor.ts (1)
normalizeMessages(50-130)types/chat.ts (1)
ChatMessage(65-65)
app/hooks/useFeedback.ts (2)
types/chat.ts (1)
ChatMessage(65-65)convex/feedback.ts (1)
createFeedback(4-54)
lib/utils.ts (1)
types/chat.ts (1)
ChatMessage(65-65)
lib/utils/message-processor.ts (1)
types/chat.ts (1)
ChatMessage(65-65)
app/components/MessageActions.tsx (1)
components/ui/with-tooltip.tsx (1)
WithTooltip(16-46)
convex/messages.ts (2)
convex/_generated/server.js (2)
internalQuery(39-39)internalQuery(39-39)convex/_generated/api.js (2)
internal(22-22)internal(22-22)
🔇 Additional comments (10)
components/ui/sonner.tsx (1)
5-5: Good addition: device-aware importImporting the mobile hook here keeps the component cohesive. LGTM.
lib/utils.ts (1)
35-36: Mapping id looks correctSwitch to message.id aligns with schema and API usage.
lib/db/actions.ts (1)
45-56: No action needed: userId correctly passed and enforced
convex.messages.saveMessage and chats.saveChat both include userId in their args; saveMessage calls verifyChatOwnership before inserting, and saveChat assigns user_id on creation.convex/chats.ts (1)
229-233: Ownership check on delete is correct.Throwing on mismatched user_id is the right behavior and keeps this mutation public without a service key.
convex/messages.ts (3)
7-27: Internal ownership check looks good.Using internalQuery with explicit args/returns and throwing on failure matches our Convex rules.
33-73: saveMessage: good idempotency check and centralized ownership verification.Early-return on duplicate id and storing user_id are correct.
90-116: Return validator is solid and forward-compatible.Typed page shape plus optional pageStatus/splitCursor aligns with paginate.
app/components/chat.tsx (1)
294-301: PropagatesetMessagesas a React Dispatch.This assumes
Messages’ssetMessagesprop is typed asReact.Dispatch<React.SetStateAction<ChatMessage[]>>. If not, update it; this will enable functional updates and prevent stale-closure bugs in feedback handlers.app/components/Messages.tsx (2)
65-72: LGTM on feedback wiring.Hook integration and prop plumbing are correct; state is scoped per-message and only shown for assistant messages.
298-303: Nice UX touches.Passing
existingFeedbackand showing inlineFeedbackInputonly when active keeps the thread compact.Also applies to: 305-313
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: 1
♻️ Duplicate comments (3)
app/hooks/useFeedback.ts (3)
1-1: TypesetMessagesas Dispatch and tighten ID typing (prevents stale closures and enforces consistency).Update imports and props to allow functional updates; also type IDs from
ChatMessageto align with our guidelines and reduce accidental string misuse.-import { useState, useCallback } from "react"; +import { useState, useCallback, type Dispatch, type SetStateAction } from "react"; @@ interface UseFeedbackProps { messages: ChatMessage[]; - setMessages: (messages: ChatMessage[]) => void; + setMessages: Dispatch<SetStateAction<ChatMessage[]>>; } @@ - const [feedbackInputMessageId, setFeedbackInputMessageId] = useState< - string | null - >(null); + const [feedbackInputMessageId, setFeedbackInputMessageId] = useState< + ChatMessage["id"] | null + >(null); @@ - async (messageId: string, type: "positive" | "negative") => { + async (messageId: ChatMessage["id"], type: "positive" | "negative") => {Also applies to: 7-10, 14-16, 23-23
41-49: Fix stale-closure update and preserve metadata on positive; also close details input if switching to positive.Use a functional state update and merge
metadatato avoid clobbering other fields; clear the inline input when a message is flipped to positive.- // Update local message state immediately - setMessages( - messages.map((msg) => - msg.id === messageId - ? { ...msg, metadata: { feedbackType: "positive" } } - : msg, - ), - ); + // Update local message state immediately (merge metadata) + setMessages((prev) => + prev.map((msg) => + msg.id === messageId + ? { + ...msg, + metadata: { + ...(msg.metadata ?? {}), + feedbackType: "positive", + }, + } + : msg, + ), + ); + // If details input was open for this message, close it + setFeedbackInputMessageId((curr) => (curr === messageId ? null : curr));Also applies to: 50-54
70-81: Do the same for the negative path: functional update + metadata merge.Prevents stale-state bugs and avoids dropping other metadata keys.
- // Update local message state immediately - setMessages( - messages.map((msg) => - msg.id === messageId - ? { ...msg, metadata: { feedbackType: "negative" } } - : msg, - ), - ); + // Update local message state immediately (merge metadata) + setMessages((prev) => + prev.map((msg) => + msg.id === messageId + ? { + ...msg, + metadata: { + ...(msg.metadata ?? {}), + feedbackType: "negative", + }, + } + : msg, + ), + );
🧹 Nitpick comments (4)
app/components/SidebarUserNav.tsx (1)
119-122: Prefer link semantics withasChildfor a11y and built‑in rel control.This avoids JS handlers and ensures proper attributes.
-<DropdownMenuItem onClick={handleHelpCenter}> - <LifeBuoy className="mr-2 h-4 w-4" /> - <span>Help Center</span> -</DropdownMenuItem> +<DropdownMenuItem asChild> + <a href="https://help.hackerai.co/en/" target="_blank" rel="noopener noreferrer"> + <LifeBuoy className="mr-2 h-4 w-4" aria-hidden="true" /> + <span>Help Center</span> + </a> +</DropdownMenuItem>app/hooks/useFeedback.ts (3)
25-33: Existing-feedback precheck can be stale; consider removing or guarding with a ref.Because
messagesis read from the outer scope, the “already positive/negative” checks can race with concurrent updates. Options:
- Remove the precheck and rely on backend idempotency (server patches existing feedback safely), or
- Track latest
messagesin a ref to avoid stale reads.
87-88: Trim dependencies after switching to functional updates.Drop
messagesfrom the dependency array to avoid needless re-creations;setMessagescan remain for clarity.- [createFeedback, messages, setMessages], + [createFeedback, setMessages],
90-112: Optional: surface details locally if UI needs it.If any component renders feedback details from message metadata, consider mirroring
feedback_detailslocally to avoid an extra fetch. Otherwise, current behavior is fine.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
app/components/SidebarUserNav.tsx(3 hunks)app/hooks/useFeedback.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)
**/*.{ts,tsx}: Use Id helper type from ./_generated/dataModel to type document IDs (e.g., Id<'users'>) instead of string
When defining Record types, specify key and value types matching validators (e.g., Record<Id<'users'>, string>)
Be strict with types for document IDs; prefer Id<'table'> over string in function args and variables
Useas constfor string literals in discriminated unions
Declare arrays with explicit generic type: const arr: Array = [...]
Declare records with explicit generic types: const record: Record<KeyType, ValueType> = {...}
Files:
app/components/SidebarUserNav.tsxapp/hooks/useFeedback.ts
🧬 Code graph analysis (2)
app/components/SidebarUserNav.tsx (1)
components/ui/dropdown-menu.tsx (1)
DropdownMenuItem(248-248)
app/hooks/useFeedback.ts (2)
types/chat.ts (1)
ChatMessage(65-65)convex/feedback.ts (1)
createFeedback(4-54)
🔇 Additional comments (1)
app/components/SidebarUserNav.tsx (1)
5-5: LGTM: correct icon import.LifeBuoy is correctly imported from lucide-react and used below.
Summary by CodeRabbit
New Features
Documentation
Chores
Bug Fixes