This document is the single source of truth for anyone iterating on Spark outside of Lovable (VS Code, WebStorm, Cursor, etc.). Follow these conventions to keep the codebase consistent and mergeable with the Lovable-managed branch.
- Quick Start
- MCP Context7 — Dependency Validation
- Tech Stack & Versions
- Project Structure
- Design System & Theming
- Component Architecture
- State Management & Data Flow
- Authentication & Backend
- Adding Content (Modules & Lessons)
- Animation & Motion
- Routing & Navigation
- Do's and Don'ts
- Testing
- Git & Lovable Sync
# Clone via GitHub (Lovable bi-directional sync is active)
git clone <YOUR_REPO_URL>
cd spark
# Install dependencies (Bun recommended, npm works too)
bun install # or npm install
# Start dev server
bun run dev # or npm run dev
# → http://localhost:8080The .env file is auto-generated by Lovable Cloud and must never be committed or edited manually. It provides:
| Variable | Purpose |
|---|---|
VITE_SUPABASE_URL |
Backend API URL |
VITE_SUPABASE_PUBLISHABLE_KEY |
Public anon key |
VITE_SUPABASE_PROJECT_ID |
Project identifier |
If developing locally, copy .env from the Lovable project settings or create your own pointing to a dev backend.
We use the Context7 MCP server to validate that every dependency we install matches the correct version and API surface. Before adding or upgrading any package:
- Query Context7 for the library's latest stable API and version constraints.
- Verify compatibility with our pinned versions (see Tech Stack).
- Never blindly upgrade — check for breaking changes against Context7's documentation index.
# In your MCP-enabled IDE (Cursor, etc.)
# 1. Ask Context7: "What is the latest stable API for framer-motion v12?"
# 2. Cross-reference with our current usage in src/components/spark/
# 3. Only then: bun add framer-motion@^12.x
| Package | Constraint | Reason |
|---|---|---|
react |
^18.3.x |
React 19 not yet adopted |
react-router-dom |
^6.x |
v7 has breaking loader/action API |
framer-motion |
^12.x |
Heavy use of motion component API |
@supabase/supabase-js |
^2.x |
v3 not yet released |
tailwindcss |
^3.4.x |
v4 has config migration |
lucide-react |
^0.462.x |
Icon set pinned for consistency |
| Layer | Technology | Config File |
|---|---|---|
| Framework | React 18 + TypeScript | tsconfig.app.json |
| Build | Vite 5 | vite.config.ts |
| Styling | Tailwind CSS 3 + shadcn/ui | tailwind.config.ts, src/index.css |
| Animations | Framer Motion 12 | — |
| Routing | React Router v6 (BrowserRouter) | src/App.tsx |
| State / Server | TanStack React Query v5 | src/App.tsx |
| Backend | Supabase (via Lovable Cloud) | supabase/config.toml |
| Forms | React Hook Form + Zod | — |
| Charts | Recharts | — |
| Icons | Lucide React | — |
| UI Primitives | shadcn/ui (Radix-based) | components.json |
src/
├── assets/ # AI-generated illustrations (import as ES6 modules)
├── components/
│ ├── spark/ # App-specific components (13 files)
│ │ ├── BottomNav.tsx # Mobile tab bar
│ │ ├── DashboardOrbitingWorlds.tsx
│ │ ├── Illustrations.tsx # SVG illustration renderer
│ │ ├── IslandVisuals.tsx # Progress island graphics
│ │ ├── ModuleCard.tsx # Module list card
│ │ ├── ModuleGraphic.tsx # Module hero graphic
│ │ ├── ModuleIcon.tsx # Emoji/icon wrapper
│ │ ├── OrbitingWorlds.tsx # Animated orbiting spheres
│ │ ├── SparkTypography.tsx # Heading/body text components
│ │ ├── Starfield.tsx # Background particle effect
│ │ ├── StatsBar.tsx # Points/streak bar
│ │ ├── WelcomeAnimation.tsx # Post-login welcome screen
│ │ └── WorldTransition.tsx # Page transition overlay
│ └── ui/ # shadcn/ui primitives (DO NOT customise inline)
├── contexts/
│ └── TransitionContext.tsx # Page transition state
├── data/
│ └── modules.ts # All 13 modules + lessons + quiz content
├── hooks/
│ ├── useAuth.ts # Auth state + sign in/up/out
│ ├── useProgress.ts # Legacy local-storage progress (deprecated)
│ ├── useSupabaseProgress.ts # Cloud-synced progress (primary)
│ └── use-mobile.tsx # Viewport detection
├── integrations/
│ └── supabase/
│ ├── client.ts # ⚠️ AUTO-GENERATED — never edit
│ └── types.ts # ⚠️ AUTO-GENERATED — never edit
├── pages/ # Route-level components
│ ├── Landing.tsx # Public landing page
│ ├── Auth.tsx # Login / signup forms
│ ├── ResetPassword.tsx
│ ├── Onboarding.tsx # Topic selection flow
│ ├── Dashboard.tsx # Main hub (orbiting worlds)
│ ├── Explore.tsx # Browse all modules
│ ├── ModuleView.tsx # Module detail + lesson list
│ ├── LessonView.tsx # Content/quiz/celebration screens
│ ├── ProgressIsland.tsx # Gamified progress map
│ ├── Profile.tsx # User settings + stats
│ └── NotFound.tsx
├── types/
│ └── spark.ts # Core domain types (Module, Lesson, UserProgress)
└── index.css # 🎨 Design tokens (HSL variables)
| File | Reason |
|---|---|
src/integrations/supabase/client.ts |
Auto-generated by Lovable Cloud |
src/integrations/supabase/types.ts |
Auto-generated from DB schema |
supabase/config.toml |
Managed by Lovable Cloud |
.env |
Auto-generated environment config |
All colours are defined as HSL values in src/index.css under :root. Never use raw hex/rgb values in components.
/* Core semantic tokens */
--background: 218 58% 10%; /* Deep navy */
--foreground: 210 40% 98%; /* Near-white */
--primary: 187 100% 50%; /* Cyan accent */
--secondary: 168 80% 50%; /* Teal */
--accent: 265 70% 65%; /* Purple */
--muted: 215 30% 20%; /* Subdued panels */
/* Module colour palette */
--cyan: 187 100% 55%;
--teal: 168 80% 50%;
--purple: 265 70% 65%;
--orange: 25 95% 60%;
--pink: 320 90% 65%;
--gold: 45 100% 60%;
/* Glass morphism */
--glass-bg: 215 45% 14%;
--glass-border: 215 30% 28%;// ✅ Correct — use Tailwind semantic classes
<div className="bg-background text-foreground border-border" />
<button className="bg-primary text-primary-foreground" />
// ❌ Wrong — never hardcode colours
<div className="bg-[#0a1628] text-white" />| Role | Font | Tailwind Class |
|---|---|---|
| Body | Inter | font-sans (default) |
| Display / Headings | Space Grotesk | font-display |
- Add new HSL variable to
:rootinsrc/index.css - Map it in
tailwind.config.ts→theme.extend.colors - Use the Tailwind class in components — never reference the CSS variable directly
| Type | Convention | Example |
|---|---|---|
| Page components | PascalCase, in src/pages/ |
Dashboard.tsx |
| Spark components | PascalCase, in src/components/spark/ |
ModuleCard.tsx |
| UI primitives | lowercase, in src/components/ui/ |
button.tsx |
| Hooks | camelCase with use prefix |
useSupabaseProgress.ts |
| Types | PascalCase interfaces | Module, Lesson, UserProgress |
- Small, focused components — one responsibility per file
- Props over context for data that flows 1-2 levels
- Framer Motion for all animations (no CSS transitions for complex motion)
- shadcn/ui as the base for all form controls, dialogs, toasts — extend via variants, don't fork
- No inline styles — Tailwind classes only, using design tokens
// src/components/spark/NewFeature.tsx
import { motion } from "framer-motion";
interface NewFeatureProps {
title: string;
isActive: boolean;
}
export function NewFeature({ title, isActive }: NewFeatureProps) {
return (
<motion.div
className="bg-card rounded-lg p-4 border border-border"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<h3 className="font-display text-lg text-foreground">{title}</h3>
</motion.div>
);
}App.tsx
├── useAuth() → User | null
├── useSupabaseProgress() → UserProgress + mutation functions
│ ├── completeLesson()
│ ├── completeModule()
│ ├── setOnboardingComplete()
│ ├── setCurrentPosition()
│ └── resetProgress()
└── Props cascade to page components
- Auth state: Managed by
useAuth()hook — listens to SupabaseonAuthStateChange - Progress state:
useSupabaseProgress(user)— reads/writes touser_progressandprofilestables - Server state: TanStack React Query
QueryClientwraps the app (for future API calls) - Transition state:
TransitionContextmanages page-to-page world transitions
- Add column via Supabase migration (run in Lovable or use
supabase migration new) - Update the
UserProgresstype insrc/types/spark.ts - Update read/write logic in
useSupabaseProgress.ts - The
types.tsfile will auto-regenerate — never edit it manually
Landing → /auth (signup/login) → Email verification → /onboarding → /dashboard
- Email + password auth via Supabase Auth
- Email verification is required (no auto-confirm)
- Password reset via
/reset-passwordroute - Session persisted in
localStoragewith auto-refresh
| Table | Purpose | RLS |
|---|---|---|
profiles |
Display name, avatar URL | Users read/write own row |
user_progress |
Lessons, modules, streaks, points | Users read/write own row |
// Always import from the integration path
import { supabase } from "@/integrations/supabase/client";All content lives in src/data/modules.ts. Each module follows the Module type from src/types/spark.ts.
Each module must use one of the 5 defined colours: "cyan" | "teal" | "purple" | "orange" | "pink"
| Type | Required Fields | Description |
|---|---|---|
content |
title, text, illustration |
Teaching screen |
quiz |
question, options, correctIndex, points |
Multiple-choice |
celebration |
title, text, points |
Completion reward |
See the illustration union type in src/types/spark.ts for the full list (30+ options including robot-happy, brain, gear, shield, atom, etc.).
{
id: "unique_snake_case_id",
title: "Module Title",
description: "Short tagline",
icon: "🔬", // Emoji
color: "teal", // One of the 5 palette colours
category: "Category Name",
lessons: [
{
id: "unique_lesson_id",
title: "Lesson Title",
duration: "3 min",
screens: [
{ type: "content", title: "...", text: "...", illustration: "brain" },
{ type: "quiz", question: "...", options: [...], correctIndex: 0, points: 10 },
{ type: "celebration", title: "...", text: "...", points: 10 },
],
},
],
}All animations use Framer Motion v12. Use the motion component API.
| Animation | Pattern |
|---|---|
| Page entry | initial={{ opacity: 0, y: 20 }} → animate={{ opacity: 1, y: 0 }} |
| Staggered lists | transition={{ delay: index * 0.1 }} |
| Exit animations | Use AnimatePresence with exit prop |
| World transitions | Managed by WorldTransition.tsx + TransitionContext |
| Background effects | Starfield.tsx, OrbitingWorlds.tsx (CSS + Framer hybrid) |
- Use
will-changesparingly - Prefer
transformandopacityanimations (GPU-accelerated) - Large particle effects (Starfield) should use
requestAnimationFrame, not Framer
| Path | Component | Auth Required |
|---|---|---|
/ |
Landing (public) / Dashboard (authed) |
Conditional |
/auth |
Auth |
No |
/reset-password |
ResetPassword |
No |
/explore |
Explore |
Yes |
/progress |
ProgressIsland |
Yes |
/profile |
Profile |
Yes |
/module/:moduleId |
ModuleView |
Yes |
/lesson/:moduleId/:lessonId |
LessonView |
Yes |
Mobile: BottomNav.tsx — fixed bottom tab bar (Home, Explore, Progress, Profile)
- Use semantic design tokens (
bg-card,text-muted-foreground, etc.) - Import assets as ES6 modules:
import img from "@/assets/image.png" - Use
font-displayfor headings,font-sansfor body - Keep components under 200 lines — extract sub-components
- Write all new animations with Framer Motion
- Query Context7 before adding/upgrading any dependency
- Use the
@/path alias for all imports
- Edit auto-generated files (
client.ts,types.ts,config.toml,.env) - Use raw hex/rgb colours — always HSL via CSS variables
- Install React 19 or React Router v7 (breaking changes)
- Use
localStoragefor progress (deprecated — useuseSupabaseProgress) - Create new Supabase client instances — import from
@/integrations/supabase/client - Skip email verification on auth (no auto-confirm)
- Use CSS transitions for complex motion — use Framer Motion
# Run all tests
bun run test
# Watch mode
bun run test:watchTests use Vitest + Testing Library. Test files go in src/test/ or co-located as *.test.ts(x).
Lovable maintains bidirectional sync with GitHub:
- Pushes from Lovable → GitHub (automatic)
- Pushes from GitHub → Lovable (automatic)
main— production branch, synced with Lovable- Feature branches — create locally, merge via PR to
main - Lovable picks up
mainchanges automatically
Use clear, descriptive commits. Lovable auto-generates commits for its changes, so keep yours distinguishable:
feat: add quantum computing module content
fix: correct streak calculation for timezone edge case
style: update module card hover animation
Last updated: March 2026