Imported from joaovitorps/devroast (
src/components/ui/AGENTS.md). Install upstream withnpx skills add joaovitorps/devroast --skill ui. Copyright stays with the author.
DevRoast - UI Components & Architecture Guide
DevRoast: An AI-powered code analysis tool that provides intelligent feedback on your code through witty "roasts". Built during NLW Operator 2026 by Rocketseat.
Project Overview
DevRoast is a Next.js application that analyzes user-submitted code and provides detailed feedback on:
- Code quality issues (critical, warning, good practices)
- Diff comparisons showing improvements
- Language-specific insights
- Leaderboard rankings based on code scores
Tech Stack
- Frontend: React 19 + Next.js 16 (App Router)
- Styling: Tailwind CSS 4 + CVA for component variants
- UI Primitives: Base-UI for accessible components
- Code Highlighting: Shiki for syntax highlighting
- Linting: Biomejs for code quality
- Language: TypeScript 5
Project Architecture
Directory Structure
src/
├── app/ # Next.js app router
│ ├── page.tsx # Home page (code submission)
│ ├── components/ # Components showcase
│ ├── layout.tsx # Root layout
│ └── globals.css # Global styles
├── components/
│ ├── ui/ # Reusable UI components
│ ├── home/ # Home page sections
│ └── navbar.tsx # Navigation
├── lib/
│ ├── utils.ts # Utility functions (cn() helper)
│ └── design-tokens.ts # Design system tokens
└── styles/ # Global stylesheets
Component Categories
Atomic Components (Simple, single-purpose)
button.tsx- Primary action buttons (3 variants: primary, secondary, link)toggle.tsx- Switch/checkbox componentscore-ring.tsx- Circular score visualizationcode-block.tsx- Server-side code display with syntax highlighting
Composite Components (Multiple sub-elements with shared context)
All follow single-file pattern with internal context management:
-
Card - Analysis card with header, title, description
- Context:
variant(critical | warning | good | needs_serious_help)
- Context:
-
Badge - Status indicator with dot and label
- Context:
variant(critical | warning | good | needs_serious_help)
- Context:
-
TableRow - Leaderboard row with rank, score, code, language
- Context:
scoreColor(accent-red | accent-amber | accent-green)
- Context:
-
DiffLine - Code diff line with type-specific prefix
- Context:
type(added | removed | context)
- Context:
-
CommentText - Inline comment with prefix
- Context:
size(xs | sm | base)
- Context:
Page Sections
HeroSection- Code input textareaActionsBar- Roast button and mode toggleStatsBar- Total roasted count and average scoreLeaderboardPreview- Top submissions preview
Global Design System
Colors (Tailwind Config)
- accent-green:
#10B981- Primary action - accent-red:
#DC2626- Critical/errors - accent-amber:
#F59E0B- Warnings - text-primary:
#FAFAFA- Main text - text-secondary:
#6B7280- Secondary text - text-tertiary:
#4B5563- Tertiary text - bg-page:
#0A0A0A- Page background - bg-surface:
#121212- Surface/card background - border-primary:
#2A2A2A- Default borders
Typography
- Font: JetBrains Mono (monospace everywhere)
- Sizes: xs (12px), sm (14px), base (16px), lg (18px), xl (20px), 2xl (24px)
Spacing Scale
- xs: 4px, sm: 8px, md: 16px, lg: 24px, xl: 40px
Border Radius
- Default:
0px(square, no rounding) - Pill:
999px(fully rounded)
Code Patterns & Conventions
1. Composite Component Pattern (Single-File Architecture)
All composite components are single files with 4 sections:
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
// ============================================================================
// CONTEXT
// ============================================================================
type ContextValue = "option1" | "option2" | "option3";
interface ComponentContextType {
value: ContextValue;
}
const ComponentContext = React.createContext<ComponentContextType | undefined>(undefined);
function useComponentContext(): ComponentContextType {
const context = React.useContext(ComponentContext);
if (!context) throw new Error("Must use within Component");
return context;
}
// ============================================================================
// ROOT COMPONENT
// ============================================================================
interface ComponentProps extends React.HTMLAttributes<HTMLDivElement> {
value?: ContextValue;
}
const Component = React.forwardRef<HTMLDivElement, ComponentProps>(
({ className, value = "option1", children, ...props }, ref) => (
<ComponentContext.Provider value={{ value }}>
<div className={cn(["flex", className])} ref={ref} {...props}>
{children}
</div>
</ComponentContext.Provider>
)
);
Component.displayName = "Component";
// ============================================================================
// SUB-COMPONENTS
// ============================================================================
const ComponentSub = React.forwardRef<HTMLDivElement, any>(
({ className, ...props }, ref) => {
const { value } = useComponentContext();
return <div className={cn(["font-mono", className])} ref={ref} {...props} />;
}
);
ComponentSub.displayName = "ComponentSub";
// ============================================================================
// EXPORTS
// ============================================================================
export { Component, ComponentSub };
2. Tailwind CSS Array Directives
Always use arrays, never string concatenation:
// ✅ CORRECT
className={cn(["flex", "gap-2", "px-4", "py-2"])}
// ❌ WRONG
className={cn("flex gap-2 px-4 py-2")}
className={`flex gap-2 ${customClass}`}
3. Canonical Tailwind Classes
Use shortest form:
shrink-0notflex-shrink-0grow-0notflex-grow-0basis-0notflex-basis-0
4. Color Mapping (Never Template Literals)
// ✅ CORRECT - Use object mapping
const colorClass = {
"accent-red": "text-accent-red",
"accent-amber": "text-accent-amber",
"accent-green": "text-accent-green",
}[variant];
// ❌ WRONG - Template literals don't work with Tailwind
className={`text-${variant}`}
5. Component Structure
All components must:
- Use
"use client"directive - Implement
React.forwardRef - Extend native HTML attributes (
React.HTMLAttributes<HTML*>) - Have
displayNamefor debugging - Export as named exports only
- Use
cn()for all className attributes
Development Guidelines
Adding a New Atomic Component
- Create file:
src/components/ui/component-name.tsx - Use CVA with array directives for variants
- Implement
forwardReffor ref handling - Add
displayNamefor debugging - Export as named export only
- Update
src/app/components/page.tsxshowcase
Adding a New Composite Component
- Create single file:
src/components/ui/component-name.tsx - Follow the 4-section pattern (CONTEXT → ROOT → SUB-COMPONENTS → EXPORTS)
- Use Context to propagate variant/type to sub-components
- All sub-components in same file
- Import all sub-components from single file
Styling Guidelines
- Extract colors from Tailwind config tokens
- Keep border-radius square (0px) by default
- Use monospace font everywhere
- Support dark mode with
dark:prefix - Test all variant combinations
Build & Deployment
# Development
npm run dev # Start dev server on :3000
# Production
npm run build # Build for production
npm start # Start production server
# Code Quality
npm run lint # Lint with Biomejs
npm run format # Format code
npm run check # Lint + format
Build Output
- Next.js with Turbopack compiler
- Static generation for pages
- Optimized for production
Key Decisions
- Single-File Composite Components: All related code in one place for easier maintenance and to fix behavior issues
- Array Directives for Tailwind: Better readability and diff clarity compared to string concatenation
- Context Pattern: Enables flexible composition while propagating shared state through portals
- No Default Exports: Consistent with ESM best practices
- Monospace Typography: Appropriate for a code analysis tool
Resources
Event Attribution
Built during NLW Operator 2026 - Rocketseat's flagship event for learning full-stack web development. Learn more at rocketseat.com.br