diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 7dcba86..e8dd808 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -27,7 +27,10 @@ Decimal phases appear between their surrounding integers in numeric order. 2. All static data files exist under `data/` and are importable with TypeScript strict — no `any` types 3. `useProjects()` composable returns typed project list and supports filtering by category and search 4. `npx nuxi typecheck` and `npx eslint .` exit with 0 errors -**Plans**: TBD +**Plans**: 2 plans +Plans: +- [ ] 01-01-PLAN.md — Scaffold Nuxt 4, modules, TypeScript strict, interfaces +- [ ] 01-02-PLAN.md — Migration donnees statiques + useProjects() ### Phase 2: SSR Shell **Goal**: Every route renders the correct language, theme, and SEO metadata on the server — confirmed by `curl` with no JavaScript @@ -39,7 +42,10 @@ Decimal phases appear between their surrounding integers in numeric order. 3. Toggling dark/light mode in the header persists across page reload with no flash on cold load 4. `curl http://localhost:3000` response includes ``, `og:title`, `og:description`, and JSON-LD script tag 5. `http://localhost:3000/sitemap.xml` returns a valid XML sitemap with `hreflang` alternates for FR and EN URLs -**Plans**: TBD +**Plans**: 2 plans +Plans: +- [ ] 01-01-PLAN.md — Scaffold Nuxt 4, modules, TypeScript strict, interfaces +- [ ] 01-02-PLAN.md — Migration donnees statiques + useProjects() **UI hint**: yes ### Phase 3: Pages & Ship @@ -52,7 +58,10 @@ Decimal phases appear between their surrounding integers in numeric order. 3. Submitting the contact form with valid data shows a success toast; EmailJS delivers the email 4. `docker build` completes and `docker run` serves the SSR app on port 3000 5. Google Analytics 4 events appear in GA4 DebugView when browsing in production mode -**Plans**: TBD +**Plans**: 2 plans +Plans: +- [ ] 01-01-PLAN.md — Scaffold Nuxt 4, modules, TypeScript strict, interfaces +- [ ] 01-02-PLAN.md — Migration donnees statiques + useProjects() **UI hint**: yes ## Progress diff --git a/.planning/phases/01-foundation/01-01-PLAN.md b/.planning/phases/01-foundation/01-01-PLAN.md new file mode 100644 index 0000000..2f4b5f6 --- /dev/null +++ b/.planning/phases/01-foundation/01-01-PLAN.md @@ -0,0 +1,338 @@ +--- +phase: 01-foundation +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - nuxt.config.ts + - package.json + - pnpm-lock.yaml + - tsconfig.json + - app/app.vue + - shared/types/index.ts + - .gitignore +autonomous: true +requirements: + - SSR-01 + - SSR-02 + - SSR-03 + - INFRA-02 + - INFRA-03 + +must_haves: + truths: + - "nuxt dev demarre sans erreur et sert localhost:3000" + - "La structure app/ est utilisee (Nuxt 4 compatibilityVersion 4)" + - "Tous les modules sont installes dans nuxt.config.ts" + - "TypeScript strict mode est actif" + - "ESLint via @nuxt/eslint fonctionne sans erreur" + artifacts: + - path: "nuxt.config.ts" + provides: "Configuration principale Nuxt 4 avec tous les modules" + contains: "compatibilityVersion: 4" + - path: "app/app.vue" + provides: "Composant racine Nuxt" + - path: "shared/types/index.ts" + provides: "Interfaces TypeScript resserrees" + exports: ["Project", "ProjectButton", "Technology", "TechStack", "Testimonial", "FAQ"] + - path: "package.json" + provides: "Dependances Nuxt 4 + tous modules" + key_links: + - from: "nuxt.config.ts" + to: "app/app.vue" + via: "Nuxt srcDir convention" + pattern: "compatibilityVersion.*4" +--- + +<objective> +Initialiser le projet Nuxt 4 avec pnpm, installer tous les modules, configurer TypeScript strict et ESLint, et definir les interfaces TypeScript resserrees. + +Purpose: Creer le squelette technique Nuxt 4 sur lequel toute la migration repose. +Output: Projet Nuxt 4 fonctionnel avec `pnpm dev` qui demarre, tous modules configures, types definis. +</objective> + +<execution_context> +@C:/Users/minit/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/minit/.claude/get-shit-done/templates/summary.md +</execution_context> + +<context> +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-foundation/01-CONTEXT.md +@.planning/phases/01-foundation/01-RESEARCH.md + +<interfaces> +<!-- Types existants a migrer et resserrer depuis src/types/index.ts --> +From src/types/index.ts: +```typescript +export interface Project { + id: string + title: string + image: string + description: string + longDescription?: string + technologies?: string[] + category?: string + featured?: boolean + buttons?: ProjectButton[] + date?: string + demoUrl?: string + githubUrl?: string + features?: string[] + gallery?: string[] + status?: string +} + +export interface ProjectButton { + title: string + link: string +} + +export interface Technology { + name: string + level: 'Beginner' | 'Intermediate' | 'Advanced' + image: string +} + +export interface TechStack { + programming: Technology[] + front: Technology[] + database: Technology[] + devtools: Technology[] + operating_systems: Technology[] + socials: Technology[] +} +``` +</interfaces> +</context> + +<tasks> + +<task type="auto"> + <name>Task 1: Initialiser le projet Nuxt 4 avec pnpm et tous les modules</name> + <files>nuxt.config.ts, package.json, pnpm-lock.yaml, app/app.vue, .gitignore, tsconfig.json</files> + <read_first> + - src/types/index.ts (types existants pour reference) + - package.json (dependances actuelles Vue 3) + - .gitignore (regles existantes) + </read_first> + <action> +1. Installer pnpm globalement si absent: `npm install -g pnpm` +2. Initialiser le projet Nuxt 4: `pnpm dlx nuxi@latest init . --force` (force car le dossier n'est pas vide). Si nuxi init ne supporte pas --force dans un repo existant, creer dans un sous-dossier temp et copier les fichiers generes. +3. Installer tous les modules (per D-08, D-09): + ```bash + pnpm add @nuxt/ui @nuxtjs/i18n @nuxt/eslint @nuxtjs/sitemap nuxt-gtag @nuxt/image + ``` + NOTE: Ne PAS installer @nuxtjs/color-mode — deja inclus dans @nuxt/ui. + +4. Configurer nuxt.config.ts avec ce contenu exact: + ```typescript + export default defineNuxtConfig({ + future: { + compatibilityVersion: 4 + }, + ssr: true, + modules: [ + '@nuxt/ui', + '@nuxtjs/i18n', + '@nuxt/eslint', + '@nuxtjs/sitemap', + 'nuxt-gtag', + '@nuxt/image' + ], + typescript: { + strict: true + }, + i18n: { + locales: ['fr', 'en'], + defaultLocale: 'fr' + }, + gtag: { + id: 'G-CDVVNFY6MV', + enabled: false + } + }) + ``` + +5. Creer `app/app.vue` minimal: + ```vue + <template> + <div> + <NuxtRouteAnnouncer /> + <NuxtPage /> + </div> + </template> + ``` + +6. Creer `app/pages/index.vue` minimal pour que le serveur demarre sans erreur: + ```vue + <template> + <div> + <h1>Portfolio Killian Dalcin</h1> + <p>Nuxt 4 Foundation</p> + </div> + </template> + ``` + +7. Mettre a jour .gitignore pour inclure: `node_modules`, `.nuxt`, `.output`, `dist`, `.env` + +8. Verifier que `pnpm dev` demarre sans erreur sur localhost:3000 + </action> + <verify> + <automated>cd C:/Users/minit/Desktop/portfolio/portfolio && pnpm dev --port 3000 & sleep 15 && curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 | grep -q "200" && echo "PASS" || echo "FAIL"; kill %1 2>/dev/null</automated> + </verify> + <acceptance_criteria> + - nuxt.config.ts contains `compatibilityVersion: 4` + - nuxt.config.ts contains `'@nuxt/ui'` in modules array + - nuxt.config.ts contains `'@nuxtjs/i18n'` in modules array + - nuxt.config.ts contains `'@nuxt/eslint'` in modules array + - nuxt.config.ts contains `'@nuxtjs/sitemap'` in modules array + - nuxt.config.ts contains `'nuxt-gtag'` in modules array + - nuxt.config.ts contains `'@nuxt/image'` in modules array + - nuxt.config.ts contains `strict: true` + - package.json contains `@nuxt/ui` in dependencies + - package.json contains `@nuxtjs/i18n` in dependencies + - app/app.vue exists with NuxtPage component + - pnpm dev starts and localhost:3000 returns HTTP 200 + </acceptance_criteria> + <done>Projet Nuxt 4 demarre sur localhost:3000 avec tous les modules installes, TypeScript strict actif</done> +</task> + +<task type="auto"> + <name>Task 2: Definir les interfaces TypeScript resserrees et configurer ESLint</name> + <files>shared/types/index.ts</files> + <read_first> + - src/types/index.ts (types existants a resserrer per D-03) + - src/data/testimonials.ts (interface Testimonial existante) + - src/data/faq.ts (interface FAQ existante) + - nuxt.config.ts (verifier @nuxt/eslint present) + </read_first> + <action> +1. Creer `shared/types/index.ts` avec les interfaces resserrees (per D-03 — rendre obligatoires technologies, category, date): + +```typescript +export interface ProjectButton { + title: string + link: string +} + +export interface Project { + id: string + image: string // URL /images/xxx.webp + technologies: string[] // OBLIGATOIRE (etait optionnel) + category: string // OBLIGATOIRE (etait optionnel) + date: string // OBLIGATOIRE (etait optionnel) + featured?: boolean + buttons?: ProjectButton[] + gallery?: string[] + demoUrl?: string + githubUrl?: string + features?: string[] + // Pas de title/description/longDescription/status — i18n via cles +} + +export interface Technology { + name: string + level: 'Beginner' | 'Intermediate' | 'Advanced' + image: string +} + +export interface TechStack { + programming: Technology[] + front: Technology[] + database: Technology[] + devtools: Technology[] + operating_systems: Technology[] + socials: Technology[] +} + +export interface Testimonial { + name: string + role: string + company: string + avatar: string + rating: number + content: string + date: string + platform: string + featured?: boolean + project_type: string + results?: string[] +} + +export interface TestimonialsStats { + totalReviews: number + averageRating: number + projectsCompleted: number +} + +export interface FAQ { + questionKey: string + answerKey: string + featuresKey: string +} +``` + +Note: FAQ utilise des cles i18n (per D-02) au lieu de texte direct. L'ancienne interface avait `question: string` (texte), la nouvelle a `questionKey: string` (cle de traduction). + +2. Verifier que `pnpm nuxi typecheck` passe (les types sont auto-importes depuis shared/ en Nuxt 4). + +3. Verifier que `pnpm eslint .` passe sans erreur (ESLint configure via @nuxt/eslint dans les modules). + </action> + <verify> + <automated>cd C:/Users/minit/Desktop/portfolio/portfolio && npx nuxi typecheck 2>&1 | tail -5</automated> + </verify> + <acceptance_criteria> + - shared/types/index.ts contains `technologies: string[]` (not optional) + - shared/types/index.ts contains `category: string` (not optional) + - shared/types/index.ts contains `date: string` (not optional, in Project interface) + - shared/types/index.ts contains `export interface Project` + - shared/types/index.ts contains `export interface Technology` + - shared/types/index.ts contains `export interface TechStack` + - shared/types/index.ts contains `export interface Testimonial` + - shared/types/index.ts contains `export interface FAQ` + - shared/types/index.ts contains `questionKey: string` + - npx nuxi typecheck exits with code 0 + </acceptance_criteria> + <done>Toutes les interfaces TypeScript resserrees existent dans shared/types/index.ts, typecheck et eslint passent sans erreur</done> +</task> + +</tasks> + +<threat_model> +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Aucune | Phase 1 est une initialisation technique sans surface d'attaque | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-01-01 | I (Information Disclosure) | nuxt.config.ts | mitigate | gtag.enabled: false — pas de tracking en dev | +| T-01-02 | T (Tampering) | pnpm dependencies | accept | lockfile pnpm-lock.yaml tracke dans git | +</threat_model> + +<verification> +1. `pnpm dev` demarre sans erreur sur localhost:3000 +2. `npx nuxi typecheck` exit 0 +3. `pnpm eslint .` exit 0 (si le script existe, sinon `npx eslint .`) +4. nuxt.config.ts contient les 6 modules et compatibilityVersion 4 +5. shared/types/index.ts exporte Project, Technology, TechStack, Testimonial, FAQ +</verification> + +<success_criteria> +- Le projet Nuxt 4 demarre localement +- Tous les modules sont installes et declares +- TypeScript strict mode actif +- Interfaces resserrees per D-03 +- ESLint fonctionne via @nuxt/eslint +</success_criteria> + +<output> +After completion, create `.planning/phases/01-foundation/01-01-SUMMARY.md` +</output> diff --git a/.planning/phases/01-foundation/01-02-PLAN.md b/.planning/phases/01-foundation/01-02-PLAN.md new file mode 100644 index 0000000..18102dd --- /dev/null +++ b/.planning/phases/01-foundation/01-02-PLAN.md @@ -0,0 +1,439 @@ +--- +phase: 01-foundation +plan: 02 +type: execute +wave: 2 +depends_on: ["01-01"] +files_modified: + - app/data/projects.ts + - app/data/testimonials.ts + - app/data/faq.ts + - app/data/techstack.ts + - app/composables/useProjects.ts + - public/images/ +autonomous: true +requirements: + - DATA-01 + - DATA-02 + - DATA-03 + - DATA-04 + - DATA-05 + +must_haves: + truths: + - "Les donnees projets sont importables depuis app/data/projects.ts avec le type Project" + - "Les donnees testimonials sont importables avec le type Testimonial" + - "Les donnees FAQ utilisent des cles i18n et non du texte direct" + - "Les donnees techstack sont importables avec le type TechStack" + - "useProjects() retourne une liste typee et supporte filterByCategory, search, findById" + - "Toutes les images referenceent /images/ et non @/assets/images/" + artifacts: + - path: "app/data/projects.ts" + provides: "Donnees brutes des 7 projets" + contains: "export const projects" + - path: "app/data/testimonials.ts" + provides: "Donnees temoignages" + contains: "export const testimonials" + - path: "app/data/faq.ts" + provides: "Donnees FAQ avec cles i18n" + contains: "export const homeFAQs" + - path: "app/data/techstack.ts" + provides: "Donnees tech stack" + contains: "export const techStack" + - path: "app/composables/useProjects.ts" + provides: "Composable filtrage/recherche projets" + exports: ["useProjects"] + key_links: + - from: "app/composables/useProjects.ts" + to: "app/data/projects.ts" + via: "import direct" + pattern: "import.*from.*data/projects" + - from: "app/data/projects.ts" + to: "shared/types/index.ts" + via: "type import" + pattern: "import type.*Project" +--- + +<objective> +Migrer toutes les donnees statiques vers app/data/, copier les images vers public/images/, et reecrire useProjects() en style Nuxt natif. + +Purpose: Les donnees du portfolio sont disponibles et typees pour les phases suivantes. +Output: 4 fichiers data, 1 composable, images dans public/images/. +</objective> + +<execution_context> +@C:/Users/minit/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/minit/.claude/get-shit-done/templates/summary.md +</execution_context> + +<context> +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-foundation/01-CONTEXT.md +@.planning/phases/01-foundation/01-RESEARCH.md +@.planning/phases/01-foundation/01-01-SUMMARY.md + +<interfaces> +<!-- Types crees par Plan 01 dans shared/types/index.ts --> +```typescript +export interface Project { + id: string + image: string + technologies: string[] + category: string + date: string + featured?: boolean + buttons?: ProjectButton[] + gallery?: string[] + demoUrl?: string + githubUrl?: string + features?: string[] +} + +export interface ProjectButton { + title: string + link: string +} + +export interface Technology { + name: string + level: 'Beginner' | 'Intermediate' | 'Advanced' + image: string +} + +export interface TechStack { + programming: Technology[] + front: Technology[] + database: Technology[] + devtools: Technology[] + operating_systems: Technology[] + socials: Technology[] +} + +export interface Testimonial { + name: string + role: string + company: string + avatar: string + rating: number + content: string + date: string + platform: string + featured?: boolean + project_type: string + results?: string[] +} + +export interface TestimonialsStats { + totalReviews: number + averageRating: number + projectsCompleted: number +} + +export interface FAQ { + questionKey: string + answerKey: string + featuresKey: string +} +``` +</interfaces> +</context> + +<tasks> + +<task type="auto"> + <name>Task 1: Migrer les donnees statiques et les images</name> + <files>app/data/projects.ts, app/data/testimonials.ts, app/data/faq.ts, app/data/techstack.ts, public/images/</files> + <read_first> + - src/composables/useProjects.ts (donnees projets inline a extraire) + - src/data/testimonials.ts (donnees + interface existantes) + - src/data/faq.ts (donnees + pattern getHomeFAQs existant) + - src/data/techstack.ts (donnees existantes) + - shared/types/index.ts (interfaces resserrees de Plan 01) + </read_first> + <action> +1. Copier toutes les images WebP de `src/assets/images/` vers `public/images/` (per D-06, D-07): + ```bash + mkdir -p public/images/flowboard + cp src/assets/images/*.webp public/images/ + cp src/assets/images/flowboard/*.webp public/images/flowboard/ + ``` + +2. Creer `app/data/projects.ts` (per D-01, D-02 — donnees separees, cles i18n): + ```typescript + import type { Project } from '~~/shared/types' + + export const projects: Project[] = [ + { + id: 'virtual-tour', + image: '/images/virtualtour.webp', + technologies: ['Vue.js', 'Three.js', 'WebGL', 'Node.js'], + category: 'Web Development', + date: '2022', + buttons: [ + { title: 'Visit', link: 'https://www.lycee-chabanne16.fr/visites/BACSN/index.htm' } + ] + }, + { + id: 'xinko', + image: '/images/xinko.webp', + technologies: ['Node.js', 'Discord.js', 'MongoDB', 'Express'], + category: 'Bot Development', + date: '2023', + featured: true, + buttons: [ + { title: 'Invite', link: 'https://discord.com/api/oauth2/authorize?client_id=1035571329866407976&permissions=292288982151&scope=applications.commands%20bot' } + ] + }, + { + id: 'image-manipulation', + image: '/images/dig.webp', + technologies: ['JavaScript', 'Node.js', 'Canvas', 'npm'], + category: 'Open Source', + date: '2022', + featured: true, + buttons: [ + { title: 'Repository', link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-image-generation' }, + { title: 'NPM Package', link: 'https://www.npmjs.com/package/discord-image-generation' } + ] + }, + { + id: 'primate-web-admin', + image: '/images/primate.webp', + technologies: ['React', 'TypeScript', 'Node.js', 'Express'], + category: 'Enterprise Software', + date: '2023' + }, + { + id: 'instagram-bot', + image: '/images/instagram.webp', + technologies: ['JavaScript', 'Node.js', 'Instagram API', 'Canvas'], + category: 'Social Media Bot', + date: '2022', + buttons: [ + { title: 'Repository', link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/instagram-bot' } + ] + }, + { + id: 'crowdin-status-bot', + image: '/images/crowdin.webp', + technologies: ['Node.js', 'Discord.js', 'Crowdin API', 'Cron'], + category: 'Automation', + date: '2023', + buttons: [ + { title: 'Repository', link: 'https://git.mrkayjaydee.xyz/Mr-KayJayDee/discord-crowdin-status' } + ] + }, + { + id: 'flowboard', + image: '/images/flowboard/flowboard_1.webp', + technologies: ['Vue.js', 'Node.js', 'TypeScript', 'MongoDB', 'Express'], + category: 'Web Development', + date: '2024', + featured: true, + features: [ + 'Organize your tasks, projects and ideas by creating thematic boards adapted to your needs', + 'Add cards for each task, assign members, set due dates, and track progress at a glance', + 'Invite colleagues and teammates to join your boards to work together, share ideas, and coordinate your efforts', + 'Keep an overview of the progress of your projects thanks to a simple and intuitive interface', + 'Use labels, lists and tables to prioritize tasks, set priorities and keep the overview clear' + ], + gallery: [ + '/images/flowboard/flowboard_1.webp', + '/images/flowboard/flowboard_2.webp', + '/images/flowboard/flowboard_3.webp', + '/images/flowboard/flowboard_4.webp' + ] + } + ] + ``` + +3. Creer `app/data/testimonials.ts` — copie directe, juste changer l'import type: + ```typescript + import type { Testimonial, TestimonialsStats } from '~~/shared/types' + + export const testimonials: Testimonial[] = [ + // ... (copier les 5 temoignages existants tels quels de src/data/testimonials.ts) + ] + + export const testimonialsStats: TestimonialsStats = { + totalReviews: 10, + averageRating: 5.0, + projectsCompleted: 25 + } + ``` + +4. Creer `app/data/faq.ts` (per D-02 — cles i18n au lieu de texte): + ```typescript + import type { FAQ } from '~~/shared/types' + + export const homeFAQs: FAQ[] = [ + { + questionKey: 'faq.homeFaq.delivery.question', + answerKey: 'faq.homeFaq.delivery.answer', + featuresKey: 'faq.homeFaq.delivery.features' + }, + { + questionKey: 'faq.homeFaq.maintenance.question', + answerKey: 'faq.homeFaq.maintenance.answer', + featuresKey: 'faq.homeFaq.maintenance.features' + }, + { + questionKey: 'faq.homeFaq.companies.question', + answerKey: 'faq.homeFaq.companies.answer', + featuresKey: 'faq.homeFaq.companies.features' + } + ] + ``` + +5. Creer `app/data/techstack.ts` — copie avec chemins images mis a jour: + ```typescript + import type { TechStack } from '~~/shared/types' + + export const techStack: TechStack = { + // ... (copier depuis src/data/techstack.ts, remplacer TOUS les `@/assets/images/xxx.webp` par `/images/xxx.webp`) + } + ``` + Remplacement a effectuer: `@/assets/images/` -> `/images/` pour CHAQUE entree (60+ images). + </action> + <verify> + <automated>cd C:/Users/minit/Desktop/portfolio/portfolio && npx nuxi typecheck 2>&1 | tail -5</automated> + </verify> + <acceptance_criteria> + - app/data/projects.ts contains `export const projects: Project[]` + - app/data/projects.ts contains `/images/virtualtour.webp` (not `@/assets/images/`) + - app/data/projects.ts contains 7 project objects (virtual-tour through flowboard) + - app/data/testimonials.ts contains `export const testimonials: Testimonial[]` + - app/data/testimonials.ts contains `export const testimonialsStats: TestimonialsStats` + - app/data/faq.ts contains `export const homeFAQs: FAQ[]` + - app/data/faq.ts contains `questionKey:` (i18n keys, not direct text) + - app/data/techstack.ts contains `export const techStack: TechStack` + - app/data/techstack.ts contains `/images/javascript.webp` (not `@/assets/images/`) + - public/images/ directory contains .webp files + - No file in app/data/ contains `@/assets/images/` + - npx nuxi typecheck exits with code 0 + </acceptance_criteria> + <done>4 fichiers data migres avec types corrects, images dans public/images/, aucune reference a @/assets/images/</done> +</task> + +<task type="auto"> + <name>Task 2: Reecrire useProjects() en style Nuxt natif</name> + <files>app/composables/useProjects.ts</files> + <read_first> + - src/composables/useProjects.ts (composable existant a reecrire) + - app/data/projects.ts (donnees separees de Task 1) + - shared/types/index.ts (interfaces) + </read_first> + <action> +Creer `app/composables/useProjects.ts` en style Nuxt natif (per D-04, D-05): + +```typescript +import { projects as projectsData } from '~/data/projects' + +export function useProjects() { + const { t } = useI18n() + + const projects = computed(() => + projectsData.map(p => ({ + ...p, + title: t(`projects.${p.id}.title`), + description: t(`projects.${p.id}.description`), + longDescription: t(`projects.${p.id}.longDescription`) || undefined + })) + ) + + const featuredProjects = computed(() => + projects.value.filter(p => p.featured) + ) + + function filterByCategory(category: string) { + return computed(() => + projects.value.filter(p => p.category === category) + ) + } + + function search(query: Ref<string> | string) { + return computed(() => { + const q = typeof query === 'string' ? query : query.value + if (!q) return projects.value + const lower = q.toLowerCase() + return projects.value.filter(p => + p.title.toLowerCase().includes(lower) || + p.description.toLowerCase().includes(lower) || + p.technologies.some(tech => tech.toLowerCase().includes(lower)) + ) + }) + } + + function findById(id: string) { + return computed(() => projects.value.find(p => p.id === id)) + } + + return { + projects, + featuredProjects, + filterByCategory, + search, + findById + } +} +``` + +Points cles per D-04: +- Pas d'import `computed`, `useI18n` — auto-importes par Nuxt +- Import des donnees depuis `~/data/projects` (pas `@/`) +- Pas de wrapper useI18n custom — utilise directement l'auto-import @nuxtjs/i18n +- Les cles i18n suivent le pattern `projects.${id}.title` (per D-02) + </action> + <verify> + <automated>cd C:/Users/minit/Desktop/portfolio/portfolio && npx nuxi typecheck 2>&1 | tail -5</automated> + </verify> + <acceptance_criteria> + - app/composables/useProjects.ts contains `export function useProjects()` + - app/composables/useProjects.ts contains `import { projects as projectsData } from '~/data/projects'` + - app/composables/useProjects.ts contains `const { t } = useI18n()` + - app/composables/useProjects.ts contains `filterByCategory` + - app/composables/useProjects.ts contains `search` + - app/composables/useProjects.ts contains `findById` + - app/composables/useProjects.ts contains `featuredProjects` + - app/composables/useProjects.ts does NOT contain `import { computed }` (auto-imported) + - app/composables/useProjects.ts does NOT contain `from '@/composables/useI18n'` + - npx nuxi typecheck exits with code 0 + </acceptance_criteria> + <done>useProjects() retourne projects, featuredProjects, filterByCategory, search, findById — tout type-safe et style Nuxt natif</done> +</task> + +</tasks> + +<threat_model> +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Aucune | Donnees statiques, pas d'input utilisateur | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-01-03 | I (Information Disclosure) | testimonials avatars | accept | URLs ui-avatars.com publiques, pas de PII | +</threat_model> + +<verification> +1. `npx nuxi typecheck` exit 0 +2. Aucun fichier dans app/data/ ne contient `@/assets/images/` +3. app/composables/useProjects.ts exporte useProjects avec 5 fonctions/proprietes +4. public/images/ contient les fichiers WebP +</verification> + +<success_criteria> +- Les 4 fichiers data existent et sont type-safe +- useProjects() compile sans erreur +- Images disponibles dans public/images/ +- Aucune reference aux anciens chemins @/assets/images/ +</success_criteria> + +<output> +After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md` +</output>