--- phase: 08-content-cocon-semantique plan: 01 type: execute wave: 1 depends_on: [] files_modified: - app/components/HytaleRecentArticles.vue - app/pages/hytale.vue - i18n/locales/fr.json - i18n/locales/en.json autonomous: true requirements: [SEO-14] tags: [nuxt-content, i18n, hytale, cocon-semantique] must_haves: truths: - "Visiter /hytale affiche une section 'Articles récents' (uniquement si ≥1 article tagué hytale avec draft:false existe en base content)" - "La section réutilise BlogCard variant compact en grille 2 colonnes desktop / 1 colonne mobile" - "Switch FR/EN met à jour la section (useAsyncData key inclut la locale + watch)" - "Si 0 article hytale publié, la section est entièrement masquée (pas d'empty state)" - "Un lien 'Voir tous les articles' pointe vers /blog (FR) ou /en/blog (EN) via localePath" artifacts: - path: "app/components/HytaleRecentArticles.vue" provides: "Section composant auto-importé, queryCollection branches littérales + filtre tag hytale + limit 2" contains: "queryCollection('blog_fr')" - path: "app/pages/hytale.vue" provides: "Insertion avant fermeture du root div" contains: " Scaffolder l'infrastructure technique du cocon sémantique côté /hytale : composant `HytaleRecentArticles.vue` (queryCollection bilingue, filtre tag=hytale, limit 2, masqué si vide), injection dans `app/pages/hytale.vue`, et clés i18n associées en FR/EN. Purpose: Préparer le conteneur qui affichera les 2 articles seed publiés en Wave 2. Le composant doit dégrader gracieusement (v-if=length) tant que les articles ne sont pas encore publiés, ce qui permet de shipper cette Wave 1 sans casser /hytale. Output: 1 nouveau composant + 1 page modifiée + 2 fichiers i18n mis à jour. Aucun article créé à ce stade. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-content-cocon-semantique/08-CONTEXT.md @.planning/phases/08-content-cocon-semantique/08-PATTERNS.md @app/pages/blog/index.vue @app/components/BlogCard.vue @app/pages/hytale.vue ```typescript interface BlogArticle { path: string title: string description?: string date: string tags?: string[] image?: string minutes?: number } interface Props { article: BlogArticle variant?: 'default' | 'compact' direction?: 'prev' | 'next' // default 'next' } ``` ```typescript const { t, locale } = useI18n() const localePath = useLocalePath() const isFr = computed(() => locale.value === 'fr') const { data: articles } = await useAsyncData( `hytale-recent-${locale.value}`, () => isFr.value ? queryCollection('blog_fr') .where('draft', '=', false) .order('date', 'DESC') .all() : queryCollection('blog_en') .where('draft', '=', false) .order('date', 'DESC') .all(), { watch: [locale] }, ) ``` Task 1: Créer composant HytaleRecentArticles.vue (query + filtre tag + render) app/components/HytaleRecentArticles.vue - app/pages/blog/index.vue (pattern queryCollection bilingue branches littérales, lignes 1-21) - app/components/BlogCard.vue (variant compact, props interface lignes 2-21) - .planning/phases/08-content-cocon-semantique/08-PATTERNS.md §"HytaleRecentArticles.vue" - .planning/phases/08-content-cocon-semantique/08-CONTEXT.md §D-10, D-11, D-12, D-13 Créer `app/components/HytaleRecentArticles.vue` (~70-90 lignes). **Script setup (TypeScript strict) :** - `const { t, locale } = useI18n()` + `const localePath = useLocalePath()` - `const isFr = computed(() => locale.value === 'fr')` - `useAsyncData` avec key littérale interpolée `` `hytale-recent-${locale.value}` ``, ternaire `isFr.value` → `queryCollection('blog_fr')` / `queryCollection('blog_en')` (branches littérales obligatoires — Pitfall Phase 5 D-03, voir STATE.md gotcha). - Chaîne de la query : `.where('draft', '=', false).order('date', 'DESC').all()` — **SANS `.limit(2)` au SQL ni `.where('tags', 'LIKE', ...)`** (l'opérateur LIKE sur champ JSON array SQLite n'est pas fiable selon D-11). À la place, **filtre JS post-query** : ```ts const articles = computed(() => { const all = data.value ?? [] return all.filter((a) => Array.isArray(a.tags) && a.tags.includes('hytale')).slice(0, 2) }) ``` (renomme la destructuration `useAsyncData` en `{ data }` et expose `articles` computed — documente en commentaire `// Filtre JS car LIKE SQLite unreliable sur tags[] — D-11`). - Option `{ watch: [locale] }` sur useAsyncData (re-fetch au switch langue). **Template :** - `
` - Wrapper intérieur `max-w-7xl mx-auto` pour cohérence /blog. - Header section : petit `// recent-articles` style mono brand + `

{{ t('hytale.recentArticles.title') }}

` (réutiliser tailwind styles de /blog hero h1, taille h2 plus sobre : `text-3xl sm:text-4xl font-bold`) + `

{{ t('hytale.recentArticles.subtitle') }}

` si clé présente. - Grille : `
` avec `` (pas de `direction` → default 'next' accepté, D-13 ne spécifie pas prev/next sémantique, acceptable). - Footer : `{{ t('hytale.recentArticles.viewAll') }} `. **Règles strictes (D-09, D-10, D-11, D-12, D-13) :** - BlogCard **auto-importé** — pas d'import explicite. - Pas de fallback empty state (D-12 : masquer section complète). - Pas d'usage de `queryCollection(variableName)` — littéraux uniquement. pnpm typecheck - Fichier `app/components/HytaleRecentArticles.vue` existe - `grep -E "queryCollection\\('blog_(fr|en)'\\)" app/components/HytaleRecentArticles.vue` retourne les 2 branches - `grep "v-if=\"articles.length\"" app/components/HytaleRecentArticles.vue` passe - `grep "variant=\"compact\"" app/components/HytaleRecentArticles.vue` passe - `grep "tags.*includes.*'hytale'" app/components/HytaleRecentArticles.vue` passe (filtre JS) - `pnpm typecheck` exit 0 Task 2: Injecter HytaleRecentArticles dans app/pages/hytale.vue + ajouter clés i18n FR/EN app/pages/hytale.vue, i18n/locales/fr.json, i18n/locales/en.json - app/pages/hytale.vue (39 lignes, template section actuelle) - .planning/phases/08-content-cocon-semantique/08-PATTERNS.md §"app/pages/hytale.vue" + §"i18n/locales" - i18n/locales/fr.json (bloc hytale.* ~ligne 471, blog.* ~ligne 557 pour le style accentué 2026) - i18n/locales/en.json (bloc hytale.* miroir) - .planning/phases/08-content-cocon-semantique/08-CONTEXT.md §D-14 **Étape 1 — `app/pages/hytale.vue` :** Template actuel (lignes 30-39) : ```vue ``` Modification exacte : insérer `` **après** le `
` qui ferme le wrapper testimonials, **avant** le `` final du root. Résultat attendu : ```vue ``` Aucun changement dans le `