commit 42852a77e18926d8b2ad4886d08a8ad57d98990b Author: Ilia Mashkov Date: Fri Dec 26 12:41:43 2025 +0300 chore: initialize git repository with workflow documentation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a7fcd1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Dependencies +node_modules/ +.pnp/ +.pnp.js + +# Build outputs +.svelte-kit/ +build/ +dist/ +.nuxt/ +.output/ +.vercel/ +.netlify/ + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE and Editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db +*.sublime-project +*.sublime-workspace + +# Testing +coverage/ +.nyc_output/ +*.lcov + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# OS files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Temporary files +*.tmp +*.temp +.cache/ +.parcel-cache/ + +# TypeScript +*.tsbuildinfo + +# Misc +*.pid +*.seed +*.pid.lock diff --git a/GLYPHDIFF_PROJECT_PLAN.md b/GLYPHDIFF_PROJECT_PLAN.md new file mode 100644 index 0000000..932baeb --- /dev/null +++ b/GLYPHDIFF_PROJECT_PLAN.md @@ -0,0 +1,2997 @@ +# GlyphDiff.com - Font Comparison Project Plan + +> **Status:** Learning-Focused MVP | **Tech Stack:** SvelteKit + TypeScript + Tailwind CSS | **Transition Path:** React → Svelte + +--- + +## Table of Contents + +1. [Project Overview](#project-overview) +2. [Executive Summary](#executive-summary) +3. [Architecture Decisions](#architecture-decisions) +4. [Font Provider Integration Strategy](#font-provider-integration-strategy) +5. [Performance Optimization](#performance-optimization) +6. [Frontend Architecture](#frontend-architecture) +7. [Data Models](#data-models) +8. [Implementation Roadmap](#implementation-roadmap) +9. [TanStack Query Research](#tanstack-query-research) +10. [Learning Guide: React → Svelte](#learning-guide-react--svelte) +11. [Scalability & Commercialization](#scalability--commercialization) +12. [Deployment](#deployment) +13. [Risk Assessment](#risk-assessment) +14. [Success Metrics](#success-metrics) +15. [Appendix](#appendix) + +--- + +## Project Overview + +### Vision + +Create a modern, fast, and intuitive web application for comparing fonts side-by-side. The tool will help designers, developers, and typographers make informed decisions about font choices by providing real-time visual comparisons, filtering, and customization options. + +### Target Audience + +- **UI/UX Designers:** Selecting typefaces for web and mobile applications +- **Web Developers:** Choosing fonts for implementation in projects +- **Graphic Designers:** Finding complementary fonts for branding +- **Typography Enthusiasts:** Exploring and comparing font characteristics + +### Core Features (MVP) + +1. **Font Catalog:** Browse available fonts from multiple providers +2. **Side-by-Side Comparison:** Compare up to 4 fonts simultaneously +3. **Custom Preview Text:** Enter custom text for realistic comparison +4. **Filter & Search:** Filter by category, popularity, weight, width +5. **Responsive Design:** Seamless experience across all devices +6. **Dark Mode:** Built-in theme toggle for comfortable viewing +7. **URL Sharing:** Share comparison sessions via shareable URLs + +### Tech Stack + +| Layer | Technology | Purpose | +|-------|-----------|---------| +| Framework | SvelteKit 2.x | Full-stack framework with SSR/SSG support | +| UI Language | Svelte 5 | Reactive component framework | +| Styling | Tailwind CSS 4.x | Utility-first CSS framework | +| Component Library | Bits UI | Accessible Svelte components (Radix UI port) | +| Type Safety | TypeScript 5.x | Static type checking | +| Hosting | Vercel | Edge deployment & preview environments | + +### Project Goals + +#### Learning Goals (Primary) +- Gain proficiency in Svelte 5 and its reactive primitives (`$state`, `$derived`, `$effect`) +- Understand SvelteKit's routing and data loading architecture +- Learn client-side state management patterns in Svelte ecosystem +- Explore performance optimization techniques for static web apps + +#### Project Goals (Secondary) +- Build a functional, visually appealing font comparison tool +- Create a performant, accessible user interface +- Establish a foundation for future commercialization +- Demonstrate mastery of modern frontend development practices + +--- + +## Executive Summary + +### Recommended Approach: Pure Client-Side Architecture + +**Recommendation:** Build the MVP as a pure client-side application using SvelteKit's static site generation (SSG) capabilities, with no backend infrastructure. + +### Key Rationale + +1. **Learning Focus:** Minimize backend complexity to concentrate on Svelte/SvelteKit mastery +2. **Data Availability:** Font metadata is publicly available via Google Fonts API and Fontshare CDN +3. **Performance:** Static generation provides instant page loads and optimal Core Web Vitals +4. **Cost Efficiency:** No server costs during MVP phase +5. **Future Flexibility:** Can easily add backend later when user data collection becomes necessary + +### Architecture at a Glance + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Browser │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ SvelteKit Client Application │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ Font Store │ │ Filter Store│ │ Comp Store │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ ┌─────────────────────────────────────────────────┐ │ │ +│ │ │ Font Provider Services │ │ │ +│ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ +│ │ │ │ Google Fonts │ │ Fontshare │ │ │ │ +│ │ │ │ API │ │ CDN │ │ │ │ +│ │ │ └──────────────┘ └──────────────┘ │ │ │ +│ │ └─────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ │ + │ HTTP Requests │ CSS Font Files + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│ Google Fonts API │ │ Fontshare CDN │ +└──────────────────┘ └──────────────────┘ +``` + +--- + +## Architecture Decisions + +### Backend Necessity Analysis + +#### Option A: Pure Client-Side (Recommended for MVP) + +**Pros:** +- **Simplicity:** No server code, database, or API endpoints to maintain +- **Performance:** Static sites load faster and score better on Core Web Vitals +- **Cost:** Zero hosting costs on Vercel free tier +- **Learning Focus:** Maximum time spent on Svelte/SvelteKit fundamentals +- **Deployment:** Instant deployments with zero configuration +- **Scalability:** Vercel Edge handles CDN distribution automatically + +**Cons:** +- **No User Data:** Cannot save user preferences or comparison history +- **No Analytics:** Limited to client-side analytics (e.g., Google Analytics) +- **Rate Limiting:** Google Fonts API has usage limits (1,000 requests/day) +- **Caching:** Cannot implement server-side caching strategies +- **Data Freshness:** Font metadata fetched on each session (mitigated by browser caching) + +**Use Cases This Supports:** +- ✅ Browse and compare fonts +- ✅ Share comparisons via URL +- ✅ Filter and search fonts +- ✅ Save preferences locally (localStorage) +- ✅ Responsive, dark mode UI + +**Use Cases This Does NOT Support:** +- ❌ User accounts and saved comparisons +- ❌ Custom font uploads +- ❌ Collaborative comparisons +- ❌ Advanced analytics + +--- + +#### Option B: Hybrid with Serverless Backend + +**Pros:** +- **Caching Layer:** Server-side caching reduces API calls and improves load times +- **Rate Limit Management:** Proxy requests to avoid Google Fonts API limits +- **User Data:** Save user preferences and comparison history +- **Custom Endpoints:** Create specialized API endpoints for font data +- **Preprocessing:** Transform and normalize font data before sending to client + +**Cons:** +- **Complexity:** Additional infrastructure to build and maintain +- **Cost:** Vercel Pro plan may be required for serverless functions +- **Latency:** Server round-trip adds latency to initial page load +- **Development Overhead:** Testing backend code requires additional considerations +- **Learning Curve:** Takes time away from Svelte learning goals + +**Recommended Backend Stack (if needed later):** +- **API Routes:** SvelteKit server endpoints (`+page.server.ts`, `+server.ts`) +- **Database:** Vercel Postgres (Neon) or SQLite (better-sqlite3) +- **Caching:** Vercel KV (Redis) or in-memory caching +- **Auth:** Lucia Auth or Clerk for user authentication + +--- + +### Recommendation + +**Build Option A (Pure Client-Side) for MVP with these considerations:** + +1. **Design for future backend:** Structure data fetching and state management to be easily adapted to server-side later +2. **Implement smart caching:** Use browser localStorage for font metadata cache with TTL +3. **Optimize API usage:** Batch requests and use Google Fonts API efficiently +4. **Monitor usage:** Track API usage and user engagement to inform backend decision +5. **Migration path:** Keep server components clear of logic that could be moved to `+page.server.ts` later + +### When to Add Backend + +Consider adding backend when: +- ✅ User growth exceeds 1,000 daily active users +- ✅ Analytics show users repeatedly saving comparisons manually +- ✅ Feedback indicates desire for user accounts +- ✅ Performance degrades due to repeated API calls +- ✅ Planning to add premium features requiring payment processing + +--- + +## Font Provider Integration Strategy + +### Provider Overview + +| Provider | API Type | License | Font Count | Integration Complexity | +|----------|----------|---------|------------|------------------------| +| Google Fonts | REST API | Open Source (SIL/OFL) | 1,600+ | Low - Official API available | +| Fontshare | GitHub/CDN | Free License | 100+ | Very Low - Static JSON available | + +--- + +### Google Fonts Integration + +#### API Endpoint + +``` +https://www.googleapis.com/webfonts/v1/webfonts?key=YOUR_API_KEY +``` + +#### TypeScript Implementation + +```typescript +// src/lib/services/google-fonts.ts +import type { FontMetadata, FontVariant, FontCategory } from '$lib/types'; + +export interface GoogleFontsResponse { + kind: string; + items: GoogleFontItem[]; +} + +export interface GoogleFontItem { + family: string; + variants: string[]; + subsets: string[]; + version: string; + lastModified: string; + files: Record; + category: 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace'; +} + +export async function fetchGoogleFonts(apiKey: string): Promise { + const response = await fetch( + `https://www.googleapis.com/webfonts/v1/webfonts?key=${apiKey}` + ); + + if (!response.ok) { + throw new Error(`Google Fonts API error: ${response.statusText}`); + } + + const data: GoogleFontsResponse = await response.json(); + + return data.items.map(mapGoogleFontToMetadata); +} + +function mapGoogleFontToMetadata(item: GoogleFontItem): FontMetadata { + const category = mapGoogleCategory(item.category); + + return { + id: `google-${item.family.toLowerCase().replace(/\s+/g, '-')}`, + provider: 'google-fonts', + family: item.family, + displayName: item.family, + category, + variants: mapGoogleVariants(item.variants), + license: { + type: 'open-source', + url: 'https://fonts.google.com/attribution', + attribution: 'Google Fonts', + commercial: true, + embedding: 'webfont' + }, + languages: item.subsets, + previewUrl: getGoogleFontPreviewUrl(item.family), + cssUrl: getGoogleFontCssUrl(item.family, '400'), + metadata: { + version: item.version, + lastModified: item.lastModified, + files: item.files + } + }; +} + +function mapGoogleCategory( + category: GoogleFontItem['category'] +): FontCategory { + const mapping = { + 'sans-serif': 'sans-serif', + 'serif': 'serif', + 'display': 'display', + 'handwriting': 'handwriting', + 'monospace': 'monospace' + } as const; + return mapping[category] || 'display'; +} + +function mapGoogleVariants(variants: string[]): FontVariant[] { + return variants.map(v => { + const weight = parseInt(v) || 400; + const style = v.includes('italic') ? 'italic' : 'normal'; + return { weight, style, name: v }; + }); +} + +function getGoogleFontPreviewUrl(family: string): string { + return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@400&display=swap`; +} + +function getGoogleFontCssUrl(family: string, weight: string): string { + return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@${weight}&display=swap`; +} +``` + +#### Dynamic Font Loading + +```typescript +// src/lib/utils/font-loader.ts +let loadedFonts = new Set(); + +export async function loadFont(family: string, weight: number = 400): Promise { + const fontKey = `${family}-${weight}`; + + if (loadedFonts.has(fontKey)) { + return; + } + + const cssUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@${weight}&display=swap`; + + try { + // Create and inject link element + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = cssUrl; + link.crossOrigin = 'anonymous'; + + await new Promise((resolve, reject) => { + link.onload = () => { + loadedFonts.add(fontKey); + resolve(); + }; + link.onerror = () => reject(new Error(`Failed to load font: ${family}`)); + document.head.appendChild(link); + }); + } catch (error) { + console.error(`Failed to load font ${family}:`, error); + throw error; + } +} + +export function unloadFont(family: string, weight: number = 400): void { + const cssUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@${weight}&display=swap`; + const links = document.querySelectorAll(`link[href="${cssUrl}"]`); + links.forEach(link => link.remove()); + loadedFonts.delete(`${family}-${weight}`); +} + +export function preloadFonts(fonts: { family: string; weight: number }[]): void { + fonts.forEach(({ family, weight }) => { + const link = document.createElement('link'); + link.rel = 'preload'; + link.as = 'font'; + link.type = 'font/woff2'; + link.href = `https://fonts.gstatic.com/s/${family.toLowerCase()}/v${weight}`; + link.crossOrigin = 'anonymous'; + document.head.appendChild(link); + }); +} +``` + +--- + +### Fontshare Integration + +#### Data Source + +Fontshare provides a static JSON file with all font metadata: + +``` +https://api.fontshare.com/v2/css?f[]= +``` + +Alternative: Use the GitHub repository data or scrape from the website. + +#### TypeScript Implementation + +```typescript +// src/lib/services/fontshare.ts +import type { FontMetadata } from '$lib/types'; + +export interface FontshareMetadata { + family: string; + slug: string; + styles: FontshareStyle[]; + subsets: string[]; + category: string; +} + +export interface FontshareStyle { + name: string; + weight: number; + file: string; + unicodeRange?: string; +} + +// Fontshare static font list (maintained manually or fetched from their docs) +const FONTHARE_FONTS: FontshareMetadata[] = [ + { + family: 'Albert Sans', + slug: 'albert-sans', + styles: [ + { name: 'Regular', weight: 400, file: 'https://api.fontshare.com/v2/css?f[]=albert-sans@400' }, + { name: 'Medium', weight: 500, file: 'https://api.fontshare.com/v2/css?f[]=albert-sans@500' }, + { name: 'Bold', weight: 700, file: 'https://api.fontshare.com/v2/css?f[]=albert-sans@700' } + ], + subsets: ['latin', 'latin-ext'], + category: 'sans-serif' + }, + // Add more Fontshare fonts as needed... +]; + +export async function fetchFontshareFonts(): Promise { + // In a real implementation, you might fetch this from a maintained JSON file + // For MVP, we use the static list or fetch from Fontshare's API + + // Option 1: Use static list + return FONTHARE_FONTS.map(mapFontshareToMetadata); + + // Option 2: Fetch from Fontshare API (if available) + // const response = await fetch('https://api.fontshare.com/v2/fonts'); + // const data = await response.json(); + // return data.map(mapFontshareToMetadata); +} + +function mapFontshareToMetadata(font: FontshareMetadata): FontMetadata { + return { + id: `fontshare-${font.slug}`, + provider: 'fontshare', + family: font.family, + displayName: font.family, + category: mapFontshareCategory(font.category), + variants: font.styles.map(style => ({ + weight: style.weight, + style: 'normal', + name: style.name, + url: style.file + })), + license: { + type: 'open-source', + url: 'https://www.fontshare.com/license', + attribution: 'Fontshare', + commercial: true, + embedding: 'webfont' + }, + languages: font.subsets, + previewUrl: font.styles[0]?.file || '', + cssUrl: font.styles[0]?.file || '', + metadata: { + slug: font.slug, + styles: font.styles + } + }; +} + +function mapFontshareCategory(category: string): FontCategory { + const validCategories: FontCategory[] = ['sans-serif', 'serif', 'display', 'handwriting', 'monospace']; + const normalized = category.toLowerCase().replace(' ', '-'); + return validCategories.includes(normalized as FontCategory) + ? (normalized as FontCategory) + : 'display'; +} +``` + +--- + +### Data Normalization + +Both providers return different data structures. Normalize to a unified interface: + +```typescript +// src/lib/types/fonts.ts +export type FontCategory = 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace'; +export type FontProvider = 'google-fonts' | 'fontshare'; + +export interface LicenseInfo { + type: 'open-source' | 'commercial' | 'free-for-personal'; + url: string; + attribution: string; + commercial: boolean; + embedding: 'webfont' | 'self-host' | 'both'; +} + +export interface FontVariant { + weight: number; + style: 'normal' | 'italic'; + name: string; + url?: string; +} + +export interface FontMetadata { + id: string; + provider: FontProvider; + family: string; + displayName: string; + category: FontCategory; + variants: FontVariant[]; + license: LicenseInfo; + languages: string[]; + previewUrl: string; + cssUrl: string; + popularity?: number; // For sorting + trending?: boolean; + metadata: Record; +} + +export interface ComparisonFont { + font: FontMetadata; + variant: FontVariant; + size: number; + lineHeight: number; + letterSpacing: number; + color: string; + text?: string; // Override default preview text +} + +export interface ComparisonSettings { + text: string; + backgroundColor: string; + showGrid: boolean; + watermark: boolean; +} +``` + +--- + +## Performance Optimization + +### Lazy Loading Strategy + +#### Font Lazy Loading + +```typescript +// src/lib/hooks/useLazyFontLoader.ts +import { tick } from 'svelte'; +import { loadFont } from '$lib/utils/font-loader'; + +export function useLazyFontLoader() { + let observer: IntersectionObserver | null = null; + let elementsToLoad = new Map(); + + function observe( + element: HTMLElement, + family: string, + weight: number = 400 + ) { + if (!observer) { + observer = new IntersectionObserver( + (entries) => { + entries.forEach(async (entry) => { + if (entry.isIntersecting) { + const data = elementsToLoad.get(entry.target as HTMLElement); + if (data) { + await loadFont(data.family, data.weight); + observer?.unobserve(entry.target); + elementsToLoad.delete(entry.target as HTMLElement); + } + } + }); + }, + { + rootMargin: '200px', // Load before element enters viewport + threshold: 0.01 + } + ); + } + + elementsToLoad.set(element, { family, weight }); + observer.observe(element); + } + + function destroy() { + observer?.disconnect(); + observer = null; + elementsToLoad.clear(); + } + + return { observe, destroy }; +} +``` + +#### Component-Level Usage + +```svelte + + + +
+ {text} +
+ + +``` + +### Caching Strategy + +#### localStorage Caching with TTL + +```typescript +// src/lib/utils/cache.ts +const CACHE_KEY = 'glyphdiff:font-cache'; +const CACHE_VERSION = 1; +const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours + +interface CacheEntry { + version: number; + timestamp: number; + data: unknown; +} + +export function setCache(key: string, data: T, ttl: number = CACHE_TTL): void { + const entry: CacheEntry = { + version: CACHE_VERSION, + timestamp: Date.now(), + data + }; + + const cache = getCacheData(); + cache[key] = entry; + + try { + localStorage.setItem(CACHE_KEY, JSON.stringify(cache)); + } catch (error) { + console.warn('Failed to set cache:', error); + } +} + +export function getCache(key: string): T | null { + const cache = getCacheData(); + const entry = cache[key]; + + if (!entry) { + return null; + } + + // Check version + if (entry.version !== CACHE_VERSION) { + delete cache[key]; + saveCacheData(cache); + return null; + } + + // Check TTL + if (Date.now() - entry.timestamp > CACHE_TTL) { + delete cache[key]; + saveCacheData(cache); + return null; + } + + return entry.data as T; +} + +export function clearCache(key?: string): void { + if (key) { + const cache = getCacheData(); + delete cache[key]; + saveCacheData(cache); + } else { + localStorage.removeItem(CACHE_KEY); + } +} + +function getCacheData(): Record { + try { + const cached = localStorage.getItem(CACHE_KEY); + return cached ? JSON.parse(cached) : {}; + } catch { + return {}; + } +} + +function saveCacheData(data: Record): void { + try { + localStorage.setItem(CACHE_KEY, JSON.stringify(data)); + } catch (error) { + console.warn('Failed to save cache:', error); + } +} +``` + +#### Service Worker Caching (Optional for Enhanced PWA) + +```typescript +// src/lib/service-worker.ts +const CACHE_NAME = 'glyphdiff-v1'; +const FONT_CACHE_PREFIX = 'font-'; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll([ + '/', + '/fonts', + '/compare', + // Add other static assets + ]); + }) + ); +}); + +self.addEventListener('fetch', (event) => { + const url = new URL(event.request.url); + + // Cache Google Fonts + if (url.hostname === 'fonts.googleapis.com' || url.hostname === 'fonts.gstatic.com') { + event.respondWith( + caches.open(`${FONT_CACHE_PREFIX}${CACHE_NAME}`).then((cache) => { + return cache.match(event.request).then((response) => { + if (response) { + return response; + } + return fetch(event.request).then((networkResponse) => { + cache.put(event.request, networkResponse.clone()); + return networkResponse; + }); + }); + }) + ); + return; + } + + // Network-first for API calls + if (url.pathname.includes('/api/')) { + event.respondWith( + fetch(event.request) + .then((response) => { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(event.request, responseClone); + }); + return response; + }) + .catch(() => caches.match(event.request)) + ); + return; + } + + // Cache-first for static assets + event.respondWith( + caches.match(event.request).then((response) => { + return response || fetch(event.request); + }) + ); +}); +``` + +### Performance Metrics + +#### Target Core Web Vitals + +| Metric | Target | Why It Matters | +|--------|--------|----------------| +| LCP (Largest Contentful Paint) | < 2.5s | Main content loads quickly | +| FID (First Input Delay) | < 100ms | Responsive to user input | +| CLS (Cumulative Layout Shift) | < 0.1 | Visual stability | +| TTFB (Time to First Byte) | < 600ms | Fast initial response | + +#### Performance Monitoring + +```typescript +// src/lib/utils/performance.ts +export function reportWebVitals(): void { + if (typeof window === 'undefined' || !('PerformanceObserver' in window)) { + return; + } + + // LCP + const lcpObserver = new PerformanceObserver((list) => { + const entries = list.getEntries(); + const lastEntry = entries[entries.length - 1] as any; + console.log('LCP:', lastEntry.startTime); + // Send to analytics + }); + lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] }); + + // FID + const fidObserver = new PerformanceObserver((list) => { + const entries = list.getEntries(); + const fid = entries[0] as any; + console.log('FID:', fid.processingStart - fid.startTime); + // Send to analytics + }); + fidObserver.observe({ entryTypes: ['first-input'] }); + + // CLS + const clsObserver = new PerformanceObserver((list) => { + let clsValue = 0; + for (const entry of list.getEntries() as any[]) { + if (!entry.hadRecentInput) { + clsValue += entry.value; + } + } + console.log('CLS:', clsValue); + // Send to analytics + }); + clsObserver.observe({ entryTypes: ['layout-shift'] }); +} +``` + +--- + +## Frontend Architecture + +### Project Structure + +``` +glyphdiff/ +├── src/ +│ ├── lib/ +│ │ ├── components/ +│ │ │ ├── FontCard.svelte +│ │ │ ├── FontPreview.svelte +│ │ │ ├── ComparisonGrid.svelte +│ │ │ ├── FilterBar.svelte +│ │ │ └── DarkModeToggle.svelte +│ │ ├── stores/ +│ │ │ ├── fontStore.ts +│ │ │ ├── comparisonStore.ts +│ │ │ ├── filterStore.ts +│ │ │ └── uiStore.ts +│ │ ├── services/ +│ │ │ ├── google-fonts.ts +│ │ │ ├── fontshare.ts +│ │ │ └── font-loader.ts +│ │ ├── hooks/ +│ │ │ ├── useLazyFontLoader.ts +│ │ │ ├── useKeyboardShortcuts.ts +│ │ │ └── useLocalStorage.ts +│ │ ├── utils/ +│ │ │ ├── cache.ts +│ │ │ ├── format.ts +│ │ │ └── url.ts +│ │ ├── types/ +│ │ │ ├── fonts.ts +│ │ │ ├── comparison.ts +│ │ │ └── index.ts +│ │ └── constants.ts +│ ├── routes/ +│ │ ├── +layout.svelte +│ │ ├── +layout.ts +│ │ ├── +page.svelte # Home page +│ │ ├── fonts/ +│ │ │ ├── +page.svelte # Font catalog +│ │ │ ├── +page.ts # Font data loading +│ │ │ └── [slug]/ +│ │ │ └── +page.svelte # Font detail page +│ │ └── compare/ +│ │ ├── +page.svelte # Comparison page +│ │ └── +page.ts # URL state parsing +│ ├── app.css +│ └── app.d.ts +├── static/ +│ ├── favicon.ico +│ └── og-image.png +├── tests/ +│ ├── unit/ +│ └── e2e/ +├── package.json +├── svelte.config.js +├── tailwind.config.js +├── tsconfig.json +└── vite.config.ts +``` + +### Component Architecture + +``` +┌────────────────────────────────────────────────────────────┐ +│ +layout.svelte │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Header │ │ +│ │ Logo | Search | DarkModeToggle │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Main │ │ +│ │ ┌────────────────────────────────────────────────┐ │ │ +│ │ │ +page.svelte (Home) │ │ │ +│ │ │ Hero section → Featured fonts → CTA │ │ │ +│ │ └────────────────────────────────────────────────┘ │ │ +│ │ ┌────────────────────────────────────────────────┐ │ │ +│ │ │ fonts/+page.svelte │ │ │ +│ │ │ FilterBar → FontGrid → FontCard │ │ │ +│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ +│ │ │ │FontCard │ │FontCard │ │FontCard │ ... │ │ │ +│ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ +│ │ └────────────────────────────────────────────────┘ │ │ +│ │ ┌────────────────────────────────────────────────┐ │ │ +│ │ │ compare/+page.svelte │ │ │ +│ │ │ ComparisonGrid → FontPreview × 4 │ │ │ +│ │ │ Toolbar (text, size, color, settings) │ │ │ +│ │ └────────────────────────────────────────────────┘ │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Footer │ │ +│ └──────────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────┘ +``` + +### Routing Strategy + +#### SvelteKit File-Based Routing + +| Route | File | Purpose | SSR/CSR | +|-------|------|---------|---------| +| `/` | `routes/+page.svelte` | Home/Landing page | SSR | +| `/fonts` | `routes/fonts/+page.svelte` | Font catalog | SSG | +| `/fonts/[slug]` | `routes/fonts/[slug]/+page.svelte` | Font details | SSG | +| `/compare` | `routes/compare/+page.svelte` | Comparison tool | CSR | +| `/api/...` | `routes/api/+server.ts` | Future API routes | Serverless | + +#### URL State for Comparisons + +```typescript +// src/routes/compare/+page.ts +import type { PageLoad } from './$types'; + +export const load: PageLoad = ({ url }) => { + const searchParams = url.searchParams; + + // Parse comparison state from URL + const fontsParam = searchParams.get('fonts'); + const text = searchParams.get('text') || 'The quick brown fox...'; + const size = parseInt(searchParams.get('size') || '24'); + const darkMode = searchParams.get('dark') === 'true'; + + let comparisonFonts: Array<{ family: string; weight: number }> = []; + + if (fontsParam) { + try { + comparisonFonts = JSON.parse(decodeURIComponent(fontsParam)); + } catch (e) { + console.error('Failed to parse fonts param:', e); + } + } + + return { + initialFonts: comparisonFonts, + settings: { text, size, darkMode } + }; +}; +``` + +```svelte + + +``` + +--- + +## Data Models + +### Core Type Definitions + +```typescript +// src/lib/types/fonts.ts + +/** + * Font provider sources + */ +export type FontProvider = 'google-fonts' | 'fontshare'; + +/** + * Standard font categories + */ +export type FontCategory = + | 'sans-serif' + | 'serif' + | 'display' + | 'handwriting' + | 'monospace'; + +/** + * License information for fonts + */ +export interface LicenseInfo { + /** License type classification */ + type: 'open-source' | 'commercial' | 'free-for-personal'; + + /** URL to full license text */ + url: string; + + /** Attribution required for display */ + attribution: string; + + /** Commercial use permitted */ + commercial: boolean; + + /** Allowed embedding methods */ + embedding: 'webfont' | 'self-host' | 'both'; +} + +/** + * Individual font variant (weight/style combination) + */ +export interface FontVariant { + /** Font weight (100-900) */ + weight: number; + + /** Font style */ + style: 'normal' | 'italic'; + + /** Display name for the variant */ + name: string; + + /** URL to the font file (CSS or WOFF2) */ + url?: string; + + /** Subset information (optional) */ + subset?: string; +} + +/** + * Complete metadata for a font family + */ +export interface FontMetadata { + /** Unique identifier for the font */ + id: string; + + /** Source provider */ + provider: FontProvider; + + /** Font family name (CSS property value) */ + family: string; + + /** Display name for UI */ + displayName: string; + + /** Font category */ + category: FontCategory; + + /** Available variants */ + variants: FontVariant[]; + + /** License information */ + license: LicenseInfo; + + /** Supported language subsets */ + languages: string[]; + + /** URL for font preview (CSS) */ + previewUrl: string; + + /** URL for CSS import */ + cssUrl: string; + + /** Popularity score (for sorting) */ + popularity?: number; + + /** Trending status */ + trending?: boolean; + + /** Additional provider-specific metadata */ + metadata: Record; +} + +/** + * Font with selected variant for display + */ +export interface ActiveFont { + /** Font metadata */ + font: FontMetadata; + + /** Selected variant */ + variant: FontVariant; +} + +/** + * Filter options for font catalog + */ +export interface FontFilters { + /** Category filter */ + categories: FontCategory[]; + + /** Weight range filter */ + weightRange: [number, number]; + + /** Provider filter */ + providers: FontProvider[]; + + /** Language subset filter */ + languages: string[]; + + /** Search query */ + search: string; + + /** Sort option */ + sortBy: 'popularity' | 'name' | 'recent' | 'trending'; + + /** Sort direction */ + sortDirection: 'asc' | 'desc'; +} +``` + +```typescript +// src/lib/types/comparison.ts + +import type { FontMetadata, FontVariant } from './fonts'; + +/** + * Display settings for comparison preview + */ +export interface FontDisplaySettings { + /** Font size in pixels */ + size: number; + + /** Line height (unitless multiplier) */ + lineHeight: number; + + /** Letter spacing in pixels */ + letterSpacing: number; + + /** Text color */ + color: string; + + /** Custom preview text override */ + text?: string; + + /** Text alignment */ + textAlign: 'left' | 'center' | 'right'; +} + +/** + * Global comparison settings + */ +export interface ComparisonSettings { + /** Default preview text */ + text: string; + + /** Background color */ + backgroundColor: string; + + /** Show comparison grid lines */ + showGrid: boolean; + + /** Show watermarks/attribution */ + watermark: boolean; + + /** Default display settings */ + display: FontDisplaySettings; +} + +/** + * Font in a comparison session + */ +export interface ComparisonFont { + /** Font metadata */ + font: FontMetadata; + + /** Selected variant */ + variant: FontVariant; + + /** Display settings (overrides global) */ + settings: Partial; +} + +/** + * Complete comparison session + */ +export interface ComparisonSession { + /** Unique session identifier */ + id: string; + + /** Fonts being compared (max 4) */ + fonts: ComparisonFont[]; + + /** Global comparison settings */ + settings: ComparisonSettings; + + /** Session metadata */ + metadata: { + /** Creation timestamp */ + createdAt: number; + + /** Last modified timestamp */ + updatedAt: number; + + /** Session name */ + name?: string; + + /** Whether session is saved */ + saved: boolean; + }; +} + +/** + * Shareable comparison data (for URL encoding) + */ +export interface ShareableComparison { + /** Font identifiers and weights */ + fonts: Array<{ family: string; weight: number }>; + + /** Preview text */ + text?: string; + + /** Font size */ + size?: number; + + /** Dark mode flag */ + dark?: boolean; +} +``` + +```typescript +// src/lib/types/ui.ts + +/** + * UI theme options + */ +export type Theme = 'light' | 'dark' | 'system'; + +/** + * UI settings persisted in localStorage + */ +export interface UISettings { + /** Selected theme */ + theme: Theme; + + /** Grid view density */ + gridDensity: 'compact' | 'comfortable' | 'spacious'; + + /** Show font variants in catalog */ + showVariants: boolean; + + /** Enable animations */ + animationsEnabled: boolean; +} + +/** + * Toast notification types + */ +export type ToastType = 'success' | 'error' | 'info' | 'warning'; + +/** + * Toast notification + */ +export interface Toast { + id: string; + type: ToastType; + message: string; + duration?: number; + action?: { + label: string; + handler: () => void; + }; +} + +/** + * Keyboard shortcut definition + */ +export interface KeyboardShortcut { + key: string; + ctrl?: boolean; + shift?: boolean; + alt?: boolean; + description: string; + action: () => void; +} + +/** + * Modal/Dialog state + */ +export interface ModalState { + id: string | null; + open: boolean; + props?: Record; +} +``` + +```typescript +// src/lib/types/index.ts + +// Re-export all types for convenient importing +export * from './fonts'; +export * from './comparison'; +export * from './ui'; +``` + +--- + +## Implementation Roadmap + +### Week 1: Svelte Fundamentals + +**Goal:** Build foundational understanding of Svelte 5 reactive primitives and set up development environment. + +#### Tasks + +- [ ] **Project Setup** + - [ ] Create SvelteKit project with TypeScript template + - [ ] Install and configure Tailwind CSS 4.x + - [ ] Install and set up Bits UI component library + - [ ] Configure Prettier and ESLint with Svelte plugin + - [ ] Set up Git repository and .gitignore + +- [ ] **Environment Configuration** + - [ ] Create `.env.example` with GOOGLE_FONTS_API_KEY + - [ ] Add `.env` to `.gitignore` + - [ ] Configure environment variable loading in `vite.config.ts` + +- [ ] **Core Types Definition** + - [ ] Create `src/lib/types/fonts.ts` + - [ ] Create `src/lib/types/comparison.ts` + - [ ] Create `src/lib/types/ui.ts` + - [ ] Create `src/lib/types/index.ts` for re-exports + +- [ ] **Svelte 5 Reactive Primitives Practice** + - [ ] Build Counter component using `$state` + - [ ] Create DerivedCounter component using `$derived` + - [ ] Implement EffectLogger component using `$effect` + - [ ] Practice with reactive arrays and objects + +- [ ] **Practice Components** + - [ ] Build simple Button component + - [ ] Create Toggle component + - [ ] Implement Select dropdown component + - [ ] Build simple FilterBar component + +#### Learning Outcomes +- Understand Svelte 5's new runes system (`$state`, `$derived`, `$effect`) +- Comfortable with Svelte component structure and syntax +- Familiar with Bits UI components +- Tailwind CSS integration working + +#### Week 1 Deliverables +- Working development environment +- 4 practice components demonstrating Svelte reactivity +- Core TypeScript types defined + +--- + +### Week 2: SvelteKit & Routing + +**Goal:** Build font catalog page with routing, data fetching, and understand SvelteKit's SSR capabilities. + +#### Tasks + +- [ ] **Google Fonts Service** + - [ ] Create `fetchGoogleFonts()` function + - [ ] Implement `mapGoogleFontToMetadata()` mapper + - [ ] Add TypeScript interfaces for Google Fonts API response + - [ ] Test with Google Fonts API key + +- [ ] **Fontshare Service** + - [ ] Create `fetchFontshareFonts()` function + - [ ] Build static font list for Fontshare + - [ ] Implement `mapFontshareToMetadata()` mapper + - [ ] Combine both providers into single font catalog + +- [ ] **Font Catalog Page** + - [ ] Create `routes/fonts/+page.svelte` + - [ ] Implement `routes/fonts/+page.ts` for data loading + - [ ] Build FontCard component + - [ ] Create responsive FontGrid layout + +- [ ] **Font Detail Page** + - [ ] Create `routes/fonts/[slug]/+page.svelte` + - [ ] Implement dynamic route parameter extraction + - [ ] Build font detail view with all variants + - [ ] Add "Add to Comparison" button + +- [ ] **Comparison Page Skeleton** + - [ ] Create `routes/compare/+page.svelte` + - [ ] Implement URL state parsing in `+page.ts` + - [ ] Build basic layout for comparison grid + +- [ ] **SSR Understanding** + - [ ] Explore SvelteKit rendering modes (SSR, CSR, SSG) + - [ ] Test page performance with SSR enabled + - [ ] Understand hydration process + - [ ] Configure static generation for font catalog + +#### Learning Outcomes +- Understand SvelteKit file-based routing +- Learn data loading patterns (`+page.ts`, `+page.server.ts`) +- Grasp SSR vs CSR concepts and trade-offs +- Comfortable with dynamic routes and parameters + +#### Week 2 Deliverables +- Working font catalog page with Google Fonts data +- Font detail pages with all metadata +- Basic comparison page with URL state +- Understanding of SvelteKit rendering modes + +--- + +### Week 3: State Management + +**Goal:** Implement comprehensive state management with Svelte stores, localStorage persistence, and URL state synchronization. + +#### Tasks + +- [ ] **Font Store** + - [ ] Create `src/lib/stores/fontStore.ts` + - [ ] Implement `$state` for fonts array + - [ ] Add derived state for filtered fonts + - [ ] Implement localStorage persistence + - [ ] Add loading and error states + +- [ ] **Filter Store** + - [ ] Create `src/lib/stores/filterStore.ts` + - [ ] Implement `$state` for all filter options + - [ ] Add derived state for active filter count + - [ ] Implement reset filters function + - [ ] Add localStorage persistence + +- [ ] **Comparison Store** + - [ ] Create `src/lib/stores/comparisonStore.ts` + - [ ] Implement `$state` for comparison fonts (max 4) + - [ ] Add functions to add/remove fonts + - [ ] Implement comparison settings state + - [ ] Add shareable URL generation + - [ ] Implement localStorage persistence + +- [ ] **UI Store** + - [ ] Create `src/lib/stores/uiStore.ts` + - [ ] Implement theme toggle logic + - [ ] Add settings for grid density, animations + - [ ] Implement system theme detection + - [ ] Add localStorage persistence + +- [ ] **Cache Utility** + - [ ] Implement `cache.ts` with TTL support + - [ ] Add caching to Google Fonts fetcher + - [ ] Implement cache invalidation logic + - [ ] Add cache management UI (optional) + +- [ ] **URL State Integration** + - [ ] Sync comparison state with URL + - [ ] Sync filter state with URL (optional) + - [ ] Implement deep linking to comparisons + - [ ] Handle URL encoding/decoding + +#### Learning Outcomes +- Master Svelte 5 stores with `$state` runes +- Understand localStorage persistence patterns +- Learn URL state synchronization +- Practice derived state and computed values + +#### Week 3 Deliverables +- Fully functional stores for fonts, filters, comparisons, UI +- localStorage persistence working +- URL state synchronization working +- Caching layer implemented + +--- + +### Week 4: UI & Polish + +**Goal:** Build polished UI with Bits UI components, dark mode, responsive design, and smooth animations. + +#### Tasks + +- [ ] **Layout Components** + - [ ] Build Header component with logo and navigation + - [ ] Create Footer component with links and attribution + - [ ] Implement global layout in `+layout.svelte` + - [ ] Add mobile navigation menu + +- [ ] **FilterBar Component** + - [ ] Use Bits UI Select for category filter + - [ ] Use Bits UI Dialog for advanced filters + - [ ] Add search input with debouncing + - [ ] Implement active filter indicators + +- [ ] **ComparisonGrid Component** + - [ ] Use Bits UI Grid for responsive layout + - [ ] Implement drag-and-drop reordering (optional) + - [ ] Add font settings panel per column + - [ ] Implement "Remove font" button + +- [ ] **FontPreview Component** + - [ ] Build preview text area + - [ ] Add size, weight, line-height controls + - [ ] Implement color picker + - [ ] Add lazy loading for fonts + +- [ ] **Dark Mode** + - [ ] Implement dark mode toggle + - [ ] Configure Tailwind dark mode classes + - [ ] Add system preference detection + - [ ] Test color contrast in both themes + +- [ ] **Responsive Design** + - [ ] Test on mobile (375px) + - [ ] Test on tablet (768px) + - [ ] Test on desktop (1280px+) + - [ ] Adjust grid columns for breakpoints + +- [ ] **Animations & Transitions** + - [ ] Add page transitions using Svelte transitions + - [ ] Implement loading skeletons + - [ ] Add hover effects on FontCard + - [ ] Create toast notification system + +- [ ] **Accessibility** + - [ ] Add ARIA labels to interactive elements + - [ ] Ensure keyboard navigation works + - [ ] Test with screen reader + - [ ] Verify color contrast ratios + +- [ ] **Performance Optimization** + - [ ] Implement image/font lazy loading + - [ ] Add code splitting where appropriate + - [ ] Test Lighthouse scores + - [ ] Optimize bundle size + +#### Learning Outcomes +- Proficient with Bits UI component library +- Comfortable building responsive layouts +- Understand accessibility best practices +- Experience with performance optimization + +#### Week 4 Deliverables +- Polished, production-ready UI +- Dark mode fully implemented +- Responsive design across all breakpoints +- Accessibility improvements completed +- Performance targets met + +--- + +## TanStack Query Research + +### What is TanStack Query? + +TanStack Query (formerly React Query) is a powerful data synchronization library for fetching, caching, and managing asynchronous state. It's widely used in the React ecosystem. + +### Key Features + +- **Automatic caching and refetching** +- **Loading and error state management** +- **Optimistic updates** +- **Pagination support** +- **Request deduplication** + +### TanStack Query for Svelte? + +TanStack Query has a Svelte implementation (`@tanstack/svelte-query`) that provides similar functionality to the React version. + +### Why TanStack Query is NOT Recommended for MVP + +| Factor | TanStack Query | SvelteKit Load | Recommendation | +|--------|----------------|----------------|----------------| +| **Data Source** | Client-side API calls | Server-side data loading | SvelteKit Load is sufficient | +| **Complexity** | Additional dependency | Built-in to framework | SvelteKit reduces complexity | +| **SSR Support** | Requires hydration | Native SSR | SvelteKit is cleaner | +| **Learning Curve** | Additional concepts to learn | Learn SvelteKit once | Focus on SvelteKit | +| **Bundle Size** | ~13KB gzipped | 0KB (built-in) | Smaller bundle | +| **Type Safety** | Good | Excellent | SvelteKit with TS is robust | +| **Caching Strategy** | In-memory cache | Static generation + CDN | SSG is more performant | + +### Use Cases Where TanStack Query Would Be Useful + +Consider adding TanStack Query in the future if: +- Implementing user authentication and profile data +- Building real-time features (WebSocket, polling) +- Complex pagination with infinite scroll +- Multiple API endpoints requiring complex cache invalidation +- Offline-first functionality + +### Recommended Data Fetching Approach for MVP + +#### Use SvelteKit's Data Loading + +```typescript +// ✅ Recommended: SvelteKit load function +// src/routes/fonts/+page.ts +import { fetchGoogleFonts, fetchFontshareFonts } from '$lib/services'; +import { setCache, getCache } from '$lib/utils/cache'; + +export const load = async ({ fetch, depends }) => { + // Check cache first + const cached = getCache('font-catalog'); + if (cached) { + return { fonts: cached, cached: true }; + } + + // Fetch from providers + const [googleFonts, fontshareFonts] = await Promise.all([ + fetchGoogleFonts(import.meta.env.VITE_GOOGLE_FONTS_API_KEY), + fetchFontshareFonts() + ]); + + const allFonts = [...googleFonts, ...fontshareFonts]; + + // Cache for 24 hours + setCache('font-catalog', allFonts); + + // Invalidate cache when needed + depends('app:fonts'); + + return { fonts: allFonts, cached: false }; +}; +``` + +#### Use Svelte Stores for Client-Side State + +```typescript +// ✅ Recommended: Svelte 5 stores with $state +// src/lib/stores/fontStore.ts +import { setCache, getCache } from '$lib/utils/cache'; + +class FontStore { + fonts = $state([]); + loading = $state(false); + error = $state(null); + + async loadFonts(apiKey: string) { + this.loading = true; + this.error = null; + + try { + const cached = getCache('font-catalog'); + if (cached) { + this.fonts = cached; + return; + } + + const [googleFonts, fontshareFonts] = await Promise.all([ + fetchGoogleFonts(apiKey), + fetchFontshareFonts() + ]); + + this.fonts = [...googleFonts, ...fontshareFonts]; + setCache('font-catalog', this.fonts); + } catch (e) { + this.error = e as Error; + } finally { + this.loading = false; + } + } +} + +export const fontStore = new FontStore(); +``` + +### Conclusion: Do NOT Use TanStack Query for MVP + +**Reasoning:** +1. SvelteKit's data loading capabilities are sufficient for this project's needs +2. Focus on learning Svelte 5 and SvelteKit rather than adding more dependencies +3. Static generation provides better performance than client-side caching +4. Simpler codebase is easier to maintain while learning + +**Future Consideration:** +Revisit this decision if the project grows to include: +- User authentication +- Real-time features +- Complex client-side data synchronization + +--- + +## Learning Guide: React → Svelte + +### React vs Svelte 5 Comparison + +| React Concept | Svelte 5 Equivalent | Notes | +|--------------|---------------------|-------| +| `useState` | `$state` | No setter function needed, direct reassignment triggers reactivity | +| `useEffect` | `$effect` | Auto-tracks dependencies, no dependency array needed | +| `useMemo` | `$derived` | Computed values that automatically update | +| `useCallback` | Direct function | Functions are auto-memoized in Svelte 5 | +| `children` prop | `` | Slot composition with named slots | +| `props` | `export let` | Props declared with `export let propName` | +| `context` | `setContext` / `getContext` | Similar but with cleaner API | +| `useRef` | `bind:this` | Direct element binding instead of ref objects | +| `useState(prev => prev + 1)` | `count += 1` | Direct mutation works with $state | +| `useReducer` | Custom store with methods | Svelte stores are more flexible | +| `forwardRef` | N/A | Ref forwarding handled automatically | +| `useImperativeHandle` | N/A | Call methods directly on component instances | + +### Code Examples + +#### Counter Component + +**React:** + +```tsx +import { useState, useEffect } from 'react'; + +function Counter({ initial = 0 }: { initial?: number }) { + const [count, setCount] = useState(initial); + const [doubled, setDoubled] = useState(0); + + useEffect(() => { + setDoubled(count * 2); + }, [count]); + + return ( +
+

Count: {count}

+

Doubled: {doubled}

+ + +
+ ); +} +``` + +**Svelte 5:** + +```svelte + + +
+

Count: {count}

+

Doubled: {doubled}

+ + +
+``` + +**Key Differences:** +- No setter function needed - direct mutation works with `$state` +- `$derived` eliminates need for `useEffect` with dependencies +- Functions are defined separately (no need for `useCallback`) +- Event handlers use lowercase `onclick` instead of `onClick` + +--- + +#### Data Fetching + +**React with React Query:** + +```tsx +import { useQuery } from '@tanstack/react-query'; + +function FontList() { + const { + data: fonts, + isLoading, + error + } = useQuery({ + queryKey: ['fonts'], + queryFn: async () => { + const response = await fetch('/api/fonts'); + if (!response.ok) throw new Error('Failed to fetch'); + return response.json(); + } + }); + + if (isLoading) return
Loading...
; + if (error) return
Error: {error.message}
; + + return ( +
    + {fonts.map((font: any) => ( +
  • {font.displayName}
  • + ))} +
+ ); +} +``` + +**SvelteKit with load function:** + +```typescript +// +page.ts +export const load = async ({ fetch }) => { + const response = await fetch('https://www.googleapis.com/webfonts/v1/webfonts?key=' + import.meta.env.VITE_GOOGLE_FONTS_API_KEY); + const fonts = await response.json(); + return { fonts }; +}; +``` + +```svelte + + + +{#if data.fonts} +
    + {#each data.fonts as font} +
  • {font.family}
  • + {/each} +
+{/if} +``` + +**Key Differences:** +- SvelteKit loads data on the server (SSR) by default +- No loading state needed for initial page load +- Data flows from `load` function to page component via props + +--- + +#### State Management + +**React with Zustand:** + +```ts +// store.ts +import { create } from 'zustand'; + +interface FontStore { + fonts: FontMetadata[]; + selectedFont: string | null; + setFonts: (fonts: FontMetadata[]) => void; + selectFont: (id: string) => void; +} + +export const useFontStore = create((set) => ({ + fonts: [], + selectedFont: null, + setFonts: (fonts) => set({ fonts }), + selectFont: (id) => set({ selectedFont: id }) +})); +``` + +```tsx +// Component.tsx +import { useFontStore } from './store'; + +function FontSelector() { + const { fonts, selectedFont, selectFont } = useFontStore(); + + return ( + + ); +} +``` + +**Svelte with Store:** + +```ts +// fontStore.ts +import type { FontMetadata } from '$lib/types'; + +class FontStore { + fonts = $state([]); + selectedFont = $state(null); + + setFonts(fonts: FontMetadata[]) { + this.fonts = fonts; + } + + selectFont(id: string) { + this.selectedFont = id; + } +} + +export const fontStore = new FontStore(); +``` + +```svelte + + + + +``` + +**Key Differences:** +- Svelte stores use `$state` directly, no need for immer +- Two-way binding with `bind:value` instead of `onChange` +- Store access is simpler - just import and use + +--- + +#### Conditional Rendering + +**React:** + +```tsx +function ConditionalRender({ fonts, loading }: { fonts: Font[]; loading: boolean }) { + if (loading) { + return ; + } + + if (fonts.length === 0) { + return ; + } + + return ( +
+ {fonts.map(font => ( + + ))} +
+ ); +} +``` + +**Svelte:** + +```svelte + + +{#if loading} + +{:else if fonts.length === 0} + +{:else} +
+ {#each fonts as font} + + {/each} +
+{/if} +``` + +**Key Differences:** +- Svelte uses `{#if}`, `{:else if}`, `{:else}`, `{/if}` block syntax +- No ternary operator needed inside JSX +- `{#each}` block with `{/each}` for lists + +--- + +#### Component Props + +**React:** + +```tsx +interface FontCardProps { + font: FontMetadata; + onClick?: (font: FontMetadata) => void; + size?: number; + showLicense?: boolean; +} + +function FontCard({ font, onClick, size = 16, showLicense = false }: FontCardProps) { + return ( +
+

{font.displayName}

+ {showLicense && } + {onClick && } +
+ ); +} +``` + +**Svelte:** + +```svelte + + +
+

{font.displayName}

+ {#if showLicense} + + {/if} + {#if onClick} + + {/if} +
+``` + +**Key Differences:** +- Props declared with `export let` instead of function parameters +- Default values assigned with `=` in declaration +- Event handlers use lowercase (`onclick`) +- Props passed as attributes without curly braces for simple values + +--- + +#### Slot/Children Composition + +**React:** + +```tsx +// Modal.tsx +function Modal({ children, title, footer }: { + children: React.ReactNode; + title: string; + footer?: React.ReactNode; +}) { + return ( +
+
{title}
+
{children}
+ {footer &&
{footer}
} +
+ ); +} + +// Usage +Save}> +

Choose a font from the list below.

+ +
+``` + +**Svelte:** + +```svelte + + + + + + + + + + +

Choose a font from the list below.

+ +
+``` + +**Key Differences:** +- Named slots using `slot="name"` and `` +- Multiple slots with different names +- Default slot uses `` without name +- `` for content without wrapper element + +--- + +### Common Patterns + +#### Form Handling + +**React:** + +```tsx +function SearchForm({ onSearch }: { onSearch: (query: string) => void }) { + const [query, setQuery] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + onSearch(query); + }; + + return ( +
+ setQuery(e.target.value)} + placeholder="Search fonts..." + /> + +
+ ); +} +``` + +**Svelte:** + +```svelte + + +
+ + +
+``` + +**Key Difference:** Two-way binding with `bind:value` eliminates the need for `onChange`. + +--- + +#### Lists with Keys + +**React:** + +```tsx +{fonts.map(font => ( + handleSelect(font)} + /> +))} +``` + +**Svelte:** + +```svelte +{#each fonts as font (font.id)} + handleSelect(font)} + /> +{/each} +``` + +**Key Difference:** Key specified in parentheses after item name: `as item (key)`. + +--- + +#### Lifecycle Methods + +**React:** + +```tsx +useEffect(() => { + console.log('Mounted'); + return () => console.log('Cleanup'); +}, []); + +useEffect(() => { + console.log('count changed:', count); +}, [count]); +``` + +**Svelte:** + +```svelte + +``` + +--- + +## Scalability & Commercialization + +### When to Add Backend + +#### Indicators for Backend Implementation + +| Metric | Threshold | Action | +|--------|-----------|--------| +| Daily Active Users | > 1,000 | Consider backend | +| Google Fonts API Usage | > 800/day (80% of limit) | Implement server proxy | +| Feature Requests | > 10/month for user accounts | Plan authentication | +| Comparison Saves | Users frequently export/save | Add database | +| Page Load Time | > 3s on mobile | Optimize with backend caching | + +#### Backend Architecture (When Needed) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Vercel Edge │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ SvelteKit Server Functions │ │ +│ │ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ │ API Routes │ │ Auth Routes │ │ │ +│ │ └──────────────┘ └──────────────┘ │ │ +│ └───────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ │ + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│ Vercel Postgres │ │ Vercel KV │ +│ (User accounts, │ │ (Caching, │ +│ saved comps) │ │ rate limiting) │ +└──────────────────┘ └──────────────────┘ +``` + +#### Recommended Backend Stack + +| Component | Technology | Why | +|-----------|------------|-----| +| Server Runtime | Vercel Edge Functions | Fast, global, free tier generous | +| Database | Vercel Postgres (Neon) | Managed, edge-ready, TypeScript native | +| Caching | Vercel KV (Redis) | Fast key-value store, built-in | +| Authentication | Lucia Auth | Lightweight, framework-agnostic | +| ORM | Drizzle ORM | TypeScript-first, no runtime overhead | +| File Storage | Vercel Blob | Simple, scalable storage | + +--- + +### Monetization Models + +#### Tier 1: Freemium (Recommended Launch Model) + +| Feature | Free | Pro ($5/mo) | +|---------|------|-------------| +| Font comparisons | 4 fonts max | Unlimited | +| Save comparisons | Browser storage only | Cloud sync | +| Export images | Watermarked | No watermark | +| Custom text | Limited chars | Unlimited | +| Advanced filters | Basic | Advanced | +| API access | ❌ | ✅ | + +#### Tier 2: Team/Agency ($29/mo) + +- All Pro features +- Team collaboration (shared workspaces) +- Brand management (save brand fonts) +- Custom domains for shared comparisons +- Priority support +- Monthly usage reports + +#### Tier 3: Enterprise (Custom) + +- Unlimited API access +- Self-hosted deployment option +- Custom integrations +- SSO authentication +- Dedicated support +- SLA guarantees + +--- + +### Additional Features for Monetization + +1. **Font Pairing Recommendations** + - ML-based font matching + - Designer-curated pairings + - Upload custom fonts for analysis + +2. **CSS Generator** + - Export optimized CSS + - Variable font support + - Fallback font stacks + +3. **Embeddable Widget** + - Embed comparison on external sites + - White-label options + - Custom branding + +4. **Font Inspector** + - Detailed glyph analysis + - Character set viewer + - Font metrics display + +5. **A/B Testing** + - Test font combinations + - User feedback collection + - Conversion tracking + +--- + +## Deployment + +### Vercel Configuration + +#### vercel.json + +```json +{ + "buildCommand": "npm run build", + "devCommand": "npm run dev", + "installCommand": "npm install", + "framework": "sveltekit", + "regions": ["iad1"], + "headers": [ + { + "source": "/(.*)", + "headers": [ + { + "key": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "X-XSS-Protection", + "value": "1; mode=block" + }, + { + "key": "Referrer-Policy", + "value": "strict-origin-when-cross-origin" + } + ] + }, + { + "source": "/static/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=31536000, immutable" + } + ] + }, + { + "source": "/fonts/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=604800" + } + ] + } + ], + "rewrites": [ + { + "source": "/api/:path*", + "destination": "/api/:path*" + } + ] +} +``` + +--- + +### Environment Configuration + +#### .env.example + +```bash +# Google Fonts API Key +# Get from: https://developers.google.com/fonts/docs/developer_api +GOOGLE_FONTS_API_KEY=your_api_key_here + +# Application Configuration +APP_URL=https://glyphdiff.com +APP_NAME=GlyphDiff + +# Analytics (Optional) +VITE_GA_ID=G-XXXXXXXXXX +VITE_PLAUSIBLE_DOMAIN= glyphdiff.com + +# Feature Flags +VITE_ENABLE_CACHE=true +VITE_ENABLE_ANALYTICS=false +``` + +#### vite.config.ts + +```typescript +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()], + define: { + // Expose env variables to client code + 'import.meta.env.VITE_GOOGLE_FONTS_API_KEY': JSON.stringify(process.env.GOOGLE_FONTS_API_KEY), + } +}); +``` + +--- + +### DNS Configuration + +#### DNS Records for glyphdiff.com + +| Type | Name | Value | TTL | +|------|------|-------|-----| +| A | @ | 76.76.21.21 | 3600 | +| CNAME | www | cname.vercel-dns.com | 3600 | + +**Note:** 76.76.21.21 is Vercel's IPv4 address for custom domains. + +--- + +### Build and Deploy Commands + +```bash +# Install dependencies +npm install + +# Run development server +npm run dev + +# Build for production +npm run build + +# Preview production build +npm run preview + +# Run tests +npm test + +# Run linter +npm run lint + +# Format code +npm run format + +# Type check +npm run check +``` + +#### package.json Scripts + +```json +{ + "name": "glyphdiff", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check . && eslint .", + "format": "prettier --write .", + "test": "playwright test", + "test:unit": "vitest" + }, + "devDependencies": { + "@sveltejs/adapter-vercel": "^5.0.0", + "@sveltejs/kit": "^2.0.0", + "svelte": "^5.0.0", + "typescript": "^5.3.0", + "vite": "^5.0.0" + } +} +``` + +--- + +### Deployment Steps + +1. **Push to GitHub** + ```bash + git init + git add . + git commit -m "Initial commit" + git branch -M main + git remote add origin git@github.com:your-username/glyphdiff.git + git push -u origin main + ``` + +2. **Connect to Vercel** + - Go to [vercel.com](https://vercel.com) + - Click "Add New Project" + - Import from GitHub + - Configure environment variables: + - `GOOGLE_FONTS_API_KEY` + +3. **Configure Custom Domain** + - Go to Project Settings → Domains + - Add `glyphdiff.com` + - Add `www.glyphdiff.com` + - Update DNS records as shown above + +4. **Deploy** + - Push changes to GitHub + - Vercel automatically deploys on push to main + - Preview deployments for pull requests + +--- + +## Risk Assessment + +### Technical Risks + +| Risk | Probability | Impact | Mitigation | +|------|-------------|--------|------------| +| Google Fonts API rate limiting | Medium | High | Implement caching, batch requests, consider backend proxy | +| Browser font loading issues | Low | Medium | Use `document.fonts.load()`, implement fallback fonts | +| Poor mobile performance | Medium | High | Lazy loading, code splitting, test on real devices | +| Accessibility violations | Low | Medium | Regular audits with axe DevTools, keyboard testing | +| Vercel free tier limits | Low | Low | Monitor usage, easy upgrade path | + +### Learning Curve Risks + +| Risk | Probability | Impact | Mitigation | +|------|-------------|--------|------------| +| Difficulty with Svelte 5 runes | Medium | High | Start with simple components, practice with $state basics | +| TypeScript integration issues | Low | Medium | Use strict mode from start, leverage SvelteKit templates | +| CSS/Tailwind complexity | Low | Low | Use utility classes, avoid custom CSS where possible | +| Bits UI learning curve | Low | Medium | Read documentation, check examples, copy patterns | + +### External Dependency Risks + +| Risk | Probability | Impact | Mitigation | +|------|-------------|--------|------------| +| Google Fonts API changes | Low | High | Use TypeScript interfaces, watch for deprecation notices | +| Fontshare availability changes | Low | Low | Include as optional source, can be removed if needed | +| Vercel downtime | Very Low | Medium | Static files served from CDN, minimal impact | +| Bits UI breaking changes | Medium | Low | Lock to specific version, update carefully | + +--- + +## Success Metrics + +### Learning Success Metrics + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Svelte 5 proficiency | Comfortable with $state, $derived, $effect | Self-assessment quiz | +| SvelteKit understanding | Build 3+ routes with data loading | Route count | +| Store management | Implement 4 stores with persistence | Store count | +| Component library usage | Use 5+ Bits UI components | Component count | +| TypeScript adoption | 100% typed codebase | Lint check | + +### Project Success Metrics + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Functional font catalog | 1000+ fonts loaded | Google Fonts API response | +| Comparison tool working | 4 fonts simultaneously | Manual testing | +| Performance score | 90+ Lighthouse score | Lighthouse audit | +| Mobile responsiveness | Pass on 375px - 1280px | Browser DevTools | +| Accessibility | WCAG 2.1 AA compliant | axe DevTools | + +### Milestones + +- [ ] **Week 1 Complete:** Environment setup, basic components, types defined +- [ ] **Week 2 Complete:** Font catalog with data fetching, routing working +- [ ] **Week 3 Complete:** All stores implemented, URL state working +- [ ] **Week 4 Complete:** Polished UI, dark mode, responsive design +- [ ] **MVP Launch:** Application deployed to glyphdiff.com +- [ ] **Post-Launch:** Collect feedback, iterate on features + +--- + +## Appendix + +### Commands Reference + +#### Project Initialization + +```bash +# Create new SvelteKit project +npm create svelte@latest glyphdiff + +# Select options: +# - Which Svelte app template? Skeleton project +# - Add type checking? Yes, using TypeScript +# - Select additional options: ESLint, Prettier, Playwright + +# Navigate to project +cd glyphdiff + +# Install dependencies +npm install + +# Add Tailwind CSS +npx svelte-add@latest tailwindcss + +# Add Bits UI +npm install bits-ui + +# Install additional dependencies +npm install clsx tailwind-merge +``` + +#### Development Commands + +```bash +# Start development server +npm run dev + +# Open browser to localhost:5173 + +# Type check +npm run check + +# Type check in watch mode +npm run check:watch + +# Lint code +npm run lint + +# Format code +npm run format + +# Run Playwright tests +npm run test + +# Run unit tests (Vitest) +npm run test:unit +``` + +#### Build Commands + +```bash +# Build for production +npm run build + +# Preview production build +npm run preview + +# Clean build artifacts +rm -rf .svelte-kit build +``` + +#### Git Commands + +```bash +# Initialize git repo +git init + +# Create .gitignore +echo ".svelte-kit +node_modules +.env +.DS_Store +dist +build +.vercel" > .gitignore + +# Stage all files +git add . + +# Commit +git commit -m "Initial commit" + +# Create main branch +git branch -M main + +# Add remote +git remote add origin git@github.com:username/glyphdiff.git + +# Push to remote +git push -u origin main +``` + +--- + +### Learning Resources + +#### Official Documentation + +- [Svelte 5 Documentation](https://svelte-5-preview.vercel.app/) +- [SvelteKit Documentation](https://kit.svelte.dev/docs) +- [Tailwind CSS Documentation](https://tailwindcss.com/docs) +- [Bits UI Documentation](https://www.bits-ui.com/) + +#### React to Svelte Migration + +- [Svelte for React Developers](https://svelte.dev/docs/svelte-vs-react) +- [Svelte 5 Runes Guide](https://svelte-5-preview.vercel.app/docs/runes-api) +- [SvelteKit Data Loading](https://kit.svelte.dev/docs/load) + +#### Google Fonts API + +- [Google Fonts API Developer Guide](https://developers.google.com/fonts/docs/developer_api) +- [Google Fonts API Reference](https://developers.google.com/fonts/docs/reference/rest) + +#### Video Resources + +- [Svelte 5 Tutorial](https://www.youtube.com/results?search_query=svelte+5+tutorial) +- [SvelteKit Crash Course](https://www.youtube.com/results?search_query=sveltekit+crash+course) +- [Tailwind CSS Full Course](https://www.youtube.com/results?search_query=tailwind+css+full+course) + +#### Community + +- [Svelte Discord](https://svelte.dev/chat) +- [Svelte subreddit](https://reddit.com/r/sveltejs) +- [SvelteKit subreddit](https://reddit.com/r/sveltekit) + +--- + +### File Templates + +#### Component Template + +```svelte + + +
+

{prop}

+

Count: {count}, Doubled: {doubled}

+ +
+ + +``` + +#### Store Template + +```typescript +import type { FontMetadata } from '$lib/types'; +import { setCache, getCache } from '$lib/utils/cache'; + +class ExampleStore { + // State + items = $state([]); + loading = $state(false); + error = $state(null); + + // Derived state (computed automatically) + get count() { + return this.items.length; + } + + // Methods + async loadItems() { + this.loading = true; + this.error = null; + + try { + // Load from cache first + const cached = getCache('items'); + if (cached) { + this.items = cached; + return; + } + + // Fetch items + // this.items = await fetchItems(); + // setCache('items', this.items); + } catch (e) { + this.error = e as Error; + } finally { + this.loading = false; + } + } + + addItem(item: FontMetadata) { + this.items = [...this.items, item]; + } + + removeItem(id: string) { + this.items = this.items.filter(item => item.id !== id); + } +} + +// Export singleton instance +export const exampleStore = new ExampleStore(); +``` + +#### Load Function Template + +```typescript +// +page.ts or +page.server.ts +import type { PageLoad } from './$types'; + +export const load: PageLoad = async ({ fetch, depends, url }) => { + // Declare dependencies for invalidation + depends('app:data'); + + // Get URL parameters + const search = url.searchParams.get('search') || ''; + + try { + // Fetch data + const response = await fetch('/api/data'); + const data = await response.json(); + + return { + data, + search + }; + } catch (error) { + return { + data: [], + search, + error: error.message + }; + } +}; +``` + +--- + +### Troubleshooting + +#### Common Issues + +**Issue: Google Fonts API returns 403** + +```bash +# Solution: Check API key and rate limits +# 1. Verify API key is correct +# 2. Check API key restrictions (set to None for testing) +# 3. Verify you haven't exceeded daily quota +``` + +**Issue: Tailwind classes not working** + +```bash +# Solution: Check configuration +# 1. Ensure app.css imports tailwind directives +# 2. Restart dev server after config changes +# 3. Check vite.config.ts includes postcss config +``` + +**Issue: Svelte 5 syntax errors** + +```bash +# Solution: Ensure you're using latest Svelte +npm install svelte@next + +# If using VS Code, install Svelte extension +# Ensure it's the latest version for Svelte 5 support +``` + +**Issue: TypeScript errors with $state** + +```bash +# Solution: Update Svelte configuration +# Ensure svelte.config.js includes: +export default { + compilerOptions: { + runes: true + } +}; +``` + +**Issue: Vercel deployment fails** + +```bash +# Solution: Check build output +npm run build + +# Review error logs in Vercel dashboard +# Common issues: +# - Missing environment variables +# - TypeScript errors +# - Build timeout +``` + +--- + +### Performance Checklist + +- [ ] Implement lazy loading for fonts +- [ ] Use SvelteKit static generation where possible +- [ ] Optimize images (use webp format) +- [ ] Minimize JavaScript bundle size +- [ ] Use code splitting for routes +- [ ] Implement caching strategy +- [ ] Enable compression (handled by Vercel) +- [ ] Test Lighthouse scores +- [ ] Minimize layout shifts +- [ ] Use appropriate font formats (WOFF2) + +--- + +## Conclusion + +This project plan provides a comprehensive roadmap for building glyphdiff.com as a learning-focused MVP. The pure client-side approach prioritizes mastering Svelte 5 and SvelteKit while building a functional font comparison tool. + +### Next Steps + +1. Set up the development environment (Week 1 tasks) +2. Obtain Google Fonts API key +3. Configure environment variables +4. Begin implementing Svelte fundamentals + +Good luck on your React → Svelte journey! 🚀 + +--- + +*Document Version: 1.0* +*Last Updated: December 2025* +*Project: glyphdiff.com* diff --git a/docs/GIT_WORKFLOW.md b/docs/GIT_WORKFLOW.md new file mode 100644 index 0000000..1db49f9 --- /dev/null +++ b/docs/GIT_WORKFLOW.md @@ -0,0 +1,592 @@ +# Git Workflow and Branching Strategy + +This document outlines the git workflow, branching strategy, commit conventions, and code review guidelines for the glyphdiff.com project. + +## Table of Contents + +1. [Branching Strategy](#branching-strategy) +2. [Branch Naming Conventions](#branch-naming-conventions) +3. [Commit Message Conventions](#commit-message-conventions) +4. [Code Splitting and Merge Request Guidelines](#code-splitting-and-merge-request-guidelines) +5. [Branch Protection Rules](#branch-protection-rules) +6. [Git Hooks Configuration](#git-hooks-configuration) + +--- + +## Branching Strategy + +We use a Gitflow-inspired branching strategy adapted for our development workflow. This strategy provides a clear structure for feature development, bug fixes, and releases. + +### Branch Types + +#### 1. `main` Branch +- **Purpose**: Production-ready code only +- **Protection**: Highest level of protection +- **Rules**: + - Only merge `release/*` or `hotfix/*` branches into `main` + - No direct commits allowed + - Must pass all tests and code reviews + - Tags are created from this branch for releases (e.g., `v1.0.0`) + +#### 2. `develop` Branch +- **Purpose**: Integration branch for features +- **Protection**: High level of protection +- **Rules**: + - Merge `feature/*` and `fix/*` branches into `develop` + - No direct commits allowed + - Must pass all tests before merging + - Serves as the base for `release/*` branches + +#### 3. `feature/*` Branches +- **Purpose**: Develop new features +- **Naming**: `feature/feature-name` (e.g., `feature/font-catalog`, `feature/comparison-grid`) +- **Base**: Always branch from `develop` +- **Merge**: Merge back into `develop` via Merge Request (MR) +- **Rules**: + - One feature per branch + - Keep branches focused and small + - Delete after merging + +#### 4. `fix/*` Branches +- **Purpose**: Fix bugs discovered during development +- **Naming**: `fix/issue-description` (e.g., `fix/font-loading-error`, `fix/responsive-layout`) +- **Base**: Branch from `develop` +- **Merge**: Merge back into `develop` via MR +- **Rules**: + - One fix per branch + - Include tests that verify the fix + - Delete after merging + +#### 5. `hotfix/*` Branches +- **Purpose**: Critical fixes for production issues +- **Naming**: `hotfix/critical-fix` (e.g., `hotfix/security-patch`, `hotfix-production-crash`) +- **Base**: Branch from `main` +- **Merge**: Merge into both `main` and `develop` +- **Rules**: + - Use only for production emergencies + - Must be thoroughly tested + - Create a release tag after merging to `main` + +#### 6. `release/*` Branches +- **Purpose**: Prepare for a new release +- **Naming**: `release/vX.Y.Z` (e.g., `release/v1.0.0`, `release/v1.1.0`) +- **Base**: Branch from `develop` +- **Merge**: Merge into both `main` and `develop` +- **Rules**: + - Finalize release notes + - Update version numbers + - Perform final testing + - Create release tag after merging to `main` + +### Branch Workflow Diagram + +``` +main (production) + ↑ + │ hotfix/*, release/* + │ +develop (integration) + ↑ + │ feature/*, fix/* + │ +feature branches +``` + +--- + +## Branch Naming Conventions + +### Feature Branches +- Format: `feature/feature-name` +- Examples: + - `feature/font-catalog` + - `feature/comparison-grid` + - `feature/dark-mode` + - `feature/google-fonts-integration` + +### Fix Branches +- Format: `fix/issue-description` +- Examples: + - `fix/font-loading-error` + - `fix/responsive-layout` + - `fix/state-persistence` + - `fix-accessibility-contrast` + +### Hotfix Branches +- Format: `hotfix/critical-fix` +- Examples: + - `hotfix/security-patch` + - `hotfix-production-crash` + - `hotfix-api-rate-limit` + +### Release Branches +- Format: `release/vX.Y.Z` +- Examples: + - `release/v1.0.0` + - `release/v1.1.0` + - `release/v2.0.0` + +### Naming Guidelines +- Use lowercase letters +- Use hyphens to separate words +- Be descriptive but concise +- Avoid special characters (except hyphens) +- Keep names under 50 characters + +--- + +## Commit Message Conventions + +We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification. This format enables automated changelog generation and better commit history readability. + +### Format + +``` +(): + + + +