Add shadcn/ui components#20
Conversation
- Initialize shadcn with Tailwind v4 and Neutral base color - Add components.json configuration - Add src/lib/utils.js (cn utility) - Add UI components: button, card, badge, tabs, textarea Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a shadcn/ui-compatible components.json and new npm scripts and dependencies in package.json. Introduces global Tailwind/CSS theming and a Claude-inspired design system in src/app/app.css and a cn utility in src/lib/utils.js. Adds theme provider and toggle, a Logo component, multiple UI primitives under src/components/ui (Badge, Button, Card and subcomponents, Tabs, Textarea), and UI assets. Replaces app layout and landing page, adds a client AI dashboard at src/app/aigit/page.js, and implements server API routes for AI-assisted code review and commit-message suggestions. Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 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: 1
🧹 Nitpick comments (2)
package.json (2)
22-22: Unrelated dependency:@anthropic-ai/sdkThe Anthropic SDK dependency appears unrelated to the shadcn/ui component additions described in the PR objectives. If this is intentional, consider documenting its purpose. If it's part of the
vibetooling, consider bundling it with those changes in a separate PR.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 22, The package.json includes an unrelated dependency "@anthropic-ai/sdk" which doesn't appear to be used by the shadcn/ui work; remove this dependency from package.json (or move it into a separate PR/feature branch) and update package-lock/yarn-lock accordingly, or if it is intentional, add a brief note in the PR description and project docs explaining its purpose; search for any references to "@anthropic-ai/sdk" in the repo to confirm whether it is unused before removal and update the commit/PR message to reflect the change.
10-19: Unrelated scripts bundled in this PR?These
vibe:*scripts and thegitscript appear unrelated to the PR objective of adding shadcn/ui components. Consider splitting them into a separate PR for cleaner change tracking and easier review.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 10 - 19, The package.json was modified to add unrelated CLI scripts ("git", and the "vibe", "vibe:setup", "vibe:uninstall", "vibe:hooks", "vibe:status", "vibe:commit", "vibe:analyze", "vibe:review", "vibe:pr" entries) which are not part of the shadcn/ui changes; remove these script entries from this PR by reverting those additions in package.json so this change only contains shadcn/ui-related edits, then create a separate branch/PR that adds the "git" and all "vibe:*" script entries (or include them in your tooling/setup PR) so they can be reviewed independently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ui/card.jsx`:
- Around line 27-29: The Tailwind arbitrary selector in the className passed to
the CardHeader component is using a descendant selector `[.border-b]:pb-6` which
targets children instead of the CardHeader itself; update the class string
inside the cn(...) call (the className assignment in src/components/ui/card.jsx)
to use the parent-aware selector `[&.border-b]:pb-6` so the padding is applied
when the CardHeader element has the border-b class.
---
Nitpick comments:
In `@package.json`:
- Line 22: The package.json includes an unrelated dependency "@anthropic-ai/sdk"
which doesn't appear to be used by the shadcn/ui work; remove this dependency
from package.json (or move it into a separate PR/feature branch) and update
package-lock/yarn-lock accordingly, or if it is intentional, add a brief note in
the PR description and project docs explaining its purpose; search for any
references to "@anthropic-ai/sdk" in the repo to confirm whether it is unused
before removal and update the commit/PR message to reflect the change.
- Around line 10-19: The package.json was modified to add unrelated CLI scripts
("git", and the "vibe", "vibe:setup", "vibe:uninstall", "vibe:hooks",
"vibe:status", "vibe:commit", "vibe:analyze", "vibe:review", "vibe:pr" entries)
which are not part of the shadcn/ui changes; remove these script entries from
this PR by reverting those additions in package.json so this change only
contains shadcn/ui-related edits, then create a separate branch/PR that adds the
"git" and all "vibe:*" script entries (or include them in your tooling/setup PR)
so they can be reviewed independently.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
components.jsonpackage.jsonsrc/app/app.csssrc/components/ui/badge.jsxsrc/components/ui/button.jsxsrc/components/ui/card.jsxsrc/components/ui/tabs.jsxsrc/components/ui/textarea.jsxsrc/lib/utils.js
There was a problem hiding this comment.
Pull request overview
This PR introduces shadcn/ui foundations into the Next.js app (Tailwind v4 setup, shared cn helper, and a set of initial UI primitives) to enable consistent styling and reusable components across the codebase.
Changes:
- Add shadcn/ui configuration (
components.json) and Tailwind v4 theme/import wiring (src/app/app.css). - Add
cnutility helper (src/lib/utils.js) for conditional className composition + Tailwind class merging. - Add initial UI components under
src/components/ui/(button, badge, card, tabs, textarea).
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| components.json | Initializes shadcn/ui config (Tailwind v4 + neutral base, aliases). |
| package.json | Adds dependencies/devDependencies needed for shadcn + Radix + Tailwind merge utilities; also adds several new npm scripts. |
| src/app/app.css | Imports shadcn/tw-animate styles and defines CSS variables/theme + base layer. |
| src/lib/utils.js | Adds cn() helper using clsx + tailwind-merge. |
| src/components/ui/button.jsx | Adds shadcn-style Button + buttonVariants. |
| src/components/ui/badge.jsx | Adds shadcn-style Badge + badgeVariants. |
| src/components/ui/card.jsx | Adds Card component suite (CardHeader/Content/Footer/etc). |
| src/components/ui/tabs.jsx | Adds Tabs primitives wrapper + variants. |
| src/components/ui/textarea.jsx | Adds Textarea component wrapper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@copilot open a new pull request to apply changes based on the comments in this thread |
- Replace home page with ClawGit production landing page (hero, features, pricing, CTA) - Replace aigit dashboard with enhanced UI using shadcn components (tabs, cards, usage tracking) - Fix layout.js: import global CSS, remove hardcoded light background, update metadata Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both /api/aigit/review and /api/aigit/suggest now accept a diff field in the request body from the browser dashboard, falling back to local git diff for CLI usage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/aigit/page.js`:
- Around line 80-82: The "Upgrade to Pro" Button elements are inert and need
navigation handlers; locate the Button JSX instances with the exact text
"Upgrade to Pro" and make them actionable by either wrapping them with a Link to
your upgrade/pricing path or adding an onClick that calls next/navigation's
router.push('/pricing') (or window.location.href='/pricing' if you prefer a full
reload), and ensure accessibility by adding role/aria-label as needed; apply the
same change to the other identical Button occurrences so each CTA actually
navigates the user to the upgrade flow or opens the intended modal.
- Around line 182-183: The JSX condition currently uses a truthy check on
result.score which hides valid 0 values; change the rendering guard in the
component (where result && result.score is used before <Card
className="bg-slate-950 border-slate-700 mt-6">) to explicitly check for
presence (e.g., result.score !== null && result.score !== undefined or typeof
result.score === 'number') so zero scores render correctly while still avoiding
undefined/null.
- Around line 18-35: Update handleReview and handleCommitMessage so they check
the HTTP response status before parsing or incrementing usage: after awaiting
fetch, verify res.ok and only call res.json(), setResult, and setUsageCount when
res.ok is true; for non-ok responses parse error body if present or throw to the
catch block so usage is not incremented. Also fix the result rendering to treat
score === 0 as valid by changing the truthy check on result.score to an explicit
null/undefined check, and wire or disable the "Upgrade to Pro" buttons
(referenced in the JSX where those buttons render) to avoid dead UI elements.
- Around line 176-177: Client-side check using usageCount and maxFreeOps is
insufficient; implement server-side per-user quota and rate limiting on the API
routes /api/aigit/review and /api/aigit/suggest. Update the API handlers to
require and validate user sessions (e.g., via your auth/session helper), look up
and enforce a per-user operation count (persisted in your DB or cache),
increment the count atomically before processing, and return 403/429 when the
user exceeds maxFreeOps; add a rate-limiting middleware (IP+user based) to those
handlers to prevent abuse. Also adjust the frontend flow around usageCount (in
page.js) to fetch the server-side remaining quota/status instead of relying on
the mock usageCount alone so the UI reflects the server-enforced limits.
In `@src/app/page.js`:
- Around line 67-69: The "Watch Demo" CTA is a dead button with no handler;
update the Button in src/app/page.js (the Button element rendering "▶ Watch
Demo") to navigate to the demo resource by either wrapping it with your routing
Link component or adding an onClick handler that opens the demo URL (e.g., /demo
or external demo link) via router.push or window.open; ensure the handler uses a
semantic action (href or onClick), includes target="_blank" and rel="noopener
noreferrer" if opening an external site, and preserves accessibility by keeping
the Button text and adding an aria-label if needed.
- Around line 27-36: The CTA blocks currently nest Button inside Link (Link +
Button), which is invalid; fix each instance by flipping the elements and using
the asChild prop so the anchor renders as the interactive control, e.g. replace
the pattern Link > Button with Button (asChild) > Link (href="...") while
keeping Button props (variant, className, gradient) and the Link href content;
update all occurrences that match the symbols Link and Button (lines noted in
the review: the two at 27–36 plus the others) so no interactive element is
nested.
| const handleReview = async () => { | ||
| if (!diff.trim()) return | ||
|
|
||
| setLoading(true) | ||
| try { | ||
| const res = await fetch('/api/aigit/review', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ diff, vibeMode: true }) | ||
| }) | ||
| const data = await res.json() | ||
| setResult(data) | ||
| setUsageCount(prev => prev + 1) | ||
| } catch (error) { | ||
| console.error('Review failed:', error) | ||
| } finally { | ||
| setLoading(false) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the file structure and read the page component
ls -la src/app/aigit/Repository: appwrite/starter-for-nextjs
Length of output: 242
🏁 Script executed:
# Read the page.js file to verify the handlers and their error handling
wc -l src/app/aigit/page.js && cat -n src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 17331
🏁 Script executed:
# Find API route handlers for aigit
find src -path "*api*aigit*" -type fRepository: appwrite/starter-for-nextjs
Length of output: 139
🏁 Script executed:
# Read the API route handlers to check error handling and quota enforcement
cat -n src/app/api/aigit/review/route.jsRepository: appwrite/starter-for-nextjs
Length of output: 2127
🏁 Script executed:
cat -n src/app/api/aigit/suggest/route.jsRepository: appwrite/starter-for-nextjs
Length of output: 1989
🏁 Script executed:
# Check if upgrade buttons have onClick handlers
grep -n "Upgrade to Pro\|Upgrade Now" src/app/aigit/page.js | head -20Repository: appwrite/starter-for-nextjs
Length of output: 403
🏁 Script executed:
# Verify if there are any event handlers attached to these buttons
rg -A 2 "Upgrade" src/app/aigit/page.js | grep -E "(onClick|href|action)"Repository: appwrite/starter-for-nextjs
Length of output: 53
Check HTTP response status before incrementing usage counter.
Both handleReview (lines 18–35) and handleCommitMessage (lines 38–55) parse JSON responses without checking res.ok. A 400 or 500 error response that returns valid JSON will still increment usage and update state. Additionally, there is no server-side quota enforcement in the API routes, so client-side usage limits are cosmetic—users can exceed them by bypassing the UI.
🛠️ Suggested hardening
const [loading, setLoading] = useState(false)
const [result, setResult] = useState(null)
+const [error, setError] = useState(null)
+const postJson = async (url, payload) => {
+ const res = await fetch(url, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ })
+ const data = await res.json().catch(() => ({}))
+ if (!res.ok) throw new Error(data?.error || "Request failed")
+ return data
+}
const handleReview = async () => {
if (!diff.trim()) return
-
+ setError(null)
setLoading(true)
try {
- const res = await fetch('/api/aigit/review', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ diff, vibeMode: true })
- })
- const data = await res.json()
+ const data = await postJson("/api/aigit/review", { diff, vibeMode: true })
setResult(data)
setUsageCount(prev => prev + 1)
} catch (error) {
- console.error('Review failed:', error)
+ setError(error?.message || "Review failed")
} finally {
setLoading(false)
}
}Also applies to: 38–55
Minor issues: Result rendering at line 182 uses truthy check on result.score, which would hide a valid score of 0. "Upgrade to Pro" buttons (lines 81, 102, 281, 313) have no onClick handlers.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/aigit/page.js` around lines 18 - 35, Update handleReview and
handleCommitMessage so they check the HTTP response status before parsing or
incrementing usage: after awaiting fetch, verify res.ok and only call
res.json(), setResult, and setUsageCount when res.ok is true; for non-ok
responses parse error body if present or throw to the catch block so usage is
not incremented. Also fix the result rendering to treat score === 0 as valid by
changing the truthy check on result.score to an explicit null/undefined check,
and wire or disable the "Upgrade to Pro" buttons (referenced in the JSX where
those buttons render) to avoid dead UI elements.
| <Button className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"> | ||
| Upgrade to Pro | ||
| </Button> |
There was a problem hiding this comment.
Upgrade CTA buttons need real navigation/actions.
These controls are currently inert, so users can’t complete the upgrade path.
🔗 Suggested fix pattern
-<Button className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700">
+<Button
+ onClick={() => window.location.assign("/#pricing")}
+ className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"
+>
Upgrade to Pro
</Button>Also applies to: 102-104, 281-283, 313-315
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/aigit/page.js` around lines 80 - 82, The "Upgrade to Pro" Button
elements are inert and need navigation handlers; locate the Button JSX instances
with the exact text "Upgrade to Pro" and make them actionable by either wrapping
them with a Link to your upgrade/pricing path or adding an onClick that calls
next/navigation's router.push('/pricing') (or window.location.href='/pricing' if
you prefer a full reload), and ensure accessibility by adding role/aria-label as
needed; apply the same change to the other identical Button occurrences so each
CTA actually navigates the user to the upgrade flow or opens the intended modal.
| disabled={loading || !diff.trim() || usageCount >= maxFreeOps} | ||
| className="bg-purple-600 hover:bg-purple-700" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "*.js" -o -name "*.ts" -o -name "*.tsx" | grep -E "(api|route)" | head -20Repository: appwrite/starter-for-nextjs
Length of output: 143
🏁 Script executed:
# Check the file structure of the project
ls -laRepository: appwrite/starter-for-nextjs
Length of output: 1199
🏁 Script executed:
# Look for API routes related to aigit
fd -e js -e ts -e tsx | grep -E "api.*aigit" | head -20Repository: appwrite/starter-for-nextjs
Length of output: 139
🏁 Script executed:
wc -l src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 96
🏁 Script executed:
sed -n '170,180p' src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 656
🏁 Script executed:
sed -n '235,250p' src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 911
🏁 Script executed:
cat src/app/api/aigit/review/route.jsRepository: appwrite/starter-for-nextjs
Length of output: 1763
🏁 Script executed:
cat src/app/api/aigit/suggest/route.jsRepository: appwrite/starter-for-nextjs
Length of output: 1611
🏁 Script executed:
# Check for authentication middleware or global auth checks
find src -type f \( -name "*.js" -o -name "*.ts" \) | xargs grep -l "middleware\|auth\|session" | head -20Repository: appwrite/starter-for-nextjs
Length of output: 108
🏁 Script executed:
# Look for usageCount, maxFreeOps initialization in the UI file
sed -n '1,100p' src/app/aigit/page.js | grep -n "usageCount\|maxFreeOps\|useState\|useEffect"Repository: appwrite/starter-for-nextjs
Length of output: 528
🏁 Script executed:
# Check how usageCount is tracked in page.js
rg -n "usageCount|maxFreeOps" src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 810
🏁 Script executed:
# Search for any rate limiting or quota checking logic
rg -n "quota|limit|usage|free.*tier|plan" src/ --type js --type tsRepository: appwrite/starter-for-nextjs
Length of output: 1563
🏁 Script executed:
# Check how handleReview and handleCommitMessage make API calls
sed -n '130,175p' src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 1944
🏁 Script executed:
# Check the API call in handleCommitMessage too
sed -n '190,240p' src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 2490
🏁 Script executed:
# Look for any session/auth handling in the API routes or middleware
rg -n "session|auth|user|verify" src/app/api/aigit/ -A 2 -B 2Repository: appwrite/starter-for-nextjs
Length of output: 53
🏁 Script executed:
# Find the handler function definitions in page.js
sed -n '260,347p' src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 3829
🏁 Script executed:
# Search for the actual API fetch calls
rg -n "fetch.*api.*aigit" src/app/aigit/page.js -A 5 -B 2Repository: appwrite/starter-for-nextjs
Length of output: 636
🏁 Script executed:
# Look at the entire page.js more systematically - find handleReview and handleCommitMessage
ast-grep --pattern 'const handleReview = $_' src/app/aigit/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 1058
🏁 Script executed:
# Search for any API call patterns in the file
rg -n "fetch\(" src/app/aigit/page.js -A 10Repository: appwrite/starter-for-nextjs
Length of output: 897
🏁 Script executed:
# Final verification: check if there's any session/auth middleware in middleware.js or similar
find src -name "middleware*" -o -name "*auth*" | head -10Repository: appwrite/starter-for-nextjs
Length of output: 53
🏁 Script executed:
# Check package.json for auth libraries
cat package.json | grep -A 20 "dependencies"Repository: appwrite/starter-for-nextjs
Length of output: 670
Add server-side quota and authentication enforcement to API handlers.
The free-tier limit is currently enforced only on the client side via usageCount state (initialized as mock data at line 15). This is bypassable—anyone can call /api/aigit/review and /api/aigit/suggest directly, bypassing the maxFreeOps check entirely. The API handlers have no quota validation, user authentication, session checks, or rate limiting.
Implement:
- Per-user quota tracking (requires user session/auth)
- Server-side operation counting before processing requests
- Reject requests that exceed limits
- Rate limiting middleware on the API routes
This applies to both operations at lines 176-177 and 241-242.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/aigit/page.js` around lines 176 - 177, Client-side check using
usageCount and maxFreeOps is insufficient; implement server-side per-user quota
and rate limiting on the API routes /api/aigit/review and /api/aigit/suggest.
Update the API handlers to require and validate user sessions (e.g., via your
auth/session helper), look up and enforce a per-user operation count (persisted
in your DB or cache), increment the count atomically before processing, and
return 403/429 when the user exceeds maxFreeOps; add a rate-limiting middleware
(IP+user based) to those handlers to prevent abuse. Also adjust the frontend
flow around usageCount (in page.js) to fetch the server-side remaining
quota/status instead of relying on the mock usageCount alone so the UI reflects
the server-enforced limits.
| <Link href="/aigit"> | ||
| <Button variant="outline" className="border-purple-500 text-purple-400 hover:bg-purple-500/10"> | ||
| Dashboard | ||
| </Button> | ||
| </Link> | ||
| <Link href="/aigit"> | ||
| <Button className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"> | ||
| Get Started | ||
| </Button> | ||
| </Link> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Explore repository structure
git ls-files | head -30Repository: appwrite/starter-for-nextjs
Length of output: 635
🏁 Script executed:
#!/bin/bash
# Find the Button component definition
fd -t f '(button|Button)' src/components --type fRepository: appwrite/starter-for-nextjs
Length of output: 99
🏁 Script executed:
#!/bin/bash
# Check src/app/page.js file size and content around the mentioned lines
wc -l src/app/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 90
🏁 Script executed:
#!/bin/bash
# Read the Button component definition
cat -n src/components/ui/button.jsxRepository: appwrite/starter-for-nextjs
Length of output: 2759
🏁 Script executed:
#!/bin/bash
# Read src/app/page.js with line numbers, focusing on the cited sections
cat -n src/app/page.js | head -75Repository: appwrite/starter-for-nextjs
Length of output: 4092
🏁 Script executed:
#!/bin/bash
# Check the remaining cited line ranges
sed -n '217,219p; 243,247p; 285,289p' src/app/page.js | cat -nRepository: appwrite/starter-for-nextjs
Length of output: 755
🏁 Script executed:
#!/bin/bash
# Find all Link/Button nesting patterns in the file
rg -n '<Link[^>]*>\s*<Button|<Button[^>]*>\s*<Link' src/app/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 53
🏁 Script executed:
#!/bin/bash
# Get full context of the cited line ranges with more detail
echo "=== Lines 27-36 ===" && sed -n '27,36p' src/app/page.js | cat -n
echo ""
echo "=== Lines 62-66 ===" && sed -n '62,66p' src/app/page.js | cat -n
echo ""
echo "=== Lines 217-219 ===" && sed -n '217,219p' src/app/page.js | cat -n
echo ""
echo "=== Lines 243-247 ===" && sed -n '243,247p' src/app/page.js | cat -n
echo ""
echo "=== Lines 285-289 ===" && sed -n '285,289p' src/app/page.js | cat -nRepository: appwrite/starter-for-nextjs
Length of output: 1688
🏁 Script executed:
#!/bin/bash
# Count total Link/Button nesting occurrences in the file
rg -c '<Link' src/app/page.js
rg -c '<Button' src/app/page.jsRepository: appwrite/starter-for-nextjs
Length of output: 74
Resolve nested interactive controls in CTA blocks.
Five instances nest Button components inside Link elements, violating HTML semantics and breaking accessibility. Interactive elements cannot nest—this causes keyboard navigation failures and screen reader confusion.
Use the asChild prop to render the link as the interactive element:
Fix pattern
-<Link href="/aigit">
- <Button>Get Started</Button>
-</Link>
+<Button asChild>
+ <Link href="/aigit">Get Started</Link>
+</Button>Applies to: lines 27–36 (2 instances), 62–66, 217–219, 243–247, 285–289.
📝 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.
| <Link href="/aigit"> | |
| <Button variant="outline" className="border-purple-500 text-purple-400 hover:bg-purple-500/10"> | |
| Dashboard | |
| </Button> | |
| </Link> | |
| <Link href="/aigit"> | |
| <Button className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"> | |
| Get Started | |
| </Button> | |
| </Link> | |
| <Button asChild variant="outline" className="border-purple-500 text-purple-400 hover:bg-purple-500/10"> | |
| <Link href="/aigit"> | |
| Dashboard | |
| </Link> | |
| </Button> | |
| <Button asChild className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"> | |
| <Link href="/aigit"> | |
| Get Started | |
| </Link> | |
| </Button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/page.js` around lines 27 - 36, The CTA blocks currently nest Button
inside Link (Link + Button), which is invalid; fix each instance by flipping the
elements and using the asChild prop so the anchor renders as the interactive
control, e.g. replace the pattern Link > Button with Button (asChild) > Link
(href="...") while keeping Button props (variant, className, gradient) and the
Link href content; update all occurrences that match the symbols Link and Button
(lines noted in the review: the two at 27–36 plus the others) so no interactive
element is nested.
| <Button size="lg" variant="outline" className="text-white border-slate-600 hover:bg-slate-800 text-lg px-8 py-6"> | ||
| ▶ Watch Demo | ||
| </Button> |
There was a problem hiding this comment.
“Watch Demo” is currently a dead CTA.
This button has no handler or destination, so the action does nothing.
🎯 Suggested fix
-<Button size="lg" variant="outline" className="text-white border-slate-600 hover:bg-slate-800 text-lg px-8 py-6">
- ▶ Watch Demo
-</Button>
+<Button
+ size="lg"
+ variant="outline"
+ className="text-white border-slate-600 hover:bg-slate-800 text-lg px-8 py-6"
+ disabled
+ aria-disabled="true"
+>
+ ▶ Watch Demo (Coming soon)
+</Button>📝 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.
| <Button size="lg" variant="outline" className="text-white border-slate-600 hover:bg-slate-800 text-lg px-8 py-6"> | |
| ▶ Watch Demo | |
| </Button> | |
| <Button | |
| size="lg" | |
| variant="outline" | |
| className="text-white border-slate-600 hover:bg-slate-800 text-lg px-8 py-6" | |
| disabled | |
| aria-disabled="true" | |
| > | |
| ▶ Watch Demo (Coming soon) | |
| </Button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/page.js` around lines 67 - 69, The "Watch Demo" CTA is a dead button
with no handler; update the Button in src/app/page.js (the Button element
rendering "▶ Watch Demo") to navigate to the demo resource by either wrapping it
with your routing Link component or adding an onClick handler that opens the
demo URL (e.g., /demo or external demo link) via router.push or window.open;
ensure the handler uses a semantic action (href or onClick), includes
target="_blank" and rel="noopener noreferrer" if opening an external site, and
preserves accessibility by keeping the Button text and adding an aria-label if
needed.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
src/app/api/aigit/review/route.js (2)
19-27: Normalize and validate request fields before use.Line 19 currently trusts input types. A string
"false"is truthy and can change control flow, and whitespace-onlydiffcan slip through to Line 46. Coerce and validatestaged,diff, andvibeModefirst.💡 Proposed refactor
+ const allowedVibeModes = new Set(['balanced', 'chill', 'strict']); + const normalizedBodyDiff = + typeof bodyDiff === 'string' ? bodyDiff.trim() : ''; + const useStaged = staged === true; + const normalizedVibeMode = + typeof vibeMode === 'string' && allowedVibeModes.has(vibeMode) + ? vibeMode + : 'balanced'; + const diff = - bodyDiff || - (staged + normalizedBodyDiff || + (useStaged ? engine.getStagedDiff() : engine.getUnstagedDiff() || engine.getStagedDiff()); @@ - const review = await reviewCode(diff, { vibeMode }); + const review = await reviewCode(diff, { vibeMode: normalizedVibeMode });Also applies to: 46-46
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/aigit/review/route.js` around lines 19 - 27, Coerce and validate incoming request fields before using them: convert staged to a true boolean (e.g., staged = String(body.staged).toLowerCase() === 'true' || Boolean(body.staged)), trim and treat bodyDiff as empty/undefined if it is a whitespace-only string (e.g., const bodyDiff = body.diff?.toString()?.trim() || undefined), and validate vibeMode against an allowlist (e.g., 'balanced','creative','precise') falling back to 'balanced' if invalid; then use these normalized values when computing diff (the diff fallback logic that calls engine.getStagedDiff()/engine.getUnstagedDiff()) and elsewhere in the handler to avoid treating strings like "false" as truthy or using blank diffs.
18-19: Consider supporting empty request bodies if CLI usage is planned.While
request.json()on line 18 will throw with an empty body, the only current endpoint caller (browser dashboard atsrc/app/aigit/page.js) always sends a body with content. No CLI implementation exists in the repository yet. If CLI usage is a planned feature, handling empty bodies defensively would allow the fallback logic at lines 22–27 to function. However, note that the fallback itself requires at least an empty JSON object{}to properly extract thestagedflag on line 19.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/aigit/review/route.js` around lines 18 - 19, The route currently calls await request.json() which throws on empty bodies, so wrap body parsing in a safe parse that falls back to an empty object and preserves destructuring of { diff: bodyDiff, staged = false, vibeMode = 'balanced' }; specifically, replace the direct await request.json() with a try/catch or conditional that attempts JSON parsing (or checks content-length/request.headers['content-type']) and on failure assigns body = {} so bodyDiff, staged and vibeMode work as intended in the rest of the handler (refer to request.json(), bodyDiff, staged, vibeMode).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/api/aigit/review/route.js`:
- Around line 49-50: The catch block in the review API currently returns raw
exception text via NextResponse.json({ error: error.message }, ...) which can
leak internals; change it to return a generic error payload (e.g., { error:
"Internal server error" }) and move the detailed error logging server-side by
logging the caught error with your logger/console (refer to the catch block and
the NextResponse.json call in route.js to locate the code). Ensure the response
still uses the 500 status but does not include error.message, and keep the
original error available only in server logs for diagnostics.
- Around line 16-52: The POST handler for the review route needs route-level
authentication and rate limiting to prevent quota abuse: add an auth check at
the top of POST (call a helper like requireAuth or validateApiKey/session) and
return 401 when missing/invalid, and add a rate-limiter check (call a helper
like rateLimit or getRateLimiter) that enforces e.g., X requests/min per user/IP
and returns 429 when exceeded; apply the same requireAuth and rateLimit helpers
to the /api/aigit/suggest handler as well, and ensure these helpers are reusable
(e.g., requireAuth(request) and await rateLimit(key)) before calling
isAIEnabled() or reviewCode(diff, { vibeMode }) so unauthorized/over-quota
requests never reach Claude.
In `@src/app/api/aigit/suggest/route.js`:
- Around line 51-53: The catch block in src/app/api/aigit/suggest/route.js
currently returns raw error.message to the client; change it to log the full
error on the server (e.g., console.error or the existing logger) and return a
generic JSON error (e.g., { error: "Internal server error" }) with the same
status code using NextResponse.json so internals aren't leaked; update the catch
associated with the route handler in route.js (the catch that returns
NextResponse.json({ error: error.message }, { status: 500 })) to perform
server-side logging of error and send a generic client-facing message instead.
- Around line 26-31: The AI calls inside the isAIEnabled() branch use
Promise.all([generateCommitMessage, generateCommitAlternatives]) which rejects
if either call fails and thus skips the pattern-based fallback; change this to
handle failures per-call (e.g., use Promise.allSettled or individual try/catch)
so you can use whichever successful result(s) are available and fall back to the
existing pattern-based commit generator when either generateCommitMessage or
generateCommitAlternatives fails; update the logic around Promise.all,
isAIEnabled, generateCommitMessage and generateCommitAlternatives to merge
successes and trigger the fallback when needed.
---
Nitpick comments:
In `@src/app/api/aigit/review/route.js`:
- Around line 19-27: Coerce and validate incoming request fields before using
them: convert staged to a true boolean (e.g., staged =
String(body.staged).toLowerCase() === 'true' || Boolean(body.staged)), trim and
treat bodyDiff as empty/undefined if it is a whitespace-only string (e.g., const
bodyDiff = body.diff?.toString()?.trim() || undefined), and validate vibeMode
against an allowlist (e.g., 'balanced','creative','precise') falling back to
'balanced' if invalid; then use these normalized values when computing diff (the
diff fallback logic that calls engine.getStagedDiff()/engine.getUnstagedDiff())
and elsewhere in the handler to avoid treating strings like "false" as truthy or
using blank diffs.
- Around line 18-19: The route currently calls await request.json() which throws
on empty bodies, so wrap body parsing in a safe parse that falls back to an
empty object and preserves destructuring of { diff: bodyDiff, staged = false,
vibeMode = 'balanced' }; specifically, replace the direct await request.json()
with a try/catch or conditional that attempts JSON parsing (or checks
content-length/request.headers['content-type']) and on failure assigns body = {}
so bodyDiff, staged and vibeMode work as intended in the rest of the handler
(refer to request.json(), bodyDiff, staged, vibeMode).
| export async function POST(request) { | ||
| try { | ||
| const body = await request.json(); | ||
| const { diff: bodyDiff, staged = false, vibeMode = 'balanced' } = body; | ||
|
|
||
| // Prefer diff from request body (browser UI); fall back to local git (CLI) | ||
| const diff = | ||
| bodyDiff || | ||
| (staged | ||
| ? engine.getStagedDiff() | ||
| : engine.getUnstagedDiff() || engine.getStagedDiff()); | ||
|
|
||
| if (!diff) { | ||
| return NextResponse.json( | ||
| { error: 'No changes to review' }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
|
|
||
| if (!isAIEnabled()) { | ||
| return NextResponse.json({ | ||
| score: 7, | ||
| summary: 'Analysis complete (AI not configured)', | ||
| issues: [], | ||
| suggestions: [], | ||
| vibeCheck: 'Add ANTHROPIC_API_KEY to .env.local to enable AI-powered reviews 🤖', | ||
| source: 'pattern-matching', | ||
| }); | ||
| } | ||
|
|
||
| const review = await reviewCode(diff, { vibeMode }); | ||
|
|
||
| return NextResponse.json({ ...review, source: 'claude', model: 'claude-opus-4-6' }); | ||
| } catch (error) { | ||
| return NextResponse.json({ error: error.message }, { status: 500 }); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check route-level guards in AI endpoints
rg -n -C3 'auth|session|token|ratelimit|rateLimit|limiter' \
src/app/api/aigit/review/route.js
# Also check if the file imports middleware or protection utilities
rg -n 'import|require' src/app/api/aigit/review/route.jsRepository: appwrite/starter-for-nextjs
Length of output: 245
🏁 Script executed:
# Locate middleware files
fd -H 'middleware\.(js|ts|mjs|cjs)$'Repository: appwrite/starter-for-nextjs
Length of output: 53
🏁 Script executed:
# Search for any route protection patterns in middleware
fd -H 'middleware\.(js|ts|mjs|cjs)$' -exec rg -n -C4 'matcher|aigit|auth|ratelimit|limiter' {} +Repository: appwrite/starter-for-nextjs
Length of output: 301
🏁 Script executed:
# Check for any auth or protection in the entire aigit directory
fd -H . src/app/api/aigit --type f | head -20Repository: appwrite/starter-for-nextjs
Length of output: 139
🏁 Script executed:
# Check all files in aigit API directory
find src/app/api/aigit -type f -name '*.js' -o -name '*.ts' 2>/dev/null | xargs ls -laRepository: appwrite/starter-for-nextjs
Length of output: 237
🏁 Script executed:
# Read the full route file
cat -n src/app/api/aigit/review/route.jsRepository: appwrite/starter-for-nextjs
Length of output: 2127
🏁 Script executed:
# Check the suggest route as well
cat -n src/app/api/aigit/suggest/route.jsRepository: appwrite/starter-for-nextjs
Length of output: 1989
🏁 Script executed:
# Search for middleware files in standard Next.js locations
find src -name '*middleware*' -type fRepository: appwrite/starter-for-nextjs
Length of output: 53
🏁 Script executed:
# Check for middleware in root and project root
ls -la | grep -i middleware || echo "No middleware in root"
find . -maxdepth 2 -name '*middleware*' -type f 2>/dev/nullRepository: appwrite/starter-for-nextjs
Length of output: 92
🏁 Script executed:
# Search for auth/ratelimit patterns across the entire codebase
rg 'auth|ratelimit|rateLimit|limiter' src/app/api --type js --type ts | head -30Repository: appwrite/starter-for-nextjs
Length of output: 53
Add authentication and rate limiting to protect this paid AI endpoint from quota abuse.
Both /api/aigit/review and /api/aigit/suggest endpoints lack any authentication or rate limiting guards. If publicly accessible, these routes can be exploited to trigger unbounded external AI calls to Claude, consuming quota and incurring costs. Add route-level middleware or in-route checks to restrict access by authentication (e.g., session/API key validation) and implement rate limiting per user/IP.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/api/aigit/review/route.js` around lines 16 - 52, The POST handler for
the review route needs route-level authentication and rate limiting to prevent
quota abuse: add an auth check at the top of POST (call a helper like
requireAuth or validateApiKey/session) and return 401 when missing/invalid, and
add a rate-limiter check (call a helper like rateLimit or getRateLimiter) that
enforces e.g., X requests/min per user/IP and returns 429 when exceeded; apply
the same requireAuth and rateLimit helpers to the /api/aigit/suggest handler as
well, and ensure these helpers are reusable (e.g., requireAuth(request) and
await rateLimit(key)) before calling isAIEnabled() or reviewCode(diff, {
vibeMode }) so unauthorized/over-quota requests never reach Claude.
| } catch (error) { | ||
| return NextResponse.json({ error: error.message }, { status: 500 }); |
There was a problem hiding this comment.
Do not return raw exception text to clients.
Line 50 exposes error.message directly. This can leak internal/provider details. Return a generic error and keep detailed diagnostics server-side.
💡 Proposed fix
} catch (error) {
- return NextResponse.json({ error: error.message }, { status: 500 });
+ console.error('POST /api/aigit/review failed:', error);
+ return NextResponse.json({ error: 'Internal server 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.
| } catch (error) { | |
| return NextResponse.json({ error: error.message }, { status: 500 }); | |
| } catch (error) { | |
| console.error('POST /api/aigit/review failed:', error); | |
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/api/aigit/review/route.js` around lines 49 - 50, The catch block in
the review API currently returns raw exception text via NextResponse.json({
error: error.message }, ...) which can leak internals; change it to return a
generic error payload (e.g., { error: "Internal server error" }) and move the
detailed error logging server-side by logging the caught error with your
logger/console (refer to the catch block and the NextResponse.json call in
route.js to locate the code). Ensure the response still uses the 500 status but
does not include error.message, and keep the original error available only in
server logs for diagnostics.
| if (isAIEnabled()) { | ||
| // Use Claude for intelligent suggestions | ||
| const [primary, alternatives] = await Promise.all([ | ||
| generateCommitMessage(diff, context), | ||
| generateCommitAlternatives(diff, context), | ||
| ]); |
There was a problem hiding this comment.
AI failure currently skips fallback and returns 500
At Line 28, Promise.all will reject if either AI call fails, which sends control to the global catch and prevents the pattern-based fallback from running.
Suggested fix
- if (isAIEnabled()) {
- // Use Claude for intelligent suggestions
- const [primary, alternatives] = await Promise.all([
- generateCommitMessage(diff, context),
- generateCommitAlternatives(diff, context),
- ]);
-
- if (primary) {
- return NextResponse.json({
- message: primary,
- alternatives: alternatives || [],
- source: 'claude',
- model: 'claude-opus-4-6',
- });
- }
- }
+ if (isAIEnabled()) {
+ try {
+ const [primaryResult, alternativesResult] = await Promise.allSettled([
+ generateCommitMessage(diff, context),
+ generateCommitAlternatives(diff, context),
+ ]);
+
+ const primary =
+ primaryResult.status === 'fulfilled' ? primaryResult.value : null;
+ const alternatives =
+ alternativesResult.status === 'fulfilled' ? alternativesResult.value : [];
+
+ if (primary) {
+ return NextResponse.json({
+ message: primary,
+ alternatives: alternatives || [],
+ source: 'claude',
+ model: 'claude-opus-4-6',
+ });
+ }
+ } catch {
+ // fall through to pattern-based fallback
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/api/aigit/suggest/route.js` around lines 26 - 31, The AI calls inside
the isAIEnabled() branch use Promise.all([generateCommitMessage,
generateCommitAlternatives]) which rejects if either call fails and thus skips
the pattern-based fallback; change this to handle failures per-call (e.g., use
Promise.allSettled or individual try/catch) so you can use whichever successful
result(s) are available and fall back to the existing pattern-based commit
generator when either generateCommitMessage or generateCommitAlternatives fails;
update the logic around Promise.all, isAIEnabled, generateCommitMessage and
generateCommitAlternatives to merge successes and trigger the fallback when
needed.
| } catch (error) { | ||
| return NextResponse.json({ error: error.message }, { status: 500 }); | ||
| } |
There was a problem hiding this comment.
Do not expose raw internal errors in API responses
At Line 52, returning error.message can leak provider/runtime internals to clients. Return a generic message and log server-side details.
Suggested fix
} catch (error) {
- return NextResponse.json({ error: error.message }, { status: 500 });
+ console.error('Failed to generate commit suggestion', error);
+ return NextResponse.json(
+ { error: 'Failed to generate commit suggestion' },
+ { status: 500 }
+ );
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/api/aigit/suggest/route.js` around lines 51 - 53, The catch block in
src/app/api/aigit/suggest/route.js currently returns raw error.message to the
client; change it to log the full error on the server (e.g., console.error or
the existing logger) and return a generic JSON error (e.g., { error: "Internal
server error" }) with the same status code using NextResponse.json so internals
aren't leaked; update the catch associated with the route handler in route.js
(the catch that returns NextResponse.json({ error: error.message }, { status:
500 })) to perform server-side logging of error and send a generic client-facing
message instead.
- Install next-themes for SSR-safe theme management - Add ThemeProvider (wraps app with defaultTheme="dark") - Add ThemeToggle component with animated Sun/Moon icon transition - Update landing page and dashboard with full dark:/light Tailwind variants - Nav, cards, terminal demo, pricing, CTA all respond to theme Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Colors: warm cream (#FAF9F6) bg, Claude orange (#D97757) accent, warm charcoal (#1C1917) dark mode — replaces purple/slate palette - Font: Lora serif for all headings/display text, Inter for body - Update shadcn CSS variables (--primary, --background, --border etc.) to match warm stone/orange tokens in both light and dark modes - Refine spacing, border-radius, and card shadows throughout - Update footer domain to ivgit.com Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (4)
src/app/page.js (2)
73-75:⚠️ Potential issue | 🟡 MinorDead CTA buttons need destination or explicit disabled state.
Line 73-75 ("Watch demo") and Line 222-223 ("Contact sales") have no action, so they do nothing when clicked.
Also applies to: 222-223
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/page.js` around lines 73 - 75, The two CTA Button instances rendering "▶ Watch demo" and "Contact sales" are inert and must either navigate or be visibly disabled; update the Button components in src/app/page.js (the elements containing the label "▶ Watch demo" and the one labeled "Contact sales") to provide a real action: add an href or onClick handler that routes to the demo and contact-sales pages or opens the appropriate modal/URL, or explicitly set a disabled prop and adjust aria-disabled and styling to indicate the inactive state; ensure accessible attributes (aria-label/role) are present if the button remains interactive.
29-38:⚠️ Potential issue | 🔴 CriticalAvoid nested interactive controls (
LinkwrappingButton).These segments render nested actionable elements, which breaks semantics and keyboard/screen-reader behavior. Use
Button asChildso the anchor is the single interactive element.Fix pattern
-<Link href="/aigit"> - <Button className="...">Get Started</Button> -</Link> +<Button asChild className="..."> + <Link href="/aigit">Get Started</Link> +</Button>#!/bin/bash # Detect Link/Button nesting patterns for confirmation. rg -n -C2 '<Link[^>]*>\s*<Button|<Button[^>]*>\s*<Link' src/app/page.jsAlso applies to: 68-72, 174-176, 200-202, 238-242
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/page.js` around lines 29 - 38, Replace nested interactive elements where a Link wraps a Button (instances of Link and Button in src/app/page.js at the shown locations) by using the Button's asChild prop so the anchor becomes the sole interactive element; specifically, change the pattern Link > Button to Button with asChild wrapping the Link (preserving href, Button props like variant/className, and visible text) for each occurrence (e.g., the "Dashboard" and "Get Started" button groups and the other reported ranges).src/app/aigit/page.js (2)
80-82:⚠️ Potential issue | 🟡 MinorUpgrade CTAs are currently inert.
These buttons have no
href/onClick, so users can’t actually enter an upgrade flow.Also applies to: 102-104, 281-283, 311-313
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/aigit/page.js` around lines 80 - 82, The "Upgrade to Pro" Button elements in page.js are inert because they lack navigation or click handlers; update each Button instance (the ones rendering the "Upgrade to Pro" label) to either wrap with Next.js Link or add an onClick that routes to your upgrade flow (e.g., router.push('/pricing') or calls openUpgradeModal()), and ensure any shared handler is imported/defined (e.g., useRouter from 'next/navigation' or an openUpgradeModal function) so the CTAs actually start the upgrade flow.
22-30:⚠️ Potential issue | 🟠 MajorCheck
res.okbefore parsing response and incrementing usage.Line 27/Line 46 parse JSON and Line 29/Line 48 increments usage even for non-2xx responses. This can consume quota on failed requests and surface error payloads as success state.
Suggested hardening
+ const postJson = async (url, payload) => { + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }) + const data = await res.json().catch(() => ({})) + if (!res.ok) throw new Error(data?.error || "Request failed") + return data + } const handleReview = async () => { if (!diff.trim()) return setLoading(true) try { - const res = await fetch('/api/aigit/review', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ diff, vibeMode: 'balanced' }) - }) - const data = await res.json() + const data = await postJson('/api/aigit/review', { diff, vibeMode: 'balanced' }) setResult(data) setUsageCount(prev => prev + 1)#!/bin/bash # Verify missing response-status checks around API calls in Dashboard handlers. rg -n -C3 'handleReview|handleCommitMessage|fetch\(|res\.ok|setUsageCount|res\.json' src/app/aigit/page.jsAlso applies to: 41-49
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/aigit/page.js` around lines 22 - 30, The fetch response is parsed and usage is incremented unconditionally; update the fetch handling (the POST to '/api/aigit/review' where res is used) to first check res.ok before calling res.json() and before calling setResult or setUsageCount. If !res.ok, parse the error payload or read text, set an error state (or throw) and do not increment usage; apply the same check/guard to the other handler (handleReview/handleCommitMessage or the similar fetch block at lines 41-49) so usage is only incremented for successful 2xx responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/aigit/page.js`:
- Around line 250-254: Replace the non-focusable clickable <div> used for
copy-to-clipboard rows with a semantic <button type="button"> so keyboard users
can focus and activate it; keep the existing key={i}, className and the onClick
handler (navigator.clipboard?.writeText(msg)), ensure the element remains
type="button" to avoid form submission, and add an accessible label such as
aria-label={`Copy ${msg}`} (or a concise aria-label) to the same element.
In `@src/app/app.css`:
- Around line 5-8: Stylelint is flagging Tailwind v4 at-rules (`@custom-variant`,
`@theme`, `@apply`, `@import`) in src/app/app.css because at-rule-no-unknown isn't
configured to ignore them; update the stylelint configuration
(.stylelintrc.json) to add an "ignoreAtRules" array that includes
"custom-variant", "theme", "apply", and "import" so those directives are treated
as valid, or alternatively enable a Tailwind/PostCSS-aware stylelint preset if
available (e.g., a plugin that supports Tailwind v4) and ensure
at-rule-no-unknown is disabled or configured accordingly.
In `@src/components/theme-toggle.jsx`:
- Around line 8-9: The component currently uses theme from useTheme which can be
"system" and mis-detect dark mode; update the dark/light detection to use
resolvedTheme from useTheme (e.g., const { theme, resolvedTheme, setTheme } =
useTheme()) and compute isDark using resolvedTheme === 'dark' (while retaining
mounted handling to avoid SSR flicker). Replace any checks that use theme ===
'dark' (and icon selection logic) to use resolvedTheme, but still use setTheme
to toggle user choice; ensure mounted guard remains around rendering so
resolvedTheme is read only after mount.
---
Duplicate comments:
In `@src/app/aigit/page.js`:
- Around line 80-82: The "Upgrade to Pro" Button elements in page.js are inert
because they lack navigation or click handlers; update each Button instance (the
ones rendering the "Upgrade to Pro" label) to either wrap with Next.js Link or
add an onClick that routes to your upgrade flow (e.g., router.push('/pricing')
or calls openUpgradeModal()), and ensure any shared handler is imported/defined
(e.g., useRouter from 'next/navigation' or an openUpgradeModal function) so the
CTAs actually start the upgrade flow.
- Around line 22-30: The fetch response is parsed and usage is incremented
unconditionally; update the fetch handling (the POST to '/api/aigit/review'
where res is used) to first check res.ok before calling res.json() and before
calling setResult or setUsageCount. If !res.ok, parse the error payload or read
text, set an error state (or throw) and do not increment usage; apply the same
check/guard to the other handler (handleReview/handleCommitMessage or the
similar fetch block at lines 41-49) so usage is only incremented for successful
2xx responses.
In `@src/app/page.js`:
- Around line 73-75: The two CTA Button instances rendering "▶ Watch demo" and
"Contact sales" are inert and must either navigate or be visibly disabled;
update the Button components in src/app/page.js (the elements containing the
label "▶ Watch demo" and the one labeled "Contact sales") to provide a real
action: add an href or onClick handler that routes to the demo and contact-sales
pages or opens the appropriate modal/URL, or explicitly set a disabled prop and
adjust aria-disabled and styling to indicate the inactive state; ensure
accessible attributes (aria-label/role) are present if the button remains
interactive.
- Around line 29-38: Replace nested interactive elements where a Link wraps a
Button (instances of Link and Button in src/app/page.js at the shown locations)
by using the Button's asChild prop so the anchor becomes the sole interactive
element; specifically, change the pattern Link > Button to Button with asChild
wrapping the Link (preserving href, Button props like variant/className, and
visible text) for each occurrence (e.g., the "Dashboard" and "Get Started"
button groups and the other reported ranges).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
package.jsonsrc/app/aigit/page.jssrc/app/app.csssrc/app/layout.jssrc/app/page.jssrc/components/theme-provider.jsxsrc/components/theme-toggle.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
| <div | ||
| key={i} | ||
| className="bg-white dark:bg-[#252120] p-4 rounded-xl border border-[#E8E0D5] dark:border-[#2C2825] hover:border-[#D97757] dark:hover:border-[#D97757] transition cursor-pointer" | ||
| onClick={() => navigator.clipboard?.writeText(msg)} | ||
| > |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists and get its size
if [ -f "src/app/aigit/page.js" ]; then
wc -l "src/app/aigit/page.js"
else
echo "File not found at src/app/aigit/page.js"
find . -name "page.js" -path "*/aigit/*" 2>/dev/null | head -10
fiRepository: appwrite/starter-for-nextjs
Length of output: 96
🏁 Script executed:
# Read the specific lines mentioned in the review
if [ -f "src/app/aigit/page.js" ]; then
sed -n '240,270p' "src/app/aigit/page.js" | cat -n
else
echo "Checking alternative paths..."
fd "page.js" | grep -i aigit
fiRepository: appwrite/starter-for-nextjs
Length of output: 2040
Use a semantic button for copy-to-clipboard rows.
Lines 250-254 use a clickable <div>, which is not keyboard-focusable by default. Replace with <button type="button"> for proper accessibility and interaction semantics.
Suggested fix
-<div
+<button
+ type="button"
key={i}
className="bg-white dark:bg-[`#252120`] p-4 rounded-xl border border-[`#E8E0D5`] dark:border-[`#2C2825`] hover:border-[`#D97757`] dark:hover:border-[`#D97757`] transition cursor-pointer"
onClick={() => navigator.clipboard?.writeText(msg)}
>
<p className="font-mono text-sm text-[`#C25E3A`] dark:text-[`#E8896A`]">{msg}</p>
{i === 0 && (
<Badge className="mt-2 bg-[`#EEDDD6`] text-[`#C25E3A`] dark:bg-[`#2C1F18`] dark:text-[`#E8896A`] border-0 text-xs">Primary</Badge>
)}
-</div>
+</button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/aigit/page.js` around lines 250 - 254, Replace the non-focusable
clickable <div> used for copy-to-clipboard rows with a semantic <button
type="button"> so keyboard users can focus and activate it; keep the existing
key={i}, className and the onClick handler
(navigator.clipboard?.writeText(msg)), ensure the element remains type="button"
to avoid form submission, and add an accessible label such as aria-label={`Copy
${msg}`} (or a concise aria-label) to the same element.
| @custom-variant dark (&:is(.dark *)); | ||
|
|
||
| /* ─── Claude-inspired custom tokens ─── */ | ||
| @theme inline { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find lint config files
echo "=== Lint Config Files ==="
find . -maxdepth 3 -type f \( -name '.stylelintrc*' -o -name 'stylelint.config.*' -o -name '.stylelintignore' \) 2>/dev/null
find . -maxdepth 3 -type f \( -name 'biome.json' -o -name 'biome.jsonc' \) 2>/dev/null
echo -e "\n=== Tailwind directives in app.css ==="
if [ -f "src/app/app.css" ]; then
cat -n src/app/app.css | head -15
echo "..."
echo "Lines 95-105:"
cat -n src/app/app.css | sed -n '95,105p'
fi
echo -e "\n=== Package.json stylelint/biome config ==="
if [ -f "package.json" ]; then
cat package.json | grep -A 10 '"stylelint\|"biome' | head -20
fiRepository: appwrite/starter-for-nextjs
Length of output: 1012
🏁 Script executed:
#!/bin/bash
# Check stylelint configs for Tailwind support
echo "=== Stylelint Configs ==="
for file in $(find . -maxdepth 3 -type f -name '.stylelintrc*' -o -name 'stylelint.config.*' 2>/dev/null); do
echo "File: $file"
cat "$file"
echo ""
done
echo "=== Biome Configs ==="
for file in $(find . -maxdepth 3 -type f \( -name 'biome.json' -o -name 'biome.jsonc' \) 2>/dev/null); do
echo "File: $file"
cat "$file"
echo ""
done
echo "=== package.json (full) ==="
cat package.jsonRepository: appwrite/starter-for-nextjs
Length of output: 4255
Add Tailwind v4 at-rule support to stylelint configuration.
The @custom-variant, @theme, and @apply directives in src/app/app.css (lines 5, 8, 99, 102) are valid Tailwind v4 syntax, but .stylelintrc.json is missing the ignoreAtRules configuration. This causes stylelint's at-rule-no-unknown rule to flag them as invalid, breaking CI. Update .stylelintrc.json to include:
{
"ignoreAtRules": ["custom-variant", "theme", "apply", "import"]
}Alternatively, consider using @tailwindcss/postcss syntax support if available in stylelint-config-standard-scss or switching to PostCSS-based linting that natively understands Tailwind v4.
🧰 Tools
🪛 Biome (2.4.4)
[error] 5-5: Tailwind-specific syntax is disabled.
(parse)
🪛 Stylelint (17.3.0)
[error] 5-5: Unexpected unknown at-rule "@custom-variant" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
[error] 8-8: Unexpected unknown at-rule "@theme" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/app.css` around lines 5 - 8, Stylelint is flagging Tailwind v4
at-rules (`@custom-variant`, `@theme`, `@apply`, `@import`) in src/app/app.css because
at-rule-no-unknown isn't configured to ignore them; update the stylelint
configuration (.stylelintrc.json) to add an "ignoreAtRules" array that includes
"custom-variant", "theme", "apply", and "import" so those directives are treated
as valid, or alternatively enable a Tailwind/PostCSS-aware stylelint preset if
available (e.g., a plugin that supports Tailwind v4) and ensure
at-rule-no-unknown is disabled or configured accordingly.
| const { theme, setTheme } = useTheme() | ||
| const [mounted, setMounted] = useState(false) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
next-themes theme vs resolvedTheme enableSystem documentation
💡 Result:
theme(fromuseTheme()): the active theme setting the user selected/stored (e.g."light","dark", or"system"whenenableSystemis on). (github.com)resolvedTheme: the effective theme actually being applied. Iftheme === "system"andenableSystem === true, thenresolvedThemebecomes"light"or"dark"based onprefers-color-scheme; otherwise it’s the same astheme. (github.com)enableSystem(ThemeProvider prop): controls whether the"system"option exists/works (i.e., switching based onprefers-color-scheme). IfenableSystemisfalse, system-based switching is disabled and the docs notedefaultThemebecomes"light". (github.com)
Rule of thumb:
- Use
themewhen you care whether the user chose System vs explicit Light/Dark (UI should show “System”). (github.com) - Use
resolvedThemewhen you need to apply styles/logic as light vs dark, even when the user selected System. (github.com)
Citations:
- 1: https://github.com/pacocoursey/next-themes?utm_source=openai
- 2: https://github.com/pacocoursey/next-themes?utm_source=openai
- 3: https://github.com/pacocoursey/next-themes?utm_source=openai
- 4: https://github.com/pacocoursey/next-themes?utm_source=openai
- 5: https://github.com/pacocoursey/next-themes?utm_source=openai
🏁 Script executed:
head -25 src/components/theme-toggle.jsxRepository: appwrite/starter-for-nextjs
Length of output: 885
🏁 Script executed:
fd -t f "layout\|provider\|_app" --extension jsx --extension tsx --extension js | head -20Repository: appwrite/starter-for-nextjs
Length of output: 53
🏁 Script executed:
rg -l "ThemeProvider\|next-themes" --type jsx --type tsx --type js | head -10Repository: appwrite/starter-for-nextjs
Length of output: 98
🏁 Script executed:
rg "ThemeProvider" --type js --type typescriptRepository: appwrite/starter-for-nextjs
Length of output: 105
🏁 Script executed:
git ls-files | grep -E "app\.|layout\|provider" | head -20Repository: appwrite/starter-for-nextjs
Length of output: 86
🏁 Script executed:
git ls-files | head -30Repository: appwrite/starter-for-nextjs
Length of output: 701
🏁 Script executed:
cat -n src/components/theme-provider.jsxRepository: appwrite/starter-for-nextjs
Length of output: 330
🏁 Script executed:
cat -n src/app/layout.jsRepository: appwrite/starter-for-nextjs
Length of output: 1207
Use resolvedTheme instead of theme for dark/light detection.
With enableSystem enabled (as shown in layout.js line 21), theme will be "system" when the user hasn't explicitly chosen light or dark mode. This causes isDark = theme === 'dark' (line 15) to evaluate to false even when the OS prefers dark mode, resulting in the toggle icon showing the wrong state.
Use resolvedTheme which resolves to the effective mode ("light" or "dark") based on OS preference when theme === "system".
Suggested fix
- const { theme, setTheme } = useTheme()
+ const { resolvedTheme, setTheme } = useTheme()
@@
- const isDark = theme === 'dark'
+ const isDark = resolvedTheme === 'dark'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/theme-toggle.jsx` around lines 8 - 9, The component currently
uses theme from useTheme which can be "system" and mis-detect dark mode; update
the dark/light detection to use resolvedTheme from useTheme (e.g., const {
theme, resolvedTheme, setTheme } = useTheme()) and compute isDark using
resolvedTheme === 'dark' (while retaining mounted handling to avoid SSR
flicker). Replace any checks that use theme === 'dark' (and icon selection
logic) to use resolvedTheme, but still use setTheme to toggle user choice;
ensure mounted guard remains around rendering so resolvedTheme is read only
after mount.
- Add Logo component (src/components/logo.jsx): inline SVG matching the ivgit-logo-hierarchy design — gradient 'IV' above large monospace 'git', git text color adapts between dark (#4A433E) and light (#EDE8E3) mode - Copy ivgit-logo.svg to public/ - Replace all ClawGit text and 🐾 emoji in nav, footer, dashboard header - Update page title metadata to ivGit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (5)
src/app/aigit/page.js (3)
247-257:⚠️ Potential issue | 🟡 MinorUse a semantic button for copy-to-clipboard rows.
At Line 247–257, clickable commit options are rendered as
<div>, which is not keyboard-focusable by default. Use<button type="button">with an accessible label.Proposed fix
-<div +<button + type="button" key={i} className="bg-white dark:bg-[`#252120`] p-4 rounded-xl border border-[`#E8E0D5`] dark:border-[`#2C2825`] hover:border-[`#D97757`] dark:hover:border-[`#D97757`] transition cursor-pointer" onClick={() => navigator.clipboard?.writeText(msg)} + aria-label={`Copy commit message ${i + 1}`} > <p className="font-mono text-sm text-[`#C25E3A`] dark:text-[`#E8896A`]">{msg}</p> {i === 0 && ( <Badge className="mt-2 bg-[`#EEDDD6`] text-[`#C25E3A`] dark:bg-[`#2C1F18`] dark:text-[`#E8896A`] border-0 text-xs">Primary</Badge> )} -</div> +</button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/aigit/page.js` around lines 247 - 257, Replace the clickable <div> used in the map over {[result.message, ...(result.alternatives || [])]} with a semantic <button type="button"> so rows become keyboard-focusable; keep the existing className and onClick handler (navigator.clipboard?.writeText(msg)), add an accessible label (e.g. aria-label={`Copy commit message: ${msg}`} or a visually-hidden label) and ensure the Badge rendering logic (i === 0) remains unchanged so the "Primary" badge still shows for the first item.
19-35:⚠️ Potential issue | 🟠 MajorGate state updates on HTTP success before incrementing usage.
At Line 19–35 and Line 38–55,
setResultandsetUsageCountrun even when the server returns non-2xx responses, becauseres.okis never checked.Proposed hardening
try { const res = await fetch('/api/aigit/review', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ diff, vibeMode: 'balanced' }) }) - const data = await res.json() + const data = await res.json().catch(() => ({})) + if (!res.ok) throw new Error(data?.error || 'Request failed') setResult(data) setUsageCount(prev => prev + 1) } catch (error) {Apply the same pattern in
handleCommitMessage.Also applies to: 38-55
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/aigit/page.js` around lines 19 - 35, The handlers (handleReview and handleCommitMessage) update state (setResult and setUsageCount) even when the fetch returns non-2xx because they never check res.ok; change both handlers so after awaiting fetch you check if (!res.ok) { const errBody = await res.text(); throw new Error(`HTTP ${res.status}: ${errBody}`); } and only then parse JSON and call setResult(...) and setUsageCount(...); ensure errors are caught in the existing catch block so non-2xx responses do not increment usage or set results.
78-80:⚠️ Potential issue | 🟡 MinorUpgrade CTAs are visible but non-actionable.
At Line 78–80, Line 100–102, Line 279–281, and Line 309–311, “Upgrade to Pro” buttons have no navigation/handler. These should route users to pricing/checkout or open an upgrade flow.
Also applies to: 100-102, 279-281, 309-311
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/aigit/page.js` around lines 78 - 80, The "Upgrade to Pro" Button elements in src/app/aigit/page.js are rendered without any navigation or click handler; update each JSX Button with the "Upgrade to Pro" text to either wrap it in a Next.js <Link> to your pricing/checkout route (e.g., <Link href="/pricing"><Button ...>...</Button></Link>) or add an onClick that calls the existing upgrade flow function (e.g., handleOpenUpgrade or router.push('/pricing')). Locate the three other identical Button instances by searching for the exact text "Upgrade to Pro" and ensure each uses the same navigation approach so the CTAs become actionable.src/app/page.js (2)
71-73:⚠️ Potential issue | 🟡 Minor“Watch demo” CTA is currently inert.
At Line 71–73, the button has no action (
href/onClick). Please wire it to a real destination or disable it with explicit “Coming soon” messaging.Proposed fix (disabled placeholder)
-<Button size="lg" variant="outline" className="..."> - ▶ Watch demo -</Button> +<Button size="lg" variant="outline" className="..." disabled aria-disabled="true"> + ▶ Watch demo (Coming soon) +</Button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/page.js` around lines 71 - 73, The "Watch demo" Button in the JSX (component: Button in src/app/page.js) is inert—add a real action or explicitly disable it: either add an href/onClick that navigates to the demo URL (e.g., set onClick to open the demo route or set href to the demo link) and include accessible attributes (aria-label) or, if the demo isn't ready, change the label to "Coming soon", set the Button to disabled (and/or aria-disabled="true") and add a tooltip or visually hidden text explaining it's not available; update the Button JSX (the Button element at the shown diff) accordingly.
27-36:⚠️ Potential issue | 🟠 MajorResolve nested interactive controls in CTA markup.
At Line 27–36, Line 66–70, Line 172–174, Line 198–200, and Line 236–240,
Buttonis nested insideLink. This creates invalid interactive nesting and breaks keyboard/screen-reader behavior.Proposed fix pattern
-<Link href="/aigit"> - <Button className="...">Get Started</Button> -</Link> +<Button asChild className="..."> + <Link href="/aigit">Get Started</Link> +</Button>Also applies to: 66-70, 172-174, 198-200, 236-240
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/page.js` around lines 27 - 36, The CTAs nest an interactive Button inside a Link (Link and Button in src/app/page.js), causing invalid interactive nesting; fix by removing the nested interactive element: either make Link the sole interactive element and replace the inner Button with a non-interactive element (e.g., span/div) that preserves styling, or remove Link and keep Button as the interactive control and perform navigation via router push/onClick; update all occurrences of Link + Button (the Dashboard/Get Started pairs at the noted locations) consistently so only one interactive widget handles activation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/page.js`:
- Line 220: The "Contact sales" Button (the Button element at Line ~220) has no
action—make it actionable by adding a handler or navigation: either wrap it with
your router Link (e.g., navigate to a /contact route) or add an onClick that
opens a mailto (window.location.href = 'mailto:...') or triggers the sales modal
function (e.g., openSalesModal()). If you don't want it usable yet, explicitly
set disabled and add an aria-disabled/tooltip; ensure you update the Button
component props (onClick, href or disabled) and any referenced handler name
(openSalesModal, handleContactClick) so the button is discoverable and
accessible.
---
Duplicate comments:
In `@src/app/aigit/page.js`:
- Around line 247-257: Replace the clickable <div> used in the map over
{[result.message, ...(result.alternatives || [])]} with a semantic <button
type="button"> so rows become keyboard-focusable; keep the existing className
and onClick handler (navigator.clipboard?.writeText(msg)), add an accessible
label (e.g. aria-label={`Copy commit message: ${msg}`} or a visually-hidden
label) and ensure the Badge rendering logic (i === 0) remains unchanged so the
"Primary" badge still shows for the first item.
- Around line 19-35: The handlers (handleReview and handleCommitMessage) update
state (setResult and setUsageCount) even when the fetch returns non-2xx because
they never check res.ok; change both handlers so after awaiting fetch you check
if (!res.ok) { const errBody = await res.text(); throw new Error(`HTTP
${res.status}: ${errBody}`); } and only then parse JSON and call setResult(...)
and setUsageCount(...); ensure errors are caught in the existing catch block so
non-2xx responses do not increment usage or set results.
- Around line 78-80: The "Upgrade to Pro" Button elements in
src/app/aigit/page.js are rendered without any navigation or click handler;
update each JSX Button with the "Upgrade to Pro" text to either wrap it in a
Next.js <Link> to your pricing/checkout route (e.g., <Link
href="/pricing"><Button ...>...</Button></Link>) or add an onClick that calls
the existing upgrade flow function (e.g., handleOpenUpgrade or
router.push('/pricing')). Locate the three other identical Button instances by
searching for the exact text "Upgrade to Pro" and ensure each uses the same
navigation approach so the CTAs become actionable.
In `@src/app/page.js`:
- Around line 71-73: The "Watch demo" Button in the JSX (component: Button in
src/app/page.js) is inert—add a real action or explicitly disable it: either add
an href/onClick that navigates to the demo URL (e.g., set onClick to open the
demo route or set href to the demo link) and include accessible attributes
(aria-label) or, if the demo isn't ready, change the label to "Coming soon", set
the Button to disabled (and/or aria-disabled="true") and add a tooltip or
visually hidden text explaining it's not available; update the Button JSX (the
Button element at the shown diff) accordingly.
- Around line 27-36: The CTAs nest an interactive Button inside a Link (Link and
Button in src/app/page.js), causing invalid interactive nesting; fix by removing
the nested interactive element: either make Link the sole interactive element
and replace the inner Button with a non-interactive element (e.g., span/div)
that preserves styling, or remove Link and keep Button as the interactive
control and perform navigation via router push/onClick; update all occurrences
of Link + Button (the Dashboard/Get Started pairs at the noted locations)
consistently so only one interactive widget handles activation.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
public/ivgit-logo.svgis excluded by!**/*.svg
📒 Files selected for processing (4)
src/app/aigit/page.jssrc/app/layout.jssrc/app/page.jssrc/components/logo.jsx
| <li className="flex gap-2"><span className="text-[#D97757]">✓</span> Team analytics</li> | ||
| <li className="flex gap-2"><span className="text-[#D97757]">✓</span> Priority support</li> | ||
| </ul> | ||
| <Button variant="outline" className="w-full border-[#E8E0D5] dark:border-[#2C2825]">Contact sales</Button> |
There was a problem hiding this comment.
“Contact sales” CTA needs an actual action.
At Line 220, the button is not connected to navigation or a handler. Make it actionable (e.g., mailto/contact route/modal trigger), or mark it disabled until available.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/page.js` at line 220, The "Contact sales" Button (the Button element
at Line ~220) has no action—make it actionable by adding a handler or
navigation: either wrap it with your router Link (e.g., navigate to a /contact
route) or add an onClick that opens a mailto (window.location.href =
'mailto:...') or triggers the sales modal function (e.g., openSalesModal()). If
you don't want it usable yet, explicitly set disabled and add an
aria-disabled/tooltip; ensure you update the Button component props (onClick,
href or disabled) and any referenced handler name (openSalesModal,
handleContactClick) so the button is discoverable and accessible.
- src/lib/github/app.js: Core GitHub App utilities - Webhook HMAC-SHA256 signature verification - Installation Octokit auth via @octokit/app - PR diff fetching (application/vnd.github.diff) - PR comment posting + previous review deletion on re-push - Review formatter: score bar, issues, suggestions, vibe check - src/app/api/github/pr-review/route.js: Webhook receiver - Verifies GitHub signature before processing - Handles pull_request opened/reopened/synchronize events - Skips draft PRs and empty diffs - Calls Claude reviewCode() and posts formatted comment - src/app/install/page.js: GitHub App installation landing page - How it works (3 steps), sample comment preview, permissions list - Install CTA linking to GitHub App URL - Landing page: nav + hero CTA now point to /install Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
components.json)src/lib/utils.jswith thecnutility helpersrc/components/ui/:button,card,badge,tabs,textareaTest plan
npm run devand verify the app builds without errorsButton) and confirm it renders correctly with Tailwind styles🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores