Skip to content

Add shadcn/ui components#20

Open
ayaqen wants to merge 7 commits into
appwrite:mainfrom
ayaqen:add-shadcn-components
Open

Add shadcn/ui components#20
ayaqen wants to merge 7 commits into
appwrite:mainfrom
ayaqen:add-shadcn-components

Conversation

@ayaqen
Copy link
Copy Markdown

@ayaqen ayaqen commented Mar 2, 2026

Summary

  • Initialize shadcn/ui with Tailwind v4 and Neutral base color (components.json)
  • Add src/lib/utils.js with the cn utility helper
  • Add 5 UI components to src/components/ui/: button, card, badge, tabs, textarea

Test plan

  • Run npm run dev and verify the app builds without errors
  • Import a component (e.g. Button) and confirm it renders correctly with Tailwind styles

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • New UI component library (Badge, Button, Card, Tabs, Textarea) with styling variants.
    • Global theme provider, ThemeToggle, logo component, and comprehensive light/dark design tokens and animations.
    • Redesigned landing page and AI dashboard (code review, commit messages, PR workflow, usage UI).
    • New AI endpoints for code review and commit-message suggestions.
  • Chores

    • Project UI config, class-name utility, new CLI-style npm scripts, and added UI/icon/developer dependencies.

- 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>
Copilot AI review requested due to automatic review settings March 2, 2026 20:26
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 2, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds 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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add shadcn/ui components' accurately describes the main addition of shadcn/ui component library integration, which represents the primary focus of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
package.json (2)

22-22: Unrelated dependency: @anthropic-ai/sdk

The 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 vibe tooling, 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 the git script 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

📥 Commits

Reviewing files that changed from the base of the PR and between 58754a2 and cccce7c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • components.json
  • package.json
  • src/app/app.css
  • src/components/ui/badge.jsx
  • src/components/ui/button.jsx
  • src/components/ui/card.jsx
  • src/components/ui/tabs.jsx
  • src/components/ui/textarea.jsx
  • src/lib/utils.js

Comment thread src/components/ui/card.jsx
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 cn utility 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.

Comment thread package.json
Comment thread src/components/ui/tabs.jsx
Comment thread src/components/ui/textarea.jsx
Comment thread src/components/ui/card.jsx
Comment thread src/components/ui/card.jsx
Comment thread package.json
Comment thread package.json
Comment thread src/components/ui/button.jsx
Comment thread src/components/ui/badge.jsx
@ayaqen
Copy link
Copy Markdown
Author

ayaqen commented Mar 2, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

ayaqen and others added 2 commits March 2, 2026 18:08
- 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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cccce7c and 31c94c3.

📒 Files selected for processing (3)
  • src/app/aigit/page.js
  • src/app/layout.js
  • src/app/page.js

Comment thread src/app/aigit/page.js
Comment on lines +18 to +35
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)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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.js

Repository: appwrite/starter-for-nextjs

Length of output: 17331


🏁 Script executed:

# Find API route handlers for aigit
find src -path "*api*aigit*" -type f

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

Repository: appwrite/starter-for-nextjs

Length of output: 2127


🏁 Script executed:

cat -n src/app/api/aigit/suggest/route.js

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

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

Comment thread src/app/aigit/page.js Outdated
Comment on lines +80 to +82
<Button className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700">
Upgrade to Pro
</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment thread src/app/aigit/page.js Outdated
Comment on lines +176 to +177
disabled={loading || !diff.trim() || usageCount >= maxFreeOps}
className="bg-purple-600 hover:bg-purple-700"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.js" -o -name "*.ts" -o -name "*.tsx" | grep -E "(api|route)" | head -20

Repository: appwrite/starter-for-nextjs

Length of output: 143


🏁 Script executed:

# Check the file structure of the project
ls -la

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

Repository: appwrite/starter-for-nextjs

Length of output: 139


🏁 Script executed:

wc -l src/app/aigit/page.js

Repository: appwrite/starter-for-nextjs

Length of output: 96


🏁 Script executed:

sed -n '170,180p' src/app/aigit/page.js

Repository: appwrite/starter-for-nextjs

Length of output: 656


🏁 Script executed:

sed -n '235,250p' src/app/aigit/page.js

Repository: appwrite/starter-for-nextjs

Length of output: 911


🏁 Script executed:

cat src/app/api/aigit/review/route.js

Repository: appwrite/starter-for-nextjs

Length of output: 1763


🏁 Script executed:

cat src/app/api/aigit/suggest/route.js

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

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

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

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

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

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

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

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

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

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

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

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

Comment thread src/app/aigit/page.js Outdated
Comment thread src/app/page.js
Comment on lines +27 to +36
<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>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Explore repository structure
git ls-files | head -30

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

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

Repository: appwrite/starter-for-nextjs

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Read the Button component definition
cat -n src/components/ui/button.jsx

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

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

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

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

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

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

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

Comment thread src/app/page.js Outdated
Comment on lines +67 to +69
<Button size="lg" variant="outline" className="text-white border-slate-600 hover:bg-slate-800 text-lg px-8 py-6">
▶ Watch Demo
</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

“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.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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-only diff can slip through to Line 46. Coerce and validate staged, diff, and vibeMode first.

💡 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 at src/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 the staged flag 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).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31c94c3 and f0093a3.

📒 Files selected for processing (2)
  • src/app/api/aigit/review/route.js
  • src/app/api/aigit/suggest/route.js

Comment on lines +16 to +52
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 });
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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.js

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

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

Repository: appwrite/starter-for-nextjs

Length of output: 237


🏁 Script executed:

# Read the full route file
cat -n src/app/api/aigit/review/route.js

Repository: appwrite/starter-for-nextjs

Length of output: 2127


🏁 Script executed:

# Check the suggest route as well
cat -n src/app/api/aigit/suggest/route.js

Repository: appwrite/starter-for-nextjs

Length of output: 1989


🏁 Script executed:

# Search for middleware files in standard Next.js locations
find src -name '*middleware*' -type f

Repository: 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/null

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

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

Comment on lines +49 to +50
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

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

Comment on lines +26 to +31
if (isAIEnabled()) {
// Use Claude for intelligent suggestions
const [primary, alternatives] = await Promise.all([
generateCommitMessage(diff, context),
generateCommitAlternatives(diff, context),
]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +51 to +53
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

ayaqen and others added 2 commits March 2, 2026 18:20
- 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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
src/app/page.js (2)

73-75: ⚠️ Potential issue | 🟡 Minor

Dead 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 | 🔴 Critical

Avoid nested interactive controls (Link wrapping Button).

These segments render nested actionable elements, which breaks semantics and keyboard/screen-reader behavior. Use Button asChild so 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.js

Also 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 | 🟡 Minor

Upgrade 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 | 🟠 Major

Check res.ok before 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.js

Also 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

📥 Commits

Reviewing files that changed from the base of the PR and between f0093a3 and 0fa8426.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • package.json
  • src/app/aigit/page.js
  • src/app/app.css
  • src/app/layout.js
  • src/app/page.js
  • src/components/theme-provider.jsx
  • src/components/theme-toggle.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Comment thread src/app/aigit/page.js
Comment on lines +250 to +254
<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)}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# 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
fi

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

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

Comment thread src/app/app.css
Comment on lines +5 to +8
@custom-variant dark (&:is(.dark *));

/* ─── Claude-inspired custom tokens ─── */
@theme inline {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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
fi

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

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

Comment on lines +8 to +9
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

next-themes theme vs resolvedTheme enableSystem documentation

💡 Result:

  • theme (from useTheme()): the active theme setting the user selected/stored (e.g. "light", "dark", or "system" when enableSystem is on). (github.com)
  • resolvedTheme: the effective theme actually being applied. If theme === "system" and enableSystem === true, then resolvedTheme becomes "light" or "dark" based on prefers-color-scheme; otherwise it’s the same as theme. (github.com)
  • enableSystem (ThemeProvider prop): controls whether the "system" option exists/works (i.e., switching based on prefers-color-scheme). If enableSystem is false, system-based switching is disabled and the docs note defaultTheme becomes "light". (github.com)

Rule of thumb:

  • Use theme when you care whether the user chose System vs explicit Light/Dark (UI should show “System”). (github.com)
  • Use resolvedTheme when you need to apply styles/logic as light vs dark, even when the user selected System. (github.com)

Citations:


🏁 Script executed:

head -25 src/components/theme-toggle.jsx

Repository: appwrite/starter-for-nextjs

Length of output: 885


🏁 Script executed:

fd -t f "layout\|provider\|_app" --extension jsx --extension tsx --extension js | head -20

Repository: appwrite/starter-for-nextjs

Length of output: 53


🏁 Script executed:

rg -l "ThemeProvider\|next-themes" --type jsx --type tsx --type js | head -10

Repository: appwrite/starter-for-nextjs

Length of output: 98


🏁 Script executed:

rg "ThemeProvider" --type js --type typescript

Repository: appwrite/starter-for-nextjs

Length of output: 105


🏁 Script executed:

git ls-files | grep -E "app\.|layout\|provider" | head -20

Repository: appwrite/starter-for-nextjs

Length of output: 86


🏁 Script executed:

git ls-files | head -30

Repository: appwrite/starter-for-nextjs

Length of output: 701


🏁 Script executed:

cat -n src/components/theme-provider.jsx

Repository: appwrite/starter-for-nextjs

Length of output: 330


🏁 Script executed:

cat -n src/app/layout.js

Repository: 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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
src/app/aigit/page.js (3)

247-257: ⚠️ Potential issue | 🟡 Minor

Use 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 | 🟠 Major

Gate state updates on HTTP success before incrementing usage.

At Line 19–35 and Line 38–55, setResult and setUsageCount run even when the server returns non-2xx responses, because res.ok is 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 | 🟡 Minor

Upgrade 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 | 🟠 Major

Resolve nested interactive controls in CTA markup.

At Line 27–36, Line 66–70, Line 172–174, Line 198–200, and Line 236–240, Button is nested inside Link. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0fa8426 and cb8be0f.

⛔ Files ignored due to path filters (1)
  • public/ivgit-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (4)
  • src/app/aigit/page.js
  • src/app/layout.js
  • src/app/page.js
  • src/components/logo.jsx

Comment thread src/app/page.js
<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>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

“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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants