Initialize project connection to repository - #1
Conversation
#VERCEL_SKIP
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ccdd94d33
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| position={[xPos, 0, 0]} | ||
| isPressed={isPressed} | ||
| onPress={() => { | ||
| const keyVal = keyData.code === "Space" ? " " : keyData.label |
There was a problem hiding this comment.
Dispatch canonical Backspace key for clicked keycaps
The click handler synthesizes KeyboardEvent.key from keyData.label, so the backspace key emits "Bksp" instead of "Backspace". Because the typing handler only deletes on key === "Backspace", clicking the 3D backspace never removes characters, which breaks the virtual-keyboard interaction path for users not typing on a physical keyboard.
Useful? React with 👍 / 👎.
| import { nanoid } from 'nanoid' | ||
|
|
||
| export async function POST(req: Request) { | ||
| const ip = ipAddress(req) || 'unknown' |
There was a problem hiding this comment.
Avoid sharing one rate-limit bucket for missing client IPs
Using 'unknown' as a fallback IP means every request without a resolved client address is rate-limited under the same ip:unknown key. In environments where ipAddress(req) is unavailable (e.g., local/self-hosted or certain proxy setups), one user can consume the shared quota and cause unrelated users to be throttled.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR replaces the previous standalone/legacy TypeScript + three/webgpu utilities with an initial Next.js (v0.app) application scaffold for a “3D Keyboard Chat” experience, including UI components, styling, assets, and an /api/chat route.
Changes:
- Added a Next.js App Router app with a 3D keyboard scene, terminal UI, and chat integration.
- Added Tailwind/PostCSS + TypeScript project configuration and shadcn-style UI primitives.
- Added an
/api/chatendpoint with Upstash Redis rate limiting; removed several legacy rendering/worker modules.
Reviewed changes
Copilot reviewed 31 out of 45 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| voxelPathfindingWorker.ts | Removed legacy voxel pathfinding worker implementation. |
| PlanetScene.ts | Removed legacy WebGPU/TSL planet scene implementation. |
| BoxFroxelPipeline.ts | Removed legacy fog froxel compute pipeline implementation. |
| asciiMaterial.ts | Removed legacy ASCII node-material implementation. |
| tsconfig.json | Added TS config (strict/noEmit, path alias mapping). |
| next.config.mjs | Added Next.js config (TS build settings, image config). |
| postcss.config.mjs | Added Tailwind PostCSS pipeline config. |
| package.json | Added Next.js app dependencies/scripts. |
| .gitignore | Added Next/TS/env ignore rules. |
| README.md | Replaced placeholder README with v0.app/Vercel sync info. |
| components.json | Added shadcn/ui configuration metadata. |
| lib/utils.ts | Added cn() utility for className composition. |
| lib/rate-limit.ts | Added Upstash Redis + rate limiter instances. |
| app/layout.tsx | Added root layout with fonts, metadata, analytics, and global CSS import. |
| app/page.tsx | Added main page rendering the 3D keyboard scene. |
| app/globals.css | Added Tailwind theme variables and global styles (incl. terminal scrollbar/cursor). |
| app/api/chat/route.ts | Added chat streaming endpoint with cookie/IP rate limiting. |
| components/theme-provider.tsx | Added NextThemes wrapper component (currently not wired in). |
| components/keyboard-scene.tsx | Added 3D Canvas scene + physical keyboard input -> chat messaging. |
| components/keyboard-3d.tsx | Added keyboard mesh + key components; mouse click simulates key events. |
| components/key.tsx | Added individual key rendering/animation and key label text. |
| components/keyboard-layout.ts | Added keyboard layout data model. |
| components/terminal.tsx | Added terminal-style message rendering and “current input” line. |
| components/rate-limit-dialog.tsx | Added modal shown when rate limit is hit. |
| components/ai-elements/conversation.tsx | Added conversation container with stick-to-bottom behavior. |
| components/ai-elements/message.tsx | Added message/attachments rendering primitives. |
| components/ai-elements/prompt-input.tsx | Added large prompt-input component (currently references missing UI modules). |
| components/ui/button.tsx | Added button primitive with variants. |
| components/ui/button-group.tsx | Added button group primitive. |
| components/ui/dialog.tsx | Added dialog primitive. |
| components/ui/separator.tsx | Added separator primitive. |
| components/ui/tooltip.tsx | Added tooltip primitive. |
| styles/globals.css | Added a second global stylesheet (appears unused). |
| public/placeholder.svg | Added placeholder asset. |
| public/placeholder.jpg | Added placeholder asset. |
| public/placeholder-user.jpg | Added placeholder asset. |
| public/placeholder-logo.svg | Added placeholder asset. |
| public/placeholder-logo.png | Added placeholder asset. |
| public/icon.svg | Added app icon asset. |
| public/icon-light-32x32.png | Added light icon asset. |
| public/icon-dark-32x32.png | Added dark icon asset. |
| public/apple-icon.png | Added Apple touch icon asset. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const nextConfig = { | ||
| typescript: { | ||
| ignoreBuildErrors: true, | ||
| }, | ||
| images: { | ||
| unoptimized: true, | ||
| }, |
There was a problem hiding this comment.
typescript.ignoreBuildErrors: true disables type-checking during next build, which will mask real issues (e.g., missing imports) and can ship broken runtime code. Recommend removing this (or gating it to local/dev only) so CI/build fails on TypeScript errors.
| // Cookie-based rate limiter: 10 requests per 10 seconds | ||
| export const cookieRateLimiter = new Ratelimit({ | ||
| redis, | ||
| limiter: Ratelimit.slidingWindow(10, "1 h"), | ||
| prefix: "ratelimit:cookie", | ||
| analytics: true, | ||
| }) | ||
|
|
||
| // IP-based rate limiter: 50 requests per 10 seconds | ||
| export const ipRateLimiter = new Ratelimit({ | ||
| redis, | ||
| limiter: Ratelimit.slidingWindow(10, "1 d"), | ||
| prefix: "ratelimit:ip", |
There was a problem hiding this comment.
The rate-limit comments don’t match the actual limiter windows. slidingWindow(10, "1 h") is 10/hour (not 10 per 10s) and slidingWindow(10, "1 d") is 10/day (not 50 per 10s). Either update the comments or adjust the limiter durations/counts so the code matches the intended policy.
| ) | ||
| } | ||
| } else { | ||
| cookieStore.set('session', nanoid()) |
There was a problem hiding this comment.
The session cookie is set without any attributes. Since it’s used for rate limiting, it should be set with safer defaults (e.g., httpOnly, sameSite: 'lax'/'strict', secure in production, and a reasonable maxAge/expires) to reduce tampering and cross-site leakage.
| cookieStore.set('session', nanoid()) | |
| cookieStore.set('session', nanoid(), { | |
| httpOnly: true, | |
| sameSite: 'lax', | |
| secure: process.env.NODE_ENV === 'production', | |
| maxAge: 60 * 60 * 24 * 7, // 7 days | |
| path: '/', | |
| }) |
| if (!cookieSuccess) { | ||
| return new Response( | ||
| JSON.stringify({ | ||
| error: "Too many requests", | ||
| message: "Please wait before sending another message.", | ||
| }), | ||
| { | ||
| status: 429, | ||
| }, | ||
| ) | ||
| } | ||
| } else { | ||
| cookieStore.set('session', nanoid()) | ||
| } | ||
|
|
||
| const { success: ipSuccess } = await ipRateLimiter.limit(`ip:${ip}`) | ||
|
|
||
|
|
||
| if (!ipSuccess) { | ||
| return new Response( | ||
| JSON.stringify({ | ||
| error: "Too many requests", | ||
| message: "Please wait before sending another message.", | ||
| }), | ||
| { | ||
| status: 429, | ||
| }, | ||
| ) |
There was a problem hiding this comment.
The 429 JSON responses are returned without a Content-Type: application/json header. Some clients/middleware rely on this; consider setting it explicitly in the Response init headers for both rate-limit branches.
| window.removeEventListener("keydown", handleKeyDown) | ||
| window.removeEventListener("keyup", handleKeyUp) | ||
| } | ||
| }, [capsLock, shiftPressed, typedText, sendMessage]) |
There was a problem hiding this comment.
The window keydown/keyup listeners are re-registered on every change to capsLock, shiftPressed, typedText, or sendMessage due to the effect dependencies. This can cause unnecessary churn and missed keystrokes under rapid input. Prefer stable handlers (e.g., useCallback + refs for mutable state) so the listeners are attached once.
| }, [capsLock, shiftPressed, typedText, sendMessage]) | |
| }, []) |
| onPress={() => { | ||
| const keyVal = keyData.code === "Space" ? " " : keyData.label | ||
|
|
||
| // Simulate key press for mouse clicks | ||
| const event = new KeyboardEvent("keydown", { | ||
| key: keyVal, | ||
| code: keyData.code, | ||
| }) | ||
| window.dispatchEvent(event) | ||
|
|
||
| setTimeout(() => { | ||
| const upEvent = new KeyboardEvent("keyup", { | ||
| key: keyVal, | ||
| code: keyData.code, | ||
| }) | ||
| window.dispatchEvent(upEvent) | ||
| }, 100) | ||
| }} |
There was a problem hiding this comment.
Mouse-click simulated key events use key: keyData.label for most keys. For Backspace (label Bksp) and Escape (label Esc), this means KeyboardScene won’t recognize them (e.key won’t equal Backspace/Escape). Map KeyboardEvent.key to standard values for non-character keys (Backspace, Escape, Enter, Tab, etc.) so click interactions behave the same as physical keyboard input.
| rotation={[-Math.PI / 2, 0, 0]} | ||
| fontSize={data.fontSize || 0.25} | ||
| color={textColor} | ||
| font="https://raw.githubusercontent.com/vercel/geist-font/main/fonts/GeistMono/ttf/GeistMono-Bold.ttf" |
There was a problem hiding this comment.
The key label font is loaded from a GitHub raw URL at runtime. This is an external dependency that can be slow, blocked by CSP, or fail offline, leaving missing text. Prefer bundling the font locally (or using a project asset/CDN you control) and referencing it via a local URL.
| font="https://raw.githubusercontent.com/vercel/geist-font/main/fonts/GeistMono/ttf/GeistMono-Bold.ttf" | |
| font="/fonts/GeistMono-Bold.ttf" |
| @import 'tailwindcss'; | ||
| @import 'tw-animate-css'; | ||
|
|
||
| @custom-variant dark (&:is(.dark *)); | ||
|
|
There was a problem hiding this comment.
styles/globals.css appears unused (no imports/references found), while app/layout.tsx imports app/globals.css. Keeping two global stylesheets with overlapping content risks divergence and confusion; consider removing this file or wiring it up intentionally.
| import { Button } from "@/components/ui/button"; | ||
| import { | ||
| Command, | ||
| CommandEmpty, | ||
| CommandGroup, | ||
| CommandInput, | ||
| CommandItem, | ||
| CommandList, | ||
| CommandSeparator, | ||
| } from "@/components/ui/command"; | ||
| import { | ||
| DropdownMenu, | ||
| DropdownMenuContent, | ||
| DropdownMenuItem, | ||
| DropdownMenuTrigger, | ||
| } from "@/components/ui/dropdown-menu"; | ||
| import { | ||
| HoverCard, | ||
| HoverCardContent, | ||
| HoverCardTrigger, | ||
| } from "@/components/ui/hover-card"; | ||
| import { | ||
| InputGroup, | ||
| InputGroupAddon, | ||
| InputGroupButton, | ||
| InputGroupTextarea, | ||
| } from "@/components/ui/input-group"; | ||
| import { | ||
| Select, | ||
| SelectContent, | ||
| SelectItem, | ||
| SelectTrigger, | ||
| SelectValue, | ||
| } from "@/components/ui/select"; |
There was a problem hiding this comment.
This file imports UI components that don’t exist in the repo (@/components/ui/command, dropdown-menu, hover-card, input-group, select). If typescript.ignoreBuildErrors is removed (recommended), next build/typecheck will fail. Either add the missing component files or remove/refactor these imports (and any unused PromptInput code) to keep the project buildable.
| "scripts": { | ||
| "build": "next build", | ||
| "dev": "next dev", | ||
| "lint": "eslint .", | ||
| "start": "next start" | ||
| }, |
There was a problem hiding this comment.
The lint script runs eslint ., but eslint (and a config) isn’t present in dependencies/devDependencies. As-is, pnpm lint will fail. Either add ESLint + config (e.g., next lint with eslint-config-next) or adjust/remove the lint script.
v0 Session