
Levera AI
An AI DSA mentor that teaches you how to think. Instead of handing over the optimal solution, Levera walks the full journey from brute force to better to optimal, naming the insight that unlocks each jump. Includes progressive hints, spaced repetition, and a LeetCode Chrome extension.
Timeline
7 weeks
Role
Full Stack Developer
Team
Solo
Status
CompletedTechnology Stack
Key Challenges
- Designing a prompt that returns three approaches instead of one
- Distinguishing new problems from follow-up questions
- Building a Manifest V3 extension with device-code auth
- Two-tier rate limiting across middleware and route handlers
- Streaming responses without breaking user scroll
- Spaced-repetition scheduling and due-date queries
- Turborepo monorepo with a shared web and extension pipeline
Key Learnings
- Next.js 16 App Router and React 19
- Vercel AI SDK v7 with multi-provider routing
- Prompt engineering for structured, staged output
- Manifest V3 extensions with Vite and CRXJS
- Better Auth with email OTP and Google OAuth
- Prisma 7 with the pg adapter on serverless Postgres
- Distributed rate limiting with Upstash Redis
- Fail-open design for non-critical dependencies
- Turborepo and pnpm workspaces
- Transactional email deliverability with SPF and DKIM
Levera: From Brute Force to Optimal
Overview
Levera is an AI mentor for data structures and algorithms, built around a single premise: the answer is not the interesting part, the gap between approaches is.
Most AI assistants hand you the optimal solution and move on. You get an answer, not an understanding, and the next problem is just as hard. Levera does the opposite. Every new problem returns a full progression, brute force then better then optimal, and each step carries its own complexity analysis plus a plain statement of why it beats the one before it.
The platform spans a Next.js web app and a Manifest V3 Chrome extension that reads the problem you are already on at LeetCode, all inside a Turborepo monorepo.
What Users Can Do
Learning
- Three approaches per problem: Every new problem returns brute force, better, and optimal, each with time and space complexity
- Named insights: Each step states the observation that unlocks the next one, so the reasoning is transferable
- Progressive hints: Stuck but do not want the answer? Reveal one clue at a time, with unlock state saved per problem per user
- Dry runs: A small input walked step by step through the algorithm
- Pattern naming: Every problem is tagged with the general pattern it belongs to
- Code review mode: Paste your own broken code and get what works, the exact line that breaks, and an input that proves it
Retention
- Spaced repetition: Save any solved problem to a notebook and Levera schedules it for review
- Rating-based intervals: Again is 1 day, Hard is 2, Good is 5, Easy is 10
- Due today: The problems page separates what is due now from what is upcoming
Workflow
- LeetCode extension: Get an optimal-approach breakdown without leaving the problem page
- Seven languages: C++, Python, Java, JavaScript, TypeScript, Go, and Rust, with a saved per-user preference
- Model switching: Choose between Groq and OpenAI models from the chat input
- Persistent history: Full chat history with search, pagination, and per-conversation delete
- Conversation-aware: Follow-ups like "explain that in Python" are answered conversationally instead of re-dumping the whole structure
Why I Built This
I solved the same problem three times in one month and got stuck in the exact same place every time. I was not learning, I was collecting answers.
- AI skips the reasoning: Assistants jump straight to the optimal solution, hiding the step that actually teaches
- The jump is the skill: Knowing that a hash map turns a nested loop into a single pass is the transferable insight, not the final code
- Answers do not stick: Without review, a solved problem is forgotten within weeks
- Context switching costs: Leaving LeetCode to ask an AI breaks focus every single time
- Hints are all or nothing: Existing tools either give nothing or give everything
Key Innovations
- Staged output: A prompt architecture that returns a three-approach progression with a stated reason for each improvement, not a single answer
- Intent routing: The system distinguishes a new problem from a follow-up from a code review and responds in the right shape for each
- Credential-free extension auth: Device-code pairing means the extension never touches a password
- Learning loop: Hints, saving, and spaced repetition turn one-off answers into retained knowledge
- Fail-open reliability: A Redis outage degrades quotas instead of taking chat down
Tech Stack
Frontend
- Next.js 16 - App Router, server components, route handlers
- React 19 - Latest React features
- TypeScript 5.9 - Full type safety
- Tailwind CSS 4 - Utility-first styling
- shadcn/ui + Base UI - Accessible component primitives
- Framer Motion - Animation and transitions
- Light and dark themes - Responsive down to 320px
AI
- Vercel AI SDK v7 - Streaming and provider abstraction
- Groq - GPT OSS 120B, GPT OSS 20B, Qwen3.6 27B
- OpenAI - GPT-4o on the premium tier
- Model registry - Central config with automatic fallback for unsupported models
Backend & Database
- PostgreSQL (Neon) - Serverless Postgres on the pooled endpoint
- Prisma 7 - Type-safe ORM using the pg adapter
- Better Auth - Email and password, email OTP, and Google OAuth
- Upstash Redis - Distributed rate limiting
- Brevo - Transactional email for OTP and password reset
- pino - Structured logging
- Zod - Request validation in every route handler
Extension
- Manifest V3 - Service worker background, content script, React popup
- Vite + CRXJS - Build tooling and hot reload
- Device-code pairing - Browser-approved sign-in flow
Tooling
- Turborepo - Monorepo pipeline
- pnpm workspaces - Package management
- Umami - Privacy-friendly analytics
- Vercel - Deployment
Architecture Highlights
Monorepo Structure
levera/
├── apps/
│ ├── web/ # Next.js 16 App Router
│ │ ├── app/api/ # Chat, auth, problems, extension routes
│ │ ├── lib/ai/ # Model registry, routing, system prompt
│ │ ├── lib/rateLimit.ts # Upstash limiters
│ │ ├── lib/review.ts # Spaced-repetition intervals
│ │ └── proxy.ts # Session gating + IP rate limiting
│ └── extension/ # Manifest V3 Chrome extension
│ ├── background/ # Service worker
│ ├── content/ # LeetCode scraper
│ └── popup/ # React popup UI
└── packages/ # Shared UI, ESLint, TypeScript configs
Request Flow
Browser / Extension
│
▼
proxy.ts ────────── IP rate limit, session gate, redirects
│
▼
Route handler ───── Session check → per-user rate limit → Zod validation
│
├──────────► Prisma / PostgreSQL (chats, problems, hint progress)
│
└──────────► Vercel AI SDK ──► Groq / OpenAI
│
▼
Streamed response
Two-Tier Rate Limiting
Auth endpoints are limited by IP in the middleware, because there is no user identity yet. Chat endpoints are limited per user inside the route handler, because identity only resolves after the session does.
| Scope | Limit | Keyed by | |-------|-------|----------| | Chat messages | 10 / minute | user | | Chat messages | 80 / day | user | | Premium models | 25 / day | user | | Auth endpoints | 5 / minute | IP | | Page requests | 120 / minute | IP |
Every limiter fails open. If Redis goes down, quotas loosen rather than chat going dark.
Extension Authentication
The extension requests a device code, the user approves it in the browser at /auth/extension, and the extension exchanges that code for a session token. Credentials are never entered into the extension itself, which keeps the trust boundary at the web app.
Technical Challenges & Solutions
Challenge 1: Getting Three Approaches Instead of One
Problem: Language models are heavily biased toward producing the best answer immediately. Asking for a brute force reliably produced something already half-optimized.
Solution: Restructured the system prompt to require an explicit progression where each stage must justify itself against the previous one. Making the comparison the required output, rather than the solutions themselves, forced the model to actually construct the naive version first.
Challenge 2: Telling a New Problem from a Follow-Up
Problem: Every message was being treated as a fresh problem, so "explain that in Python" returned an entire three-approach breakdown again.
Solution: Added intent classification ahead of generation. New problems get the full structured response, follow-ups get a conversational answer scoped to the existing context, and pasted code switches into review mode with a different output contract.
Challenge 3: Streaming That Fought the User's Scroll
Problem: Auto-scroll ran on every streamed token using smooth scrolling. Each token cancelled and restarted the previous animation, and scrolling up during generation dragged the view straight back down.
Solution: Replaced the naive "am I near the bottom" check with tracked scroll intent. Wheel and touch gestures disarm auto-scroll immediately, before the scroll position settles, so the gesture beats the next token instead of racing it. Streaming now uses instant scroll coalesced to one update per animation frame, with smooth scrolling reserved for discrete events like sending a message.
Challenge 4: Auth Flow Deadlock
Problem: Sign-up auto-created a session, but the middleware redirected any session holder away from the OTP verification page. The verification email sent correctly and the page was unreachable, so the code could never be entered.
Solution: Disabled auto sign-in at sign-up so the OTP becomes a real gate rather than a decorative step. This also surfaced a deeper issue: with auto sign-in the user was already authenticated before verifying, meaning the OTP verified nothing at all.
Challenge 5: Serverless Connection Exhaustion
Problem: Prisma on serverless functions opens a connection per invocation, which exhausts a Postgres instance under any real traffic.
Solution: Moved to the Prisma pg adapter against Neon's pooled endpoint, with a global client singleton in development to survive hot reload without leaking connections.
Challenge 6: Migrations Drifting from Production
Problem: A table created with db push during development existed in production but was absent from the migration history, so deploys would try to recreate it.
Solution: Wrote migrations defensively with CREATE TABLE IF NOT EXISTS and exception-guarded constraint blocks, making them idempotent and safe to replay against a database that has drifted.
Project Highlights
- Full-stack monorepo: Web app and browser extension sharing config and CI through Turborepo
- Multi-provider AI: Groq and OpenAI behind one interface with automatic fallback
- Three auth methods: Email and password, email OTP, and Google OAuth
- Seven output languages: With a persisted per-user preference
- Production hardening: Two-tier rate limiting, structured logging, Zod validation on every route
- Spaced repetition: A real retention loop, not just a chat log
- Browser extension: Manifest V3 with a credential-free pairing flow
- Open source: MIT licensed
Performance & Reliability
- Streaming responses: Tokens render as they arrive rather than blocking on the full answer
- Fail-open limiters: Non-critical dependencies degrade instead of causing outages
- Pooled connections: Serverless-safe database access
- Frame-coalesced scrolling: Streaming UI updates batched to one scroll per animation frame
- Model fallback: Any unsupported or unconfigured model routes to a working default
- Responsive down to 320px: Fully usable on small screens
Future Enhancements
- [ ] Interactive algorithm visualizations for BFS, DFS, sorting, and DP tables
- [ ] Step-through dry-run player with variable and pointer state
- [ ] Interview mode where the AI withholds the optimal solution and probes your reasoning
- [ ] Automatic edge-case generation
- [ ] Export solutions to Markdown, PDF, and shareable links
- [ ] Progress analytics and personalised learning paths
- [ ] Company-specific interview preparation tracks
- [ ] Voice explanations
Impact
This project demonstrates my ability to:
- Ship a complete product: Auth, database, production-grade rate limiting, email, analytics, and deployment
- Engineer prompts as architecture: Treating model output structure as a design problem, not a string
- Build across surfaces: A web app and a browser extension sharing one backend and one monorepo
- Design for failure: Fail-open limiters, model fallback, and idempotent migrations
- Debug production systems: Diagnosing auth deadlocks and streaming UI races from first principles
- Manage a monorepo: Turborepo pipelines across multiple apps and shared packages
Live Demo
Visit levera-ai.vercel.app to try the three-approach breakdown, progressive hints, and the spaced-repetition notebook.
Key Learnings
- Prompt architecture: Structuring output contracts is closer to API design than to writing instructions
- Intent routing: One endpoint serving several response shapes needs explicit classification, not heuristics
- Serverless databases: Connection pooling is not optional once you leave a long-lived server
- Auth is a state machine: Session creation timing and route guards must be reasoned about together, or they deadlock
- Streaming UI is hard: Anything that updates dozens of times per second will fight the user unless intent is tracked explicitly
- Fail-open vs fail-closed: Deciding which dependencies are allowed to take the product down is an architectural choice
- Extension security: Keeping the trust boundary at the web app avoids ever handling credentials in a less trusted context
Conclusion
Levera is built on the belief that the optimal solution is the least interesting part of a problem. What matters is the observation that got you there, and whether you can find it again next time.
By pairing a three-approach progression with progressive hints, spaced repetition, and a browser extension that meets you where you already practise, Levera turns AI from an answer machine into something closer to a mentor.