diff --git a/dev-docs/tasks/phase-0-foundation-stabilisation/TASK-010-project-creation-bug-fix/CURRENT-STATUS.md b/dev-docs/tasks/phase-0-foundation-stabilisation/TASK-010-project-creation-bug-fix/CURRENT-STATUS.md
new file mode 100644
index 0000000..2f96379
--- /dev/null
+++ b/dev-docs/tasks/phase-0-foundation-stabilisation/TASK-010-project-creation-bug-fix/CURRENT-STATUS.md
@@ -0,0 +1,400 @@
+# TASK-010B: Preview "No HOME Component" Bug - Status Actuel
+
+**Date**: 12 janvier 2026, 11:40
+**Status**: 🔴 EN COURS - CRITIQUE
+**Priority**: P0 - BLOQUEUR ABSOLU
+
+## 🚨 Symptômes Actuels
+
+**Le preview ne fonctionne JAMAIS après création de projet**
+
+### Ce que l'utilisateur voit:
+
+```
+ERROR
+
+No 🏠 HOME component selected
+Click Make home as shown below.
+[Image avec instructions]
+```
+
+### Logs Console:
+
+```
+✅ Using real ProjectOrganizationService
+ProjectsPage.tsx:67 🔧 Initializing GitHub OAuth service...
+GitHubOAuthService.ts:353 🔧 Initializing GitHubOAuthService
+ProjectsPage.tsx:73 ✅ GitHub OAuth initialized. Authenticated: false
+ViewerConnection.ts:49 Connected to viewer server at ws://localhost:8574
+projectmodel.modules.ts:104 noodl_modules folder not found (fresh project), skipping module loading
+ProjectsPage.tsx:112 🔔 Projects list changed, updating dashboard
+useProjectOrganization.ts:75 ✅ Using real ProjectOrganizationService
+LocalProjectsModel.ts:286 Project created successfully: lkh
+[object%20Module]:1 Failed to load resource: net::ERR_FILE_NOT_FOUND
+nodegrapheditor.ts:374 Failed to load AI assistant outer icon: Event
+nodegrapheditor.ts:379 Failed to load warning icon: Event
+nodegrapheditor.ts:369 Failed to load AI assistant inner icon: Event
+nodegrapheditor.ts:359 Failed to load home icon: Event
+nodegrapheditor.ts:364 Failed to load component icon: Event
+projectmodel.ts:1259 Project saved Mon Jan 12 2026 11:21:48 GMT+0100
+```
+
+**Point clé**: Le projet est créé avec succès, sauvegardé, mais le preview affiche quand même l'erreur "No HOME component".
+
+---
+
+## 📋 Historique des Tentatives de Fix
+
+### Tentative #1 (8 janvier): LocalTemplateProvider avec chemins relatifs
+
+**Status**: ❌ ÉCHOUÉ
+**Problème**: Résolution de chemin avec `__dirname` ne fonctionne pas dans webpack
+**Erreur**: `Template not found at: ./project-examples/...`
+
+### Tentative #2 (8 janvier): LocalTemplateProvider avec process.cwd()
+
+**Status**: ❌ ÉCHOUÉ
+**Problème**: `process.cwd()` pointe vers le mauvais répertoire
+**Erreur**: `Template not found at: /Users/tw/.../packages/noodl-editor/project-examples/...`
+
+### Tentative #3 (9 janvier): Génération programmatique
+
+**Status**: ❌ ÉCHOUÉ
+**Problème**: Structure JSON incomplète
+**Erreur**: `Cannot read properties of undefined (reading 'comments')`
+**Résolution**: Ajout du champ `comments: []` dans la structure
+
+### Tentative #4 (12 janvier - AUJOURD'HUI): Fix rootComponent
+
+**Status**: 🟡 EN TEST
+**Changements**:
+
+1. Ajout de `rootComponent: 'App'` dans `hello-world.template.ts`
+2. Ajout du type `rootComponent?: string` dans `ProjectTemplate.ts`
+3. Modification de `ProjectModel.fromJSON()` pour gérer `rootComponent`
+
+**Fichiers modifiés**:
+
+- `packages/noodl-editor/src/editor/src/models/template/templates/hello-world.template.ts`
+- `packages/noodl-editor/src/editor/src/models/template/ProjectTemplate.ts`
+- `packages/noodl-editor/src/editor/src/models/projectmodel.ts`
+
+**Hypothèse**: Le runtime attend une propriété `rootComponent` dans le project.json pour savoir quel composant afficher dans le preview.
+
+**Résultat**: ⏳ ATTENTE DE CONFIRMATION - L'utilisateur rapporte que ça ne fonctionne toujours pas
+
+---
+
+## 🔍 Analyse du Problème Actuel
+
+### Questions Critiques
+
+1. **Le fix du rootComponent est-il appliqué?**
+
+ - Le projet a-t-il été créé APRÈS le fix?
+ - Faut-il redémarrer le dev server?
+ - Y a-t-il un problème de cache webpack?
+
+2. **Le project.json contient-il rootComponent?**
+
+ - Emplacement probable: `~/Documents/[nom-projet]/project.json` ou `~/Noodl Projects/[nom-projet]/project.json`
+ - Contenu attendu: `"rootComponent": "App"`
+
+3. **Le runtime charge-t-il correctement le projet?**
+ - Vérifier dans `noodl-runtime/src/models/graphmodel.js`
+ - Méthode `importEditorData()` ligne ~83: `this.setRootComponentName(exportData.rootComponent)`
+
+### Points de Contrôle
+
+```typescript
+// 1. EmbeddedTemplateProvider.download() - ligne 92
+await filesystem.writeFile(projectJsonPath, JSON.stringify(projectContent, null, 2));
+// ✅ Vérifié: Le template content inclut bien rootComponent
+
+// 2. ProjectModel.fromJSON() - ligne 172
+if (json.rootComponent && !_this.rootNode) {
+ const rootComponent = _this.getComponentWithName(json.rootComponent);
+ if (rootComponent) {
+ _this.setRootComponent(rootComponent);
+ }
+}
+// ✅ Ajouté: Gestion de rootComponent
+
+// 3. ProjectModel.setRootComponent() - ligne 233
+setRootComponent(component: ComponentModel) {
+ const root = _.find(component.graph.roots, function (n) {
+ return n.type.allowAsExportRoot;
+ });
+ if (root) this.setRootNode(root);
+}
+// ⚠️ ATTENTION: Dépend de n.type.allowAsExportRoot
+```
+
+### Hypothèses sur le Problème Persistant
+
+**Hypothèse A**: Cache webpack non vidé
+
+- Le nouveau code n'est pas chargé
+- Solution: `npm run clean:all && npm run dev`
+
+**Hypothèse B**: Projet créé avec l'ancien template
+
+- Le projet existe déjà et n'a pas rootComponent
+- Solution: Supprimer le projet et en créer un nouveau
+
+**Hypothèse C**: Le runtime ne charge pas rootComponent
+
+- Le graphmodel.js ne gère peut-être pas rootComponent?
+- Solution: Vérifier `noodl-runtime/src/models/graphmodel.js`
+
+**Hypothèse D**: Le node Router ne permet pas allowAsExportRoot
+
+- `setRootComponent()` cherche un node avec `allowAsExportRoot: true`
+- Le Router ne l'a peut-être pas?
+- Solution: Vérifier la définition du node Router
+
+**Hypothèse E**: Mauvaise synchronisation editor ↔ runtime
+
+- Le project.json a rootComponent mais le runtime ne le reçoit pas
+- Solution: Vérifier ViewerConnection et l'envoi du projet
+
+---
+
+## 🚀 Plan de Débogage Immédiat
+
+### Étape 1: Vérifier que le fix est appliqué (5 min)
+
+```bash
+# 1. Nettoyer complètement les caches
+npm run clean:all
+
+# 2. Redémarrer le dev server
+npm run dev
+
+# 3. Attendre que webpack compile (voir "webpack compiled successfully")
+```
+
+### Étape 2: Créer un NOUVEAU projet (2 min)
+
+- Supprimer le projet "lkh" existant depuis le dashboard
+- Créer un nouveau projet avec un nom différent (ex: "test-preview")
+- Observer les logs console
+
+### Étape 3: Vérifier le project.json créé (2 min)
+
+```bash
+# Trouver le projet
+find ~ -name "test-preview" -type d 2>/dev/null | grep -i noodl
+
+# Afficher son project.json
+cat [chemin-trouvé]/project.json | grep -A 2 "rootComponent"
+```
+
+**Attendu**: On devrait voir `"rootComponent": "App"`
+
+### Étape 4: Ajouter des logs de débogage (10 min)
+
+Si ça ne fonctionne toujours pas, ajouter des console.log:
+
+**Dans `ProjectModel.fromJSON()`** (ligne 172):
+
+```typescript
+if (json.rootComponent && !_this.rootNode) {
+ console.log('🔍 Loading rootComponent from template:', json.rootComponent);
+ const rootComponent = _this.getComponentWithName(json.rootComponent);
+ console.log('🔍 Found component?', !!rootComponent);
+ if (rootComponent) {
+ console.log('🔍 Setting root component:', rootComponent.name);
+ _this.setRootComponent(rootComponent);
+ console.log('🔍 Root node after setRootComponent:', _this.rootNode?.id);
+ }
+}
+```
+
+**Dans `ProjectModel.setRootComponent()`** (ligne 233):
+
+```typescript
+setRootComponent(component: ComponentModel) {
+ console.log('🔍 setRootComponent called with:', component.name);
+ console.log('🔍 Graph roots:', component.graph.roots.length);
+ const root = _.find(component.graph.roots, function (n) {
+ console.log('🔍 Checking node:', n.type, 'allowAsExportRoot:', n.type.allowAsExportRoot);
+ return n.type.allowAsExportRoot;
+ });
+ console.log('🔍 Found export root?', !!root);
+ if (root) this.setRootNode(root);
+}
+```
+
+### Étape 5: Vérifier le runtime (15 min)
+
+**Vérifier `noodl-runtime/src/models/graphmodel.js`**:
+
+```javascript
+// Ligne ~83 dans importEditorData()
+this.setRootComponentName(exportData.rootComponent);
+```
+
+Ajouter des logs:
+
+```javascript
+console.log('🔍 Runtime receiving rootComponent:', exportData.rootComponent);
+this.setRootComponentName(exportData.rootComponent);
+console.log('🔍 Runtime rootComponent set to:', this.rootComponent);
+```
+
+---
+
+## 🎯 Solutions Possibles
+
+### Solution Rapide: Forcer le rootComponent manuellement
+
+Si le template ne fonctionne pas, forcer dans `LocalProjectsModel.ts` après création:
+
+```typescript
+// Dans newProject(), après projectFromDirectory
+projectFromDirectory(dirEntry, (project) => {
+ if (!project) {
+ console.error('Failed to create project from template');
+ fn();
+ return;
+ }
+
+ project.name = name;
+
+ // 🔧 FORCE ROOT COMPONENT
+ const appComponent = project.getComponentWithName('App');
+ if (appComponent && !project.getRootNode()) {
+ console.log('🔧 Forcing root component to App');
+ project.setRootComponent(appComponent);
+ }
+
+ this._addProject(project);
+ // ...
+});
+```
+
+### Solution Robuste: Vérifier allowAsExportRoot
+
+Vérifier que le node Router a bien cette propriété. Sinon, utiliser un Group comme root:
+
+```typescript
+// Dans hello-world.template.ts
+graph: {
+ roots: [
+ {
+ id: generateId(),
+ type: 'Group', // Au lieu de 'Router'
+ x: 100,
+ y: 100,
+ parameters: {},
+ ports: [],
+ children: [
+ {
+ id: generateId(),
+ type: 'Router',
+ x: 0,
+ y: 0,
+ parameters: {
+ startPage: '/#__page__/Home'
+ },
+ ports: [],
+ children: []
+ }
+ ]
+ }
+ ];
+}
+```
+
+### Solution Alternative: Utiliser rootNodeId au lieu de rootComponent
+
+Si `rootComponent` par nom ne fonctionne pas, utiliser `rootNodeId`:
+
+```typescript
+// Dans le template, calculer l'ID du premier root
+const appRootId = generateId();
+
+content: {
+ rootComponent: 'App', // Garder pour compatibilité
+ rootNodeId: appRootId, // Ajouter ID direct
+ components: [
+ {
+ name: 'App',
+ graph: {
+ roots: [
+ {
+ id: appRootId, // Utiliser le même ID
+ type: 'Router',
+ // ...
+ }
+ ]
+ }
+ }
+ ]
+}
+```
+
+---
+
+## ✅ Checklist de Résolution
+
+### Tests Immédiats
+
+- [ ] Cache webpack vidé (`npm run clean:all`)
+- [ ] Dev server redémarré
+- [ ] Nouveau projet créé (pas le même nom)
+- [ ] project.json contient `rootComponent: "App"`
+- [ ] Logs ajoutés dans ProjectModel
+- [ ] Console montre les logs de rootComponent
+- [ ] Preview affiche "Hello World!" au lieu de "No HOME component"
+
+### Si ça ne fonctionne toujours pas
+
+- [ ] Vérifier graphmodel.js dans noodl-runtime
+- [ ] Vérifier définition du node Router (allowAsExportRoot)
+- [ ] Tester avec un Group comme root
+- [ ] Tester avec rootNodeId au lieu de rootComponent
+- [ ] Vérifier ViewerConnection et l'envoi du projet
+
+### Documentation Finale
+
+- [ ] Documenter la solution qui fonctionne
+- [ ] Mettre à jour CHANGELOG.md
+- [ ] Ajouter dans LEARNINGS.md
+- [ ] Créer tests de régression
+- [ ] Mettre à jour README de TASK-010
+
+---
+
+## 📞 Prochaines Actions pour l'Utilisateur
+
+### Action Immédiate (2 min)
+
+1. Arrêter le dev server (Ctrl+C)
+2. Exécuter: `npm run clean:all`
+3. Relancer: `npm run dev`
+4. Attendre "webpack compiled successfully"
+5. Supprimer le projet "lkh" existant
+6. Créer un NOUVEAU projet avec un nom différent
+7. Tester le preview
+
+### Si ça ne marche pas
+
+Me dire:
+
+- Le nom du nouveau projet créé
+- Le chemin où il se trouve
+- Le contenu de `project.json` (surtout la présence de `rootComponent`)
+- Les nouveaux logs console
+
+### Commande pour trouver le projet.json:
+
+```bash
+find ~ -name "project.json" -path "*/Noodl*" -type f -exec grep -l "rootComponent" {} \; 2>/dev/null
+```
+
+---
+
+**Mis à jour**: 12 janvier 2026, 11:40
+**Prochaine révision**: Après test avec cache vidé
diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-001-home-component-type.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-001-home-component-type.md
new file mode 100644
index 0000000..00e1f44
--- /dev/null
+++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-001-home-component-type.md
@@ -0,0 +1,106 @@
+# BUG-001: Home Component Shown as "Component" not "Page"
+
+**Severity**: 🟡 Medium (Cosmetic/UX Issue)
+**Status**: Identified
+**Category**: UI Display
+
+---
+
+## 🐛 Symptom
+
+When creating a new project, the Components panel shows:
+
+- ✅ **App** - displayed as regular component
+- ❌ **Home** - displayed as regular component (should show as "page")
+
+**Expected**: Home should have a page icon (router icon) indicating it's a page component.
+
+**Actual**: Home shows with standard component icon.
+
+---
+
+## 🔍 Root Cause
+
+The component name **IS correct** in the template (`'/#__page__/Home'`), but the UI display logic may not be recognizing it properly.
+
+### Template Structure (CORRECT)
+
+```typescript
+// packages/noodl-editor/src/editor/src/models/template/templates/hello-world.template.ts
+
+components: [
+ {
+ name: 'App' // ✅ Regular component
+ // ...
+ },
+ {
+ name: '/#__page__/Home' // ✅ CORRECT - Has page prefix!
+ // ...
+ }
+];
+```
+
+The `/#__page__/` prefix is the standard Noodl convention for marking page components.
+
+---
+
+## 💡 Analysis
+
+**Location**: `packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/hooks/useComponentsPanel.ts`
+
+The issue is likely in how the Components Panel determines if something is a page:
+
+```typescript
+// Pseudo-code of likely logic:
+const isPage = component.name.startsWith('/#__page__/');
+```
+
+**Possible causes**:
+
+1. The component naming is correct, but display logic has a bug
+2. The icon determination logic doesn't check for page prefix
+3. UI state not updated after project load
+
+---
+
+## 🛠️ Proposed Solution
+
+### Option 1: Verify Icon Logic (Recommended)
+
+Check `ComponentItem.tsx` line ~85:
+
+```typescript
+let icon = IconName.Component;
+if (component.isRoot) {
+ icon = IconName.Home;
+} else if (component.isPage) {
+ // ← Verify this is set correctly
+ icon = IconName.PageRouter;
+}
+```
+
+Ensure `component.isPage` is correctly detected from the `/#__page__/` prefix.
+
+### Option 2: Debug Data Flow
+
+Add temporary logging:
+
+```typescript
+console.log('Component:', component.name);
+console.log('Is Page?', component.isPage);
+console.log('Is Root?', component.isRoot);
+```
+
+---
+
+## ✅ Verification Steps
+
+1. Create new project from launcher
+2. Open Components panel
+3. Check icon next to "Home" component
+4. Expected: Should show router/page icon, not component icon
+
+---
+
+**Impact**: Low - Cosmetic issue only, doesn't affect functionality
+**Priority**: P2 - Fix after critical bugs
diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-002-app-not-home.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-002-app-not-home.md
new file mode 100644
index 0000000..41ba522
--- /dev/null
+++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-002-app-not-home.md
@@ -0,0 +1,118 @@
+# BUG-002: App Component Not Set as Home
+
+**Severity**: 🔴 CRITICAL
+**Status**: Root Cause Identified
+**Category**: Core Functionality
+
+---
+
+## 🐛 Symptom
+
+After creating a new project:
+
+- ❌ Preview shows error: **"No 🏠 HOME component selected"**
+- ❌ App component is not marked as Home in Components panel
+- ❌ `ProjectModel.instance.rootNode` is `undefined`
+
+**Expected**: App component should be automatically set as Home, preview should work.
+
+**Actual**: No home component is set, preview fails.
+
+---
+
+## 🔍 Root Cause
+
+**Router node is missing `allowAsExportRoot: true`**
+
+### The Problem Chain
+
+1. **Template includes `rootComponent`**:
+
+```typescript
+// hello-world.template.ts
+content: {
+ rootComponent: 'App', // ✅ This is correct
+ components: [...]
+}
+```
+
+2. **ProjectModel.fromJSON() tries to set it**:
+
+```typescript
+// projectmodel.ts:172
+if (json.rootComponent && !_this.rootNode) {
+ const rootComponent = _this.getComponentWithName(json.rootComponent);
+ if (rootComponent) {
+ _this.setRootComponent(rootComponent); // ← Calls the broken method
+ }
+}
+```
+
+3. **setRootComponent() SILENTLY FAILS**:
+
+```typescript
+// projectmodel.ts:233
+setRootComponent(component: ComponentModel) {
+ const root = _.find(component.graph.roots, function (n) {
+ return n.type.allowAsExportRoot; // ❌ Router returns undefined!
+ });
+ if (root) this.setRootNode(root); // ❌ NEVER EXECUTES!
+ // NO ERROR THROWN - Silent failure!
+}
+```
+
+4. **Router node has NO `allowAsExportRoot`**:
+
+```typescript
+// packages/noodl-viewer-react/src/nodes/navigation/router.tsx
+const RouterNode = {
+ name: 'Router'
+ // ❌ MISSING: allowAsExportRoot: true
+ // ...
+};
+```
+
+---
+
+## 💥 Impact
+
+This is a **BLOCKER**:
+
+- New projects cannot be previewed
+- Users see cryptic error message
+- "Make Home" button also fails (same root cause)
+- No console errors to debug
+
+---
+
+## 🛠️ Solution
+
+**Add one line to router.tsx**:
+
+```typescript
+const RouterNode = {
+ name: 'Router',
+ displayNodeName: 'Page Router',
+ allowAsExportRoot: true, // ✅ ADD THIS
+ category: 'Visuals'
+ // ...
+};
+```
+
+**That's it!** This single line fixes both Bug #2 and Bug #3.
+
+---
+
+## ✅ Verification
+
+After fix:
+
+1. Create new project
+2. Check Components panel - App should have home icon
+3. Open preview - should show "Hello World!"
+4. No error messages
+
+---
+
+**Priority**: P0 - MUST FIX IMMEDIATELY
+**Blocks**: All new project workflows
diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-003-make-home-silent-fail.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-003-make-home-silent-fail.md
new file mode 100644
index 0000000..d0c1e3a
--- /dev/null
+++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-003-make-home-silent-fail.md
@@ -0,0 +1,99 @@
+# BUG-003: "Make Home" Context Menu Does Nothing
+
+**Severity**: 🔴 CRITICAL
+**Status**: Root Cause Identified (Same as BUG-002)
+**Category**: Core Functionality
+
+---
+
+## 🐛 Symptom
+
+When right-clicking on App component and selecting "Make Home":
+
+- ❌ Nothing happens
+- ❌ No console output
+- ❌ No error messages
+- ❌ Component doesn't become Home
+
+**Expected**: App should be set as Home, preview should work.
+
+**Actual**: Silent failure, no feedback.
+
+---
+
+## 🔍 Root Cause
+
+**Same as BUG-002**: Router node missing `allowAsExportRoot: true`
+
+### The Code Path
+
+1. **User clicks "Make Home"** in context menu
+
+2. **Handler is called correctly**:
+
+```typescript
+// useComponentActions.ts:27
+const handleMakeHome = useCallback((node: TreeNode) => {
+ const component = node.data.component;
+
+ ProjectModel.instance?.setRootComponent(component); // ← This is called!
+}, []);
+```
+
+3. **setRootComponent() FAILS SILENTLY**:
+
+```typescript
+// projectmodel.ts:233
+setRootComponent(component: ComponentModel) {
+ const root = _.find(component.graph.roots, function (n) {
+ return n.type.allowAsExportRoot; // ❌ Returns undefined for Router!
+ });
+ if (root) this.setRootNode(root); // ❌ Never reaches here
+ // ❌ NO ERROR, NO LOG, NO FEEDBACK
+}
+```
+
+---
+
+## 💡 Why It's Silent
+
+The method doesn't throw errors or log anything. It just:
+
+1. Searches for a node with `allowAsExportRoot: true`
+2. Finds nothing (Router doesn't have it)
+3. Exits quietly
+
+**No one knows it failed!**
+
+---
+
+## 🛠️ Solution
+
+**Same fix as BUG-002**: Add `allowAsExportRoot: true` to Router node.
+
+```typescript
+// packages/noodl-viewer-react/src/nodes/navigation/router.tsx
+const RouterNode = {
+ name: 'Router',
+ displayNodeName: 'Page Router',
+ allowAsExportRoot: true // ✅ ADD THIS LINE
+ // ...
+};
+```
+
+---
+
+## ✅ Verification
+
+After fix:
+
+1. Create new project
+2. Right-click App component
+3. Click "Make Home"
+4. App should get home icon
+5. Preview should work
+
+---
+
+**Priority**: P0 - MUST FIX IMMEDIATELY
+**Fixes With**: BUG-002 (same root cause, same solution)
diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-004-create-page-modal-styling.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-004-create-page-modal-styling.md
new file mode 100644
index 0000000..f88fbbf
--- /dev/null
+++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/BUG-004-create-page-modal-styling.md
@@ -0,0 +1,98 @@
+# BUG-004: "Create Page" Modal Misaligned
+
+**Severity**: 🟡 Medium (UI/UX Issue)
+**Status**: Identified
+**Category**: CSS Styling
+
+---
+
+## 🐛 Symptom
+
+When clicking "+ Add new page" in the Page Router property editor:
+
+- ❌ Modal rectangle appears **below** the pointer triangle
+- ❌ Triangle "floats" and barely touches the rectangle
+- ❌ Looks unprofessional and broken
+
+**Expected**: Triangle should be seamlessly attached to the modal rectangle.
+
+**Actual**: Triangle and rectangle are visually disconnected.
+
+---
+
+## 🔍 Root Cause
+
+**CSS positioning issue in legacy PopupLayer system**
+
+### The Modal Components
+
+```typescript
+// Pages.tsx line ~195
+PopupLayer.instance.showPopup({
+ content: { el: $(div) },
+ attachTo: $(this.popupAnchor),
+ position: 'right' // ← Position hint
+ // ...
+});
+```
+
+The popup uses legacy jQuery + CSS positioning from `pages.css`.
+
+### Likely CSS Issue
+
+```css
+/* packages/noodl-editor/src/editor/src/styles/propertyeditor/pages.css */
+
+/* Triangle pointer */
+.popup-layer-arrow {
+ /* Positioned absolutely */
+}
+
+/* Modal rectangle */
+.popup-layer-content {
+ /* Also positioned absolutely */
+ /* ❌ Offset calculations may be incorrect */
+}
+```
+
+The triangle and rectangle are positioned separately, causing misalignment.
+
+---
+
+## 🛠️ Solution
+
+### Option 1: Fix CSS (Recommended)
+
+Adjust positioning in `pages.css`:
+
+```css
+.popup-layer-content {
+ /* Ensure top aligns with triangle */
+ margin-top: 0;
+ /* Adjust offset if needed */
+}
+
+.popup-layer-arrow {
+ /* Ensure connects to content */
+}
+```
+
+### Option 2: Migrate to Modern Popup
+
+Replace legacy PopupLayer with modern PopupMenu (from `@noodl-core-ui`).
+
+**Complexity**: Higher, but better long-term solution.
+
+---
+
+## ✅ Verification
+
+1. Open project with Page Router
+2. Click "+ Add new page" button
+3. Check modal appearance
+4. Triangle should seamlessly connect to rectangle
+
+---
+
+**Priority**: P2 - Fix after critical bugs
+**Impact**: Cosmetic only, doesn't affect functionality
diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/INVESTIGATION-project-creation-bugs.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/INVESTIGATION-project-creation-bugs.md
new file mode 100644
index 0000000..175adf9
--- /dev/null
+++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/INVESTIGATION-project-creation-bugs.md
@@ -0,0 +1,103 @@
+# Investigation: Project Creation Bugs
+
+**Date**: January 12, 2026
+**Status**: 🔴 CRITICAL - Multiple Issues Identified
+**Priority**: P0 - Blocks Basic Functionality
+
+---
+
+## 📋 Summary
+
+Four critical bugs were identified when creating new projects from the launcher:
+
+1. **Home component shown as "component" not "page"** in Components panel
+2. **App component not set as Home** (preview fails with "No HOME component")
+3. **"Make Home" context menu does nothing** (no console output, no error)
+4. **"Create new page" modal misaligned** (triangle pointer detached from rectangle)
+
+---
+
+## 🎯 Root Cause Analysis
+
+### CRITICAL: Router Node Missing `allowAsExportRoot`
+
+**Location**: `packages/noodl-viewer-react/src/nodes/navigation/router.tsx`
+
+The Router node definition is **missing the `allowAsExportRoot` property**:
+
+```typescript
+const RouterNode = {
+ name: 'Router',
+ displayNodeName: 'Page Router'
+ // ❌ Missing: allowAsExportRoot: true
+ // ...
+};
+```
+
+This causes `ProjectModel.setRootComponent()` to fail silently:
+
+```typescript
+// packages/noodl-editor/src/editor/src/models/projectmodel.ts:233
+setRootComponent(component: ComponentModel) {
+ const root = _.find(component.graph.roots, function (n) {
+ return n.type.allowAsExportRoot; // ❌ Returns undefined for Router!
+ });
+ if (root) this.setRootNode(root); // ❌ Never executes!
+}
+```
+
+**Impact**: This single missing property causes bugs #2 and #3.
+
+---
+
+## 🔍 Detailed Analysis
+
+See individual bug files for complete analysis:
+
+- **[BUG-001-home-component-type.md](./BUG-001-home-component-type.md)** - Home shown as component not page
+- **[BUG-002-app-not-home.md](./BUG-002-app-not-home.md)** - App component not set as Home
+- **[BUG-003-make-home-silent-fail.md](./BUG-003-make-home-silent-fail.md)** - "Make Home" does nothing
+- **[BUG-004-create-page-modal-styling.md](./BUG-004-create-page-modal-styling.md)** - Modal alignment issue
+
+---
+
+## 🛠️ Proposed Solutions
+
+See **[SOLUTIONS.md](./SOLUTIONS.md)** for detailed fixes.
+
+### Quick Summary
+
+| Bug | Solution | Complexity | Files Affected |
+| ------- | --------------------------------------- | ------------ | -------------- |
+| #1 | Improve UI display logic | Low | 1 file |
+| #2 & #3 | Add `allowAsExportRoot: true` to Router | **Critical** | 1 file |
+| #4 | Fix CSS positioning | Low | 1 file |
+
+---
+
+## 📂 Related Files
+
+### Core Files
+
+- `packages/noodl-viewer-react/src/nodes/navigation/router.tsx` - Router node (NEEDS FIX)
+- `packages/noodl-editor/src/editor/src/models/projectmodel.ts` - Root component logic
+- `packages/noodl-editor/src/editor/src/models/template/templates/hello-world.template.ts` - Template definition
+
+### UI Files
+
+- `packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/hooks/useComponentActions.ts` - "Make Home" handler
+- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/Pages/Pages.tsx` - Create page modal
+- `packages/noodl-editor/src/editor/src/styles/propertyeditor/pages.css` - Modal styling
+
+---
+
+## ✅ Next Steps
+
+1. **CRITICAL**: Add `allowAsExportRoot: true` to Router node
+2. Test project creation flow end-to-end
+3. Fix remaining UI issues (bugs #1 and #4)
+4. Add regression tests
+
+---
+
+_Created: January 12, 2026_
diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/SOLUTIONS.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/SOLUTIONS.md
new file mode 100644
index 0000000..6a3e169
--- /dev/null
+++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001B-launcher-fixes/SOLUTIONS.md
@@ -0,0 +1,191 @@
+# Solutions: Project Creation Bugs
+
+**Date**: January 12, 2026
+**Status**: Ready for Implementation
+
+---
+
+## 🎯 Priority Order
+
+1. **CRITICAL** - BUG-002 & BUG-003 (Same fix)
+2. **Medium** - BUG-001 (UI improvement)
+3. **Low** - BUG-004 (CSS fix)
+
+---
+
+## 🔴 CRITICAL FIX: Add `allowAsExportRoot` to Router
+
+**Fixes**: BUG-002 (App not Home) + BUG-003 ("Make Home" fails)
+
+### File to Edit
+
+`packages/noodl-viewer-react/src/nodes/navigation/router.tsx`
+
+### The Fix (ONE LINE!)
+
+```typescript
+const RouterNode = {
+ name: 'Router',
+ displayNodeName: 'Page Router',
+ allowAsExportRoot: true, // ✅ ADD THIS LINE
+ category: 'Visuals',
+ docs: 'https://docs.noodl.net/nodes/navigation/page-router',
+ useVariants: false
+ // ... rest of the definition
+};
+```
+
+### Why This Works
+
+- `ProjectModel.setRootComponent()` searches for nodes with `allowAsExportRoot: true`
+- Router node currently doesn't have this property
+- Adding it allows Router to be set as the root of a component
+- This fixes both project creation AND "Make Home" functionality
+
+### Testing
+
+```bash
+# 1. Apply the fix
+# 2. Restart dev server: npm run dev
+# 3. Create new project
+# 4. Preview should show "Hello World!"
+# 5. "Make Home" should work on any component
+```
+
+---
+
+## 🟡 BUG-001: Fix Home Component Display
+
+**Severity**: Medium (Cosmetic)
+
+### Investigation Needed
+
+The template correctly creates `'/#__page__/Home'` with the page prefix.
+
+**Check**: `useComponentsPanel.ts` line where it builds tree data.
+
+### Potential Fix
+
+Ensure `isPage` flag is properly set:
+
+```typescript
+// In tree data building logic
+const isPage = component.name.startsWith('/#__page__/');
+
+return {
+ // ...
+ isPage: isPage, // ✅ Ensure this is set
+ // ...
+};
+```
+
+### Alternative
+
+Check `ComponentItem.tsx` icon logic:
+
+```typescript
+let icon = IconName.Component;
+if (component.isRoot) {
+ icon = IconName.Home;
+} else if (component.isPage) {
+ // ← Must be true for pages
+ icon = IconName.PageRouter;
+}
+```
+
+---
+
+## 🟡 BUG-004: Fix Modal Styling
+
+**Severity**: Low (Cosmetic)
+
+### File to Edit
+
+`packages/noodl-editor/src/editor/src/styles/propertyeditor/pages.css`
+
+### Investigation Steps
+
+1. Inspect the popup when it appears
+2. Check CSS classes on triangle and rectangle
+3. Look for positioning offsets
+
+### Likely Fix
+
+Adjust vertical alignment:
+
+```css
+.popup-layer-content {
+ margin-top: 0 !important;
+ /* or adjust to match triangle position */
+}
+
+.popup-layer-arrow {
+ /* Ensure positioned correctly relative to content */
+}
+```
+
+### Long-term Solution
+
+Migrate from legacy PopupLayer to modern `PopupMenu` from `@noodl-core-ui`.
+
+---
+
+## ✅ Implementation Checklist
+
+### Phase 1: Critical Fix (30 minutes)
+
+- [ ] Add `allowAsExportRoot: true` to Router node
+- [ ] Test new project creation
+- [ ] Test "Make Home" functionality
+- [ ] Verify preview works
+
+### Phase 2: UI Improvements (1-2 hours)
+
+- [ ] Debug BUG-001 (page icon not showing)
+- [ ] Fix if needed
+- [ ] Debug BUG-004 (modal alignment)
+- [ ] Fix CSS positioning
+
+### Phase 3: Documentation (30 minutes)
+
+- [ ] Update LEARNINGS.md with findings
+- [ ] Document `allowAsExportRoot` requirement
+- [ ] Add regression test notes
+
+---
+
+## 📝 Regression Test Plan
+
+After fixes, test:
+
+1. **New Project Flow**
+
+ - Create project from launcher
+ - App should be Home automatically
+ - Preview shows "Hello World!"
+
+2. **Make Home Feature**
+
+ - Create second component
+ - Right-click → "Make Home"
+ - Should work without errors
+
+3. **Page Router**
+ - App has Router as root
+ - Can add pages to Router
+ - Modal styling looks correct
+
+---
+
+## 🚀 Expected Results
+
+| Bug | Before | After |
+| --- | ------------------------- | ------------------------- |
+| #1 | Home shows component icon | Home shows page icon |
+| #2 | Preview error | Preview works immediately |
+| #3 | "Make Home" does nothing | "Make Home" works |
+| #4 | Modal misaligned | Modal looks professional |
+
+---
+
+_Ready for implementation!_
diff --git a/dev-docs/tasks/phase-9-styles-overhaul/STYLE-001-token-system-enhancement/CHANGELOG-MVP.md b/dev-docs/tasks/phase-9-styles-overhaul/STYLE-001-token-system-enhancement/CHANGELOG-MVP.md
new file mode 100644
index 0000000..b5244c3
--- /dev/null
+++ b/dev-docs/tasks/phase-9-styles-overhaul/STYLE-001-token-system-enhancement/CHANGELOG-MVP.md
@@ -0,0 +1,214 @@
+# STYLE-001 MVP Implementation - CHANGELOG
+
+**Date**: 2026-01-12
+**Phase**: STYLE-001-MVP (Minimal Viable Product)
+**Status**: ✅ Complete - Ready for Testing
+
+---
+
+## 📦 What Was Implemented
+
+This MVP provides the **foundation** for the Style Tokens system. It includes:
+
+1. **Default Tokens** - 10 essential design tokens
+2. **Storage System** - Tokens saved in project metadata
+3. **CSS Injection** - Tokens automatically injected into preview
+4. **Real-time Updates** - Changes reflected immediately
+
+### What's NOT in this MVP
+
+- ❌ UI Panel to edit tokens
+- ❌ Token Picker component
+- ❌ Import/Export functionality
+- ❌ All token categories (only essentials)
+
+These will come in future phases.
+
+---
+
+## 🗂️ Files Created
+
+### Editor Side (Token Management)
+
+```
+packages/noodl-editor/src/editor/src/models/StyleTokens/
+├── DefaultTokens.ts ✅ NEW - 10 default tokens
+├── StyleTokensModel.ts ✅ NEW - Token management model
+└── index.ts ✅ NEW - Exports
+```
+
+**Purpose**: Manage tokens in the editor, save to project metadata.
+
+### Viewer Side (CSS Injection)
+
+```
+packages/noodl-viewer-react/src/
+├── style-tokens-injector.ts ✅ NEW - Injects CSS into preview
+└── viewer.jsx ✅ MODIFIED - Initialize injector
+```
+
+**Purpose**: Load tokens from project and inject as CSS custom properties.
+
+---
+
+## 🎨 Default Tokens Included
+
+| Token | Value | Category | Usage |
+| -------------- | ---------------------- | -------- | ---------------------- |
+| `--primary` | `#3b82f6` (Blue) | color | Primary buttons, links |
+| `--background` | `#ffffff` (White) | color | Page background |
+| `--foreground` | `#0f172a` (Near black) | color | Text color |
+| `--border` | `#e2e8f0` (Light gray) | color | Borders |
+| `--space-sm` | `8px` | spacing | Small padding/margins |
+| `--space-md` | `16px` | spacing | Medium padding/margins |
+| `--space-lg` | `24px` | spacing | Large padding/margins |
+| `--radius-md` | `8px` | border | Border radius |
+| `--shadow-sm` | `0 1px 2px ...` | shadow | Small shadow |
+| `--shadow-md` | `0 4px 6px ...` | shadow | Medium shadow |
+
+---
+
+## 🔧 Technical Implementation
+
+### 1. Data Flow
+
+```
+ProjectModel
+ ↓ (stores metadata)
+StyleTokensModel.ts
+ ↓ (loads/saves tokens)
+StyleTokensInjector.ts
+ ↓ (generates CSS)
+
+```
+
+**✅ Pass Criteria**: The style tag exists with all 10 default tokens.
+
+---
+
+### Test 2: Use Tokens in Element Styles
+
+**Goal**: Verify tokens can be used in visual elements.
+
+**Steps:**
+
+1. In the node graph, add a **Group** node
+2. In the property panel, go to **Style** section
+3. Add custom styles:
+ ```css
+ background: var(--primary);
+ padding: var(--space-md);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-md);
+ ```
+4. Make the Group visible by setting width/height (e.g., 200px x 200px)
+
+**Expected Result:**
+
+- Background color: Blue (#3b82f6)
+- Padding: 16px on all sides
+- Border radius: 8px rounded corners
+- Box shadow: Medium shadow visible
+
+**✅ Pass Criteria**: All token values are applied correctly.
+
+---
+
+### Test 3: Verify Token Persistence
+
+**Goal**: Confirm tokens persist across editor restarts.
+
+**Steps:**
+
+1. With a project open, open DevTools Console
+2. Set a custom token value:
+ ```javascript
+ ProjectModel.instance.setMetaData('styleTokens', {
+ '--primary': '#ff0000'
+ });
+ ```
+3. Reload the preview (Cmd+R / Ctrl+R)
+4. Check if the element from Test 2 now has a red background
+
+**Expected Result:**
+
+- Background changes from blue to red
+- Other tokens remain unchanged (still using defaults)
+
+**✅ Pass Criteria**: Custom token value persists after reload.
+
+---
+
+### Test 4: Real-Time Token Updates
+
+**Goal**: Verify tokens update without page reload.
+
+**Steps:**
+
+1. With the preview showing an element styled with tokens
+2. Open DevTools Console
+3. Change a token value:
+ ```javascript
+ ProjectModel.instance.setMetaData('styleTokens', {
+ '--primary': '#00ff00',
+ '--space-md': '32px'
+ });
+ ```
+4. Observe the preview **without reloading**
+
+**Expected Result:**
+
+- Background changes from previous color to green
+- Padding increases from 16px to 32px
+- Changes happen immediately
+
+**✅ Pass Criteria**: Changes reflected in real-time.
+
+---
+
+### Test 5: Multiple Element Usage
+
+**Goal**: Confirm tokens work across multiple elements.
+
+**Steps:**
+
+1. Add multiple visual elements:
+ - Group 1: `background: var(--primary)`
+ - Group 2: `background: var(--primary)`
+ - Text: `color: var(--foreground)`
+2. Set a custom `--primary` value:
+ ```javascript
+ ProjectModel.instance.setMetaData('styleTokens', {
+ '--primary': '#9333ea' // Purple
+ });
+ ```
+
+**Expected Result:**
+
+- Both Group 1 and Group 2 change to purple simultaneously
+- Text color remains the foreground color
+
+**✅ Pass Criteria**: Token change applies to all elements using it.
+
+---
+
+### Test 6: Invalid Token Handling
+
+**Goal**: Verify system handles invalid token values gracefully.
+
+**Steps:**
+
+1. Add an element with `background: var(--nonexistent-token)`
+2. Observe what happens
+
+**Expected Result:**
+
+- No error in console
+- Element uses browser default (likely transparent/white)
+- Preview doesn't crash
+
+**✅ Pass Criteria**: System degrades gracefully.
+
+---
+
+### Test 7: Token in Different CSS Properties
+
+**Goal**: Confirm tokens work in various CSS properties.
+
+**Steps:**
+
+1. Create an element with multiple token usages:
+ ```css
+ background: var(--primary);
+ color: var(--foreground);
+ padding: var(--space-sm) var(--space-md);
+ margin: var(--space-lg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-sm);
+ ```
+2. Inspect the computed styles in DevTools
+
+**Expected Result:**
+
+All properties resolve to their token values:
+
+- `background: rgb(59, 130, 246)` (--primary)
+- `padding: 8px 16px` (--space-sm --space-md)
+- etc.
+
+**✅ Pass Criteria**: All tokens resolve correctly.
+
+---
+
+## 🐛 Troubleshooting
+
+### Issue: Tokens not showing in preview
+
+**Solution:**
+
+1. Check browser console for errors
+2. Verify the style element exists: `document.getElementById('noodl-style-tokens')`
+3. Check if StyleTokensInjector was initialized:
+ ```javascript
+ // In DevTools console
+ console.log('Injector exists:', !!window._styleTokensInjector);
+ ```
+
+### Issue: Changes don't persist
+
+**Solution:**
+
+1. Ensure you're using `ProjectModel.instance.setMetaData()`
+2. Check if metadata is saved:
+ ```javascript
+ console.log(ProjectModel.instance.getMetaData('styleTokens'));
+ ```
+3. Save the project explicitly if needed
+
+### Issue: Tokens not updating in real-time
+
+**Solution:**
+
+1. Check if the 'metadataChanged' event is firing
+2. Verify StyleTokensInjector is listening to events
+3. Try a hard refresh (Cmd+Shift+R / Ctrl+Shift+F5)
+
+---
+
+## 📊 Test Results Template
+
+```
+STYLE-001 MVP Testing - Results
+================================
+Date: ___________
+Tester: ___________
+
+Test 1: Default Tokens Injected [ ] PASS [ ] FAIL
+Test 2: Use Tokens in Styles [ ] PASS [ ] FAIL
+Test 3: Token Persistence [ ] PASS [ ] FAIL
+Test 4: Real-Time Updates [ ] PASS [ ] FAIL
+Test 5: Multiple Element Usage [ ] PASS [ ] FAIL
+Test 6: Invalid Token Handling [ ] PASS [ ] FAIL
+Test 7: Various CSS Properties [ ] PASS [ ] FAIL
+
+Notes:
+_____________________________________________________
+_____________________________________________________
+_____________________________________________________
+
+Overall Status: [ ] PASS [ ] FAIL
+```
+
+---
+
+## 🚀 Next Steps After Testing
+
+**If all tests pass:**
+
+- ✅ MVP is ready for use
+- Document any findings
+- Plan STYLE-001 full version (UI panel)
+
+**If tests fail:**
+
+- Document which tests failed
+- Note error messages
+- Create bug reports
+- Fix issues before continuing
+
+---
+
+_Last Updated: 2026-01-12_
diff --git a/packages/noodl-editor/src/editor/src/models/StyleTokens/DefaultTokens.ts b/packages/noodl-editor/src/editor/src/models/StyleTokens/DefaultTokens.ts
new file mode 100644
index 0000000..c86cfe5
--- /dev/null
+++ b/packages/noodl-editor/src/editor/src/models/StyleTokens/DefaultTokens.ts
@@ -0,0 +1,119 @@
+/**
+ * Default Style Tokens (Minimal Set for MVP)
+ *
+ * This file defines the minimal set of CSS custom properties (design tokens)
+ * that will be available in every Noodl project.
+ *
+ * These tokens can be used in any CSS property that accepts the relevant value type.
+ * Example: style="background: var(--primary); padding: var(--space-md);"
+ *
+ * @module StyleTokens
+ */
+
+export interface StyleToken {
+ name: string;
+ value: string;
+ category: TokenCategory;
+ description: string;
+}
+
+export type TokenCategory = 'color' | 'spacing' | 'border' | 'shadow';
+
+/**
+ * Minimal set of design tokens for MVP
+ * Following modern design system conventions (similar to Tailwind/shadcn)
+ */
+export const DEFAULT_TOKENS: Record = {
+ // ===== COLORS =====
+ '--primary': {
+ name: '--primary',
+ value: '#3b82f6', // Blue
+ category: 'color',
+ description: 'Primary brand color for main actions and highlights'
+ },
+
+ '--background': {
+ name: '--background',
+ value: '#ffffff',
+ category: 'color',
+ description: 'Main background color'
+ },
+
+ '--foreground': {
+ name: '--foreground',
+ value: '#0f172a', // Near black
+ category: 'color',
+ description: 'Main text color'
+ },
+
+ '--border': {
+ name: '--border',
+ value: '#e2e8f0', // Light gray
+ category: 'color',
+ description: 'Default border color'
+ },
+
+ // ===== SPACING =====
+ '--space-sm': {
+ name: '--space-sm',
+ value: '8px',
+ category: 'spacing',
+ description: 'Small spacing (padding, margin, gap)'
+ },
+
+ '--space-md': {
+ name: '--space-md',
+ value: '16px',
+ category: 'spacing',
+ description: 'Medium spacing (padding, margin, gap)'
+ },
+
+ '--space-lg': {
+ name: '--space-lg',
+ value: '24px',
+ category: 'spacing',
+ description: 'Large spacing (padding, margin, gap)'
+ },
+
+ // ===== BORDERS =====
+ '--radius-md': {
+ name: '--radius-md',
+ value: '8px',
+ category: 'border',
+ description: 'Medium border radius for rounded corners'
+ },
+
+ // ===== SHADOWS =====
+ '--shadow-sm': {
+ name: '--shadow-sm',
+ value: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
+ category: 'shadow',
+ description: 'Small shadow for subtle elevation'
+ },
+
+ '--shadow-md': {
+ name: '--shadow-md',
+ value: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
+ category: 'shadow',
+ description: 'Medium shadow for moderate elevation'
+ }
+};
+
+/**
+ * Get all default tokens as a simple key-value map
+ * Useful for CSS injection
+ */
+export function getDefaultTokenValues(): Record {
+ const values: Record = {};
+ for (const [key, token] of Object.entries(DEFAULT_TOKENS)) {
+ values[key] = token.value;
+ }
+ return values;
+}
+
+/**
+ * Get tokens by category
+ */
+export function getTokensByCategory(category: TokenCategory): StyleToken[] {
+ return Object.values(DEFAULT_TOKENS).filter((token) => token.category === category);
+}
diff --git a/packages/noodl-editor/src/editor/src/models/StyleTokens/StyleTokensModel.ts b/packages/noodl-editor/src/editor/src/models/StyleTokens/StyleTokensModel.ts
new file mode 100644
index 0000000..94678ad
--- /dev/null
+++ b/packages/noodl-editor/src/editor/src/models/StyleTokens/StyleTokensModel.ts
@@ -0,0 +1,211 @@
+/**
+ * Style Tokens Model
+ *
+ * Manages CSS custom properties (design tokens) for a Noodl project.
+ * Tokens are stored in project metadata and can be customized per project.
+ *
+ * @module StyleTokens
+ */
+
+import Model from '../../../../shared/model';
+import { EventDispatcher } from '../../../../shared/utils/EventDispatcher';
+import { ProjectModel } from '../projectmodel';
+import { getDefaultTokenValues, DEFAULT_TOKENS, StyleToken, TokenCategory } from './DefaultTokens';
+
+export class StyleTokensModel extends Model {
+ /** Custom token values (overrides defaults) */
+ private customTokens: Record;
+
+ constructor() {
+ super();
+ this.customTokens = {};
+ this.loadFromProject();
+ this.bindListeners();
+ }
+
+ /**
+ * Bind to project events to stay in sync
+ */
+ private bindListeners() {
+ const onProjectChanged = () => {
+ this.loadFromProject();
+ this.notifyListeners('tokensChanged');
+ };
+
+ EventDispatcher.instance.on(
+ ['ProjectModel.importComplete', 'ProjectModel.instanceHasChanged'],
+ () => {
+ if (ProjectModel.instance) {
+ onProjectChanged();
+ }
+ },
+ this
+ );
+
+ EventDispatcher.instance.on(
+ 'ProjectModel.metadataChanged',
+ ({ key }) => {
+ if (key === 'styleTokens') {
+ onProjectChanged();
+ }
+ },
+ this
+ );
+ }
+
+ /**
+ * Unbind listeners
+ */
+ private unbindListeners() {
+ EventDispatcher.instance.off(this);
+ }
+
+ /**
+ * Load tokens from current project
+ */
+ private loadFromProject() {
+ if (ProjectModel.instance) {
+ this.customTokens = ProjectModel.instance.getMetaData('styleTokens') || {};
+ } else {
+ this.customTokens = {};
+ }
+ }
+
+ /**
+ * Save tokens to current project
+ */
+ private saveToProject() {
+ if (ProjectModel.instance) {
+ this.unbindListeners();
+ ProjectModel.instance.setMetaData('styleTokens', this.customTokens);
+ this.bindListeners();
+ }
+ }
+
+ /**
+ * Get all tokens (defaults + custom overrides)
+ */
+ getAllTokens(): Record {
+ const defaults = getDefaultTokenValues();
+ return {
+ ...defaults,
+ ...this.customTokens
+ };
+ }
+
+ /**
+ * Get a specific token value
+ * @param name Token name (e.g., '--primary')
+ * @returns Token value or undefined if not found
+ */
+ getToken(name: string): string | undefined {
+ // Check custom tokens first
+ if (this.customTokens[name] !== undefined) {
+ return this.customTokens[name];
+ }
+
+ // Fall back to default
+ const defaultToken = DEFAULT_TOKENS[name];
+ return defaultToken?.value;
+ }
+
+ /**
+ * Set a custom token value
+ * @param name Token name (e.g., '--primary')
+ * @param value Token value (e.g., '#ff0000')
+ */
+ setToken(name: string, value: string) {
+ // Validate token name starts with --
+ if (!name.startsWith('--')) {
+ console.warn(`Token name must start with -- : ${name}`);
+ return;
+ }
+
+ this.customTokens[name] = value;
+ this.saveToProject();
+ this.notifyListeners('tokensChanged');
+ this.notifyListeners('tokenChanged', { name, value });
+ }
+
+ /**
+ * Reset a token to its default value
+ * @param name Token name (e.g., '--primary')
+ */
+ resetToken(name: string) {
+ if (this.customTokens[name] !== undefined) {
+ delete this.customTokens[name];
+ this.saveToProject();
+ this.notifyListeners('tokensChanged');
+ this.notifyListeners('tokenReset', { name });
+ }
+ }
+
+ /**
+ * Reset all tokens to defaults
+ */
+ resetAllTokens() {
+ this.customTokens = {};
+ this.saveToProject();
+ this.notifyListeners('tokensChanged');
+ this.notifyListeners('allTokensReset');
+ }
+
+ /**
+ * Check if a token has been customized
+ */
+ isTokenCustomized(name: string): boolean {
+ return this.customTokens[name] !== undefined;
+ }
+
+ /**
+ * Get token metadata
+ */
+ getTokenInfo(name: string): StyleToken | undefined {
+ return DEFAULT_TOKENS[name];
+ }
+
+ /**
+ * Get all tokens by category
+ */
+ getTokensByCategory(category: TokenCategory): Record {
+ const tokens: Record = {};
+
+ for (const [name, tokenInfo] of Object.entries(DEFAULT_TOKENS)) {
+ if (tokenInfo.category === category) {
+ tokens[name] = this.getToken(name) || tokenInfo.value;
+ }
+ }
+
+ return tokens;
+ }
+
+ /**
+ * Generate CSS string for injection
+ * @returns CSS custom properties as a string
+ */
+ generateCSS(): string {
+ const allTokens = this.getAllTokens();
+ const entries = Object.entries(allTokens);
+
+ if (entries.length === 0) {
+ return '';
+ }
+
+ const declarations = entries.map(([name, value]) => ` ${name}: ${value};`).join('\n');
+
+ return `:root {\n${declarations}\n}`;
+ }
+
+ /**
+ * Cleanup
+ */
+ dispose() {
+ this.unbindListeners();
+ this.removeAllListeners();
+ }
+}
+
+/**
+ * Singleton instance
+ */
+export const StyleTokens = new StyleTokensModel();
diff --git a/packages/noodl-editor/src/editor/src/models/StyleTokens/index.ts b/packages/noodl-editor/src/editor/src/models/StyleTokens/index.ts
new file mode 100644
index 0000000..f402db0
--- /dev/null
+++ b/packages/noodl-editor/src/editor/src/models/StyleTokens/index.ts
@@ -0,0 +1,11 @@
+/**
+ * Style Tokens System
+ *
+ * Exports for the Style Tokens system
+ *
+ * @module StyleTokens
+ */
+
+export { StyleTokensModel, StyleTokens } from './StyleTokensModel';
+export { DEFAULT_TOKENS, getDefaultTokenValues, getTokensByCategory } from './DefaultTokens';
+export type { StyleToken, TokenCategory } from './DefaultTokens';
diff --git a/packages/noodl-editor/src/editor/src/models/projectmodel.ts b/packages/noodl-editor/src/editor/src/models/projectmodel.ts
index 63e06db..72f5698 100644
--- a/packages/noodl-editor/src/editor/src/models/projectmodel.ts
+++ b/packages/noodl-editor/src/editor/src/models/projectmodel.ts
@@ -169,6 +169,14 @@ export class ProjectModel extends Model {
if (json.rootNodeId) _this.rootNode = _this.findNodeWithId(json.rootNodeId);
+ // Handle rootComponent from templates (name of component instead of node ID)
+ if (json.rootComponent && !_this.rootNode) {
+ const rootComponent = _this.getComponentWithName(json.rootComponent);
+ if (rootComponent) {
+ _this.setRootComponent(rootComponent);
+ }
+ }
+
// Upgrade project if necessary
ProjectModel.upgrade(_this);
diff --git a/packages/noodl-editor/src/editor/src/models/template/ProjectTemplate.ts b/packages/noodl-editor/src/editor/src/models/template/ProjectTemplate.ts
index f926c90..436fd76 100644
--- a/packages/noodl-editor/src/editor/src/models/template/ProjectTemplate.ts
+++ b/packages/noodl-editor/src/editor/src/models/template/ProjectTemplate.ts
@@ -40,6 +40,9 @@ export interface ProjectContent {
/** Project name (will be overridden by user input) */
name: string;
+ /** Name of the root component that serves as the entry point */
+ rootComponent?: string;
+
/** Array of component definitions */
components: ComponentDefinition[];
diff --git a/packages/noodl-editor/src/editor/src/models/template/templates/hello-world.template.ts b/packages/noodl-editor/src/editor/src/models/template/templates/hello-world.template.ts
index 90b10d3..b2e67bd 100644
--- a/packages/noodl-editor/src/editor/src/models/template/templates/hello-world.template.ts
+++ b/packages/noodl-editor/src/editor/src/models/template/templates/hello-world.template.ts
@@ -36,6 +36,7 @@ export const helloWorldTemplate: ProjectTemplate = {
content: {
name: 'Hello World Project',
+ rootComponent: 'App',
components: [
// App component (root)
{
diff --git a/packages/noodl-viewer-react/src/nodes/navigation/router.tsx b/packages/noodl-viewer-react/src/nodes/navigation/router.tsx
index 766e5ab..e5ac4ca 100644
--- a/packages/noodl-viewer-react/src/nodes/navigation/router.tsx
+++ b/packages/noodl-viewer-react/src/nodes/navigation/router.tsx
@@ -59,6 +59,7 @@ const RouterNode = {
displayNodeName: 'Page Router',
category: 'Visuals',
docs: 'https://docs.noodl.net/nodes/navigation/page-router',
+ allowAsExportRoot: true,
useVariants: false,
connectionPanel: {
groupPriority: ['General', 'Actions', 'Events', 'Mounted']
diff --git a/packages/noodl-viewer-react/src/style-tokens-injector.ts b/packages/noodl-viewer-react/src/style-tokens-injector.ts
new file mode 100644
index 0000000..892f530
--- /dev/null
+++ b/packages/noodl-viewer-react/src/style-tokens-injector.ts
@@ -0,0 +1,163 @@
+/**
+ * Style Tokens Injector
+ *
+ * Injects CSS custom properties (design tokens) into the DOM
+ * for use in Noodl projects.
+ *
+ * This class is responsible for:
+ * - Injecting default tokens into the page
+ * - Updating tokens when project settings change
+ * - Cleaning up on unmount
+ */
+
+interface GraphModel {
+ getMetaData(): Record | undefined;
+ on(event: string, handler: (data: unknown) => void): void;
+ off(event: string): void;
+}
+
+interface StyleTokensInjectorOptions {
+ graphModel: GraphModel;
+}
+
+export class StyleTokensInjector {
+ private styleElement: HTMLStyleElement | null = null;
+ private graphModel: GraphModel;
+ private tokens: Record = {};
+
+ constructor(options: StyleTokensInjectorOptions) {
+ this.graphModel = options.graphModel;
+
+ // Load tokens from project metadata
+ this.loadTokens();
+
+ // Inject tokens into DOM
+ this.injectTokens();
+
+ // Listen for project changes
+ this.bindListeners();
+ }
+
+ /**
+ * Load tokens from project metadata
+ */
+ private loadTokens() {
+ try {
+ const metadata = this.graphModel.getMetaData();
+ const styleTokens = metadata?.styleTokens;
+
+ // Validate that styleTokens is a proper object
+ if (styleTokens && typeof styleTokens === 'object' && !Array.isArray(styleTokens)) {
+ this.tokens = styleTokens as Record;
+ } else {
+ this.tokens = this.getDefaultTokens();
+ }
+ } catch (error) {
+ console.warn('Failed to load style tokens, using defaults:', error);
+ this.tokens = this.getDefaultTokens();
+ }
+ }
+
+ /**
+ * Get default tokens (fallback if project doesn't have custom tokens)
+ */
+ private getDefaultTokens(): Record {
+ return {
+ // Colors
+ '--primary': '#3b82f6',
+ '--background': '#ffffff',
+ '--foreground': '#0f172a',
+ '--border': '#e2e8f0',
+ // Spacing
+ '--space-sm': '8px',
+ '--space-md': '16px',
+ '--space-lg': '24px',
+ // Borders
+ '--radius-md': '8px',
+ // Shadows
+ '--shadow-sm': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
+ '--shadow-md': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)'
+ };
+ }
+
+ /**
+ * Inject tokens as CSS custom properties
+ */
+ private injectTokens() {
+ // Support SSR
+ if (typeof document === 'undefined') return;
+
+ // Remove existing style element if any
+ this.removeStyleElement();
+
+ // Create new style element
+ this.styleElement = document.createElement('style');
+ this.styleElement.id = 'noodl-style-tokens';
+ this.styleElement.textContent = this.generateCSS();
+
+ // Inject into head
+ document.head.appendChild(this.styleElement);
+ }
+
+ /**
+ * Generate CSS string from tokens
+ */
+ private generateCSS(): string {
+ const entries = Object.entries(this.tokens);
+
+ if (entries.length === 0) {
+ return '';
+ }
+
+ const declarations = entries.map(([name, value]) => ` ${name}: ${value};`).join('\n');
+
+ return `:root {\n${declarations}\n}`;
+ }
+
+ /**
+ * Update tokens and re-inject
+ */
+ updateTokens(newTokens: Record) {
+ this.tokens = { ...this.getDefaultTokens(), ...newTokens };
+ this.injectTokens();
+ }
+
+ /**
+ * Bind to graph model events
+ */
+ private bindListeners() {
+ if (!this.graphModel) return;
+
+ // Listen for metadata changes
+ this.graphModel.on('metadataChanged', (metadata: unknown) => {
+ if (metadata && typeof metadata === 'object' && 'styleTokens' in metadata) {
+ const data = metadata as Record;
+ if (data.styleTokens && typeof data.styleTokens === 'object') {
+ this.updateTokens(data.styleTokens as Record);
+ }
+ }
+ });
+ }
+
+ /**
+ * Remove style element from DOM
+ */
+ private removeStyleElement() {
+ if (this.styleElement && this.styleElement.parentNode) {
+ this.styleElement.parentNode.removeChild(this.styleElement);
+ this.styleElement = null;
+ }
+ }
+
+ /**
+ * Cleanup
+ */
+ dispose() {
+ this.removeStyleElement();
+ if (this.graphModel) {
+ this.graphModel.off('metadataChanged');
+ }
+ }
+}
+
+export default StyleTokensInjector;
diff --git a/packages/noodl-viewer-react/src/viewer.jsx b/packages/noodl-viewer-react/src/viewer.jsx
index e269d39..90d2734 100644
--- a/packages/noodl-viewer-react/src/viewer.jsx
+++ b/packages/noodl-viewer-react/src/viewer.jsx
@@ -8,6 +8,7 @@ import NoodlJSAPI from './noodl-js-api';
import projectSettings from './project-settings';
import { createNodeFromReactComponent } from './react-component-node';
import registerNodes from './register-nodes';
+import { StyleTokensInjector } from './style-tokens-injector';
import Styles from './styles';
if (typeof window !== 'undefined' && window.NoodlEditor) {
@@ -189,6 +190,11 @@ export default class Viewer extends React.Component {
//make the styles available to all nodes via `this.context.styles`
noodlRuntime.context.styles = this.styles;
+ // Initialize style tokens injector
+ this.styleTokensInjector = new StyleTokensInjector({
+ graphModel: noodlRuntime.graphModel
+ });
+
this.state.waitingForExport = !this.runningDeployed;
if (this.runningDeployed) {