1704da8ce5
- Mark RESEARCH.md Open Questions as RESOLVED with decisions - Fix Plan 01-02 Task 1 verify to be independent of Task 2 (file existence + grep check instead of typecheck) - Strengthen negative criterion: all app/data/ files must NOT contain @/assets/images/
440 lines
15 KiB
Markdown
440 lines
15 KiB
Markdown
---
|
|
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 && node -e "const fs=require('fs'); const files=['app/data/projects.ts','app/data/testimonials.ts','app/data/faq.ts','app/data/techstack.ts']; let ok=true; for(const f of files){if(!fs.existsSync(f)){console.log('MISSING: '+f);ok=false;}else{const c=fs.readFileSync(f,'utf8');if(c.includes('@/assets/images/')){console.log('FAIL: '+f+' still contains @/assets/images/');ok=false;}}} if(!fs.existsSync('public/images')){console.log('MISSING: public/images/');ok=false;} console.log(ok?'PASS':'FAIL');"</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 does NOT contain `@/assets/images/` (all paths migrated)
|
|
- app/data/projects.ts does NOT contain `@/assets/images/` (all paths migrated)
|
|
- No file in app/data/ contains `@/assets/images/`
|
|
- public/images/ directory contains .webp files
|
|
</acceptance_criteria>
|
|
<done>4 fichiers data migres avec types corrects, images dans public/images/, aucune reference a @/assets/images/ dans aucun fichier app/data/</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>
|