From 3ced2979a9588a547a10772cfff19f60ae16352d Mon Sep 17 00:00:00 2001 From: rah7202 Date: Sat, 4 Apr 2026 18:43:59 +0530 Subject: [PATCH 1/5] fix: removed old readme file --- frontend/README.md | 73 ---------------------------------------------- 1 file changed, 73 deletions(-) delete mode 100644 frontend/README.md diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 7dbf7eb..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` From 5b82dcecd36f66aa6f0638538691fd24a75352ae Mon Sep 17 00:00:00 2001 From: rah7202 Date: Sat, 4 Apr 2026 19:16:52 +0530 Subject: [PATCH 2/5] fix: added a ROOT README file --- README.md => ROOT_README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.md => ROOT_README.md (100%) diff --git a/README.md b/ROOT_README.md similarity index 100% rename from README.md rename to ROOT_README.md From 50c7555eb7b225217e7820c2dcb553e3c070ecf8 Mon Sep 17 00:00:00 2001 From: rah7202 Date: Sat, 4 Apr 2026 19:19:00 +0530 Subject: [PATCH 3/5] fix: added a ROOT README file --- ROOT_README.md | 439 ++++++++++++++++++++++++------------------------- 1 file changed, 217 insertions(+), 222 deletions(-) diff --git a/ROOT_README.md b/ROOT_README.md index 891ae1f..38e5b1a 100644 --- a/ROOT_README.md +++ b/ROOT_README.md @@ -1,295 +1,290 @@ -# Smart Code Lab πŸ§ͺ +# Smart Code Lab -A real-time collaborative code editor with AI-powered assistance, multi-language support, and version β€” built for teams who want to write, run, and review code together. +> _Code together. Think faster._ -![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white) -![React](https://img.shields.io/badge/React-20232A?style=flat&logo=react&logoColor=61DAFB) -![Node.js](https://img.shields.io/badge/Node.js-339933?style=flat&logo=node.js&logoColor=white) -![Prisma](https://img.shields.io/badge/Prisma-2D3748?style=flat&logo=prisma&logoColor=white) -![Socket.io](https://img.shields.io/badge/Socket.io-010101?style=flat&logo=socket.io&logoColor=white) +A real-time collaborative code editor with AI-powered assistance. Multiple users can simultaneously write, edit, and debug code in the same room β€” with live cursor tracking, instant code synchronization, and an AI assistant powered by Google Gemini that streams responses in real-time. + +[![CI Pipeline](https://github.com/rah7202/smart-code-lab/actions/workflows/ci.yml/badge.svg)](https://github.com/rah7202/smart-code-lab/actions) + +--- + +## Features + +| Feature | Description | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------- | +| πŸ–₯️ **Monaco Editor** | VS Code-quality editor with syntax highlighting, IntelliSense, and custom themes per language | +| πŸ‘₯ **Real-Time Collaboration** | Multiple users edit simultaneously with live cursor positions and colored name badges | +| πŸ€– **AI Assistant (Gemini)** | Ask Gemini to explain, review, fix, or optimize your code β€” responses stream in real-time via SSE | +| βœ‚οΈ **Selection Toolbar** | Select any code snippet β†’ inline floating toolbar with Explain, Review, Fix, Optimize, and custom Ask actions | +| ▢️ **Code Execution** | Run code directly in the browser (Python, JavaScript, C++, C) via Judge0 API | +| πŸ“Έ **Version History** | Automatic snapshots on every run + manual save with one-click restore | +| πŸ” **JWT Authentication** | Secure signup/signin with bcrypt password hashing and JWT tokens | +| ⚑ **Redis-Backed State** | Room state, user presence, and code content stored in Redis for horizontal scaling | +| πŸ–±οΈ **Resizable AI Panel** | Drag to resize the AI output section for a customized workspace | +| πŸ“₯ **Code Download** | Download your code as a properly named file (e.g., `Rahul-2026-04-04.py`) | +| 🎨 **Custom Themes** | Each language has its own color-tuned Monaco theme (blue for Python, amber for JS, purple for C++, teal for C) | --- -## ✨ Features +## Supported Languages -- **Real-time collaboration** β€” Live cursors, user presence, and instant code sync across all users in a room -- **Multi-language support** β€” Python, JavaScript, C++, C with per-language syntax themes and starter templates -- **AI assistance (Gemini Flash 2.5)** β€” Full code analysis, freeform Q&A, and selection-aware inline toolbar -- **Code execution** β€” Run code via Judge0 with stdin support and live output -- **Version history** β€” Auto-saved snapshots on every run and Ctrl+S, with one-click restore -- **Persistent rooms** β€” Code is saved to the database and reloaded on re-join -- **Download code** β€” Auto-named file with username + timestamp -- **Rate limiting** β€” Client + server side (5 req/min per user) +| Language | Badge | Judge0 ID | Monaco Theme | +| ---------- | ----- | --------- | ------------ | +| JavaScript | `JS` | 63 | Warm amber | +| Python | `PY` | 71 | Deep blue | +| C++ | `C++` | 54 | Purple | +| C | `C` | 50 | Teal | --- -## πŸ—οΈ Architecture - -```mermaid -graph TB - subgraph Client ["Frontend (React + TypeScript + Vite)"] - direction TB - EP["EditorPage"] - NB["Navbar"] - UP["UserPresenceBar"] - ME["Monaco Editor"] - ST["SelectionToolbar"] - - subgraph RightSide ["Right Panel"] - CI["CodeInputPanel"] - AI["AIPanel"] - VP["VersionPanel"] - end - - subgraph Hooks ["Custom Hooks"] - UC["useCollaboration"] - UAI["useAI"] - UEP["useEditorPersistence"] - end - - EP --> NB - EP --> UP - EP --> ME - EP --> ST - EP --> RightSide - EP --> Hooks - end - - subgraph Server ["Backend (Node.js + Express + TypeScript)"] - direction TB - APP["app.ts"] - - subgraph Routes ["Routes"] - AR["ai.route.ts"] - CR["compile.route.ts"] - RR["room.route.ts"] - SR["codeSnapshot.routes.ts"] - end - - subgraph Controllers ["Controllers"] - AC["ai.controller.ts"] - CC["compile.controller.ts"] - RC["room.controller.ts"] - SC["codeSnapshot.controller.ts"] - end - - subgraph Services ["Services"] - AS["ai.service.ts"] - J0["judge0.service.ts"] - RS["room.service.ts"] - CSS["codeSnapshot.service.ts"] - end - - SOC["socket.ts (Socket.IO)"] - DB["prisma.ts (Prisma ORM)"] - - APP --> Routes - APP --> SOC - Routes --> Controllers - Controllers --> Services - Services --> DB - end - - subgraph External ["External Services"] - GEM["Gemini Flash 2.5 API"] - J0EXT["Judge0 API"] - PG[("PostgreSQL / SQLite")] - end - - ME -- "onChange / onMount" --> UC - UC -- "socket events" --> SOC - SOC -- "cursor-move\ncontent-edited\nusers\ncode-sync" --> UC - - UAI -- "POST /ai/generate" --> AC - AC --> AS - AS --> GEM - - CI -- "POST /compile" --> CC - CC --> J0 - J0 --> J0EXT - - UEP -- "GET /room/:id\nPOST /room/:id/save\nPOST /snapshot/:id" --> RC - RC --> RS - RS --> DB - DB --> PG - - VP -- "GET /snapshots/:id" --> SC - SC --> CSS - CSS --> DB +## Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Frontend (React) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Monaco β”‚ β”‚ AI β”‚ β”‚Version β”‚ β”‚ +β”‚ β”‚ Editor β”‚ β”‚Panel β”‚ β”‚History β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Hooks Layer β”‚ β”‚ +β”‚ β”‚ useCollaboration β”‚ β”‚ +β”‚ β”‚ useAI (SSE streaming) β”‚ β”‚ +β”‚ β”‚ useEditorPersistence β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ WebSocketβ”‚ REST/SSE β”‚ REST + β”‚ β”‚ β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β” +β”‚ β”‚ Backend (Express) β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”΄β”€β”€β”€β” β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β” β”‚ +β”‚ β”‚Socket.IOβ”‚ β”‚ AI β”‚ β”‚ Room β”‚ β”‚ +β”‚ β”‚ Server β”‚ β”‚Serviceβ”‚ β”‚Service β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Data Layer β”‚ β”‚ +β”‚ β”‚ PostgreSQL Redis β”‚ β”‚ +β”‚ β”‚ (Prisma ORM) (state/adapter)β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- -## πŸ“ Project Structure +## Project Structure ``` -Smart Code Lab/ -β”œβ”€β”€ backend/ -β”‚ β”œβ”€β”€ prisma/ -β”‚ β”‚ β”œβ”€β”€ migrations/ -β”‚ β”‚ └── schema.prisma -β”‚ └── src/ -β”‚ β”œβ”€β”€ controllers/ -β”‚ β”‚ β”œβ”€β”€ ai.controller.ts -β”‚ β”‚ β”œβ”€β”€ codeSnapshot.controller.ts -β”‚ β”‚ β”œβ”€β”€ compile.controller.ts -β”‚ β”‚ └── room.controller.ts -β”‚ β”œβ”€β”€ db/ -β”‚ β”‚ └── prisma.ts -β”‚ β”œβ”€β”€ routes/ -β”‚ β”‚ β”œβ”€β”€ ai.route.ts -β”‚ β”‚ β”œβ”€β”€ codeSnapshot.routes.ts -β”‚ β”‚ β”œβ”€β”€ compile.route.ts -β”‚ β”‚ └── room.route.ts -β”‚ β”œβ”€β”€ services/ -β”‚ β”‚ β”œβ”€β”€ ai.service.ts -β”‚ β”‚ β”œβ”€β”€ codeSnapshot.service.ts -β”‚ β”‚ β”œβ”€β”€ judge0.service.ts -β”‚ β”‚ └── room.service.ts -β”‚ β”œβ”€β”€ sockets/ -β”‚ β”‚ └── socket.ts -β”‚ β”œβ”€β”€ types/ -β”‚ β”‚ └── index.ts -β”‚ β”œβ”€β”€ app.ts -β”‚ └── index.ts -β”‚ -└── frontend/ - └── src/ - β”œβ”€β”€ assets/ - β”œβ”€β”€ components/ - β”‚ β”œβ”€β”€ AIPanel.tsx - β”‚ β”œβ”€β”€ CodeInputPanel.tsx - β”‚ β”œβ”€β”€ EditorPage.tsx - β”‚ β”œβ”€β”€ Footer.tsx - β”‚ β”œβ”€β”€ Home.tsx - β”‚ β”œβ”€β”€ LanguageBadge.tsx - β”‚ β”œβ”€β”€ Navbar.tsx - β”‚ β”œβ”€β”€ RightPanel.tsx - β”‚ β”œβ”€β”€ SelectionToolbar.tsx - β”‚ β”œβ”€β”€ UserPresenceBar.tsx - β”‚ β”œβ”€β”€ VersionHistory.tsx - β”‚ └── VersionPanel.tsx - β”œβ”€β”€ hooks/ - β”‚ β”œβ”€β”€ useAI.ts - β”‚ β”œβ”€β”€ useCollaboration.ts - β”‚ └── useEditorPersistence.ts - β”œβ”€β”€ languageOptions.ts - β”œβ”€β”€ socket.ts - └── App.tsx +smart-code-lab/ +β”œβ”€β”€ .github/ +β”‚ └── workflows/ +β”‚ └── ci.yml # CI pipeline (GitHub Actions) +β”œβ”€β”€ backend/ # Express API + Socket.IO server +β”œβ”€β”€ frontend/ # React + Vite SPA +β”œβ”€β”€ .gitignore +└── README.md ``` --- -## πŸš€ Getting Started +## Tech Stack + +### Frontend + +| Technology | Version | Purpose | +| ---------------- | ------- | --------------------------------- | +| React | 19.2 | UI framework | +| Vite | 8.0 | Build tool & dev server | +| TypeScript | 5.9 | Type safety | +| TailwindCSS | 4.2 | Utility-first styling | +| Monaco Editor | 4.7 | Code editor (VS Code engine) | +| Socket.IO Client | 4.8 | Real-time WebSocket communication | +| Axios | 1.13 | HTTP client with interceptors | +| React Router DOM | 7.13 | Client-side routing | +| React Markdown | 10.1 | AI response rendering | +| Vitest | 4.1 | Testing framework | +| Testing Library | 16.3 | Component testing | + +### Backend + +| Technology | Version | Purpose | +| ------------------------ | ------- | --------------------------------- | +| Express | 5.2 | Web framework | +| TypeScript | 5.9 | Type safety | +| Prisma | 5.22 | ORM for PostgreSQL | +| PostgreSQL | β€” | Primary database | +| Redis | 5.11 | Session state + Socket.IO adapter | +| Socket.IO | 4.8 | Real-time WebSocket server | +| @socket.io/redis-adapter | 8.3 | Multi-server pub/sub | +| Google Generative AI | 0.24 | Gemini AI integration | +| JSON Web Token | 9.0 | Authentication | +| Bcrypt | 6.0 | Password hashing | +| Zod | 4.3 | Request validation | +| Helmet | 8.1 | Security headers | +| Express Rate Limit | 8.3 | Rate limiting | +| Jest | 30.3 | Testing framework | +| Supertest | 7.2 | HTTP assertion library | + +--- + +## Getting Started ### Prerequisites -- Node.js β‰₯ 18 -- npm or pnpm -- A running PostgreSQL instance (or update `schema.prisma` for SQLite) -- Judge0 API key (self-hosted or RapidAPI) -- Google Gemini API key +- **Node.js** β‰₯ 20 +- **npm** β‰₯ 9 +- **PostgreSQL** (local or hosted, e.g., Neon) +- **Redis** (local or hosted, e.g., Render Redis) +- **Gemini API Key** β€” [Get one here](https://aistudio.google.com/apikey) -### 1. Clone the repo +### 1. Clone the Repository ```bash git clone https://github.com/rah7202/smart-code-lab.git cd smart-code-lab ``` -### 2. Backend setup +### 2. Backend Setup ```bash cd backend npm install ``` -Create a `.env` file: +Create `backend/.env.local`: ```env DATABASE_URL="postgresql://user:password@localhost:5432/smartcodelab" -GEMINI_API_KEY="your_gemini_api_key" -JUDGE0_API_KEY="your_judge0_api_key" -JUDGE0_BASE_URL="https://judge0-ce.p.rapidapi.com" -PORT=8000 +GEMINI_API_KEY=your_gemini_api_key +JWT_SECRET=your_jwt_secret_here_minimum_64_chars +ALLOWED_ORIGINS=http://localhost:5173 +REDIS_URL=redis://localhost:6379 +LOG_LEVEL=debug ``` -Run migrations and start: +Generate Prisma client and run migrations: ```bash +npx prisma generate npx prisma migrate dev +``` + +Start the backend dev server: + +```bash npm run dev +# β†’ Server starts on http://localhost:8000 ``` -### 3. Frontend setup +### 3. Frontend Setup ```bash -cd frontend +cd ../frontend npm install +``` + +Create `frontend/.env.local`: + +```env +VITE_BACKEND_URL=http://localhost:8000 +``` + +Start the frontend dev server: + +```bash npm run dev +# β†’ App opens on http://localhost:5173 ``` -App runs at `http://localhost:5173` +### 4. Open the App + +1. Navigate to `http://localhost:5173` +2. Sign up for an account +3. Create a room or enter an existing Room ID +4. Start coding! --- -## πŸ”Œ API Reference +## Environment Variables -| Method | Endpoint | Description | -|--------|----------|-------------| -| `POST` | `/compile` | Execute code via Judge0 | -| `POST` | `/ai/generate` | Ask Gemini about code | -| `DELETE` | `/ai/history/:roomId` | Clear AI chat history | -| `GET` | `/room/:roomId` | Load room code + language | -| `POST` | `/room/:roomId/save` | Save current code to room | -| `POST` | `/snapshot/:roomId` | Save a named snapshot | -| `GET` | `/snapshots/:roomId` | Get all snapshots for a room | +### Backend (`backend/.env.local`) -### Socket Events +| Variable | Required | Description | +| ----------------- | -------- | -------------------------------------------- | +| `DATABASE_URL` | βœ… | PostgreSQL connection string | +| `GEMINI_API_KEY` | βœ… | Google Gemini API key | +| `JWT_SECRET` | βœ… | Secret for signing JWT tokens (min 64 chars) | +| `ALLOWED_ORIGINS` | βœ… | Comma-separated allowed CORS origins | +| `REDIS_URL` | βœ… | Redis connection string | +| `LOG_LEVEL` | ❌ | Log level (`debug`, `info`, `warn`, `error`) | +| `NODE_ENV` | ❌ | Environment mode | -| Event | Direction | Payload | Description | -|-------|-----------|---------|-------------| -| `join` | Client β†’ Server | `{ RoomId, username }` | Join a room | -| `content-edited` | Client ↔ Server | `{ code, language }` | Broadcast code change | -| `cursor-move` | Client ↔ Server | `{ line, column, username, color, socketId }` | Broadcast cursor position | -| `code-sync` | Server β†’ Client | `string` | Send existing code to new joiner | -| `users` | Server β†’ Client | `User[]` | Updated user list with colors | +### Frontend (`frontend/.env.local`) + +| Variable | Required | Description | +| ------------------ | -------- | ------------------ | +| `VITE_BACKEND_URL` | βœ… | Backend server URL | --- -## πŸ› οΈ Tech Stack - -| Layer | Technology | -|-------|-----------| -| Frontend framework | React 18 + TypeScript + Vite | -| Editor | Monaco Editor (`@monaco-editor/react`) | -| Styling | Tailwind CSS v4 | -| Real-time | Socket.IO | -| Backend | Node.js + Express + TypeScript | -| ORM | Prisma | -| Database | PostgreSQL | -| Code execution | Judge0 API | -| AI | Google Gemini Flash 2.5 | -| State management | React hooks (no external lib) | +## Scripts Reference + +### Backend + +| Script | Description | +| --------------- | -------------------------------- | +| `npm run dev` | Start dev server with hot reload | +| `npm run build` | Compile TypeScript to `dist/` | +| `npm start` | Start production server | +| `npm test` | Run tests with coverage report | + +### Frontend + +| Script | Description | +| ----------------------- | --------------------------------- | +| `npm run dev` | Start Vite dev server | +| `npm run build` | Type-check + build for production | +| `npm test` | Run tests once | +| `npm run test:coverage` | Run tests with coverage report | +| `npm run lint` | Lint all files | --- -## ⌨️ Keyboard Shortcuts +## CI/CD Pipeline -| Shortcut | Action | -|----------|--------| -| `Ctrl + S` | Save snapshot | -| `Enter` (in AI input) | Send question to Gemini | -| `Shift + Enter` | New line in AI input | +GitHub Actions runs on every push/PR to `main`. Both jobs enforce **70% coverage thresholds** for lines and functions. + +``` +backend job: + 1. Checkout β†’ Setup Node 20 β†’ npm ci (cached) + 2. npx prisma generate + 3. npm test -- --coverage --ci + 4. npm run build + +frontend job (runs after backend passes): + 1. Checkout β†’ Setup Node 20 β†’ npm ci (cached) + 2. npm run test:coverage + 3. npm run build +``` --- -## 🀝 Contributing +## Future Roadmap -Pull requests are welcome. For major changes, please open an issue first. +| Feature | Priority | Notes | +| --------------------------- | ------------- | ---------------------------------------------------------------------------- | +| **Yjs / CRDT Integration** | πŸ”΅ Next Major | Replace keystroke broadcasting with binary delta sync for better concurrency | +| **Docker Compose** | 🟑 Medium | Redis + PostgreSQL + App in one-command setup | +| **More Languages** | 🟑 Medium | Java, Go, Rust, etc. | +| **Room Sharing Link** | 🟒 Easy | Copy-to-clipboard shareable URL | +| **AI Context Awareness** | πŸ”΅ Future | Pass execution output to Gemini for smarter debugging | +| **JWT Blacklist on Logout** | 🟒 Easy | `redis.set(blacklist:${token})` with TTL | --- -## πŸ“„ License +## Author + +**Rahul** β€” [github.com/rah7202](https://github.com/rah7202) -MIT Β© [rah7202](https://github.com/rah7202) +**Repository** β€” [github.com/rah7202/smart-code-lab](https://github.com/rah7202/smart-code-lab) From e4f82873007b57189f51a413eb0582e0441ddb58 Mon Sep 17 00:00:00 2001 From: rah7202 Date: Sat, 4 Apr 2026 19:33:40 +0530 Subject: [PATCH 4/5] fix: Added a FRONTEND README file --- frontend/FRONTEND_README.md | 356 ++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 frontend/FRONTEND_README.md diff --git a/frontend/FRONTEND_README.md b/frontend/FRONTEND_README.md new file mode 100644 index 0000000..23d32c8 --- /dev/null +++ b/frontend/FRONTEND_README.md @@ -0,0 +1,356 @@ +# Smart Code Lab β€” Frontend + +React + Vite + TypeScript SPA. Monaco editor with real-time collaboration via Socket.IO, AI-powered code assistance with SSE streaming, and full version history. + +--- + +## File Structure + +``` +frontend/ +β”œβ”€β”€ public/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ App.tsx # Routes: /, /login, /signup, /editor/:roomId +β”‚ β”œβ”€β”€ App.css # Global styles +β”‚ β”œβ”€β”€ main.tsx # React entry point +β”‚ β”œβ”€β”€ socket.ts # Socket.IO client (autoConnect: false) +β”‚ β”œβ”€β”€ languageOptions.ts # 4 languages + custom Monaco themes +β”‚ β”œβ”€β”€ components/ +β”‚ β”‚ β”œβ”€β”€ EditorPage.tsx # Main editor page β€” Monaco + all hooks +β”‚ β”‚ β”œβ”€β”€ AIPanel.tsx # AI chat with markdown + SSE rendering + resize +β”‚ β”‚ β”œβ”€β”€ CodeInputPanel.tsx # stdin input + Run + Ask Gemini buttons +β”‚ β”‚ β”œβ”€β”€ RightPanel.tsx # Composes CodeInputPanel + AIPanel + VersionPanel +β”‚ β”‚ β”œβ”€β”€ SelectionToolbar.tsx # Floating toolbar on text selection +β”‚ β”‚ β”œβ”€β”€ Navbar.tsx # Language/theme selectors, save/download/clear +β”‚ β”‚ β”œβ”€β”€ UserPresenceBar.tsx # Colored user badges for room members +β”‚ β”‚ β”œβ”€β”€ VersionPanel.tsx # Toggle for version history +β”‚ β”‚ β”œβ”€β”€ VersionHistory.tsx # Snapshot list with restore +β”‚ β”‚ β”œβ”€β”€ LanguageBadge.tsx # Language indicator badge (PY/JS/C++/C) +β”‚ β”‚ β”œβ”€β”€ Home.tsx # Room create/join + logout +β”‚ β”‚ β”œβ”€β”€ LoginPage.tsx # Sign in form +β”‚ β”‚ β”œβ”€β”€ Signup.tsx # Sign up form +β”‚ β”‚ └── Footer.tsx # GitHub link +β”‚ β”œβ”€β”€ hooks/ +β”‚ β”‚ β”œβ”€β”€ useAI.ts # AI SSE streaming + rate limiting + 3 modes +β”‚ β”‚ β”œβ”€β”€ useCollaboration.ts # Socket.IO collab + cursor decorations +β”‚ β”‚ └── useEditorPersistence.ts # DB sync, Ctrl+S, beforeunload snapshots +β”‚ β”œβ”€β”€ lib/ +β”‚ β”‚ └── authAxios.ts # Axios instance with JWT interceptor + 401 redirect +β”‚ β”œβ”€β”€ utils/ +β”‚ β”‚ β”œβ”€β”€ auth.ts # Token validation + decode helpers +β”‚ β”‚ └── ProtectedRoute.tsx # Route guard component +β”‚ β”œβ”€β”€ assets/ +β”‚ β”‚ └── geminiLogo.png +β”‚ └── test/ +β”‚ β”œβ”€β”€ setup.ts # jsdom + @testing-library/jest-dom +β”‚ β”œβ”€β”€ socket.test.ts +β”‚ β”œβ”€β”€ languageOptions.test.ts +β”‚ β”œβ”€β”€ __mocks__/ +β”‚ β”‚ └── socket.ts # Socket.IO mock +β”‚ β”œβ”€β”€ components/ # 12 component test files +β”‚ └── hooks/ # 3 hook test files +β”œβ”€β”€ .env.local +β”œβ”€β”€ vite.config.ts # Vite + Vitest config +β”œβ”€β”€ package.json +└── tsconfig.json +``` + +--- + +## Architecture + +### EditorPage β€” The Orchestration Hub + +`EditorPage.tsx` is the main page component. It owns Monaco, refs, and imperative helpers β€” but delegates all business logic to three custom hooks: + +``` +EditorPage +β”‚ +β”œβ”€β”€ useEditorPersistence DB load/save, Ctrl+S, auto-save, beforeunload, download, restore +β”œβ”€β”€ useCollaboration Socket connect, code-sync, cursor decorations, emit helpers +└── useAI SSE streaming, rate limiting, chat history, 3 AI modes +``` + +### Monaco β€” Uncontrolled Pattern + +Monaco is rendered **without a `value` prop** (uncontrolled). This prevents React from fighting Monaco over content during collaboration. + +All updates go through two imperative helpers defined in `EditorPage`: + +```typescript +applyCode(code: string) +// Sets editor content via editor.setValue() +// Sets isRemoteUpdate = true first to block onChange re-emit + +applyLang(lang: string) +// Sets Monaco model language + theme without re-mounting +// Also updates React state (userLang, userLangId) as a mirror +``` + +The `onChange` handler only fires for **real user keystrokes** β€” all programmatic updates are gated by the `isRemoteUpdate` ref. + +### Data Flow + +``` +User types in Monaco + β”‚ + β–Ό +onChange fires (isRemoteUpdate = false) + β”‚ + β”œβ”€β”€ persistence.handleEditorChange(code, lang) β†’ debounced DB save + └── emitCodeChange({ code, language }) β†’ Socket.IO broadcast + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό + Other users receive "content-edited" + β”‚ + β–Ό + onCodeChange(code) callback + β”‚ + β–Ό + applyCode(code) β†’ editor.setValue() with isRemoteUpdate = true + persistence.setUserCode(code) β†’ keeps AI / DB saves in sync +``` + +### Auth Flow + +```typescript +// authAxios.ts β€” shared axios instance +const api = axios.create({ baseURL: import.meta.env.VITE_BACKEND_URL }); + +api.interceptors.request.use((config) => { + config.headers.Authorization = `Bearer ${localStorage.getItem("token")}`; + return config; +}); + +api.interceptors.response.use(null, (error) => { + if (error.response?.status === 401) { + localStorage.removeItem("token"); + window.location.href = "/login"; + } + return Promise.reject(error); +}); +``` + +Login/Signup pages use plain `axios` (not `api`) to avoid the 401 interceptor triggering a redirect loop during authentication failures. + +--- + +## Component Breakdown + +| Component | Responsibility | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **EditorPage** | Wires Monaco, all three hooks, and child components. Owns `editorRef`, `monacoRef`, `isRemoteUpdate`, `applyCode`, `applyLang` | +| **Navbar** | Language dropdown (with `LanguageBadge`), theme dropdown, font size slider, save/clear/download buttons, leave room | +| **UserPresenceBar** | Renders a colored pill badge for each connected user. Hides when room is empty | +| **RightPanel** | Wrapper that stacks `CodeInputPanel`, `AIPanel`, and `VersionPanel` | +| **CodeInputPanel** | stdin textarea, Run button with loading spinner, Ask Gemini button, rate limit countdown | +| **AIPanel** | Question input, execution output display, scrollable AI chat with markdown rendering, copy buttons on code blocks, clear chat, resizable via drag handle | +| **SelectionToolbar** | Appears above selected Monaco text. Quick actions: Explain / Review / Fix / Optimize. Custom "Ask…" input. Shows line count of selection | +| **VersionPanel** | Toggle button to open/close `VersionHistory` | +| **VersionHistory** | Lists snapshots with relative timestamps. Click any to restore. Fetches from `/api/snapshot/:roomId` | +| **LanguageBadge** | Colored dot + short label (PY / JS / C++ / C) β€” uses Tailwind classes from `languageOptions.ts` | +| **Home** | Room ID input, Join button, Create Room button (calls `/api/room/create`), logout | +| **LoginPage** | Username + password form, POSTs to `/api/auth/signin`, stores token in `localStorage` | +| **Signup** | Username + password form, POSTs to `/api/auth/signup`, stores token in `localStorage` | +| **Footer** | GitHub icon link | + +--- + +## Custom Hook Details + +### `useAI` + +Manages all AI interactions with three distinct modes. + +**SSE Streaming:** + +```typescript +const res = await fetch(`${URL}/api/ai/stream`, { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + body: JSON.stringify({ prompt, roomId }), +}); + +const reader = res.body!.getReader(); +// Reads chunks, parses "data: {...}\n\n" SSE format +// Updates last history entry on each chunk +``` + +**AI Modes:** + +| Mode | Trigger | What Gets Sent | +| ----------- | ------------------------ | ------------------------------------------- | +| `code` | "Ask Gemini" button | Full editor code + structured review prompt | +| `selection` | Selection Toolbar action | Selected snippet + specific action prompt | +| `question` | Text input | Freeform question β€” no code injected | + +**Rate Limiting:** + +- Client-side 5 requests/60 seconds +- Timestamps tracked in a ref (`aiTimestamps`) +- Countdown timer displayed in `CodeInputPanel` +- Error toast when limit is hit + +**History:** + +- Fetches existing history from `/api/ai/history/:roomId` on mount +- Single insertion point: user message + empty AI placeholder added together before streaming starts +- Streaming updates the last entry in-place β€” no duplicate insertions + +--- + +### `useCollaboration` + +Manages the full Socket.IO lifecycle and cursor decorations. + +**Socket Lifecycle:** + +```typescript +useEffect(() => { + socket.auth = { token: localStorage.getItem("token") }; + socket.connect(); + + socket.on("connect", () => { + socket.emit("join", { RoomId: roomId }); // only after confirmed connected + }); + + // ... attach all listeners + + return () => { + socket.off("connect"); + // ... remove all listeners + socket.disconnect(); + }; +}, [roomId]); +``` + +**Cursor Decorations:** + +- Injects `