docs(08): create phase plan — content & cocon sémantique (3 plans, 2 waves)

- 08-01 (W1): HytaleRecentArticles.vue scaffold + injection hytale.vue + i18n
- 08-02 (W2): article tutorial how-to-build-your-first-hytale-plugin FR+EN
- 08-03 (W2): article positionnement hytale-plugin-development-2026 FR+EN
This commit is contained in:
2026-04-22 18:38:13 +02:00
parent 9d6ed1780b
commit 9cc1dbec5d
4 changed files with 909 additions and 5 deletions
+4 -5
View File
@@ -175,12 +175,11 @@ Plans:
2. Chaque article blog contient au moins un lien interne vers `/hytale` dans le corps du texte
3. La page `/hytale` affiche une section "Articles recents" avec liens vers les 2 articles seed
4. Les articles existent en FR et EN — `curl localhost:3000/en/blog` les liste en anglais
**Plans:** 4 plans
**Plans:** 3 plans
Plans:
- [ ] 06-01-PLAN.md — Content schema Zod extension (draft/wordCount/minutes) + Nitro reading-time hook + draft:true sur test articles
- [ ] 06-02-PLAN.md — i18n keys blog.*/nav.blog/a11y.blog* + lien Blog dans AppHeader + BlogCard.vue unifié (default + compact)
- [ ] 06-03-PLAN.md — Page listing app/pages/blog/index.vue (hero + grid + empty state, SSR bilingue)
- [ ] 06-04-PLAN.md — BlogToc.vue + BlogPrevNext.vue + enrichissement app/pages/blog/[slug].vue (breadcrumb + header + TOC + surround)
- [ ] 08-01-PLAN.md — Scaffold HytaleRecentArticles.vue (queryCollection bilingue + filtre tag hytale + limit 2) + injection hytale.vue + i18n hytale.recentArticles.*
- [ ] 08-02-PLAN.md — Article seed tutorial how-to-build-your-first-hytale-plugin (FR+EN, draft:false, bloc Kotlin, liens inline /hytale)
- [ ] 08-03-PLAN.md — Article seed autorité hytale-plugin-development-2026 (FR+EN, draft:false, bloc Kotlin coroutines, liens inline /hytale)
**UI hint**: yes
---
@@ -0,0 +1,290 @@
---
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 <HytaleRecentArticles /> avant fermeture du root div"
contains: "<HytaleRecentArticles"
- path: "i18n/locales/fr.json"
provides: "Clés hytale.recentArticles.title/subtitle/viewAll en FR accentué"
contains: "recentArticles"
- path: "i18n/locales/en.json"
provides: "Clés hytale.recentArticles.title/subtitle/viewAll en EN"
contains: "recentArticles"
key_links:
- from: "app/components/HytaleRecentArticles.vue"
to: "BlogCard.vue (variant compact)"
via: "auto-import Nuxt + props article+variant"
pattern: "variant=\"compact\""
- from: "app/components/HytaleRecentArticles.vue"
to: "@nuxt/content collections blog_fr / blog_en"
via: "queryCollection(literal).where('tags', ...).limit(2)"
pattern: "queryCollection\\('blog_(fr|en)'\\)"
- from: "app/pages/hytale.vue"
to: "app/components/HytaleRecentArticles.vue"
via: "auto-import + template insertion"
pattern: "<HytaleRecentArticles"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
<interfaces>
<!-- BlogCard.vue props (from app/components/BlogCard.vue lines 2-21) -->
```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'
}
```
<!-- queryCollection bilingual pattern (from app/pages/blog/index.vue lines 1-21) -->
```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] },
)
```
<!-- i18n insertion point: existing hytale.* block in i18n/locales/fr.json starts ~line 471 (per PATTERNS.md). Add recentArticles sibling to hero/services/pricing. -->
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Créer composant HytaleRecentArticles.vue (query + filtre tag + render)</name>
<files>app/components/HytaleRecentArticles.vue</files>
<read_first>
- 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
</read_first>
<action>
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 :**
- `<section v-if="articles.length" class="py-16 md:py-20 px-4 sm:px-6 lg:px-8">`
- Wrapper intérieur `max-w-7xl mx-auto` pour cohérence /blog.
- Header section : petit `<span>// recent-articles</span>` style mono brand + `<h2>{{ t('hytale.recentArticles.title') }}</h2>` (réutiliser tailwind styles de /blog hero h1, taille h2 plus sobre : `text-3xl sm:text-4xl font-bold`) + `<p>{{ t('hytale.recentArticles.subtitle') }}</p>` si clé présente.
- Grille : `<div class="grid grid-cols-1 md:grid-cols-2 gap-5 lg:gap-6 mt-8">` avec `<BlogCard v-for="article in articles" :key="article.path" :article="article" variant="compact" />` (pas de `direction` → default 'next' accepté, D-13 ne spécifie pas prev/next sémantique, acceptable).
- Footer : `<NuxtLink :to="localePath('/blog')" class="inline-flex items-center gap-2 mt-8 text-brand-500 hover:text-brand-600 font-medium">{{ t('hytale.recentArticles.viewAll') }} <UIcon name="i-lucide-arrow-right" /></NuxtLink>`.
**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.
</action>
<verify>
<automated>pnpm typecheck</automated>
</verify>
<done>
- 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
</done>
</task>
<task type="auto">
<name>Task 2: Injecter HytaleRecentArticles dans app/pages/hytale.vue + ajouter clés i18n FR/EN</name>
<files>app/pages/hytale.vue, i18n/locales/fr.json, i18n/locales/en.json</files>
<read_first>
- 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
</read_first>
<action>
**Étape 1 — `app/pages/hytale.vue` :**
Template actuel (lignes 30-39) :
```vue
<template>
<div>
<HytaleHeroSection />
<HytaleServicesSection />
<HytalePricingSection />
<div class="relative bg-gray-50/50 dark:bg-gray-900/20">
<TestimonialsSection />
</div>
</div>
</template>
```
Modification exacte : insérer `<HytaleRecentArticles />` **après** le `</div>` qui ferme le wrapper testimonials, **avant** le `</div>` final du root. Résultat attendu :
```vue
<template>
<div>
<HytaleHeroSection />
<HytaleServicesSection />
<HytalePricingSection />
<div class="relative bg-gray-50/50 dark:bg-gray-900/20">
<TestimonialsSection />
</div>
<HytaleRecentArticles />
</div>
</template>
```
Aucun changement dans le `<script setup>`. Aucun import (auto-import Nuxt).
**Étape 2 — `i18n/locales/fr.json` :**
Localiser le bloc `"hytale": { ... }` (début ~ligne 471). Ajouter un sous-objet `recentArticles` en sibling de `hero`/`services`/`pricing` (ordre libre, mais placer à la fin du bloc hytale pour minimiser le diff). Style **accentué** (cohérent avec `blog.*` ajouté Phase 6-02, voir PATTERNS.md §i18n) :
```json
"recentArticles": {
"title": "Articles récents",
"subtitle": "Les dernières publications sur le développement de plugins Hytale",
"viewAll": "Voir tous les articles"
}
```
**Étape 3 — `i18n/locales/en.json` :**
Miroir exact dans le bloc `"hytale": { ... }` :
```json
"recentArticles": {
"title": "Recent articles",
"subtitle": "Latest writing on Hytale plugin development",
"viewAll": "View all articles"
}
```
**Règles :**
- JSON valide : pas de trailing comma, double quotes, virgule correcte entre la clé sibling précédente et `recentArticles`.
- Conserver l'indentation existante du fichier.
- Ne PAS modifier d'autres clés.
</action>
<verify>
<automated>pnpm typecheck &amp;&amp; node -e "JSON.parse(require('fs').readFileSync('i18n/locales/fr.json','utf8')); JSON.parse(require('fs').readFileSync('i18n/locales/en.json','utf8'))"</automated>
</verify>
<done>
- `grep "<HytaleRecentArticles" app/pages/hytale.vue` passe
- `grep -A 3 "\"recentArticles\"" i18n/locales/fr.json` affiche title/subtitle/viewAll
- `grep -A 3 "\"recentArticles\"" i18n/locales/en.json` affiche title/subtitle/viewAll
- `pnpm typecheck` exit 0
- JSON parse FR + EN sans erreur
- Run `pnpm dev` puis `curl http://localhost:3000/hytale` → HTML rendu sans erreur 500 (section absente tant que 0 article hytale, conforme D-12)
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| content DB → SSR render | Données lues par queryCollection ; Zod-validées Phase 5, pas d'user input |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-08-01 | T (Tampering) | filtre JS tags.includes('hytale') | mitigate | `Array.isArray(a.tags)` guard avant `.includes()` pour éviter TypeError si frontmatter cassé passe le schema |
| T-08-02 | I (Info Disclosure) | queryCollection where draft=false | mitigate | Filtre draft=false SQL obligatoire (déjà pattern éprouvé /blog) — pas de leak d'articles draft:true |
| T-08-03 | D (DoS) | limit 2 post-filter | accept | Limite post-filter sur JS, volume d'articles < 100 attendu, négligeable |
</threat_model>
<verification>
- `pnpm typecheck` exit 0
- `curl http://localhost:3000/hytale` et `curl http://localhost:3000/en/hytale` retournent 200 SSR sans erreur
- Tant qu'aucun article hytale:true n'existe, section invisible dans HTML (grep `recentArticles` absent de la sortie curl) — conforme D-12
- Après Wave 2, re-curl : section visible avec 2 slugs
</verification>
<success_criteria>
- Composant HytaleRecentArticles.vue livré, auto-importé, TypeScript strict, pattern Phase 5 Pitfall-safe
- app/pages/hytale.vue injecte `<HytaleRecentArticles />` en dernière position du root
- Clés i18n FR+EN présentes sous `hytale.recentArticles.*`
- Zéro erreur typecheck, zéro warning console SSR sur /hytale
</success_criteria>
<output>
Après complétion, créer `.planning/phases/08-content-cocon-semantique/08-01-SUMMARY.md` (template summary).
</output>
@@ -0,0 +1,312 @@
---
phase: 08-content-cocon-semantique
plan: 02
type: execute
wave: 2
depends_on: ["08-01"]
files_modified:
- content/fr/blog/how-to-build-your-first-hytale-plugin.md
- content/en/blog/how-to-build-your-first-hytale-plugin.md
autonomous: true
requirements: [BLOG-07, SEO-14]
tags: [content, blog, hytale, tutorial, kotlin]
must_haves:
truths:
- "Article 'how-to-build-your-first-hytale-plugin' publié (draft: false) en FR et EN avec le même slug"
- "Chaque version contient au moins 1 bloc code Kotlin réaliste (event listener ou command handler)"
- "Version FR contient au moins 1 lien markdown inline vers /hytale ; version EN vers /en/hytale"
- "Frontmatter Zod-valide : title/description localisés, date ISO, tags incluent 'hytale', draft: false"
- "Article apparaît dans /blog (FR) et /en/blog (EN) en listing"
artifacts:
- path: "content/fr/blog/how-to-build-your-first-hytale-plugin.md"
provides: "Article FR complet (800-1500 mots) — tutorial débutant plugin Hytale"
contains: "draft: false"
- path: "content/en/blog/how-to-build-your-first-hytale-plugin.md"
provides: "Article EN équivalent, même slug"
contains: "draft: false"
key_links:
- from: "content/fr/blog/how-to-build-your-first-hytale-plugin.md"
to: "/hytale (page service)"
via: "lien markdown inline `[texte](/hytale)`"
pattern: "\\]\\(/hytale\\)"
- from: "content/en/blog/how-to-build-your-first-hytale-plugin.md"
to: "/en/hytale"
via: "lien markdown inline `[text](/en/hytale)`"
pattern: "\\]\\(/en/hytale\\)"
- from: "Sitemap Nitro endpoint (Phase 7-04)"
to: "URL /blog/how-to-build-your-first-hytale-plugin avec hreflang FR+EN"
via: "Auto-découverte via queryCollection (déjà live)"
pattern: "how-to-build-your-first-hytale-plugin"
---
<objective>
Publier l'article seed 1 "How to build your first Hytale plugin" en FR et EN. Tutorial débutant, 800-1500 mots, bloc code Kotlin réaliste, 1-2 liens inline vers la page `/hytale` (commission service). Ne nécessite aucune modif code applicatif — markdown pur + frontmatter Zod.
Purpose: Premier article du cocon sémantique. Anchor text SEO-friendly "commissioner un plugin Hytale" / "commission a Hytale plugin" renvoyant vers l'offre service. Intent transactionnel-info.
Output: 2 fichiers markdown (FR + EN), même slug, tags `['hytale', 'tutorial', 'kotlin']`, publiés (draft: false).
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/08-content-cocon-semantique/08-CONTEXT.md
@.planning/phases/08-content-cocon-semantique/08-PATTERNS.md
@.planning/phases/05-nuxt-content-setup-renderer/05-CONTEXT.md
@content/fr/blog/test-kotlin-syntax.md
</context>
<editorial_brief>
**Slug (D-01, D-03) :** `how-to-build-your-first-hytale-plugin` — identique FR+EN.
**Titre FR :** "Créer son premier plugin Hytale : guide pas à pas"
**Titre EN :** "How to build your first Hytale plugin: a step-by-step guide"
**Description FR :** "Apprends à coder ton premier plugin Hytale en Kotlin : setup, event listener, et commande custom — avec le code source complet."
**Description EN :** "Learn to build your first Hytale plugin in Kotlin: project setup, event listener, custom command — with the complete source code."
**Tags :** `["hytale", "tutorial", "kotlin"]`
**Date :** `"2026-04-22"`
**Draft :** `false`
**Image :** champ omis (fallback `/og-blog-default.jpg` Phase 7 D-05 s'applique automatiquement).
**Champ `updated` :** omis (D-06).
**Ton (D-07) :** première personne ("je", "I"), technique mais accessible, concret. Voix Killian dev 7 ans, pas corporate. Éviter jargon non-expliqué. Inclure anecdotes pratiques ("la première fois que j'ai lancé...").
**Longueur cible :** 1000-1300 mots par version.
**Outline recommandé (6-8 sections, ~200 mots chacune) :**
1. **Intro — pourquoi Hytale, pourquoi maintenant** (~150 mots)
- Le contexte : Hytale API en 2026, public indie + serveurs custom, opportunité dev.
- Ce qu'on va construire : un plugin de base qui écoute un event et ajoute une commande.
- **Placement naturel du 1er lien `/hytale`** : "Si tu préfères faire [commissioner un plugin Hytale](/hytale) plutôt que le coder toi-même, c'est une option." (FR) / "If you'd rather [commission a Hytale plugin](/en/hytale) instead of coding it yourself, that works too." (EN)
2. **Prérequis** (~100 mots)
- JDK 17+ (ou version actuelle Hytale SDK 2026), IntelliJ IDEA Community, Gradle, connaissances Kotlin basiques.
- Fichier `build.gradle.kts` minimal (snippet court optionnel).
3. **Scaffold du projet** (~200 mots)
- Arborescence `src/main/kotlin/com/example/myplugin/MyPlugin.kt`.
- `plugin.yml` / `plugin.toml` (selon convention Hytale 2026).
- Petit exemple de manifest.
4. **Premier event listener — le cœur du plugin** (~250 mots) — **BLOC CODE KOTLIN OBLIGATOIRE ici** :
```kotlin
package com.example.myplugin
import io.hytale.api.HytalePlugin
import io.hytale.api.event.EventHandler
import io.hytale.api.event.player.PlayerJoinEvent
class MyPlugin : HytalePlugin() {
override fun onEnable() {
logger.info("MyPlugin enabled")
server.events.register(this)
}
@EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) {
event.player.sendMessage("Welcome, ${event.player.name}!")
}
}
```
- Explication ligne par ligne : `onEnable`, registration, annotation handler, accès `event.player`.
- Note : l'API exacte 2026 peut varier — préciser "exemple conforme au SDK public 2026, adapter selon la doc officielle au moment de la lecture".
5. **Ajouter une commande custom** (~200 mots) — 2e bloc code court :
```kotlin
@Command("hello")
fun onHelloCommand(sender: CommandSender, args: List<String>) {
sender.sendMessage("Hello from MyPlugin!")
}
```
- Où placer dans la classe, comment tester en jeu.
6. **Build + deploy local** (~150 mots)
- `./gradlew build`
- Copier le JAR dans `plugins/`, lancer le serveur.
- **Placement naturel du 2e lien `/hytale`** (optionnel, 1 suffit) : "Si tu veux déployer un plugin plus ambitieux et que tu préfères déléguer, tu peux [commissioner un plugin Hytale sur-mesure](/hytale)." (FR)
7. **Prochaines étapes** (~150 mots)
- Suggestions : écouter d'autres events, persister des données, optimiser perf.
- Liens externes vers doc Hytale (ne PAS ajouter en Wave 2 — Claude peut si source connue, sinon omettre).
8. **Conclusion** (~100 mots)
- Résumé, encouragement.
**Liens internes — règle absolue (D-08, D-09) :**
- Version **FR** : **au moins 1** lien markdown `](/hytale)` inline dans la prose. Anchor text en français naturel. Idéalement 2 liens (intro + build section).
- Version **EN** : **au moins 1** lien markdown `](/en/hytale)` inline. Anchor text en anglais naturel. Path commence par `/en/` (préfixe i18n explicite).
- **NE PAS** utiliser `localePath()` ou `<NuxtLink>` en markdown — hardcode des paths.
**Bloc code Kotlin (D-05) :** au moins 1 bloc ```kotlin réaliste (pas pseudo-code — signatures API, imports cohérents). L'exemple onPlayerJoin ci-dessus satisfait l'exigence minimale. Un 2e bloc (commande) est bonus.
**Frontmatter exact FR :**
```yaml
---
title: "Créer son premier plugin Hytale : guide pas à pas"
description: "Apprends à coder ton premier plugin Hytale en Kotlin : setup, event listener, et commande custom — avec le code source complet."
date: "2026-04-22"
tags: ["hytale", "tutorial", "kotlin"]
draft: false
---
```
**Frontmatter exact EN :**
```yaml
---
title: "How to build your first Hytale plugin: a step-by-step guide"
description: "Learn to build your first Hytale plugin in Kotlin: project setup, event listener, custom command — with the complete source code."
date: "2026-04-22"
tags: ["hytale", "tutorial", "kotlin"]
draft: false
---
```
</editorial_brief>
<tasks>
<task type="auto">
<name>Task 1: Rédiger version FR de l'article tutorial</name>
<files>content/fr/blog/how-to-build-your-first-hytale-plugin.md</files>
<read_first>
- content/fr/blog/test-kotlin-syntax.md (pattern frontmatter + code block + callouts MDC)
- .planning/phases/08-content-cocon-semantique/08-CONTEXT.md §D-01, D-04, D-05, D-06, D-07, D-08
- .planning/phases/08-content-cocon-semantique/08-PATTERNS.md §"content/{fr,en}/blog/..."
- Section `<editorial_brief>` ci-dessus (outline complet)
</read_first>
<action>
Créer `content/fr/blog/how-to-build-your-first-hytale-plugin.md`.
**Frontmatter exact** (copier depuis `<editorial_brief>`) — `draft: false`, tags `["hytale", "tutorial", "kotlin"]`, date `"2026-04-22"`, pas de champ `image`, pas de champ `updated`.
**Corps de l'article** : suivre l'outline 8 sections du brief éditorial, ton première personne (je/moi/mon), concret, 1000-1300 mots.
**Exigences dures non-négociables :**
1. Au moins 1 bloc ```kotlin avec imports + classe + event handler (réaliste, pas pseudo-code). Voir exemple exact dans le brief §section 4. Un 2e bloc court pour la commande est recommandé.
2. Au moins 1 lien markdown inline `](/hytale)` — path en dur, pas `localePath()`. Anchor text français naturel, ex: "[commissioner un plugin Hytale sur-mesure](/hytale)". Idéalement 2 occurrences (intro + build section).
3. Pas de champ `image:` au frontmatter. Pas de champ `updated`.
4. Respect strict du schema Zod blog_fr (Phase 5) : les champs non déclarés sont strippés — ne pas inventer.
**Interdits :**
- Pseudo-code Kotlin (doit compiler conceptuellement).
- `<NuxtLink>` ou `localePath()` dans le markdown.
- Liens absolus `https://killiandalcin.fr/hytale` — utiliser path relatif `/hytale`.
- Contenu AI-slop générique ("dans ce guide, nous allons explorer...") — voix Killian concrète.
**Style Markdown :**
- Titres `##` pour les 8 sections (pas de `#` qui est réservé au title frontmatter).
- Code inline avec backticks pour noms de classes/méthodes.
- Callouts `::alert{type="tip"}` ou `::alert{type="info"}` optionnels si pertinent (pattern Phase 5 MDC).
</action>
<verify>
<automated>pnpm typecheck</automated>
</verify>
<done>
- Fichier existe : `test -f content/fr/blog/how-to-build-your-first-hytale-plugin.md`
- `grep "draft: false" content/fr/blog/how-to-build-your-first-hytale-plugin.md` passe
- `grep -E "tags:.*hytale" content/fr/blog/how-to-build-your-first-hytale-plugin.md` passe (ou check YAML array form)
- `grep -c '\](/hytale)' content/fr/blog/how-to-build-your-first-hytale-plugin.md` ≥ 1
- `grep -c '```kotlin' content/fr/blog/how-to-build-your-first-hytale-plugin.md` ≥ 1
- Mots ≥ 800 : `wc -w content/fr/blog/how-to-build-your-first-hytale-plugin.md` ≥ 800
- `pnpm typecheck` exit 0 (validation Zod schema blog_fr passe)
</done>
</task>
<task type="auto">
<name>Task 2: Rédiger version EN de l'article tutorial (même slug, contenu équivalent)</name>
<files>content/en/blog/how-to-build-your-first-hytale-plugin.md</files>
<read_first>
- content/fr/blog/how-to-build-your-first-hytale-plugin.md (juste créé par Task 1 — référence directe pour équivalence)
- content/en/blog/test-kotlin-syntax.md (pattern frontmatter EN)
- .planning/phases/08-content-cocon-semantique/08-CONTEXT.md §D-03, D-08, D-09
</read_first>
<action>
Créer `content/en/blog/how-to-build-your-first-hytale-plugin.md`**même slug** que la version FR, contenu équivalent traduit (pas une simple traduction automatique : adapter idiomes, garder la voix naturelle en anglais).
**Frontmatter exact** :
```yaml
---
title: "How to build your first Hytale plugin: a step-by-step guide"
description: "Learn to build your first Hytale plugin in Kotlin: project setup, event listener, custom command — with the complete source code."
date: "2026-04-22"
tags: ["hytale", "tutorial", "kotlin"]
draft: false
---
```
**Corps :** équivalent à la version FR (mêmes 8 sections, même outline, même blocs code Kotlin identiques — les commentaires code peuvent rester en anglais dans les deux versions).
**Règle critique liens internes (D-09) :**
- Version EN : lien vers **`/en/hytale`** (préfixe explicite), PAS `/hytale`. Exemple : `[commission a Hytale plugin](/en/hytale)`.
- Au moins 1 occurrence `](/en/hytale)` — idéalement 2.
- Path en dur dans le markdown. Pas de `localePath()`.
**Longueur :** 1000-1300 mots. Voix première personne ("I", "my first plugin"), ton technique accessible.
**Blocs code :** mêmes snippets Kotlin que la version FR (les imports et signatures API n'ont pas à être traduits). Les explications textuelles autour doivent être en anglais.
**Interdits identiques à Task 1.**
</action>
<verify>
<automated>pnpm typecheck</automated>
</verify>
<done>
- Fichier existe
- `grep "draft: false" content/en/blog/how-to-build-your-first-hytale-plugin.md` passe
- `grep -E "tags:.*hytale" content/en/blog/how-to-build-your-first-hytale-plugin.md` passe
- `grep -c '\](/en/hytale)' content/en/blog/how-to-build-your-first-hytale-plugin.md` ≥ 1
- `grep -c '```kotlin' content/en/blog/how-to-build-your-first-hytale-plugin.md` ≥ 1
- Mots ≥ 800
- `pnpm typecheck` exit 0
- Run `pnpm dev` puis `curl http://localhost:3000/blog/how-to-build-your-first-hytale-plugin` → 200 + HTML contient titre FR
- `curl http://localhost:3000/en/blog/how-to-build-your-first-hytale-plugin` → 200 + HTML contient titre EN
- `curl http://localhost:3000/hytale` → HTML contient section "Articles récents" + lien vers le slug (l'article Plan 08-01 est maintenant alimenté)
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| markdown author → Zod schema → SSR | Contenu statique rédigé par dev, Zod-validé Phase 5, aucun user input |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-08-04 | T (Tampering) | frontmatter YAML | mitigate | Schema Zod blog_fr/blog_en strip les champs inconnus, typecheck rattrape les erreurs |
| T-08-05 | I (Info Disclosure) | lien inline | accept | Les paths `/hytale` / `/en/hytale` sont publics, pas de leak |
| T-08-06 | S (Spoofing) | auteur article | accept | Pas de champ `author` user-controlled — author implicite Killian via schema.org Phase 7 |
</threat_model>
<verification>
- Les 2 articles passent le typecheck (Zod schema validation au build time)
- `curl /blog` contient le slug en FR, `curl /en/blog` en EN
- `grep '](/hytale)'` sur FR ≥ 1, `grep '](/en/hytale)'` sur EN ≥ 1
- Sitemap : `curl /sitemap.xml | grep how-to-build-your-first-hytale-plugin` retourne au moins 2 occurrences (FR + EN avec hreflang alternates automatiques Phase 7-04)
</verification>
<success_criteria>
- 2 articles `.md` publiés (draft: false), mêmes slugs, frontmatter Zod-valide
- Cocon sémantique : chaque article a ≥1 lien vers `/hytale` (locale-aware, paths en dur)
- Bloc code Kotlin réaliste (pas pseudo-code) dans chaque version
- Minimum 800 mots par version, cible 1000-1300
- Section "Articles récents" sur /hytale (livrée Plan 08-01) affiche cet article après deploy
</success_criteria>
<output>
Après complétion, créer `.planning/phases/08-content-cocon-semantique/08-02-SUMMARY.md`.
</output>
@@ -0,0 +1,303 @@
---
phase: 08-content-cocon-semantique
plan: 03
type: execute
wave: 2
depends_on: ["08-01"]
files_modified:
- content/fr/blog/hytale-plugin-development-2026.md
- content/en/blog/hytale-plugin-development-2026.md
autonomous: true
requirements: [BLOG-07, SEO-14]
tags: [content, blog, hytale, industry, analysis]
must_haves:
truths:
- "Article 'hytale-plugin-development-2026' publié (draft: false) en FR et EN avec le même slug"
- "Chaque version contient au moins 1 bloc code Kotlin réaliste (pattern moderne 2026)"
- "Version FR contient au moins 1 lien markdown inline vers /hytale ; version EN vers /en/hytale"
- "Frontmatter Zod-valide : tags incluent 'hytale', draft: false, date ISO"
- "Article apparaît dans /blog (FR) et /en/blog (EN), et dans la section 'Articles récents' de /hytale (aux côtés de l'article 08-02)"
artifacts:
- path: "content/fr/blog/hytale-plugin-development-2026.md"
provides: "Article FR positionnement/autorité — état de l'art Hytale 2026 (800-1500 mots)"
contains: "draft: false"
- path: "content/en/blog/hytale-plugin-development-2026.md"
provides: "Article EN équivalent, même slug"
contains: "draft: false"
key_links:
- from: "content/fr/blog/hytale-plugin-development-2026.md"
to: "/hytale"
via: "lien markdown inline"
pattern: "\\]\\(/hytale\\)"
- from: "content/en/blog/hytale-plugin-development-2026.md"
to: "/en/hytale"
via: "lien markdown inline"
pattern: "\\]\\(/en/hytale\\)"
---
<objective>
Publier l'article seed 2 "Hytale plugin development in 2026" en FR et EN. Article de positionnement/autorité : état de l'art 2026, stack, outlook, tendances. 800-1500 mots, 1 bloc code Kotlin moderne (ex: coroutines pour event async), 1-2 liens inline vers `/hytale`.
Purpose: 2e pilier du cocon sémantique. Capte le trafic info long-tail ("Hytale plugin 2026", "Hytale API state"). Complément du tutorial (08-02) : l'un convertit, l'autre asseoit l'autorité.
Output: 2 fichiers markdown (FR + EN), même slug, tags `['hytale', 'industry', 'analysis']`, publiés.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/08-content-cocon-semantique/08-CONTEXT.md
@.planning/phases/08-content-cocon-semantique/08-PATTERNS.md
@content/fr/blog/test-kotlin-syntax.md
</context>
<editorial_brief>
**Slug :** `hytale-plugin-development-2026` — identique FR+EN.
**Titre FR :** "Développement de plugins Hytale en 2026 : état de l'art et perspectives"
**Titre EN :** "Hytale plugin development in 2026: state of the art and outlook"
**Description FR :** "Tour d'horizon de l'écosystème plugin Hytale en 2026 : stack technique, patterns modernes, et ce qui attend la communauté."
**Description EN :** "A 2026 snapshot of the Hytale plugin ecosystem: tech stack, modern patterns, and what's next for the community."
**Tags :** `["hytale", "industry", "analysis"]` (D-15)
**Date :** `"2026-04-22"`
**Draft :** `false`
**Image :** omis (fallback applicable)
**Champ `updated` :** omis
**Ton :** première personne, analytique mais sans être sec. Perspective praticien ("ce que j'ai observé", "ce qui tourne en prod").
**Longueur :** 1000-1400 mots.
**Outline recommandé (6 sections) :**
1. **Intro — Hytale en 2026, où en est-on ?** (~200 mots)
- Contexte sortie + maturité SDK.
- Qui code pour Hytale aujourd'hui : indie, serveurs communautaires, devs commerciaux.
- Thèse de l'article : le paysage plugin s'est professionnalisé, voici ce qui change.
- **Placement 1er lien `/hytale`** : "Je développe moi-même [des plugins Hytale sur commande](/hytale) depuis les premières betas, et le paysage a radicalement changé." (FR) / "I've been building [Hytale plugins on commission](/en/hytale) since the early betas, and the landscape has shifted dramatically." (EN)
2. **La stack 2026 : Kotlin, coroutines, et outillage mature** (~250 mots)
- Kotlin reste la lingua franca ; Java résiduel.
- Gradle Kotlin DSL standard.
- IDE support (IntelliJ IDEA Ultimate recommandé).
- Testing : JUnit 5 + MockK pour plugins.
- **Bloc code Kotlin moderne obligatoire ici — coroutines pour event async :**
```kotlin
package com.example.ecoplugin
import io.hytale.api.HytalePlugin
import io.hytale.api.event.EventHandler
import io.hytale.api.event.player.PlayerJoinEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class EcoPlugin : HytalePlugin() {
private val scope = CoroutineScope(Dispatchers.IO)
@EventHandler
fun onJoin(event: PlayerJoinEvent) {
scope.launch {
val profile = profileRepo.fetch(event.player.uuid)
event.player.sendMessage("Welcome back, balance: ${profile.balance}")
}
}
}
```
- Expliquer : pourquoi coroutines (non-blocking I/O), scope lifecycle, dispatcher choice.
3. **Patterns modernes — ce qui a remplacé les mauvaises habitudes Bukkit-era** (~250 mots)
- Dependency injection (Koin / manual constructor injection).
- Event handlers séparés de la logique métier.
- Config typée (kotlinx.serialization).
- Tests unitaires sur la logique, intégration sur les handlers.
4. **Écosystème — libs et SDKs qui comptent** (~200 mots)
- SDK officiel Hytale.
- Communauté : hubs GitHub actifs, Discord devs.
- Anti-patterns à éviter (sans nommer de projets précis pour éviter les affirmations fragiles).
5. **Ce que l'avenir apporte** (~200 mots)
- Scripting côté client (si annoncé / spéculation cadrée).
- Packaging formats évolutifs.
- Monétisation indie : modèles commission, rev-share.
- **Placement 2e lien `/hytale`** : "Si tu veux externaliser le dev d'un plugin ambitieux, je propose [du développement Hytale sur commande](/hytale) — configs et patterns modernes inclus." (FR)
6. **Conclusion** (~150 mots)
- 2026 = l'année où le dev Hytale devient un vrai métier, pas juste un hobby.
**Liens internes (D-08, D-09) :**
- FR : ≥1 lien `](/hytale)`, idéalement 2.
- EN : ≥1 lien `](/en/hytale)`, idéalement 2.
- Paths hardcoded, pas `localePath()`.
**Bloc code Kotlin :** au moins 1 bloc réaliste (exemple coroutines ci-dessus satisfait). Imports kotlinx.coroutines cohérents. Pas pseudo-code.
**Frontmatter exact FR :**
```yaml
---
title: "Développement de plugins Hytale en 2026 : état de l'art et perspectives"
description: "Tour d'horizon de l'écosystème plugin Hytale en 2026 : stack technique, patterns modernes, et ce qui attend la communauté."
date: "2026-04-22"
tags: ["hytale", "industry", "analysis"]
draft: false
---
```
**Frontmatter exact EN :**
```yaml
---
title: "Hytale plugin development in 2026: state of the art and outlook"
description: "A 2026 snapshot of the Hytale plugin ecosystem: tech stack, modern patterns, and what's next for the community."
date: "2026-04-22"
tags: ["hytale", "industry", "analysis"]
draft: false
---
```
**Précision importante :** comme l'article fait des claims sur l'état de l'industrie en 2026, l'executor DOIT formuler les affirmations de manière défendable (éviter les chiffres inventés, éviter de nommer des projets tiers de manière non-vérifiée). Préférer "ce que j'observe", "ce qui tourne chez mes clients", "la tendance que je constate".
</editorial_brief>
<tasks>
<task type="auto">
<name>Task 1: Rédiger version FR de l'article positionnement 2026</name>
<files>content/fr/blog/hytale-plugin-development-2026.md</files>
<read_first>
- content/fr/blog/test-kotlin-syntax.md (pattern frontmatter + code block)
- .planning/phases/08-content-cocon-semantique/08-CONTEXT.md §D-02, D-04, D-05, D-07, D-08, D-15
- .planning/phases/08-content-cocon-semantique/08-PATTERNS.md §"content/{fr,en}/blog/..."
- Section `<editorial_brief>` ci-dessus
</read_first>
<action>
Créer `content/fr/blog/hytale-plugin-development-2026.md`.
**Frontmatter exact** (copier depuis `<editorial_brief>`) — draft: false, tags `["hytale", "industry", "analysis"]`.
**Corps** : suivre l'outline 6 sections, 1000-1400 mots, ton analytique praticien première personne.
**Exigences dures :**
1. Au moins 1 bloc ```kotlin avec imports cohérents (coroutines + event handler). Exemple exact dans le brief §section 2.
2. Au moins 1 lien `](/hytale)` inline. Idéalement 2 (intro + section 5).
3. Pas de champ `image:`. Pas de `updated:`.
4. Frontmatter Zod-valide.
**Interdits :**
- Affirmations numériques inventées ("Xk plugins publiés") — utiliser formulations qualitatives.
- Noms de projets tiers non-vérifiés.
- Pseudo-code.
- `localePath()` / `<NuxtLink>` dans markdown.
- Liens absolus (préférer `/hytale` relatif).
**Style :**
- Titres `##` pour les 6 sections.
- Callouts optionnels (`::alert{type="info"}` pour nuances/disclaimers).
- Code inline pour noms de libs/classes.
</action>
<verify>
<automated>pnpm typecheck</automated>
</verify>
<done>
- `test -f content/fr/blog/hytale-plugin-development-2026.md`
- `grep "draft: false" content/fr/blog/hytale-plugin-development-2026.md` passe
- `grep -E "industry" content/fr/blog/hytale-plugin-development-2026.md` trouve le tag
- `grep -c '\](/hytale)' content/fr/blog/hytale-plugin-development-2026.md` ≥ 1
- `grep -c '```kotlin' content/fr/blog/hytale-plugin-development-2026.md` ≥ 1
- `wc -w content/fr/blog/hytale-plugin-development-2026.md` ≥ 800
- `pnpm typecheck` exit 0
</done>
</task>
<task type="auto">
<name>Task 2: Rédiger version EN de l'article positionnement 2026</name>
<files>content/en/blog/hytale-plugin-development-2026.md</files>
<read_first>
- content/fr/blog/hytale-plugin-development-2026.md (juste créé — référence équivalence)
- content/en/blog/test-kotlin-syntax.md (pattern EN)
- .planning/phases/08-content-cocon-semantique/08-CONTEXT.md §D-03, D-08, D-09
</read_first>
<action>
Créer `content/en/blog/hytale-plugin-development-2026.md` — même slug, contenu équivalent adapté en anglais idiomatique (pas traduction littérale).
**Frontmatter exact** :
```yaml
---
title: "Hytale plugin development in 2026: state of the art and outlook"
description: "A 2026 snapshot of the Hytale plugin ecosystem: tech stack, modern patterns, and what's next for the community."
date: "2026-04-22"
tags: ["hytale", "industry", "analysis"]
draft: false
---
```
**Corps :** 6 sections équivalentes, 1000-1400 mots.
**Règle critique liens (D-09) :**
- Version EN : `](/en/hytale)` — au moins 1, idéalement 2. JAMAIS `/hytale` sans préfixe.
**Bloc code Kotlin :** identique à la version FR (snippet coroutines — le code n'a pas à être traduit).
**Exigences et interdits identiques à Task 1.**
</action>
<verify>
<automated>pnpm typecheck</automated>
</verify>
<done>
- Fichier existe
- `grep "draft: false" content/en/blog/hytale-plugin-development-2026.md` passe
- `grep -c '\](/en/hytale)' content/en/blog/hytale-plugin-development-2026.md` ≥ 1
- `grep -c '```kotlin' content/en/blog/hytale-plugin-development-2026.md` ≥ 1
- `wc -w content/en/blog/hytale-plugin-development-2026.md` ≥ 800
- `pnpm typecheck` exit 0
- Run `pnpm dev` puis `curl http://localhost:3000/blog/hytale-plugin-development-2026` → 200 FR
- `curl http://localhost:3000/en/blog/hytale-plugin-development-2026` → 200 EN
- `curl http://localhost:3000/blog` contient les 2 articles (celui-ci + celui de 08-02) en FR
- `curl http://localhost:3000/hytale` contient la section "Articles récents" avec les 2 slugs
- `curl http://localhost:3000/sitemap.xml` (ou sitemaps indexés) contient les 2 URLs FR+EN du slug 2026 avec hreflang alternates (Phase 7-04 automatique)
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| markdown author → Zod schema → SSR | Contenu statique, Zod-validé, aucun user input |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-08-07 | T (Tampering) | frontmatter YAML | mitigate | Schema Zod blog_fr/blog_en, typecheck gate |
| T-08-08 | R (Repudiation) | claims industrie 2026 | mitigate | Formulations qualitatives "ce que j'observe" plutôt que chiffres — évite affirmations non-sourcées |
| T-08-09 | I (Info Disclosure) | liens inline | accept | Paths publics |
</threat_model>
<verification>
- 2 articles passent typecheck
- `curl /blog` et `/en/blog` listent désormais **au moins 2 articles tagués hytale** au total (celui-ci + 08-02)
- Section "Articles récents" sur /hytale affiche 2 cards BlogCard compact
- Sitemap inclut les 4 URLs (2 slugs × 2 locales) avec hreflang
</verification>
<success_criteria>
- Article positionnement publié FR+EN
- Ensemble avec Plan 08-02 : le cocon sémantique est fermé (≥2 articles tagués hytale, bidirectionnels)
- Phase goal atteint : "Section 'Articles récents' affiche des cards réelles sur /hytale"
</success_criteria>
<output>
Après complétion, créer `.planning/phases/08-content-cocon-semantique/08-03-SUMMARY.md`.
</output>