diff --git a/.planning/phases/06-blog-pages/06-04-SUMMARY.md b/.planning/phases/06-blog-pages/06-04-SUMMARY.md new file mode 100644 index 0000000..d990425 --- /dev/null +++ b/.planning/phases/06-blog-pages/06-04-SUMMARY.md @@ -0,0 +1,149 @@ +--- +phase: 06-blog-pages +plan: "04" +subsystem: blog-article-chrome +tags: [blog, article-chrome, toc, prev-next, intersection-observer, breadcrumb] +dependency_graph: + requires: ['01', '02'] + provides: [blog-article-chrome, BlogToc, BlogPrevNext] + affects: + - app/pages/blog/[slug].vue + - app/components/BlogToc.vue + - app/components/BlogPrevNext.vue +key_files: + created: + - app/components/BlogToc.vue + - app/components/BlogPrevNext.vue + modified: + - app/pages/blog/[slug].vue +decisions: + - "UDrawer prop name = `direction` (pas `side`) pour Nuxt UI v3 — validé via DrawerProps Pick. USlideover dans AppHeader.vue reste avec `side` (composant différent)." + - "isFr converti en computed — Phase 5 avait `const isFr = locale.value === 'fr'` (non-réactif au switch). Corrigé via Pitfall 3." + - "{ watch: [locale] } sur les 2 useAsyncData — article ET surround doivent re-fetch au switch FR/EN." + - "Mapping surround[0]=next / surround[1]=prev : Pitfall 4 — en `.order('date','DESC')` le surroundings helper retourne [before-in-collection, after-in-collection]. Avec DESC : before = plus récent (next UI), after = plus ancien (prev UI)." + - "Query article principale SANS filtre draft — D-14 : accès direct par URL reste possible pour test/preview. Surround AVEC filtre draft — les drafts ne polluent jamais la nav prev/next." + - "TocLink type local dans [slug].vue — duplique celui de BlogToc.vue mais évite un shared-types file pour cette phase. À consolider en Phase 7 si besoin." + - "SurroundArticle type local + cast explicite — @nuxt/content v3 expose surround comme ContentNavigationItem[] (type minimal) qui n'inclut pas `date` statiquement, même quand `fields:['date']` est passé en runtime. Le cast est safe car fields[] garantit la présence runtime." + - "tocLinks type cast via `page.value?.body as { toc?: ... }` — même raison : body est typé 'minimal' (tuples) en v3." + - "Cover image `loading=\"eager\"` (pas `lazy`) — hero above-the-fold, LCP optimisation. Opposé de BlogCard listing." + - "Layout grid desktop `lg:grid-cols-[1fr_16rem] lg:gap-12` — colonne article flex + aside TOC 256px fixe. Wrapper interne `max-w-3xl mx-auto lg:mx-0` pour lisibilité prose (D-08)." +metrics: + duration: "~15 min (exécution inline après bascule subagent Task freeze)" + completed: "2026-04-22" + tasks_completed: 3 + tasks_total: 3 + files_created: 2 + files_modified: 1 + checkpoint: "none (autonomous)" +--- + +# Phase 06 Plan 04: Article Chrome Summary + +Phase 6 se termine avec l'enrichissement substantiel de la page article `/blog/[slug]`. Ajout de 2 composants réutilisables (`BlogToc` sticky+drawer+observer, `BlogPrevNext` grid 2 cols) et refactorisation complète du script/template de `[slug].vue` pour passer du minimal Phase 5 au chrome complet Phase 6 : breadcrumb, header riche, TOC sticky/drawer, prev/next cards. + +## Tasks Completed + +| Task | Name | Commit | Files | +|------|------|--------|-------| +| 4.1 | Créer BlogToc.vue (sticky + drawer + IntersectionObserver) | `b72b564` | app/components/BlogToc.vue | +| 4.2 | Créer BlogPrevNext.vue (grid 2 cols BlogCard compact) | `0ff3678` | app/components/BlogPrevNext.vue | +| 4.3 | Enrichir [slug].vue (breadcrumb + header + TOC + surround + prev/next) | `f18b0bf` | app/pages/blog/[slug].vue | + +## Decisions Made + +1. **UDrawer `direction` prop** — validé empiriquement via `node_modules/@nuxt/ui/.../Drawer.vue.d.ts` qui fait `Pick`. USlideover (utilisé dans AppHeader) reste avec `side` — ce sont deux composants différents de Nuxt UI v3. + +2. **Fix Pitfall 3 (isFr non-réactif)** — Phase 5 avait `const isFr = locale.value === 'fr'` au top-level du setup (capture one-shot). Phase 6 convertit en `computed(() => locale.value === 'fr')`. Sans ça + sans `{ watch: [locale] }`, le switch langue gardait l'article FR même sur URL `/en/...`. + +3. **Mapping prev/next inversé (Pitfall 4)** — `queryCollectionItemSurroundings` retourne `[before, after]` dans l'ordre de la collection. Avec `.order('date', 'DESC')` la collection est triée du plus récent au plus ancien, donc `surround[0]` (before) = plus récent = **next UI** ("article suivant" en chronologie blog conventionnelle), `surround[1]` (after) = plus ancien = **prev UI** ("article précédent"). Commentaire explicite dans le code pour éviter futurs bugs. + +4. **Asymétrie draft filter** — la query article principale utilise `queryCollection('blog_fr').path(path).first()` SANS `.where('draft')` → un article marqué draft reste accessible par URL directe (test/preview, D-14). La query surround utilise `queryCollectionItemSurroundings(...).where('draft', '=', false)` → les drafts ne sont jamais proposés comme voisins. + +5. **Types locaux `TocLink` + `SurroundArticle`** — dupliqués avec BlogToc.vue et BlogPrevNext.vue mais évite un shared-types file pour cette phase. Le cast est nécessaire car @nuxt/content v3 expose `body` comme type `'minimal'` (tuples d'AST) et `ContentNavigationItem` (surround return) ne déclare pas `date`/`tags`/`image` statiquement même si le runtime les fournit via `fields[]`. + +6. **Layout grid responsive** — `lg:grid lg:grid-cols-[1fr_16rem] lg:gap-12` sur desktop : colonne article flex (avec `max-w-3xl mx-auto lg:mx-0` pour lisibilité prose D-08) + aside TOC 16rem (256px) fixe. Sur mobile (