diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md deleted file mode 100644 index fa6916e..0000000 --- a/.planning/codebase/ARCHITECTURE.md +++ /dev/null @@ -1,197 +0,0 @@ -# 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 deleted file mode 100644 index a4dbf87..0000000 --- a/.planning/codebase/CONCERNS.md +++ /dev/null @@ -1,212 +0,0 @@ -# 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 deleted file mode 100644 index 4782507..0000000 --- a/.planning/codebase/CONVENTIONS.md +++ /dev/null @@ -1,225 +0,0 @@ -# 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:** -- `