| name | quantum-ds |
| description | Règles et conventions du Quantum Design System pour l'ACADEMY Rust Learning Platform (React/Vite/TypeScript). Stack front-end, composants, state management, WebLLM, exécution WASM. |
| user-invocable | false |
Quantum Design System — Rust ACADEMY Platform
Plateforme d'apprentissage Rust gamifiée, 100% front-end statique (React/Vite/TypeScript + WebAssembly).
STACK TECHNIQUE
| Technologie | Usage |
|---|
| React 19 | UI components, hooks, Suspense |
| Vite 6 | Build tool, HMR, static export |
| TypeScript 5 | Typage strict, pas de any |
| React Router 7 | Routing déclaratif |
| CodeMirror 6 | Éditeur de code Rust embarqué |
| WebLLM (MLC) | LLM local dans le navigateur (Gemma 2B / Qwen2.5-Coder) |
| WASM Runtime | Exécution Rust sandboxée côté client |
RÈGLE ABSOLUE
Zéro backend. Tout tourne côté client : compilation WASM, inférence LLM, persistance IndexedDB.
Zéro appel réseau nécessaire après le chargement initial (PWA offline-first).
Le LLM est optionnel : si WebGPU absent, l'app fonctionne sans IA.
Architecture des dossiers
src/
├── app/ # App root, router, providers
│ ├── router.tsx
│ └── providers.tsx
├── features/ # Feature modules (kata, skill-tree, dashboard)
│ ├── kata-editor/ # Éditeur, exécution, tests
│ ├── skill-tree/ # Arbre de compétences D3.js
│ ├── mentor/ # Chat LLM, code review, hints
│ ├── profile/ # Dashboard, badges, XP
│ └── landing/ # Page d'accueil
├── shared/ # Composants réutilisables
│ ├── ui/ # Design system components
│ ├── layout/ # Header, sidebar, shell
│ └── lib/ # Hooks, utils, types
├── data/ # Katas JSON, progression IndexedDB
├── wasm/ # Runtime WASM bridge
└── llm/ # WebLLM integration
Composants du Design System
Tous les composants UI sont dans src/shared/ui/, exportés depuis un barrel index.ts.
Principes
- Composants atomiques : Button, Input, Badge, Modal, ProgressBar
- Composés : FormField (label + input + caption), Card (header + body + footer)
- headless : hooks réutilisables (
useToggle, useDebounce, useKeyboardShortcut)
- Pas de librairie UI externe (sauf CodeMirror pour l'éditeur). Tout est custom, léger, tailwindé.
Palette (CSS variables tailwind)
:root {
--color-rust: #CE422B;
--color-rust-dark: #8B2C1A;
--color-rust-light:#FDE8E4;
--color-surface: #FFFFFF;
--color-bg: #F8F9FA;
--color-border: #E2E8F0;
--color-text: #1A1A2E;
--color-muted: #64748B;
--color-success: #10B981;
--color-warning: #F59E0B;
--color-error: #EF4444;
--color-accent: #6366F1;
}
Composants disponibles
| Composant | Props | Usage |
|---|
<Button> | `variant: primary | secondary |
<CodeBlock> | code, language, showLineNumbers | Affichage de code Rust |
<ProgressBar> | value, max, color, animated | XP bar, quêtes |
<Badge> | `variant: info | success |
<Modal> | open, onClose, title, size | Dialogues |
<Tooltip> | content, position | Infobulles |
<SkillNode> | skill: SkillDef, `state: locked | unlocked |
<Toast> | message, type, duration | Notifications (badges, level up) |
<Editor> | value, onChange, errors[], readOnly | CodeMirror wrapper Rust |
<MemoryView> | frames: StackFrame[] | Visualisation stack/heap |
<ChatMessage> | `role: user | assistant, content, code?` |
State Management
Pas de librairie externe (Redux/Zustand). On utilise React 19 hooks + Context :
<QueryClientProvider>
<RouterProvider> // React Router
<ProgressProvider> // XP, niveaux, streak (IndexedDB)
<MentorProvider> // WebLLM state (loaded, ready, generating)
<EditorProvider> // Code actuel dans l'éditeur
</EditorProvider>
</MentorProvider>
</ProgressProvider>
</RouterProvider>
</QueryClientProvider>
Hooks clés
useProgress()
useMentor()
useKata(id)
useCode()
useWasm()
useStorage<T>()
Persistance
localStorage → préférences UI (thème, taille police)
IndexedDB → progression (XP, badges, streak, code sauvegardé, modèle LLM)
Patterns WebLLM
const [engine, setEngine] = useState<MLCEngine | null>(null);
const [progress, setProgress] = useState(0);
useEffect(() => {
const init = async () => {
const mlc = await CreateMLCEngine("Qwen2.5-Coder-1.5B-Q4_1", {
initProgressCallback: (p: number) => setProgress(p),
});
setEngine(mlc);
};
init();
}, []);
const chat = async (messages: ChatMessage[]) => {
const reply = await engine.chat.completions.create({
messages: [
{ role: "system", content: SYSTEM_PROMPT_MENTOR },
...messages.map(m => ({
role: m.role as "user" | "assistant",
content: buildContextPrompt(m.content, currentKata, currentCode),
})),
],
stream: true,
});
for await (const chunk of reply) {
setStreamedText(prev => prev + chunk.choices[0]?.delta?.content || "");
}
};
Patterns CodeMirror + Rust
import { EditorView, basicSetup } from "codemirror";
import { rust } from "@codemirror/lang-rust";
import { lintGutter } from "@codemirror/lint";
import { oneDark } from "@codemirror/theme-one-dark";
const editor = new EditorView({
doc: initialCode,
extensions: [
basicSetup,
rust(),
lintGutter(),
oneDark,
EditorView.updateListener.of(update => {
if (update.docChanged) onCodeChange(update.state.doc.toString());
}),
],
parent: editorRef.current,
});
Patterns Exécution WASM
interface WasmResult {
stdout: string;
stderr: string;
testResults: TestResult[];
memorySnapshots: MemorySnapshot[];
}
async function executeRust(code: string, tests: string): Promise<WasmResult> {
const wasmModule = await WebAssembly.instantiate(WASM_BYTES, {
env: { print: (ptr: number) => captureStdout(ptr) },
});
return wasmModule.instance.exports.run(code, tests) as WasmResult;
}
Règles systématiques
- Tout est typé : pas de
any, pas de as cast. Utiliser les guards (isResult, isError)
- Components React : function components + hooks, pas de classes
- CSS : Tailwind utility classes. Les styles customs vont dans
*.module.css (CSS Modules)
- Code Rust : jamais modifié côté front. Le parsing produit un JSON frozen au build
- LLM : toujours streamer, jamais
await bloquant. Afficher un squelette UI pendant le chargement
- Erreurs :
Result<T, E> pattern (type <T, E> union). Pas de try/catch sauf à la limite du système
- Accessibilité :
aria-* sur tous les composants interactifs, role sur les icônes décoratives, focus trap dans les modales et l'éditeur
- Performance :
React.memo sur les composants lourds (SkillNode, MemoryView). Suspense + lazy par route. useMemo/useCallback uniquement si profilé
Tests
vitest → Tests unitaires composants + hooks
playwright → E2E : ouverture kata, édition, run tests, level up
Déploiement
npm run build
npm run preview
Déploiement sur GitHub Pages ou Netlify (SPA avec fallback index.html pour le routing).