diff --git a/.gitignore b/.gitignore index 8ee54e8..799cd78 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ coverage *.sw? *.tsbuildinfo +.claude \ No newline at end of file diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md new file mode 100644 index 0000000..fa6916e --- /dev/null +++ b/.planning/codebase/ARCHITECTURE.md @@ -0,0 +1,197 @@ +# Architecture + +**Analysis Date:** 2026-04-07 + +## Pattern Overview + +**Overall:** Vue 3 SPA (Single Page Application) with component-based architecture and SSR-friendly design patterns + +**Key Characteristics:** +- Client-side routing with lazy-loaded views for performance optimization +- Composition API-based composables for shared logic and state management +- Global state managed via Pinia stores +- Multi-language support with vue-i18n +- Theme switching with localStorage persistence +- SEO-optimized with dynamic meta tags and structured data +- Google Analytics and GTM integration for tracking + +## Layers + +**Presentation Layer (Components):** +- Purpose: Render UI and handle user interactions +- Location: `src/components/` +- Contains: Vue Single File Components organized by domain (layout, sections, shared, testimonials, icons) +- Depends on: Composables for data access and side effects, Router for navigation +- Used by: Views and other components + +**View Layer (Pages):** +- Purpose: Page-level component assembly and routing targets +- Location: `src/views/` +- Contains: Full page components (HomePage, ProjectsPage, ContactPage, AboutPage, FiverrPage, FormationPage, ProjectDetailPage) +- Depends on: Composables (useSeo, useI18n, useProjects), components, data stores +- Used by: Router for navigation + +**Business Logic Layer (Composables):** +- Purpose: Encapsulate reusable logic, data fetching, and side effects +- Location: `src/composables/` +- Contains: Vue composables for projects, SEO, i18n, themes, galleries, date formatting, assets, site config +- Depends on: Types, stores, external libraries (vue-router, vue-i18n) +- Used by: Components and views + +**Data/State Layer (Stores & Data):** +- Purpose: Global state and static data management +- Location: `src/stores/`, `src/data/` +- Contains: Pinia stores, static project data, testimonials, tech stack, FAQs +- Depends on: Types, composables (useI18n for localized data) +- Used by: Composables and components + +**Configuration Layer:** +- Purpose: Application-wide settings and configuration +- Location: `src/config/`, `src/router/`, `src/i18n/` +- Contains: Site configuration, router setup, i18n initialization, locale messages +- Depends on: Types, data +- Used by: Main entry point and throughout app + +**Type Definitions:** +- Purpose: TypeScript interfaces and types +- Location: `src/types/index.ts` +- Contains: Project, Technology, TechStack, SocialLink, ContactInfo, FiverrService, SiteConfig interfaces + +## Data Flow + +**1. Initial Page Load:** +1. `index.html` loads with embedded Google Analytics and Google AdSense scripts +2. `src/main.ts` initializes Vue app, Pinia store, router, and i18n +3. `src/App.vue` applies theme from localStorage and renders AppHeader + RouterView + AppFooter +4. Router initializes and loads HomePage or requested route +5. `useTheme()` applies saved theme class to document +6. `useI18n()` loads saved locale from localStorage + +**2. Route Navigation:** +1. User clicks link or navigates directly +2. Router's `beforeEach` hook updates document title and meta description from route.meta +3. Router's `afterEach` hook triggers scroll to top and Google Analytics page view tracking +4. Target view component mounts and runs `useSeo()` for SEO metadata +5. View renders child components with fetched data +6. Components subscribe to composables for reactive data + +**3. Data Access Pattern (Example: Projects):** +1. Component imports `useProjects()` composable +2. Composable accesses base project data from `src/data/` or static store +3. Composable uses `useI18n()` to localize strings +4. Component receives computed reactive `projects` array +5. Component renders with v-for or passes data to child components + +**4. Theme Switching:** +1. `ThemeToggle` component toggles `isDark` ref in `useTheme()` +2. Watch handler applies class to document.documentElement +3. Watch handler saves to localStorage +4. Browser CSS respects `dark` class on document + +**5. Language Switching:** +1. `LanguageSwitcher` component calls `switchLocale()` from `useI18n()` +2. vue-i18n locale updates and all `{{ t() }}` expressions re-evaluate +3. New locale saved to localStorage +4. Computed properties like `homeFAQs` in HomePage.vue re-evaluate with new translations + +**State Management:** +- **Global:** Pinia stores (currently minimal - `useCounterStore` exists but unused) +- **Composable State:** Reactive refs in composables (theme, locale, gallery state) +- **Component State:** Local reactive refs for UI state (menu toggle, form inputs) +- **Persistence:** localStorage for theme and locale preferences +- **Server-Side Data:** Static JSON-like data in `src/data/` files, not fetched from API + +## Key Abstractions + +**useI18n() Composable:** +- Purpose: Unified i18n access with convenience methods +- Examples: `src/composables/useI18n.ts` +- Pattern: Wraps vue-i18n's `useI18n()`, adds locale switching and computed locale state +- Usage: Available in all components via injection + +**useSeo() Composable:** +- Purpose: Dynamic SEO tag management for SPA +- Examples: `src/composables/useSeo.ts` +- Pattern: Lifecycle hooks to create/remove meta tags on mount/unmount, prevents tag duplication +- Usage: Called in view components with options object for title, description, OG tags, structured data + +**useProjects() Composable:** +- Purpose: Project data access with localization +- Examples: `src/composables/useProjects.ts` +- Pattern: Base data stored separately, computed properties merge translations on read +- Usage: Returns computed `projects` array that updates when language changes + +**useTheme() Composable:** +- Purpose: Centralized theme state and persistence +- Examples: `src/composables/useTheme.ts` +- Pattern: Reactive boolean with computed getter, watch for persistence, DOM manipulation +- Usage: Injected globally in App.vue, consumed by ThemeToggle component + +**TechStack Interface:** +- Purpose: Typed structure for technology categories +- Examples: `src/types/index.ts` +- Pattern: Categorized array structure (programming, front, database, devtools, operating_systems, socials) +- Usage: Imported in `src/data/techstack.ts` and AboutPage.vue + +**SiteConfig:** +- Purpose: Single source of truth for site-wide settings +- Examples: `src/config/site.ts` +- Pattern: Exported constant object with typed structure, includes contact info, social links, SEO config +- Usage: Imported where needed for links, contact info, performance settings + +## Entry Points + +**HTML Entry Point:** +- Location: `index.html` +- Triggers: Browser page load +- Responsibilities: Define DOM root (`#app`), load analytics/ads scripts, include meta tags, defer main.ts loading + +**Application Entry Point:** +- Location: `src/main.ts` +- Triggers: After HTML DOM ready +- Responsibilities: Create Vue app, install plugins (Pinia, Router, i18n), mount to #app + +**Router Entry Point:** +- Location: `src/router/index.ts` +- Triggers: App.use(router) in main.ts +- Responsibilities: Define route table, implement beforeEach/afterEach hooks for SEO and analytics + +**Root Component:** +- Location: `src/App.vue` +- Triggers: After Vue app mounts +- Responsibilities: Initialize theme, render layout structure (header + router-view + footer), handle route-change scroll behavior + +**View Components:** +- Location: `src/views/*.vue` +- Triggers: Router navigation to matching path +- Responsibilities: Page-specific SEO setup via `useSeo()`, compose sections and content, manage page-level state + +## Error Handling + +**Strategy:** Graceful degradation with fallback content; no explicit error boundaries detected + +**Patterns:** +- Lazy-loaded routes with no 404 component (TODO comment in router) - currently redirects to HomePage +- SEO composable safely creates/finds meta elements before updating +- Theme fallback to 'dark' if localStorage empty +- Locale fallback to 'en' if not in localStorage +- Gallery modal (GalleryModal.vue) handles missing images gracefully +- Contact form likely has validation but not visible in read scope + +## Cross-Cutting Concerns + +**Logging:** Console-based or via external services - no custom logger detected; development uses Vue DevTools plugin + +**Validation:** Form validation in components (ContactPage uses validation likely); no centralized validation layer + +**Authentication:** No built-in auth system - portfolio is public facing; new auth stores/views added (LoginView, RegisterView, VerifyEmailView, DashboardView, ForgotPasswordView, guards.ts) but not integrated into main router + +**SEO:** Centralized via `useSeo()` composable and router hooks; dynamic meta tags, Open Graph, Twitter cards, structured data; Google Analytics via gtag in router afterEach; Umami analytics via deferred script tag in index.html + +**Performance:** Code splitting via lazy routes, vendor chunk separation in vite.config.ts, CSS code splitting enabled, Terser minification, webp image support configured, lazy image loading configurable in siteConfig + +**Accessibility:** ARIA labels on interactive elements (AppHeader navigation, buttons); semantic HTML (header, nav, main, section roles); focus styles defined in App.vue + +--- + +*Architecture analysis: 2026-04-07* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md new file mode 100644 index 0000000..a4dbf87 --- /dev/null +++ b/.planning/codebase/CONCERNS.md @@ -0,0 +1,212 @@ +# Codebase Concerns + +**Analysis Date:** 2026-04-07 + +## Tech Debt + +**Missing 404 Page Implementation:** +- Issue: 404 catch-all route currently redirects to HomePage instead of a dedicated 404 page +- Files: `src/router/index.ts` (line 51: TODO comment) +- Impact: Users encountering invalid routes see the homepage instead of a proper error page, creating confusion and poor UX. SEO also treats all invalid URLs as the homepage. +- Fix approach: Create a new `NotFoundPage.vue` component in `src/views/` with proper 404 messaging, then update the route in `src/router/index.ts` to point to this component. + +**Hardcoded GA Tracking ID:** +- Issue: Google Analytics tracking ID `G-CDVVNFY6MV` is hardcoded in multiple places +- Files: `index.html` (line 9), `src/router/index.ts` (line 109) +- Impact: Cannot change analytics without code updates. Difficult to manage different GA IDs for development vs production environments. +- Fix approach: Move tracking ID to environment variables (`.env`), access via `import.meta.env.VITE_GA_ID`. + +**Hardcoded Site URL and Analytics:** +- Issue: URLs and site identifiers hardcoded throughout the codebase +- Files: `src/composables/useSeo.ts` (lines 103, 113, 143, 149), `src/config/site.ts` (line 69), `src/router/index.ts` (line 109) +- Impact: Difficult to deploy to different environments (staging vs production). Requires code changes for different domains. +- Fix approach: Move to environment configuration. Create environment-based config: `VITE_SITE_URL`, `VITE_SITE_DOMAIN`, etc. + +**External Image Asset Dependency on Placeholder Service:** +- Issue: Missing images fallback to external placeholder.com service without reliability guarantee +- Files: `src/composables/useAssets.ts` (lines 18, 42) +- Impact: If placeholder.com goes down or is rate-limited, missing images break visually. External dependency increases load time. +- Fix approach: Use local SVG or base64-encoded placeholder image instead of external URL. + +## Known Bugs + +**Scroll Position Duplication:** +- Symptoms: Multiple scroll handlers (in router and App.vue) could cause double-scrolling or jank +- Files: `src/App.vue` (lines 14-18), `src/router/index.ts` (lines 62-82, 118-125) +- Trigger: Navigation between routes +- Workaround: Currently works, but behavior is fragile due to redundancy + +**FormationPage Billing Toggle State Issue:** +- Symptoms: Billing toggle (monthly/annual) uses `isAnnual` state but HTML shows `billingType` variable +- Files: `src/views/FormationPage.vue` (lines 12-20, 41) +- Trigger: When switching between monthly and annual billing +- Workaround: Component probably has missing reactive computed for `billingType` + +## Security Considerations + +**v-html Usage in FAQ Component:** +- Risk: XSS vulnerability if FAQ answers contain user-generated content or external data +- Files: `src/components/ServiceFAQ.vue` (line 22: `

`) +- Current mitigation: FAQ content is hardcoded in component props (trusted source), but pattern is risky if content source changes +- Recommendations: Replace with v-text or plain text content. If HTML is needed, use a sanitization library like `DOMPurify`. + +**Personal Information Exposed in Configuration:** +- Risk: Email, phone number, and social profile IDs hardcoded in public config +- Files: `src/config/site.ts` (lines 71-100), `index.html` (lines 88, 239-240) +- Current mitigation: This is intentional (portfolio/contact site), but increases spam/scraping risk +- Recommendations: Consider using form submission instead of direct email/phone links on sensitive pages. Monitor contact form for abuse. + +**Hardcoded Analytics and AdSense IDs:** +- Risk: Analytics and AdSense IDs in HTML source reveal account information +- Files: `index.html` (lines 9, 19) +- Current mitigation: None - IDs are public by design (Google Analytics is meant to be public) +- Recommendations: Ensure AdSense account is properly secured with password/2FA. Monitor for unauthorized modifications. + +**Discord User ID Exposed:** +- Risk: Discord user ID `370940770225618954` is hardcoded and publicly visible +- Files: `src/config/site.ts` (line 92) +- Current mitigation: Discord doesn't allow impersonation via ID alone, but enables targeted attacks +- Recommendations: Keep Discord contact via link only, without exposing raw user ID. Use Discord username instead. + +## Performance Bottlenecks + +**Eager Image Loading with import.meta.glob:** +- Problem: All images under `src/assets/images/**` are eagerly loaded into memory at startup +- Files: `src/composables/useAssets.ts` (line 6: `eager: true`) +- Cause: `eager: true` loads all matching modules immediately instead of lazy-loading on-demand +- Improvement path: Change to lazy loading and implement on-demand imports, or at minimum separate critical images from lazy ones. + +**External Script Dependencies Block Rendering:** +- Problem: Google Analytics, Google AdSense, and Umami scripts are loaded synchronously +- Files: `index.html` (lines 9, 19, 239-240) +- Cause: Multiple external scripts without proper `async` loading strategy +- Improvement path: Ensure all external scripts use `async` or `defer` attributes. Currently Umami and GTM use `defer` (good), but ensure they don't block critical rendering path. + +**No Lazy Loading on Images:** +- Problem: No lazy-loading attributes on images, causing initial page load to fetch all images +- Files: Multiple components (`src/components/layout/AppFooter.vue` line 35, `src/components/layout/AppHeader.vue` line 33) +- Cause: `loading="eager"` and `loading="lazy"` not used strategically +- Improvement path: Set `loading="lazy"` on all below-fold images. Keep only above-fold images with `loading="eager"`. + +## Fragile Areas + +**GalleryModal Event Listener Management:** +- Files: `src/components/GalleryModal.vue` (lines 44-50) +- Why fragile: Global document keydown listener added/removed on mount/unmount. If component unmounts unexpectedly, listener could remain attached or fail to attach. +- Safe modification: Add try-catch around removeEventListener. Consider using a ref-based approach or delegated events. +- Test coverage: No test coverage visible for keyboard navigation. Recommend adding unit tests for keyboard shortcuts. + +**Image URL Resolution Fallback Chain:** +- Files: `src/composables/useAssets.ts` (lines 13-44) +- Why fragile: Multiple fallback layers (module lookup → URL construction → placeholder) make debugging hard. If one fallback path fails, error is silently logged. +- Safe modification: Add explicit logging for each fallback step. Document expected path formats clearly. +- Test coverage: Recommend unit tests for edge cases (empty path, missing extensions, malformed paths). + +**SEO Composable DOM Manipulation:** +- Files: `src/composables/useSeo.ts` (lines 35-70) +- Why fragile: Directly manipulates DOM with `document.querySelector`, `createElement`, `appendChild`. No error handling if DOM structure changes. +- Safe modification: Use Vue's ref system or a dedicated SEO library (e.g., `@unhead/vue`). Add error boundaries. +- Test coverage: No test coverage for SSR compatibility or DOM cleanup on route changes. + +**Route-based SEO Title Dependency:** +- Files: `src/composables/useSeo.ts` (lines 75-76) +- Why fragile: Relies on route being available in useRoute() hook. If used in wrong context (non-routed component), will fail silently. +- Safe modification: Add null checks. Consider creating a composable specifically for page-level SEO that enforces route dependency. +- Test coverage: Recommend tests for components used in and outside routing context. + +## Scaling Limits + +**Single-Page History State:** +- Current capacity: Router handles typical portfolio page count (6-8 pages) +- Limit: If projects list grows beyond 50-100 items, ProjectsPage.vue performance degrades (all projects loaded at once) +- Scaling path: Implement pagination or virtual scrolling in ProjectsPage. Use server-side filtering if projects become dynamic. + +**Inline Image Modules:** +- Current capacity: ~15-20 images loaded eagerly in memory +- Limit: If image count exceeds 100+, startup time and memory usage increase significantly +- Scaling path: Migrate to lazy-loading strategy. Consider CDN for image serving in production. + +**Localization Key Lookups:** +- Current capacity: 500+ localization keys across en.ts and fr.ts +- Limit: If keys exceed 1000+, lookup performance and bundle size become concerns +- Scaling path: Implement lazy-loaded locale files (load only active language). Consider JSON-based locale format for better optimization. + +## Dependencies at Risk + +**No Testing Framework:** +- Risk: Zero test coverage visible in codebase. Refactoring breaks are undetectable. +- Impact: Security fixes, performance optimizations, and feature additions are risky. +- Migration plan: Add Jest + Vue Test Utils. Start with critical paths (router guards, composables, SEO). + +**Hardcoded Translation Keys:** +- Risk: If translation key structure changes, UI silently breaks (missing translations show key names) +- Impact: Refactoring translations is error-prone and breaks are not caught in CI +- Migration plan: Add TypeScript strict typing for i18n keys using type-safe i18n library. + +**External Analytics Dependency:** +- Risk: If Google Analytics changes API or service, tracking breaks +- Impact: Loss of analytics data, no visibility into user behavior +- Migration plan: Already using Umami (self-hosted alternative). Consider making analytics provider pluggable. + +## Missing Critical Features + +**No Error Boundary:** +- Problem: No Vue error boundary or fallback component for runtime errors +- Blocks: Cannot gracefully handle component errors or show error UI +- Fix approach: Create an error boundary component using Vue 3 error handling hooks (errorCaptured), wrap main app with it. + +**No Offline Support:** +- Problem: No service worker or offline fallback +- Blocks: Portfolio becomes completely unavailable if user loses connection +- Fix approach: Implement service worker with offline fallback. Cache critical assets (HTML, CSS, JS). + +**No Loading States:** +- Problem: No skeleton loaders or loading indicators for async operations +- Blocks: Users don't know if page is loading or broken. Especially impacts image loading. +- Fix approach: Add skeleton screens for ProjectDetailPage. Add loading indicators for gallery modal. + +**No Proper 404 Page:** +- Problem: 404 redirects to homepage (mentioned in Tech Debt) +- Blocks: Users cannot identify when they've hit an invalid URL +- Fix approach: Create NotFoundPage.vue with suggestions for navigation. + +**No Analytics Event Tracking:** +- Problem: Only page views tracked, no event analytics (clicks, form submissions, etc.) +- Blocks: Cannot understand user behavior beyond page traffic +- Fix approach: Add event tracking for CTA clicks, social link clicks, gallery interactions. + +## Test Coverage Gaps + +**No Unit Tests:** +- What's not tested: Composables (useSeo, useAssets, useProjects, useI18n), utility functions +- Files: All of `src/composables/` and `src/data/` +- Risk: Refactoring introduces subtle bugs. Type safety is partial (TypeScript, but no runtime checks). +- Priority: High - composables are core to the app and impact SEO/styling + +**No Component Tests:** +- What's not tested: Interactive components (GalleryModal, ServiceFAQ, language switcher, theme toggle) +- Files: `src/components/GalleryModal.vue`, `src/components/ServiceFAQ.vue`, `src/components/ThemeToggle.vue`, `src/components/LanguageSwitcher.vue` +- Risk: UI behavior breaks silently. Accessibility features (keyboard nav, ARIA) may regress. +- Priority: High - GalleryModal keyboard navigation and language switching are user-facing + +**No Integration Tests:** +- What's not tested: Router navigation, SEO meta tag updates, i18n language switching across pages +- Files: `src/router/index.ts`, cross-file composable interactions +- Risk: Multi-step user flows break. SEO meta tags may not update correctly on navigation. +- Priority: Medium - can catch cross-cutting issues + +**No E2E Tests:** +- What's not tested: Full user journeys (landing → project detail → gallery → contact) +- Framework: None (not using Cypress, Playwright, etc.) +- Risk: Visual regressions, layout issues, navigation bugs in real browser contexts +- Priority: Medium - would catch integration issues and performance regressions + +**No Accessibility Tests:** +- What's not tested: Keyboard navigation, screen reader compatibility, color contrast, focus management +- Files: All components with interactive elements +- Risk: Accessibility fails silently. Users with disabilities cannot navigate. +- Priority: High - portfolio should be accessible to all users + +--- + +*Concerns audit: 2026-04-07* diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md new file mode 100644 index 0000000..4782507 --- /dev/null +++ b/.planning/codebase/CONVENTIONS.md @@ -0,0 +1,225 @@ +# Coding Conventions + +**Analysis Date:** 2026-04-07 + +## Naming Patterns + +**Files:** +- Vue components: PascalCase (e.g., `AppHeader.vue`, `ProjectCard.vue`) +- Composables: camelCase with `use` prefix (e.g., `useTheme.ts`, `useProjects.ts`) +- Utility/config files: camelCase (e.g., `site.ts`, `techstack.ts`) +- Data files: camelCase (e.g., `testimonials.ts`, `faq.ts`) +- Type definitions: camelCase in `types/index.ts` + +**Functions:** +- All functions use camelCase (e.g., `toggleTheme`, `openGallery`, `getImageUrl`) +- Composables are named with `use` prefix: `useTheme()`, `useGallery()`, `useSeo()` +- Getter functions use `get` prefix: `getTheme()`, `getImageUrl()` +- Boolean functions/computed use `is`/`has` prefix: `isDark`, `hasNext`, `isOpen` +- Handler functions use verb + `Handler`: `toggleTheme`, `openGallery`, `closeGallery` + +**Variables:** +- Refs and computed properties: camelCase (e.g., `isDark`, `currentIndex`, `isOpen`) +- Interfaces and types: PascalCase (e.g., `Props`, `SeoOptions`, `Theme`) +- Constants: UPPER_SNAKE_CASE for config constants (not extensively used in codebase) +- Private/module state: camelCase prefixed with `_` if truly private + +**Types:** +- Type aliases: PascalCase (e.g., `type Theme = 'light' | 'dark'`) +- Interface names: PascalCase (e.g., `interface Props`, `interface SeoOptions`) +- Props interfaces: Always named `Props` (e.g., in ` +``` + +## Type Safety + +**TypeScript Configuration:** +- Version: ~5.8.0 +- DOM-focused (`tsconfig.dom.json` from @vue/tsconfig) +- Path alias `@/*` points to `./src/*` +- Type checking enabled in build: `npm run type-check` runs `vue-tsc --build` + +**Type Usage Patterns:** +- All component Props use interface definitions +- Composable return values typed explicitly +- Function parameters and return types annotated +- Type imports use `import type` syntax +- Avoid `any` type; use proper interfaces/generics + +## Vue 3 Specific + +**Composition API:** +- `