From c9643d898ae453431ae397302be4cd46f2cd5f89 Mon Sep 17 00:00:00 2001
From: srfwb <264158739+srfwb@users.noreply.github.com>
Date: Sun, 26 Apr 2026 20:35:22 +0200
Subject: [PATCH 01/15] docs(spec): lessons and challenges system design
---
.gitignore | 1 +
.../specs/2026-04-26-lessons-system-design.md | 298 ++++++++++++++++++
2 files changed, 299 insertions(+)
create mode 100644 docs/superpowers/specs/2026-04-26-lessons-system-design.md
diff --git a/.gitignore b/.gitignore
index 4e69aa4..1436f73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ dist-ssr
.claude.local.md
.claude-*/
CLAUDE.md
+.superpowers/
diff --git a/docs/superpowers/specs/2026-04-26-lessons-system-design.md b/docs/superpowers/specs/2026-04-26-lessons-system-design.md
new file mode 100644
index 0000000..e75ac78
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-26-lessons-system-design.md
@@ -0,0 +1,298 @@
+# Lessons & Challenges System — Design Spec
+
+## Context
+
+WeCode is a desktop IDE that teaches HTML and CSS. The app currently has two views: Home (project picker) and IDE (editor + preview). This spec adds a third pillar: **structured learning** through guided lessons and free-form challenges, both reusing the existing IDE shell.
+
+The system ships as milestone 3. Scope for v1: the full engine (navigation, validation, progression, dock UI) plus **1 lesson + 1 challenge** as proof of concept.
+
+## Architecture
+
+### View routing
+
+```
+useIdeStore.view: "home" | "ide" | "lesson"
+```
+
+- `"home"` — Home shell with rail-routed sub-views
+- `"ide"` — Free project editing (unchanged)
+- `"lesson"` — IDE shell wrapped in a `LessonProvider` context
+
+### Home sub-views (rail as router)
+
+The Home rail gains clickable items that swap the main content zone:
+
+```
+useHomeStore.tab: "accueil" | "lessons" | "challenges"
+```
+
+| Rail item | Tab value | What the main zone shows |
+|-----------|-----------|--------------------------|
+| Accueil | `"accueil"` | Current home content (search, continue card, recent projects, templates) |
+| Leçons | `"lessons"` | Grid of all lessons with progress indicators |
+| Challenges | `"challenges"` | Grid of all challenges with completion status |
+
+Items not yet wired (Projets, Cheatsheets, Settings) remain visible but disabled.
+
+The rail shows a progress counter next to Leçons: `3 / 12` (completed / total).
+
+### LessonProvider (React Context)
+
+When `view === "lesson"`, `App.tsx` renders:
+
+```tsx
+
+
+
+```
+
+The context exposes:
+
+```ts
+interface LessonContextValue {
+ lesson: LessonData;
+ checkpoints: CheckpointState[]; // { id, status: "todo"|"active"|"done" }
+ currentStepIndex: number;
+ fileOpsLocked: boolean; // true for lessons, false for challenges
+ progress: number; // 0–1 ratio
+ validateNow: () => void;
+ exitLesson: () => void;
+}
+```
+
+Components that adapt their behavior in lesson mode:
+- **FileTree** — hides create/delete/rename buttons when `fileOpsLocked`
+- **Toolbar** — brand click calls `exitLesson()` instead of `setView("home")`
+- **LessonDock** — reads `lesson`, `checkpoints`, `currentStepIndex` from context
+- **StatusBar** — shows checkpoint progress instead of version chip
+
+When the context is absent (free project mode), all components behave as today.
+
+### Stores
+
+**`useLessonStore`** (new Zustand store):
+- `activeLessonId: string | null`
+- `checkpointStates: Record`
+- `startLesson(id): void` — hydrates VFS with starter files, sets view to `"lesson"`
+- `completeCheckpoint(id): void`
+- `resetLesson(id): void`
+
+**`useHomeStore`** (new Zustand store):
+- `tab: "accueil" | "lessons" | "challenges"`
+- `setTab(tab): void`
+
+**Global progression** — persisted via `tauri-plugin-store` as `wecode.progress.json`:
+```ts
+interface ProgressStore {
+ version: 1;
+ completed: Record; // lessonId → timestamp
+ checkpoints: Record; // lessonId → completed checkpoint ids
+}
+```
+
+## Lesson data format
+
+Each lesson/challenge is a JSON file in `src/lessons/data/`:
+
+```ts
+interface LessonData {
+ id: string;
+ type: "lesson" | "challenge";
+ title: string;
+ description: string;
+ difficulty: "débutant" | "intermédiaire" | "avancé";
+ estimatedMinutes: number;
+ tags: string[];
+ allowFileOps: boolean; // false for lessons, true for challenges
+ starterFiles: Record; // VFS path → content
+ steps: LessonStep[];
+}
+
+interface LessonStep {
+ heading: string;
+ paragraphs: LessonParagraph[];
+ checkpoints: CheckpointDef[];
+}
+
+interface LessonParagraph {
+ kind: "text" | "code";
+ content: string; // text may contain `backtick` for inline code
+}
+
+interface CheckpointDef {
+ id: string;
+ label: string;
+ rule: ValidationRule;
+}
+```
+
+## Validation engine
+
+### Rule types
+
+| Type | Fields | What it checks |
+|------|--------|----------------|
+| `element-exists` | `selector`, `file` | `querySelector(selector)` returns ≥1 match |
+| `element-count` | `selector`, `file`, `min?`, `max?` | Match count within range |
+| `element-text` | `selector`, `file`, `text`, `match` | Element text content matches (`contains`, `exact`, `regex`, `not-empty`) |
+| `attribute-exists` | `selector`, `file`, `attribute` | Element has the attribute |
+| `attribute-value` | `selector`, `file`, `attribute`, `value?`, `match` | Attribute value check |
+| `attribute-count` | `selector`, `file`, `minAttributes` | Element has ≥N attributes |
+| `css-property` | `selector`, `file`, `property`, `match` | CSS property exists on selector |
+| `css-property-value` | `selector`, `file`, `property`, `value`, `match` | CSS property value check (`exact`, `contains`, `gte`, `lte`) |
+| `file-contains` | `file`, `text` | Raw source contains string |
+| `file-not-contains` | `file`, `text` | Raw source does NOT contain string |
+| `file-regex` | `file`, `pattern` | Raw source matches regex |
+| `nesting` | `parent`, `child`, `direct?`, `file` | Child is descendant (or direct child) of parent |
+| `element-order` | `selectors[]`, `within`, `file` | Elements appear in document order |
+| `sibling` | `first`, `then`, `file` | Two elements are adjacent siblings |
+| `indent-style` | `file`, `style` (`spaces`\|`tabs`), `size?` | Source follows indentation convention |
+| `composite` | `operator` (`and`\|`or`), `rules[]` | Combines multiple rules |
+
+### Match modes
+
+`exact`, `contains`, `starts-with`, `ends-with`, `regex`, `not-empty`, `exists`, `gte`, `lte`.
+
+### Execution
+
+- **Trigger**: VFS `change` event, debounced 300 ms.
+- **Method**: `DOMParser` parses the HTML source into a DOM. `querySelector` / `querySelectorAll` run the selectors. CSS rules are checked by parsing `