Skip to content

feat(playground): add ai-powered readme generator#268

Open
AdityaM-IITH wants to merge 6 commits into
piyushdotcomm:mainfrom
AdityaM-IITH:feat/auto-readme
Open

feat(playground): add ai-powered readme generator#268
AdityaM-IITH wants to merge 6 commits into
piyushdotcomm:mainfrom
AdityaM-IITH:feat/auto-readme

Conversation

@AdityaM-IITH
Copy link
Copy Markdown
Contributor

@AdityaM-IITH AdityaM-IITH commented May 26, 2026

Summary

  • Added a README generator feature to the playground toolbar. A new README button opens a dialog where the user picks a template style, then the system analyzes the project file tree and package.json to stream a complete README.md directly into the editor via the existing AI provider (gemini / groq / mistral).
  • Closes a long-standing gap where users had to write documentation manually outside the editor.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Test or CI improvement
  • Starter template change

Related issue

Closes #128

Validation

  • npm run lint
  • npm test
  • npm run build

List any additional manual verification you performed:

  • npm run lint — 0 errors, 25 pre-existing warnings (none in changed files)
  • npm test — 112/112 tests passed across 16 test files
  • npm run build — fails with Cannot find module '.prisma/client/default' due to Prisma client not being generated locally (requires a live DB + npx prisma generate). This failure exists on main and is unrelated to this PR
  • Manually verified collectFilePaths serializes nested folder structures correctly
  • Manually verified addOrUpdateFile upserts README.md at root without corrupting existing file tree

Screenshots or recordings

UI diff is visible in the changed files. The button renders in the toolbar between the core actions group and Deploy. The dialog follows the same visual structure as DeployDialog (same DialogContent, Tabs, Button components).

Checklist

  • I kept this PR focused on one primary change
  • I updated documentation if behavior changed
  • I did not commit secrets, local logs, or scratch files
  • I am requesting review on the correct scope

Summary by CodeRabbit

Release Notes

  • New Features
    • AI-powered README generation in the playground via a new "Generate README" toolbar button.
    • Choose a README template style; the tool analyzes your project files and streams generated content live.
    • Generated README is inserted into the workspace and automatically opened in the editor with success confirmation.

Review Change Stack

@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions
Copy link
Copy Markdown

👋 Thanks for opening a PR, @AdityaM-IITH!

Your PR has entered the 🚦 PR Review Pipeline.

Standard PR detected — your PR will follow the standard review pipeline.


What happens next

Stage Reviewer Checks
Stage 1 — Automated Validation 🤖 Bot DCO · Format · AI/Slop · Duplicate
Stage 2 — Human Review 👥 Maintainer Code + Quality Review
Stage 3 — PA / Maintainer Review 🔑 Project Admin Final Merge Decision

A pipeline status comment will appear below and update automatically as your PR progresses.


While you wait

  • Sign all commits (git commit -s)
  • Link your issue (Closes #123)
  • Use a feature branch (not main)
  • Avoid unrelated changes

This comment is posted only once.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 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

Implements AI-powered README generation: a POST /api/readme streaming endpoint with Zod validation, auth and IP rate limiting, frontend ReadmeGeneratorDialog that streams generated Markdown, and editor wiring to save/open the produced README.md.

Changes

README Generator Feature

Layer / File(s) Summary
UI State Management for README Dialog
modules/playground/hooks/usePlaygroundUI.ts
Extends Zustand store with isReadmeDialogOpen boolean state, setIsReadmeDialogOpen action, false initial value, and inclusion in resetUI.
Backend README API Route with Auth & Streaming
app/api/readme/route.ts
Implements POST /api/readme with a strict Markdown system prompt, Zod request validation, auth/userApiKey requirement, IP-based rate limiting, per-template instruction concatenation with fileTree/package.json, provider/model and API key resolution, and streaming via streamText returned with toTextStreamResponse().
README Generator Dialog Component
modules/playground/components/readme-generator-dialog.tsx
Adds ReadmeGeneratorDialog with template style tabs, extractPackageJson helper, handleGenerate that builds payload from file tree and optional package.json, POSTs to /api/readme, decodes and accumulates streamed chunks, calls onReadmeGenerated repeatedly, validates final output, shows success UI, and auto-closes.
Toolbar Button & Modal Wiring
modules/playground/components/playground-header.tsx, modules/playground/components/playground-modals.tsx
Adds ScrollText toolbar button to open the dialog, imports and renders ReadmeGeneratorDialog, and implements handleReadmeGenerated to add/update README.md in template items, persist via saveTemplateData, and open the file in the editor.

Sequence Diagram

sequenceDiagram
  participant User
  participant Toolbar as Header Button
  participant Dialog as ReadmeGeneratorDialog
  participant API as POST /api/readme
  participant Modals as PlaygroundModals
  participant Editor as File Editor

  User->>Toolbar: Click "Generate README"
  Toolbar->>Dialog: setIsReadmeDialogOpen(true)
  Dialog->>Dialog: Extract fileTree + package.json
  Dialog->>API: POST payload (fileTree, packageJson?, template, provider?, userApiKey?)
  API->>API: validate, auth, rate limit
  API-->>Dialog: stream README Markdown
  Dialog->>Dialog: accumulate streamed content
  Dialog->>Modals: onReadmeGenerated(content)
  Modals->>Modals: addOrUpdateFile("README.md", content)
  Modals->>Modals: saveTemplateData()
  Modals->>Editor: open README.md
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • piyushdotcomm

Poem

🐇 In the burrow of code I cheer,
Streaming readmes far and near,
File trees whisper, prompts take flight,
README grows in markdown light,
Tap the button — docs appear!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(playground): add ai-powered readme generator' clearly and concisely summarizes the main change—adding an AI-powered README generator feature to the playground.
Description check ✅ Passed The PR description covers the summary, type of change, related issue, validation steps, manual verification, screenshots, and all checklist items are properly addressed.
Linked Issues check ✅ Passed The PR successfully implements all key requirements from issue #128: adds a README generator button, supports multiple templates, analyzes project structure/dependencies, and saves README.md to the editor [#128].
Out of Scope Changes check ✅ Passed All changes are focused on implementing the README generator feature. No unrelated modifications to other features or systems were introduced.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/readme/route.ts`:
- Around line 67-113: Normalize userApiKey once at the top of the handler (e.g.,
const normalizedUserApiKey = userApiKey?.trim() || undefined) and use
normalizedUserApiKey everywhere instead of raw userApiKey; update the auth check
that currently does (!userApiKey || userApiKey.trim() === "") to use
normalizedUserApiKey, and replace each provider branch's apiKey assignment
(where code uses userApiKey || (isAuthenticated ? process.env.* : undefined))
with normalizedUserApiKey || (isAuthenticated ? process.env.* : undefined) so
whitespace-only keys won't block fallback to server-configured keys (affects
symbols: userApiKey, normalizedUserApiKey, provider branches that call
createGoogleGenerativeAI, createGroq, and model assignment).

In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 21-26: The component treats onReadmeGenerated as synchronous but
callers perform async persistence; update ReadmeGeneratorDialogProps to have
onReadmeGenerated return a Promise (e.g. onReadmeGenerated: (content: string) =>
Promise<void>), then await onReadmeGenerated(...) where the generation flow
currently sets the success state/auto-closes (refer to onReadmeGenerated call
sites around lines 116-118) and only show success/close the dialog after the
awaited promise resolves; also catch and handle rejection to show an error state
instead of closing on failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 35b15124-b224-495f-b82f-a0e3164cf835

📥 Commits

Reviewing files that changed from the base of the PR and between 354353e and d5120ab.

📒 Files selected for processing (5)
  • app/api/readme/route.ts
  • modules/playground/components/playground-header.tsx
  • modules/playground/components/playground-modals.tsx
  • modules/playground/components/readme-generator-dialog.tsx
  • modules/playground/hooks/usePlaygroundUI.ts

Comment thread app/api/readme/route.ts
Comment thread modules/playground/components/readme-generator-dialog.tsx
Copy link
Copy Markdown
Owner

@piyushdotcomm piyushdotcomm left a comment

Choose a reason for hiding this comment

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

add screenshot and recording of this feature

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modules/playground/components/readme-generator-dialog.tsx (1)

120-124: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear the timeout on unmount or early close to prevent stale state updates.

If the user manually closes the dialog before the 1.5s delay, the timeout still fires and calls setIsDone(false) / onOpenChange(false) on a potentially unmounted component, which can cause React warnings or unexpected behavior.

🛡️ Proposed fix using a ref to track and clear the timeout
+import React, { useState, useCallback, useRef, useEffect } from "react";
...
 export function ReadmeGeneratorDialog({
     ...
 }: ReadmeGeneratorDialogProps) {
     const [template, setTemplate] = useState<ReadmeTemplate>("standard");
     const [isGenerating, setIsGenerating] = useState(false);
     const [isDone, setIsDone] = useState(false);
+    const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

     const { provider, getUserApiKey } = useAI();

+    useEffect(() => {
+        return () => {
+            if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
+        };
+    }, []);

     const handleGenerate = useCallback(async () => {
         ...
             // Auto-close after a short delay so the user sees the success state.
-            setTimeout(() => {
+            closeTimeoutRef.current = setTimeout(() => {
                 onOpenChange(false);
                 setIsDone(false);
             }, 1500);
         ...
     }, [...]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/readme-generator-dialog.tsx` around lines 120 -
124, The setTimeout in the readme-generator-dialog component can fire after the
dialog unmounts or is closed early, causing stale state updates from
setIsDone(false) and onOpenChange(false); fix by storing the timeout id in a ref
(e.g., timeoutRef), clearTimeout(timeoutRef.current) before starting a new
timeout, and clear it in the component cleanup (useEffect return) and any
early-close/onClose handler so the timeout never runs after unmount or manual
close; update the setTimeout usage around onOpenChange and setIsDone to use this
ref-managed timeout.
🧹 Nitpick comments (2)
modules/playground/components/readme-generator-dialog.tsx (1)

74-130: ⚡ Quick win

Add an AbortController to cancel the fetch when the dialog closes or component unmounts.

If the user closes the dialog while README generation is in progress, the fetch continues running in the background, wasting resources and potentially calling state setters on an unmounted component.

♻️ Proposed approach
 export function ReadmeGeneratorDialog({
     ...
 }: ReadmeGeneratorDialogProps) {
     ...
+    const abortControllerRef = useRef<AbortController | null>(null);

+    useEffect(() => {
+        return () => {
+            abortControllerRef.current?.abort();
+        };
+    }, []);

     const handleGenerate = useCallback(async () => {
         ...
         setIsGenerating(true);
         setIsDone(false);
+        abortControllerRef.current = new AbortController();

         try {
             const userApiKey = getUserApiKey(provider as AIProvider);

             const res = await fetch("/api/readme", {
                 method: "POST",
                 headers: { "Content-Type": "application/json" },
                 body: JSON.stringify({ ... }),
+                signal: abortControllerRef.current.signal,
             });
             ...
         } catch (error: unknown) {
+            if (error instanceof Error && error.name === "AbortError") {
+                return; // User cancelled, no error toast needed
+            }
             console.error("README generation error:", error);
             toast.error(error instanceof Error ? error.message : "Failed to generate README.");
         } finally {
             setIsGenerating(false);
         }
     }, [...]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/readme-generator-dialog.tsx` around lines 74 -
130, Wrap the fetch("/api/readme") call with an AbortController: create a
controller (e.g., in a ref) and pass controller.signal to fetch, store/replace
it when generation starts (where setIsGenerating(true) is called), and call
controller.abort() when the dialog closes or the component unmounts (hook into
onOpenChange(false) close handler and useEffect cleanup). Ensure the
fetch/stream read loop (res.body.getReader()) will be canceled by the signal and
handle the abort error in the catch block so you don't show a failure toast for
user-initiated cancels (detect DOMException/name === "AbortError" or similar),
and clear/reset the controller and UI state (setIsGenerating(false),
setIsDone(false) as appropriate) after abort so onReadmeGenerated and success
toast are not called for aborted requests.
app/api/readme/route.ts (1)

124-130: ⚡ Quick win

Pass AbortSignal to streamText so the LLM call cancels when the client disconnects.

In app/api/readme/route.ts, the POST handler already receives request: NextRequest (so request.signal is available); wiring it into streamText prevents continued token generation if the connection drops.

♻️ Proposed change
         const resultStream = streamText({
             model,
             system: README_SYSTEM_PROMPT,
             prompt: contextParts,
+            abortSignal: request.signal,
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/readme/route.ts` around lines 124 - 130, The LLM streaming call
should be cancellable when the client disconnects: update the streamText
invocation in the POST handler to pass the incoming request.signal (available
from the NextRequest) as the AbortSignal (e.g., add a signal: request.signal
property alongside model, system: README_SYSTEM_PROMPT, and prompt:
contextParts), and ensure streamText (and any downstream code that performs
fetch/LLM requests) accepts and forwards that signal so token generation stops
on client disconnect before returning resultStream.toTextStreamResponse().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 120-124: The setTimeout in the readme-generator-dialog component
can fire after the dialog unmounts or is closed early, causing stale state
updates from setIsDone(false) and onOpenChange(false); fix by storing the
timeout id in a ref (e.g., timeoutRef), clearTimeout(timeoutRef.current) before
starting a new timeout, and clear it in the component cleanup (useEffect return)
and any early-close/onClose handler so the timeout never runs after unmount or
manual close; update the setTimeout usage around onOpenChange and setIsDone to
use this ref-managed timeout.

---

Nitpick comments:
In `@app/api/readme/route.ts`:
- Around line 124-130: The LLM streaming call should be cancellable when the
client disconnects: update the streamText invocation in the POST handler to pass
the incoming request.signal (available from the NextRequest) as the AbortSignal
(e.g., add a signal: request.signal property alongside model, system:
README_SYSTEM_PROMPT, and prompt: contextParts), and ensure streamText (and any
downstream code that performs fetch/LLM requests) accepts and forwards that
signal so token generation stops on client disconnect before returning
resultStream.toTextStreamResponse().

In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 74-130: Wrap the fetch("/api/readme") call with an
AbortController: create a controller (e.g., in a ref) and pass controller.signal
to fetch, store/replace it when generation starts (where setIsGenerating(true)
is called), and call controller.abort() when the dialog closes or the component
unmounts (hook into onOpenChange(false) close handler and useEffect cleanup).
Ensure the fetch/stream read loop (res.body.getReader()) will be canceled by the
signal and handle the abort error in the catch block so you don't show a failure
toast for user-initiated cancels (detect DOMException/name === "AbortError" or
similar), and clear/reset the controller and UI state (setIsGenerating(false),
setIsDone(false) as appropriate) after abort so onReadmeGenerated and success
toast are not called for aborted requests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f6ad971-982d-4451-8a86-053e5c15c0eb

📥 Commits

Reviewing files that changed from the base of the PR and between d5120ab and b943133.

📒 Files selected for processing (2)
  • app/api/readme/route.ts
  • modules/playground/components/readme-generator-dialog.tsx

@AdityaM-IITH
Copy link
Copy Markdown
Contributor Author

Here is a quick demo of the README Generator feature in action! 🚀

The feature analyzes the current workspace (file tree and package.json) and streams the AI-generated README.md directly into the editor for a seamless experience.

UI Screenshots:
Screenshot 2026-05-26 141013
Screenshot 2026-05-26 141100

Screen Recording:

pr-268.mp4

Let me know if you need any adjustments or if this looks good to merge.

@Maxd646 Maxd646 self-requested a review May 26, 2026 09:21
Copy link
Copy Markdown
Collaborator

@Maxd646 Maxd646 left a comment

Choose a reason for hiding this comment

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

Great work on this feature! but the following need changed before the merge

  1. Fix the Streaming UX: In readme-generator-dialog.tsx, the response body is being accumulated fully before updating the UI. This completely defeats the purpose of the server-side streaming; the user just sees a static loading spinner. We need to progressively feed chunks to onReadmeGenerated so it streams into the editor in real-time.

  2. Timeout Memory Leak: The 1500ms auto-close setTimeout can trigger an unmounted state update if a user manually dismisses the dialog quickly. Wrap it with a useRef and clear it on unmount using a useEffect cleanup hook.

and for optimzation

  • API Optimization: Move the authentication check above the rate limiter in the API route to prevent unauthenticated requests from exhausting the rate limit pool.

@AdityaM-IITH
Copy link
Copy Markdown
Contributor Author

Thanks for the feedback! I've addressed all the requested changes:

  • Streaming UX: The response body is now progressively fed to onReadmeGenerated as chunks arrive, allowing it to stream into the editor in real-time.
  • Timeout Memory Leak: The auto-close timeout is now tracked with a useRef and properly cleared on unmount using useEffect.
  • API Optimization: Moved the authentication check above the rate limiter in the API route so unauthenticated requests don't exhaust the rate limit pool.

Let me know if there's anything else you need.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/api/readme/route.ts (1)

53-75: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply rate limiting before the unauthorized early return.

Line 53 returns 401 before Lines 60-75, so unauthenticated traffic is currently not rate-limited. That creates an easy abuse path against this endpoint.

🔧 Suggested fix
-        if (!session?.user?.id && !normalizedUserApiKey) {
-            return NextResponse.json(
-                { success: false, error: "Unauthorized: Please log in or provide your own API key in settings." },
-                { status: 401 }
-            );
-        }
-
         const ip = getClientIp(request);
         // Stricter limit than chat: README generation is more expensive.
         const { allowed, remaining } = await rateLimit(ip, 5, 60_000);

         if (!allowed) {
             return NextResponse.json(
                 { success: false, error: "Rate limit exceeded. Please wait before generating again." },
                 {
                     status: 429,
                     headers: {
                         "Retry-After": "60",
                         "X-RateLimit-Remaining": String(remaining),
                     },
                 }
             );
         }
+
+        if (!session?.user?.id && !normalizedUserApiKey) {
+            return NextResponse.json(
+                { success: false, error: "Unauthorized: Please log in or provide your own API key in settings." },
+                { status: 401 }
+            );
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/readme/route.ts` around lines 53 - 75, Move the rate-limit check to
run before the unauthorized early return: call getClientIp(request) and await
rateLimit(ip, 5, 60_000) immediately after computing
session/normalizedUserApiKey, then if !allowed return the 429 response (with
"Retry-After" and "X-RateLimit-Remaining") before the existing unauthorized
check that returns NextResponse.json(..., { status: 401 }); ensure you still
compute and use remaining in the 429 response and keep the same authorization
logic using session?.user?.id and normalizedUserApiKey.
modules/playground/components/readme-generator-dialog.tsx (1)

113-126: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t persist README on every stream chunk.

Line 117 calls onReadmeGenerated for each chunk, which can trigger repeated persistence/open-file side effects and save partial content before final validation. Persist once after finalContent is validated.

🔧 Suggested fix
             while (true) {
                 const { done, value } = await reader.read();
                 if (done) break;
                 accumulated += decoder.decode(value, { stream: true });
-                await onReadmeGenerated(accumulated);
             }

             const finalContent = accumulated.trim();
             if (!finalContent) {
                 throw new Error("Model returned an empty README. Try again.");
             }
+
+            await onReadmeGenerated(finalContent);

             setIsDone(true);
             toast.success("README.md generated and opened in editor.");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/readme-generator-dialog.tsx` around lines 113 -
126, The code currently calls onReadmeGenerated for every stream chunk inside
the reader.read loop, causing repeated persistence/open-file side effects and
saving partial content; modify the loop that reads from reader.read so it only
accumulates chunks into the accumulated string (using decoder.decode with
{stream: true}) and remove the per-chunk call to onReadmeGenerated, then after
the loop compute finalContent = accumulated.trim(), validate that finalContent
is not empty, and only then call onReadmeGenerated(finalContent) (followed by
setIsDone(true) and toast.success) to persist/open the README once; keep
references to reader.read, accumulated, decoder.decode, onReadmeGenerated,
finalContent, setIsDone, and toast.success when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@middleware.ts`:
- Around line 40-43: The branch in middleware.ts that checks "if (!isLoggedIn &&
!isPublicRoute)" currently returns null and must instead redirect
unauthenticated users to the sign-in page; change the return to use
Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT /* or "/auth/sign-in" */,
nextUrl)) (or the existing sign-in URL constant) so protected pages enforce
auth, keeping API passthrough logic intact; update the return in that branch
(referencing isLoggedIn, isPublicRoute, nextUrl, and DEFAULT_LOGIN_REDIRECT)
accordingly.

In `@modules/playground/actions/index.ts`:
- Around line 102-118: Replace the hardcoded stub in getPlaygroundById with the
original implementation that performs the authenticated database lookup and
returns the real playground and template data; remove the static test object and
reintroduce the DB call(s) and auth checks used elsewhere (e.g., the code path
used by SaveUpdatedCode) so getPlaygroundById validates the user, queries the
playground record by id, and returns the same { playgroundData, templateData }
shape expected by consumers. Ensure you call the same auth utilities and DB
methods used across this module (or restore the previous function body) so loads
match saves.

---

Outside diff comments:
In `@app/api/readme/route.ts`:
- Around line 53-75: Move the rate-limit check to run before the unauthorized
early return: call getClientIp(request) and await rateLimit(ip, 5, 60_000)
immediately after computing session/normalizedUserApiKey, then if !allowed
return the 429 response (with "Retry-After" and "X-RateLimit-Remaining") before
the existing unauthorized check that returns NextResponse.json(..., { status:
401 }); ensure you still compute and use remaining in the 429 response and keep
the same authorization logic using session?.user?.id and normalizedUserApiKey.

In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 113-126: The code currently calls onReadmeGenerated for every
stream chunk inside the reader.read loop, causing repeated persistence/open-file
side effects and saving partial content; modify the loop that reads from
reader.read so it only accumulates chunks into the accumulated string (using
decoder.decode with {stream: true}) and remove the per-chunk call to
onReadmeGenerated, then after the loop compute finalContent =
accumulated.trim(), validate that finalContent is not empty, and only then call
onReadmeGenerated(finalContent) (followed by setIsDone(true) and toast.success)
to persist/open the README once; keep references to reader.read, accumulated,
decoder.decode, onReadmeGenerated, finalContent, setIsDone, and toast.success
when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4831547c-7e08-45ba-a61e-402c043e6778

📥 Commits

Reviewing files that changed from the base of the PR and between b943133 and cfe9775.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • app/api/readme/route.ts
  • middleware.ts
  • modules/playground/actions/index.ts
  • modules/playground/components/readme-generator-dialog.tsx

Comment thread middleware.ts Outdated
Comment thread modules/playground/actions/index.ts
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.

🧹 Nitpick comments (1)
modules/playground/components/readme-generator-dialog.tsx (1)

109-117: ⚡ Quick win

Flush the TextDecoder after the stream ends.

When using TextDecoder with { stream: true }, a final decode() call without arguments is needed to flush any buffered bytes from incomplete multi-byte sequences. Without this, the last character could be truncated if it spans chunk boundaries.

Proposed fix
             const reader = res.body.getReader();
             const decoder = new TextDecoder();
             let accumulated = "";

             while (true) {
                 const { done, value } = await reader.read();
                 if (done) break;
                 accumulated += decoder.decode(value, { stream: true });
             }
+            accumulated += decoder.decode(); // flush remaining bytes

             const finalContent = accumulated.trim();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/readme-generator-dialog.tsx` around lines 109 -
117, The stream read loop using reader = res.body.getReader() and decoder = new
TextDecoder() uses decoder.decode(value, { stream: true }) but never flushes the
decoder; after the while loop (once done is true) call decoder.decode() with no
arguments and append its result to accumulated to flush any buffered bytes from
incomplete multi-byte sequences (update the logic around reader/read and
accumulated in readme-generator-dialog's streaming handler to append
decoder.decode() after the loop).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 109-117: The stream read loop using reader = res.body.getReader()
and decoder = new TextDecoder() uses decoder.decode(value, { stream: true }) but
never flushes the decoder; after the while loop (once done is true) call
decoder.decode() with no arguments and append its result to accumulated to flush
any buffered bytes from incomplete multi-byte sequences (update the logic around
reader/read and accumulated in readme-generator-dialog's streaming handler to
append decoder.decode() after the loop).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c5a87be-4c13-401a-a669-18f3866c3867

📥 Commits

Reviewing files that changed from the base of the PR and between cfe9775 and 44b40d7.

📒 Files selected for processing (2)
  • app/api/readme/route.ts
  • modules/playground/components/readme-generator-dialog.tsx

Copy link
Copy Markdown
Collaborator

@Maxd646 Maxd646 left a comment

Choose a reason for hiding this comment

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

Thanks for the quick response! However, looking through the diff, it seems a few of the requested changes didn't actually make it into the code updates.

  1. Streaming UX -> readme-generator-dialog.tsx
    You've set up the reader.read() loop, but onReadmeGenerated is still being called after the loop completes on line 114. We need to call it inside the loop so the chunks update the editor in real-time.

Fix: Inside your while (true) loop, pass the accumulated text to the callback on every chunk:

like
while (true) {
const { done, value } = await reader.read();
if (done) break;
accumulated += decoder.decode(value, { stream: true });
// Feed the stream chunks into the editor in real-time
await onReadmeGenerated(accumulated);
}
-> You'll also want to make sure your loading spinner UI doesn't completely block the user from seeing the editor update once streaming begins).

  1. API Rate Limiting -> app/api/readme/route.ts
    The rate limiter is still executing before the authentication check. Unauthenticated requests will still trigger hit counters on the rate limit pool.

-> Fix: Move the if (!session?.user?.id && !normalizedUserApiKey) block above the rateLimit(ip, 5, 60_000) block.

Let me know when you've pushed these updates and I'll take another look!

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modules/playground/components/readme-generator-dialog.tsx (1)

116-121: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid persistence side-effects in the per-chunk stream loop.

Line 120 currently does await onReadmeGenerated(accumulated) for every chunk. With the current caller (modules/playground/components/playground-modals.tsx, Lines 57-70), this causes repeated saveTemplateData(...) + openFile(...) calls during streaming, which can create heavy write churn and flaky UX on longer outputs.

Proposed contract split (progress vs final)
 interface ReadmeGeneratorDialogProps {
   open: boolean;
   onOpenChange: (open: boolean) => void;
   templateData: TemplateFolder | null;
+  onReadmeProgress?: (content: string) => void;
   onReadmeGenerated: (content: string) => Promise<void> | void;
 }
@@
 export function ReadmeGeneratorDialog({
   open,
   onOpenChange,
   templateData,
+  onReadmeProgress,
   onReadmeGenerated,
 }: ReadmeGeneratorDialogProps) {
@@
             while (true) {
                 const { done, value } = await reader.read();
                 if (done) break;
                 accumulated += decoder.decode(value, { stream: true });
-                await onReadmeGenerated(accumulated);
+                onReadmeProgress?.(accumulated);
             }
@@
             const finalContent = accumulated.trim();
             if (!finalContent) {
                 throw new Error("Model returned an empty README. Try again.");
             }
+            await onReadmeGenerated(finalContent);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/playground/components/readme-generator-dialog.tsx` around lines 116 -
121, The per-chunk loop in readme-generator-dialog.tsx calls await
onReadmeGenerated(accumulated) for every chunk (inside the while(true)
reader.read() loop), which causes the caller's heavy side-effects
(saveTemplateData/openFile) to run repeatedly; change the contract so the loop
emits progress-only updates and call the final persistence once when streaming
completes: add a new onReadmeProgress(chunkOrAccumulated) callback and invoke
that inside the loop using decoder.decode(value, {stream:true})/accumulated for
UI progress, then after the loop finishes call onReadmeGenerated(accumulated)
exactly once; update the caller (playground-modals.tsx) to use onReadmeProgress
for incremental UI and keep saveTemplateData/openFile only in the final
onReadmeGenerated handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@modules/playground/components/readme-generator-dialog.tsx`:
- Around line 116-121: The per-chunk loop in readme-generator-dialog.tsx calls
await onReadmeGenerated(accumulated) for every chunk (inside the while(true)
reader.read() loop), which causes the caller's heavy side-effects
(saveTemplateData/openFile) to run repeatedly; change the contract so the loop
emits progress-only updates and call the final persistence once when streaming
completes: add a new onReadmeProgress(chunkOrAccumulated) callback and invoke
that inside the loop using decoder.decode(value, {stream:true})/accumulated for
UI progress, then after the loop finishes call onReadmeGenerated(accumulated)
exactly once; update the caller (playground-modals.tsx) to use onReadmeProgress
for incremental UI and keep saveTemplateData/openFile only in the final
onReadmeGenerated handler.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 652c41e1-b06b-4d5e-b1ae-afccee9c3530

📥 Commits

Reviewing files that changed from the base of the PR and between 44b40d7 and 25e66ad.

📒 Files selected for processing (1)
  • modules/playground/components/readme-generator-dialog.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add AI-Powered Auto README Generator with Templates & Project Analysis

3 participants