808835d5eb
2 plans, 2 waves. Plan 01 installe @nuxt/content + typography et configure Shiki dual-theme + collections bilingues. Plan 02 crée ProseImg/Alert MDC et articles de test FR/EN avec checkpoint visuel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
16 KiB
16 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 |
|
|
false |
|
|
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 NuxtImgapp/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èrescontent/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>
**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
- .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

## 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

## 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> |
- Démarrer le serveur de dev :
pnpm dev - Créer une page de test temporaire (ou utiliser la console) pour rendre l'article :
- Si une page
/testexiste, y ajouter<ContentRenderer> - Sinon, créer
app/pages/test.vuetemporairement 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>
- Si une page
- Naviguer vers
http://localhost:3000/test - 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.vueetAlert.vueexistent dansapp/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 typecheckpasse (0 erreur TypeScript) </success_criteria>