From 5779daf34d3bc3829d36a57560777c0eefec3be6 Mon Sep 17 00:00:00 2001 From: kayjaydee Date: Wed, 22 Apr 2026 09:15:55 +0200 Subject: [PATCH] docs(06-02): complete components UI + i18n locales plan - Add 06-02-SUMMARY.md with 3 task commits (d299383, 0e42a05, d0ebf35) - Update STATE.md : plan counter 11/15 (73%), next = 06-03 listing page - Update ROADMAP.md Phase 6 progress : 2/4 plans complete - Record gotcha 06-02 : slug derivation via path.split('/').filter(Boolean).pop() --- .planning/ROADMAP.md | 2 +- .planning/STATE.md | 16 +- .../phases/06-blog-pages/06-02-SUMMARY.md | 242 ++++++++++++++++++ 3 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 .planning/phases/06-blog-pages/06-02-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 91a82a2..3f045ca 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -190,6 +190,6 @@ Plans: | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| | 5. @nuxt/content Setup & Renderer | 2/2 | Complete | 2026-04-22 | -| 6. Blog Pages | 1/4 | In progress | - | +| 6. Blog Pages | 2/4 | In progress | - | | 7. SEO Blog | 0/? | Not started | - | | 8. Content & Cocon Semantique | 0/? | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 20ed006..f07186c 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,15 +2,15 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone -status: Phase 6 — Plan 06-01 shipped (1/4), ready for Plan 06-02 -last_updated: "2026-04-22T09:10:00.000Z" +status: Phase 6 — Plan 06-02 shipped (2/4), ready for Plan 06-03 +last_updated: "2026-04-22T09:25:00.000Z" last_activity: 2026-04-22 progress: total_phases: 8 completed_phases: 3 total_plans: 15 - completed_plans: 10 - percent: 66 + completed_plans: 11 + percent: 73 --- # Project State @@ -24,10 +24,10 @@ progress: ## Current Focus Phase: Phase 6 — Blog Pages -Plan: 06-02 (next — Wave 1 also, components UI + i18n locales) -Status: Plan 06-01 shipped — schema + reading-time hook + drafts en place, typecheck vert, cache @nuxt/content vidé +Plan: 06-03 (next — Wave 3, listing page /blog) +Status: Plan 06-02 shipped — i18n FR+EN complet, nav link Blog en place, BlogCard.vue (variant default+compact) auto-importable, typecheck vert Last activity: 2026-04-22 -Resume file: .planning/phases/06-blog-pages/06-02-PLAN.md +Resume file: .planning/phases/06-blog-pages/06-03-PLAN.md ## Accumulated Context @@ -45,3 +45,5 @@ Resume file: .planning/phases/06-blog-pages/06-02-PLAN.md - **Plan 06-01 shipped (2026-04-22)** : blogSchema étendu (draft.default(false) + wordCount.optional + minutes.optional), Nitro hook `content:file:afterParse` injecte wordCount+minutes (200 wpm, floor 1 min) sur chaque `.md` via `countWordsInMinimalBody`, composable fallback `useReadingTime(number|string)` auto-importé, articles `test-kotlin-syntax.md` (FR+EN) marqués `draft: true` — exclus des listings `where('draft', '=', false)` mais accessibles par URL directe. Cache `node_modules/.cache/content` + `.nuxt` vidés. - **Gotcha 06-01** : Le hook `content:file:afterParse` exige que `wordCount`/`minutes` soient déclarés dans le schema Zod (`.optional()` sans default) sinon ils sont strippés avant persistance DB — les propriétés injectées par hook ne sont queryables que si le schema les expose. - **Gotcha 06-01 (convention)** : Dans un plugin Nitro, importer depuis `app/utils/` se fait via `~/utils/...` (et non `~~/app/utils/...`). Nuxt 4 mappe `~/` → `app/` par défaut. Vérifié par typecheck vert sur server/plugins/reading-time.ts. +- **Plan 06-02 shipped (2026-04-22)** : i18n `nav.blog` + 3 clés `a11y.blog*` (avec interpolation `{title}`) + bloc `blog.*` 14 clés (title, subtitle, stats.*, readingTime, prevArticle/nextArticle, backToBlog, toc.title, emptyState.*, breadcrumb.*) ajoutés dans fr.json + en.json. AppHeader.vue navLinks : `{ key: 'blog', path: '/blog' }` inséré entre hytale et projects (ligne 11, ordre D-15 respecté). `app/components/BlogCard.vue` créé (192 lignes, auto-importé Nuxt) : variant `default` (listing) avec cover conditional + tag UBadge + date Intl.DateTimeFormat + h2 + description line-clamp-2 + reading-time (minutes hook || useReadingTime fallback) + extra tags pills + full-card NuxtLink SEO + Schema.org BlogPosting markup ; variant `compact` (prev/next, D-09/D-10) : no image + label row avec UIcon arrow directionnelle + h3 + date + NuxtLink aria-label interpolé `a11y.blogPrev`/`a11y.blogNext`. Typecheck exit 0. +- **Gotcha 06-02 (slug derivation)** : Les articles @nuxt/content ont un `path` de forme `/fr/blog/my-slug`. Dans BlogCard.vue, on extrait le slug via `article.path.split('/').filter(Boolean).pop()` puis on reconstruit `localePath('/blog/' + slug)` — locale-agnostique. Évite de demander un champ `slug` explicite dans le frontmatter (cohérent convention @nuxt/content : path dérivé du nom de fichier). diff --git a/.planning/phases/06-blog-pages/06-02-SUMMARY.md b/.planning/phases/06-blog-pages/06-02-SUMMARY.md new file mode 100644 index 0000000..fa4b63b --- /dev/null +++ b/.planning/phases/06-blog-pages/06-02-SUMMARY.md @@ -0,0 +1,242 @@ +--- +phase: 06-blog-pages +plan: 02 +subsystem: ui-components +tags: [blog, i18n, nav, blog-card, shared-components] + +requires: + - phase: 06-blog-pages + plan: 01 + provides: "blogSchema étendu (draft/wordCount/minutes) + useReadingTime composable fallback — consommés par BlogCard.vue" +provides: + - "Clés i18n complètes blog.* + nav.blog + a11y.blog* en FR et EN (14 clés par locale)" + - "Lien nav Blog dans AppHeader entre Hytale et Projects (desktop + mobile)" + - "Composant BlogCard.vue unifié avec variant default (listing) + compact (prev/next)" +affects: [06-03-blog-listing, 06-04-blog-article-chrome] + +tech-stack: + added: [] + patterns: + - "Pattern composant multi-variant via prop discriminante + v-if branch (variant='default'|'compact')" + - "Pattern slug derivation depuis article.path @nuxt/content (split /filter(Boolean).pop())" + - "Pattern i18n date formatting via Intl.DateTimeFormat + locale.value guard" + - "Pattern absolute inset-0 NuxtLink pour SEO + full-card click (cohabite avec tags non-cliquables D-02)" + - "Pattern Schema.org BlogPosting prêt pour JSON-LD Phase 7 (headline/description/keywords/url/image/datePublished)" + - "Pattern reading-time avec injection hook + fallback composable (minutes ?? useReadingTime(description))" + +key-files: + created: + - app/components/BlogCard.vue + modified: + - i18n/locales/fr.json + - i18n/locales/en.json + - app/components/layout/AppHeader.vue + +key-decisions: + - "BlogCard unique avec variant prop (D-20) plutôt que 2 composants séparés — 1 source of truth pour date/slug/reading-time" + - "Slug extrait du dernier segment du path (split/filter/pop) plutôt qu'un champ frontmatter dédié — cohérent @nuxt/content convention, zero burden pour auteur" + - "Reading-time : minutes injecté par hook Nitro prioritaire, useReadingTime(description) en fallback uniquement — évite drift listing vs article" + - "Variant compact sans image (D-10) + text-right sur next / text-left sur prev — UX directionnelle (flèche + texte suivent la direction du clic)" + - "FR i18n accentué dans bloc blog.* (Bientôt, précédent, Sommaire) suivant convention PATTERNS.md §i18n — cohérent avec bloc projects, distinct de a11y/seo (ASCII)" + +requirements-completed: [BLOG-02, BLOG-03, BLOG-06] + +duration: ~15min +completed: 2026-04-22 +--- + +# Phase 6 Plan 02 : Components UI + i18n Locales Summary + +**Couche composition partagée : clés i18n blog complètes (FR+EN), lien nav Blog, composant BlogCard.vue unifié variant default/compact — prêt pour Wave 3 (pages listing + article).** + +## Performance + +- **Duration:** ~15 min +- **Started:** 2026-04-22T09:10Z +- **Completed:** 2026-04-22T09:25Z +- **Tasks:** 3 / 3 +- **Files modified:** 4 (1 créé, 3 modifiés) + +## Accomplishments + +- **i18n complet** : 14 clés par locale ajoutées — `nav.blog`, 3 clés `a11y.blog*` avec interpolation `{title}`, bloc `blog.*` de 14 clés (title, subtitle, stats.articles/tags/languages, readingTime avec `{minutes}`, prevArticle, nextArticle, backToBlog, toc.title, emptyState.title/description/cta, breadcrumb.home/blog). FR accentué, EN traduction complète. JSON valide des 2 fichiers. +- **Nav link Blog** : insertion d'1 ligne dans `navLinks` computed de AppHeader.vue, position ligne 11 (entre hytale ligne 10 et projects ligne 12). Aucune autre modification — template `v-for` existant propage automatiquement au desktop + mobile slideover. +- **BlogCard.vue** : composant unique 192 lignes, 2 variants branchés par `v-if="variant === 'default'"` / `v-else` (compact). Script setup TS strict. Props `article` + `variant?='default'|'compact'` + `direction?='prev'|'next'`. Date formatée `Intl.DateTimeFormat` avec locale dynamique. Reading time avec fallback composable. Schema.org `BlogPosting` markup prêt pour JSON-LD Phase 7. 3 occurrences de `localePath(\`/blog/\${slug}\`)` (NuxtLink image + full-card SEO + variant compact). + +## Task Commits + +1. **Task 2.1 : i18n FR + EN** — `d299383` (feat) +2. **Task 2.2 : Nav link Blog dans AppHeader** — `0e42a05` (feat) +3. **Task 2.3 : BlogCard.vue variant default + compact** — `d0ebf35` (feat) + +## Files Created/Modified + +### Created + +- `app/components/BlogCard.vue` *(NEW, 192 lignes)* + - `