Skip to content

Conversation

@FirePheonix
Copy link

@FirePheonix FirePheonix commented Dec 21, 2025

@ajeetunc as proposed, here's the PR for
Here's the working demo video:
https://youtu.be/gmg0PAEpNlc

Do the following changes in the .env file for testing:
(if razorpay keys not provided, do disable the razorpay keys for testing)

OPEN_ROUTER_API_KEY=sk-or-v1-.....<rest of your openrouter api key>
OPENROUTER_MODEL=google/gemini-2.5-flash

It's inspired by Zach's https://code2tutorial.com/ , the open sourced repo is in python, I've implemented it in TS for Opensox.

Summary by CodeRabbit

Release Notes

  • New Features

    • Generate interactive tutorials from GitHub repositories with AI-powered code analysis
    • New tutorial viewer with markdown rendering, syntax highlighting, and Mermaid diagram support
    • Interactive file browser for selecting specific repository files to include in tutorials
    • Tutorial history management with creation timestamps and quick access
    • Download tutorials as markdown files for offline use
  • Chores

    • Added dependencies for markdown processing, syntax highlighting, and diagram rendering

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

@vercel
Copy link

vercel bot commented Dec 21, 2025

@FirePheonix is attempting to deploy a commit to the AJEET PRATAP SINGH's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

Walkthrough

This PR introduces an end-to-end tutorial generation system. It adds a Prisma model for persisting tutorials, creates backend services for GitHub repository crawling and LLM-powered analysis, implements a multi-step tutorial generation pipeline, exposes tRPC operations for tutorial management, and provides frontend components for generating, browsing, and viewing tutorials with rich markdown rendering and syntax highlighting.

Changes

Cohort / File(s) Summary
Database Schema & Migrations
apps/api/prisma/schema.prisma, apps/api/prisma/migrations/20251215163900_add_tutorial_model/migration.sql
Introduces new Tutorial model with id, userId, repoUrl, projectName, language, indexContent, mermaidDiagram, chapters, timestamps. Includes indexes on userId and repoUrl.
GitHub Integration Service
apps/api/src/services/github-crawler.service.ts
New service for parsing GitHub URLs, fetching repository file metadata and content via Git Trees/Contents APIs, crawling with include/exclude patterns, and listing files for file-browser UIs. Exports crawlGitHubFiles, crawlSelectedFiles, listRepoFiles, and related utilities.
LLM Integration Service
apps/api/src/services/llm.service.ts
New service for calling OpenRouter API (Gemini 2.5 Flash) with in-memory caching, request logging, and error handling. Exports callLLM function for external usage.
Tutorial Generation Pipeline
apps/api/src/services/tutorial.service.ts
New service implementing 6-step LLM-driven pipeline: parse repo, fetch files, identify abstractions, analyze relationships, order chapters, and write chapters. Returns TutorialResult with projectName, indexContent, chapters, and mermaidDiagram.
API Router & Configuration
apps/api/src/routers/tutorial.ts, apps/api/src/routers/_app.ts, apps/api/src/index.ts
New tRPC tutorial router with procedures: listRepoFiles, checkExisting, getById, getUserTutorials, generate, delete, healthCheck. Integrated into appRouter. Body parsing limits increased (5MB for /trpc, 100KB default) to accommodate tutorial content.
Web Dependencies
apps/web/package.json
Added: mermaid, react-markdown, react-syntax-highlighter, highlight.js, rehype-highlight, rehype-raw, remark-gfm, @types/react-syntax-highlighter for markdown rendering and syntax highlighting.
Tutorial Page & Orchestration
apps/web/src/app/(main)/dashboard/tutorial/page.tsx
New page component coordinating TutorialGenerator, TutorialViewer, and TutorialHistory. Manages selected and generated tutorial state; switches between generator and viewer UI modes.
File Browser Component
apps/web/src/components/tutorial/FileBrowser.tsx
Interactive hierarchical file tree with expandable folders, multi-select with tri-state checkboxes, size constraints (max 3 MB), batch actions (select/deselect all), and per-folder toggles. Returns selected file paths to parent.
Tutorial Generator Component
apps/web/src/components/tutorial/TutorialGenerator.tsx
Multi-stage component for collecting GitHub URL and language, checking existing tutorials, listing and selecting repository files, and triggering generation via TRPC. Shows loading and error states; invokes onTutorialGenerated callback.
Tutorial History Component
apps/web/src/components/tutorial/TutorialHistory.tsx
Panel displaying recent user tutorials with project name, repo, date, and language. Includes delete functionality with confirmation dialog. Formats dates ("Today", "Yesterday", etc.) and extracts repo names from GitHub URLs.
Tutorial Viewer Component
apps/web/src/components/tutorial/TutorialViewer.tsx
Rich tutorial renderer with chapter navigation sidebar, Mermaid diagram rendering, CodeBlock with syntax highlighting and copy-to-clipboard, and ReactMarkdown with custom renderers. Supports downloading tutorial as single Markdown file.
Tutorial Components Index
apps/web/src/components/tutorial/index.ts
Barrel export for TutorialGenerator, TutorialViewer, and TutorialHistory components.
Styling & Navigation
apps/web/src/app/globals.css, apps/web/src/components/dashboard/Sidebar.tsx
New .tutorial-content CSS block for tutorial typography, code blocks, syntax highlighting (HLJS tokens), and Mermaid diagram theming. Added "Code Tutorial" sidebar link with BookOpenIcon and AI badge.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Web as Frontend<br/>(TutorialGenerator)
    participant tRPC as tRPC API<br/>(tutorial router)
    participant GHCrawler as GitHub<br/>Crawler Service
    participant GitHub as GitHub API
    participant LLM as LLM Service
    participant OpenRouter as OpenRouter<br/>API
    participant DB as Prisma<br/>(Database)

    User->>Web: Enter repo URL & language
    Web->>tRPC: checkExisting(repoUrl)
    tRPC->>DB: Query tutorials by repoUrl
    
    alt Existing tutorials found
        tRPC-->>Web: Return existing tutorials
        User->>Web: Confirm regenerate or view
    else No existing tutorials
        Web->>tRPC: listRepoFiles(repoUrl)
        tRPC->>GHCrawler: listRepoFiles()
        GHCrawler->>GitHub: Fetch repo file tree
        GitHub-->>GHCrawler: File metadata
        GHCrawler-->>tRPC: File list with sizes
        tRPC-->>Web: Return file list
        
        User->>Web: Select files (max 3MB)
        Web->>tRPC: generate({ repoUrl, selectedFiles, language })
        
        tRPC->>GHCrawler: crawlSelectedFiles()
        GHCrawler->>GitHub: Fetch file contents
        GitHub-->>GHCrawler: Raw file content
        
        tRPC->>LLM: Step 2: Identify abstractions
        LLM->>OpenRouter: Send code + prompt
        OpenRouter-->>LLM: LLM response (JSON)
        
        tRPC->>LLM: Step 3: Analyze relationships
        LLM->>OpenRouter: Send abstractions + code
        OpenRouter-->>LLM: Relationship graph
        
        tRPC->>LLM: Step 4: Order chapters
        LLM->>OpenRouter: Determine sequence
        OpenRouter-->>LLM: Chapter order
        
        tRPC->>LLM: Step 5: Write chapters
        LLM->>OpenRouter: Generate per-chapter markdown
        OpenRouter-->>LLM: Chapter content
        
        tRPC->>DB: Insert tutorial
        DB-->>tRPC: Tutorial persisted
        
        tRPC-->>Web: Return generated TutorialResult
        Web->>Web: Store generated tutorial
        Web->>Web: Switch to TutorialViewer
    end

    User->>Web: View tutorial
    Web->>Web: Render chapters, code blocks, Mermaid diagram
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • apps/api/src/services/tutorial.service.ts: Multi-step LLM pipeline with 6 sequential stages—validate JSON parsing error handling, LLM prompt engineering, abstraction validation logic, and relationship graph construction. Ensure indices are correctly validated against file counts.
  • apps/api/src/services/github-crawler.service.ts: GitHub API integration with fallback logic (Trees → Contents API), branch resolution, file filtering by size/count—verify edge cases for large repos, private repos, and rate limiting behavior.
  • apps/api/src/services/llm.service.ts: External API calls to OpenRouter with caching—confirm error handling for API failures, timeout behavior, and token limits. Validate environment variable initialization.
  • apps/web/src/components/tutorial/FileBrowser.tsx: Complex tri-state checkbox logic, tree building, and size calculations—test edge cases with deeply nested directories, large file counts, and boundary conditions at 3 MB limit.
  • apps/web/src/components/tutorial/TutorialViewer.tsx: Custom Markdown renderers with syntax highlighting, Mermaid diagram rendering, and state synchronization—verify code block escaping, diagram error handling, and download Markdown assembly.
  • apps/api/src/index.ts: Body parser limit changes (5 MB for /trpc vs 100 KB default)—ensure memory and performance implications are acceptable; confirm no unintended impact on webhook routes.
  • Security/Tokens: GitHub and OpenRouter token handling in services—verify no tokens leak in logs or error messages; confirm environment variable initialization and fallback behavior.

Possibly related PRs

  • PR #193: Modifies apps/web/src/components/dashboard/Sidebar.tsx (same file updated in this PR) — related through shared sidebar navigation changes.
  • PR #158: Updates both apps/web/src/components/dashboard/Sidebar.tsx and apps/web/package.json — overlapping file modifications with navigation and dependency management.

Poem

🐰 A rabbit hops through code and git,
With crawlers, llms, and chapters fit,
Six steps dance to weave the tale,
Mermaid flows where insights sail! 📚✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: ai tutorial for any open source codebase' clearly and directly summarizes the main feature being added—an AI-powered tutorial generation system for open source repositories.
Docstring Coverage ✅ Passed Docstring coverage is 84.85% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cla-assistant
Copy link

cla-assistant bot commented Dec 21, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

1 similar comment
@cla-assistant
Copy link

cla-assistant bot commented Dec 21, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

@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: 20

🧹 Nitpick comments (20)
apps/web/package.json (1)

23-23: Move type definitions to devDependencies.

@types/react-syntax-highlighter is a TypeScript type definition package and should be placed in devDependencies rather than dependencies. Type definitions are only needed at build time, not runtime.

🔎 Suggested change

Move line 23 from dependencies to devDependencies:

  "devDependencies": {
    "@tailwindcss/line-clamp": "^0.4.4",
    "@types/jsdom": "^27.0.0",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
+   "@types/react-syntax-highlighter": "^15.5.13",
    "@types/sanitize-html": "^2.16.0",
apps/web/src/app/globals.css (1)

85-87: Hardcoded hex value in pre background.

Per coding guidelines, hardcoded hex values should be avoided. Consider using a CSS variable or Tailwind's theme colors. However, I understand this may be intentional to match the github-dark highlight.js theme.

🔎 Optional: Use CSS variable
 .tutorial-content pre {
-  @apply bg-[#0d1117] !important;
+  background-color: var(--background) !important;
 }

Or define a custom color in tailwind.config.ts if this specific shade is needed.

apps/web/src/components/tutorial/TutorialViewer.tsx (4)

126-137: Direct innerHTML assignment bypasses React's XSS protection.

While highlight.js does escape HTML entities by default, directly setting innerHTML is still a risk if the library behavior changes or if there are edge cases. Consider using the hljs.highlightElement approach or ensuring the output is sanitized.

🔎 Alternative: Use highlightElement or sanitize
   useEffect(() => {
     if (codeRef.current && language && children) {
       try {
         if (hljs.getLanguage(language)) {
-          const highlighted = hljs.highlight(children, { language }).value;
-          codeRef.current.innerHTML = highlighted;
+          codeRef.current.textContent = children;
+          hljs.highlightElement(codeRef.current);
         }
       } catch (e) {
         console.error("Highlight error:", e);
       }
     }
   }, [children, language]);

53-53: Consider stricter typing for chapters field.

Chapter[] | unknown is quite permissive. If the API can return chapters as a JSON string or array, consider a more explicit union type like Chapter[] | string to better document the expected formats and improve type safety.

-  chapters: Chapter[] | unknown;
+  chapters: Chapter[] | string;

539-545: Consider next/image for image optimization.

Per guidelines, next/image should be used for image optimization. However, since tutorial images come from dynamic external sources (markdown content), you may need to configure remotePatterns in next.config.js or use a custom loader. If images are primarily from GitHub, consider adding github.com to allowed patterns.


505-505: Avoid using any type.

Per coding guidelines, avoid any type. Define proper types for the ReactMarkdown code component props.

🔎 Suggested typing
+interface CodeComponentProps {
+  node?: unknown;
+  inline?: boolean;
+  className?: string;
+  children?: React.ReactNode;
+}
+
-                code({ node, inline, className, children, ...props }: any) {
+                code({ inline, className, children }: CodeComponentProps) {
apps/web/src/components/tutorial/TutorialHistory.tsx (4)

6-12: Define a shared TypeScript type for Tutorial instead of local interface.

The Tutorial interface is defined locally but appears to mirror the data structure returned by the API. Consider extracting this to a shared types file to maintain consistency across components and avoid duplication.

💡 Suggested approach

Create a shared types file (e.g., apps/web/src/types/tutorial.ts) and import it here:

// apps/web/src/types/tutorial.ts
export interface Tutorial {
  id: string;
  projectName: string;
  repoUrl: string;
  language: string;
  createdAt: string | Date;
}

Then import in this file:

-interface Tutorial {
-  id: string;
-  projectName: string;
-  repoUrl: string;
-  language: string;
-  createdAt: string | Date;
-}
+import type { Tutorial } from "@/types/tutorial";

31-36: Replace browser confirm() with an accessible dialog component.

The browser's native confirm() dialog doesn't follow the design system and has accessibility limitations. Consider using a proper modal/dialog component that supports keyboard navigation and aria labels.

As per coding guidelines: "Include proper aria labels for accessibility" and "Ensure keyboard navigation works."


38-57: Consider using a date formatting library for consistency.

While the date formatting logic is correct, using a library like date-fns or dayjs would provide better consistency across the application and reduce custom date manipulation code.


82-113: Enhance accessibility for interactive tutorial cards.

The tutorial cards use a clickable div without proper accessibility attributes. For better keyboard navigation and screen reader support, consider the following improvements:

  1. Add role="button" and tabIndex={0} to the tutorial card div
  2. Add onKeyDown handler for Enter/Space key support
  3. Add aria-label to the delete button describing the action with context

As per coding guidelines: "Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)."

🔎 Suggested improvements
              <div
                key={tutorial.id}
                onClick={() => onSelectTutorial(tutorial.id)}
+               role="button"
+               tabIndex={0}
+               onKeyDown={(e) => {
+                 if (e.key === 'Enter' || e.key === ' ') {
+                   e.preventDefault();
+                   onSelectTutorial(tutorial.id);
+                 }
+               }}
                className="group p-4 bg-dash-base hover:bg-dash-hover border border-dash-border rounded-lg cursor-pointer transition-all hover:border-brand-purple/30"
              >
                {/* ... */}
                <button
                  onClick={(e) => handleDelete(e, tutorial.id)}
+                 aria-label={`Delete tutorial ${tutorial.projectName}`}
                  className="p-1.5 rounded-md opacity-0 group-hover:opacity-100 hover:bg-red-500/10 transition-all"
                  title="Delete tutorial"
                >
apps/api/src/index.ts (1)

65-70: The implementation correctly applies route-specific size limits with appropriate ordering and ordering precedence.

The 5MB limit for tRPC endpoints provides adequate buffer for the stated 3.0MB file content requirement. However, consider that the tRPC input validation schema (via Zod) validates only request parameters (maxFiles, maxAbstractions, selectedFiles), not actual file content payload sizes. While the Express middleware provides a hard limit, adding explicit file size validation within the tutorial router schema would provide earlier rejection of oversized payloads with more granular error feedback.

apps/web/src/components/tutorial/FileBrowser.tsx (2)

17-19: Avoid using any type for the tutorial parameter.

The onTutorialGenerated callback uses any for the tutorial type. Define a proper TypeScript interface for the tutorial object to improve type safety. Based on coding guidelines, avoid any type.

Suggested improvement
+interface Tutorial {
+  id: string;
+  projectName: string;
+  indexContent: string;
+  chapters: Array<{ filename: string; content: string }>;
+  mermaidDiagram: string;
+  fromHistory?: boolean;
+}
+
 interface TutorialGeneratorProps {
-  onTutorialGenerated: (tutorial: any) => void;
+  onTutorialGenerated: (tutorial: Tutorial) => void;
 }

314-346: Dead code in handleToggle - unreachable variable allFiles.

Lines 326-329 compute allFiles but it's never used. The actual logic uses filePaths computed from folderNode. This appears to be leftover code that should be removed.

     } else if (children) {
-      // For folders, toggle all children
-      const allFiles = children.flatMap(c => getAllFilePaths({
-        ...{ name: "", path, type: "dir", children },
-        children,
-      } as TreeNode));
-      
       // Get actual file paths from the folder
       const folderNode: TreeNode = { name: "", path, type: "dir", children };
       const filePaths = getAllFilePaths(folderNode);
apps/web/src/components/tutorial/TutorialGenerator.tsx (3)

17-19: Replace any types with proper TypeScript interfaces.

Multiple uses of any type violate coding guidelines. Define proper interfaces for tutorial data and error handling.

Suggested improvement
+interface ExistingTutorial {
+  id: string;
+  projectName: string;
+  createdAt: string;
+  language: string;
+  isOwnTutorial: boolean;
+}
+
+interface GeneratedTutorial {
+  id: string;
+  projectName?: string;
+  indexContent?: string;
+  chapters?: Array<{ filename: string; content: string }>;
+  mermaidDiagram?: string;
+  fromHistory?: boolean;
+}
+
 interface TutorialGeneratorProps {
-  onTutorialGenerated: (tutorial: any) => void;
+  onTutorialGenerated: (tutorial: GeneratedTutorial) => void;
 }

-  const [existingTutorials, setExistingTutorials] = useState<any[]>([]);
+  const [existingTutorials, setExistingTutorials] = useState<ExistingTutorial[]>([]);

   // Generate mutation
   const generateMutation = trpc.tutorial.generate.useMutation({
-    onSuccess: (data: any) => {
+    onSuccess: (data: GeneratedTutorial) => {
       setViewMode("form");
       onTutorialGenerated(data);
     },
-    onError: (err: any) => {
+    onError: (err: Error) => {
       setViewMode("form");
       setError(err.message);
     },
   });

Also applies to: 31-31, 50-50, 54-54


84-85: Type the caught errors properly instead of using any.

Error handling uses any type. Use unknown and narrow the type appropriately.

-    } catch (err: any) {
-      setError(err.message || "Failed to fetch repository files");
+    } catch (err: unknown) {
+      setError(err instanceof Error ? err.message : "Failed to fetch repository files");

Also applies to: 127-128


238-253: Add aria-label to the language select element for accessibility.

The select element lacks an accessible label association. While there's a visible label, adding id and htmlFor attributes creates a proper association.

         <div>
-          <label className="block text-sm font-medium text-text-secondary mb-2">
+          <label 
+            htmlFor="tutorial-language"
+            className="block text-sm font-medium text-text-secondary mb-2"
+          >
             Tutorial Language
           </label>
           <select
+            id="tutorial-language"
             value={language}
             onChange={(e) => setLanguage(e.target.value)}
apps/api/src/services/tutorial.service.ts (2)

70-84: Avoid any return type and add safer JSON parsing.

The parseJsonFromResponse function returns any, which propagates type unsafety. Consider using unknown and validating the structure, or at minimum documenting the expected shape. Additionally, the regex matching could fail silently.

Suggested improvement
-function parseJsonFromResponse(response: string): any {
+function parseJsonFromResponse(response: string): unknown {
   // Try to find JSON block in markdown code fence
   const jsonMatch = response.match(/```json\n?([\s\S]*?)```/);
   if (jsonMatch && jsonMatch[1]) {
-    return JSON.parse(jsonMatch[1].trim());
+    try {
+      return JSON.parse(jsonMatch[1].trim());
+    } catch (e) {
+      // fall through to try raw JSON
+    }
   }
   
   // Try to find raw JSON array or object
   const rawJsonMatch = response.match(/(\[[\s\S]*\]|\{[\s\S]*\})/);
   if (rawJsonMatch && rawJsonMatch[1]) {
     return JSON.parse(rawJsonMatch[1]);
   }
   
   throw new Error("No JSON block found in LLM response");
 }

693-700: Internal step functions are exposed unnecessarily.

The tutorialService object exports internal pipeline functions (identifyAbstractions, analyzeRelationships, etc.) that appear to be implementation details. Consider whether these need to be part of the public API or if only generateTutorial should be exposed.

apps/api/src/services/github-crawler.service.ts (2)

375-411: Consider parallel fetching with concurrency limit for crawlSelectedFiles.

The function fetches files sequentially, which can be slow for many files. Consider using Promise.all with a concurrency limiter for better performance while avoiding rate limits.

Suggested improvement
+const CONCURRENCY_LIMIT = 5;
+
+async function processInBatches<T, R>(
+  items: T[],
+  processor: (item: T) => Promise<R>,
+  batchSize: number
+): Promise<R[]> {
+  const results: R[] = [];
+  for (let i = 0; i < items.length; i += batchSize) {
+    const batch = items.slice(i, i + batchSize);
+    const batchResults = await Promise.all(batch.map(processor));
+    results.push(...batchResults);
+  }
+  return results;
+}
+
 export async function crawlSelectedFiles(
   repoUrl: string,
   selectedPaths: string[],
   options: { maxFileSize?: number } = {}
 ): Promise<CrawlResult> {
   const { maxFileSize = 100 * 1024 } = options;
   
   const { owner, repo, branch } = parseGitHubUrl(repoUrl);
   
   console.log(`Crawling ${selectedPaths.length} selected files from ${owner}/${repo}`);

-  const files: Array<{ path: string; content: string }> = [];
-  let skippedFiles = 0;
-
-  for (const filePath of selectedPaths) {
-    // Fetch file content
-    const content = await fetchFileContent(owner, repo, filePath, branch);
-    if (content) {
-      files.push({ path: filePath, content });
-      console.log(`Added ${filePath}`);
-    } else {
-      skippedFiles++;
-    }
-  }
+  const results = await processInBatches(
+    selectedPaths,
+    async (filePath) => {
+      const content = await fetchFileContent(owner, repo, filePath, branch);
+      return { filePath, content };
+    },
+    CONCURRENCY_LIMIT
+  );
+
+  const files = results
+    .filter((r): r is { filePath: string; content: string } => r.content !== null)
+    .map(r => ({ path: r.filePath, content: r.content }));
+  
+  const skippedFiles = results.filter(r => r.content === null).length;

1-6: Unused import: graphql from @octokit/graphql.

The graphql import is declared but never used in this file. Remove unused imports per coding guidelines.

-import { graphql } from "@octokit/graphql";
 import dotenv from "dotenv";
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f6dbc3 and a03a906.

📒 Files selected for processing (17)
  • apps/api/prisma/migrations/20251215163900_add_tutorial_model/migration.sql (1 hunks)
  • apps/api/prisma/schema.prisma (1 hunks)
  • apps/api/src/index.ts (1 hunks)
  • apps/api/src/routers/_app.ts (2 hunks)
  • apps/api/src/routers/tutorial.ts (1 hunks)
  • apps/api/src/services/github-crawler.service.ts (1 hunks)
  • apps/api/src/services/llm.service.ts (1 hunks)
  • apps/api/src/services/tutorial.service.ts (1 hunks)
  • apps/web/package.json (2 hunks)
  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx (1 hunks)
  • apps/web/src/app/globals.css (1 hunks)
  • apps/web/src/components/dashboard/Sidebar.tsx (2 hunks)
  • apps/web/src/components/tutorial/FileBrowser.tsx (1 hunks)
  • apps/web/src/components/tutorial/TutorialGenerator.tsx (1 hunks)
  • apps/web/src/components/tutorial/TutorialHistory.tsx (1 hunks)
  • apps/web/src/components/tutorial/TutorialViewer.tsx (1 hunks)
  • apps/web/src/components/tutorial/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (23)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/api/src/routers/_app.ts
  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/api/src/services/llm.service.ts
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/api/src/routers/tutorial.ts
  • apps/api/src/index.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
  • apps/web/src/components/tutorial/TutorialViewer.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/api/src/routers/_app.ts
  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/api/src/services/llm.service.ts
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/api/src/routers/tutorial.ts
  • apps/api/src/index.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
  • apps/web/src/components/tutorial/TutorialViewer.tsx
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/api/src/routers/_app.ts
  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/api/src/services/llm.service.ts
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/api/src/routers/tutorial.ts
  • apps/api/src/index.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
  • apps/web/src/components/tutorial/TutorialViewer.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/api/src/routers/_app.ts
  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/api/src/services/llm.service.ts
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/api/src/routers/tutorial.ts
  • apps/api/src/index.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
  • apps/web/src/components/tutorial/TutorialViewer.tsx
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/api/src/routers/_app.ts
  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/api/src/services/llm.service.ts
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/api/src/routers/tutorial.ts
  • apps/api/src/index.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
  • apps/web/src/components/tutorial/TutorialViewer.tsx
apps/api/src/**/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)

Files:

  • apps/api/src/routers/_app.ts
  • apps/api/src/services/llm.service.ts
  • apps/api/src/routers/tutorial.ts
  • apps/api/src/index.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid any type; use unknown for truly dynamic data and narrow with type guards

Files:

  • apps/api/src/routers/_app.ts
  • apps/api/src/services/llm.service.ts
  • apps/api/src/routers/tutorial.ts
  • apps/api/src/index.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

apps/web/src/**/*.{ts,tsx}: Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts
NEVER use hardcoded hex values (e.g., #5519f7) directly in components; ALWAYS reference colors from the design token system using Tailwind classes
Use semantic color names that describe purpose, not appearance
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobile p-4 (1rem) and desktop p-[60px]
Use appropriate border radius: small elements rounded-lg, medium rounded-xl, large rounded-2xl, buttons rounded-[16px]
Use animation durations: fast duration-100 (0.1s), normal duration-300 (0.3s), slow duration-600 (0.6s)

Files:

  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/web/src/components/tutorial/TutorialViewer.tsx
apps/web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Optimize images using next/image

apps/web/src/**/*.{tsx,ts}: Use Zustand for global state, located in src/store/
Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Optimize images using next/image
Memoize expensive computations
Define a type when defining const functions

Files:

  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/web/src/components/tutorial/TutorialViewer.tsx
apps/web/src/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/**/*.{tsx,ts,jsx,js}: Organize imports in order: React → third-party → local components → utils → types
Use absolute imports from @/ prefix when available
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables
Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags
Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)
Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Files:

  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/web/src/components/tutorial/TutorialViewer.tsx
apps/api/src/**/{services,models,database}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Encrypt sensitive data (passwords, tokens, API keys) before storing in database

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/{database,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Handle database connection failures gracefully with retry logic

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/{database,models,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Always use parameterized queries or ORM methods to prevent SQL injection

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/{services,database}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Wrap database transactions in try-catch blocks with proper rollback on failure

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/{services,repositories}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid N+1 queries; use eager loading or batch queries when fetching related data

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/{services,models}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Validate data against schema before database operations

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/{database,clients,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Use connection pooling for database and external service clients

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/{services,clients,handlers}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Implement timeouts for external API calls to prevent hanging requests

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
apps/api/src/**/{middleware,services,routes,controllers}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Log all critical operations (auth attempts, payment processing, data mutations)

Files:

  • apps/api/src/services/llm.service.ts
  • apps/api/src/services/tutorial.service.ts
  • apps/api/src/services/github-crawler.service.ts
**/*[A-Z]*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Use PascalCase for component file names (e.g., UserProfile.tsx)

Files:

  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/web/src/components/tutorial/TutorialViewer.tsx
apps/web/src/components/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts,jsx,js}: Never use hardcoded hex values directly in components; always reference colors from the design token system using Tailwind classes
Use semantic color names from the design token system that describe purpose, not appearance (e.g., bg-brand-purple, bg-surface-primary, text-text-primary)
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale for section padding: p-4 (1rem) on mobile, p-[60px] on desktop
Use rounded-lg (0.5rem) for small elements, rounded-xl (1rem) for medium elements, rounded-2xl (1.5rem) for large elements, and rounded-[16px] for buttons
Use duration-100 (0.1s) for fast transitions, duration-300 (0.3s) for normal transitions, and duration-600 (0.6s) for slow transitions
Use available custom animations: animate-accordion-down, animate-accordion-up, animate-scrollRight, animate-scrollLeft, animate-customspin, animate-spin-slow, animate-spin-slow-reverse, animate-marquee, animate-marquee-vertical, animate-shine
Prefer functional components with TypeScript
Extract reusable logic into custom hooks
Prefer controlled components over uncontrolled
Include proper aria labels for accessibility
Ensure keyboard navigation works in interactive components
Maintain proper heading hierarchy in page components
Provide alt text for images
Use 'class:' instead of the ternary operator in class tags whenever possible
Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)
Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts

Files:

  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/web/src/components/tutorial/TutorialViewer.tsx
apps/web/src/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts}: Use proper TypeScript types and avoid using any type
Use descriptive prop names and define prop types using TypeScript interfaces or types
Name components using PascalCase (e.g., UserProfile.tsx)

Files:

  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/tutorial/index.ts
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/web/src/components/tutorial/TutorialViewer.tsx
apps/api/src/{index,main,app,server}.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Validate required environment variables on startup; fail fast if missing

Files:

  • apps/api/src/index.ts
🧠 Learnings (13)
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,middleware}/*.{js,ts} : Implement rate limiting on all public endpoints to prevent abuse

Applied to files:

  • apps/api/src/routers/_app.ts
  • apps/api/src/index.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,types}/*.ts : Define strict TypeScript interfaces for request/response bodies

Applied to files:

  • apps/api/src/routers/_app.ts
  • apps/api/src/index.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,middleware}/*.{js,ts} : Use middleware for auth checks; never duplicate auth logic in route handlers

Applied to files:

  • apps/api/src/routers/_app.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Use zod or similar for runtime validation of incoming data

Applied to files:

  • apps/api/src/routers/_app.ts
  • apps/api/src/index.ts
📚 Learning: 2025-12-17T12:39:29.991Z
Learnt from: huamanraj
Repo: apsinghdev/opensox PR: 262
File: apps/web/src/components/checkout/CheckoutWrapper.tsx:5-5
Timestamp: 2025-12-17T12:39:29.991Z
Learning: In Next.js apps, avoid using redirect() from next/navigation inside Client Components during render. It should not be used in event handlers like onClick or onSubmit; for those cases, use useRouter().push() or router.replace() instead. This guidance applies to TSX files (React components) across the codebase; implement in client components to handle conditional redirects during render, and use router-based navigation in handlers.

Applied to files:

  • apps/web/src/app/(main)/dashboard/tutorial/page.tsx
  • apps/web/src/components/tutorial/TutorialHistory.tsx
  • apps/web/src/components/dashboard/Sidebar.tsx
  • apps/web/src/components/tutorial/TutorialGenerator.tsx
  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/web/src/components/tutorial/TutorialViewer.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Ensure keyboard navigation works in interactive components

Applied to files:

  • apps/web/src/components/tutorial/FileBrowser.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)

Applied to files:

  • apps/web/src/components/tutorial/FileBrowser.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Prefer functional components with TypeScript

Applied to files:

  • apps/web/src/components/tutorial/FileBrowser.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Include proper aria labels for accessibility

Applied to files:

  • apps/web/src/components/tutorial/FileBrowser.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Organize imports in order: React → third-party → local components → utils → types

Applied to files:

  • apps/web/src/components/tutorial/FileBrowser.tsx
  • apps/web/package.json
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Remove unused imports

Applied to files:

  • apps/web/package.json
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{logging,middleware,utils}/*.{js,ts} : Use structured logging (JSON format) for easier parsing and analysis

Applied to files:

  • apps/api/src/index.ts
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Maintain proper heading hierarchy

Applied to files:

  • apps/web/src/components/tutorial/TutorialViewer.tsx
🧬 Code graph analysis (7)
apps/api/src/routers/_app.ts (1)
apps/api/src/routers/tutorial.ts (1)
  • tutorialRouter (20-183)
apps/api/src/services/llm.service.ts (1)
apps/api/test-ddos.js (1)
  • response (28-28)
apps/web/src/components/tutorial/TutorialHistory.tsx (1)
apps/web/src/lib/trpc.ts (1)
  • trpc (4-4)
apps/web/src/components/tutorial/TutorialGenerator.tsx (4)
apps/web/src/lib/trpc.ts (1)
  • trpc (4-4)
apps/web/src/components/tutorial/FileBrowser.tsx (1)
  • FileBrowser (264-503)
apps/web/src/components/ui/input.tsx (1)
  • Input (22-22)
apps/web/src/providers/trpc-provider.tsx (1)
  • TRPCProvider (11-53)
apps/api/src/services/tutorial.service.ts (2)
apps/api/src/services/llm.service.ts (1)
  • callLLM (71-87)
apps/api/src/services/github-crawler.service.ts (3)
  • parseGitHubUrl (78-101)
  • crawlSelectedFiles (375-411)
  • crawlGitHubFiles (268-328)
apps/api/src/services/github-crawler.service.ts (1)
apps/api/test-ddos.js (1)
  • response (28-28)
apps/web/src/components/tutorial/TutorialViewer.tsx (1)
apps/web/src/lib/trpc.ts (1)
  • trpc (4-4)
🪛 ast-grep (0.40.0)
apps/api/src/services/github-crawler.service.ts

[warning] 47-47: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${regexPattern}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

apps/web/src/components/tutorial/TutorialViewer.tsx

[warning] 100-100: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/web/src/components/tutorial/TutorialViewer.tsx

[error] 101-101: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (19)
apps/web/src/components/dashboard/Sidebar.tsx (1)

53-58: LGTM!

The new tutorial route entry follows the established pattern for free routes with proper icon, label, and badge. The "AI" badge appropriately signals the feature's nature.

apps/web/package.json (1)

32-48: LGTM!

The markdown/syntax highlighting/diagram rendering dependencies are appropriate for the tutorial content features. The version selections align with current stable releases.

apps/web/src/app/globals.css (2)

99-152: Hardcoded hex values for Mermaid theming are acceptable here.

While the codebase generally avoids hardcoded hex values, Mermaid generates inline SVGs that require direct CSS overrides with !important. The color palette is cohesive with the dark theme. Consider documenting these color choices in a comment for maintainability.


154-193: LGTM!

Good use of Tailwind utility classes for syntax highlighting token colors, maintaining consistency with the design system approach.

apps/api/src/services/llm.service.ts (2)

43-54: LGTM on error handling.

Good error handling with informative error messages. The error logging captures API response details for debugging without exposing sensitive information like the API key.


71-91: LGTM on the service interface.

Clean exported API with sensible defaults. The caching logic is straightforward.

apps/web/src/components/tutorial/TutorialViewer.tsx (1)

195-227: LGTM on Mermaid initialization.

Good configuration of Mermaid with dark theme variables and sensible defaults for flowcharts and sequence diagrams. The startOnLoad: false is correct since rendering is handled manually.

apps/web/src/components/tutorial/index.ts (1)

1-3: LGTM! Clean barrel exports.

The index file correctly re-exports the tutorial components, enabling clean imports throughout the application.

apps/api/src/routers/tutorial.ts (1)

9-18: LGTM! Well-defined input validation.

The input schema uses zod validation effectively with appropriate constraints on maxAbstractions (3-15) and maxFiles (5-100) to prevent abuse.

Based on learnings: "Use zod or similar for runtime validation of incoming data."

apps/web/src/components/tutorial/FileBrowser.tsx (2)

37-84: LGTM on buildFileTree implementation.

The tree-building logic correctly handles path splitting, hierarchical insertion, and sorting (folders first, then alphabetical). The recursive sorting approach is clean.


86-113: LGTM on utility functions.

The formatSize and getFileColor helper functions are well-implemented with clear logic and appropriate fallbacks.

apps/web/src/components/tutorial/TutorialGenerator.tsx (3)

21-22: Use UPPER_SNAKE_CASE for constants per coding guidelines.

The constant is correctly named but could benefit from explicit typing.


60-89: LGTM on the file fetching workflow.

The handleFetchFiles function properly validates the GitHub URL, checks for existing tutorials before proceeding, and handles errors with appropriate state management.


154-216: LGTM on the existing tutorial modal implementation.

The modal properly displays existing tutorials with ownership indication and provides clear actions for viewing existing or generating new tutorials.

apps/api/src/services/tutorial.service.ts (3)

630-691: LGTM on the main generateTutorial orchestration.

The function properly coordinates the multi-step pipeline with clear logging, handles both user-selected and automatic file selection, and validates that files were found before proceeding.


550-625: LGTM on combineTutorial implementation.

The function correctly generates the Mermaid diagram, builds the index content, and assembles the chapter files with proper filename generation and content normalization.


89-185: Consider input sanitization for LLM prompts.

User-provided repository content is directly interpolated into LLM prompts. While the LLM itself handles this, consider that malicious code content could potentially influence LLM behavior (prompt injection). This is a defense-in-depth consideration.

apps/api/src/services/github-crawler.service.ts (2)

78-101: LGTM on parseGitHubUrl implementation.

The URL parsing correctly extracts owner, repo, and optional branch from various GitHub URL formats, with proper error handling for invalid URLs.


330-369: LGTM on listRepoFiles implementation.

The function correctly lists files without fetching content, applies default patterns for filtering, and returns structured data suitable for the file browser UI.

Comment on lines +1 to +21
-- CreateTable
CREATE TABLE "Tutorial" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"repoUrl" TEXT NOT NULL,
"projectName" TEXT NOT NULL,
"language" TEXT NOT NULL DEFAULT 'english',
"indexContent" TEXT NOT NULL,
"mermaidDiagram" TEXT NOT NULL,
"chapters" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Tutorial_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "Tutorial_userId_idx" ON "Tutorial"("userId");

-- CreateIndex
CREATE INDEX "Tutorial_repoUrl_idx" ON "Tutorial"("repoUrl");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Consider adding a foreign key constraint for userId.

The Tutorial table references userId but doesn't enforce referential integrity with a foreign key constraint. This could lead to orphaned tutorial records if users are deleted. Consider adding a foreign key constraint with an appropriate ON DELETE action (CASCADE or SET NULL).

🔎 Suggested fix

After line 15, add:

-- Add foreign key constraint
ALTER TABLE "Tutorial" ADD CONSTRAINT "Tutorial_userId_fkey" 
  FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE;

Also ensure the Prisma schema reflects this relationship with:

model Tutorial {
  // ... existing fields
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  // ... existing fields
  tutorials Tutorial[]
}
🤖 Prompt for AI Agents
In apps/api/prisma/migrations/20251215163900_add_tutorial_model/migration.sql
around lines 1 to 21, the "Tutorial" table defines userId but lacks a foreign
key constraint; add a constraint after the table definition to enforce
referential integrity, e.g. ALTER TABLE "Tutorial" ADD CONSTRAINT
"Tutorial_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE
CASCADE (or ON DELETE SET NULL if preferred), and update the Prisma schema so
Tutorial has user User @relation(fields: [userId], references: [id], onDelete:
Cascade) and User includes tutorials Tutorial[] to keep the DB and Prisma models
in sync.

Comment on lines +106 to +120
model Tutorial {
id String @id @default(cuid())
userId String
repoUrl String
projectName String
language String @default("english")
indexContent String @db.Text
mermaidDiagram String @db.Text
chapters Json // Array of {filename: string, content: string}
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([repoUrl])
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add Prisma relation between Tutorial and User models.

The Tutorial model references userId but doesn't define a Prisma relation to the User model. This prevents you from using Prisma's relation features and doesn't enforce referential integrity at the schema level.

🔎 Recommended changes

Update the Tutorial model:

 model Tutorial {
   id             String   @id @default(cuid())
   userId         String
   repoUrl        String
   projectName    String
   language       String   @default("english")
   indexContent   String   @db.Text
   mermaidDiagram String   @db.Text
   chapters       Json
   createdAt      DateTime @default(now())
   updatedAt      DateTime @updatedAt
+  user           User     @relation(fields: [userId], references: [id], onDelete: Cascade)

   @@index([userId])
   @@index([repoUrl])
 }

And add to the User model (around line 44):

 model User {
   id             String         @id @default(cuid())
   email          String         @unique
   firstName      String
   authMethod     String
   createdAt      DateTime       @default(now())
   lastLogin      DateTime       @updatedAt
   completedSteps Json?
   accounts       Account[]
   payments       Payment[]
   subscriptions  Subscription[]
+  tutorials      Tutorial[]
 }

After making these changes, run:

npx prisma migrate dev --name add_tutorial_user_relation
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
model Tutorial {
id String @id @default(cuid())
userId String
repoUrl String
projectName String
language String @default("english")
indexContent String @db.Text
mermaidDiagram String @db.Text
chapters Json // Array of {filename: string, content: string}
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([repoUrl])
}
model Tutorial {
id String @id @default(cuid())
userId String
repoUrl String
projectName String
language String @default("english")
indexContent String @db.Text
mermaidDiagram String @db.Text
chapters Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([repoUrl])
}
🤖 Prompt for AI Agents
In apps/api/prisma/schema.prisma around lines 106 to 120, the Tutorial model
declares userId but lacks a Prisma relation to the User model; add a relation by
keeping the userId scalar and adding a relation field (e.g., user User
@relation(fields: [userId], references: [id])) and ensure userId remains String,
then update the User model (around line ~44) to include a back-relation field
(e.g., tutorials Tutorial[]) so Prisma enforces referential integrity; after
editing the schema run npx prisma migrate dev --name add_tutorial_user_relation
to apply the migration.

import { projectRouter } from "./projects.js";
import { authRouter } from "./auth.js";
import { paymentRouter } from "./payment.js";
import { tutorialRouter } from "./tutorial.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine key files in the tutorial router area
find apps/api/src -name "*.ts" | grep -E "(trpc|router)" | head -20

Repository: apsinghdev/opensox

Length of output: 299


🏁 Script executed:

#!/bin/bash
# Look at the _app.ts file where the import is
cat apps/api/src/routers/_app.ts

Repository: apsinghdev/opensox

Length of output: 862


🏁 Script executed:

#!/bin/bash
# Look for tutorial router file
find apps/api -name "*tutorial*" -type f

Repository: apsinghdev/opensox

Length of output: 136


🏁 Script executed:

#!/bin/bash
# Search for protectedProcedure and publicProcedure definitions
rg -n "protectedProcedure|publicProcedure" apps/api/src --type ts -A 2

Repository: apsinghdev/opensox

Length of output: 6084


🏁 Script executed:

#!/bin/bash
# Examine the delete mutation and service implementation
sed -n '157,175p' apps/api/src/routers/tutorial.ts
cat apps/api/src/services/tutorial.service.ts | head -100

Repository: apsinghdev/opensox

Length of output: 2882


🏁 Script executed:

#!/bin/bash
# Check for rate limiting middleware/configuration
rg -n "rateLimit|middleware" apps/api/src --type ts | head -20

Repository: apsinghdev/opensox

Length of output: 624


🏁 Script executed:

#!/bin/bash
# Look for rate limiting or middleware configuration files
find apps/api -name "*rate*" -o -name "*middleware*" | head -20

Repository: apsinghdev/opensox

Length of output: 85


🏁 Script executed:

#!/bin/bash
# Check the rate limiting setup in index.ts
sed -n '40,100p' apps/api/src/index.ts

Repository: apsinghdev/opensox

Length of output: 1880


🏁 Script executed:

#!/bin/bash
# Check if there's endpoint-specific rate limiting for generate
rg -n "generate.*rate|rate.*generate" apps/api/src --type ts -i

Repository: apsinghdev/opensox

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look at the full trpc middleware setup and how rate limiting applies
sed -n '260,280p' apps/api/src/index.ts

Repository: apsinghdev/opensox

Length of output: 560


🏁 Script executed:

#!/bin/bash
# Check the generate endpoint implementation and if it has any internal protection
sed -n '112,155p' apps/api/src/routers/tutorial.ts

Repository: apsinghdev/opensox

Length of output: 1349


Secure tutorial endpoints with authentication and per-endpoint rate limiting.

All tutorial router endpoints use publicProcedure and fall back to "anonymous" user when unauthenticated. This creates security and data consistency issues:

  1. generate: Allows anonymous users to trigger resource-intensive LLM-powered tutorial generation without authentication
  2. delete/getUserTutorials: Use a shared "anonymous" userId, causing multiple unauthenticated users to share the same data scope—allowing unauthorized access to or deletion of others' tutorials
  3. Rate limiting: Global /trpc rate limit (100 req/15min) exists but is insufficient for the expensive generate endpoint

Refactor to:

  • Use protectedProcedure for all three endpoints (generate, delete, getUserTutorials)
  • Remove the "anonymous" fallback and require authentication
  • Consider adding endpoint-specific rate limiting for generate via middleware

Per the learnings: "Use middleware for auth checks; never duplicate auth logic in route handlers" and "Implement rate limiting on all public endpoints."

🤖 Prompt for AI Agents
In apps/api/src/routers/_app.ts around line 7, the tutorial router currently
exposes endpoints as publicProcedure and relies on an "anonymous" fallback;
update the router so generate, delete and getUserTutorials use
protectedProcedure (remove any anonymous user fallback and require
authentication), move all auth checks into middleware rather than inside
handlers, and add/attach an endpoint-specific rate-limit middleware to the
generate endpoint to throttle expensive LLM requests (e.g., per-user or per-IP
limits) while keeping global rate limits intact.

Comment on lines +39 to +66
checkExisting: publicProcedure
.input(z.object({ repoUrl: z.string() }))
.query(async ({ input, ctx }) => {
// @ts-ignore
const currentUserId = ctx.user?.id || "anonymous";

const tutorials = await prisma.tutorial.findMany({
where: {
repoUrl: input.repoUrl,
},
orderBy: { createdAt: "desc" },
select: {
id: true,
projectName: true,
language: true,
createdAt: true,
userId: true,
},
});

return {
exists: tutorials.length > 0,
tutorials: tutorials.map(t => ({
...t,
isOwnTutorial: t.userId === currentUserId,
})),
};
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove @ts-ignore and properly type the context user.

The @ts-ignore directive on line 42 suppresses TypeScript errors instead of fixing the underlying type issue. This hides potential runtime errors and makes the code harder to maintain.

Additionally, the "anonymous" fallback enables unauthenticated access which may not be the intended behavior for comparing tutorial ownership.

🔎 Recommended fix

Define proper types for the context:

// In your context.ts or types file
interface AuthenticatedContext {
  user: {
    id: string;
    email: string;
    // ... other user properties
  };
}

Then update the procedure:

   checkExisting: publicProcedure
     .input(z.object({ repoUrl: z.string() }))
     .query(async ({ input, ctx }) => {
-      // @ts-ignore
-      const currentUserId = ctx.user?.id || "anonymous";
+      const currentUserId = ctx.user?.id;

Consider whether this endpoint should require authentication or if anonymous access is intentional.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/api/src/routers/tutorial.ts around lines 39 to 66, remove the //
@ts-ignore and properly type ctx.user instead of falling back to the string
"anonymous": update the tRPC context types (or import the existing
Context/Session types) so ctx.user is typed as optional or required user object,
then either make this procedure require authentication (switch to
protectedProcedure or guard at the router level) so currentUserId comes from
ctx.user!.id, or if anonymous access is intended, set currentUserId to
undefined/null and adjust the isOwnTutorial comparison to handle absence of a
user; ensure you update imports and the context/type definitions rather than
suppressing the TypeScript error.

Comment on lines +90 to +107
getUserTutorials: publicProcedure.query(async ({ ctx }) => {
// @ts-ignore
const userId = ctx.user?.id || "anonymous";

const tutorials = await prisma.tutorial.findMany({
where: { userId },
orderBy: { createdAt: "desc" },
select: {
id: true,
projectName: true,
repoUrl: true,
language: true,
createdAt: true,
},
});

return tutorials;
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

getUserTutorials should require authentication.

This endpoint retrieves user-specific tutorials but falls back to "anonymous" when no user is authenticated. This could lead to:

  1. Anonymous users seeing each other's tutorials
  2. Misleading UI showing tutorials for unauthenticated users
  3. Type safety issues with the @ts-ignore directive

This endpoint should use a protectedProcedure instead of publicProcedure to enforce authentication.

🔎 Recommended fix
-  getUserTutorials: publicProcedure.query(async ({ ctx }) => {
-    // @ts-ignore
-    const userId = ctx.user?.id || "anonymous";
+  getUserTutorials: protectedProcedure.query(async ({ ctx }) => {
+    const userId = ctx.user.id;

     const tutorials = await prisma.tutorial.findMany({

Ensure protectedProcedure is imported from your trpc.ts file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getUserTutorials: publicProcedure.query(async ({ ctx }) => {
// @ts-ignore
const userId = ctx.user?.id || "anonymous";
const tutorials = await prisma.tutorial.findMany({
where: { userId },
orderBy: { createdAt: "desc" },
select: {
id: true,
projectName: true,
repoUrl: true,
language: true,
createdAt: true,
},
});
return tutorials;
}),
getUserTutorials: protectedProcedure.query(async ({ ctx }) => {
const userId = ctx.user.id;
const tutorials = await prisma.tutorial.findMany({
where: { userId },
orderBy: { createdAt: "desc" },
select: {
id: true,
projectName: true,
repoUrl: true,
language: true,
createdAt: true,
},
});
return tutorials;
}),
🤖 Prompt for AI Agents
In apps/api/src/routers/tutorial.ts around lines 90 to 107, the getUserTutorials
handler uses publicProcedure and falls back to "anonymous" with a @ts-ignore,
which allows unauthenticated access and risks leaking tutorials; change it to
use protectedProcedure (import it from your trpc setup), remove the @ts-ignore,
read userId from ctx.user.id (no fallback), and rely on the protectedProcedure
guard so you can safely query prisma.tutorial.findMany({ where: { userId }, ...
}) and return the results.

Comment on lines +151 to +246
return (
<div>
<div
className={`flex items-center gap-2 py-1.5 px-2 rounded-md cursor-pointer hover:bg-dash-hover transition-colors group ${
level > 0 ? "ml-4" : ""
}`}
style={{ paddingLeft: `${level * 16 + 8}px` }}
>
{/* Expand/Collapse for folders */}
{isFolder ? (
<button
onClick={(e) => {
e.stopPropagation();
onToggleExpand(node.path);
}}
className="p-0.5 hover:bg-dash-base rounded"
>
{isExpanded ? (
<ChevronDownIcon className="w-4 h-4 text-text-tertiary" />
) : (
<ChevronRightIcon className="w-4 h-4 text-text-tertiary" />
)}
</button>
) : (
<span className="w-5" />
)}

{/* Checkbox */}
<button
onClick={() => onToggle(node.path, node.type, node.children)}
className={`w-4 h-4 rounded border flex items-center justify-center transition-colors ${
selectionState === "full"
? "bg-brand-purple border-brand-purple"
: selectionState === "partial"
? "bg-brand-purple/50 border-brand-purple"
: "border-dash-border hover:border-brand-purple/50"
}`}
>
{selectionState === "full" && (
<CheckIcon className="w-3 h-3 text-white" />
)}
{selectionState === "partial" && (
<div className="w-2 h-0.5 bg-white rounded" />
)}
</button>

{/* Icon */}
{isFolder ? (
isExpanded ? (
<FolderOpenIcon className="w-4 h-4 text-yellow-400" />
) : (
<FolderIcon className="w-4 h-4 text-yellow-400" />
)
) : (
<DocumentIcon className={`w-4 h-4 ${getFileColor(node.name)}`} />
)}

{/* Name */}
<span
className={`flex-1 text-sm truncate ${
isSelected || selectionState !== "none"
? "text-text-primary"
: "text-text-secondary"
}`}
onClick={() => onToggle(node.path, node.type, node.children)}
>
{node.name}
</span>

{/* Size for files */}
{node.size !== undefined && (
<span className="text-xs text-text-tertiary">
{formatSize(node.size)}
</span>
)}
</div>

{/* Children */}
{isFolder && isExpanded && (
<div>
{node.children.map((child) => (
<TreeNodeItem
key={child.path}
node={child}
selectedPaths={selectedPaths}
onToggle={onToggle}
expandedPaths={expandedPaths}
onToggleExpand={onToggleExpand}
level={level + 1}
/>
))}
</div>
)}
</div>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add accessibility attributes to interactive elements.

The TreeNodeItem component has interactive elements (expand/collapse button, checkbox button, clickable name) that lack proper accessibility attributes. Per coding guidelines, interactive elements need aria-label, tabindex, and onKeyDown handlers.

Suggested improvements
         {isFolder ? (
           <button
             onClick={(e) => {
               e.stopPropagation();
               onToggleExpand(node.path);
             }}
+            onKeyDown={(e) => {
+              if (e.key === 'Enter' || e.key === ' ') {
+                e.stopPropagation();
+                onToggleExpand(node.path);
+              }
+            }}
+            aria-label={isExpanded ? `Collapse ${node.name}` : `Expand ${node.name}`}
+            aria-expanded={isExpanded}
             className="p-0.5 hover:bg-dash-base rounded"
           >
         {/* Checkbox */}
         <button
           onClick={() => onToggle(node.path, node.type, node.children)}
+          onKeyDown={(e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+              e.preventDefault();
+              onToggle(node.path, node.type, node.children);
+            }
+          }}
+          aria-label={`${selectionState === 'full' ? 'Deselect' : 'Select'} ${node.name}`}
+          aria-checked={selectionState === 'full' ? true : selectionState === 'partial' ? 'mixed' : false}
+          role="checkbox"
           className={`w-4 h-4 rounded border flex items-center justify-center transition-colors ${
         {/* Name */}
         <span
           className={`flex-1 text-sm truncate ${
             isSelected || selectionState !== "none"
               ? "text-text-primary"
               : "text-text-secondary"
           }`}
           onClick={() => onToggle(node.path, node.type, node.children)}
+          onKeyDown={(e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+              onToggle(node.path, node.type, node.children);
+            }
+          }}
+          tabIndex={0}
+          role="button"
+          aria-label={`Toggle selection for ${node.name}`}
         >
🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/FileBrowser.tsx around lines 151 to 246,
several interactive pieces (folder expand/collapse button, checkbox button, and
the clickable name span) are missing accessibility attributes and keyboard
handlers; add appropriate ARIA and keyboard support: give the expand/collapse
button an aria-label and aria-expanded reflecting isExpanded, ensure the
checkbox button has aria-label and aria-checked reflecting selectionState
("true"/"mixed"/"false"), make the name span keyboard-accessible by adding
tabindex={0}, role="button" (or convert to a real button) and an onKeyDown
handler that triggers the same action on Enter and Space; ensure all interactive
elements remain focusable and provide clear, contextual aria-label strings
(e.g., `Toggle folder <name>`, `Select <name>`) so screen readers get meaningful
information.

Comment on lines +399 to +404
<button
onClick={onCancel}
className="p-2 hover:bg-dash-hover rounded-lg transition-colors"
>
<XMarkIcon className="w-5 h-5 text-text-tertiary" />
</button>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add aria-label to close button.

The close button in the header lacks an accessible label for screen readers. Per coding guidelines, include proper aria labels for accessibility.

           <button
             onClick={onCancel}
             className="p-2 hover:bg-dash-hover rounded-lg transition-colors"
+            aria-label="Close file browser"
           >
             <XMarkIcon className="w-5 h-5 text-text-tertiary" />
           </button>
🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/FileBrowser.tsx around lines 399 to 404, the
close button lacks an accessible label; add an aria-label attribute to the
button (e.g., aria-label="Close" or more specific like "Close file browser") so
screen readers announce its purpose, ensure the label is descriptive and
localized if your app uses i18n, and keep the existing icon-only UI intact.

Comment on lines +59 to +62
const extractRepoName = (url: string) => {
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/);
return match ? `${match[1]}/${match[2]}` : url;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, examine the actual file and function
cat -n apps/web/src/components/tutorial/TutorialHistory.tsx | sed -n '55,70p'

Repository: apsinghdev/opensox

Length of output: 665


🏁 Script executed:

# Search for other GitHub URL parsing patterns
rg -n "github\.com" --type ts --type tsx -C 2

Repository: apsinghdev/opensox

Length of output: 89


🏁 Script executed:

# Test the regex against edge cases
node <<'EOF'
const extractRepoName = (url) => {
  const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/);
  return match ? `${match[1]}/${match[2]}` : url;
};

const testCases = [
  "https://github.com/owner/repo",
  "https://github.com/owner/repo/",
  "https://github.com/owner/repo.git",
  "https://github.com/owner/repo/tree/main",
  "https://github.com/owner/repo/pull/123",
  "github.com/owner/repo",
  "https://github.com/owner/repo.git/"
];

testCases.forEach(url => {
  console.log(`Input: ${url}`);
  console.log(`Output: ${extractRepoName(url)}`);
  console.log('---');
});
EOF

Repository: apsinghdev/opensox

Length of output: 510


🏁 Script executed:

# Search for other GitHub URL parsing using different patterns
rg "github" apps/web/src --type ts -i -A 3

Repository: apsinghdev/opensox

Length of output: 35307


🏁 Script executed:

# Search for extractRepoName usage
rg "extractRepoName" apps/web/src

Repository: apsinghdev/opensox

Length of output: 268


Fix regex to handle .git suffix in GitHub URLs.

The regex pattern /github\.com\/([^\/]+)\/([^\/]+)/ doesn't strip the .git extension, so URLs like https://github.com/owner/repo.git return owner/repo.git instead of owner/repo. Update the regex to:

const extractRepoName = (url: string) => {
  const match = url.match(/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/)?$/);
  return match ? `${match[1]}/${match[2]}` : url;
};

This handles trailing slashes, .git suffixes, and additional path segments consistently.

🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/TutorialHistory.tsx around lines 59 to 62,
the current regex doesn't strip a trailing ".git" or handle trailing
slashes/extra path segments; update the pattern to match owner and repo but
optionally discard ".git" and trailing slash or extra segments (e.g., use a
non-greedy capture for repo and an optional non-capturing group for ".git" and
optional slash/end) so the function returns "owner/repo" for inputs like
"https://github.com/owner/repo.git" or with a trailing slash.

Comment on lines +97 to +103
return (
<div
ref={containerRef}
className="my-6 flex justify-center overflow-x-auto bg-dash-base/50 rounded-lg p-4 border border-dash-border"
dangerouslySetInnerHTML={{ __html: svg }}
/>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

XSS risk with unsanitized SVG injection.

Static analysis correctly flags dangerouslySetInnerHTML here. While Mermaid generates SVGs internally, the output should be sanitized before injection to prevent potential XSS if malicious content is embedded in the diagram definition. The codebase already has dompurify as a dependency.

🔎 Proposed fix using DOMPurify
+import DOMPurify from "dompurify";
+
 function MermaidDiagram({ chart }: { chart: string }) {
   const containerRef = useRef<HTMLDivElement>(null);
   const [svg, setSvg] = useState<string>("");
   const [error, setError] = useState<string | null>(null);

   useEffect(() => {
     const renderChart = async () => {
       if (!chart.trim()) return;
       
       try {
         const id = `mermaid-${Math.random().toString(36).substring(7)}`;
         const { svg: renderedSvg } = await mermaid.render(id, chart);
-        setSvg(renderedSvg);
+        setSvg(DOMPurify.sanitize(renderedSvg, { USE_PROFILES: { svg: true } }));
         setError(null);
       } catch (err) {
         console.error("Mermaid render error:", err);
         setError("Failed to render diagram");
       }
     };

     renderChart();
   }, [chart]);

   // ... rest of component
   return (
     <div 
       ref={containerRef}
       className="my-6 flex justify-center overflow-x-auto bg-dash-base/50 rounded-lg p-4 border border-dash-border"
-      dangerouslySetInnerHTML={{ __html: svg }}
+      dangerouslySetInnerHTML={{ __html: svg }} // now sanitized
     />
   );
 }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ast-grep (0.40.0)

[warning] 100-100: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)

[error] 101-101: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/TutorialViewer.tsx around lines 97 to 103,
the SVG string is injected with dangerouslySetInnerHTML without sanitization;
import DOMPurify from 'dompurify' and sanitize the SVG before injection (e.g.
const safeSvg = DOMPurify.sanitize(svg, { SAFE_FOR_SVG: true }) or equivalent
options), then pass the sanitized string to dangerouslySetInnerHTML; ensure the
import is added, perform sanitization where svg is produced/updated (or memoize
it) and use the sanitized value in the returned div to eliminate the XSS risk.

Comment on lines +170 to +174
interface TutorialViewerProps {
tutorialId: string;
generatedTutorial?: TutorialData | null;
onBack: () => void;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Duplicate interface definition.

TutorialViewerProps is already defined at lines 59-63. Remove this duplicate.

🔎 Fix
-interface TutorialViewerProps {
-  tutorialId: string;
-  generatedTutorial?: TutorialData | null;
-  onBack: () => void;
-}
-
 export default function TutorialViewer({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface TutorialViewerProps {
tutorialId: string;
generatedTutorial?: TutorialData | null;
onBack: () => void;
}
export default function TutorialViewer({
🤖 Prompt for AI Agents
In apps/web/src/components/tutorial/TutorialViewer.tsx around lines 170 to 174,
there's a duplicate declaration of the TutorialViewerProps interface (it already
exists at lines 59-63); remove the duplicate declaration and ensure any uses in
the file refer to the existing interface, or if the two variants differ,
consolidate them into the single interface at the original location and update
imports/uses accordingly.

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