Files
kayjaydee 29c1dff759 fix(05): resolve checker issues — open questions resolved, depends_on corrected, test.vue added
- 05-RESEARCH.md: rename section to 'Open Questions (RESOLVED)' with explicit answers
  (frontmatter schema: tags array, image relative path, author implicit from site.ts;
   i18n prefix: /blog for blog_fr, /en/blog for blog_en)
- 05-02-PLAN.md: fix depends_on from '05-01-PLAN.md' to '01'
- 05-02-PLAN.md: add app/pages/test.vue in Task 2 files (with note to delete after checkpoint)
2026-04-21 12:52:51 +02:00

17 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
05-nuxt-content-setup-renderer 02 execute 2
01
app/components/content/ProseImg.vue
app/components/content/Alert.vue
app/components/content/ProseImg.vue
app/components/content/Alert.vue
content/fr/blog/test-kotlin-syntax.md
content/en/blog/test-kotlin-syntax.md
app/pages/test.vue
false
BLOG-01
BLOG-05
truths artifacts key_links
Un article markdown avec un bloc Kotlin est rendu avec coloration syntaxique visible
Une image référencée dans l'article s'affiche via NuxtImg avec lazy loading et srcset
Un tableau markdown est rendu avec le style prose correct
Un callout ::alert{type='info'} affiche un UAlert stylisé Nuxt UI
Les quatre types de callout (info, warning, tip, danger) fonctionnent
path provides exports
app/components/content/ProseImg.vue Override ProseImg → NuxtImg optimisé
default (component)
path provides exports
app/components/content/Alert.vue Composant MDC callout via UAlert
default (component)
path provides contains
content/fr/blog/test-kotlin-syntax.md Article de test FR couvrant les 4 success criteria ```kotlin
path provides contains
content/en/blog/test-kotlin-syntax.md Article de test EN — même slug ```kotlin
from to via pattern
content/fr/blog/test-kotlin-syntax.md app/components/content/ProseImg.vue ContentRenderer détecte les balises img et les route vers ProseImg ProseImg
from to via pattern
content/fr/blog/test-kotlin-syntax.md app/components/content/Alert.vue MDC ::alert{type} appelle Alert.vue ::alert
Créer les composants de rendu markdown (ProseImg + Alert) et les articles de test permettant de valider visuellement les 4 success criteria de la phase.

Purpose: Les composants MDC sont le liant entre le markdown brut et le rendu visuel. ProseImg garantit que chaque image passe par NuxtImg (BLOG-05). Alert garantit que les callouts ::alert sont rendus comme des composants Nuxt UI stylisés (BLOG-01).

Output:

  • app/components/content/ProseImg.vue — override transparent NuxtImg
  • app/components/content/Alert.vue — callout MDC avec 4 types (info/warning/tip/danger)
  • content/fr/blog/test-kotlin-syntax.md — article de test couvrant les 4 critères
  • content/en/blog/test-kotlin-syntax.md — version EN du même article
  • Checkpoint visuel validant rendu Kotlin coloré + image NuxtImg + tableau + callout

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/05-nuxt-content-setup-renderer/05-CONTEXT.md @.planning/phases/05-nuxt-content-setup-renderer/05-RESEARCH.md @.planning/phases/05-nuxt-content-setup-renderer/05-UI-SPEC.md @.planning/phases/05-nuxt-content-setup-renderer/05-01-SUMMARY.md
<NuxtImg
  :src="project.image"
  :alt="`...`"
  loading="lazy"
  format="webp"
  width="400"
  height="300"
  class="w-full h-52 object-cover"
/>
const props = withDefaults(defineProps<Props>(), {
  showLevel: true,
  showImage: true,
})
const levelColor = computed(() => {
  switch (techData.value.level) {
    case 'Advanced': return 'success' as const
    // ...
  }
})
components: [
  {
    path: '~/components',
    pathPrefix: false,  // composants dans content/ sont auto-importés ET reconnus MDC
  },
],
<article class="prose dark:prose-invert max-w-none">
  <ContentRenderer :value="page" />
</article>
Task 1: Créer les composants MDC ProseImg.vue et Alert.vue app/components/content/ProseImg.vue, app/components/content/Alert.vue - app/components/content/ (vérifier si le dossier existe — le créer si nécessaire) - .planning/phases/05-nuxt-content-setup-renderer/05-RESEARCH.md (Pattern 4 ProseImg, Pattern 5 Alert) - .planning/phases/05-nuxt-content-setup-renderer/05-UI-SPEC.md (Component Inventory, tableau iconMap/colorMap) Créer le dossier `app/components/content/` s'il n'existe pas.
**1. Créer `app/components/content/ProseImg.vue` :**
```vue
<script setup lang="ts">
interface Props {
  src: string
  alt?: string
  title?: string
  width?: string | number
  height?: string | number
}
const props = withDefaults(defineProps<Props>(), {
  alt: '',
})
</script>

<template>
  <NuxtImg
    :src="props.src"
    :alt="props.alt"
    :title="props.title"
    :width="props.width"
    :height="props.height"
    class="rounded-lg w-full"
    sizes="sm:600px md:800px lg:1000px"
  />
</template>
```

NuxtImg est auto-importé par @nuxt/image — pas d'import explicite nécessaire.
NE PAS ajouter `loading="lazy"` explicite sur NuxtImg — @nuxt/image gère lazy par défaut.

**2. Créer `app/components/content/Alert.vue` :**
```vue
<script setup lang="ts">
interface Props {
  type?: 'info' | 'warning' | 'tip' | 'danger'
}
const props = withDefaults(defineProps<Props>(), {
  type: 'info',
})

const iconMap = {
  info: 'i-heroicons-information-circle',
  warning: 'i-heroicons-exclamation-triangle',
  tip: 'i-heroicons-light-bulb',
  danger: 'i-heroicons-x-circle',
}
const colorMap = {
  info: 'info',
  warning: 'warning',
  tip: 'success',
  danger: 'error',
}
</script>

<template>
  <UAlert
    :icon="iconMap[props.type]"
    :color="colorMap[props.type] as any"
    variant="soft"
    class="my-4"
  >
    <template #description>
      <ContentSlot :use="$slots.default" unwrap="p" />
    </template>
  </UAlert>
</template>
```

CRITIQUE : `<ContentSlot :use="$slots.default" unwrap="p" />` est OBLIGATOIRE — sans cette ligne, le contenu entre `::alert` et `::` n'est pas rendu (Pitfall 4 RESEARCH.md).
UAlert et ContentSlot sont auto-importés — pas d'import explicite.
```bash test -f app/components/content/ProseImg.vue && echo "ProseImg OK" test -f app/components/content/Alert.vue && echo "Alert OK" grep "NuxtImg" app/components/content/ProseImg.vue grep "ContentSlot" app/components/content/Alert.vue grep "iconMap" app/components/content/Alert.vue grep "i-heroicons-information-circle" app/components/content/Alert.vue ``` - `app/components/content/ProseImg.vue` existe et contient `` - `Alert.vue` définit les 4 types : `'info' | 'warning' | 'tip' | 'danger'` - `Alert.vue` contient `iconMap` avec les 4 icônes Heroicons - `Alert.vue` contient `colorMap` avec les 4 couleurs Nuxt UI - `Alert.vue` utilise `UAlert` avec `variant="soft"` - `ProseImg.vue` utilise `withDefaults` avec `alt: ''` comme valeur par défaut - Aucun import explicite de NuxtImg, UAlert ou ContentSlot (auto-importés) ProseImg.vue et Alert.vue créés et conformes aux patterns du projet. Task 2: Créer les articles de test markdown FR et EN content/fr/blog/test-kotlin-syntax.md, content/en/blog/test-kotlin-syntax.md, app/pages/test.vue (a supprimer apres checkpoint visuel) - .planning/phases/05-nuxt-content-setup-renderer/05-UI-SPEC.md (Copywriting Contract — copie exacte des textes) - .planning/phases/05-nuxt-content-setup-renderer/05-RESEARCH.md (Code Examples — structure de l'article de test) - content.config.ts (vérifier que le schema Zod attend ces champs frontmatter) Créer les dossiers `content/fr/blog/` et `content/en/blog/` s'ils n'existent pas.
**1. Créer `content/fr/blog/test-kotlin-syntax.md` :**

````markdown
---
title: "Test Kotlin Syntax Highlighting"
description: "Article de test pour valider le renderer @nuxt/content"
date: "2026-04-21"
tags: ["kotlin", "hytale", "test"]
---

## Bloc de code Kotlin

```kotlin
fun main() {
    println("Hello, Hytale!")
}

fun createPlugin(name: String): Plugin {
    return Plugin(name = name, version = "1.0.0")
}
```

## Image optimisée

![Image de test pour NuxtImg dans les articles](/images/og-image.png)

## Tableau

| Fonctionnalité | Statut | Notes |
|----------------|--------|-------|
| Syntax highlighting | ✅ Actif | Kotlin, Java, TypeScript, Shell |
| Images optimisées | ✅ Actif | Via NuxtImg (lazy + srcset) |
| Tableaux | ✅ Actif | Rendu prose |
| Callouts | ✅ Actif | MDC ::alert{type} |

## Callouts

::alert{type="info"}
Ceci est un callout d'information.
::

::alert{type="warning"}
Ceci est un avertissement.
::

::alert{type="tip"}
Conseil pratique de développement Kotlin.
::

::alert{type="danger"}
Erreur critique — à ne pas ignorer.
::
````

**2. Créer `content/en/blog/test-kotlin-syntax.md` :**

````markdown
---
title: "Test Kotlin Syntax Highlighting"
description: "Test article to validate the @nuxt/content renderer"
date: "2026-04-21"
tags: ["kotlin", "hytale", "test"]
---

## Kotlin Code Block

```kotlin
fun main() {
    println("Hello, Hytale!")
}

fun createPlugin(name: String): Plugin {
    return Plugin(name = name, version = "1.0.0")
}
```

## Optimized Image

![Test image for NuxtImg in articles](/images/og-image.png)

## Table

| Feature | Status | Notes |
|---------|--------|-------|
| Syntax highlighting | ✅ Active | Kotlin, Java, TypeScript, Shell |
| Optimized images | ✅ Active | Via NuxtImg (lazy + srcset) |
| Tables | ✅ Active | Prose rendering |
| Callouts | ✅ Active | MDC ::alert{type} |

## Callouts

::alert{type="info"}
This is an information callout.
::

::alert{type="warning"}
This is a warning.
::

::alert{type="tip"}
Practical Kotlin development tip.
::

::alert{type="danger"}
Critical error — do not ignore.
::
````

Note sur l'image : utiliser `/images/og-image.png` qui existe déjà dans `public/images/` — cela valide le pipeline ProseImg sans nécessiter une image supplémentaire.
```bash test -f content/fr/blog/test-kotlin-syntax.md && echo "FR OK" test -f content/en/blog/test-kotlin-syntax.md && echo "EN OK" grep '```kotlin' content/fr/blog/test-kotlin-syntax.md grep '::alert{type="info"}' content/fr/blog/test-kotlin-syntax.md grep '::alert{type="warning"}' content/fr/blog/test-kotlin-syntax.md grep '::alert{type="tip"}' content/fr/blog/test-kotlin-syntax.md grep '::alert{type="danger"}' content/fr/blog/test-kotlin-syntax.md grep "| Colonne\|Fonctionnalité\|Feature" content/fr/blog/test-kotlin-syntax.md content/en/blog/test-kotlin-syntax.md ``` - `content/fr/blog/test-kotlin-syntax.md` existe avec frontmatter complet (title, description, date, tags) - `content/en/blog/test-kotlin-syntax.md` existe avec frontmatter EN - Les deux fichiers contiennent un bloc ` ```kotlin ` avec au moins 2 lignes de code - Les deux fichiers contiennent une image markdown `![...](...)` - Les deux fichiers contiennent un tableau markdown avec header `|...|...|` - Les deux fichiers contiennent les 4 callouts : `::alert{type="info"}`, `::alert{type="warning"}`, `::alert{type="tip"}`, `::alert{type="danger"}` - Le slug est identique dans les deux langues : `test-kotlin-syntax` Articles de test créés en FR et EN. L'article couvre les 4 success criteria de la phase.

<threat_model>

Trust Boundaries

Boundary Description
Markdown → ContentRenderer HTML généré par @nuxt/content — pas d'input utilisateur dans cette phase
MDC composants → DOM Composants Vue rendus côté serveur — auto-échappement Vue actif

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-05-05 Tampering app/components/content/Alert.vue prop type accept Valeur type vient du frontmatter markdown (auteur contrôlé) — pas d'input utilisateur; TypeScript union 'info' | 'warning' | 'tip' | 'danger' limite les valeurs
T-05-06 Information Disclosure ProseImg.vue prop src accept src vient du markdown statique — pas d'SSRF possible (NuxtImg résout les chemins au build)
T-05-07 Spoofing ContentSlot dans Alert.vue accept ContentSlot est un composant officiel @nuxt/content — pas de XSS, le contenu est du texte markdown échappé
</threat_model>
Après exécution du plan 02 (checkpoint visuel requis) :
  1. Démarrer le serveur de dev : pnpm dev
  2. Créer une page de test temporaire (ou utiliser la console) pour rendre l'article :
    • Si une page /test existe, y ajouter <ContentRenderer>
    • Sinon, créer app/pages/test.vue temporairement avec :
      <script setup lang="ts">
      const { data: page } = await useAsyncData('test', () =>
        queryCollection('blog_fr').path('/blog/test-kotlin-syntax').first()
      )
      </script>
      <template>
        <article class="prose dark:prose-invert max-w-none p-8">
          <ContentRenderer v-if="page" :value="page" />
        </article>
      </template>
      
  3. Naviguer vers http://localhost:3000/test
  4. Vérifier visuellement les 4 critères

La page de test temporaire peut être supprimée après validation — elle est hors scope de cette phase.

Vérifications grep :

test -f app/components/content/ProseImg.vue
test -f app/components/content/Alert.vue
grep "ContentSlot" app/components/content/Alert.vue
test -f content/fr/blog/test-kotlin-syntax.md
test -f content/en/blog/test-kotlin-syntax.md

<success_criteria>

  • ProseImg.vue et Alert.vue existent dans app/components/content/
  • Les articles de test FR et EN existent avec les 4 éléments de validation
  • Checkpoint visuel : bloc Kotlin coloré visible (spans avec couleurs Shiki)
  • Checkpoint visuel : image rendue via <img srcset=...> (NuxtImg actif)
  • Checkpoint visuel : tableau affiché avec bordures prose
  • Checkpoint visuel : callout info affiché comme UAlert bleu avec icône
  • pnpm typecheck passe (0 erreur TypeScript) </success_criteria>
- ProseImg.vue : override transparent qui route toutes les images markdown vers NuxtImg - Alert.vue : composant MDC pour ::alert{type} avec 4 types (info/warning/tip/danger) via UAlert Nuxt UI - Article de test FR/EN contenant les 4 éléments de validation 1. S'assurer que `pnpm dev` tourne 2. Créer `app/pages/test.vue` temporairement (voir section verification ci-dessus) 3. Visiter http://localhost:3000/test 4. Vérifier visuellement : - [ ] Le bloc Kotlin est coloré (pas du texte brut gris) — en mode dark, fond sombre avec tokens colorés - [ ] L'image s'affiche (pas de 404) et l'élément DOM est `` (inspecter avec DevTools) - [ ] Le tableau markdown est rendu avec des lignes horizontales et en-têtes distingués - [ ] Le callout "info" apparaît comme une alerte bleue avec icône cercle-information - [ ] En passant en mode light (toggle du site), les couleurs Shiki changent (github-light) 5. Supprimer `app/pages/test.vue` après validation Taper "approved" si les 5 points sont validés, ou décrire le problème rencontré Après completion, créer `.planning/phases/05-nuxt-content-setup-renderer/05-02-SUMMARY.md`