docs: map existing codebase

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 17:50:10 +02:00
parent 7f776298a9
commit ce7cd19fef
7 changed files with 658 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# Architecture
**Analysis Date:** 2026-04-10
## SSR Strategy
Nuxt 4 with `ssr: true` and `compatibilityVersion: 4`. Every page renders server-side HTML with SEO metadata before hydrating client-side. Cookie-only persistence for locale and theme (no localStorage, SSR-safe).
## Layer Breakdown
```
Pages (app/pages/)
└─> Layout (app/layouts/default.vue)
├─> AppHeader (nav, locale toggle, theme toggle)
├─> Page content (slot)
└─> AppFooter (social links, copyright)
└─> Components (app/components/)
└─> Composables (app/composables/)
└─> Static Data (app/data/)
└─> Shared Types (shared/types/)
Server (server/api/)
└─> Contact POST handler (nodemailer SMTP)
```
## Data Flow
### Static Data + i18n
1. `app/data/projects.ts` exports projects WITHOUT translatable fields (title, description, longDescription omitted)
2. `app/composables/useProjects.ts` merges static data with i18n translations at runtime via `computed()`
3. Components consume `useProjects()` which returns reactive translated data
4. Language changes trigger recomputation automatically
### SSR Render Flow
1. Request hits Nitro server
2. Nuxt resolves locale from cookie (`i18n_redirected`) or URL prefix (`/en/`)
3. `useLocaleHead()` in `app.vue` sets `<html lang="...">` and alternate links
4. Page's `useSeoMeta()` resolves i18n keys server-side
5. `useHead()` injects JSON-LD structured data
6. Full HTML sent to client with correct locale, theme class, SEO metadata
### Theme Resolution
1. `@nuxtjs/color-mode` reads `nuxt-color-mode` cookie
2. Default: `dark` for new visitors
3. Cookie persistence — no flash on cold load (class applied server-side)
### Contact Form Flow
1. Client: Zod validation in `ContactForm.vue`
2. POST to `/api/contact` (Nitro route)
3. Server: manual validation, nodemailer SMTP via `useRuntimeConfig()` env vars
4. Response: success/error JSON
## Module System
| Module | Purpose |
|--------|---------|
| `@nuxt/ui` | Component library (Nuxt UI v3) |
| `@nuxtjs/i18n` | Internationalization (prefix_except_default, FR default) |
| `@nuxtjs/sitemap` | Auto-generated sitemap with i18n alternates |
| `nuxt-gtag` | Google Analytics (runtime config) |
| `@nuxt/image` | Image optimization |
| `@nuxt/eslint` | ESLint integration |
## Entry Points
| File | Role |
|------|------|
| `app/app.vue` | Root — wraps in `<UApp>`, applies `useLocaleHead()` |
| `app/layouts/default.vue` | Default layout — AppHeader + slot + AppFooter |
| `app/error.vue` | Global error handler (404 page) |
| `nuxt.config.ts` | App configuration |
| `app.config.ts` | Nuxt UI theme tokens (primary color) |
## State Management
No Pinia store. All state is:
- **Composable-scoped:** `useProjects()` returns reactive computed data
- **Module-managed:** locale via `@nuxtjs/i18n`, theme via `@nuxtjs/color-mode`
- **Component-local:** `ref()` / `reactive()` in `<script setup>`
## Error Handling
- Pages: `throw createError({ status: 404 })` for invalid routes/IDs
- `app/error.vue` catches all errors with i18n messages and navigation back
- Contact form: `try/catch/finally` with `useToast()` user feedback
- Server routes: `createError({ statusCode: 400 })` for validation failures
## Cross-Cutting Concerns
- **SEO:** `useSeoMeta()` per page + `useHead()` for JSON-LD + `useLocaleHead()` global
- **Accessibility:** Semantic HTML, aria attributes, keyboard navigation
- **i18n:** All user-facing text via `t()` keys, `te()` guards for optional keys
- **Images:** WebP in `public/images/`, served via `@nuxt/image`
---
*Architecture analysis: 2026-04-10*
+40
View File
@@ -0,0 +1,40 @@
# Concerns
## Security
- No rate limiting on `server/api/contact.post.ts` — the contact API accepts unlimited POST requests, enabling spam/email flooding
- No CAPTCHA or honeypot bot protection on `app/components/ContactForm.vue`
- `.env.example` only documents `NUXT_PUBLIC_GTAG_ID` but the contact form requires four SMTP vars (`NUXT_SMTP_HOST`, `NUXT_SMTP_USER`, `NUXT_SMTP_PASS`, `NUXT_SMTP_TO`) with no documentation
- Server-side email validation in `contact.post.ts` line 12 uses `email.includes('@')` instead of a proper regex, while client-side already uses Zod's `z.string().email()`
## Tech Debt
- `'https://killiandalcin.fr/og-image.png'` hardcoded verbatim in 6 page files — any domain change requires editing all of them
- Static `public/sitemap.xml` bypasses the installed `@nuxtjs/sitemap` module — new projects are never indexed, and `/formation` in the sitemap has no matching page
- Both `package-lock.json` (npm) and `pnpm-lock.yaml` (pnpm) coexist; `Dockerfile` uses `npm ci` after migration to pnpm
- `flowboard` project `features[]` array in `app/data/projects.ts` (lines 91-97) is hardcoded English, not i18n keys, while all other project content goes through `useProjects.ts`
- `siteConfig.seo.organization.aggregateRating` in `app/data/site.ts` claims `reviewCount: '50'` while `app/data/testimonials.ts` has `totalReviews: 10` — mismatched structured data Google could flag
- Two Fiverr services have `url: '#'` in `app/data/site.ts` — non-functional CTAs on the `/fiverr` page
## Performance / UX
- `HeroSection.vue` splits the title string by `.split(' ').slice(-2)` to apply gradient styling — breaks if the FR/EN title has a different word count
- All testimonial avatar URLs point to `https://ui-avatars.com/api/...` (external CDN, external HTTP requests per avatar on every render)
## Missing SEO Features
- No `ogUrl` set on any page (all `useSeoMeta` calls omit it)
- `app/pages/project/[id].vue` uses the generic `og-image.png` instead of `project.value?.image`
- No `<link rel="canonical">` — the `prefix_except_default` i18n strategy produces `/` and `/en/` duplicate URLs without canonical deduplication
- `/formation` in `public/sitemap.xml` has no corresponding page (`app/pages/formation.vue` does not exist)
## i18n Completeness
- `app/error.vue` lines 39-44: two hardcoded English error description strings not in locale files
- `app/components/sections/HeroSection.vue` line 30: `'Available for projects'` badge is raw English, not `t()`
- Same file lines 148, 153: `'50+ projects'` and `'5.0 rating'` decorative stats are hardcoded English
- `a11y.langToggle` in both locale files hardcodes the current language name as a static string
## Testing
- Zero test files exist anywhere in the project — no coverage for the security-sensitive contact API validation, `useProjects` composable, or i18n key resolution
+161
View File
@@ -0,0 +1,161 @@
# Coding Conventions
**Analysis Date:** 2026-04-10
## Naming Patterns
**Files:**
- Vue components: PascalCase — `AppHeader.vue`, `ProjectCard.vue`, `ContactForm.vue`
- Pages (Nuxt file-based routing): kebab-case — `about.vue`, `project/[id].vue`
- Layouts: kebab-case — `default.vue`
- Composables: camelCase with `use` prefix — `useProjects.ts`
- Data files: camelCase — `projects.ts`, `faq.ts`, `site.ts`, `techstack.ts`, `testimonials.ts`
- Type files: `index.ts` in a typed directory — `shared/types/index.ts`
- Server routes: `[name].[method].ts` Nitro convention — `contact.post.ts`
- Config files: camelCase — `nuxt.config.ts`, `app.config.ts`
**Functions:**
- Exported composables: `useX()``useProjects()` in `app/composables/useProjects.ts`
- Toggle handlers: verb + noun — `toggleLocale()`, `toggleTheme()`
- Query predicates: verb + noun — `isActive(path)`, `findById(id)`, `filterByCategory(category)`, `search(query)`
- Async handlers: `onX` prefix — `onSubmit(event)`
- Event handlers: `defineEventHandler` (Nitro server) — `server/api/contact.post.ts`
**Variables:**
- Reactive refs: camelCase — `mobileOpen`, `loading`
- Computed values: camelCase — `navLinks`, `translatedCategory`, `relatedProjects`, `featuredProjects`
- Constants/config exports: camelCase — `siteConfig`, `projects`, `homeFAQs`
**Types:**
- Interfaces: PascalCase — `Project`, `ProjectButton`, `Technology`, `TechStack`, `SiteConfig`, `FAQ`
- Props interfaces: always named `Props` in `<script setup>` — see `app/components/ProjectCard.vue`
- Type aliases derived from Zod: inline `type Schema = z.output<typeof schema>``app/components/ContactForm.vue`
- Enum-like string unions: `'Beginner' | 'Intermediate' | 'Advanced'``Technology.level` in `shared/types/index.ts`
## Code Style
**Formatting:**
- No dedicated Prettier config at root; formatting via ESLint through `@nuxt/eslint`
- ESLint config `eslint.config.mjs` delegates entirely to `withNuxt()` from `.nuxt/eslint.config.mjs`
- `@nuxt/eslint` module generates type-aware rules; `typescript: { strict: true }` in `nuxt.config.ts`
**Observed style from source:**
- No semicolons
- Single quotes for strings
- Trailing commas in multi-line objects/arrays
- 2-space indentation
- Long template attribute chains are NOT broken across lines (single long lines acceptable in templates)
## TypeScript Usage
**Strict Mode:** `typescript: { strict: true }` in `nuxt.config.ts` — all strict checks enforced project-wide.
**Type imports:** Always use `import type` for type-only imports:
```typescript
import type { Project } from '~~/shared/types'
import type { FormSubmitEvent } from '@nuxt/ui'
```
**Props typing:** Always `defineProps<Props>()` with an explicit interface named `Props`:
```typescript
interface Props {
project: Project
}
const props = defineProps<Props>()
```
**Return type inference:** Composables rely on inference; explicit generics where needed:
```typescript
const projects = computed<Project[]>(() => projectsData.map(...))
```
**Zod for runtime validation:** Client-side form schemas with Zod in components (`ContactForm.vue`). Server-side uses manual validation with `createError` — Zod not used on server.
## Import Organization
**Path Aliases:**
- `~/` -> `app/` directory (Nuxt 4 convention)
- `~~/` -> project root (for cross-layer imports: `~~/shared/types`)
- Use `~/data/projects` for app-internal imports; `~~/shared/types` to reach shared layer
**Import order (observed):**
1. Third-party: `import { z } from 'zod'`
2. Nuxt UI types: `import type { FormSubmitEvent } from '@nuxt/ui'`
3. Internal types: `import type { Project } from '~~/shared/types'`
4. Internal data: `import { projects as projectsData } from '~/data/projects'`
5. Auto-imports (no explicit import): `ref`, `computed`, `reactive`, `useI18n`, `useRoute`, `useColorMode`, `useSeoMeta`, `useHead`, `useToast`, `useLocalePath`
**Auto-imports:** All Nuxt/Vue composables and all `app/components/**/*.vue` components are auto-imported. Never write explicit `import ref from 'vue'` or component imports in `.vue` files. `pathPrefix: false` in `nuxt.config.ts` means `AppHeader` registers as `AppHeader` not `LayoutAppHeader`.
## Vue Patterns
**Component structure order:**
1. `<script setup lang="ts">` — always first, always `lang="ts"`
2. `<template>` — second
3. No `<style>` blocks — all styling via Tailwind utility classes
**Composition API rules:**
- `<script setup>` exclusively — no Options API
- Destructure composables at top: `const { t } = useI18n()`
- `ref()` for mutable primitives: `const mobileOpen = ref(false)`
- `reactive()` for multi-field form state: `const state = reactive({ name: '', email: '', message: '' })`
- `computed()` for derived state: `const relatedProjects = computed(() => [...])`
- `useTemplateRef()` for component refs (Vue 3.5 API): `const galleryRef = useTemplateRef('gallery')`
**Pages pattern:**
- `useSeoMeta()` called at top of each page's `<script setup>` with reactive getter functions
- Structured data injected via `useHead({ script: [{ type: 'application/ld+json', innerHTML: JSON.stringify({...}) }] })`
- 404s thrown inline: `throw createError({ status: 404, statusText: 'Project not found' })`
**i18n:**
- `useI18n()` called at component level; `t`, `locale`, `setLocale`, `te` destructured as needed
- `useLocalePath()` used for all `<NuxtLink :to>` values: `:to="localePath('/projects')"`
- `te()` guards optional translation keys before `t()` call
**Error handling:**
- Async operations wrapped in `try/catch/finally` with user feedback via `useToast()`
**Template conventions:**
- Semantic HTML: `<header>`, `<nav>`, `<article>`, `<aside>`, `<section>`, `<time>`
- Schema.org microdata: `itemscope`, `itemtype`, `itemprop` attributes directly on elements
- `aria-*` on all interactive elements and navigation landmarks
- `aria-current="page"` on active nav links
- `aria-hidden="true"` on decorative/background elements
## Composable Design
```typescript
export function useProjects() {
// logic
return {
projects,
featuredProjects,
filterByCategory,
search,
findById,
}
}
```
- Always return a named object, never a single value
- Filter/find functions return `computed()` so callers get reactivity
## Data Layer Conventions
**Static data** in `app/data/`:
- Export named typed constants
- Translatable fields omitted; resolved at runtime via i18n in the composable layer
- `app/data/projects.ts` exports `Omit<Project, 'title' | 'description' | 'longDescription'>[]`
**Shared types** in `shared/types/index.ts`:
- Single source for all domain interfaces
- Imported by both `app/` and `server/` via `~~/shared/types`
**Server routes** in `server/api/`:
- Use `defineEventHandler`, `readBody`, `useRuntimeConfig(event)`
- Manual input validation with `createError({ statusCode: 400 })`
- Return plain serializable objects
---
*Convention analysis: 2026-04-10*
+119
View File
@@ -0,0 +1,119 @@
# External Integrations
**Analysis Date:** 2026-04-10
## APIs & External Services
**Analytics:**
- Google Analytics / Google Tag Manager via `nuxt-gtag` ^4.1.0
- SDK/Client: `nuxt-gtag` Nuxt module
- Auth: `NUXT_PUBLIC_GTAG_ID` env var (public runtime config)
- Enabled only in production: `enabled: import.meta.env.NODE_ENV === 'production'`
- Config in `nuxt.config.ts` under `gtag:` and `runtimeConfig.public.gtag`
## Data Storage
**Databases:**
- None — all portfolio data is static (TypeScript data files in `app/data/`)
**File Storage:**
- Local filesystem only — images served from `public/` or via `@nuxt/image`
**Caching:**
- None — Nuxt SSR per-request rendering
## Authentication & Identity
**Auth Provider:**
- None — no user authentication required for this portfolio site
## Email
**SMTP Email (Contact Form):**
- Provider: Any SMTP-compatible server (configured at runtime)
- Implementation: `nodemailer` ^8.0.5 in server API route `app/api/contact.post.ts`
- Validation: `zod` ^4.3.6 validates request body server-side
- Auth env vars:
- `NUXT_SMTP_HOST` - SMTP server hostname
- `NUXT_SMTP_USER` - SMTP credentials username
- `NUXT_SMTP_PASS` - SMTP credentials password
- `NUXT_SMTP_TO` - Destination email address for contact messages
## SEO & Discoverability
**Sitemap:**
- `@nuxtjs/sitemap` ^8.0.12 — automatic XML sitemap generation
- Base URL: `https://killiandalcin.fr` (configured in `nuxt.config.ts` under `site:`)
- Site name: "Killian' DAL-CIN - Developpeur Full Stack"
## Monitoring & Observability
**Error Tracking:**
- None detected
**Logs:**
- Standard Node.js stdout/stderr (captured by Docker/host)
## CI/CD & Deployment
**Hosting:**
- Self-hosted Docker container on VPS
- Image: `node:22-alpine` (multi-stage build)
- Container port: 3000
- Reverse proxy: Traefik
- TLS via Let's Encrypt (`certresolver=public`)
- Wildcard cert covering `killiandalcin.fr` and `*.killiandalcin.fr`
- www → non-www permanent redirect middleware
- Config via Docker labels in `docker-compose.yml`
**CI Pipeline:**
- None detected — manual Docker image build and deploy
**Build process:**
1. `docker build` — runs `npm ci` + `nuxt build` in `node:22-alpine`
2. Output `.output/` copied to runtime stage
3. `docker-compose up` starts the container with runtime env vars
## Internationalization
**i18n Provider:**
- `@nuxtjs/i18n` ^10.2.4
- Strategy: `prefix_except_default` (French at `/`, English at `/en/`)
- Default locale: `fr`
- Supported locales: `fr` (fr-FR), `en` (en-US)
- Locale files: `i18n/locales/fr.json`, `i18n/locales/en.json`
- Browser detection: cookie-based (`i18n_redirected`) for SSR safety
## Image Optimization
**Provider:**
- `@nuxt/image` ^2.0.0
- Default provider: local (no external image CDN configured)
- Images served from `public/`
## Webhooks & Callbacks
**Incoming:**
- `POST /api/contact` — contact form submission endpoint (`app/api/contact.post.ts`)
**Outgoing:**
- None
## Environment Configuration
**Required env vars (production):**
- `NUXT_SMTP_HOST` - SMTP server hostname
- `NUXT_SMTP_USER` - SMTP username
- `NUXT_SMTP_PASS` - SMTP password
- `NUXT_SMTP_TO` - Contact form recipient email
- `NUXT_PUBLIC_GTAG_ID` - Google Analytics tag ID
- `PORTFOLIO_URL` - Primary domain (used in Traefik labels)
- `PORTFOLIO_URL_WWW` - WWW variant (used in Traefik www-redirect rule)
**Secrets location:**
- Passed as Docker environment variables at runtime (not committed to repo)
- `docker-compose.yml` reads from host environment via `${VAR_NAME}` syntax
---
*Integration audit: 2026-04-10*
+103
View File
@@ -0,0 +1,103 @@
# Technology Stack
**Analysis Date:** 2026-04-10
## Languages
**Primary:**
- TypeScript ~5.8.0 - Full application (strict mode enforced via `nuxt.config.ts`)
- HTML5 - Server-rendered markup via Nuxt SSR
**Secondary:**
- CSS - Styling via Tailwind CSS v4 (`app/assets/css/main.css`)
## Runtime
**Environment:**
- Node.js 22 (Alpine) - Development, build, and production server
**Package Manager:**
- pnpm (primary — `pnpm-lock.yaml` present)
- npm also supported (used in Dockerfile via `npm ci`)
- Lockfile: both `pnpm-lock.yaml` and `package-lock.json` present
## Frameworks
**Core:**
- Nuxt 4 (`^4.0.0`) - SSR framework, `compatibilityVersion: 4` set in `nuxt.config.ts`
- Vue (latest) - Component framework
- Vue Router (latest) - File-based routing via Nuxt
**Nuxt Modules:**
- `@nuxt/ui` ^3.0.0 - Component library (Tailwind v4 based, configured in `app.config.ts`)
- `@nuxtjs/i18n` ^10.2.4 - Internationalization with FR/EN support
- `@nuxtjs/sitemap` ^8.0.12 - Automatic sitemap generation
- `nuxt-gtag` ^4.1.0 - Google Analytics/Google Tag Manager integration
- `@nuxt/image` ^2.0.0 - Optimized image handling
- `@nuxt/eslint` ^1.15.2 - ESLint integration
**Build/Dev:**
- Tailwind CSS ^4.2.2 (devDependency — compiled at build time)
- ESLint (via `@nuxt/eslint`) - Config: `eslint.config.mjs`
- TypeScript ~5.8.0 - Compiler and type checking via `nuxt typecheck`
## Key Dependencies
**Critical:**
- `nuxt` ^4.0.0 - Core framework with SSR engine
- `@nuxt/ui` ^3.0.0 - Provides all UI primitives (buttons, forms, modals, etc.)
- `@nuxtjs/i18n` ^10.2.4 - Multilingual routing (`fr` default, `en` prefixed via `/en/`)
- `zod` ^4.3.6 - Schema validation (used in server API routes for contact form)
**Infrastructure:**
- `nodemailer` ^8.0.5 - SMTP email sending from server API (`app/api/contact.post.ts`)
- `@types/nodemailer` ^8.0.0 - Type definitions for nodemailer
## Configuration
**Environment:**
- No `.env` committed; secrets passed at runtime via Docker environment variables
- Key runtime config variables (defined in `nuxt.config.ts` `runtimeConfig`):
- `NUXT_SMTP_HOST` - SMTP server hostname
- `NUXT_SMTP_USER` - SMTP username
- `NUXT_SMTP_PASS` - SMTP password
- `NUXT_SMTP_TO` - Recipient email address
- `NUXT_PUBLIC_GTAG_ID` - Google Analytics tag ID
**Build:**
- `nuxt.config.ts` - Main Nuxt configuration (SSR enabled, modules, i18n, color mode, sitemap)
- `app.config.ts` - App-level UI config (primary color: `brand`)
- `tsconfig.json` + `tsconfig.app.json` + `tsconfig.node.json` - TypeScript project references
- `eslint.config.mjs` - ESLint flat config
**Key nuxt.config.ts settings:**
- `ssr: true` — SSR always enabled
- `colorMode.storage: 'cookie'` — SSR-safe theme persistence
- `i18n.detectBrowserLanguage.useCookie: true` — SSR-safe locale detection
- `typescript.strict: true` — Strict TypeScript mode
## Platform Requirements
**Development:**
- Node.js 22+
- pnpm (or npm)
**Production:**
- Docker with Node.js 22 Alpine image
- SSR server runs on port 3000 (`node .output/server/index.mjs`)
- Reverse proxy: Traefik (TLS termination, www redirect, routing)
## Scripts & Commands
```bash
pnpm dev # Start dev server with HMR
pnpm build # Build SSR bundle → .output/
pnpm generate # Static generation (SSG mode)
pnpm preview # Preview built output
pnpm lint # Run ESLint
pnpm typecheck # Run nuxt typecheck (vue-tsc)
```
---
*Stack analysis: 2026-04-10*
+95
View File
@@ -0,0 +1,95 @@
# Structure
**Analysis Date:** 2026-04-10
## Directory Layout
```
portfolio/
├── app/ # Nuxt 4 app directory (srcDir)
│ ├── app.vue # Root component (UApp wrapper)
│ ├── error.vue # Error/404 page
│ ├── assets/css/main.css # Global CSS (Tailwind imports)
│ ├── components/
│ │ ├── layout/
│ │ │ ├── AppHeader.vue # Sticky header with nav, locale/theme toggles
│ │ │ └── AppFooter.vue # Footer with social links, copyright
│ │ ├── sections/
│ │ │ ├── HeroSection.vue # Landing hero with CTA
│ │ │ ├── FeaturedProjectsSection.vue
│ │ │ ├── ServicesSection.vue
│ │ │ ├── TestimonialsSection.vue
│ │ │ ├── FAQSection.vue
│ │ │ └── CTASection.vue
│ │ ├── ContactForm.vue # Form with Zod validation + honeypot
│ │ ├── ProjectCard.vue # Project display card
│ │ ├── ProjectGallery.vue # Image gallery modal
│ │ └── TechBadge.vue # Technology badge with icon
│ ├── composables/
│ │ └── useProjects.ts # Project data access + i18n + filtering
│ ├── data/ # Static typed data
│ │ ├── projects.ts # 7 projects (Omit translatable fields)
│ │ ├── testimonials.ts # Client testimonials
│ │ ├── techstack.ts # Technology categories
│ │ ├── faq.ts # FAQ entries (i18n keys)
│ │ └── site.ts # Site config (SEO, contact, social)
│ ├── layouts/
│ │ └── default.vue # Main layout (header + slot + footer)
│ └── pages/ # File-based routing
│ ├── index.vue # Homepage
│ ├── about.vue # About page
│ ├── contact.vue # Contact form page
│ ├── projects.vue # Project listing with filters
│ ├── fiverr.vue # Fiverr services page
│ └── project/[id].vue # Dynamic project detail
├── i18n/locales/ # Translation files
│ ├── fr.json # French (default locale)
│ └── en.json # English
├── server/api/
│ └── contact.post.ts # Contact form POST handler (nodemailer)
├── shared/types/
│ └── index.ts # All TypeScript interfaces
├── public/images/ # Static images (WebP)
├── nuxt.config.ts # Nuxt configuration
├── app.config.ts # Nuxt UI theme tokens
├── Dockerfile # Multi-stage SSR build (node:22-alpine)
├── docker-compose.yml # Docker compose with Traefik
├── package.json # Dependencies (pnpm)
└── pnpm-lock.yaml # pnpm lockfile
```
## Page Inventory
| Route | File | Description |
|-------|------|-------------|
| `/` | `index.vue` | Homepage with 6 sections (hero, projects, services, testimonials, FAQ, CTA) |
| `/about` | `about.vue` | About page with tech stack badges |
| `/projects` | `projects.vue` | Project listing with search + category filters |
| `/project/:id` | `project/[id].vue` | Dynamic project detail with gallery |
| `/contact` | `contact.vue` | Contact form page |
| `/fiverr` | `fiverr.vue` | Fiverr services page |
| `/en/*` | (same files) | English prefix routes via i18n |
## Component Hierarchy
- **Layout components** (`layout/`): AppHeader, AppFooter — used in `default.vue` layout
- **Section components** (`sections/`): 6 homepage sections — composed in `index.vue`
- **Shared components** (root): ContactForm, ProjectCard, ProjectGallery, TechBadge — reused across pages
All components auto-imported with `pathPrefix: false` — use `AppHeader` not `LayoutAppHeader`.
## Where to Add Things
| To add... | Location |
|-----------|----------|
| New page | `app/pages/newpage.vue` (auto-routed) |
| New component | `app/components/` (auto-imported) |
| New section | `app/components/sections/` |
| New API route | `server/api/name.method.ts` |
| New data file | `app/data/name.ts` |
| New type | `shared/types/index.ts` |
| New i18n keys | `i18n/locales/fr.json` + `en.json` |
---
*Structure analysis: 2026-04-10*
+43
View File
@@ -0,0 +1,43 @@
# Testing Patterns
**Analysis Date:** 2026-04-10
## Test Framework
**Runner:** None detected
**Assertion Library:** None detected
No test runner or test framework is installed. `package.json` contains no testing dependencies (no Vitest, Jest, Playwright, Cypress, or any `@testing-library/*` package). No `test` script is defined in `package.json`.
**Run Commands:**
```bash
# No test commands available
pnpm run lint # ESLint only
pnpm run typecheck # Nuxt type checking (vue-tsc via nuxt typecheck)
```
## Test File Organization
No test files exist in the codebase. A search for `*.test.*` and `*.spec.*` across the entire project returned no results.
## What Currently Exists as Quality Gates
**TypeScript strict mode** (`nuxt.config.ts`):
- `typescript: { strict: true }` — all strict checks enforced at compile time
- `pnpm run typecheck` runs `nuxt typecheck` (wraps vue-tsc)
**ESLint** (`eslint.config.mjs`):
- `@nuxt/eslint` module with auto-generated type-aware rules
- `pnpm run lint` runs `eslint .`
**Runtime validation:**
- Client side: Zod schema in `app/components/ContactForm.vue` validates form input before API call
- Server side: Manual validation in `server/api/contact.post.ts` rejects malformed payloads with HTTP 400
## Test Coverage
**Current coverage: 0%** — no automated tests of any kind.
---
*Testing analysis: 2026-04-10*