33800 Docs

← Retour

QwikPress - Backlog Fonctionnalités

Date : 29/12/2024 Priorite : HAUTE - CETTE SEMAINE Projet : QwikPress CMS


1. Module Custom Runtime (Hot Module)

Priorité : Haute Complexité : Élevée

Description

Permettre de créer des modules directement depuis l'interface admin sans nécessiter de rebuild du projet.

Fonctionnalités requises

1.1 Éditeur de code intégré

1.2 Structure d'un Hot Module

{
  "id": "custom-module-1",
  "type": "hot-module",
  "template": "HotModule",
  "data": {
    "name": "Mon Module Custom",
    "clientJs": "console.log('Hello client'); document.querySelector('.btn').onclick = () => alert('Clicked!');",
    "serverJs": "const data = await fetch('https://api.example.com/data').then(r => r.json()); return { items: data };",
    "jsx": "<div class=\"my-module\"><h2>{props.title}</h2><ul>{serverData.items.map(i => <li>{i.name}</li>)}</ul><button class=\"btn\">Click me</button></div>",
    "css": ".my-module { padding: 2rem; } .btn { background: blue; color: white; }",
    "props": {
      "title": "Liste des items"
    }
  }
}

1.3 Sécurité

1.4 Interface Admin

Implémentation technique


2. Architecture Multi-Domaines (Public / Admin)

Priorité : Moyenne Complexité : Moyenne

Description

Séparer le site public et l'administration sur deux domaines distincts pour renforcer la sécurité.

Configuration cible

monsite.com          → Site public (lecture seule)
admin.monsite.com    → Administration (authentifié)

Fonctionnalités requises

2.1 Variables d'environnement

# Mode domaine unique (actuel)
DOMAIN_MODE=single
BASE_URL=https://monsite.com

# Mode multi-domaines
DOMAIN_MODE=split
PUBLIC_DOMAIN=https://monsite.com
ADMIN_DOMAIN=https://admin.monsite.com

2.2 Routing conditionnel

2.3 Sécurité renforcée

2.4 Partage des données

Implémentation technique

Middleware de routage

// src/routes/plugin@domain.ts
export const onRequest: RequestHandler = async ({ url, redirect, env }) => {
  const isAdminDomain = url.hostname === new URL(env.ADMIN_DOMAIN).hostname;
  const isAdminRoute = url.pathname.startsWith('/admin');

  if (env.DOMAIN_MODE === 'split') {
    // Sur domaine public, bloquer /admin
    if (!isAdminDomain && isAdminRoute) {
      throw redirect(302, `${env.ADMIN_DOMAIN}${url.pathname}`);
    }
    // Sur domaine admin, bloquer routes publiques (optionnel)
    if (isAdminDomain && !isAdminRoute && url.pathname !== '/') {
      throw redirect(302, `${env.PUBLIC_DOMAIN}${url.pathname}`);
    }
  }
};

Config Nginx (exemple)

# Public
server {
    server_name monsite.com;
    location / {
        proxy_pass http://localhost:4500;
    }
    location /admin {
        return 302 https://admin.monsite.com$request_uri;
    }
}

# Admin
server {
    server_name admin.monsite.com;
    # IP whitelist optionnel
    # allow 192.168.1.0/24;
    # deny all;
    location / {
        proxy_pass http://localhost:4500;
    }
}

3. Autres idées (à prioriser)


Statut

Feature Statut Assigné Date cible
Hot Module 🔴 À faire - -
Multi-domaines 🔴 À faire - -

Ordonnancement et dépendances entre Catchers

Les catchers peuvent dépendre les uns des autres. Exemple :

Déclaration des dépendances

{
  "id": "user-orders",
  "name": "Commandes utilisateur",
  "connector": "shop-api",
  "endpoint": "getOrders",
  "depends": ["current-user"],
  "params": {
    "userId": "${catchers.current-user.id}"
  }
}
{
  "id": "order-products",
  "name": "Produits des commandes", 
  "connector": "shop-api",
  "endpoint": "getProducts",
  "depends": ["user-orders"],
  "params": {
    "ids": "${catchers.user-orders.map(o => o.productIds).flat().join(',')}"
  }
}

Graphe d'exécution (DAG)

Le système construit automatiquement un graphe orienté acyclique :

                    ┌─────────────────┐
                    │  current-user   │  (pas de dépendance)
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │   user-orders   │  (dépend de current-user)
                    └────────┬────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
     ┌────────────┐  ┌─────────────┐  ┌────────────┐
     │order-products│ │order-stats │  │ shipping   │
     └────────────┘  └─────────────┘  └────────────┘

Exécution parallèle optimisée

// Pseudo-code du resolver
async function resolveDataCatchers(catchers: DataCatcher[], context: RequestContext) {
  const graph = buildDependencyGraph(catchers);
  const results: Record<string, any> = {};

  // Exécuter par niveaux (catchers sans dépendances d'abord)
  for (const level of graph.levels) {
    // Tous les catchers d'un même niveau en parallèle
    const levelResults = await Promise.all(
      level.map(catcher => executeCatcher(catcher, results, context))
    );

    // Stocker les résultats pour les niveaux suivants
    level.forEach((catcher, i) => {
      results[catcher.id] = levelResults[i];
    });
  }

  return results;
}

Niveau 0 (parallèle) : current-user, site-settings, featured-products Niveau 1 (parallèle, après niveau 0) : user-orders, user-favorites Niveau 2 (parallèle, après niveau 1) : order-products, order-stats, shipping

Accès aux données des autres catchers

Dans les params, transform, ou filter d'un catcher :

{
  "id": "personalized-products",
  "depends": ["current-user", "user-favorites"],
  "params": {
    "category": "${catchers['current-user'].preferences.category}",
    "exclude": "${catchers['user-favorites'].map(f => f.productId)}"
  },
  "transform": {
    "type": "custom",
    "code": "items.filter(i => !catchers['user-favorites'].includes(i.id))"
  }
}

Gestion des erreurs

{
  "id": "user-orders",
  "depends": ["current-user"],
  "onDependencyError": "skip",
  "fallback": []
}

Options :

Détection des cycles

Le système détecte et refuse les dépendances circulaires :

❌ ERREUR: Dépendance circulaire détectée
   catcher-a → catcher-b → catcher-c → catcher-a

Cache intelligent avec dépendances

Le cache tient compte des dépendances :

{
  "id": "user-orders",
  "depends": ["current-user"],
  "cache": {
    "ttl": 300,
    "key": "orders-${catchers['current-user'].id}",
    "invalidateOn": ["current-user.changed"]
  }
}

Si current-user change, le cache de user-orders est invalidé.

Visualisation dans l'Admin

┌─────────────────────────────────────────────────────────┐
│ Data Catchers - Graphe de dépendances                   │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   ○ current-user (0ms)                                  │
│   │                                                     │
│   ├──○ user-orders (45ms)                              │
│   │  │                                                  │
│   │  ├──○ order-products (120ms)                       │
│   │  └──○ order-stats (30ms)                           │
│   │                                                     │
│   └──○ user-favorites (25ms)                           │
│      │                                                  │
│      └──○ personalized-products (80ms)                 │
│                                                         │
│   ○ featured-products (50ms) [indépendant]             │
│                                                         │
│   Total: 200ms (parallélisé) vs 350ms (séquentiel)     │
└─────────────────────────────────────────────────────────┘


5. Philosophie "Everything is a Module" + Layouts

Priorité : Haute Complexité : Moyenne

Vision globale

Objectif : Permettre aux développeurs de créer facilement modules, connecteurs et catchers pendant le développement, ET permettre de faire exactement la même chose via le navigateur une fois en production (sans rebuild).

┌────────────────────────────────────────────────────────────────┐
│                        DÉVELOPPEMENT                           │
│                                                                │
│   src/modules/       → Modules compilés (TypeScript/Qwik)     │
│   src/connectors/    → Connecteurs compilés                   │
│   src/catchers/      → Data Catchers compilés                 │
│                                                                │
│   ✅ Type-safe, performant, testé                              │
└────────────────────────────────────────────────────────────────┘
                              │
                              │ même API, même structure
                              ▼
┌────────────────────────────────────────────────────────────────┐
│                        RUNTIME (Browser)                       │
│                                                                │
│   /admin/custom/modules     → Hot Modules (JS + JSX)          │
│   /admin/custom/connectors  → Connecteurs dynamiques          │
│   /admin/custom/catchers    → Data Catchers dynamiques        │
│                                                                │
│   ✅ Pas de rebuild, déploiement instantané                    │
└────────────────────────────────────────────────────────────────┘

Header & Footer = Modules

Avant (config séparée) :

{
  "site": { ... },
  "header": {
    "logo": "...",
    "navigation": [...]
  },
  "footer": {
    "columns": [...],
    "social": [...]
  }
}

Après (modules) :

{
  "site": { ... },
  "layouts": {
    "default": {
      "header": {
        "module": "HeaderModule",
        "data": { "logo": "...", "navigation": [...] },
        "options": { "sticky": true, "transparent": false }
      },
      "footer": {
        "module": "FooterModule", 
        "data": { "columns": [...], "social": [...] },
        "options": { "showNewsletter": true }
      },
      "wrapper": {
        "module": "PageWrapperModule",
        "options": { "maxWidth": "1200px", "padding": "2rem" }
      }
    },
    "landing": {
      "header": {
        "module": "MinimalHeaderModule",
        "options": { "transparent": true, "absolute": true }
      },
      "footer": null
    },
    "admin": {
      "header": { "module": "AdminHeaderModule" },
      "sidebar": { "module": "AdminSidebarModule" },
      "footer": null
    }
  }
}

Système de Layouts

Un layout définit :

┌─────────────────────────────────────────────────────────┐
│                    HEADER (zone)                        │
│              [HeaderModule + options]                   │
├──────────────┬──────────────────────────────────────────┤
│   SIDEBAR    │              CONTENT                     │
│   (zone)     │              (zone)                      │
│              │                                          │
│  [Module]    │   ┌────────────────────────────────┐    │
│              │   │  Module 1 (page content)       │    │
│              │   ├────────────────────────────────┤    │
│              │   │  Module 2                      │    │
│              │   ├────────────────────────────────┤    │
│              │   │  Module 3                      │    │
│              │   └────────────────────────────────┘    │
│              │                                          │
├──────────────┴──────────────────────────────────────────┤
│                    FOOTER (zone)                        │
│              [FooterModule + options]                   │
└─────────────────────────────────────────────────────────┘

Définition d'un Layout

{
  "id": "default",
  "name": "Layout par défaut",
  "zones": [
    {
      "id": "header",
      "position": "top",
      "sticky": true,
      "module": {
        "type": "HeaderModule",
        "data": { ... }
      }
    },
    {
      "id": "content",
      "position": "main",
      "wrapper": {
        "maxWidth": "1200px",
        "padding": "2rem 1rem"
      }
    },
    {
      "id": "footer",
      "position": "bottom",
      "module": {
        "type": "FooterModule",
        "data": { ... }
      }
    }
  ]
}

Assignation du Layout par page

{
  "meta": {
    "id": "landing-page",
    "slug": "/promo",
    "layout": "landing",
    "layoutOverrides": {
      "header": {
        "options": { "transparent": true }
      }
    }
  }
}

Layouts prédéfinis

Layout Header Sidebar Footer Usage
default ✅ Standard ✅ Standard Pages normales
landing ✅ Minimal/Transparent ❌ ou Minimal Landing pages
blog ✅ Standard ✅ Droite ✅ Standard Articles
docs ✅ Standard ✅ Gauche (nav) Documentation
admin ✅ Admin ✅ Menu Administration
blank Iframe, embed

Custom Layouts (runtime)

Comme les modules, les layouts peuvent être créés via l'admin :

/admin/layouts
├── Liste des layouts
├── Créer un layout
│   ├── Définir les zones
│   ├── Assigner les modules
│   ├── Options de style
│   └── Preview live
└── Assigner aux pages

Avantages

  1. Cohérence : Tout est module (header, footer, sidebar = modules)
  2. Flexibilité : Chaque page peut avoir son layout
  3. Réutilisabilité : Un layout = plusieurs pages
  4. Runtime : Créer des layouts sans rebuild
  5. Override : Surcharger les options par page

6. Récapitulatif Architecture Finale

┌─────────────────────────────────────────────────────────────────┐
│                         QWIKPRESS                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  CONNECTEURS ──────► DATA CATCHERS ──────► MODULES             │
│  (APIs)              (Fetch + Transform)   (UI Components)      │
│                             │                    │              │
│                             │    ┌───────────────┘              │
│                             │    │                              │
│                             ▼    ▼                              │
│                      ┌─────────────────┐                        │
│                      │     LAYOUTS     │                        │
│                      │  (Zones + CSS)  │                        │
│                      └────────┬────────┘                        │
│                               │                                 │
│                               ▼                                 │
│                      ┌─────────────────┐                        │
│                      │      PAGES      │                        │
│                      │ (Layout + Mods) │                        │
│                      └─────────────────┘                        │
│                                                                 │
├─────────────────────────────────────────────────────────────────┤
│  DEV TIME (compiled)          │  RUNTIME (hot/custom)          │
│  ─────────────────────        │  ────────────────────          │
│  src/modules/*                │  /admin/custom/modules         │
│  src/connectors/*             │  /admin/custom/connectors      │
│  src/catchers/*               │  /admin/custom/catchers        │
│  src/layouts/*                │  /admin/custom/layouts         │
└─────────────────────────────────────────────────────────────────┘

Tout peut être créé :

Même structure, même API, même résultat.



7. Layouts imbriqués (nested layouts)

Priorité : Haute Complexité : Moyenne

Concept

Comme Qwik-City, les layouts s'héritent et s'imbriquent via l'arborescence des pages :

pages/
├── _layout.json          ← Layout racine (header + footer)
├── home/
├── about/
├── blog/
│   ├── _layout.json      ← Layout blog (sidebar articles)
│   ├── index/            ← Liste articles (hérite blog + racine)
│   └── [slug]/           ← Article (hérite blog + racine)
├── docs/
│   ├── _layout.json      ← Layout docs (sidebar nav)
│   ├── getting-started/
│   │   ├── _layout.json  ← Layout sous-section (breadcrumb)
│   │   ├── install/
│   │   └── config/
│   └── api/
│       ├── _layout.json  ← Layout API (version selector)
│       └── endpoints/
└── admin/
    ├── _layout.json      ← Layout admin (remplace tout)
    └── ...

Chaînage des layouts

Une page /docs/getting-started/install passe par :

┌─────────────────────────────────────────────────────────────┐
│ Layout Racine (_layout.json)                                │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ [HeaderModule]                                          │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │                                                         │ │
│ │  Layout Docs (docs/_layout.json)                       │ │
│ │  ┌─────────────────────────────────────────────────┐   │ │
│ │  │ [DocsSidebarModule]  │  <slot>                  │   │ │
│ │  │                      │                          │   │ │
│ │  │  - Getting Started   │  Layout Getting-Started  │   │ │
│ │  │  - API Reference     │  ┌───────────────────┐   │   │ │
│ │  │  - Examples          │  │ [BreadcrumbModule]│   │   │ │
│ │  │                      │  ├───────────────────┤   │   │ │
│ │  │                      │  │                   │   │   │ │
│ │  │                      │  │  PAGE CONTENT     │   │   │ │
│ │  │                      │  │  (modules)        │   │   │ │
│ │  │                      │  │                   │   │   │ │
│ │  │                      │  └───────────────────┘   │   │ │
│ │  └─────────────────────────────────────────────────┘   │ │
│ │                                                         │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ [FooterModule]                                          │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Structure d'un _layout.json

{
  "id": "docs-layout",
  "name": "Documentation",
  "inherit": true,
  "zones": {
    "before": [
      {
        "module": "DocsSidebarModule",
        "position": "left",
        "width": "280px",
        "data": {
          "source": "auto"
        }
      }
    ],
    "content": {
      "wrapper": {
        "class": "docs-content",
        "maxWidth": "900px"
      }
    },
    "after": []
  },
  "options": {
    "containerClass": "docs-layout",
    "fullHeight": true
  }
}

Modes d'héritage

{
  "inherit": true,
  "inheritMode": "wrap"
}
Mode Comportement
"wrap" (défaut) Le layout parent enveloppe ce layout
"replace" Remplace complètement le layout parent
"extend" Fusionne les zones (ajoute sans remplacer)

Exemple replace (admin) :

{
  "id": "admin-layout",
  "inherit": false,
  "zones": {
    "header": { "module": "AdminHeaderModule" },
    "sidebar": { "module": "AdminSidebarModule" },
    "content": { "wrapper": { "class": "admin-main" } }
  }
}

→ Ignore complètement le layout racine

Slot et injection

Le layout enfant s'injecte dans le <slot> du parent :

Layout Parent:
┌────────────────────────┐
│ [Header]               │
├────────────────────────┤
│                        │
│       <slot/>          │  ← Layout enfant injecté ici
│                        │
├────────────────────────┤
│ [Footer]               │
└────────────────────────┘

Data Catchers dans les layouts

Les layouts peuvent aussi avoir des data catchers :

{
  "id": "blog-layout",
  "dataCatchers": [
    {
      "catcher": "blog-categories",
      "as": "categories"
    },
    {
      "catcher": "recent-posts",
      "as": "recentPosts",
      "options": { "limit": 5 }
    }
  ],
  "zones": {
    "sidebar": {
      "module": "BlogSidebarModule",
      "data": {
        "categories": "${layoutData.categories}",
        "recent": "${layoutData.recentPosts}"
      }
    }
  }
}

→ Les catchers du layout sont disponibles pour toutes les pages enfants via layoutData

Chaîne de données complète

globalData (site)
    └── layoutData[0] (layout racine)
            └── layoutData[1] (layout section)
                    └── layoutData[2] (layout sous-section)
                            └── pageData (page)
                                    └── moduleData (module)

Chaque niveau peut accéder aux données des niveaux supérieurs.

Interface Admin

/admin/pages
└── Structure des pages
    ├── 📁 / (racine)
    │   ├── ⚙️ _layout.json [Éditer]
    │   ├── 📄 home
    │   ├── 📁 blog/
    │   │   ├── ⚙️ _layout.json [Éditer]
    │   │   ├── 📄 index
    │   │   └── 📄 [slug]
    │   └── 📁 docs/
    │       ├── ⚙️ _layout.json [Éditer]
    │       ├── 📁 getting-started/
    │       │   ├── ⚙️ _layout.json [Éditer]
    │       │   └── 📄 install
    │       └── 📄 api

Clic sur un _layout.json → Éditeur de layout avec :

Résumé chaînage

URL: /docs/getting-started/install

Layouts appliqués (dans l'ordre):
1. /_layout.json        → Header + Footer
2. /docs/_layout.json   → + Sidebar docs
3. /docs/getting-started/_layout.json → + Breadcrumb

Data disponible:
- globalData (site)
- layoutData.root (catchers layout racine)
- layoutData.docs (catchers layout docs)  
- layoutData.gettingStarted (catchers layout getting-started)
- pageData (catchers page install)


8. Révision et corrections

Après réflexion, voici les clarifications et corrections :

8.1 Flux de données simplifié

Problème : layoutData[0], layoutData[1]... devient confus avec les nested layouts.

Solution : Cascade avec fusion et override

// Données disponibles dans un module
interface ModuleContext {
  global: GlobalData;      // Catchers globaux (site)
  layout: LayoutData;      // Catchers fusionnés de tous les layouts (enfant override parent)
  page: PageData;          // Catchers de la page
  module: ModuleData;      // Catchers du module lui-même

  // Helper pour accéder à tout
  get(key: string): any;   // Cherche dans module → page → layout → global
}

Fusion des layouts (du plus profond au racine) :

Layout racine : { nav: [...], footer: {...} }
Layout /docs : { sidebar: [...], footer: { override: true } }
Layout /docs/api : { apiVersion: "v2" }

Résultat fusionné :
{
  nav: [...],           // de racine
  sidebar: [...],       // de /docs
  footer: { override }, // de /docs (override racine)
  apiVersion: "v2"      // de /docs/api
}

8.2 Héritage de layout simplifié

Problème : wrap, replace, extend sont confus.

Solution : Override par zone

{
  "id": "docs-layout",
  "zones": {
    "header": "inherit",
    "sidebar": {
      "module": "DocsSidebarModule",
      "data": { ... }
    },
    "content": "inherit",
    "footer": null
  }
}
Valeur Comportement
"inherit" Utilise la zone du parent
null Supprime cette zone
{ module, data } Override avec ce module

Cas spécial : Layout admin qui remplace tout

{
  "id": "admin-layout",
  "resetParent": true,
  "zones": { ... }
}

8.3 Distinction Zone vs Slot

Zones (niveau Layout) :

Slots (niveau Module) :

LAYOUT (zones)
┌─────────────────────────────────┐
│ zone:header  [HeaderModule]     │
├──────────┬──────────────────────┤
│ zone:    │ zone:content         │
│ sidebar  │ ┌──────────────────┐ │
│          │ │ Module 1         │ │
│ [Sidebar │ │ ┌──────────────┐ │ │
│  Module] │ │ │ slot:default │ │ │  ← SLOT dans le module
│          │ │ └──────────────┘ │ │
│          │ ├──────────────────┤ │
│          │ │ Module 2         │ │
│          │ └──────────────────┘ │
├──────────┴──────────────────────┤
│ zone:footer  [FooterModule]     │
└─────────────────────────────────┘

8.4 Source de données pour Header/Footer

Problème : Si header/footer sont des modules, d'où viennent leurs données ?

Solution : Comme tout module, via data catchers

{
  "zones": {
    "header": {
      "module": "HeaderModule",
      "catcher": "site-navigation",
      "options": { "sticky": true }
    },
    "footer": {
      "module": "FooterModule",
      "catcher": "footer-data"
    }
  }
}

Ou données inline pour les cas simples :

{
  "zones": {
    "header": {
      "module": "HeaderModule",
      "data": {
        "logo": "/logo.png",
        "nav": [...]
      }
    }
  }
}

8.5 Stockage des layouts

Mode fichier :

STORAGE/data/
├── layouts/
│   ├── _root.json        → Layout racine
│   ├── docs.json         → Layout /docs/*
│   └── docs-api.json     → Layout /docs/api/*
└── pages/
    └── ...

Mapping path → layout :

// Dans config.json
{
  "layoutMappings": {
    "/": "_root",
    "/docs": "docs",
    "/docs/api": "docs-api",
    "/admin": "admin"
  }
}

Mode Supabase :

CREATE TABLE qwikpress.layouts (
  id UUID PRIMARY KEY,
  site_id UUID,
  path VARCHAR(255),      -- "/" ou "/docs" ou "/docs/api"
  config JSONB,
  created_at TIMESTAMP,
  updated_at TIMESTAMP,
  UNIQUE(site_id, path)
);

8.6 Sécurité Hot Modules

Problème : Exécuter du JS arbitraire = dangereux

Solutions :

Client JS :

Server JS :

Validation JSX :

8.7 Résumé architecture corrigée

┌─────────────────────────────────────────────────────────────┐
│                       QWIKPRESS v2                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐   │
│  │ CONNECTEURS │────▶│DATA CATCHERS│────▶│   MODULES   │   │
│  │  (APIs)     │     │(fetch+logic)│     │    (UI)     │   │
│  └─────────────┘     └──────┬──────┘     └─────────────┘   │
│                             │                   │           │
│         ┌───────────────────┼───────────────────┤           │
│         │                   │                   │           │
│         ▼                   ▼                   ▼           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                      LAYOUTS                        │   │
│  │  ┌─────────────────────────────────────────────┐   │   │
│  │  │ _root (zones: header, content, footer)      │   │   │
│  │  │  └── docs (override: +sidebar, footer:null) │   │   │
│  │  │       └── api (override: +version selector) │   │   │
│  │  └─────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────┘   │
│                             │                               │
│                             ▼                               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                       PAGES                         │   │
│  │  - Héritent du layout correspondant à leur path     │   │
│  │  - Contiennent les modules de la zone "content"     │   │
│  │  - Peuvent avoir leurs propres data catchers        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                     CRÉATION                                │
│                                                             │
│  DEV (TypeScript, compilé)    RUNTIME (Hot, navigateur)    │
│  ════════════════════════     ═══════════════════════════  │
│  src/modules/*.tsx            /admin/custom/modules        │
│  src/connectors/*.ts          /admin/custom/connectors     │
│  src/catchers/*.ts            /admin/custom/catchers       │
│  src/layouts/*.tsx            /admin/custom/layouts        │
│                                                             │
│  → Type-safe, performant      → Pas de rebuild, sandbox    │
│  → Tests unitaires            → Prototypage rapide         │
│  → Tree-shaking               → Modifications à chaud      │
└─────────────────────────────────────────────────────────────┘

8.8 Ordre d'exécution complet

Pour une requête sur /docs/api/endpoints :

1. LAYOUTS résolus (du plus spécifique au racine)
   └── /docs/api/_layout.json
   └── /docs/_layout.json  
   └── /_layout.json

2. DATA CATCHERS collectés et ordonnés
   ├── Global : site-settings, main-nav
   ├── Layout / : footer-data
   ├── Layout /docs : docs-sidebar, docs-search
   ├── Layout /docs/api : api-versions
   └── Page : endpoint-list

3. GRAPHE DE DÉPENDANCES construit
   site-settings (niveau 0)
   main-nav (niveau 0)
   docs-sidebar ──depends──▶ main-nav (niveau 1)
   api-versions (niveau 0)
   endpoint-list ──depends──▶ api-versions (niveau 1)

4. EXÉCUTION PARALLÈLE par niveau
   Niveau 0: site-settings, main-nav, api-versions (parallèle)
   Niveau 1: docs-sidebar, endpoint-list (parallèle, après niveau 0)

5. FUSION DES DONNÉES
   {
     global: { siteSettings, mainNav },
     layout: { footerData, docsSidebar, docsSearch, apiVersions },
     page: { endpointList }
   }

6. RENDU LAYOUTS (du racine au plus spécifique)
   /_layout → /docs/_layout → /docs/api/_layout → page content

7. RENDU MODULES avec données injectées


9. Clarifications supplémentaires

9.1 Sources de données automatiques (méta)

Les catchers ne sont pas la seule source de données. Le système expose automatiquement les métadonnées de base :

interface AutoData {
  // Métadonnées pages (pour construire menus, breadcrumbs, sitemap...)
  pages: {
    all: PageMeta[];           // Toutes les pages
    published: PageMeta[];     // Pages publiées uniquement
    tree: PageTreeNode[];      // Arborescence hiérarchique
    siblings: PageMeta[];      // Pages au même niveau
    children: PageMeta[];      // Pages enfants de la courante
    ancestors: PageMeta[];     // Fil d'Ariane (parents)
  };

  // Page courante
  currentPage: {
    meta: PageMeta;
    slug: string;
    path: string[];            // ["docs", "api", "endpoints"]
    depth: number;
  };

  // Site
  site: {
    name: string;
    description: string;
    url: string;
    logo: string;
  };

  // Layouts actifs
  layouts: {
    current: string[];         // ["_root", "docs", "docs-api"]
    config: LayoutConfig[];
  };
}

Exemple : Menu auto-généré

{
  "zones": {
    "header": {
      "module": "HeaderModule",
      "data": {
        "logo": "${auto.site.logo}",
        "nav": "${auto.pages.tree.filter(p => p.meta.inMenu)}"
      }
    }
  }
}

Exemple : Breadcrumb automatique

{
  "module": "BreadcrumbModule",
  "data": {
    "items": "${auto.currentPage.ancestors}",
    "current": "${auto.currentPage.meta.title}"
  }
}

Exemple : Sidebar enfants

{
  "module": "SidebarNavModule",
  "data": {
    "title": "Dans cette section",
    "links": "${auto.pages.children}"
  }
}

9.2 Héritage de layouts : ACCUMULATION par défaut

Point important : Les layouts s'accumulent (wrap), ils ne se remplacent pas.

Structure:
/                    → _root layout
/docs                → docs layout  
/docs/api            → docs-api layout
/docs/api/endpoints  → (pas de layout propre)

Requête sur /docs/api/endpoints :

Layouts appliqués (tous, imbriqués) :

┌─────────────────────────────────────────────────────────┐
│ _root layout                                            │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [HeaderModule]                                      │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │                                                     │ │
│ │  docs layout                                        │ │
│ │  ┌─────────────────────────────────────────────┐   │ │
│ │  │            │                                │   │ │
│ │  │ [DocsSide  │  docs-api layout              │   │ │
│ │  │  barMod]   │  ┌────────────────────────┐   │   │ │
│ │  │            │  │ [ApiVersionSelector]   │   │   │ │
│ │  │            │  ├────────────────────────┤   │   │ │
│ │  │            │  │                        │   │   │ │
│ │  │            │  │  PAGE CONTENT          │   │   │ │
│ │  │            │  │  (endpoints modules)   │   │   │ │
│ │  │            │  │                        │   │   │ │
│ │  │            │  └────────────────────────┘   │   │ │
│ │  │            │                                │   │ │
│ │  └─────────────────────────────────────────────┘   │ │
│ │                                                     │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ [FooterModule]                                      │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

On a les TROIS layouts, pas juste le plus spécifique.

9.3 Cas d'override explicite

Pour remplacer au lieu d'accumuler (cas rare) :

{
  "id": "admin-layout",
  "resetParent": true,
  "zones": { ... }
}

Ou pour supprimer une zone héritée :

{
  "id": "landing-layout",
  "zones": {
    "header": "inherit",
    "footer": null,
    "sidebar": null
  }
}

9.4 Schéma final des sources de données

┌─────────────────────────────────────────────────────────────┐
│                    SOURCES DE DONNÉES                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  AUTO (métadonnées système)     CATCHERS (fetch externe)   │
│  ═══════════════════════════    ════════════════════════   │
│  auto.pages.*                   Via connecteurs             │
│  auto.currentPage.*             + Transform                 │
│  auto.site.*                    + Cache                     │
│  auto.layouts.*                 + Dépendances               │
│                                                             │
│         │                              │                    │
│         └──────────────┬───────────────┘                    │
│                        ▼                                    │
│              ┌─────────────────┐                            │
│              │ CONTEXTE MODULE │                            │
│              ├─────────────────┤                            │
│              │ auto.*          │ ← Toujours dispo           │
│              │ global.*        │ ← Catchers site            │
│              │ layout.*        │ ← Catchers layouts fusionnés│
│              │ page.*          │ ← Catchers page            │
│              │ module.*        │ ← Catchers module          │
│              │ data.*          │ ← Data inline du module    │
│              └─────────────────┘                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

9.5 Résumé règles de layout

Situation Comportement
/ a un layout Toutes les pages héritent
/docs a un layout Pages /docs/* ont les DEUX (root + docs)
/docs/api a un layout Pages /docs/api/* ont les TROIS
resetParent: true Coupe la chaîne, repart de zéro
zone: null Supprime cette zone pour ce layout et ses enfants
zone: "inherit" Garde la zone du parent (défaut implicite)