Files
portfolio/.planning/codebase/CONVENTIONS.md
T
2026-04-07 22:47:51 +02:00

8.0 KiB

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 <script setup lang="ts"> components)
  • Generic types from Vue use their original names (e.g., Ref<boolean>, Computed<string>)

Code Style

Formatting:

  • Tool: Prettier 3.5.3
  • Semi-colons: disabled (semi: false)
  • Quotes: single quotes (singleQuote: true)
  • Print width: 100 characters (printWidth: 100)

Linting:

  • Tool: ESLint 9.22.0 with Vue support
  • Config: eslint.config.ts using flat config format
  • Plugins:
    • @vue/eslint-config-typescript - TypeScript support
    • eslint-plugin-vue v10.0.0 - Vue 3 rules
    • @vue/eslint-config-prettier/skip-formatting - Prettier integration (skip-formatting enabled)

Import Organization

Order:

  1. Vue and framework imports (vue, vue-router, pinia, vue-i18n)
  2. Type imports (use import type for TypeScript types)
  3. Local components (@/components/...)
  4. Composables (@/composables/...)
  5. Utilities and helpers
  6. Data and configuration files
  7. Styles (scoped CSS imported at end of <style>)

Path Aliases:

  • @/ maps to ./src/ (configured in tsconfig.app.json)
  • Always use @/ prefix for imports from src directory
  • Examples:
    • import AppHeader from '@/components/layout/AppHeader.vue'
    • import { useTheme } from '@/composables/useTheme'
    • import type { Project } from '@/types'
    • import { techStack } from '@/data/techstack'

Error Handling

Patterns:

  • Try-catch blocks wrap risky operations (e.g., dynamic imports, DOM manipulation)
  • Fallback values provided when operations fail:
    • In useAssets(): returns placeholder image URL if asset fails to load
    • In useSeo(): gracefully handles missing meta elements by creating them
  • Console warnings for non-critical failures:
    • console.warn('message') for warnings during execution
    • Error objects logged with context: console.warn('Failed to load image: ${path}', error)
  • Silent failures with fallbacks preferred over throwing errors for UI operations

Examples from codebase:

// In useAssets.ts - graceful fallback
if (!path || path.trim() === '') {
  console.warn('getImageUrl called with empty or undefined path')
  return `https://via.placeholder.com/400x300/f3f4f6/9ca3af?text=${encodeURIComponent('No image')}`
}

// In useSeo.ts - create if missing
let meta = document.querySelector(`meta[${property ? 'property' : 'name'}="${name}"]`)
if (!meta) {
  meta = document.createElement('meta')
  // ... setup ...
  document.head.appendChild(meta)
}

Logging

Framework: console object (no dedicated logging library)

Patterns:

  • console.warn() for warnings (missing assets, invalid input)
  • Logging only in composables for utility functions
  • No console.log() in production code (only development/debugging)
  • Error context included: console.warn('context', error)

Comments

When to Comment:

  • JSDoc comments for composable functions (exported functions)
  • Inline comments for non-obvious logic (especially SEO handling in router)
  • Comments explaining why (not what the code does)
  • TODO comments for known issues: // TODO: page 404 in src/router/index.ts

JSDoc/TSDoc:

  • Composables include JSDoc for exported functions
  • Example from useAssets.ts:
    /**
     * Get image URL from assets folder
     * @param path - Path like '@/assets/images/filename.webp' or 'filename.webp'
     * @returns string - The image URL
     */
    const getImageUrl = (path: string | undefined): string => { ... }
    
  • Not consistently applied across all files; use when function signature isn't obvious

Function Design

Size: Composables are typically 50-100 lines; keep focused on single responsibility

Parameters:

  • Props interfaces always named Props in components
  • Use destructuring in setup: const { t } = useI18n()
  • Optional config objects in composables (e.g., SeoOptions with defaults)
  • Explicit typing on all parameters

Return Values:

  • Composables return object with all exposed functions and reactive state
  • Always return computed versions of reactive state when exposing refs:
    return {
      isOpen,           // reactive ref
      currentImage,     // computed from ref
      openGallery,      // function
      closeGallery      // function
    }
    
  • Functions return early on validation failures with fallbacks

Module Design

Exports:

  • Composables export single named function: export function useTheme() { ... }
  • Config files export named constants: export const siteConfig: SiteConfig = { ... }
  • Type definitions export interfaces and types: export interface Project { ... }
  • Data files export arrays or objects: export const techStack: TechStack = { ... }

Barrel Files:

  • Not extensively used; direct imports preferred
  • Only src/types/index.ts serves as barrel export for type definitions
  • Components use direct imports: import AppHeader from '@/components/layout/AppHeader.vue'

Component Structure (Vue SFC):

  • <script setup lang="ts"> for all components (Vue 3 Composition API)
  • Props validated with TypeScript interfaces
  • Composables called at top of setup
  • Computed properties for derived state
  • Functions defined after setup calls
  • <template> uses semantic HTML and accessibility attributes
  • Scoped styles at bottom with @import for external stylesheets

Example pattern from AppHeader.vue:

<script setup lang="ts">
import { ref, computed } from 'vue'
// Composables first
const { getImageUrl } = useAssets()
const { t } = useI18n()
// State
const isMenuOpen = ref(false)
// Computed
const navigation = computed(() => [ ... ])
// Functions
const toggleMenu = () => { ... }
</script>

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:

  • <script setup> syntax exclusively used
  • No Options API in codebase
  • Composables follow Composition API patterns

Lifecycle Hooks:

  • onMounted() for initialization (theme loading, SEO setup)
  • onUnmounted() for cleanup (removing DOM elements in useSeo)
  • watch() for reactive side effects (theme changes)

Reactivity:

  • ref() for primitive state
  • computed() for derived state
  • Avoid unnecessary reactivity; use constants when possible
  • Return computed versions of refs from composables

Convention analysis: 2026-04-07