SPEC — Système de Visibilité Starlight
Cycle : DOC → SPEC → CODE → TEST → INTÉG
Composant : Système de Visibilité Starlight
Fichier :docs/L2_Presentation/astro/starlight/SPEC-Starlight-Visibility.md
Statut : SPEC
1. Périmètre et Objectifs
Section titled “1. Périmètre et Objectifs”1.1. Objectifs
Section titled “1.1. Objectifs”- Spécifier le schéma Zod pour le champ
visibility - Définir la configuration content.config.ts
- Spécifier les modifications du middleware
- Définir la stratégie de migration des documents existants
- Spécifier les tests de validation
1.2. Contraintes
Section titled “1.2. Contraintes”- Secure by default : tous les documents privés par défaut
- Utiliser Zod pour validation et typage TypeScript
- Intégration transparente avec Starlight
- Aucun impact sur les documents sans champ
visibility
2. Spécification Schéma Zod
Section titled “2. Spécification Schéma Zod”2.1. Extension Schema Starlight
Section titled “2.1. Extension Schema Starlight”Configuration content.config.ts :
import { defineCollection } from 'astro:content';import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders';import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';import { z } from 'astro/zod';
const docs = defineCollection({ loader: docsLoader(), schema: docsSchema({ extend: z.object({ // Champ de visibilité : public ou private (par défaut) visibility: z.enum(['public', 'private']).default('private'), }), }),});
export const collections = { docs, i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),};2.2. Interface TypeScript Générée
Section titled “2.2. Interface TypeScript Générée”Interface générée automatiquement par Astro :
interface DocsEntry { id: string; slug: string; body: string; collection: 'docs'; data: { title: string; description: string; visibility: 'public' | 'private'; // Nouveau champ // ... autres champs Starlight };}2.3. Validation Zod
Section titled “2.3. Validation Zod”Règles de validation :
visibilitydoit être'public'ou'private'- Valeur par défaut :
'private' - Si champ absent :
'private'(secure by default) - Si valeur invalide : erreur de validation Zod
3. Spécification Frontmatter
Section titled “3. Spécification Frontmatter”3.1. Format Frontmatter
Section titled “3.1. Format Frontmatter”Document public :
---title: "Guide de Démarrage"description: "Guide pour les nouveaux développeurs"visibility: "public"---
Contenu du guide...Document privé (valeur par défaut) :
---title: "Architecture Interne"description: "Documentation technique sensible"visibility: "private"---
Contenu technique sensible...Document sans champ (privé par défaut) :
---title: "Document Privé"description: "Ce document est privé par défaut"---
Contenu...3.2. Stratégie de Visibilité
Section titled “3.2. Stratégie de Visibilité”Règle 1 : Par défaut, tous les documents sont privés
Règle 2 : Explicitement marquer les documents publics
Règle 3 : overview est public via override middleware
Règle 4 : Audit régulier des documents publics
4. Spécification Middleware
Section titled “4. Spécification Middleware”4.1. Logique Middleware Actuelle
Section titled “4.1. Logique Middleware Actuelle”Fichier : apps/web/src/middleware.ts
Logique existante :
let isPrivate = true;
try { const entry = await getEntry('docs', docId); if (entry && entry.data) { const data = entry.data as { visibility?: 'public' | 'private' }; if (data.visibility === 'public') { isPrivate = false; } }} catch (_e) { isPrivate = true;}
// Override pour overviewif (docId === 'overview') { isPrivate = false;}4.2. Modification Requise
Section titled “4.2. Modification Requise”Amélioration de la logique :
let isPrivate = true;
try { const entry = await getEntry('docs', docId); if (entry && entry.data) { const data = entry.data as { visibility?: 'public' | 'private' }; // Si visibility est explicitement 'public', document public if (data.visibility === 'public') { isPrivate = false; } }} catch (_e) { // En cas d'erreur de lecture, considérer comme privé (secure by default) isPrivate = true;}
// Override explicite pour overview (public par défaut)if (docId === 'overview') { isPrivate = false;}
// Pour les documents privés, vérifier l'authentificationif (isPrivate) { const sessionToken = context.cookies.get('auth_session')?.value; if (!sessionToken) { return context.redirect(`/login?redirectTo=${encodeURIComponent(pathname)}`); }
const isAuthValid = await verifyAuthSession(sessionToken); if (!isAuthValid.valid) { return context.redirect(`/login?redirectTo=${encodeURIComponent(pathname)}`); }
context.locals.user = isAuthValid.user;}4.3. Interface TypeScript
Section titled “4.3. Interface TypeScript”Déclaration types :
declare namespace App { interface Locals { user?: { id: string; email: string; role: string; } | null; isDocPrivate?: boolean; }}5. Spécification Configuration Starlight
Section titled “5. Spécification Configuration Starlight”5.1. Configuration Sidebar
Section titled “5.1. Configuration Sidebar”Fichier : apps/web/astro.config.mjs
Configuration actuelle (déjà correcte) :
sidebar: [ { label: 'Getting Started', items: [{ autogenerate: { directory: 'gettingStarted' } }], }, { label: 'Documentation Technique', items: [{ autogenerate: { directory: 'docs' } }], },],Note : La configuration existante est déjà correcte avec les répertoires gettingStarted et docs.
5.2. Comportement Sidebar
Section titled “5.2. Comportement Sidebar”Mise à jour (Octobre 2026) : Voir la spécification dédiée SPEC-Starlight-Dual-Sidebar-Algolia.md et l’ADR ADR-Starlight-Dual-Sidebar-Algolia.md.
Règles initiales v1.0 :
- Sidebar affiche TOUS les documents (publics et privés)
- Navigation fonctionne pour tous les documents
- Accès aux documents privés redirige vers login
Nouvelles règles bimodales v2.0 (SPEC-Starlight-Dual-Sidebar-Algolia) :
- Visiteur public (non connecté) : Sidebar filtrée dynamiquement via
routeMiddlewarepour afficher uniquement les documents publics (visibility: 'public'). Élagage des groupes vides et de la pagination. - Membre authentifié (connecté) : Sidebar complète affichant documents publics et privés, avec ajout de badges visuels « Privé ».
6. Spécification Migration Documents
Section titled “6. Spécification Migration Documents”6.1. Stratégie de Migration
Section titled “6.1. Stratégie de Migration”Approche progressive :
- Aucune modification requise pour les documents privés (défaut)
- Ajouter
visibility: "public"aux documents devant être publics overviewreste public via override middleware
6.2. Documents Candidats Publics
Section titled “6.2. Documents Candidats Publics”Critères pour document public :
- Documentation Getting Started générale
- Guides de démarrage non sensibles
- Documentation API publique
- Overview et pages d’accueil
Critères pour document privé :
- Architecture interne
- Spécifications techniques sensibles
- Configuration et secrets
- Documentation de développement interne
6.3. Script de Migration (Optionnel)
Section titled “6.3. Script de Migration (Optionnel)”Script pour marquer des documents comme publics :
# Marquer tous les docs d'un répertoire comme publicsfind src/content/docs/gettingStarted -name "*.md" -exec sed -i '2a visibility: "public"' {} \;Note : Utiliser avec précaution, vérifier manuellement après exécution.
7. Spécification Tests
Section titled “7. Spécification Tests”7.1. Tests Schéma
Section titled “7.1. Tests Schéma”Test validation Zod :
describe('Visibility Schema Validation', () => { it('should accept "public" value', () => { const result = docsSchema.parse({ title: 'Test', description: 'Test', visibility: 'public' }); expect(result.visibility).toBe('public'); });
it('should accept "private" value', () => { const result = docsSchema.parse({ title: 'Test', description: 'Test', visibility: 'private' }); expect(result.visibility).toBe('private'); });
it('should default to "private" when omitted', () => { const result = docsSchema.parse({ title: 'Test', description: 'Test' }); expect(result.visibility).toBe('private'); });
it('should reject invalid values', () => { expect(() => { docsSchema.parse({ title: 'Test', description: 'Test', visibility: 'invalid' }); }).toThrow(); });});7.2. Tests Middleware
Section titled “7.2. Tests Middleware”Test protection documents privés :
describe('Middleware Visibility', () => { it('should allow access to public documents without auth', async () => { const response = await fetch('/docs/public-doc'); expect(response.status).toBe(200); });
it('should redirect to login for private documents without auth', async () => { const response = await fetch('/docs/private-doc'); expect(response.status).toBe(302); expect(response.headers.get('location')).toContain('/login'); });
it('should allow access to private documents with valid auth', async () => { const response = await fetch('/docs/private-doc', { headers: { Cookie: 'auth_session=valid-token' } }); expect(response.status).toBe(200); });
it('should treat overview as public', async () => { const response = await fetch('/overview'); expect(response.status).toBe(200); });});7.3. Tests Intégration Starlight
Section titled “7.3. Tests Intégration Starlight”Test sidebar et navigation :
describe('Starlight Integration', () => { it('should display all documents in sidebar', async () => { const response = await fetch('/docs'); const html = await response.text(); expect(html).toContain('Getting Started'); expect(html).toContain('Documentation Technique'); });
it('should generate correct TypeScript types', async () => { // Vérifier que le typecheck passe const result = await exec('pnpm typecheck'); expect(result.exitCode).toBe(0); });});8. Spécification Performance
Section titled “8. Spécification Performance”8.1. Impact Performance
Section titled “8.1. Impact Performance”Impact minimal :
- Lecture frontmatter : négligeable (déjà fait par Starlight)
- Middleware : vérification simple du champ
visibility - Aucun impact sur le build statique
8.2. Optimisation
Section titled “8.2. Optimisation”Cache middleware :
- Les résultats
getEntrysont cachés par Astro - Aucune optimisation supplémentaire requise
9. Spécification Sécurité
Section titled “9. Spécification Sécurité”9.1. Secure by Default
Section titled “9.1. Secure by Default”Principe :
- Valeur par défaut :
'private' - En cas d’erreur : considérer comme privé
- Override explicite uniquement pour
overview
9.2. Validation
Section titled “9.2. Validation”Validation Zod :
- Empêche les valeurs invalides
- Typage TypeScript automatique
- Erreurs de build si valeurs invalides
10. Critères de Validation
Section titled “10. Critères de Validation”10.1. Validation Technique
Section titled “10.1. Validation Technique”- Schéma Zod configuré dans content.config.ts
- Middleware modifié pour lire
visibility - TypeScript types générés correctement
- Build réussi sans erreurs
10.2. Validation Fonctionnelle
Section titled “10.2. Validation Fonctionnelle”- Documents publics accessibles sans auth
- Documents privés redirigent vers login
- Overview accessible sans auth
- Sidebar affiche tous les documents
- Navigation fonctionne correctement
10.3. Validation Sécurité
Section titled “10.3. Validation Sécurité”- Par défaut privé (secure by default)
- Erreur de lecture → document privé
- Validation Zod empêche valeurs invalides
- Aucun document sensible public par accident
11. Documentation Utilisateur
Section titled “11. Documentation Utilisateur”11.1. Guide pour Éditeurs
Section titled “11.1. Guide pour Éditeurs”Comment marquer un document public :
---title: "Mon Document Public"description: "Description"visibility: "public"---
Contenu...Comment garder un document privé :
---title: "Mon Document Privé"description: "Description"# visibility: "private" # Optionnel, c'est le défaut---
Contenu...11.2. Checklist Publication
Section titled “11.2. Checklist Publication”Avant de publier un document :
- Le contenu est-il sensible ?
- Si oui, ne PAS ajouter
visibility: "public" - Si non, ajouter
visibility: "public" - Vérifier que le document s’affiche correctement
12. Historique des Modifications
Section titled “12. Historique des Modifications”| Date | Version | Description | Auteur |
|---|---|---|---|
| 2026-09-27 | 1.0 | Création initiale SPEC Système Visibilité | Team (Agent) |