Skip to content

SPEC — Système de Visibilité Starlight

SPEC — Système de Visibilité Starlight

Spécification technique détaillée

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

1.1. Objectifs

  1. Spécifier le schéma Zod pour le champ visibility
  2. Définir la configuration content.config.ts
  3. Spécifier les modifications du middleware
  4. Définir la stratégie de migration des documents existants
  5. Spécifier les tests de validation

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

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

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

Règles de validation :

  • visibility doit ê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

3.1. Format Frontmatter1/3

Document public :

---
title: "Guide de Démarrage"
description: "Guide pour les nouveaux développeurs"
visibility: "public"
---
Contenu du guide...

3.1. Format Frontmatter2/3

Document privé (valeur par défaut) :

---
title: "Architecture Interne"
description: "Documentation technique sensible"
visibility: "private"
---
Contenu technique sensible...

3.1. Format Frontmatter3/3

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é

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

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 overview
if (docId === 'overview') {
isPrivate = false;
}

4.2. Modification Requise1/2

Amélioration de la logique :

4.2. Modification Requise2/2

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'authentification
if (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

Déclaration types :

apps/web/src/env.d.ts
declare namespace App {
interface Locals {
user?: {
id: string;
email: string;
role: string;
} | null;
isDocPrivate?: boolean;
}
}

5. Spécification Configuration Starlight

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 Sidebar1/2

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

5.2. Comportement Sidebar2/2

Nouvelles règles bimodales v2.0 (SPEC-Starlight-Dual-Sidebar-Algolia) :

  • Visiteur public (non connecté) : Sidebar filtrée dynamiquement via routeMiddleware pour 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

6.1. Stratégie de Migration

Approche progressive :

  1. Aucune modification requise pour les documents privés (défaut)
  2. Ajouter visibility: "public" aux documents devant être publics
  3. overview reste public via override middleware

6.2. Documents Candidats Publics1/2

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

6.2. Documents Candidats Publics2/2

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)

Script pour marquer des documents comme publics :

Terminal window
# Marquer tous les docs d'un répertoire comme publics
find 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

7.1. Tests Schéma1/2

Test validation Zod :

7.1. Tests Schéma2/2

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

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

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

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

Cache middleware :

  • Les résultats getEntry sont cachés par Astro
  • Aucune optimisation supplémentaire requise

9. Spécification Sécurité

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

Validation Zod :

  • Empêche les valeurs invalides
  • Typage TypeScript automatique
  • Erreurs de build si valeurs invalides

10. Critères de Validation

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

  • 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é

  • 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

11.1. Guide pour Éditeurs1/2

Comment marquer un document public :

---
title: "Mon Document Public"
description: "Description"
visibility: "public"
---
Contenu...

11.1. Guide pour Éditeurs2/2

Comment garder un document privé :

---
title: "Mon Document Privé"
description: "Description"
# visibility: "private" # Optionnel, c'est le défaut
---
Contenu...

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

Date Version Description Auteur
2026-09-27 1.0 Création initiale SPEC Système Visibilité Team (Agent)
Switch to normal mode

Contents