# Project export: vibecheck

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Your candidates will use Cursor and Claude on day one. Why test them without it?
- Devpost: https://devpost.com/software/igotrejectedfrominternshipsbcicantleetcodesoimadethisplatfor
- GitHub: https://github.com/ngethan/calhacks-2025
- Demo: https://calhacks.tech/auth/login
- Video: https://www.youtube.com/embed/KRYA-kEiXrc?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Ethan Ng (29 commits), Evan Yu (27 commits), aaronhubhachen (14 commits), Luke (7 commits)

## Devpost submission (written by the team)

### Inspiration

Traditional programming assessments test memorizing algorithms and solving puzzles in isolation. This does not reflect how engineers write code anymore. Instead, developers use ai tools like Cursor and Claude code. It's time to assess what actually matters which is how candidates think, prompt, debug, and build with AI. Because that's the future of software engineering.

### What it does

VibeCheck is an AI-powered coding assessment platform that tests candidates on real-world software engineering skills rather than algorithmic puzzles. Key Features: Full IDE Experience - Candidates code in a WebContainer-powered browser IDE with file management, terminal access, and live preview Full IDE Experience - Candidates code in a WebContainer-powered browser IDE with file management, terminal access, and live preview AI-Assisted Coding - Tests how well candidates leverage AI tools (like Cursor/Copilot), mirroring modern development workflows AI-Assisted Coding - Tests how well candidates leverage AI tools (like Cursor/Copilot), mirroring modern development workflows AI-Generated Realistic Challenges - Claude creates browser-based coding problems that simulate actual product features (e.g., build a recipe manager, create an e-commerce cart) AI-Generated Realistic Challenges - Claude creates browser-based coding problems that simulate actual product features (e.g., build a recipe manager, create an e-commerce cart) Holistic AI Grading - Claude Sonnet 4.5 evaluates submissions based on framework-specific rubrics, code quality, UI/UX, and effective AI utilization Holistic AI Grading - Claude Sonnet 4.5 evaluates submissions based on framework-specific rubrics, code quality, UI/UX, and effective AI utilization Time-Travel Debugging - Records every keystroke, AI interaction, and file change for session replay—see HOW candidates think and problem-solve Time-Travel Debugging - Records every keystroke, AI interaction, and file change for session replay—see HOW candidates think and problem-solve Detailed Feedback - Provides category-by-category scoring, strengths, areas for improvement, and AI effectiveness analysis Detailed Feedback - Provides category-by-category scoring, strengths, areas for improvement, and AI effectiveness analysis The Flow: 1) Candidate selects their preferred framework (React Router v7 or Next.js) 2) AI generates a realistic, practical coding challenge 3) Candidate builds the solution in the browser IDE with AI assistance 4) AI grades their ability to code with AI 5) Employer reviews detailed results and can replay the entire session

### How we built it

frontend Stack: Next.js 16 with App Router for the application framework WebContainer API for running a full development environment in the browser Monaco Editor for code editing with syntax highlighting XTerm.js for terminal emulation TailwindCSS + shadcn/ui for beautiful, responsive UI Backend Stack: Next.js API Routes for serverless endpoints Drizzle ORM with PostgreSQL (Neon) Better Auth for authentication AI Integration: OpenRouter with Claude Sonnet 4.5 for challenge generation and grading AI SDK from Vercel for streaming AI responses

### Challenges we ran into

WebContainer Limitations - Running a full Node.js environment in the browser had constraints. We had to filter out node_modules, .next, and build artifacts when collecting code for grading. WebContainer Limitations - Running a full Node.js environment in the browser had constraints. We had to filter out node_modules, .next, and build artifacts when collecting code for grading. Database Relations - Drizzle ORM required explicit relation definitions for nested queries. We had to properly define assessmentSubmissionRelations and assessmentSessionRelations. Database Relations - Drizzle ORM required explicit relation definitions for nested queries. We had to properly define assessmentSubmissionRelations and assessmentSessionRelations. Filesystem Implementation - The filesystem is implemented with the zen-fs library. However, the webcontainer also has it's own filesystem, and the editor ui needs to maintain a copy of the file tree. Keeping all three in sync was a challenge. Especially when it came to editing the files. Filesystem Implementation - The filesystem is implemented with the zen-fs library. However, the webcontainer also has it's own filesystem, and the editor ui needs to maintain a copy of the file tree. Keeping all three in sync was a challenge. Especially when it came to editing the files. Race Conditions in Rubric Generation - The rubric took 40+ seconds to generate, causing grading to fail if users submitted too quickly. We implemented a retry mechanism with 50-second polling intervals. Race Conditions in Rubric Generation - The rubric took 40+ seconds to generate, causing grading to fail if users submitted too quickly. We implemented a retry mechanism with 50-second polling intervals. Markdown Rendering - Initially used Streamdown but it failed silently. Switched to react-markdown for reliable rendering of coding challenges. Markdown Rendering - Initially used Streamdown but it failed silently. Switched to react-markdown for reliable rendering of coding challenges. AI Response Parsing - AI sometimes returned malformed JSON. We implemented robust regex extraction and error handling to parse grading results. AI Response Parsing - AI sometimes returned malformed JSON. We implemented robust regex extraction and error handling to parse grading results.

### Accomplishments we're proud of

Built a full WebContainer IDE that runs entirely in the browser—no backend servers for code execution! Created an AI-powered assessment pipeline from challenge generation → IDE coding → automated grading Framework-specific evaluation that adapts grading rubrics based on whether candidates use React Router v7 or Next.js

### What we learned

AI is the future of technical evaluation - Traditional tests are outdated; AI can assess code quality, architecture, and problem-solving holistically Browser capabilities are incredible - WebContainers let us run Node.js, package managers, and dev servers entirely in the browser Real-world tests are more valuable - Building a recipe manager reveals more about a candidate than solving "Two Sum" AI-assisted coding is a skill - How well someone prompts and leverages AI is as important as raw coding ability Event sourcing enables powerful features - Recording state changes as events unlocked session replay without complex infrastructure

## README (from the GitHub repository)

# Create T3 App

This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.

## What's next? How do I make an app with this?

We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary.

If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help.

- [Next.js](https://nextjs.org)
- [NextAuth.js](https://next-auth.js.org)
- [Prisma](https://prisma.io)
- [Drizzle](https://orm.drizzle.team)
- [Tailwind CSS](https://tailwindcss.com)
- [tRPC](https://trpc.io)

## Learn More

To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:

- [Documentation](https://create.t3.gg/)
- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials

You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!

## How do I deploy this?

Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information.


## Detected evidence (automated analysis)

Indexed codebase: 106 recognized source files, 718 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel AI SDK (technology) — detected in the code

## Codebase structure (from repository index)

### Files (115 of 115)

```
.env.example
.gitignore
biome.jsonc
components.json
drizzle.config.ts
drizzle/0000_numerous_jack_flag.sql
drizzle/0001_thin_exiles.sql
drizzle/meta/_journal.json
drizzle/meta/0000_snapshot.json
drizzle/meta/0001_snapshot.json
next.config.js
package.json
postcss.config.js
public/site.webmanifest
README.md
src/app/api/ai/assessment/route.ts
src/app/api/ai/chat/route.ts
src/app/api/ai/grading-rubric/route.ts
src/app/api/assessment/abandon/route.ts
src/app/api/assessment/active/route.ts
src/app/api/assessment/grade/route.ts
src/app/api/assessment/rubric/route.ts
src/app/api/assessment/start/route.ts
src/app/api/assessment/submission/[id]/route.ts
src/app/api/assessment/submit/route.ts
src/app/api/auth/[...all]/route.ts
src/app/api/github/callback/route.ts
src/app/api/github/check-auth/route.ts
src/app/api/github/export/route.ts
src/app/api/github/oauth/route.ts
src/app/api/trpc/[trpc]/route.ts
src/app/auth/login/page.tsx
src/app/auth/signup/page.tsx
src/app/ide/page.tsx
src/app/layout.tsx
src/app/page.tsx
src/app/results/page.tsx
src/components/container.tsx
src/components/file-icon.tsx
src/components/github-export-dialog.tsx
src/components/ui/alert-dialog.tsx
src/components/ui/badge.tsx
src/components/ui/button.tsx
src/components/ui/card.tsx
src/components/ui/context-menu.tsx
src/components/ui/dropdown-menu.tsx
src/components/ui/input.tsx
src/components/ui/progress.tsx
src/components/ui/resizable.tsx
src/components/ui/scroll-area.tsx
src/components/ui/separator.tsx
src/components/ui/shadows.tsx
src/components/ui/sheet.tsx
src/components/ui/sidebar.tsx
src/components/ui/skeleton.tsx
src/components/ui/sonner.tsx
src/components/ui/tabs.tsx
src/components/ui/textarea.tsx
src/components/ui/tooltip.tsx
src/components/xterm-console/index.css
src/components/xterm-console/index.tsx
src/contexts/assessment-context.tsx
src/env.js
src/hooks/use-assessment-timer.ts
src/hooks/use-assessment.ts
src/hooks/use-mobile.ts
src/hooks/use-rubric-generator.ts
src/hooks/use-rubric-status.ts
src/ide/ai/context.ts
src/ide/ai/diff-utils.ts
src/ide/ai/diff-viewer.tsx
src/ide/ai/syntax-highlighter.tsx
src/ide/app.tsx
src/ide/chat.tsx
src/ide/chat/command-output-card.tsx
src/ide/chat/context-utils.ts
src/ide/chat/file-picker.tsx
src/ide/editor.tsx
src/ide/editor/editor-tab-content.tsx
src/ide/editor/tab-content.tsx
src/ide/editor/tab.tsx
src/ide/files.ts
src/ide/filesystem/index.ts
src/ide/filesystem/zen-fs.ts
src/ide/iframe-context.tsx
src/ide/iframe.tsx
src/ide/pages/challenge/index.tsx
src/ide/pages/files/index.tsx
src/ide/pages/search/index.tsx
src/ide/pages/search/search-utils.ts
src/ide/pages/settings/index.tsx
src/ide/router/index.tsx
src/ide/sidebar/content.tsx
src/ide/sidebar/index.tsx
src/ide/stores/chat-store.ts
src/lib/auth-client.ts
src/lib/auth.ts
src/lib/github-export.ts
src/lib/utils/buffer.ts
src/lib/utils/debounce-hooks.ts
src/lib/utils/index.ts
src/lib/utils/istextorbinary.ts
src/middleware.ts
src/server/api/root.ts
src/server/api/routers/ai.ts
src/server/api/trpc.ts
src/server/db/index.ts
src/server/db/schema/assessment-schema.ts
src/server/db/schema/auth-schema.ts
src/server/db/schema/core.ts
src/styles/globals.css
src/trpc/query-client.ts
src/trpc/react.tsx
src/trpc/server.ts
tsconfig.json
```

### Dependencies

- package.json: @ai-sdk/openai@^2.0.53, @ai-sdk/react@^2.0.80, @better-fetch/fetch@^1.1.18, @biomejs/biome@1.9.4, @monaco-editor/react@^4.7.0, @radix-ui/react-alert-dialog@^1.1.15, @radix-ui/react-context-menu@^2.2.16, @radix-ui/react-dialog@^1.1.15, @radix-ui/react-dropdown-menu@^2.1.16, @radix-ui/react-progress@^1.1.7, @radix-ui/react-scroll-area@^1.2.10, @radix-ui/react-separator@^1.1.7, @radix-ui/react-slot@^1.2.3, @radix-ui/react-tabs@^1.1.13, @radix-ui/react-tooltip@^1.2.8, @t3-oss/env-nextjs@^0.12.0, @tailwindcss/postcss@^4.0.15, @tanstack/react-query@^5.69.0, @trpc/client@^11.0.0, @trpc/react-query@^11.0.0, @trpc/server@^11.0.0, @typefox/monaco-editor-react@^7.2.0, @types/node@^20.14.10, @types/react@19.2.1, @types/react-dom@19.2.1, @webcontainer/api@1.6.1-internal.1, @xterm/addon-fit@^0.10.0, @xterm/addon-web-links@^0.11.0, @xterm/xterm@^5.5.0, @zenfs/core@^2.4.2, @zenfs/dom@^1.2.5, ai@^5.0.78, better-auth@^1.3.31, class-variance-authority@^0.7.1, clsx@^2.1.1, diff@^8.0.2, drizzle-kit@^0.30.5, drizzle-orm@^0.41.0, immer@^10.1.3, lucide-react@^0.548.0, monaco-editor@^0.54.0, monaco-editor-wrapper@^6.12.0, motion@^12.23.24, next@16.0.10, next-themes@^0.4.6, node-fetch@^3.3.2, openai@^6.7.0, postcss@^8.5.3, postgres@^3.4.4, react@19.2.1, react-diff-view@^3.3.2, react-dom@19.2.1, react-resizable-panels@^3.0.6, server-only@^0.0.1, shiki@^3.13.0, sonner@^2.0.7, streamdown@^1.4.0, superjson@^2.2.1, tailwind-merge@^3.3.1, tailwindcss@^4.0.15, tw-animate-css@^1.4.0, typescript@^5.8.2, vscode-icons-js@^11.6.1, zod@^3.24.2, zustand@^5.0.8

### Recent commits (newest first)

- Merge pull request #9 from ngethan/vercel/react-server-components-cve-vu-ljtn0q
- Fix React Server Components CVE vulnerabilities
- Merge pull request #8 from ngethan/vercel/dependencies-for-react-flight-nl4uba
- Update dependencies for React Flight RCE advisory
- fixes
- fix: webcontainer blocked
- fix: lint
- Merge branch 'main' of https://github.com/ngethan/calhacks-2025
- Merge branch 'main' of https://github.com/ngethan/calhacks-2025
- fix: temp fix monaco
- last min bug fixes
- Merge branch 'main' of https://github.com/ngethan/calhacks-2025
- fix: properly init projects
- Merge pull request #7 from ngethan/luke
- assessment page styling
- Merge branch 'luke'
- im gonna crash out
- use streamdown instead of react-markdown
- add logout functionality, misc styling
- Merge pull request #6 from ngethan/grading

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "calhacks-2025",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "next build",
    "check": "biome check .",
    "check:unsafe": "biome check --write --unsafe .",
    "check:write": "biome check --write .",
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:push": "drizzle-kit push",
    "db:studio": "drizzle-kit studio",
    "dev": "next dev --turbo",
    "preview": "next build && next start",
    "start": "next start",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@ai-sdk/openai": "^2.0.53",
    "@ai-sdk/react": "^2.0.80",
    "@better-fetch/fetch": "^1.1.18",
    "@monaco-editor/react": "^4.7.0",
    "@radix-ui/react-alert-dialog": "^1.1.15",
    "@radix-ui/react-context-menu": "^2.2.16",
    "@radix-ui/react-dialog": "^1.1.15",
    "@radix-ui/react-dropdown-menu": "^2.1.16",
    "@radix-ui/react-progress": "^1.1.7",
    "@radix-ui/react-scroll-area": "^1.2.10",
    "@radix-ui/react-separator": "^1.1.7",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-tabs": "^1.1.13",
    "@radix-ui/react-tooltip": "^1.2.8",
    "@t3-oss/env-nextjs": "^0.12.0",
    "@tanstack/react-query": "^5.69.0",
    "@trpc/client": "^11.0.0",
    "@trpc/react-query": "^11.0.0",
    "@trpc/server": "^11.0.0",
    "@typefox/monaco-editor-react": "^7.2.0",
    "@webcontainer/api": "1.6.1-internal.1",
    "@xterm/addon-fit": "^0.10.0",
    "@xterm/addon-web-links": "^0.11.0",
    "@xterm/xterm": "^5.5.0",
    "@zenfs/core": "^2.4.2",
    "@zenfs/dom": "^1.2.5",
    "ai": "^5.0.78",
    "better-auth": "^1.3.31",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "diff": "^8.0.2",
    "drizzle-orm": "^0.41.0",
    "immer": "^10.1.3",
    "lucide-react": "^0.548.0",
    "monaco-editor-wrapper": "^6.12.0",
    "motion": "^12.23.24",
    "next": "16.0.10",
    "next-themes": "^0.4.6",
    "node-fetch": "^3.3.2",
    "openai": "^6.7.0",
    "postgres": "^3.4.4",
    "react": "19.2.1",
    "react-diff-view": "^3.3.2",
    "react-dom": "19.2.1",
    "react-resizable-panels": "^3.0.6",
    "server-only": "^0.0.1",
    "shiki": "^3.13.0",
    "sonner": "^2.0.7",
    "streamdown": "^1.4.0",
    "superjson": "^2.2.1",
    "tailwind-merge": "^3.3.1",
    "vscode-icons-js": "^11.6.1",
    "zod": "^3.24.2",
    "zustand": "^5.0.8"
  },
  "devDependencies": {
    "@biomejs/biome": "1.9.4",
    "@tailwindcss/postcss": "^4.0.15",
    "@types/node": "^20.14.10",
    "@types/react": "19.2.1",
    "@types/react-dom": "19.2.1",
    "drizzle-kit": "^0.30.5",
    "monaco-editor": "^0.54.0",
    "postcss": "^8.5.3",
    "tailwindcss": "^4.0.15",
    "tw-animate-css": "^1.4.0",
    "typescript": "^5.8.2"
  },
  "ct3aMetadata": {
    "initVersion": "7.39.3"
  },
  "packageManager": "pnpm@10.15.1"
}

```

### src/app/layout.tsx

```typescript
import "@/styles/globals.css";

import type { Metadata } from "next";
import { Geist } from "next/font/google";
import { Toaster } from "sonner";

import { TRPCReactProvider } from "@/trpc/react";

export const metadata: Metadata = {
  title: "vibecheck",
  description: "Generated by create-t3-app",
  icons: [{ rel: "icon", url: "/favicon.ico" }],
};

const geist = Geist({
  subsets: ["latin"],
  variable: "--font-geist-sans",
});

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en" className={`${geist.variable} dark`}>
      <body>
        <TRPCReactProvider>{children}</TRPCReactProvider>
        <Toaster position="top-center" richColors closeButton />
      </body>
    </html>
  );
}

```

### src/trpc/server.ts

```typescript
import "server-only";

import { createHydrationHelpers } from "@trpc/react-query/rsc";
import { headers } from "next/headers";
import { cache } from "react";

import { type AppRouter, createCaller } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";
import { createQueryClient } from "./query-client";

/**
 * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
 * handling a tRPC call from a React Server Component.
 */
const createContext = cache(async () => {
  const heads = new Headers(await headers());
  heads.set("x-trpc-source", "rsc");

  return createTRPCContext({
    headers: heads,
  });
});

const getQueryClient = cache(createQueryClient);
const caller = createCaller(createContext);

export const { trpc: api, HydrateClient } = createHydrationHelpers<AppRouter>(
  caller,
  getQueryClient,
);

```

### src/ide/app.tsx

```typescript
"use client";

import { WebContainerProvider, useWebContainer } from "@/components/container";
import { GitHubExportDialog } from "@/components/github-export-dialog";
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable";
import { SidebarProvider } from "@/components/ui/sidebar";
import XTermConsole from "@/components/xterm-console";
import { useAssessment } from "@/hooks/use-assessment";
import { useRubricGenerator } from "@/hooks/use-rubric-generator";
import { Chat } from "@/ide/chat";
import { IDEEditor, useEditorState } from "@/ide/editor";
import { IFrame } from "@/ide/iframe";
import { IDESidebar } from "@/ide/sidebar";
import { IDESidebarContent } from "@/ide/sidebar/content";
import { Eye, Github, Loader2, Menu, MessageSquare, Send } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { IFrameProvider } from "./iframe-context";

const AppContent = () => {
  const [showChat, setShowChat] = useState(true);
  const [chatWidth, setChatWidth] = useState(25);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [showGitHubDialog, setShowGitHubDialog] = useState(false);
  const [isGitHubAuthenticated, setIsGitHubAuthenticated] = useState(false);
  const editorState = useEditorState();
  const container = useWebContainer();
  const router = useRouter();
  const searchParams = useSearchParams();
  const assessment = useAssessment();

  // Generate rubric on mount if not already generated
  useRubricGenerator();

  // Check for GitHub OAuth callback
  useEffect(() => {
    const githubSuccess = searchParams.get("github_success");
    const githubError = searchParams.get("github_error");

    if (githubSuccess === "true") {
      setIsGitHubAuthenticated(true);
      toast.success("Connected to GitHub successfully!");
      // Show the export dialog
      setShowGitHubDialog(true);
      // Clean up URL
      window.history.replaceState({}, "", "/ide");
    } else if (githubError) {
      toast.error("Failed to connect to GitHub", {
        description: `Error: ${githubError}`,
      });
      // Clean up URL
      window.history.replaceState({}, "", "/ide");
    }
  }, [searchParams]);

  // Check if already authenticated on mount
  useEffect(() => {
    // Check if the github_access_token cookie exists
    const checkAuth = async () => {
      try {
        const response = await fetch("/api/github/check-auth");
        if (response.ok) {
          const data = await response.json();
          setIsGitHubAuthenticated(data.authenticated);
        }
      } catch (error) {
        console.error("Failed to check GitHub auth:", error);
      }
    };
    checkAuth();
  }, []);

  const handleExportToGithub = () => {
    if (isGitHubAuthenticated) {
      setShowGitHubDialog(true);
    } else {
      setShowGitHubDialog(true);
    }
  };

  const handleGitHubAuth = () => {
    toast.info("Connecting to GitHub...");
    window.location.href = "/api/github/oauth";
  };

  const handleSubmit = async () => {
    if (!container || container.status !== "ready") {
      toast.error("Container not ready. Please wait and try again.");
      return;
    }

    setIsSubmitting(true);

    try {
      // Get active session from localStorage
      const sessionData = await fetch("/api/assessment/active");
      if (!sessionData.ok) {
        throw new Error("No active assessment session found");
      }

      const { session } = await sessionData.json();
      if (!session) {
        throw new Error("No active assessment session found");
      }

      toast.info("Collecting your code...");

      // Use the same file collection method as GitHub export
      const { collectFilesForExport } = await import("@/lib/github-export");
      const filesList = await collectFilesForExport();

      if (filesList.length === 0) {
        throw new Error(
          "No files found to submit. Please write some code first.",
        );
      }

      // Convert to Record<string, string> format expected by the API
      const files: Record<string, string> = {};
      for (const file of filesList) {
        files[file.path] = file.content;
      }

      console.log(`[Submit] Collected ${Object.keys(files).length} files`);
      console.log("[Submit] File paths:", Object.keys(files).slice(0, 10)); // Log first 10 for debugging

      // Submit the code
      const response = await fetch("/api/assessment/submit", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          sessionId: session.id,
          code: { files },
        }),
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error || "Failed to submit assessment");
      }

      const { submission } = await response.json();

      toast.success("Assessment submitted successfully!", {
        description: "Your code is being graded. Redirecting to results...",
      });

      // Redirect to results page
      setTimeout(() => {
        router.push(`/results?submissionId=${submission.id}`);
      }, 1000);
    } catch (error) {
      console.error("Error submitting assessment:", error);
      toast.error(
        error instanceof Error ? error.message : "Failed to submit assessment",
      );
      setIsSubmitting(false);
    }
  };

  return (
    <div className="flex h-screen flex-col">
      <div className="flex shrink-0 items-center justify-between gap-2 border-border border-b bg-sidebar px-4 py-2">
        <div className="flex items-center gap-2">
          <img
            src="/logo.png"
            alt="Aligned Logo"
           
[truncated — 5940 more characters]
```

### src/lib/utils/index.ts

```typescript
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

```

### src/server/db/index.ts

```typescript
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";

import { env } from "@/env";
import * as schema from "./schema/core";

/**
 * Cache the database connection in development. This avoids creating a new connection on every HMR
 * update.
 */
const globalForDb = globalThis as unknown as {
  conn: postgres.Sql | undefined;
};

const conn = globalForDb.conn ?? postgres(env.DATABASE_URL);
if (env.NODE_ENV !== "production") globalForDb.conn = conn;

export const db = drizzle(conn, { schema });

```

### src/ide/router/index.tsx

```typescript
import {
  FileTextIcon,
  FilesIcon,
  SearchIcon,
  SettingsIcon,
} from "lucide-react";
import { create } from "zustand";

export type Page = {
  name: string;
  icon: React.ElementType;
  bottom?: boolean;
};
export type AllPages = "files" | "challenge" | "search" | "settings";
export const pages: { [key in AllPages]: Page } = {
  files: {
    name: "Files",
    icon: FilesIcon,
  },
  search: {
    name: "Search",
    icon: SearchIcon,
  },
  challenge: {
    name: "Challenge",
    icon: FileTextIcon,
  },
  settings: {
    name: "Settings",
    icon: SettingsIcon,
    bottom: true,
  },
};

type IDERouterState = {
  page: AllPages;
  setPage: (page: AllPages) => void;
};

export const useIDERouter = create<IDERouterState>()((set) => ({
  page: "challenge", // Default to challenge page so users see the problem immediately
  setPage: (page) => set({ page }),
}));

```

### src/app/ide/page.tsx

```typescript
import { AssessmentProvider } from "@/contexts/assessment-context";
import { auth } from "@/lib/auth";
import { db } from "@/server/db";
import { assessmentSession } from "@/server/db/schema/assessment-schema";
import { and, eq } from "drizzle-orm";
import dynamic from "next/dynamic";
import { headers } from "next/headers";
import { redirect } from "next/navigation";

const App = dynamic(() => import("@/ide/app"));

export default async function IDEPage() {
  const session = await auth.api.getSession({
    headers: await headers(),
  });

  if (!session?.user) {
    redirect("/api/auth/sign-in");
  }

  // Fetch active assessment directly from database
  const activeAssessment = await db.query.assessmentSession.findFirst({
    where: and(
      eq(assessmentSession.userId, session.user.id),
      eq(assessmentSession.status, "active"),
    ),
  });

  return (
    <AssessmentProvider
      userId={session.user.id}
      sessionId={activeAssessment?.id ?? null}
      framework={activeAssessment?.framework ?? null}
    >
      <App />
    </AssessmentProvider>
  );
}

```

### src/ide/filesystem/index.ts

```typescript
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";

// example data structure
// const files = {
//   "test.txt": {
//     file: {
//       size: 1024,
//     }
//   },
//   "test.js": {
//     file: {
//       size: 1024,
//     }
//   },
//   "dir": {
//     directory: {
//       "test.txt": {
//         file: {
//           size: 1024,
//         }
//       },
//       "symlink-file": {
//         symlink: {
//           target: "test.txt",
//         }
//       },
//     }
//   }
// }

export type FSFile = {
  file: {
    size: number;
    isBinary: boolean;
  };
};
export type FSDirectory = {
  directory: {
    [key: string]: FSFile | FSDirectory | FSSymlink;
  };
  open: boolean;
};
export type FSSymlink = {
  symlink: {
    target: string;
  };
};

export type FSNode = FSFile | FSDirectory | FSSymlink;

type FileSystemState = {
  files: FSDirectory;
  setFiles: (files: FSDirectory) => void;
};

export const useFileSystem = create<FileSystemState>()(
  immer((set) => ({
    files: {
      directory: {},
      open: true,
    },
    setFiles: (files) => set({ files }),
  })),
);

```

### src/app/page.tsx

```typescript
"use client";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { motion } from "motion/react";
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { Streamdown } from "streamdown";

type Framework = "react-router-v7" | "nextjs" | null;

type AssessmentSession = {
  id: string;
  userId: string;
  framework: string;
  problemContent: string;
  status: string;
  startedAt: string;
  completedAt: string | null;
  createdAt: string;
  updatedAt: string;
};

function AnimatedStreamdown({ content }: { content: string }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const previousLengthRef = useRef(0);
  const observerRef = useRef<MutationObserver | null>(null);

  // Set up mutation observer once
  useEffect(() => {
    if (!containerRef.current) return;

    const observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node instanceof HTMLElement) {
            // Animate newly added elements
            node.style.opacity = "0";
            node.style.animation = "streamFadeIn 1s ease-out forwards";
          } else if (node.nodeType === Node.TEXT_NODE && node.parentElement) {
            // Animate parent of newly added text nodes
            const parent = node.parentElement;
            if (!parent.classList.contains("stream-animated")) {
              parent.classList.add("stream-animated");
              parent.style.animation = "streamFadeIn 0.5s ease-out forwards";
            }
          }
        }
      }
    });

    observer.observe(containerRef.current, {
      childList: true,
      subtree: true,
    });

    observerRef.current = observer;

    return () => {
      observer.disconnect();
    };
  }, []);

  // Track content changes
  useEffect(() => {
    if (content.length > previousLengthRef.current) {
      previousLengthRef.current = content.length;
    } else if (content.length === 0) {
      previousLengthRef.current = 0;
    }
  }, [content]);

  return (
    <>
      <style jsx global>{`
        @keyframes streamFadeIn {
          from {
            opacity: 0;
          }
          to {
            opacity: 1;
          }
        }
      `}</style>
      <div ref={containerRef}>
        <Streamdown>{content}</Streamdown>
      </div>
    </>
  );
}

export default function AssessmentPage() {
  const router = useRouter();
  const [isStreaming, setIsStreaming] = useState(false);
  const [assessmentContent, setAssessmentContent] = useState("");
  const [generatedAt, setGeneratedAt] = useState<string | null>(null);
  const [selectedFramework, setSelectedFramework] = useState<Framework>(null);
  const [activeSession, setActiveSession] = useState<AssessmentSession | null>(
    null,
  );
  const [isCheckingSession, setIsCheckingSession] = useState(true);
  const [isStarting, setIsStarting] = useState(false);
  const [showAbandonConfirmation, setShowAbandonConfirmation] = useState(false);
  const [isAbandoning, setIsAbandoning] = useState(false);

  // Check for active session on mount
  useEffect(() => {
    checkForActiveSession();
  }, []);

  const checkForActiveSession = async () => {
    try {
      const response = await fetch("/api/assessment/active");
      if (response.ok) {
        const data = await response.json();
        if (data.session) {
          setActiveSession(data.session);
        }
      }
    } catch (error) {
      console.error("Error checking for active session:", error);
    } finally {
      setIsCheckingSession(false);
    }
  };

  const handleResumeSession = () => {
    if (activeSession) {
      // Navigate to IDE - it will fetch the active session from the database
      router.push("/ide");
    }
  };

  const handleAbandonSession = async () => {
    if (!activeSession) return;

    setIsAbandoning(true);

    try {
      const response = await fetch("/api/assessment/abandon", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ sessionId: activeSession.id }),
      });

      if (response.ok) {
        setActiveSession(null);
        setShowAbandonConfirmation(false);
        toast.success("Session abandoned! You can now start a new assessment.");
      } else {
        const error = await response.json();
        toast.error(`Failed to abandon session: ${error.error}`);
      }
    } catch (error) {
      console.error("Error abandoning session:", error);
      toast.error("Failed to abandon session. Please try again.");
    } finally {
      setIsAbandoning(false);
    }
  };

  const handleViewProblem = async () => {
    // Check if there's an active session
    if (activeSession) {
      toast.error(
        "You already have an active assessment. Please resume or abandon it first.",
      );
      return;
    }

    setIsStreaming(true);
    setAssessmentContent("");
    setGeneratedAt(new Date().toISOString());

    try {
      const requestBody = selectedFramework
        ? { framework: selectedFramework }
        : {};

      const response = await fetch("/api/ai/assessment", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(requestBody),
      });

      if (!response.ok) {
        const errorText = await response.text().catch(() => "Unknown error");
        console.error("Assessment API error:", response.status, errorText);
        throw new Error(
          `Failed to generate assessment: ${response.status} - ${errorText}`,
        );
      }

      const reader = response.body?.getReader();
      const decoder = new TextDecoder();

      if (!reader) {
        throw new Error("No response body");
    
[truncated — 17335 more characters]
```

[95 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]