feat: Implement core project management, AI conversation, and code ed… - #14
feat: Implement core project management, AI conversation, and code ed…#14Mayank-saraswal wants to merge 2 commits into
Conversation
…iting features with associated APIs and UI components.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis pull request migrates the backend from Convex to a Prisma/Postgres + Next.js API routes architecture, adds Razorpay billing and token/token-economy management, introduces Redis rate limiting and caching, integrates Azure Blob Storage for file blobs, implements E2B sandbox utilities, and updates frontend hooks/components to use string IDs with React Query and Supabase realtime where needed. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/Client
participant APIRoute as POST /api/messages
participant Auth as Clerk Auth
participant RateLimit as Redis RateLimiter
participant TokenCheck as checkTokenBalance
participant Prisma as Prisma Client
participant Inngest as Inngest Event
participant ProcessMsg as process-message Handler
participant TokenDeduct as deductTokens
User->>APIRoute: Send message with conversationId
APIRoute->>Auth: Verify user identity
Auth-->>APIRoute: UserId confirmed
APIRoute->>RateLimit: Check message rate limit
alt Rate limit exceeded
RateLimit-->>APIRoute: 429 Too Many Requests
APIRoute-->>User: Error response
else Rate limit OK
RateLimit-->>APIRoute: Limit ok
APIRoute->>TokenCheck: Check token balance
alt Insufficient tokens
TokenCheck-->>APIRoute: insufficient
APIRoute-->>User: 402 Payment Required
else Tokens available
TokenCheck-->>APIRoute: ok
APIRoute->>Prisma: Validate conversation ownership
Prisma-->>APIRoute: conversation OK
APIRoute->>Prisma: Create user message & assistant placeholder
Prisma-->>APIRoute: message IDs
APIRoute->>Inngest: Trigger process-message (with messageId)
APIRoute-->>User: 200 Created
Inngest->>ProcessMsg: Execute handler
ProcessMsg->>Prisma: Fetch context & messages
ProcessMsg->>ProcessMsg: Run AI + tools (file ops, e2b, etc.)
ProcessMsg->>Prisma: Update assistant message status/content
ProcessMsg->>TokenDeduct: Deduct tokens and log usage
TokenDeduct->>Prisma: Update subscription/usage
ProcessMsg-->>Inngest: Complete
end
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (12)
src/features/preview/components/preview-settings-popover.tsx (1)
48-58:⚠️ Potential issue | 🟠 MajorMutation errors are silently swallowed — user receives no feedback on failure
When
mutateAsyncrejects (network error, API error, etc.), TanStack Form's internalhandleSubmitwrapper catches the exception, resetsisSubmittingtofalse, and re-enables the Save button — with no visible indication of what went wrong.setOpenandonSaveare already correctly skipped, but the user is left staring at the same open popover with no error message.🔒️ Proposed fix: wrap mutation in try/catch and surface the error
onSubmit: async ({ value }) => { - await updateSettings.mutateAsync({ - id: projectId, - settings: { - installCommand: value.installCommand || undefined, - devCommand: value.devCommand || undefined, - }, - }); - setOpen(false); - onSave?.(); + try { + await updateSettings.mutateAsync({ + id: projectId, + settings: { + installCommand: value.installCommand || undefined, + devCommand: value.devCommand || undefined, + }, + }); + setOpen(false); + onSave?.(); + } catch (error) { + // Surface the error via your app's notification system, e.g.: + // toast.error("Failed to save settings. Please try again."); + console.error("Failed to update project settings:", error); + } }Alternatively, read
updateSettings.isError/updateSettings.errorfrom the mutation object and render an inline error below the submit button — this avoids local try/catch and integrates naturally with TanStack Query's state model.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/preview/components/preview-settings-popover.tsx` around lines 48 - 58, The submit handler currently calls updateSettings.mutateAsync and then closes the popover (setOpen) and calls onSave, but any rejection is swallowed — wrap the mutateAsync call in a try/catch (or alternatively rely on updateSettings.isError/updateSettings.error) and on error set a visible error state (e.g., local submitError or use form.setError) and render that message below the Save button; only call setOpen and onSave on success so failures surface to the user (reference updateSettings.mutateAsync, setOpen, onSave in the preview-settings-popover component).src/features/projects/components/import-github-dialog.tsx (1)
63-89:⚠️ Potential issue | 🟠 Major
error.response.json()can throw inside the catch block, swallowing the fallback toastIf the server returns a non-JSON error response (e.g., a 502/504 with an HTML body, an empty body, or a network-level error page),
error.response.json()throws aSyntaxError. Because this occurs inside thecatchblock, the exception propagates out ofonSubmitas an unhandled rejection — the fallbacktoast.error(...)on line 88 is never reached, and the form may remain stuck inisSubmittingstate.🛡️ Proposed fix
} catch (error) { if (error instanceof HTTPError) { - const body = await error.response.json<{ error: string }>(); - if (body.error?.includes("Pro plan required")) { - toast.error("Upgrade to import repositories", { - action: { - label: "Upgrade", - onClick: () => openUserProfile(), - }, - }); - onOpenChange(false); - return; - } - - if (body.error?.includes("GitHub not connected")) { - toast.error("GitHub account not connected", { - action: { - label: "Connect", - onClick: () => openUserProfile(), - }, - }); - onOpenChange(false); - return; - } + try { + const body = await error.response.json<{ error: string }>(); + if (body.error?.includes("Pro plan required")) { + toast.error("Upgrade to import repositories", { + action: { label: "Upgrade", onClick: () => openUserProfile() }, + }); + onOpenChange(false); + return; + } + if (body.error?.includes("GitHub not connected")) { + toast.error("GitHub account not connected", { + action: { label: "Connect", onClick: () => openUserProfile() }, + }); + onOpenChange(false); + return; + } + } catch { + // Non-JSON response body; fall through to generic error toast + } } toast.error("Unable to import repository. Please check the URL and try again"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/projects/components/import-github-dialog.tsx` around lines 63 - 89, The catch block in onSubmit assumes error.response.json() always succeeds (checking instanceof HTTPError and reading body), but JSON parsing can throw and bypass the fallback toast; update the catch to safely parse the response (wrap error.response.json() in its own try/catch or use a safe parser that falls back to text) so parse failures do not escape the outer catch, then inspect the parsed body/error string for "Pro plan required" or "GitHub not connected" and only after those checks call the fallback toast.error("Unable to import repository..."); keep existing calls to onOpenChange(false) and openUserProfile() as currently used when matching those messages.src/features/projects/components/export-popover.tsx (2)
56-66:⚠️ Potential issue | 🟠 Major
repoNamedefault value won't populate when project data loads asynchronously.
useFormevaluatesdefaultValuessynchronously at initialization. IfuseProject(projectId)hasn't resolved yet (cache miss),projectisundefinedandrepoNameinitializes to"". When React Query later setsproject, the form state is not updated.✏️ Proposed fix — reset the form when project data arrives
+import React, { useEffect } from "react"; // ... const form = useForm({ defaultValues: { repoName: project?.name?.replace(/[^a-zA-Z0-9._-]/g, "-") ?? "", visibility: "private" as "public" | "private", description: "", }, // ... }); + +useEffect(() => { + if (project?.name) { + form.setFieldValue( + "repoName", + project.name.replace(/[^a-zA-Z0-9._-]/g, "-") + ); + } +}, [project?.name]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/projects/components/export-popover.tsx` around lines 56 - 66, The form's repoName default is set from project at initialization so it stays empty if project loads later; update the form when project data arrives by adding an effect that watches project and calls the form API (e.g., form.reset or form.setValue) to set repoName to project?.name?.replace(/[^a-zA-Z0-9._-]/g, "-") and any other fields you want to initialize; reference useProject(projectId) to detect the loaded project and use the form instance returned from useForm to perform the update.
8-14:⚠️ Potential issue | 🟡 MinorDead code: unused imports and unreachable handlers left over from removed export-status feature.
CheckCheckIcon,CheckCircle2Icon,ExternalLinkIcon,LoaderIcon,XCircleIconare imported but never referenced in the simplified render path.handleCancelExport(line 112) andhandleResetExport(line 118) are defined but never called — these were part of the removed exporting/completed/failed status UI.Remove all five unused imports and both handlers to avoid dead code.
Also applies to: 112-123
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/projects/components/export-popover.tsx` around lines 8 - 14, Remove the dead code left from the old export-status UI: delete the unused icon imports CheckCheckIcon, CheckCircle2Icon, ExternalLinkIcon, LoaderIcon, XCircleIcon from the import list in export-popover.tsx, and remove the two unused handler functions handleCancelExport and handleResetExport (and any related unused local variables) so there are no unreachable or unreferenced symbols left in the component.src/features/conversations/inngest/constants.ts (1)
7-13:⚠️ Potential issue | 🟡 MinorStale "IDs" references in workflow steps conflict with the new
parentPath-based rules.Lines 7 and 10 still instruct the agent to "Note the IDs of folders" and "get their IDs", but lines 17–18 now direct it to use
parentPathinstead. This creates contradictory guidance within the same prompt.✏️ Proposed fix
-1. Call listFiles to see the current project structure. Note the IDs of folders you need. +1. Call listFiles to see the current project structure. Note the paths of folders you need. 2. Call readFiles to understand existing code when relevant. 3. Execute ALL necessary changes: - - Create folders first to get their IDs + - Create folders first to determine their paths - Use createFiles to batch create multiple files in the same folder (more efficient)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/conversations/inngest/constants.ts` around lines 7 - 13, Update the stale instructions that reference "IDs" to consistently use the new parentPath-based convention: replace the phrases "Note the IDs of folders" and "get their IDs" with wording like "note the parentPath of folders" or "use parentPath" so steps 1 and 3 no longer conflict with the lines that already reference parentPath; ensure the strings "Note the IDs of folders" and "get their IDs" are updated to reference "parentPath" and keep the rest of the workflow steps intact so all guidance consistently uses parentPath-based rules.src/features/projects/components/project-id-view.tsx (1)
50-50:⚠️ Potential issue | 🟡 MinorDead code —
useProjectcalled but result never used, triggering an unnecessary network request.
projectstores the fullQueryObserverResultobject (not the project data), and nothing in the component's JSX references it. This fires a redundant/api/projects/:idfetch on every render. Remove the call (or destructure{ data: project }if project data is needed in this component in the future).🛠️ Proposed fix
- const project = useProject(projectId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/projects/components/project-id-view.tsx` at line 50, The call to useProject(projectId) returns a QueryObserverResult saved to the variable project but its value is never used, causing an unnecessary API fetch; either delete the unused line "const project = useProject(projectId);" to stop the request, or replace it with a destructure that only grabs data when needed (e.g., "const { data: project } = useProject(projectId);" or pass an options object to disable fetching until required) so the component no longer triggers the redundant /api/projects/:id call.src/app/api/github/import/route.ts (1)
27-28:⚠️ Potential issue | 🟠 Major
requestSchema.parse(body)throws unhandledZodErroron invalid URLSame issue as the export route — use
safeParseand return a 400.🛠️ Proposed fix
- const { url } = requestSchema.parse(body); + const parsed = requestSchema.safeParse(body); + if (!parsed.success) { + return NextResponse.json( + { error: parsed.error.issues[0]?.message ?? "Invalid request" }, + { status: 400 } + ); + } + const { url } = parsed.data;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/github/import/route.ts` around lines 27 - 28, Replace the unhandled requestSchema.parse call with requestSchema.safeParse(body) in the import route handler: call const result = requestSchema.safeParse(body), check result.success, and if false return a 400 response (including the validation error message or a concise error payload) instead of letting ZodError bubble; otherwise use result.data.url for the rest of the logic (this mirrors the export route fix and prevents unhandled ZodError crashes).src/app/api/github/export/route.ts (1)
39-48:⚠️ Potential issue | 🟠 MajorGitHub OAuth token passed plaintext in Inngest event payload — will be stored in event logs
Inngest persists all event
datato its event history/log storage, accessible from the dashboard and API. IncludinggithubTokenhere exposes a live OAuth credential to anyone with Inngest dashboard access and to any Inngest log storage backend.The token should be fetched at job execution time inside the Inngest function handler instead of being passed as event data. The
userIdalready present in the event is sufficient to re-fetch the token from Clerk at execution time.🛠️ Proposed fix
const event = await inngest.send({ name: "github/export.repo", data: { projectId, repoName, visibility, description, - githubToken, }, });In the Inngest handler, re-fetch the token using
userIdfrom event metadata or passuserIdin the event instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/github/export/route.ts` around lines 39 - 48, The event payload currently includes the plaintext githubToken (see inngest.send call and data object with githubToken) which will be persisted; remove githubToken from the data sent to Inngest and instead include the caller's userId (or ensure userId is present in event metadata), then in the Inngest function handler re-fetch the GitHub OAuth token using Clerk (or your token store) keyed by userId at execution time (update the handler that processes "github/export.repo" to retrieve the token from Clerk using userId and use that token for API calls).src/app/api/projects/create-with-prompt/route.ts (1)
36-37:⚠️ Potential issue | 🟠 MajorUnhandled
request.json()and Zod parse errors will produce raw 500 responses.Neither the JSON parsing nor the Zod validation is wrapped in error handling. A malformed body or missing
promptwill throw an unstructured error. Consider wrapping in try-catch and returning a 400 with validation details.Proposed fix
+ let prompt: string; + try { + const body = await request.json(); + ({ prompt } = requestSchema.parse(body)); + } catch { + return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); + } - const body = await request.json(); - const { prompt } = requestSchema.parse(body);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/projects/create-with-prompt/route.ts` around lines 36 - 37, Wrap the JSON parsing and Zod validation in a try-catch inside the route handler so malformed JSON or validation failures don't bubble up as 500s; call await request.json() and requestSchema.parse(body) inside the try, catch SyntaxError (or generic error from request.json) and ZodError from requestSchema.parse, and return a 400 response containing the validation/parsing details (e.g., error.message or ZodError.format()) instead of throwing. Ensure the catch branches reference the same symbols (request.json and requestSchema.parse) so the handler returns structured 400 responses for bad input.src/features/projects/inngest/import-to-github-repo.ts (1)
94-147:⚠️ Potential issue | 🟠 MajorAll file fetches, uploads, and DB writes run inside a single Inngest step — timeout risk for large repos.
The
"create-files"step iterates over every file in the repo, performing a GitHub API call, a Prisma create, an Azure upload, and a Prisma update per file — all within one step. For repositories with hundreds or thousands of files, this will likely exceed the Inngest step execution time limit.Consider batching files into multiple steps (e.g., chunks of 20-50) using
step.runper batch, which also gives you automatic retries per batch rather than restarting the entire file import on failure.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/projects/inngest/import-to-github-repo.ts` around lines 94 - 147, The "create-files" Inngest step currently processes allFiles in a single step (inside step.run("create-files")), causing timeout risk; change the implementation to split allFiles into batches (e.g., chunks of 20–50) and call step.run for each batch (e.g., step.run(`create-files-${batchIndex}`)) so each batch fetches blobs via octokit.rest.git.getBlob, creates records via prisma.file.create, uploads via uploadBinaryFile/uploadTextFile, and updates via prisma.file.update within its own step; this gives per-batch retries and keeps each step short—ensure you reference the existing symbols allFiles, step.run("create-files"), uploadBinaryFile, uploadTextFile, prisma.file.create, and prisma.file.update when locating and refactoring the code.src/features/projects/inngest/export-to-github.ts (1)
153-160:⚠️ Potential issue | 🟠 MajorTree creation without
base_treediscards the auto-init README.
auto_init: truecreates a README.md in the initial commit, butcreateTreeis called withoutbase_tree. This creates a new tree containing only the exported files, which replaces the initial tree when main is updated. The README.md will not appear in the exported repository. If preserving the README is desired, usebase_treeset to the initial commit SHA, or include README.md in the tree items before creation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/projects/inngest/export-to-github.ts` around lines 153 - 160, createTree is being called without a base_tree which discards the repo’s auto_init README; update the create-tree step (the octokit.rest.git.createTree call that uses treeItems) to either pass base_tree set to the initial commit SHA (fetch the initial commit SHA beforehand) or ensure README.md is included in treeItems so the README is preserved in the resulting tree before updating refs.src/features/projects/components/projects-list.tsx (1)
37-59:⚠️ Potential issue | 🟠 MajorNested interactive elements:
<Button>wrapping<Link>.A
<button>containing an<a>(rendered by<Link>) is invalid HTML and creates accessibility problems — screen readers see two interactive elements, and keyboard focus/activation behavior becomes ambiguous. Use<Button asChild>to merge the Button into the Link element.Fix: use asChild prop
<Button variant="outline" className="h-auto items-start justify-start p-4 bg-background border rounded-none flex flex-col gap-2" + asChild > <Link href={`/projects/${data.id}`} className="group">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/projects/components/projects-list.tsx` around lines 37 - 59, The Button currently wraps a Link (creating nested interactive elements); change Button to use the asChild prop so the Link becomes the rendered element with Button styling: add asChild to the <Button> declaration and keep the <Link href={`/projects/${data.id}`} ...> as the direct child (preserving the inner structure including getProjectIcon(data), {data.name}, ArrowRightIcon and the timestamp formatting via formatTimestamp(data.updatedAt)), and ensure any Button-specific className/variant are applied to the Button so they flow through to the Link child.
| @@ -0,0 +1,12 @@ | |||
| import path from "node:path"; | |||
| import dotenv from "dotenv"; | |||
| import { defineConfig } from "@prisma/config"; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "prisma.config.ts" -o -name "prisma.config.js" 2>/dev/nullRepository: Mayank-saraswal/moris
Length of output: 83
🏁 Script executed:
cat package.json | grep -A 20 '"dependencies"' | head -30Repository: Mayank-saraswal/moris
Length of output: 831
🏁 Script executed:
cat -n prisma.config.ts 2>/dev/null || echo "File not found at root"Repository: Mayank-saraswal/moris
Length of output: 473
🏁 Script executed:
rg "prisma" package.jsonRepository: Mayank-saraswal/moris
Length of output: 156
🌐 Web query:
Prisma 7 defineConfig import correct path prisma/config
💡 Result:
In Prisma ORM v7, the correct import for defineConfig in prisma.config.ts is:
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
// ...
});So the path is "prisma/config" (not @prisma/config). [1], [2]
🌐 Web query:
__dirname ESM JavaScript modules
💡 Result:
In Node.js ES modules (ESM), __dirname isn’t defined (it’s a CommonJS-only variable). Node recommends deriving it from import.meta.url like this: (nodejs.org)
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);If you’re on Node v20.11.0+ / v21.2.0+, Node also provides import.meta.dirname (and import.meta.filename) directly (file: modules only). (nodejs.org)
Citations:
- 1: https://nodejs.org/download/release/v12.22.6/docs/api/esm.html?utm_source=openai
- 2: https://nodejs.org/download/release/v22.11.0/docs/api/esm.html?utm_source=openai
Wrong package import — "@prisma/config" does not exist; use "prisma/config".
The Prisma 7 official docs import defineConfig and the type-safe env() helper from "prisma/config", which is a subpath export of the prisma package you already have installed. The @prisma/config scoped package does not exist and this import will fail at runtime. Additionally, process.env.DATABASE_URL is typed as string | undefined; using the env() helper provides a type-safe, fail-fast alternative.
__dirname is not available in ESM. The file uses import syntax but __dirname is a CommonJS global. Use import.meta.dirname (Node 20.11.0+) or derive it from import.meta.url.
Proposed fix
-import path from "node:path";
-import dotenv from "dotenv";
-import { defineConfig } from "@prisma/config";
+import "dotenv/config";
+import { defineConfig, env } from "prisma/config";-dotenv.config({ path: path.resolve(__dirname, ".env.local") });
+// dotenv/config loads .env; remove __dirname and use .env instead of .env.local export default defineConfig({
datasource: {
- url: process.env.DATABASE_URL,
+ url: env("DATABASE_URL"),
},
});If .env.local override is required for the Prisma CLI specifically, use import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); to safely derive __dirname in ESM.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@prisma.config.ts` at line 3, The import is incorrect and ESM-only globals are
used: replace the non-existent import from "@prisma/config" with the documented
"prisma/config" and use the type-safe env() helper from that module for
DATABASE_URL (replace direct use of process.env.DATABASE_URL with
env("DATABASE_URL")). Also remove use of CommonJS __dirname; derive a directory
in ESM instead (e.g., compute dirname from import.meta.url or use
import.meta.dirname on Node ≥20.11) if you need to reference files for Prisma;
update any references in this file (e.g., defineConfig, env(), and __dirname
usage) accordingly so the module imports and path resolution work in ESM.
| // Ensure user has a subscription record | ||
| let userSub = await prisma.userSubscription.findUnique({ | ||
| where: { userId }, | ||
| }); | ||
|
|
||
| if (!userSub) { | ||
| userSub = await prisma.userSubscription.create({ | ||
| data: { userId, plan: "free" }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
TOCTOU race condition: concurrent requests can both see null and attempt duplicate inserts.
findUnique + conditional create is not atomic. Two simultaneous subscribe requests for the same user can both pass the null check and both attempt to create, causing a unique constraint violation on userId. Use upsert instead.
Proposed fix
- // Ensure user has a subscription record
- let userSub = await prisma.userSubscription.findUnique({
- where: { userId },
- });
-
- if (!userSub) {
- userSub = await prisma.userSubscription.create({
- data: { userId, plan: "free" },
- });
- }
+ // Ensure user has a subscription record
+ const userSub = await prisma.userSubscription.upsert({
+ where: { userId },
+ update: {},
+ create: { userId, plan: "free" },
+ });📝 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.
| // Ensure user has a subscription record | |
| let userSub = await prisma.userSubscription.findUnique({ | |
| where: { userId }, | |
| }); | |
| if (!userSub) { | |
| userSub = await prisma.userSubscription.create({ | |
| data: { userId, plan: "free" }, | |
| }); | |
| } | |
| // Ensure user has a subscription record | |
| const userSub = await prisma.userSubscription.upsert({ | |
| where: { userId }, | |
| update: {}, | |
| create: { userId, plan: "free" }, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/api/billing/subscribe/route.ts` around lines 22 - 31, The current
pattern does a non-atomic findUnique followed by create which can race and cause
duplicate-insert unique constraint errors for userSubscription; replace the
findUnique + conditional create logic with a single atomic
prisma.userSubscription.upsert call (use where: { userId }, update: {} and
create: { userId, plan: "free" }) and assign its result back to userSub so
concurrent requests do not conflict.
| const signature = request.headers.get("x-razorpay-signature") ?? ""; | ||
| const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET ?? ""; | ||
|
|
||
| // Verify signature | ||
| if (!verifyWebhookSignature(body, signature, webhookSecret)) { | ||
| return NextResponse.json( | ||
| { error: "Invalid signature" }, | ||
| { status: 400 } | ||
| ); | ||
| } |
There was a problem hiding this comment.
Webhook signature verification has two security weaknesses.
-
Timing-unsafe comparison:
verifyWebhookSignature(insrc/lib/razorpay.ts:59) uses===instead ofcrypto.timingSafeEqual, making it susceptible to timing attacks that can incrementally guess the signature. -
Empty secret fallback: If
RAZORPAY_WEBHOOK_SECRETis unset,webhookSecretdefaults to""(line 10). An attacker can computeHMAC-SHA256("", body)and forge valid webhooks. The handler should fail fast if the secret is missing.
Proposed fix in src/lib/razorpay.ts
export function verifyWebhookSignature(
body: string,
signature: string,
secret: string
): boolean {
- const crypto = require("crypto");
+ const crypto = await import("node:crypto");
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
- return expectedSignature === signature;
+ if (expectedSignature.length !== signature.length) return false;
+ return crypto.timingSafeEqual(
+ Buffer.from(expectedSignature, "hex"),
+ Buffer.from(signature, "hex"),
+ );
}Proposed fix for empty secret in webhook/route.ts
- const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET ?? "";
+ const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET;
+ if (!webhookSecret) {
+ console.error("RAZORPAY_WEBHOOK_SECRET is not configured");
+ return NextResponse.json({ error: "Server configuration error" }, { status: 500 });
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const signature = request.headers.get("x-razorpay-signature") ?? ""; | |
| const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET ?? ""; | |
| // Verify signature | |
| if (!verifyWebhookSignature(body, signature, webhookSecret)) { | |
| return NextResponse.json( | |
| { error: "Invalid signature" }, | |
| { status: 400 } | |
| ); | |
| } | |
| const signature = request.headers.get("x-razorpay-signature") ?? ""; | |
| const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET; | |
| if (!webhookSecret) { | |
| console.error("RAZORPAY_WEBHOOK_SECRET is not configured"); | |
| return NextResponse.json({ error: "Server configuration error" }, { status: 500 }); | |
| } | |
| // Verify signature | |
| if (!verifyWebhookSignature(body, signature, webhookSecret)) { | |
| return NextResponse.json( | |
| { error: "Invalid signature" }, | |
| { status: 400 } | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/api/billing/webhook/route.ts` around lines 9 - 18, The webhook
handler currently accepts an empty secret and uses a timing-unsafe equality;
update verifyWebhookSignature to perform HMAC-SHA256 on the payload and compare
the computed and provided signatures using crypto.timingSafeEqual (convert both
to Buffers and first check equal length before timingSafeEqual to avoid
exceptions), and modify the route to fail fast if the secret is missing by
checking process.env.RAZORPAY_WEBHOOK_SECRET (or the local webhookSecret) and
returning an error response (e.g., 500/400) instead of defaulting to "" so no
request is validated against an empty secret.
| const file = await prisma.file.findUnique({ | ||
| where: { id: fileId }, | ||
| select: { id: true, name: true, type: true, projectId: true, blobPath: true }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "schema.prisma" -x cat {}Repository: Mayank-saraswal/moris
Length of output: 4234
🏁 Script executed:
cat -n src/features/conversations/inngest/tools/delete-files.tsRepository: Mayank-saraswal/moris
Length of output: 4182
Critical bug: folder children query uses file.name instead of file.path, and path field is not selected.
Lines 71 and 91 attempt to find folder children using path: { startsWith: ${file.name}/ }, but file.name is only the folder name (e.g., "components"), not the full path (e.g., "src/components"). This query will fail to locate actual children nested under the folder's path.
Additionally, the select clause on line 42 doesn't include the path field, and the filesToDelete type annotation (lines 31-37) omits it, so even if corrected, file.path would be undefined at runtime.
🐛 Required fixes
- Add
pathto the select clause on line 42:
select: { id: true, name: true, type: true, projectId: true, blobPath: true },
+ select: { id: true, name: true, path: true, type: true, projectId: true, blobPath: true },- Add
path: stringto thefilesToDeletetype annotation (lines 31-37):
const filesToDelete: {
id: string;
name: string;
+ path: string;
type: string;
projectId: string;
blobPath: string | null;
}[] = [];- Replace
file.namewithfile.pathon lines 71 and 91:
path: { startsWith: `${file.name}/` },
+ path: { startsWith: `${file.path}/` },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/conversations/inngest/tools/delete-files.ts` around lines 40 -
42, The folder-child lookup bug is caused by selecting only name (not path) and
using file.name for startsWith; update the prisma.file.findUnique select to
include path, add path: string to the filesToDelete type definition, and change
the folder-children queries that currently use file.name to use file.path
instead (e.g., the queries inside the delete-files flow that call path: {
startsWith: `${file.name}/` } should use `${file.path}/` and rely on the
selected file.path).
|
|
||
| // Build tree from path-based files (no content for initial mount) | ||
| const fileTree = buildFileTree(files); | ||
| await container.mount(fileTree); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate the key files mentioned in the review
fd 'buildFileTree|build-file-tree' --type f -e ts -e tsx -e js -e jsxRepository: Mayank-saraswal/moris
Length of output: 47
🏁 Script executed:
# Also search for the use-webcontainer file to understand the context
fd 'use-webcontainer' --type fRepository: Mayank-saraswal/moris
Length of output: 111
🏁 Script executed:
# Search for the useFiles hook
fd 'useFiles|use-files' --type fRepository: Mayank-saraswal/moris
Length of output: 105
🏁 Script executed:
# Look for API routes related to files
fd 'files.*route|route.*files' --type fRepository: Mayank-saraswal/moris
Length of output: 47
🏁 Script executed:
# Search for the Prisma File model definition
fd 'schema.prisma' --type fRepository: Mayank-saraswal/moris
Length of output: 85
🏁 Script executed:
# Read the use-webcontainer.ts file to see the context and understand the flow
cat -n src/features/preview/hooks/use-webcontainer.tsRepository: Mayank-saraswal/moris
Length of output: 6408
🏁 Script executed:
# Read the use-files.ts hook
cat -n src/features/projects/hooks/use-files.tsRepository: Mayank-saraswal/moris
Length of output: 6051
🏁 Script executed:
# Read the Prisma schema to understand File model
cat -n prisma/schema.prisma | head -100Repository: Mayank-saraswal/moris
Length of output: 3824
🏁 Script executed:
# Find the file-tree utility file
fd 'file-tree' --type f -e ts -e tsxRepository: Mayank-saraswal/moris
Length of output: 146
🏁 Script executed:
# Search for buildFileTree usage and content loading patterns
rg 'buildFileTree|content.*fetch|blob.*content' --type ts --type tsx -B 3 -A 3Repository: Mayank-saraswal/moris
Length of output: 92
🏁 Script executed:
# Read the buildFileTree implementation
cat -n src/features/preview/utils/file-tree.tsxRepository: Mayank-saraswal/moris
Length of output: 2670
🏁 Script executed:
# Search for content fetching or blob handling
rg 'buildFileTree|content|blob' --type ts -B 2 -A 2Repository: Mayank-saraswal/moris
Length of output: 50377
🏁 Script executed:
# Let me verify the npm install happens immediately after mount by checking the exact flow
sed -n '54,90p' src/features/preview/hooks/use-webcontainer.ts | cat -nRepository: Mayank-saraswal/moris
Length of output: 1889
🏁 Script executed:
# Check if package.json is typically created with blobPath or inline content
rg 'package\.json' --type ts -B 2 -A 2 | head -50Repository: Mayank-saraswal/moris
Length of output: 1053
package.json content not available for initial npm install.
The buildFileTree function (line 45) excludes files with blobPath when no inline content is provided: if (content || !file.blobPath). Since buildFileTree(files) at line 58 is called without the optional fileContents parameter, all files resolve to empty content and are filtered out if they have a blobPath. This means files stored in Azure Blob Storage—including package.json—are absent from the initial mount tree.
The npm install command runs immediately at line 72 after the mount completes at line 59, before any content is fetched. This will fail with a missing or empty package.json.
Blob content must be fetched and loaded into the file tree before mounting, or the install step must be deferred until content is available.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/preview/hooks/use-webcontainer.ts` around lines 56 - 59, The
mount is missing files that only have blobPath (so package.json is absent)
because buildFileTree(files) is called before blob content is fetched; fetch and
await blob contents for any file with a blobPath (including package.json) and
supply those contents to buildFileTree (or otherwise populate files' content)
before calling container.mount(fileTree), or alternatively defer the npm install
step until after blob fetch and mount complete; reference buildFileTree,
container.mount, files, blobPath and the npm install step to locate where to
preload/await blob content and then proceed with mount and install.
| // Sync file changes to WebContainer (hot-reload) | ||
| useEffect(() => { | ||
| const container = containerRef.current; | ||
| if (!container || !files || status !== "running") return; | ||
|
|
||
| const filesMap = new Map(files.map((f) => [f._id, f])); | ||
|
|
||
| for (const file of files) { | ||
| if (file.type !== "file" || file.storageId || !file.content) continue; | ||
|
|
||
| const filePath = getFilePath(file, filesMap); | ||
| container.fs.writeFile(filePath, file.content); | ||
| for (const file of (files ?? [])) { | ||
| if (file.type !== "file") continue; | ||
| // Write empty file placeholder — content will be loaded when needed | ||
| const filePath = getFilePath(file); | ||
| container.fs.writeFile(filePath, "").catch(() => { | ||
| // Ignore errors for files that can't be written | ||
| }); | ||
| } | ||
| }, [files, status]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and inspect the buildFileTree function
rg -n "buildFileTree" --type=ts -A 30 src/Repository: Mayank-saraswal/moris
Length of output: 8037
🏁 Script executed:
# Check the FileItem type and useFiles hook
rg -n "FileItem\|useFiles" --type=ts -B 2 -A 5 src/ | head -100Repository: Mayank-saraswal/moris
Length of output: 47
🏁 Script executed:
# Examine the use-files.ts to understand the data structure
find src -name "use-files.ts" -o -name "*use-files*" | head -5Repository: Mayank-saraswal/moris
Length of output: 105
🏁 Script executed:
# Check how files are imported and what properties they have
rg -n "interface FileItem\|type FileItem" --type=ts -A 10Repository: Mayank-saraswal/moris
Length of output: 47
🏁 Script executed:
# Get the complete buildFileTree function
cat -n src/features/preview/utils/file-tree.tsx | head -80Repository: Mayank-saraswal/moris
Length of output: 2670
🏁 Script executed:
# Check the full use-webcontainer.ts to see how buildFileTree is called and what's passed
cat -n src/features/preview/hooks/use-webcontainer.tsRepository: Mayank-saraswal/moris
Length of output: 6408
🏁 Script executed:
# Inspect use-files.ts to understand the FileRecord type and data structure
cat -n src/features/projects/hooks/use-files.tsRepository: Mayank-saraswal/moris
Length of output: 6051
🏁 Script executed:
# Double-check if fileContents is ever populated in use-webcontainer.ts
rg -n "fileContents" src/features/preview/hooks/use-webcontainer.tsRepository: Mayank-saraswal/moris
Length of output: 47
🏁 Script executed:
# Check if there's any call to fetch file content or pass fileContents to buildFileTree
rg -n "useFileContent\|fetchContent" src/features/preview/ --type=tsRepository: Mayank-saraswal/moris
Length of output: 47
🏁 Script executed:
# Check if buildFileTree is called anywhere else with fileContents
rg -n "buildFileTree" --type=ts -B 2 -A 5Repository: Mayank-saraswal/moris
Length of output: 2113
🏁 Script executed:
# Verify the logic: if a file has blobPath and no content, it's excluded from the tree
# Check if any files actually have content loaded during initial mount
rg -n "blobPath\|fileContents" src/features/preview/ --type=ts -B 2 -A 2Repository: Mayank-saraswal/moris
Length of output: 47
Hot-reload sync writes empty strings to every file, destroying content; files with blob storage are not mounted initially.
The hot-reload effect (lines 115–128) iterates through all files when files changes during the "running" state and writes "" to each path via container.fs.writeFile. This destroys any file content that exists on the mounted filesystem.
Additionally, the initial mount at line 58 calls buildFileTree(files) without populating file content. Since buildFileTree defaults to empty string for missing content (line 44), and the inclusion logic at line 45 excludes files where content || !file.blobPath evaluates to false, files stored in Azure blob storage (with blobPath) are excluded entirely from the mount. This breaks projects that rely on content from blob storage (e.g., package.json required for npm install).
The comment on line 122 claims "content will be loaded when needed," but no mechanism exists to load or sync actual file content—only empty strings are written.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/preview/hooks/use-webcontainer.ts` around lines 115 - 128, The
hot-reload effect is currently overwriting every path with an empty string via
container.fs.writeFile (useEffect referencing containerRef, getFilePath and
container.fs.writeFile) and buildFileTree is excluding files that only have
blobPath, so blob-backed files never get mounted; fix by changing the sync
effect to only write files that have real content (e.g., file.content != null)
and skip writing placeholders for blob-backed files, and update buildFileTree
(and the mounting flow) to include files with blobPath by either pre-fetching
their content before mounting or by wiring a lazy loader that fetches blob
content when the file is first read (ensure getFilePath, buildFileTree, and the
mounting logic treat blobPath files as present rather than omitted).
| className="h-auto items-start justify-start p-4 bg-background border rounded-none flex flex-col gap-2" | ||
| > | ||
| <Link href={`/projects/${data._id}`} className="group"> | ||
| <Link href={`/projects/${data.id}`} className="group"> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check which route pattern exists in the app directory
echo "=== Looking for route files under src/app/projects ==="
fd . src/app/projects -type f 2>/dev/null | head -20
echo "=== Looking for route files under src/app/project ==="
fd . src/app/project -type f 2>/dev/null | head -20
echo "=== All directories at src/app top level ==="
ls -la src/app/ 2>/dev/null | grep '^d'
echo "=== Directory structure of src/app (up to depth 3) ==="
tree -d -L 3 src/app 2>/dev/null || find src/app -type d -maxdepth 3 | sortRepository: Mayank-saraswal/moris
Length of output: 1111
Fix inconsistent route: ProjectItem links to non-existent /project/ route.
The app defines src/app/projects/[projectId], not /project/. Line 67 must be /projects/${data.id} to match line 41 and the actual route structure, otherwise users will encounter 404 errors when clicking on project items.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/projects/components/projects-list.tsx` at line 41, The
ProjectItem component uses an incorrect route (`/project/${data.id}`) causing
404s; update the Link href in the projects-list component so it points to the
correct route `/projects/${data.id}` to match the app route defined in
src/app/projects/[projectId] (look for the Link usage inside ProjectItem /
projects-list.tsx and replace the singular `/project/` path with `/projects/`).
| @@ -0,0 +1,145 @@ | |||
| import { Sandbox } from "e2b"; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if e2b is declared in any package manifest
fd package.json --max-depth 2 --exec grep -l "e2b" {} 2>/dev/null
echo "---"
# Also check if node_modules/e2b exists or if there's a resolution
rg '"e2b"' --type json -g 'package.json' -nRepository: Mayank-saraswal/moris
Length of output: 68
🏁 Script executed:
#!/bin/bash
# Show the actual package.json to see what dependencies ARE declared
cat package.json | head -100Repository: Mayank-saraswal/moris
Length of output: 3728
Add e2b to package.json dependencies.
The file imports Sandbox from "e2b" at line 1, but the package is not declared in the project's package.json. This will cause the import to fail. Add e2b (with the appropriate version) to the dependencies section.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/e2b-sandbox.ts` at line 1, The import of Sandbox from "e2b" in
src/lib/e2b-sandbox.ts will fail because "e2b" is not declared in package.json;
add "e2b" to the dependencies section of package.json (choose the appropriate
semver version for your project or use latest) so the import statement import {
Sandbox } from "e2b"; resolves correctly and runs npm/yarn/pnpm install to
update lockfile.
| export function createRateLimiter( | ||
| requests: number = 20, | ||
| windowSeconds: number = 60 | ||
| ) { | ||
| return new Ratelimit({ | ||
| redis: getRedis(), | ||
| limiter: Ratelimit.slidingWindow(requests, `${windowSeconds} s`), | ||
| analytics: true, | ||
| prefix: "moris:ratelimit", | ||
| }); | ||
| } | ||
|
|
||
| // Pre-configured rate limiters for different endpoints | ||
| export const rateLimiters = { | ||
| messages: () => createRateLimiter(15, 60), // 15 messages/min | ||
| suggestions: () => createRateLimiter(60, 60), // 60 suggestions/min | ||
| quickEdit: () => createRateLimiter(20, 60), // 20 edits/min | ||
| billing: () => createRateLimiter(5, 60), // 5 billing actions/min | ||
| }; |
There was a problem hiding this comment.
All rate limiters share the same Redis prefix — rate limit counters are conflated across endpoints.
Every limiter created by createRateLimiter uses the fixed prefix "moris:ratelimit". Since Upstash Ratelimit keys are {prefix}:{identifier}, calling rateLimiters.messages().limit(userId) and rateLimiters.suggestions().limit(userId) read/write the same Redis key. The 15 req/min messages limit will incorrectly count suggestion requests (and vice versa), causing premature 429s.
Pass a unique prefix per endpoint:
Proposed fix
export function createRateLimiter(
+ name: string,
requests: number = 20,
windowSeconds: number = 60
) {
return new Ratelimit({
redis: getRedis(),
limiter: Ratelimit.slidingWindow(requests, `${windowSeconds} s`),
analytics: true,
- prefix: "moris:ratelimit",
+ prefix: `moris:ratelimit:${name}`,
});
}
export const rateLimiters = {
- messages: () => createRateLimiter(15, 60),
- suggestions: () => createRateLimiter(60, 60),
- quickEdit: () => createRateLimiter(20, 60),
- billing: () => createRateLimiter(5, 60),
+ messages: () => createRateLimiter("messages", 15, 60),
+ suggestions: () => createRateLimiter("suggestions", 60, 60),
+ quickEdit: () => createRateLimiter("quick-edit", 20, 60),
+ billing: () => createRateLimiter("billing", 5, 60),
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/redis.ts` around lines 23 - 41, The current createRateLimiter uses a
fixed prefix ("moris:ratelimit") causing counters to be shared across endpoints;
change createRateLimiter to accept a prefix parameter (e.g.,
createRateLimiter(prefix: string, requests?: number, windowSeconds?: number))
and pass that into the Ratelimit config instead of the hard-coded string, then
update the rateLimiters map (messages, suggestions, quickEdit, billing) to call
createRateLimiter with unique prefixes like "moris:ratelimit:messages",
"moris:ratelimit:suggestions", etc., so each endpoint uses its own Redis
keyspace and counters.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
.agents/skills/frontend-design/SKILL.md (1)
42-42: Hardcoded model nameClaudereduces portability of the skill.The closing line names a specific vendor model while the rest of the document is deliberately model-agnostic. If this skill framework is ever used with a different model, or if the product is redistributed, this line becomes misleading.
Consider replacing with a generic reference, e.g.:
✏️ Suggested wording
-Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. +Remember: this skill is capable of extraordinary creative work. Don't hold back — show what can truly be created when committing fully to a distinctive, well-reasoned vision.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/frontend-design/SKILL.md at line 42, The closing sentence in .agents/skills/frontend-design/SKILL.md hardcodes the vendor model name "Claude", reducing portability; update that line to use a model-agnostic phrase (e.g., "the assistant", "the model", or "the generated creative model") and scan the file for any other occurrences of "Claude" to replace them similarly so the skill remains vendor-neutral and consistent with the rest of the document.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.agents/skills/frontend-design/LICENSE.txt:
- Line 177: The LICENSE.txt is missing the canonical Apache License 2.0 APPENDIX
after the line "END OF TERMS AND CONDITIONS"; append the standard informational
APPENDIX text from the official Apache License 2.0 template (the paragraph(s)
beginning with "APPENDIX: How to apply the Apache License to your work"
including the short guidance on including a NOTICE file and recommended header)
immediately following "END OF TERMS AND CONDITIONS" so the file matches the
canonical template.
In @.agents/skills/ui-ux-pro-max/SKILL.md:
- Line 3: Standardize the capability counts by choosing a canonical set (e.g.,
the values currently in the description string: "50 styles, 21 palettes, 50 font
pairings, 20 charts, 9 stacks") and update every duplicated occurrence in
SKILL.md so they match exactly; search for the "description:" field and all
other places that list palettes/font pairings/charts/stacks and replace their
counts to the chosen canonical numbers, keeping formatting and punctuation
identical to the original description string.
- Around line 178-184: The fenced code block in SKILL.md is missing a language
identifier which triggers markdownlint MD040; update the opening fence ("```")
to include a language (e.g., "```text" or "```md") so the block becomes
"```text" and keep the block contents unchanged; search for the exact fenced
block text in SKILL.md to locate and update it.
- Around line 138-139: The CLI examples in SKILL.md use the non-existent command
string "python3 skills/ui-ux-pro-max/scripts/search.py ..." so update all
affected examples to point to a valid executable path or add the missing script
and fix the symlink: either 1) change every occurrence of "python3
skills/ui-ux-pro-max/scripts/search.py" to the real runnable path (e.g., the
actual script location under .agents/skills/ui-ux-pro-max/scripts or the correct
src path used in the repo), or 2) implement a search.py at the expected location
(and update/repair the symlink target to ../../../src/ui-ux-pro-max/scripts if
you place it under src) so the command works as documented; ensure each example
(the ones using the "<product_type> <industry> <keywords>" pattern and flags
like --design-system / -p) is updated consistently.
In @.agents/skills/web-design-guidelines/SKILL.md:
- Around line 25-27: The fenced code block containing the raw URL (the block
with
"https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md")
lacks a language identifier and triggers markdownlint MD040; update that fenced
block in SKILL.md by adding a language tag (e.g., use "text" or "markdown" such
as ```text) immediately after the opening backticks so the block becomes fenced
with a language identifier.
- Around line 16-27: Replace the mutable guidelines URL under the "Guidelines
Source" block with a commit-pinned URL (replace `.../main/command.md` with the
same path at a specific commit SHA) so reviews are deterministic; update the
text near the URL to note the pinned commit SHA and add a short bumping policy
(e.g., "bump monthly or on-demand via manual review" and how to update the
`<commit-sha>`) so maintainers know when/how to advance the pinned revision —
edit the SKILL.md Guidelines Source section to perform these changes.
---
Nitpick comments:
In @.agents/skills/frontend-design/SKILL.md:
- Line 42: The closing sentence in .agents/skills/frontend-design/SKILL.md
hardcodes the vendor model name "Claude", reducing portability; update that line
to use a model-agnostic phrase (e.g., "the assistant", "the model", or "the
generated creative model") and scan the file for any other occurrences of
"Claude" to replace them similarly so the skill remains vendor-neutral and
consistent with the rest of the document.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
.agents/skills/frontend-design/LICENSE.txt.agents/skills/frontend-design/SKILL.md.agents/skills/ui-ux-pro-max/SKILL.md.agents/skills/ui-ux-pro-max/data.agents/skills/ui-ux-pro-max/scripts.agents/skills/web-design-guidelines/SKILL.mdskills-lock.jsonsrc/app/api/projects/[projectId]/files/route.ts
✅ Files skipped from review due to trivial changes (3)
- skills-lock.json
- .agents/skills/ui-ux-pro-max/data
- .agents/skills/ui-ux-pro-max/scripts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/app/api/projects/[projectId]/files/route.ts
| incurred by, or claims asserted against, such Contributor by reason | ||
| of your accepting any such warranty or additional liability. | ||
|
|
||
| END OF TERMS AND CONDITIONS |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the LICENSE.txt file
cat -n .agents/skills/frontend-design/LICENSE.txt | tail -20Repository: Mayank-saraswal/moris
Length of output: 1426
🌐 Web query:
Apache License 2.0 full canonical text complete official template appendix
💡 Result:
Canonical official Apache License 2.0 (full text + official APPENDIX template):
- Plain-text canonical license (includes “END OF TERMS AND CONDITIONS” + “APPENDIX: How to apply the Apache License to your work”): (apache.org)
https://www.apache.org/licenses/LICENSE-2.0.txt
- HTML canonical license page (same content; links to the plain-text “Text version”): (apache.org)
https://www.apache.org/licenses/LICENSE-2.0
Citations:
License file is missing the standard Apache 2.0 Appendix.
The canonical Apache License 2.0 template includes an informational APPENDIX after END OF TERMS AND CONDITIONS explaining how to apply the license to a work. While its omission doesn't invalidate the license, using the full canonical text avoids ambiguity and is conventional practice.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/frontend-design/LICENSE.txt at line 177, The LICENSE.txt is
missing the canonical Apache License 2.0 APPENDIX after the line "END OF TERMS
AND CONDITIONS"; append the standard informational APPENDIX text from the
official Apache License 2.0 template (the paragraph(s) beginning with "APPENDIX:
How to apply the Apache License to your work" including the short guidance on
including a NOTICE file and recommended header) immediately following "END OF
TERMS AND CONDITIONS" so the file matches the canonical template.
| @@ -0,0 +1,386 @@ | |||
| --- | |||
| name: ui-ux-pro-max | |||
| description: "UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 9 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient. Integrations: shadcn/ui MCP for component search and examples." | |||
There was a problem hiding this comment.
Resolve conflicting capability counts across the document.
Line 3 says 21 palettes / 50 font pairings / 20 charts / 9 stacks, while Line 8 and Line 212 present different totals. Please keep one canonical set of numbers to avoid user confusion.
Also applies to: 8-8, 212-212
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/ui-ux-pro-max/SKILL.md at line 3, Standardize the capability
counts by choosing a canonical set (e.g., the values currently in the
description string: "50 styles, 21 palettes, 50 font pairings, 20 charts, 9
stacks") and update every duplicated occurrence in SKILL.md so they match
exactly; search for the "description:" field and all other places that list
palettes/font pairings/charts/stacks and replace their counts to the chosen
canonical numbers, keeping formatting and punctuation identical to the original
description string.
| python3 skills/ui-ux-pro-max/scripts/search.py "<product_type> <industry> <keywords>" --design-system [-p "Project Name"] | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Finding all search.py files ==="
fd -a 'search.py$'
echo ""
echo "=== Checking if skills directory exists at root ==="
if [ -d "skills" ]; then
ls -la skills/
else
echo "skills/ directory does NOT exist at repository root"
fi
echo ""
echo "=== Checking .agents/skills structure ==="
if [ -d ".agents/skills" ]; then
ls -la .agents/skills/
fi
echo ""
echo "=== Checking for symlinks ==="
ls -la | grep "skills\|agents"
echo ""
echo "=== Reading SKILL.md lines mentioned in review ==="
wc -l .agents/skills/ui-ux-pro-max/SKILL.mdRepository: Mayank-saraswal/moris
Length of output: 805
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Extract command examples from lines 138-139 ==="
sed -n '138,139p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "=== Extract command examples from all mentioned lines ==="
echo "Lines 149-150:"
sed -n '149,150p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "Lines 157-158:"
sed -n '157,158p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "Lines 166-167:"
sed -n '166,167p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "Lines 191-192:"
sed -n '191,192p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "Lines 209-210:"
sed -n '209,210p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "Lines 263-264:"
sed -n '263,264p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "Lines 272-276:"
sed -n '272,276p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "Lines 281-282:"
sed -n '281,282p' .agents/skills/ui-ux-pro-max/SKILL.md
echo ""
echo "Lines 294-298:"
sed -n '294,298p' .agents/skills/ui-ux-pro-max/SKILL.mdRepository: Mayank-saraswal/moris
Length of output: 1636
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Contents of .agents/skills/ui-ux-pro-max/ ==="
ls -la .agents/skills/ui-ux-pro-max/
echo ""
echo "=== Check if scripts directory exists ==="
if [ -d ".agents/skills/ui-ux-pro-max/scripts" ]; then
echo "scripts/ directory exists. Contents:"
ls -la .agents/skills/ui-ux-pro-max/scripts/
else
echo "scripts/ directory does NOT exist at .agents/skills/ui-ux-pro-max/"
fiRepository: Mayank-saraswal/moris
Length of output: 510
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check what 'scripts' actually is ==="
file .agents/skills/ui-ux-pro-max/scripts
cat .agents/skills/ui-ux-pro-max/scripts
echo ""
echo "=== Verify the exact relative path from SKILL.md ==="
cd .agents/skills/ui-ux-pro-max
echo "Current location: $(pwd)"
echo "SKILL.md is in this directory"
echo "If scripts/search.py existed here, the relative path would be: ./scripts/search.py or scripts/search.py"
echo "But from repository root, it would be: .agents/skills/ui-ux-pro-max/scripts/search.py"Repository: Mayank-saraswal/moris
Length of output: 167
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check what 'scripts' actually is ==="
cat .agents/skills/ui-ux-pro-max/scripts
echo ""
echo "=== Check what 'data' actually is ==="
cat .agents/skills/ui-ux-pro-max/data
echo ""
echo "=== List entire repository structure to understand layout ==="
find . -maxdepth 3 -type f -name "*.py" | head -20Repository: Mayank-saraswal/moris
Length of output: 272
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Resolve the symlink pointers ==="
echo "scripts pointer content: $(cat .agents/skills/ui-ux-pro-max/scripts)"
echo "Resolving to: src/ui-ux-pro-max/scripts"
echo ""
echo "=== Check if src/ui-ux-pro-max/scripts/search.py exists ==="
if [ -f "src/ui-ux-pro-max/scripts/search.py" ]; then
echo "✓ Found: src/ui-ux-pro-max/scripts/search.py"
wc -l src/ui-ux-pro-max/scripts/search.py
else
echo "✗ NOT found at src/ui-ux-pro-max/scripts/search.py"
fi
echo ""
echo "=== Find all search.py files in the repository ==="
find . -name "search.py" -type f 2>/dev/null || echo "No search.py found"
echo ""
echo "=== List repository root structure ==="
ls -la | grep -E "^d|^l"Repository: Mayank-saraswal/moris
Length of output: 798
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Explore src/ui-ux-pro-max directory ==="
if [ -d "src/ui-ux-pro-max" ]; then
echo "src/ui-ux-pro-max exists. Contents:"
ls -la src/ui-ux-pro-max/
else
echo "src/ui-ux-pro-max does NOT exist"
fi
echo ""
echo "=== List all files in src ==="
find src -maxdepth 3 -type f | head -30Repository: Mayank-saraswal/moris
Length of output: 1294
Update CLI path examples: commands reference non-existent paths and script files.
The command examples throughout this file use python3 skills/ui-ux-pro-max/scripts/search.py, but:
- No
skills/directory exists at the repository root - The symlink at
.agents/skills/ui-ux-pro-max/scriptspoints to../../../src/ui-ux-pro-max/scripts, which does not exist - The
search.pyscript is not present in the repository
These documented commands will fail when copied and executed. Update all command examples to use the correct path, or implement the missing script file(s).
Affected lines: 138-139, 149-150, 157-158, 166-167, 191-192, 209-210, 263-264, 272-276, 281-282, 294-298
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/ui-ux-pro-max/SKILL.md around lines 138 - 139, The CLI
examples in SKILL.md use the non-existent command string "python3
skills/ui-ux-pro-max/scripts/search.py ..." so update all affected examples to
point to a valid executable path or add the missing script and fix the symlink:
either 1) change every occurrence of "python3
skills/ui-ux-pro-max/scripts/search.py" to the real runnable path (e.g., the
actual script location under .agents/skills/ui-ux-pro-max/scripts or the correct
src path used in the repo), or 2) implement a search.py at the expected location
(and update/repair the symlink target to ../../../src/ui-ux-pro-max/scripts if
you place it under src) so the command works as documented; ensure each example
(the ones using the "<product_type> <industry> <keywords>" pattern and flags
like --design-system / -p) is updated consistently.
| ``` | ||
| I am building the [Page Name] page. Please read design-system/MASTER.md. | ||
| Also check if design-system/pages/[page-name].md exists. | ||
| If the page file exists, prioritize its rules. | ||
| If not, use the Master rules exclusively. | ||
| Now, generate the code... | ||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced code block.
Line 178 starts a fenced block without a language, which triggers markdownlint MD040.
Suggested fix
-```
+```text
I am building the [Page Name] page. Please read design-system/MASTER.md.
Also check if design-system/pages/[page-name].md exists.
If the page file exists, prioritize its rules.
If not, use the Master rules exclusively.
Now, generate the code...</details>
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.21.0)</summary>
[warning] 178-178: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/ui-ux-pro-max/SKILL.md around lines 178 - 184, The fenced
code block in SKILL.md is missing a language identifier which triggers
markdownlint MD040; update the opening fence ("") to include a language (e.g., "text" or "md") so the block becomes "text" and keep the block
contents unchanged; search for the exact fenced block text in SKILL.md to locate
and update it.
</details>
<!-- fingerprinting:phantom:triton:hawk -->
<!-- This is an auto-generated comment by CodeRabbit -->
| 1. Fetch the latest guidelines from the source URL below | ||
| 2. Read the specified files (or prompt user for files/pattern) | ||
| 3. Check against all rules in the fetched guidelines | ||
| 4. Output findings in the terse `file:line` format | ||
|
|
||
| ## Guidelines Source | ||
|
|
||
| Fetch fresh guidelines before each review: | ||
|
|
||
| ``` | ||
| https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if the file exists and show its full content
if [ -f ".agents/skills/web-design-guidelines/SKILL.md" ]; then
echo "=== File exists. Full content with line numbers ==="
cat -n ".agents/skills/web-design-guidelines/SKILL.md"
else
echo "File not found. Searching for SKILL.md files..."
find . -name "SKILL.md" -type f 2>/dev/null | head -20
fiRepository: Mayank-saraswal/moris
Length of output: 1622
🏁 Script executed:
#!/bin/bash
# Search for raw GitHub URLs with mutable branches (main/master)
echo "=== Searching for mutable raw GitHub URLs in .md files ==="
rg -n --type md 'raw\.githubusercontent\.com/.+/(main|master)/' --max-count=20Repository: Mayank-saraswal/moris
Length of output: 123
Pin the guidelines source to an immutable revision.
The URL at line 26 currently points to the mutable main branch, making review outcomes nondeterministic and allowing upstream rule changes to alter behavior unexpectedly.
Proposed fix
-https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md
+https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/<commit-sha>/command.mdAdd a short note on how/when to bump <commit-sha> (e.g., monthly or manual review).
📝 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.
| 1. Fetch the latest guidelines from the source URL below | |
| 2. Read the specified files (or prompt user for files/pattern) | |
| 3. Check against all rules in the fetched guidelines | |
| 4. Output findings in the terse `file:line` format | |
| ## Guidelines Source | |
| Fetch fresh guidelines before each review: | |
| ``` | |
| https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md | |
| ``` | |
| 1. Fetch the latest guidelines from the source URL below | |
| 2. Read the specified files (or prompt user for files/pattern) | |
| 3. Check against all rules in the fetched guidelines | |
| 4. Output findings in the terse `file:line` format | |
| ## Guidelines Source | |
| Fetch fresh guidelines before each review: | |
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 25-25: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/web-design-guidelines/SKILL.md around lines 16 - 27, Replace
the mutable guidelines URL under the "Guidelines Source" block with a
commit-pinned URL (replace `.../main/command.md` with the same path at a
specific commit SHA) so reviews are deterministic; update the text near the URL
to note the pinned commit SHA and add a short bumping policy (e.g., "bump
monthly or on-demand via manual review" and how to update the `<commit-sha>`) so
maintainers know when/how to advance the pinned revision — edit the SKILL.md
Guidelines Source section to perform these changes.
| ``` | ||
| https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md | ||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced block.
Line 25 starts a fenced code block without a language, which triggers markdownlint MD040.
Suggested fix
-```
+```text
https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **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.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 25-25: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/web-design-guidelines/SKILL.md around lines 25 - 27, The
fenced code block containing the raw URL (the block with
"https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md")
lacks a language identifier and triggers markdownlint MD040; update that fenced
block in SKILL.md by adding a language tag (e.g., use "text" or "markdown" such
as ```text) immediately after the opening backticks so the block becomes fenced
with a language identifier.
…iting features with associated APIs and UI components.
Summary by CodeRabbit