Skip to content

Feat: Download Projects, Deploy to Vercel + Confetti Celebrations#5

Open
M-DEV-1 wants to merge 3 commits intomainfrom
code-rabbit-preview
Open

Feat: Download Projects, Deploy to Vercel + Confetti Celebrations#5
M-DEV-1 wants to merge 3 commits intomainfrom
code-rabbit-preview

Conversation

@M-DEV-1
Copy link
Owner

@M-DEV-1 M-DEV-1 commented Dec 14, 2025

Summary by CodeRabbit

  • New Features
    • Download project source code as a ZIP archive.
    • Deploy projects to Vercel directly from the app.
    • Celebratory confetti animation when preview loads.
    • File system now updates automatically every 3 seconds.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Dec 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
autodev Ready Ready Preview, Comment Dec 14, 2025 4:57pm

@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

Walkthrough

This pull request introduces a project download feature with ZIP archive generation, adds a Deploy to Vercel button with GitHub URL integration, and migrates API calls from environment-based URLs to proxy endpoints. Client-side features include confetti animation and file polling, while server-side enhancements add file size gating to prevent large file transmission.

Changes

Cohort / File(s) Summary
Package dependencies
apps/client/package.json, apps/server/package.json
Added canvas-confetti (client) and archiver (server) dependencies with corresponding type definitions.
Client API endpoint migration
apps/client/src/app/project/[projectId]/page.tsx, apps/client/src/components/agent/ChatPanel.tsx, apps/client/src/store/project.ts
Replaced environment-based API URL references with fixed proxy endpoints (/api/proxy/*). Default API URL now empty string when undefined.
File system and preview panel enhancements
apps/client/src/components/agent/FileSystemPanel.tsx, apps/client/src/components/agent/PreviewPanel.tsx
Added download link for project source, implemented 3-second file polling, integrated GitHub URL fetching and Deploy to Vercel button, and triggered confetti animation on preview ready.
Server project download endpoint
apps/server/src/index.ts
Implemented GET /api/projects/:projectId/download endpoint that streams ZIP archives of project code with proper error handling and headers.
File content gating logic
apps/server/src/services/workspace.ts
Added file size threshold (100KB) to hide large file contents with placeholder text instead of transmitting full content.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–75 minutes

Areas requiring extra attention:

  • Verify the archiver ZIP generation logic handles edge cases (missing files, permission errors, empty projects)
  • Validate proxy endpoint routing is correctly configured and doesn't introduce security bypasses
  • Confirm file polling (3-second interval) doesn't cause performance issues or excessive API load
  • Review file size gating logic (100KB threshold) to ensure consistency with codebase standards
  • Inspect confetti animation trigger conditions to avoid unintended celebrations
  • Ensure GitHub URL fetch gracefully handles missing or private repositories

Poem

🐰 A download button hops with glee,
Deploy to Vercel, fancy and free,
Confetti rains when preview is tight,
Large files hide—keeping payloads light! ✨📦

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the three main features introduced: project download capability, Vercel deployment integration, and confetti celebration animations.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch code-rabbit-preview

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

Copy link

@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 (3)
apps/client/src/store/project.ts (1)

105-116: Handle non-2xx responses in fetchFiles
res.json() on error responses can throw and you lose status/context.

-            const res = await fetch(`/api/proxy/projects/${projectId}/files`);
-            const data = await res.json();
+            const res = await fetch(`/api/proxy/projects/${projectId}/files`);
+            if (!res.ok) throw new Error(`fetchFiles failed: ${res.status} ${res.statusText}`);
+            const data = await res.json();
apps/server/src/services/workspace.ts (1)

79-113: Fix path separator consistency (Windows breaks client tree parsing)
Client-side tree building typically assumes /. On Windows, path.join will produce \, causing incorrect nesting/selection.

-                const relPath = path.join(relativePath, entry.name);
+                const relPath = path.posix.join(relativePath, entry.name);

Also consider guarding fs.stat per-file so one bad entry doesn’t fail the whole listing.

apps/client/src/components/agent/PreviewPanel.tsx (1)

102-137: Toolbar structure is broken with duplicate elements.

The new elements (divider, Deploy button, refresh, and external link) are placed outside the flex container that closes at line 114. Additionally, there are duplicate RefreshCw and ExternalLink buttons—one set inside the container (lines 108-113) and another outside (lines 132-137).

This will cause:

  1. Layout issues—new elements won't be aligned within the toolbar pill
  2. Duplicate buttons rendered to the user

Move all toolbar elements inside the flex container and remove duplicates:

             <div className="flex items-center gap-2 px-3 py-1.5 bg-[#0A0A0B]/80 backdrop-blur-md rounded-full border border-white/10 shadow-xl shadow-indigo-500/10">
                 <span className="text-[10px] text-zinc-400 font-mono max-w-[150px] truncate">
                     /preview/{projectId}
                 </span>
                 <div className="h-3 w-px bg-white/10" />
+                {githubUrl && (
+                    <>
+                        <a
+                            href={`https://vercel.com/new/import?repository-url=${encodeURIComponent(githubUrl)}`}
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            className="flex items-center gap-1 text-xs font-medium text-white bg-black hover:bg-neutral-900 px-2 py-0.5 rounded transition-colors"
+                        >
+                            <Rocket className="w-3 h-3" />
+                            Deploy
+                        </a>
+                        <div className="h-3 w-px bg-white/10" />
+                    </>
+                )}
                 <button onClick={handleRefresh} className="text-zinc-400 hover:text-white transition-colors">
                     <RefreshCw className={cn("w-3 h-3", status === 'loading' && "animate-spin")} />
                 </button>
                 <a href={previewUrl} target="_blank" rel="noopener noreferrer" className="text-zinc-400 hover:text-white transition-colors">
                     <ExternalLink className="w-3 h-3" />
                 </a>
             </div>
-            <div className="h-3 w-px bg-white/10" />
-
-            {githubUrl && (
-                <>
-                    <a
-                        href={`https://vercel.com/new/import?repository-url=${encodeURIComponent(githubUrl)}`}
-                        target="_blank"
-                        rel="noopener noreferrer"
-                        className="flex items-center gap-1 text-xs font-medium text-white bg-black hover:bg-neutral-900 px-2 py-0.5 rounded transition-colors"
-                    >
-                        <Rocket className="w-3 h-3" />
-                        Deploy
-                    </a>
-                    <div className="h-3 w-px bg-white/10" />
-                </>
-            )}
-
-            <button onClick={handleRefresh} className="text-zinc-400 hover:text-white transition-colors">
-                <RefreshCw className={cn("w-3 h-3", status === 'loading' && "animate-spin")} />
-            </button>
-            <a href={previewUrl} target="_blank" rel="noopener noreferrer" className="hover:text-blue-400 transition-colors">
-                <ExternalLink className="w-3 h-3" />
-            </a>
🧹 Nitpick comments (2)
apps/client/src/store/project.ts (1)

46-46: Avoid io(""); prefer explicit same-origin or fail-fast in prod
Passing an empty string can mask misconfiguration and produce environment-dependent socket targets.

-const API_URL = process.env.NEXT_PUBLIC_API_URL || "";
+const API_URL = process.env.NEXT_PUBLIC_API_URL;
-        const socket = io(API_URL);
+        const socket = io(API_URL ?? undefined); // lets socket.io-client pick current origin
+        // or: io(API_URL ?? window.location.origin)
apps/client/src/components/agent/FileSystemPanel.tsx (1)

20-30: Polling: avoid overlapping fetchFiles calls under slow networks
Current interval can trigger concurrent requests; consider a simple in-flight guard.

-        const interval = setInterval(() => {
-            fetchFiles(projectId).catch(console.error);
-        }, 3000);
+        let inFlight = false;
+        const interval = setInterval(async () => {
+            if (inFlight) return;
+            inFlight = true;
+            try { await fetchFiles(projectId); } catch (e) { console.error(e); }
+            finally { inFlight = false; }
+        }, 3000);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 958c970 and e6fb41e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • apps/client/package.json (2 hunks)
  • apps/client/src/app/project/[projectId]/page.tsx (1 hunks)
  • apps/client/src/components/agent/ChatPanel.tsx (1 hunks)
  • apps/client/src/components/agent/FileSystemPanel.tsx (4 hunks)
  • apps/client/src/components/agent/PreviewPanel.tsx (5 hunks)
  • apps/client/src/store/project.ts (2 hunks)
  • apps/server/package.json (3 hunks)
  • apps/server/src/index.ts (2 hunks)
  • apps/server/src/services/workspace.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/client/src/components/agent/PreviewPanel.tsx (1)
apps/client/src/lib/utils.ts (1)
  • cn (4-6)
apps/server/src/index.ts (2)
apps/server/src/models/Project.ts (1)
  • Project (32-32)
apps/server/src/services/workspace.ts (1)
  • WorkspaceService (7-120)
🔇 Additional comments (7)
apps/client/src/components/agent/ChatPanel.tsx (1)

51-60: Proxy endpoint switch looks good; ensure cookies/credentials expectations match
If the old API was cross-origin, verify the proxy forwards auth headers/cookies as intended.

apps/client/src/app/project/[projectId]/page.tsx (1)

24-35: LGTM: proxy-based validation fetch
No concerns with the endpoint migration here.

apps/server/src/index.ts (1)

13-14: Dependency usage: confirm archiver API matches v7 semantics
Just ensure import style and archive.finalize() behavior matches archiver@7.

apps/client/src/components/agent/FileSystemPanel.tsx (1)

92-101: Download link wiring looks good
Proxy-relative href + rel="noopener noreferrer" is the right combo for _blank.

apps/client/package.json (1)

17-23: canvas-confetti v1.9.4 and @types/canvas-confetti v1.9.0 are compatible and secure. No known security advisories exist for either package. Ensure the library is only called from client-side code (Client Components with "use client", inside useEffect, or via dynamic import with ssr: false) to avoid SSR-related errors, since canvas-confetti depends on browser APIs (window/document/canvas).

apps/server/package.json (1)

9-12: archiver@^7.0.1 and @types/archiver@^7.0.0 are compatible with your Node runtime and have no known security advisories.

archiver 7.0.1 requires Node >=14, which is satisfied by your project's Node 18+ requirement. No direct CVEs exist for these npm packages; earlier versions had a transitive inflight vulnerability that 7.0.0+ resolves.

apps/client/src/components/agent/PreviewPanel.tsx (1)

68-74: Nice UX enhancement with confetti celebration.

The confetti animation on preview ready state provides a satisfying user feedback. Configuration looks appropriate.

Comment on lines +162 to +201
// Download Route
app.get("/api/projects/:projectId/download", async (req, res) => {
try {
const { projectId } = req.params;
// Verify project exists
const project = await Project.findById(projectId);
if (!project) {
return res.status(404).json({ error: "Project not found" });
}

const repoPath = WorkspaceService.getRepoPath(projectId);
if (!fs.existsSync(repoPath)) {
return res.status(404).json({ error: "No code generated yet" });
}

res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', `attachment; filename="${project.name.replace(/[^a-z0-9]/gi, '_').toLowerCase() || 'project'}.zip"`);

const archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
});

archive.on('error', function (err) {
console.error("Archiver error:", err);
res.status(500).send({ error: err.message });
});

// pipe archive data to the file
archive.pipe(res);

// append files from a sub-directory, putting its contents at the root of archive
archive.directory(repoPath, false);

archive.finalize();

} catch (error) {
console.error("Download failed:", error);
res.status(500).json({ error: "Download failed" });
}
});
Copy link

@coderabbitai coderabbitai bot Dec 14, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Streaming error handling is unsafe after archive.pipe(res)
After piping, res.status(500).send(...) can throw or partially corrupt the zip. Prefer abort/destroy the stream and log.

-        archive.on('error', function (err) {
-            console.error("Archiver error:", err);
-            res.status(500).send({ error: err.message });
-        });
+        archive.on('error', (err) => {
+            console.error("Archiver error:", err);
+            // response is a stream at this point; don't try to write JSON/status reliably
+            res.destroy(err);
+        });
+
+        req.on('close', () => {
+            if (!res.writableEnded) archive.abort();
+        });

Copy link
Owner Author

Choose a reason for hiding this comment

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

Nice catch.

Copy link

Choose a reason for hiding this comment

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

@M-DEV-1, thanks for the acknowledgment! Let me know if you'd like any clarification on the suggested approach for handling stream errors more safely.

@M-DEV-1 M-DEV-1 changed the title [Feature] Feat: Download Projects, Deploy to Vercel + Confetti Celebrations Dec 14, 2025
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.

1 participant