diff --git a/_go-learning/diagrams/01-system-architecture.md b/_go-learning/diagrams/01-system-architecture.md
new file mode 100644
index 0000000..ea26efb
--- /dev/null
+++ b/_go-learning/diagrams/01-system-architecture.md
@@ -0,0 +1,272 @@
+# System Architecture Diagram
+
+## Overall System Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ CV Website System │
+│ │
+│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
+│ │ Client │────────▶│ Server │───────▶│ Storage │ │
+│ │ Browser │◀────────│ (Bun/Go) │◀───────│ (Static) │ │
+│ └────────────┘ └────────────┘ └──────────────┘ │
+│ │ │ │ │
+│ │ HTMX │ Templates │ JSON │
+│ │ HTTP │ Rendering │ Files │
+│ │ │ │ │
+│ ▼ ▼ ▼ │
+│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
+│ │ UI/UX │ │ Handlers │ │ Data Models │ │
+│ │ Components │ │ Middleware │ │ CV/UI │ │
+│ └────────────┘ └────────────┘ └──────────────┘ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+## Layered Architecture
+
+```
+┌──────────────────────────────────────────────────────────────────┐
+│ Presentation Layer │
+│ ┌────────────────────────────────────────────────────────────┐ │
+│ │ HTML Templates + HTMX + Hyperscript + CSS │ │
+│ │ - Server-side rendering │ │
+│ │ - Hypermedia-driven architecture │ │
+│ │ - Progressive enhancement │ │
+│ └────────────────────────────────────────────────────────────┘ │
+└──────────────────────────────────────────────────────────────────┘
+ ↓
+┌──────────────────────────────────────────────────────────────────┐
+│ Application Layer │
+│ ┌────────────────────────────────────────────────────────────┐ │
+│ │ HTTP Handlers (internal/handlers/) │ │
+│ │ ┌──────────────┬──────────────┬──────────────┐ │ │
+│ │ │ cv_pages.go │ cv_htmx.go │ cv_pdf.go │ │ │
+│ │ │ Page render │ HTMX toggles │ PDF export │ │ │
+│ │ └──────────────┴──────────────┴──────────────┘ │ │
+│ └────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────────────┐ │
+│ │ Middleware Chain (internal/middleware/) │ │
+│ │ Recovery → Logger → SecurityHeaders → Preferences │ │
+│ └────────────────────────────────────────────────────────────┘ │
+└──────────────────────────────────────────────────────────────────┘
+ ↓
+┌──────────────────────────────────────────────────────────────────┐
+│ Business Layer │
+│ ┌────────────────────────────────────────────────────────────┐ │
+│ │ Data Models (internal/models/) │ │
+│ │ ┌──────────────┬──────────────┬──────────────┐ │ │
+│ │ │ cv/ │ ui/ │ Validation │ │ │
+│ │ │ CV data │ UI strings │ Rules │ │ │
+│ │ └──────────────┴──────────────┴──────────────┘ │ │
+│ └────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────────────┐ │
+│ │ Services (internal/pdf/, internal/lang/) │ │
+│ │ - PDF generation (chromedp) │ │
+│ │ - Language handling │ │
+│ │ - Template management │ │
+│ └────────────────────────────────────────────────────────────┘ │
+└──────────────────────────────────────────────────────────────────┘
+ ↓
+┌──────────────────────────────────────────────────────────────────┐
+│ Data Layer │
+│ ┌────────────────────────────────────────────────────────────┐ │
+│ │ Static Files │ │
+│ │ ┌──────────────┬──────────────┬──────────────┐ │ │
+│ │ │ data/ │ templates/ │ static/ │ │ │
+│ │ │ cv-*.json │ *.html │ css/js/ │ │ │
+│ │ │ ui-*.json │ partials/ │ images/ │ │ │
+│ │ └──────────────┴──────────────┴──────────────┘ │ │
+│ └────────────────────────────────────────────────────────────┘ │
+└──────────────────────────────────────────────────────────────────┘
+```
+
+## Component Interaction
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ HTTP Request Flow │
+└─────────────────────────────────────────────────────────────────┘
+
+Client Request
+ │
+ ├─→ Browser sends HTTP/HTMX request
+ │
+ ▼
+┌─────────────┐
+│ Router │ Match URL pattern
+│ (ServeMux) │ ├─ / → Home
+└─────────────┘ ├─ /cv → CVContent
+ │ ├─ /toggle/* → HTMX handlers
+ ▼ └─ /export/pdf → ExportPDF
+┌─────────────┐
+│ Middleware │ Execute middleware chain
+│ Chain │ ├─ Recovery (panic handler)
+└─────────────┘ ├─ Logger (request logging)
+ │ ├─ SecurityHeaders (CSP, HSTS)
+ ▼ └─ PreferencesMiddleware (cookies → context)
+┌─────────────┐
+│ Handler │ Process request
+│ Function │ ├─ Parse request (typed)
+└─────────────┘ ├─ Load data (models)
+ │ ├─ Prepare template data
+ ▼ └─ Render response
+┌─────────────┐
+│ Template │ Server-side rendering
+│ Rendering │ ├─ Load template
+└─────────────┘ ├─ Execute with data
+ │ └─ Generate HTML
+ ▼
+┌─────────────┐
+│ Response │ Send to client
+│ (HTML/PDF) │ └─ HTTP 200 OK
+└─────────────┘
+ │
+ ▼
+Client receives response
+```
+
+## Data Flow
+
+```
+┌────────────────────────────────────────────────────────────────┐
+│ Data Flow Diagram │
+└────────────────────────────────────────────────────────────────┘
+
+ Application Start
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Load Configuration (config.Load()) │
+ │ ├─ Server settings (port, timeouts) │
+ │ └─ Template settings (dir, hot reload) │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Initialize Template Manager │
+ │ ├─ Scan template directory │
+ │ ├─ Parse all templates │
+ │ └─ Cache compiled templates │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Initialize Handlers │
+ │ └─ CVHandler with template manager │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Setup Routes + Middleware │
+ │ └─ routes.Setup(cvHandler, ...) │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Start HTTP Server │
+ │ └─ Listen on :8080 │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ Ready for Requests
+
+─────────────────────────────────────────────────────────────────
+
+ Per-Request Flow
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Request arrives │
+ │ └─ GET /?lang=es │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ PreferencesMiddleware reads cookies │
+ │ ├─ cv-length = "long" │
+ │ ├─ cv-icons = "show" │
+ │ ├─ cv-language = "es" │
+ │ └─ Store in request context │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Handler.Home() called │
+ │ ├─ Get preferences from context │
+ │ ├─ Validate language │
+ │ └─ Call prepareTemplateData("es") │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Load Data │
+ │ ├─ cvmodel.LoadCV("es") │
+ │ │ └─ Read data/cv-es.json │
+ │ └─ uimodel.LoadUI("es") │
+ │ └─ Read data/ui-es.json │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Process Data │
+ │ ├─ Calculate durations │
+ │ ├─ Split skills into columns │
+ │ └─ Add SEO metadata │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Render Template │
+ │ ├─ Get cached template │
+ │ ├─ Execute with data map │
+ │ └─ Generate HTML │
+ └──────────────────────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────────────────────┐
+ │ Send Response │
+ │ └─ HTTP 200 + HTML body │
+ └──────────────────────────────────────────┘
+```
+
+## Package Dependencies
+
+```
+main.go
+ ├─→ internal/config
+ ├─→ internal/templates
+ ├─→ internal/handlers
+ │ ├─→ internal/middleware
+ │ ├─→ internal/models/cv
+ │ ├─→ internal/models/ui
+ │ ├─→ internal/pdf
+ │ └─→ internal/templates
+ ├─→ internal/routes
+ │ ├─→ internal/handlers
+ │ └─→ internal/middleware
+ └─→ internal/middleware
+
+internal/handlers/
+ ├─ cv.go (constructor)
+ ├─ cv_pages.go (renders)
+ ├─ cv_htmx.go (toggles)
+ ├─ cv_pdf.go (PDF export)
+ ├─ cv_helpers.go (utilities)
+ ├─ types.go (request/response)
+ └─ errors.go (error handling)
+
+internal/middleware/
+ └─ preferences.go (cookie → context)
+
+internal/models/
+ ├─ cv/ (CV data structures)
+ └─ ui/ (UI text structures)
+```
+
+## Related Diagrams
+
+- [Request Flow](./02-request-flow.md) - Detailed HTTP request lifecycle
+- [Middleware Chain](./03-middleware-chain.md) - Middleware execution order
+- [Handler Organization](./04-handler-organization.md) - Handler file structure
diff --git a/_go-learning/diagrams/02-request-flow.md b/_go-learning/diagrams/02-request-flow.md
new file mode 100644
index 0000000..b8f17fa
--- /dev/null
+++ b/_go-learning/diagrams/02-request-flow.md
@@ -0,0 +1,447 @@
+# Request Flow Diagram
+
+## Complete HTTP Request Lifecycle
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Full Request Lifecycle │
+└─────────────────────────────────────────────────────────────────┘
+
+Client Browser
+ │
+ ├─→ User visits /?lang=es&cv-length=long
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HTTP Request │
+│ ┌───────────────────────────────────────────────────────┐ │
+│ │ GET /?lang=es&cv-length=long HTTP/1.1 │ │
+│ │ Host: localhost:8080 │ │
+│ │ Cookie: cv-length=short; cv-icons=show │ │
+│ │ Accept: text/html │ │
+│ └───────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Go HTTP Server (net/http) │
+│ ├─ Port :8080 │
+│ ├─ ServeMux Router │
+│ └─ Match route pattern │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ MIDDLEWARE CHAIN (4 layers) │
+│ │
+│ 1. Recovery Middleware │
+│ └─→ Wraps entire request in defer/recover │
+│ │
+│ 2. Logger Middleware │
+│ └─→ Logs: [GET] / 127.0.0.1 │
+│ │
+│ 3. SecurityHeaders Middleware │
+│ └─→ Sets: CSP, X-Frame-Options, etc. │
+│ │
+│ 4. PreferencesMiddleware │
+│ ├─→ Reads cookies │
+│ ├─→ Migrates old values │
+│ └─→ Stores in request context │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ ROUTER (ServeMux) │
+│ ├─ Pattern: / │
+│ ├─ Match: Home handler │
+│ └─ Call: handler.Home(w, r) │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HANDLER: CVHandler.Home() │
+│ (internal/handlers/cv_pages.go) │
+│ │
+│ Step 1: Get preferences from context │
+│ ├─→ prefs := middleware.GetPreferences(r) │
+│ └─→ Result: CVLength="long", CVLanguage="es" │
+│ │
+│ Step 2: Validate language from query params │
+│ ├─→ lang := r.URL.Query().Get("lang") │
+│ ├─→ Fallback to: prefs.CVLanguage if empty │
+│ └─→ Validate: must be "en" or "es" │
+│ │
+│ Step 3: Prepare template data │
+│ ├─→ Call: h.prepareTemplateData(lang) │
+│ └─→ Returns: map with CV, UI, preferences │
+│ │
+│ Step 4: Render template │
+│ ├─→ Call: h.tmpl.Render(w, "index.html", data) │
+│ └─→ Returns: HTML response │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ TEMPLATE PREPARATION │
+│ (prepareTemplateData helper) │
+│ │
+│ 1. Load CV data │
+│ ├─→ cv, err := cvmodel.LoadCV(lang) │
+│ └─→ Read: data/cv-es.json │
+│ │
+│ 2. Load UI strings │
+│ ├─→ ui, err := uimodel.LoadUI(lang) │
+│ └─→ Read: data/ui-es.json │
+│ │
+│ 3. Calculate experience durations │
+│ └─→ For each experience: years/months │
+│ │
+│ 4. Split skills into columns │
+│ └─→ Distribute skills evenly across columns │
+│ │
+│ 5. Build data map │
+│ └─→ Return: CV, UI, preferences, SEO metadata │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ TEMPLATE RENDERING │
+│ (internal/templates/manager.go) │
+│ │
+│ 1. Get cached template │
+│ ├─→ tmpl := m.templates["index.html"] │
+│ └─→ (or reload if hot reload enabled) │
+│ │
+│ 2. Execute template │
+│ ├─→ tmpl.Execute(w, data) │
+│ ├─→ Process: {{.CV.Name}}, {{range .CV.Experience}} │
+│ └─→ Include partials: header, footer, sections │
+│ │
+│ 3. Generate HTML │
+│ └─→ Full HTML page with data injected │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ RESPONSE GENERATION │
+│ │
+│ Headers: │
+│ ├─ Content-Type: text/html; charset=utf-8 │
+│ ├─ Content-Security-Policy: [CSP rules] │
+│ ├─ X-Frame-Options: DENY │
+│ └─ Set-Cookie: cv-language=es; Path=/; Max-Age=... │
+│ │
+│ Body: │
+│ └─ │
+│ │
+│
... │
+│ │
+│ │
+│ │
+│ │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ LOGGER MIDDLEWARE (completion) │
+│ └─→ Log: Completed in 45ms (status: 200) │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+Client Browser receives HTML
+```
+
+## HTMX Toggle Request Flow
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ HTMX Toggle Request (Partial Update) │
+└─────────────────────────────────────────────────────────────┘
+
+User clicks toggle button
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HTMX Request │
+│ ┌───────────────────────────────────────────────────────┐ │
+│ │ GET /toggle/length?current=short HTTP/1.1 │ │
+│ │ HX-Request: true │ │
+│ │ HX-Trigger: toggle-length-btn │ │
+│ │ HX-Target: #main-content │ │
+│ └───────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+Middleware Chain (same as above)
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ ROUTER │
+│ ├─ Pattern: /toggle/length │
+│ └─ Handler: CVHandler.ToggleCVLength(w, r) │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HANDLER: CVHandler.ToggleCVLength() │
+│ (internal/handlers/cv_htmx.go) │
+│ │
+│ 1. Get current preferences │
+│ └─→ prefs := middleware.GetPreferences(r) │
+│ │
+│ 2. Toggle state │
+│ ├─→ currentLength := prefs.CVLength │
+│ └─→ newLength := "long" if current == "short" │
+│ │
+│ 3. Save new preference │
+│ └─→ middleware.SetPreferenceCookie(w, "cv-length", newLength) │
+│ │
+│ 4. Get language and prepare data │
+│ └─→ data := h.prepareTemplateData(lang) │
+│ │
+│ 5. Render partial template │
+│ └─→ h.tmpl.Render(w, "partials/cv_content.html", data) │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ PARTIAL TEMPLATE RENDERING │
+│ └─ Only renders: partials/cv_content.html │
+│ (Not full page, just the content section) │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HTMX Response │
+│ ┌───────────────────────────────────────────────────────┐ │
+│ │ HTTP/1.1 200 OK │ │
+│ │ Content-Type: text/html │ │
+│ │ Set-Cookie: cv-length=long; Path=/; Max-Age=... │ │
+│ │ │ │
+│ │ │ │
+│ │ │ │
+│ │
│ │
+│ └───────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+HTMX swaps content in #main-content
+(No page reload, instant update)
+```
+
+## PDF Export Request Flow
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ PDF Export Request Flow │
+└─────────────────────────────────────────────────────────────┘
+
+User clicks "Export PDF"
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HTTP POST Request │
+│ ┌───────────────────────────────────────────────────────┐ │
+│ │ POST /export/pdf HTTP/1.1 │ │
+│ │ Content-Type: application/json │ │
+│ │ Origin: http://localhost:8080 │ │
+│ │ │ │
+│ │ { │ │
+│ │ "lang": "es", │ │
+│ │ "length": "long", │ │
+│ │ "icons": "show", │ │
+│ │ "version": "with_skills" │ │
+│ │ } │ │
+│ └───────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+Global Middleware Chain
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ ROUTE-SPECIFIC MIDDLEWARE │
+│ │
+│ 1. OriginChecker │
+│ └─→ Verify same-origin request │
+│ │
+│ 2. RateLimiter │
+│ └─→ Check: 3 requests/min per IP │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HANDLER: CVHandler.ExportPDF() │
+│ (internal/handlers/cv_pdf.go) │
+│ │
+│ 1. Parse and validate request │
+│ ├─→ var req PDFExportRequest │
+│ ├─→ json.NewDecoder(r.Body).Decode(&req) │
+│ └─→ Validate: lang, length, icons, version │
+│ │
+│ 2. Render HTML for PDF │
+│ ├─→ Build data map with preferences │
+│ ├─→ Render to buffer: index.html template │
+│ └─→ Result: Full HTML page in memory │
+│ │
+│ 3. Generate PDF │
+│ ├─→ Call: pdf.GeneratePDF(htmlContent, pdfOptions) │
+│ └─→ Uses: chromedp to render HTML → PDF │
+│ │
+│ 4. Send PDF response │
+│ ├─→ Set headers: application/pdf │
+│ ├─→ Set filename: CV-[Name]-[lang].pdf │
+│ └─→ Write: PDF bytes to response │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ PDF GENERATION (chromedp) │
+│ (internal/pdf/generator.go) │
+│ │
+│ 1. Launch headless Chrome │
+│ └─→ chromedp.NewContext() │
+│ │
+│ 2. Navigate to data URL │
+│ └─→ Load HTML content │
+│ │
+│ 3. Wait for rendering │
+│ └─→ Ensure fonts, images loaded │
+│ │
+│ 4. Generate PDF │
+│ ├─→ chromedp.PrintToPDF() with options │
+│ ├─→ A4 size, margins, print background │
+│ └─→ Return: PDF bytes │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ PDF Response │
+│ ┌───────────────────────────────────────────────────────┐ │
+│ │ HTTP/1.1 200 OK │ │
+│ │ Content-Type: application/pdf │ │
+│ │ Content-Disposition: attachment; filename="CV-..." │ │
+│ │ Content-Length: 245678 │ │
+│ │ │ │
+│ │ [PDF binary data] │ │
+│ └───────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+Browser triggers download
+```
+
+## Error Handling Flow
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Error Handling Flow │
+└─────────────────────────────────────────────────────────────┘
+
+Request with invalid language
+ │
+ ▼
+Handler validation detects error
+ │
+ ├─→ Create: InvalidLanguageError("xx")
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ DomainError Created │
+│ ├─ Code: INVALID_LANGUAGE │
+│ ├─ Message: "Unsupported language: xx (use 'en' or 'es')" │
+│ ├─ StatusCode: 400 │
+│ └─ Field: "lang" │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Handler.HandleError(w, r, err) │
+│ (internal/handlers/errors.go) │
+│ │
+│ 1. Check if DomainError │
+│ └─→ Extract: code, message, status, field │
+│ │
+│ 2. Log error │
+│ └─→ log.Printf("[ERROR] %s: %s", code, message) │
+│ │
+│ 3. Build error response │
+│ ├─→ Create: ErrorInfo struct │
+│ └─→ Create: APIResponse wrapper │
+│ │
+│ 4. Send error response │
+│ ├─→ Set status: 400 Bad Request │
+│ ├─→ Set content-type: application/json │
+│ └─→ Write: JSON error response │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Error Response │
+│ { │
+│ "success": false, │
+│ "error": { │
+│ "code": "INVALID_LANGUAGE", │
+│ "message": "Unsupported language: xx", │
+│ "field": "lang" │
+│ } │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+Client receives error
+```
+
+## Performance Metrics
+
+```
+Typical Request Timings:
+
+┌─────────────────────────────────────────────────────┐
+│ Component Time % │
+├─────────────────────────────────────────────────────┤
+│ Middleware overhead ~350 μs 0.7% │
+│ ├─ Recovery ~10 ns │
+│ ├─ Logger ~100 μs │
+│ ├─ SecurityHeaders ~50 ns │
+│ └─ Preferences ~200 μs │
+│ │
+│ Handler processing ~500 μs 1.0% │
+│ ├─ Get preferences ~10 μs │
+│ ├─ Validate input ~50 μs │
+│ └─ Prepare data ~440 μs │
+│ │
+│ Data loading ~2 ms 4.0% │
+│ ├─ Load CV JSON ~1 ms │
+│ └─ Load UI JSON ~1 ms │
+│ │
+│ Template rendering ~45 ms 90% │
+│ ├─ Template execution ~40 ms │
+│ └─ HTML generation ~5 ms │
+│ │
+│ Response transmission ~2 ms 4.0% │
+├─────────────────────────────────────────────────────┤
+│ TOTAL REQUEST TIME ~50 ms 100% │
+└─────────────────────────────────────────────────────┘
+
+PDF Export Timings:
+
+┌─────────────────────────────────────────────────────┐
+│ Component Time % │
+├─────────────────────────────────────────────────────┤
+│ Middleware + Handler ~1 ms 0.1% │
+│ Template rendering ~50 ms 5% │
+│ Chrome launch/navigation ~200 ms 20% │
+│ PDF generation ~700 ms 70% │
+│ Response transmission ~50 ms 5% │
+├─────────────────────────────────────────────────────┤
+│ TOTAL PDF EXPORT TIME ~1 sec 100% │
+└─────────────────────────────────────────────────────┘
+```
+
+## Related Diagrams
+
+- [System Architecture](./01-system-architecture.md) - Overall system design
+- [Middleware Chain](./03-middleware-chain.md) - Middleware execution details
+- [Handler Organization](./04-handler-organization.md) - Handler structure
+- [Error Handling Flow](./06-error-handling-flow.md) - Error propagation details
diff --git a/_go-learning/diagrams/03-middleware-chain.md b/_go-learning/diagrams/03-middleware-chain.md
new file mode 100644
index 0000000..c6ce2ff
--- /dev/null
+++ b/_go-learning/diagrams/03-middleware-chain.md
@@ -0,0 +1,315 @@
+# Middleware Chain Diagram
+
+## Middleware Execution Order
+
+```
+HTTP Request
+ │
+ ▼
+┌────────────────────────────────────────────────────────────┐
+│ MIDDLEWARE CHAIN │
+│ │
+│ ┌──────────────────────────────────────────────────────┐ │
+│ │ 1. Recovery Middleware │ │
+│ │ - Catches panics │ │
+│ │ - Logs stack trace │ │
+│ │ - Returns 500 error │ │
+│ └──────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────────────────────────────────────────┐ │
+│ │ 2. Logger Middleware │ │
+│ │ - Logs request method, path, IP │ │
+│ │ - Measures request duration │ │
+│ │ - Logs response status │ │
+│ └──────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────────────────────────────────────────┐ │
+│ │ 3. SecurityHeaders Middleware │ │
+│ │ - Sets CSP header │ │
+│ │ - Sets X-Frame-Options │ │
+│ │ - Sets X-Content-Type-Options │ │
+│ │ - Sets Referrer-Policy │ │
+│ └──────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────────────────────────────────────────┐ │
+│ │ 4. PreferencesMiddleware │ │
+│ │ - Reads preference cookies │ │
+│ │ - Migrates old values │ │
+│ │ - Stores in request context │ │
+│ └──────────────────────────────────────────────────────┘ │
+│ │ │
+└───────────────────────────┼────────────────────────────────┘
+ ▼
+ ┌───────────────┐
+ │ Router │
+ │ (ServeMux) │
+ └───────────────┘
+ │
+ ▼
+ ┌───────────────┐
+ │ Handler │
+ └───────────────┘
+```
+
+## Detailed Flow
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Request Processing Flow │
+└─────────────────────────────────────────────────────────────────┘
+
+Client Request: GET /?lang=es
+ │
+ ▼
+╔═══════════════════════════════════════════════════════════╗
+║ RECOVERY MIDDLEWARE ║
+╠═══════════════════════════════════════════════════════════╣
+║ defer func() { ║
+║ if err := recover(); err != nil { ║
+║ log error + stack trace ║
+║ http.Error(w, "Internal Server Error", 500) ║
+║ } ║
+║ }() ║
+║ ║
+║ next.ServeHTTP(w, r) ────────────────┐ ║
+╚════════════════════════════════════════│══════════════════╝
+ │
+ ▼
+╔═══════════════════════════════════════════════════════════╗
+║ LOGGER MIDDLEWARE ║
+╠═══════════════════════════════════════════════════════════╣
+║ start := time.Now() ║
+║ log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr) ║
+║ ║
+║ wrapped := responseWriter wrapper ║
+║ next.ServeHTTP(wrapped, r) ──────────┐ ║
+║ │ ║
+║ duration := time.Since(start) │ ║
+║ log.Printf("Completed in %v (status: %d)", duration, status) ║
+╚═════════════════════════════════════════│════════════════╝
+ │
+ ▼
+╔═══════════════════════════════════════════════════════════╗
+║ SECURITY HEADERS MIDDLEWARE ║
+╠═══════════════════════════════════════════════════════════╣
+║ w.Header().Set("Content-Security-Policy", CSP_POLICY) ║
+║ w.Header().Set("X-Frame-Options", "DENY") ║
+║ w.Header().Set("X-Content-Type-Options", "nosniff") ║
+║ w.Header().Set("Referrer-Policy", "strict-origin") ║
+║ ║
+║ next.ServeHTTP(w, r) ────────────────┐ ║
+╚════════════════════════════════════════│══════════════════╝
+ │
+ ▼
+╔═══════════════════════════════════════════════════════════╗
+║ PREFERENCES MIDDLEWARE ║
+╠═══════════════════════════════════════════════════════════╣
+║ // Read cookies ║
+║ prefs := &Preferences{ ║
+║ CVLength: getCookie(r, "cv-length", "short"), ║
+║ CVIcons: getCookie(r, "cv-icons", "show"), ║
+║ CVLanguage: getCookie(r, "cv-language", "en"), ║
+║ CVTheme: getCookie(r, "cv-theme", "default"), ║
+║ ColorTheme: getCookie(r, "color-theme", "light"), ║
+║ } ║
+║ ║
+║ // Migrate old values ║
+║ if prefs.CVLength == "extended" { ║
+║ prefs.CVLength = "long" ║
+║ } ║
+║ ║
+║ // Store in context ║
+║ ctx := context.WithValue(r.Context(), PreferencesKey, prefs) ║
+║ next.ServeHTTP(w, r.WithContext(ctx)) ───┐ ║
+╚═════════════════════════════════════════════│════════════╝
+ │
+ ▼
+ ┌──────────────────┐
+ │ ROUTER HANDLER │
+ │ │
+ │ Matches route │
+ │ Calls handler │
+ └──────────────────┘
+ │
+ ▼
+ ┌──────────────────┐
+ │ HANDLER FUNC │
+ │ │
+ │ Processes req │
+ │ Returns resp │
+ └──────────────────┘
+```
+
+## Route-Specific Middleware
+
+```
+┌────────────────────────────────────────────────────────────────┐
+│ Route-Specific Middleware Example │
+│ (PDF Export Endpoint) │
+└────────────────────────────────────────────────────────────────┘
+
+Global Middleware Chain (all routes)
+ │
+ ├─ Recovery
+ ├─ Logger
+ ├─ SecurityHeaders
+ └─ PreferencesMiddleware
+ │
+ ▼
+ ┌─────────────────────────────────────────┐
+ │ Router (ServeMux) │
+ │ │
+ │ /export/pdf → pdfHandler (protected) │
+ │ │ │
+ │ ▼ │
+ │ ┌───────────────────────────┐ │
+ │ │ Route-Specific Chain │ │
+ │ │ │ │
+ │ │ 1. OriginChecker │ │
+ │ │ └─ Verify same origin│ │
+ │ │ │ │
+ │ │ 2. RateLimiter │ │
+ │ │ └─ 3 req/min per IP │ │
+ │ │ │ │
+ │ │ 3. ExportPDF Handler │ │
+ │ │ └─ Generate PDF │ │
+ │ └───────────────────────────┘ │
+ └─────────────────────────────────────────┘
+```
+
+## Middleware Wrapping Pattern
+
+```go
+// Middleware function signature
+type Middleware func(http.Handler) http.Handler
+
+// Wrapping example
+handler := routes.Setup(cvHandler, healthHandler)
+// Returns:
+// Recovery(
+// Logger(
+// SecurityHeaders(
+// PreferencesMiddleware(mux)
+// )
+// )
+// )
+
+// Execution flow (unwraps from outside to inside):
+Request
+ ↓ enters Recovery
+ ↓ enters Logger
+ ↓ enters SecurityHeaders
+ ↓ enters PreferencesMiddleware
+ ↓ enters mux/handler
+ ↓ handler processes
+ ↑ exits PreferencesMiddleware
+ ↑ exits SecurityHeaders
+ ↑ exits Logger (logs duration)
+ ↑ exits Recovery
+ ↑
+Response
+```
+
+## Context Flow
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Context Values Through Middleware │
+└─────────────────────────────────────────────────────────────┘
+
+Initial Request Context
+ │
+ ├─ Empty context.Background()
+ │
+ ▼
+PreferencesMiddleware
+ │
+ ├─ Reads cookies
+ ├─ Creates Preferences struct
+ └─ Adds to context
+ │
+ └─→ ctx = context.WithValue(r.Context(), PreferencesKey, prefs)
+ │
+ ▼
+ ┌──────────────────────────────────────┐
+ │ Modified Request Context │
+ │ │
+ │ PreferencesKey → &Preferences{ │
+ │ CVLength: "long", │
+ │ CVIcons: "show", │
+ │ CVLanguage: "es", │
+ │ CVTheme: "default", │
+ │ ColorTheme: "light", │
+ │ } │
+ └──────────────────────────────────────┘
+ │
+ ▼
+ Handler receives request with enriched context
+ │
+ ├─→ prefs := middleware.GetPreferences(r)
+ │ // Retrieves from context
+ │
+ └─→ lang := middleware.GetLanguage(r)
+ // Helper that calls GetPreferences
+```
+
+## Error Handling in Middleware
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Error Handling Flow │
+└────────────────────────────────────────────────────────────┘
+
+Recovery Middleware
+ │
+ │ Normal Flow:
+ │ ┌─────────────────────────────────────┐
+ │ │ next.ServeHTTP(w, r) │
+ │ │ ↓ │
+ │ │ Handler processes successfully │
+ │ │ ↓ │
+ │ │ Returns response │
+ │ └─────────────────────────────────────┘
+ │
+ │ Panic Flow:
+ │ ┌─────────────────────────────────────┐
+ │ │ next.ServeHTTP(w, r) │
+ │ │ ↓ │
+ │ │ Handler panics! │
+ │ │ ↓ │
+ │ │ defer recover() catches it │
+ │ │ ↓ │
+ │ │ log.Printf("PANIC: %v\\n%s", │
+ │ │ err, debug.Stack()) │
+ │ │ ↓ │
+ │ │ http.Error(w, "Internal Error", 500)│
+ │ └─────────────────────────────────────┘
+ │
+ ▼
+Response to client
+```
+
+## Performance Characteristics
+
+```
+Middleware Performance Impact (per request):
+
+Recovery: ~10 ns (defer overhead)
+Logger: ~100 μs (time measurements, string formatting)
+SecurityHeaders: ~50 ns (header setting)
+Preferences: ~200 μs (cookie parsing, context creation)
+
+Total overhead: ~350 μs per request
+Handler time: ~1-5 ms (template rendering)
+
+Total request: ~1.5-5.5 ms
+```
+
+## Related Diagrams
+
+- [System Architecture](./01-system-architecture.md) - Overall system design
+- [Request Flow](./02-request-flow.md) - Complete HTTP request lifecycle
+- [Error Handling](./06-error-handling-flow.md) - Error propagation
diff --git a/_go-learning/diagrams/04-handler-organization.md b/_go-learning/diagrams/04-handler-organization.md
new file mode 100644
index 0000000..1209b52
--- /dev/null
+++ b/_go-learning/diagrams/04-handler-organization.md
@@ -0,0 +1,389 @@
+# Handler Organization Diagram
+
+## Handler File Structure
+
+```
+internal/handlers/
+├── cv.go Constructor, shared state
+├── cv_pages.go Full page renders (Home, CVContent)
+├── cv_htmx.go HTMX partial updates (4 toggles)
+├── cv_pdf.go PDF export endpoint
+├── cv_helpers.go Shared utilities (prepareTemplateData, etc.)
+├── types.go Request/response types, validation
+├── errors.go Error handling, domain errors
+├── cv_pages_test.go Tests for page handlers
+├── cv_htmx_test.go Tests for HTMX handlers
+└── benchmarks_test.go Benchmark tests
+```
+
+## File Responsibilities
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ cv.go │
+│ (Constructor & State) │
+├──────────────────────────────────────────────────────────────┤
+│ type CVHandler struct { │
+│ tmpl *templates.Manager // Template renderer │
+│ host string // For absolute URLs │
+│ } │
+│ │
+│ func NewCVHandler(tmpl, host) *CVHandler │
+│ └─→ Constructor for handler initialization │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ cv_pages.go │
+│ (Full Page Renders) │
+├──────────────────────────────────────────────────────────────┤
+│ func (h *CVHandler) Home(w, r) │
+│ └─→ GET / │
+│ ├─ Get preferences from context │
+│ ├─ Validate language parameter │
+│ ├─ Prepare full template data │
+│ └─ Render: index.html (full page) │
+│ │
+│ func (h *CVHandler) CVContent(w, r) │
+│ └─→ GET /cv │
+│ ├─ Get preferences from context │
+│ ├─ Prepare template data │
+│ └─ Render: partials/cv_content.html │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ cv_htmx.go │
+│ (HTMX Partial Updates) │
+├──────────────────────────────────────────────────────────────┤
+│ func (h *CVHandler) ToggleCVLength(w, r) │
+│ └─→ GET /toggle/length?current=short │
+│ ├─ Get current preferences │
+│ ├─ Toggle: short ↔ long │
+│ ├─ Save cookie: cv-length │
+│ └─ Render: partials/cv_content.html │
+│ │
+│ func (h *CVHandler) ToggleCVIcons(w, r) │
+│ └─→ GET /toggle/icons?current=show │
+│ ├─ Toggle: show ↔ hide │
+│ ├─ Save cookie: cv-icons │
+│ └─ Render: partials/cv_content.html │
+│ │
+│ func (h *CVHandler) ToggleCVTheme(w, r) │
+│ └─→ GET /toggle/theme?current=default │
+│ ├─ Toggle: default ↔ minimal │
+│ ├─ Save cookie: cv-theme │
+│ └─ Render: partials/cv_content.html │
+│ │
+│ func (h *CVHandler) ToggleLanguage(w, r) │
+│ └─→ GET /toggle/language?current=en │
+│ ├─ Toggle: en ↔ es │
+│ ├─ Save cookie: cv-language │
+│ └─ Render: index.html (full page for i18n) │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ cv_pdf.go │
+│ (PDF Export) │
+├──────────────────────────────────────────────────────────────┤
+│ func (h *CVHandler) ExportPDF(w, r) │
+│ └─→ POST /export/pdf │
+│ ├─ Parse JSON request body │
+│ ├─ Validate: lang, length, icons, version │
+│ ├─ Render HTML to buffer │
+│ ├─ Generate PDF via chromedp │
+│ └─ Send PDF response with download header │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ cv_helpers.go │
+│ (Shared Utilities) │
+├──────────────────────────────────────────────────────────────┤
+│ func (h *CVHandler) prepareTemplateData(lang) map │
+│ └─→ Shared data preparation for all handlers │
+│ ├─ Load CV data: cvmodel.LoadCV(lang) │
+│ ├─ Load UI strings: uimodel.LoadUI(lang) │
+│ ├─ Calculate durations for experiences │
+│ ├─ Split skills into columns │
+│ ├─ Add SEO metadata │
+│ └─ Return: complete data map │
+│ │
+│ func (h *CVHandler) getFullURL(path) string │
+│ └─→ Build absolute URLs for SEO/PDF │
+│ └─ Return: http://host/path │
+│ │
+│ func validateLanguage(lang) error │
+│ └─→ Validate language parameter │
+│ └─ Check: lang in ["en", "es"] │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ types.go │
+│ (Request/Response Types) │
+├──────────────────────────────────────────────────────────────┤
+│ // Request Types │
+│ type PDFExportRequest struct { │
+│ Lang string `json:"lang" validate:"required,oneof=en es"` │
+│ Length string `json:"length" validate:"required,oneof=short long"` │
+│ Icons string `json:"icons" validate:"required,oneof=show hide"` │
+│ Version string `json:"version" validate:"required,oneof=with_skills clean"` │
+│ } │
+│ │
+│ // Response Types │
+│ type APIResponse struct { │
+│ Success bool `json:"success"` │
+│ Data interface{} `json:"data,omitempty"` │
+│ Error *ErrorInfo `json:"error,omitempty"` │
+│ Meta *MetaInfo `json:"meta,omitempty"` │
+│ } │
+│ │
+│ type ErrorInfo struct { │
+│ Code string `json:"code"` │
+│ Message string `json:"message"` │
+│ Field string `json:"field,omitempty"` │
+│ } │
+│ │
+│ type MetaInfo struct { │
+│ Timestamp time.Time `json:"timestamp"` │
+│ RequestID string `json:"request_id,omitempty"` │
+│ } │
+│ │
+│ // Constructor Functions │
+│ func NewAPIResponse(data interface{}) *APIResponse │
+│ func NewErrorResponse(code, message string) *APIResponse │
+│ func NewPDFExportRequest() *PDFExportRequest │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ errors.go │
+│ (Error Handling) │
+├──────────────────────────────────────────────────────────────┤
+│ // Error Codes │
+│ type ErrorCode string │
+│ const ( │
+│ ErrCodeInvalidLanguage = "INVALID_LANGUAGE" │
+│ ErrCodeInvalidLength = "INVALID_LENGTH" │
+│ ErrCodeInvalidIcons = "INVALID_ICONS" │
+│ ErrCodePDFGeneration = "PDF_GENERATION" │
+│ ErrCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED" │
+│ // ... 8 more error codes │
+│ ) │
+│ │
+│ // Domain Error Type │
+│ type DomainError struct { │
+│ Code ErrorCode │
+│ Message string │
+│ Err error │
+│ StatusCode int │
+│ Field string │
+│ } │
+│ │
+│ // Error Constructors │
+│ func InvalidLanguageError(lang) *DomainError │
+│ func InvalidLengthError(length) *DomainError │
+│ func PDFGenerationError(err) *DomainError │
+│ // ... 10 more constructors │
+│ │
+│ // Error Handler │
+│ func (h *CVHandler) HandleError(w, r, err) │
+│ └─→ Centralized error handling │
+│ ├─ Log error with code │
+│ ├─ Build error response │
+│ └─ Send JSON error │
+└──────────────────────────────────────────────────────────────┘
+```
+
+## Handler Dependencies
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Handler Dependencies │
+└────────────────────────────────────────────────────────────┘
+
+CVHandler
+ ├─→ internal/templates (template rendering)
+ │ └─→ Manager.Render(w, name, data)
+ │
+ ├─→ internal/models/cv (CV data)
+ │ └─→ LoadCV(lang) (*CV, error)
+ │
+ ├─→ internal/models/ui (UI strings)
+ │ └─→ LoadUI(lang) (*UI, error)
+ │
+ ├─→ internal/middleware (preferences)
+ │ ├─→ GetPreferences(r) *Preferences
+ │ ├─→ GetLanguage(r) string
+ │ ├─→ IsLongCV(r) bool
+ │ └─→ SetPreferenceCookie(w, name, value)
+ │
+ ├─→ internal/pdf (PDF generation)
+ │ └─→ GeneratePDF(html, options) ([]byte, error)
+ │
+ └─→ encoding/json (JSON parsing)
+ └─→ json.NewDecoder(r.Body).Decode(&req)
+```
+
+## Handler Call Flow
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Typical Handler Call Flow │
+└────────────────────────────────────────────────────────────┘
+
+Request arrives
+ │
+ ▼
+┌─────────────────────┐
+│ Middleware Chain │
+│ (preferences set) │
+└─────────────────────┘
+ │
+ ▼
+┌─────────────────────┐
+│ Handler Method │
+│ (cv_pages.go) │
+└─────────────────────┘
+ │
+ ├─→ middleware.GetPreferences(r)
+ │ └─→ Extract from request context
+ │
+ ├─→ validateLanguage(lang)
+ │ └─→ Check valid language
+ │
+ ├─→ h.prepareTemplateData(lang)
+ │ │ (cv_helpers.go)
+ │ │
+ │ ├─→ cvmodel.LoadCV(lang)
+ │ │ └─→ Read data/cv-{lang}.json
+ │ │
+ │ ├─→ uimodel.LoadUI(lang)
+ │ │ └─→ Read data/ui-{lang}.json
+ │ │
+ │ ├─→ calculateDurations()
+ │ │ └─→ For each experience
+ │ │
+ │ └─→ splitSkillsIntoColumns()
+ │ └─→ Distribute evenly
+ │
+ └─→ h.tmpl.Render(w, "index.html", data)
+ └─→ Execute template with data
+```
+
+## Handler Testing Structure
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Handler Tests │
+└────────────────────────────────────────────────────────────┘
+
+cv_pages_test.go
+ ├─ TestHome
+ │ ├─ Valid requests (en, es)
+ │ ├─ Invalid language
+ │ ├─ With preferences
+ │ └─ Default fallback
+ │
+ └─ TestCVContent
+ ├─ Valid language
+ ├─ With preferences
+ └─ Error handling
+
+cv_htmx_test.go
+ ├─ TestToggleCVLength
+ │ ├─ short → long
+ │ ├─ long → short
+ │ └─ Cookie setting
+ │
+ ├─ TestToggleCVIcons
+ │ ├─ show → hide
+ │ └─ hide → show
+ │
+ ├─ TestToggleCVTheme
+ │ └─ default ↔ minimal
+ │
+ └─ TestToggleLanguage
+ └─ en ↔ es
+
+benchmarks_test.go
+ ├─ BenchmarkHome
+ ├─ BenchmarkCVContent
+ ├─ BenchmarkToggleCVLength
+ ├─ BenchmarkToggleCVIcons
+ ├─ BenchmarkToggleCVTheme
+ ├─ BenchmarkToggleLanguage
+ ├─ BenchmarkExportPDF
+ ├─ BenchmarkPrepareTemplateData
+ ├─ BenchmarkValidateLanguage
+ ├─ BenchmarkErrorResponse
+ └─ BenchmarkNewAPIResponse
+```
+
+## Handler Pattern Summary
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Handler Organization Principles │
+└────────────────────────────────────────────────────────────┘
+
+1. SEPARATION BY RESPONSIBILITY
+ ├─ Pages: Full page renders
+ ├─ HTMX: Partial updates
+ ├─ PDF: Export functionality
+ └─ Helpers: Shared utilities
+
+2. TYPE SAFETY
+ ├─ Structured request types
+ ├─ Structured response types
+ └─ Validation tags
+
+3. ERROR HANDLING
+ ├─ Domain-specific errors
+ ├─ Error codes
+ └─ Centralized error handler
+
+4. TESTABILITY
+ ├─ Unit tests per file
+ ├─ Integration tests
+ └─ Benchmark tests
+
+5. DEPENDENCY INJECTION
+ ├─ Template manager injected
+ ├─ No global state
+ └─ Easy to mock
+
+6. MIDDLEWARE INTEGRATION
+ ├─ Preferences from context
+ ├─ Helper functions
+ └─ Clean separation
+```
+
+## Performance Profile
+
+```
+Handler Performance Characteristics:
+
+┌─────────────────────────────────────────────────────────┐
+│ Handler Time Allocations │
+├─────────────────────────────────────────────────────────┤
+│ Home() ~50 ms ~1200 allocs │
+│ CVContent() ~45 ms ~1100 allocs │
+│ ToggleCVLength() ~45 ms ~1100 allocs │
+│ ToggleCVIcons() ~45 ms ~1100 allocs │
+│ ToggleCVTheme() ~45 ms ~1100 allocs │
+│ ToggleLanguage() ~50 ms ~1200 allocs │
+│ ExportPDF() ~1000 ms ~5000 allocs │
+├─────────────────────────────────────────────────────────┤
+│ prepareTemplateData() ~2 ms ~50 allocs │
+│ validateLanguage() ~10 ns 0 allocs │
+└─────────────────────────────────────────────────────────┘
+
+Memory Profile:
+- Most allocations in template rendering (~90%)
+- JSON parsing minimal (<1%)
+- Helper functions optimized (zero-alloc where possible)
+```
+
+## Related Diagrams
+
+- [System Architecture](./01-system-architecture.md) - Overall system design
+- [Request Flow](./02-request-flow.md) - HTTP request lifecycle
+- [Middleware Chain](./03-middleware-chain.md) - Middleware execution
+- [Error Handling Flow](./06-error-handling-flow.md) - Error propagation
diff --git a/_go-learning/diagrams/05-data-models.md b/_go-learning/diagrams/05-data-models.md
new file mode 100644
index 0000000..82fef4f
--- /dev/null
+++ b/_go-learning/diagrams/05-data-models.md
@@ -0,0 +1,481 @@
+# Data Models Diagram
+
+## Data Model Overview
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ Data Model Structure │
+└──────────────────────────────────────────────────────────────┘
+
+internal/models/
+ ├── cv/ CV data structures
+ │ ├── cv.go Main CV model
+ │ ├── personal.go Personal information
+ │ ├── experience.go Work experience
+ │ ├── education.go Education history
+ │ ├── skills.go Technical skills
+ │ └── languages.go Language proficiency
+ │
+ └── ui/ UI text structures
+ ├── ui.go Main UI model
+ ├── sections.go Section titles
+ ├── buttons.go Button labels
+ └── messages.go User messages
+```
+
+## CV Data Model
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ CV Structure (cv/cv.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type CV struct { │
+│ Personal Personal `json:"personal"` │
+│ Summary string `json:"summary"` │
+│ Experience []Experience `json:"experience"` │
+│ Education []Education `json:"education"` │
+│ Skills Skills `json:"skills"` │
+│ Languages []Language `json:"languages"` │
+│ } │
+│ │
+│ Methods: │
+│ ├─ LoadCV(lang string) (*CV, error) │
+│ │ └─→ Read data/cv-{lang}.json │
+│ │ │
+│ ├─ Validate() error │
+│ │ └─→ Ensure all required fields present │
+│ │ │
+│ └─ CalculateDurations() │
+│ └─→ Calculate years/months for experiences │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Personal Information (cv/personal.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Personal struct { │
+│ Name string `json:"name"` │
+│ Title string `json:"title"` │
+│ Email string `json:"email"` │
+│ Phone string `json:"phone,omitempty"` │
+│ Location string `json:"location"` │
+│ Website string `json:"website,omitempty"` │
+│ LinkedIn string `json:"linkedin,omitempty"` │
+│ GitHub string `json:"github,omitempty"` │
+│ Photo string `json:"photo,omitempty"` │
+│ } │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Work Experience (cv/experience.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Experience struct { │
+│ Company string `json:"company"` │
+│ Position string `json:"position"` │
+│ Location string `json:"location"` │
+│ StartDate string `json:"start_date"` │
+│ EndDate string `json:"end_date,omitempty"` │
+│ Current bool `json:"current"` │
+│ Description string `json:"description"` │
+│ Highlights []string `json:"highlights"` │
+│ Duration string `json:"-"` // Calculated │
+│ } │
+│ │
+│ Methods: │
+│ ├─ CalculateDuration() string │
+│ │ ├─ Parse StartDate and EndDate │
+│ │ ├─ Calculate difference │
+│ │ └─ Return: "2 years 3 months" or "Present" │
+│ │ │
+│ └─ IsCurrent() bool │
+│ └─→ Check if EndDate is empty or Current flag │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Education (cv/education.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Education struct { │
+│ Institution string `json:"institution"` │
+│ Degree string `json:"degree"` │
+│ Field string `json:"field"` │
+│ Location string `json:"location"` │
+│ StartDate string `json:"start_date"` │
+│ EndDate string `json:"end_date,omitempty"` │
+│ GPA string `json:"gpa,omitempty"` │
+│ Honors []string `json:"honors,omitempty"` │
+│ } │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Skills (cv/skills.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Skills struct { │
+│ Technical []Skill `json:"technical"` │
+│ Soft []Skill `json:"soft"` │
+│ Tools []Skill `json:"tools"` │
+│ } │
+│ │
+│ type Skill struct { │
+│ Name string `json:"name"` │
+│ Level string `json:"level,omitempty"` │
+│ Icon string `json:"icon,omitempty"` │
+│ Category string `json:"category,omitempty"` │
+│ } │
+│ │
+│ Methods: │
+│ └─ SplitIntoColumns(numCols int) [][]Skill │
+│ └─→ Distribute skills evenly across columns │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Languages (cv/languages.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Language struct { │
+│ Name string `json:"name"` │
+│ Level string `json:"level"` │
+│ Proficiency string `json:"proficiency,omitempty"` │
+│ } │
+│ │
+│ Levels: Native, Fluent, Professional, Intermediate, Basic │
+└──────────────────────────────────────────────────────────────┘
+```
+
+## UI Data Model
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ UI Structure (ui/ui.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type UI struct { │
+│ Sections Sections `json:"sections"` │
+│ Buttons Buttons `json:"buttons"` │
+│ Messages Messages `json:"messages"` │
+│ Labels Labels `json:"labels"` │
+│ } │
+│ │
+│ Methods: │
+│ └─ LoadUI(lang string) (*UI, error) │
+│ └─→ Read data/ui-{lang}.json │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Section Titles (ui/sections.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Sections struct { │
+│ Summary string `json:"summary"` │
+│ Experience string `json:"experience"` │
+│ Education string `json:"education"` │
+│ Skills string `json:"skills"` │
+│ Languages string `json:"languages"` │
+│ Contact string `json:"contact"` │
+│ } │
+│ │
+│ Example (English): │
+│ { │
+│ "summary": "Professional Summary", │
+│ "experience": "Work Experience", │
+│ "education": "Education", │
+│ "skills": "Technical Skills", │
+│ "languages": "Languages", │
+│ "contact": "Contact Information" │
+│ } │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Button Labels (ui/buttons.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Buttons struct { │
+│ ExportPDF string `json:"export_pdf"` │
+│ ToggleLength string `json:"toggle_length"` │
+│ ToggleIcons string `json:"toggle_icons"` │
+│ ToggleTheme string `json:"toggle_theme"` │
+│ ToggleLanguage string `json:"toggle_language"` │
+│ Print string `json:"print"` │
+│ } │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ User Messages (ui/messages.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Messages struct { │
+│ Loading string `json:"loading"` │
+│ Error string `json:"error"` │
+│ Success string `json:"success"` │
+│ PDFGenerating string `json:"pdf_generating"` │
+│ PDFReady string `json:"pdf_ready"` │
+│ } │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Labels (ui/labels.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Labels struct { │
+│ ShortCV string `json:"short_cv"` │
+│ LongCV string `json:"long_cv"` │
+│ ShowIcons string `json:"show_icons"` │
+│ HideIcons string `json:"hide_icons"` │
+│ Light string `json:"light"` │
+│ Dark string `json:"dark"` │
+│ } │
+└──────────────────────────────────────────────────────────────┘
+```
+
+## Data Flow
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Data Flow │
+└────────────────────────────────────────────────────────────┘
+
+JSON Files (data/)
+ ├── cv-en.json English CV data
+ ├── cv-es.json Spanish CV data
+ ├── ui-en.json English UI strings
+ └── ui-es.json Spanish UI strings
+ │
+ ▼
+┌─────────────────────────┐
+│ LoadCV(lang) │
+│ LoadUI(lang) │
+│ (internal/models/) │
+└─────────────────────────┘
+ │
+ ├─→ Parse JSON
+ ├─→ Validate structure
+ └─→ Return typed structs
+ │
+ ▼
+┌─────────────────────────┐
+│ Handler │
+│ (internal/handlers/) │
+└─────────────────────────┐
+ │
+ ├─→ Calculate durations
+ ├─→ Split skills
+ └─→ Build template data map
+ │
+ ▼
+┌─────────────────────────┐
+│ Template Rendering │
+│ (templates/) │
+└─────────────────────────┘
+ │
+ ▼
+ HTML Response
+```
+
+## Example Data Structure
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Sample CV Data (data/cv-en.json) │
+└────────────────────────────────────────────────────────────┘
+
+{
+ "personal": {
+ "name": "John Doe",
+ "title": "Senior Software Engineer",
+ "email": "john@example.com",
+ "location": "San Francisco, CA",
+ "linkedin": "linkedin.com/in/johndoe",
+ "github": "github.com/johndoe"
+ },
+ "summary": "Experienced software engineer with 8+ years...",
+ "experience": [
+ {
+ "company": "Tech Corp",
+ "position": "Senior Software Engineer",
+ "location": "San Francisco, CA",
+ "start_date": "2020-01",
+ "end_date": "",
+ "current": true,
+ "description": "Leading backend development...",
+ "highlights": [
+ "Designed and implemented microservices architecture",
+ "Reduced API response time by 60%",
+ "Mentored 5 junior developers"
+ ]
+ }
+ ],
+ "education": [
+ {
+ "institution": "University of California",
+ "degree": "Bachelor of Science",
+ "field": "Computer Science",
+ "location": "Berkeley, CA",
+ "start_date": "2012-09",
+ "end_date": "2016-05",
+ "gpa": "3.8/4.0"
+ }
+ ],
+ "skills": {
+ "technical": [
+ {"name": "Go", "level": "Expert", "icon": "golang"},
+ {"name": "JavaScript", "level": "Advanced", "icon": "js"},
+ {"name": "Python", "level": "Intermediate", "icon": "python"}
+ ],
+ "tools": [
+ {"name": "Docker", "icon": "docker"},
+ {"name": "Kubernetes", "icon": "k8s"},
+ {"name": "Git", "icon": "git"}
+ ]
+ },
+ "languages": [
+ {"name": "English", "level": "Native"},
+ {"name": "Spanish", "level": "Fluent"}
+ ]
+}
+
+┌────────────────────────────────────────────────────────────┐
+│ Sample UI Data (data/ui-en.json) │
+└────────────────────────────────────────────────────────────┘
+
+{
+ "sections": {
+ "summary": "Professional Summary",
+ "experience": "Work Experience",
+ "education": "Education",
+ "skills": "Technical Skills",
+ "languages": "Languages"
+ },
+ "buttons": {
+ "export_pdf": "Export PDF",
+ "toggle_length": "Toggle Length",
+ "toggle_icons": "Toggle Icons",
+ "toggle_theme": "Toggle Theme",
+ "toggle_language": "Switch Language"
+ },
+ "messages": {
+ "loading": "Loading...",
+ "error": "An error occurred",
+ "pdf_generating": "Generating PDF...",
+ "pdf_ready": "PDF is ready for download"
+ },
+ "labels": {
+ "short_cv": "Short",
+ "long_cv": "Long",
+ "show_icons": "Show Icons",
+ "hide_icons": "Hide Icons"
+ }
+}
+```
+
+## Data Validation
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Validation Rules │
+└────────────────────────────────────────────────────────────┘
+
+CV Validation:
+├─ Personal
+│ ├─ Name: Required, non-empty
+│ ├─ Title: Required, non-empty
+│ ├─ Email: Required, valid email format
+│ └─ Location: Required, non-empty
+│
+├─ Experience
+│ ├─ Company: Required, non-empty
+│ ├─ Position: Required, non-empty
+│ ├─ StartDate: Required, valid date (YYYY-MM)
+│ └─ EndDate: Optional, must be after StartDate if present
+│
+├─ Education
+│ ├─ Institution: Required, non-empty
+│ ├─ Degree: Required, non-empty
+│ └─ Field: Required, non-empty
+│
+├─ Skills
+│ ├─ Name: Required, non-empty
+│ └─ Level: Optional, one of [Basic, Intermediate, Advanced, Expert]
+│
+└─ Languages
+ ├─ Name: Required, non-empty
+ └─ Level: Required, one of [Native, Fluent, Professional, Intermediate, Basic]
+
+UI Validation:
+├─ All section titles: Required, non-empty
+├─ All button labels: Required, non-empty
+└─ All messages: Required, non-empty
+```
+
+## Model Lifecycle
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Model Lifecycle │
+└────────────────────────────────────────────────────────────┘
+
+Application Start
+ │
+ └─→ Models NOT loaded (lazy loading)
+ │
+ ▼
+Request Arrives (lang=es)
+ │
+ ├─→ Handler calls LoadCV("es")
+ │ ├─ Check cache (if caching enabled)
+ │ ├─ Read data/cv-es.json
+ │ ├─ Parse JSON → CV struct
+ │ ├─ Validate struct
+ │ └─ Return *CV
+ │
+ ├─→ Handler calls LoadUI("es")
+ │ ├─ Read data/ui-es.json
+ │ ├─ Parse JSON → UI struct
+ │ └─ Return *UI
+ │
+ └─→ Handler processes data
+ ├─ Calculate durations
+ ├─ Split skills
+ └─ Render template
+
+Next Request (lang=es)
+ │
+ └─→ Models reloaded (no persistent cache)
+ (Each request loads fresh data for hot reload)
+```
+
+## Data Transformation
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Data Transformation Pipeline │
+└────────────────────────────────────────────────────────────┘
+
+JSON (static)
+ │
+ ├─ "start_date": "2020-01"
+ └─ "end_date": ""
+ │
+ ▼
+Go Struct (typed)
+ │
+ ├─ StartDate: "2020-01"
+ ├─ EndDate: ""
+ └─ Duration: "" (empty)
+ │
+ ▼
+Calculate Duration
+ │
+ ├─ Parse dates
+ ├─ Calculate difference
+ └─ Format: "3 years 2 months"
+ │
+ ▼
+Template Data (enriched)
+ │
+ ├─ StartDate: "2020-01"
+ ├─ EndDate: "Present"
+ └─ Duration: "3 years 2 months"
+ │
+ ▼
+HTML (rendered)
+ │
+ └─ 3 years 2 months
+```
+
+## Related Diagrams
+
+- [System Architecture](./01-system-architecture.md) - Overall system design
+- [Request Flow](./02-request-flow.md) - HTTP request lifecycle
+- [Template Rendering](./07-template-rendering.md) - Template processing
diff --git a/_go-learning/diagrams/06-error-handling-flow.md b/_go-learning/diagrams/06-error-handling-flow.md
new file mode 100644
index 0000000..3ae3f38
--- /dev/null
+++ b/_go-learning/diagrams/06-error-handling-flow.md
@@ -0,0 +1,492 @@
+# Error Handling Flow Diagram
+
+## Error Handling Architecture
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ Error Handling Architecture │
+└──────────────────────────────────────────────────────────────┘
+
+Error Types:
+├── Domain Errors Application-level business logic errors
+├── Validation Errors Input validation failures
+├── System Errors Infrastructure/system failures
+└── Panic Recovery Runtime panic handling
+```
+
+## Domain Error Structure
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ DomainError (internal/handlers/errors.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type DomainError struct { │
+│ Code ErrorCode // Enum error code │
+│ Message string // Human-readable message │
+│ Err error // Underlying error (if any) │
+│ StatusCode int // HTTP status code │
+│ Field string // Field that caused error │
+│ } │
+│ │
+│ func (e *DomainError) Error() string │
+│ func (e *DomainError) Unwrap() error │
+│ func (e *DomainError) WithField(field string) *DomainError │
+└──────────────────────────────────────────────────────────────┘
+
+┌──────────────────────────────────────────────────────────────┐
+│ Error Codes │
+├──────────────────────────────────────────────────────────────┤
+│ type ErrorCode string │
+│ │
+│ const ( │
+│ // Input Validation (400) │
+│ ErrCodeInvalidLanguage = "INVALID_LANGUAGE" │
+│ ErrCodeInvalidLength = "INVALID_LENGTH" │
+│ ErrCodeInvalidIcons = "INVALID_ICONS" │
+│ ErrCodeInvalidTheme = "INVALID_THEME" │
+│ ErrCodeInvalidVersion = "INVALID_VERSION" │
+│ ErrCodeValidationFailed = "VALIDATION_FAILED" │
+│ │
+│ // Resource Errors (404, 500) │
+│ ErrCodeDataNotFound = "DATA_NOT_FOUND" │
+│ ErrCodeTemplateNotFound = "TEMPLATE_NOT_FOUND" │
+│ ErrCodeTemplateError = "TEMPLATE_ERROR" │
+│ │
+│ // Processing Errors (500) │
+│ ErrCodePDFGeneration = "PDF_GENERATION" │
+│ ErrCodeInternalError = "INTERNAL_ERROR" │
+│ │
+│ // Rate Limiting (429) │
+│ ErrCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED" │
+│ │
+│ // Security (403) │
+│ ErrCodeOriginMismatch = "ORIGIN_MISMATCH" │
+│ ) │
+└──────────────────────────────────────────────────────────────┘
+```
+
+## Error Flow Patterns
+
+### Pattern 1: Validation Error
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Validation Error Flow │
+└────────────────────────────────────────────────────────────┘
+
+Request: GET /?lang=xx
+ │
+ ▼
+┌─────────────────────────┐
+│ Handler.Home() │
+│ (cv_pages.go) │
+└─────────────────────────┘
+ │
+ ├─→ lang := r.URL.Query().Get("lang")
+ │ // lang = "xx"
+ │
+ ├─→ err := validateLanguage(lang)
+ │ // "xx" not in ["en", "es"]
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ validateLanguage(lang) │
+│ (cv_helpers.go) │
+│ │
+│ if lang != "en" && lang != "es" { │
+│ return InvalidLanguageError(lang) │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ InvalidLanguageError(lang) │
+│ (errors.go) │
+│ │
+│ return NewDomainError( │
+│ ErrCodeInvalidLanguage, │
+│ fmt.Sprintf("Unsupported language: %s", lang), │
+│ http.StatusBadRequest, │
+│ ).WithField("lang") │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Handler receives error │
+│ │
+│ if err != nil { │
+│ h.HandleError(w, r, err) │
+│ return │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ HandleError(w, r, err) │
+│ (errors.go) │
+│ │
+│ 1. Cast to DomainError │
+│ domErr, ok := err.(*DomainError) │
+│ │
+│ 2. Log error │
+│ log.Printf("[ERROR] %s: %s", domErr.Code, domErr.Message) │
+│ │
+│ 3. Build response │
+│ response := NewErrorResponse( │
+│ string(domErr.Code), │
+│ domErr.Message, │
+│ ) │
+│ response.Error.Field = domErr.Field │
+│ │
+│ 4. Send JSON error │
+│ w.Header().Set("Content-Type", "application/json") │
+│ w.WriteHeader(domErr.StatusCode) │
+│ json.NewEncoder(w).Encode(response) │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Client Response │
+│ │
+│ HTTP/1.1 400 Bad Request │
+│ Content-Type: application/json │
+│ │
+│ { │
+│ "success": false, │
+│ "error": { │
+│ "code": "INVALID_LANGUAGE", │
+│ "message": "Unsupported language: xx (use 'en' or 'es')", │
+│ "field": "lang" │
+│ } │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Pattern 2: Data Loading Error
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Data Loading Error Flow │
+└────────────────────────────────────────────────────────────┘
+
+Handler calls LoadCV("es")
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ cvmodel.LoadCV(lang) │
+│ (internal/models/cv/cv.go) │
+│ │
+│ 1. Build file path │
+│ filePath := fmt.Sprintf("data/cv-%s.json", lang) │
+│ │
+│ 2. Read file │
+│ data, err := os.ReadFile(filePath) │
+│ if err != nil { │
+│ return nil, fmt.Errorf("failed to read CV: %w", err) │
+│ } │
+│ │
+│ 3. Parse JSON │
+│ var cv CV │
+│ err = json.Unmarshal(data, &cv) │
+│ if err != nil { │
+│ return nil, fmt.Errorf("failed to parse CV: %w", err) │
+│ } │
+│ │
+│ 4. Validate │
+│ if err := cv.Validate(); err != nil { │
+│ return nil, fmt.Errorf("invalid CV data: %w", err) │
+│ } │
+│ │
+│ 5. Return │
+│ return &cv, nil │
+└─────────────────────────────────────────────────────────────┘
+ │
+ │ Error Case: File not found
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Handler receives error │
+│ │
+│ cv, err := cvmodel.LoadCV(lang) │
+│ if err != nil { │
+│ // Wrap in DomainError │
+│ domErr := DataNotFoundError("CV", lang) │
+│ domErr.Err = err │
+│ h.HandleError(w, r, domErr) │
+│ return │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Client Response │
+│ │
+│ HTTP/1.1 500 Internal Server Error │
+│ Content-Type: application/json │
+│ │
+│ { │
+│ "success": false, │
+│ "error": { │
+│ "code": "DATA_NOT_FOUND", │
+│ "message": "CV data not found for language: es" │
+│ } │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Pattern 3: PDF Generation Error
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ PDF Generation Error Flow │
+└────────────────────────────────────────────────────────────┘
+
+Handler calls GeneratePDF()
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ pdf.GeneratePDF(htmlContent, options) │
+│ (internal/pdf/generator.go) │
+│ │
+│ 1. Create context │
+│ ctx, cancel := chromedp.NewContext(...) │
+│ defer cancel() │
+│ │
+│ 2. Launch Chrome │
+│ if err := chromedp.Run(ctx, ...); err != nil { │
+│ return nil, fmt.Errorf("chrome launch: %w", err) │
+│ } │
+│ │
+│ 3. Navigate and render │
+│ err := chromedp.Run(ctx, │
+│ chromedp.Navigate(dataURL), │
+│ chromedp.WaitReady("body"), │
+│ chromedp.PrintToPDF(&pdfBytes), │
+│ ) │
+│ if err != nil { │
+│ return nil, fmt.Errorf("PDF generation: %w", err) │
+│ } │
+│ │
+│ 4. Return PDF bytes │
+│ return pdfBytes, nil │
+└─────────────────────────────────────────────────────────────┘
+ │
+ │ Error Case: Chrome failed
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Handler.ExportPDF receives error │
+│ (cv_pdf.go) │
+│ │
+│ pdfBytes, err := pdf.GeneratePDF(htmlContent, opts) │
+│ if err != nil { │
+│ domErr := PDFGenerationError(err) │
+│ h.HandleError(w, r, domErr) │
+│ return │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Client Response │
+│ │
+│ HTTP/1.1 500 Internal Server Error │
+│ Content-Type: application/json │
+│ │
+│ { │
+│ "success": false, │
+│ "error": { │
+│ "code": "PDF_GENERATION", │
+│ "message": "Failed to generate PDF. Please try again." │
+│ } │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Pattern 4: Panic Recovery
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Panic Recovery Flow │
+└────────────────────────────────────────────────────────────┘
+
+Request enters system
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Recovery Middleware │
+│ (internal/middleware/recovery.go) │
+│ │
+│ func Recovery(next http.Handler) http.Handler { │
+│ return http.HandlerFunc(func(w, r) { │
+│ defer func() { │
+│ if err := recover(); err != nil { │
+│ // Capture panic │
+│ stack := debug.Stack() │
+│ │
+│ // Log with stack trace │
+│ log.Printf("PANIC: %v\n%s", err, stack) │
+│ │
+│ // Send error response │
+│ http.Error(w, │
+│ "Internal Server Error", │
+│ http.StatusInternalServerError) │
+│ } │
+│ }() │
+│ │
+│ // Continue to next handler │
+│ next.ServeHTTP(w, r) │
+│ }) │
+│ } │
+└─────────────────────────────────────────────────────────────┘
+ │
+ │ Normal flow: no panic
+ ├──────────────────────────────────┐
+ ▼ │ Panic occurs
+Handler executes ▼
+ │ ┌─────────────────────────────────┐
+ │ │ panic("something went wrong") │
+ │ └─────────────────────────────────┘
+ │ │
+ │ ▼
+ │ ┌─────────────────────────────────┐
+ │ │ defer recover() catches it │
+ │ │ ├─ Get stack trace │
+ │ │ ├─ Log error + stack │
+ │ │ └─ Send 500 response │
+ │ └─────────────────────────────────┘
+ ▼ │
+Response sent ▼
+ ┌─────────────────────────────────┐
+ │ Client receives 500 │
+ └─────────────────────────────────┘
+```
+
+## Error Response Formats
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Error Response Formats │
+└────────────────────────────────────────────────────────────┘
+
+Standard API Error (JSON):
+{
+ "success": false,
+ "error": {
+ "code": "INVALID_LANGUAGE",
+ "message": "Unsupported language: xx (use 'en' or 'es')",
+ "field": "lang"
+ }
+}
+
+Validation Error with Multiple Fields:
+{
+ "success": false,
+ "error": {
+ "code": "VALIDATION_FAILED",
+ "message": "Request validation failed",
+ "fields": {
+ "lang": "Invalid language",
+ "length": "Invalid length"
+ }
+ }
+}
+
+Internal Error (Generic):
+{
+ "success": false,
+ "error": {
+ "code": "INTERNAL_ERROR",
+ "message": "An unexpected error occurred. Please try again."
+ }
+}
+
+HTML Error Page (for page requests):
+
+
+Error
+
+ Oops! Something went wrong
+ We're sorry, but we couldn't process your request.
+ Error: INVALID_LANGUAGE
+ Go back home
+
+
+```
+
+## Error Logging
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Error Logging │
+└────────────────────────────────────────────────────────────┘
+
+Log Format:
+[ERROR] :
+[ERROR] Additional context:
+[ERROR] Stack trace (if panic):
+
+
+Examples:
+
+[ERROR] INVALID_LANGUAGE: Unsupported language: xx (use 'en' or 'es')
+[ERROR] Field: lang
+
+[ERROR] PDF_GENERATION: Failed to generate PDF
+[ERROR] Underlying error: chrome launch failed: context deadline exceeded
+
+[ERROR] PANIC: runtime error: invalid memory address
+[ERROR] Stack trace:
+goroutine 23 [running]:
+main.(*CVHandler).Home(...)
+ /app/internal/handlers/cv_pages.go:42
+...
+```
+
+## Error Handling Best Practices
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Error Handling Best Practices │
+└────────────────────────────────────────────────────────────┘
+
+1. USE TYPED ERRORS
+ ✓ return InvalidLanguageError(lang)
+ ✗ return errors.New("invalid language")
+
+2. WRAP ERRORS WITH CONTEXT
+ ✓ return fmt.Errorf("failed to load CV: %w", err)
+ ✗ return err
+
+3. LOG BEFORE RESPONDING
+ ✓ log.Printf("[ERROR] %s", err)
+ h.HandleError(w, r, err)
+ ✗ h.HandleError(w, r, err) // No logging
+
+4. USE APPROPRIATE STATUS CODES
+ ✓ 400 for validation errors
+ 404 for not found
+ 429 for rate limiting
+ 500 for server errors
+ ✗ Always returning 500
+
+5. DON'T LEAK INTERNAL DETAILS
+ ✓ "Failed to generate PDF. Please try again."
+ ✗ "chromedp: chrome crashed at line 42 in generator.go"
+
+6. PROVIDE ACTIONABLE MESSAGES
+ ✓ "Unsupported language: xx (use 'en' or 'es')"
+ ✗ "Invalid input"
+
+7. USE RECOVERY MIDDLEWARE
+ ✓ Catch all panics at middleware level
+ ✗ Let panics crash the server
+
+8. INCLUDE FIELD INFORMATION
+ ✓ error.WithField("lang")
+ ✗ Generic error without field context
+```
+
+## Related Diagrams
+
+- [Request Flow](./02-request-flow.md) - HTTP request lifecycle
+- [Middleware Chain](./03-middleware-chain.md) - Middleware execution
+- [Handler Organization](./04-handler-organization.md) - Handler structure
diff --git a/_go-learning/diagrams/07-template-rendering.md b/_go-learning/diagrams/07-template-rendering.md
new file mode 100644
index 0000000..e475b23
--- /dev/null
+++ b/_go-learning/diagrams/07-template-rendering.md
@@ -0,0 +1,541 @@
+# Template Rendering Diagram
+
+## Template System Architecture
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ Template System Architecture │
+└──────────────────────────────────────────────────────────────┘
+
+internal/templates/
+├── manager.go Template manager (caching, rendering)
+└── functions.go Custom template functions
+
+templates/
+├── index.html Main page template
+├── partials/ Reusable components
+│ ├── header.html
+│ ├── footer.html
+│ ├── cv_content.html
+│ ├── experience.html
+│ ├── education.html
+│ ├── skills.html
+│ └── languages.html
+└── layouts/ Layout templates
+ └── base.html
+```
+
+## Template Manager
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ Template Manager (internal/templates/manager.go) │
+├──────────────────────────────────────────────────────────────┤
+│ type Manager struct { │
+│ templates map[string]*template.Template │
+│ config *config.TemplateConfig │
+│ mu sync.RWMutex // Thread-safe access │
+│ } │
+│ │
+│ type TemplateConfig struct { │
+│ Dir string // templates/ │
+│ PartialsDir string // templates/partials/ │
+│ HotReload bool // Reload on every render │
+│ } │
+│ │
+│ Methods: │
+│ ├─ NewManager(config) (*Manager, error) │
+│ │ └─→ Initialize and load all templates │
+│ │ │
+│ ├─ Render(w, name, data) error │
+│ │ └─→ Execute template with data │
+│ │ │
+│ ├─ loadTemplates() error │
+│ │ └─→ Parse and cache all templates │
+│ │ │
+│ └─ reloadIfNeeded() error │
+│ └─→ Reload templates if hot reload enabled │
+└──────────────────────────────────────────────────────────────┘
+```
+
+## Template Loading Flow
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Template Loading Flow │
+└────────────────────────────────────────────────────────────┘
+
+Application Start
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ NewManager(config) │
+│ (internal/templates/manager.go) │
+│ │
+│ 1. Create manager │
+│ m := &Manager{ │
+│ templates: make(map[string]*template.Template), │
+│ config: config, │
+│ } │
+│ │
+│ 2. Load all templates │
+│ if err := m.loadTemplates(); err != nil { │
+│ return nil, err │
+│ } │
+│ │
+│ 3. Return manager │
+│ return m, nil │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ loadTemplates() │
+│ │
+│ 1. Scan template directory │
+│ files, err := filepath.Glob(config.Dir + "/*.html") │
+│ │
+│ 2. For each template file: │
+│ ├─ Create new template │
+│ │ tmpl := template.New(name) │
+│ │ │
+│ ├─ Add custom functions │
+│ │ tmpl.Funcs(customFunctions()) │
+│ │ │
+│ ├─ Parse main template │
+│ │ tmpl.ParseFiles(file) │
+│ │ │
+│ ├─ Parse partials │
+│ │ tmpl.ParseGlob(config.PartialsDir + "/*.html") │
+│ │ │
+│ └─ Cache template │
+│ m.templates[name] = tmpl │
+│ │
+│ 3. Log loaded templates │
+│ log.Printf("Loaded %d templates", len(m.templates)) │
+└─────────────────────────────────────────────────────────────┘
+```
+
+## Template Rendering Flow
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Template Rendering Flow │
+└────────────────────────────────────────────────────────────┘
+
+Handler calls Render()
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Manager.Render(w, "index.html", data) │
+│ (internal/templates/manager.go) │
+│ │
+│ 1. Lock for reading │
+│ m.mu.RLock() │
+│ defer m.mu.RUnlock() │
+│ │
+│ 2. Hot reload check │
+│ if m.config.HotReload { │
+│ m.mu.RUnlock() │
+│ m.mu.Lock() │
+│ m.loadTemplates() // Reload all templates │
+│ m.mu.Unlock() │
+│ m.mu.RLock() │
+│ } │
+│ │
+│ 3. Get template from cache │
+│ tmpl, ok := m.templates[name] │
+│ if !ok { │
+│ return fmt.Errorf("template not found: %s", name) │
+│ } │
+│ │
+│ 4. Execute template │
+│ err := tmpl.Execute(w, data) │
+│ if err != nil { │
+│ return fmt.Errorf("template execution: %w", err) │
+│ } │
+│ │
+│ 5. Return │
+│ return nil │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Template Execution │
+│ │
+│ 1. Parse template directives │
+│ {{.CV.Personal.Name}} │
+│ {{range .CV.Experience}}...{{end}} │
+│ {{template "partials/header.html" .}} │
+│ │
+│ 2. Execute custom functions │
+│ {{formatDate .StartDate}} │
+│ {{join .Highlights ", "}} │
+│ {{lower .CVLanguage}} │
+│ │
+│ 3. Include partials │
+│ {{template "partials/cv_content.html" .}} │
+│ {{template "partials/experience.html" .}} │
+│ │
+│ 4. Generate HTML │
+│ Write to http.ResponseWriter │
+└─────────────────────────────────────────────────────────────┘
+```
+
+## Template Hierarchy
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Template Hierarchy │
+└────────────────────────────────────────────────────────────┘
+
+index.html (Main Template)
+ │
+ ├─→ {{template "partials/header.html" .}}
+ │ └─→ Navigation, language toggle, theme toggle
+ │
+ ├─→ {{template "partials/cv_content.html" .}}
+ │ │
+ │ ├─→ {{template "partials/experience.html" .}}
+ │ │ └─→ {{range .CV.Experience}}
+ │ │ ├─ Company, position, dates
+ │ │ ├─ {{.Duration}} (calculated)
+ │ │ └─ {{range .Highlights}}
+ │ │
+ │ ├─→ {{template "partials/education.html" .}}
+ │ │ └─→ {{range .CV.Education}}
+ │ │ ├─ Institution, degree, field
+ │ │ └─ Dates, GPA, honors
+ │ │
+ │ ├─→ {{template "partials/skills.html" .}}
+ │ │ └─→ {{range .SkillsColumns}}
+ │ │ └─ {{range .}}
+ │ │ ├─ Skill name
+ │ │ ├─ Level badge
+ │ │ └─ Icon (if enabled)
+ │ │
+ │ └─→ {{template "partials/languages.html" .}}
+ │ └─→ {{range .CV.Languages}}
+ │ ├─ Language name
+ │ └─ Proficiency level
+ │
+ └─→ {{template "partials/footer.html" .}}
+ └─→ PDF export button, copyright
+```
+
+## Template Data Structure
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Template Data Structure │
+└────────────────────────────────────────────────────────────┘
+
+Data passed to templates:
+
+map[string]interface{}{
+ // CV Data
+ "CV": &cvmodel.CV{
+ Personal: cvmodel.Personal{
+ Name: "John Doe",
+ Title: "Senior Software Engineer",
+ Email: "john@example.com",
+ Location: "San Francisco, CA",
+ },
+ Experience: []cvmodel.Experience{
+ {
+ Company: "Tech Corp",
+ Position: "Senior Engineer",
+ StartDate: "2020-01",
+ EndDate: "",
+ Current: true,
+ Duration: "3 years 2 months", // Calculated
+ Highlights: []string{...},
+ },
+ },
+ Education: []cvmodel.Education{...},
+ Skills: cvmodel.Skills{...},
+ Languages: []cvmodel.Language{...},
+ },
+
+ // UI Strings
+ "UI": &uimodel.UI{
+ Sections: uimodel.Sections{
+ Summary: "Professional Summary",
+ Experience: "Work Experience",
+ Education: "Education",
+ Skills: "Technical Skills",
+ Languages: "Languages",
+ },
+ Buttons: uimodel.Buttons{...},
+ Messages: uimodel.Messages{...},
+ },
+
+ // User Preferences
+ "Preferences": &middleware.Preferences{
+ CVLength: "long",
+ CVIcons: "show",
+ CVLanguage: "es",
+ CVTheme: "default",
+ ColorTheme: "light",
+ },
+
+ // Processed Data
+ "SkillsColumns": [][]cvmodel.Skill{
+ []cvmodel.Skill{...}, // Column 1
+ []cvmodel.Skill{...}, // Column 2
+ []cvmodel.Skill{...}, // Column 3
+ },
+
+ // SEO Metadata
+ "PageTitle": "John Doe - Senior Software Engineer",
+ "MetaDescription": "Professional CV of John Doe...",
+ "CanonicalURL": "http://localhost:8080/",
+ "OGImage": "http://localhost:8080/static/images/og-image.png",
+}
+```
+
+## Custom Template Functions
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Custom Template Functions │
+│ (internal/templates/functions.go) │
+└────────────────────────────────────────────────────────────┘
+
+template.FuncMap{
+ // String manipulation
+ "lower": strings.ToLower,
+ "upper": strings.ToUpper,
+ "title": strings.Title,
+
+ // Date formatting
+ "formatDate": func(date string) string {
+ if date == "" {
+ return "Present"
+ }
+ t, _ := time.Parse("2006-01", date)
+ return t.Format("Jan 2006")
+ },
+
+ // Array operations
+ "join": strings.Join,
+ "split": strings.Split,
+
+ // Math
+ "add": func(a, b int) int {
+ return a + b
+ },
+ "multiply": func(a, b int) int {
+ return a * b
+ },
+
+ // Conditional helpers
+ "eq": func(a, b interface{}) bool {
+ return a == b
+ },
+ "ne": func(a, b interface{}) bool {
+ return a != b
+ },
+
+ // HTML safety
+ "safe": func(s string) template.HTML {
+ return template.HTML(s)
+ },
+}
+
+Usage in templates:
+
+{{formatDate .StartDate}}
+// "2020-01" → "Jan 2020"
+
+{{join .Highlights ", "}}
+// ["foo", "bar"] → "foo, bar"
+
+{{if eq .CVLength "long"}}
+
+{{end}}
+
+{{.Description | safe}}
+// Render HTML without escaping
+```
+
+## Template Conditionals
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Template Conditionals │
+└────────────────────────────────────────────────────────────┘
+
+Show/Hide based on CV length:
+{{if eq .Preferences.CVLength "long"}}
+
+
+ {{range .Highlights}}
+
{{.}}
+ {{end}}
+
+{{end}}
+
+Show/Hide based on icons preference:
+{{if eq .Preferences.CVIcons "show"}}
+
+{{end}}
+
+Conditional classes:
+
+ ...
+
+
+Language-specific content:
+{{if eq .Preferences.CVLanguage "es"}}
+ Experiencia Profesional
+{{else}}
+ Professional Experience
+{{end}}
+
+Current vs. past experience:
+{{if .Current}}
+ Present
+{{else}}
+ {{formatDate .EndDate}}
+{{end}}
+```
+
+## Template Performance
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Template Performance │
+└────────────────────────────────────────────────────────────┘
+
+Performance Characteristics:
+
+┌─────────────────────────────────────────────────────────┐
+│ Operation Time Notes │
+├─────────────────────────────────────────────────────────┤
+│ Template Loading ~50ms On app start │
+│ ├─ Parse templates ~40ms Compile Go templates│
+│ └─ Cache templates ~10ms Store in map │
+│ │
+│ Template Rendering ~45ms Per request │
+│ ├─ Template lookup ~10ns Map access │
+│ ├─ Template execute ~40ms Main cost │
+│ ├─ Partial includes ~5ms Include partials │
+│ └─ Function calls ~100μs Custom functions │
+│ │
+│ Hot Reload ~50ms If enabled │
+│ └─ Reload all ~50ms Parse again │
+└─────────────────────────────────────────────────────────┘
+
+Optimization Strategies:
+1. Template Caching
+ └─→ Pre-compile templates at startup
+ Serve from memory cache
+
+2. Hot Reload (Development Only)
+ └─→ Reload on every request for dev
+ Disable in production for speed
+
+3. Minimize Partials
+ └─→ Balance reusability vs. overhead
+ Each partial adds ~1ms
+
+4. Pre-calculate Data
+ └─→ Calculate durations in handler
+ Split skills before rendering
+
+5. Use Buffer Pool
+ └─→ Reuse buffers for rendering
+ Reduce allocations
+```
+
+## Template Error Handling
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Template Error Handling │
+└────────────────────────────────────────────────────────────┘
+
+Error Types:
+
+1. Template Not Found
+ Error: template "foo.html" not found
+ Cause: Template doesn't exist in cache
+ Fix: Create template file, reload
+
+2. Parse Error
+ Error: template: index.html:42: unexpected "}"
+ Cause: Syntax error in template
+ Fix: Check template syntax
+
+3. Execution Error
+ Error: template: executing "index.html": map has no entry for key "Foo"
+ Cause: Missing data in template data map
+ Fix: Ensure all required data passed
+
+4. Function Error
+ Error: template: function "unknownFunc" not defined
+ Cause: Custom function not registered
+ Fix: Register function in FuncMap
+
+Error Flow:
+
+Template Error
+ │
+ ├─→ Logged with stack trace
+ │ log.Printf("[ERROR] Template: %v", err)
+ │
+ ├─→ Wrapped in DomainError
+ │ TemplateError(err)
+ │
+ └─→ Sent as 500 response
+ {
+ "success": false,
+ "error": {
+ "code": "TEMPLATE_ERROR",
+ "message": "Failed to render page"
+ }
+ }
+```
+
+## Hot Reload Flow
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Hot Reload Flow │
+│ (Development Mode) │
+└────────────────────────────────────────────────────────────┘
+
+Developer edits template
+ │
+ ▼
+Next request arrives
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Render() called │
+│ │
+│ if m.config.HotReload { │
+│ // Reload all templates │
+│ m.mu.Lock() │
+│ m.loadTemplates() │
+│ m.mu.Unlock() │
+│ } │
+│ │
+│ // Use fresh templates │
+│ tmpl := m.templates[name] │
+│ tmpl.Execute(w, data) │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ▼
+Page rendered with updated template
+(No server restart needed)
+
+⚠️ Hot reload disabled in production for performance
+```
+
+## Related Diagrams
+
+- [System Architecture](./01-system-architecture.md) - Overall system design
+- [Request Flow](./02-request-flow.md) - HTTP request lifecycle
+- [Handler Organization](./04-handler-organization.md) - Handler structure
+- [Data Models](./05-data-models.md) - Data structures
diff --git a/_go-learning/diagrams/08-pdf-generation.md b/_go-learning/diagrams/08-pdf-generation.md
new file mode 100644
index 0000000..4b9c47f
--- /dev/null
+++ b/_go-learning/diagrams/08-pdf-generation.md
@@ -0,0 +1,529 @@
+# PDF Generation Diagram
+
+## PDF Export Architecture
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ PDF Export Architecture │
+└──────────────────────────────────────────────────────────────┘
+
+Client (Browser)
+ │
+ ├─→ User clicks "Export PDF"
+ │
+ ▼
+┌─────────────────────────┐
+│ Modal with options │
+│ ├─ Language (en/es) │
+│ ├─ Length (short/long) │
+│ ├─ Icons (show/hide) │
+│ └─ Version (with/clean)│
+└─────────────────────────┘
+ │
+ ▼
+POST /export/pdf
+ │
+ ▼
+┌─────────────────────────┐
+│ Route Middleware │
+│ ├─ OriginChecker │
+│ └─ RateLimiter │
+└─────────────────────────┘
+ │
+ ▼
+┌─────────────────────────┐
+│ CVHandler.ExportPDF() │
+│ (cv_pdf.go) │
+└─────────────────────────┘
+ │
+ ▼
+┌─────────────────────────┐
+│ PDF Generator │
+│ (internal/pdf/) │
+└─────────────────────────┘
+ │
+ ▼
+┌─────────────────────────┐
+│ Chromedp │
+│ (Headless Chrome) │
+└─────────────────────────┘
+ │
+ ▼
+PDF Response
+```
+
+## PDF Generation Flow
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ PDF Generation Flow │
+└────────────────────────────────────────────────────────────┘
+
+1. REQUEST VALIDATION
+ ┌─────────────────────────────────────────────────────────┐
+ │ Handler.ExportPDF(w, r) │
+ │ (internal/handlers/cv_pdf.go) │
+ │ │
+ │ // Parse JSON request │
+ │ var req PDFExportRequest │
+ │ err := json.NewDecoder(r.Body).Decode(&req) │
+ │ │
+ │ // Validate fields │
+ │ if req.Lang != "en" && req.Lang != "es" { │
+ │ return InvalidLanguageError(req.Lang) │
+ │ } │
+ │ if req.Length != "short" && req.Length != "long" { │
+ │ return InvalidLengthError(req.Length) │
+ │ } │
+ └─────────────────────────────────────────────────────────┘
+
+2. HTML GENERATION
+ ┌─────────────────────────────────────────────────────────┐
+ │ // Build template data │
+ │ data := map[string]interface{}{ │
+ │ "CV": cv, │
+ │ "UI": ui, │
+ │ "Preferences": &middleware.Preferences{ │
+ │ CVLength: req.Length, │
+ │ CVIcons: req.Icons, │
+ │ CVLanguage: req.Lang, │
+ │ }, │
+ │ "SkillsColumns": skillColumns, │
+ │ "IsPDF": true, // PDF-specific flag │
+ │ } │
+ │ │
+ │ // Render to buffer │
+ │ var buf bytes.Buffer │
+ │ err := h.tmpl.Render(&buf, "index.html", data) │
+ │ htmlContent := buf.String() │
+ └─────────────────────────────────────────────────────────┘
+
+3. PDF OPTIONS
+ ┌─────────────────────────────────────────────────────────┐
+ │ opts := pdf.Options{ │
+ │ PaperSize: pdf.A4, │
+ │ Orientation: pdf.Portrait, │
+ │ MarginTop: "1cm", │
+ │ MarginRight: "1cm", │
+ │ MarginBottom: "1cm", │
+ │ MarginLeft: "1cm", │
+ │ PrintBackground: true, // Include colors │
+ │ Scale: 1.0, │
+ │ Landscape: false, │
+ │ } │
+ └─────────────────────────────────────────────────────────┘
+
+4. PDF GENERATION
+ ┌─────────────────────────────────────────────────────────┐
+ │ pdfBytes, err := pdf.GeneratePDF(htmlContent, opts) │
+ │ if err != nil { │
+ │ return PDFGenerationError(err) │
+ │ } │
+ └─────────────────────────────────────────────────────────┘
+
+5. RESPONSE
+ ┌─────────────────────────────────────────────────────────┐
+ │ // Build filename │
+ │ filename := fmt.Sprintf("CV-%s-%s.pdf", │
+ │ cv.Personal.Name, req.Lang) │
+ │ filename = strings.ReplaceAll(filename, " ", "-") │
+ │ │
+ │ // Set headers │
+ │ w.Header().Set("Content-Type", "application/pdf") │
+ │ w.Header().Set("Content-Disposition", │
+ │ fmt.Sprintf("attachment; filename=%s", filename)) │
+ │ w.Header().Set("Content-Length", │
+ │ fmt.Sprintf("%d", len(pdfBytes))) │
+ │ │
+ │ // Send PDF │
+ │ w.WriteHeader(http.StatusOK) │
+ │ w.Write(pdfBytes) │
+ └─────────────────────────────────────────────────────────┘
+```
+
+## Chromedp PDF Generation
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Chromedp PDF Generation (internal/pdf/generator.go) │
+└────────────────────────────────────────────────────────────┘
+
+func GeneratePDF(htmlContent string, opts Options) ([]byte, error) {
+
+ 1. CREATE CONTEXT
+ ┌──────────────────────────────────────────────────────┐
+ │ // Allocate context │
+ │ ctx, cancel := chromedp.NewContext( │
+ │ context.Background(), │
+ │ chromedp.WithLogf(log.Printf), │
+ │ ) │
+ │ defer cancel() │
+ │ │
+ │ // Set timeout │
+ │ ctx, cancel = context.WithTimeout(ctx, 30*time.Second) │
+ │ defer cancel() │
+ └──────────────────────────────────────────────────────┘
+
+ 2. PREPARE HTML
+ ┌──────────────────────────────────────────────────────┐
+ │ // Wrap HTML in data URL │
+ │ dataURL := fmt.Sprintf( │
+ │ "data:text/html;base64,%s", │
+ │ base64.StdEncoding.EncodeToString( │
+ │ []byte(htmlContent), │
+ │ ), │
+ │ ) │
+ └──────────────────────────────────────────────────────┘
+
+ 3. LAUNCH CHROME
+ ┌──────────────────────────────────────────────────────┐
+ │ // Run Chrome tasks │
+ │ var pdfBytes []byte │
+ │ err := chromedp.Run(ctx, │
+ │ // Navigate to data URL │
+ │ chromedp.Navigate(dataURL), │
+ │ │
+ │ // Wait for body to be ready │
+ │ chromedp.WaitReady("body", chromedp.ByQuery), │
+ │ │
+ │ // Wait for fonts and images │
+ │ chromedp.Sleep(500 * time.Millisecond), │
+ │ │
+ │ // Generate PDF │
+ │ chromedp.ActionFunc(func(ctx context.Context) error { │
+ │ buf, _, err := page.PrintToPDF(). │
+ │ WithPrintBackground(opts.PrintBackground). │
+ │ WithPaperWidth(opts.PaperWidth). │
+ │ WithPaperHeight(opts.PaperHeight). │
+ │ WithMarginTop(opts.MarginTop). │
+ │ WithMarginRight(opts.MarginRight). │
+ │ WithMarginBottom(opts.MarginBottom). │
+ │ WithMarginLeft(opts.MarginLeft). │
+ │ WithScale(opts.Scale). │
+ │ Do(ctx) │
+ │ if err != nil { │
+ │ return err │
+ │ } │
+ │ pdfBytes = buf │
+ │ return nil │
+ │ }), │
+ │ ) │
+ │ │
+ │ if err != nil { │
+ │ return nil, fmt.Errorf("chromedp: %w", err) │
+ │ } │
+ └──────────────────────────────────────────────────────┘
+
+ 4. RETURN PDF
+ ┌──────────────────────────────────────────────────────┐
+ │ return pdfBytes, nil │
+ └──────────────────────────────────────────────────────┘
+}
+```
+
+## PDF-Specific Template Adjustments
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ PDF-Specific Template Adjustments │
+└────────────────────────────────────────────────────────────┘
+
+In templates/index.html:
+
+{{if .IsPDF}}
+
+
+{{else}}
+
+
+{{end}}
+```
+
+## PDF Request/Response Example
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ PDF Request/Response Example │
+└────────────────────────────────────────────────────────────┘
+
+REQUEST:
+POST /export/pdf HTTP/1.1
+Host: localhost:8080
+Content-Type: application/json
+Origin: http://localhost:8080
+
+{
+ "lang": "es",
+ "length": "long",
+ "icons": "show",
+ "version": "with_skills"
+}
+
+RESPONSE (Success):
+HTTP/1.1 200 OK
+Content-Type: application/pdf
+Content-Disposition: attachment; filename="CV-John-Doe-es.pdf"
+Content-Length: 245678
+
+[PDF binary data]
+
+RESPONSE (Error - Invalid Language):
+HTTP/1.1 400 Bad Request
+Content-Type: application/json
+
+{
+ "success": false,
+ "error": {
+ "code": "INVALID_LANGUAGE",
+ "message": "Unsupported language: xx (use 'en' or 'es')",
+ "field": "lang"
+ }
+}
+
+RESPONSE (Error - Rate Limited):
+HTTP/1.1 429 Too Many Requests
+Content-Type: application/json
+
+{
+ "success": false,
+ "error": {
+ "code": "RATE_LIMIT_EXCEEDED",
+ "message": "Too many PDF exports. Please wait a minute."
+ }
+}
+
+RESPONSE (Error - PDF Generation Failed):
+HTTP/1.1 500 Internal Server Error
+Content-Type: application/json
+
+{
+ "success": false,
+ "error": {
+ "code": "PDF_GENERATION",
+ "message": "Failed to generate PDF. Please try again."
+ }
+}
+```
+
+## PDF Options Structure
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ PDF Options (internal/pdf/options.go) │
+└────────────────────────────────────────────────────────────┘
+
+type Options struct {
+ // Paper settings
+ PaperSize PaperSize // A4, Letter, Legal
+ Orientation Orientation // Portrait, Landscape
+ PaperWidth float64 // In inches
+ PaperHeight float64 // In inches
+
+ // Margins
+ MarginTop string // "1cm", "0.5in"
+ MarginRight string
+ MarginBottom string
+ MarginLeft string
+
+ // Rendering
+ PrintBackground bool // Include background colors
+ Scale float64 // 0.5 to 2.0
+ Landscape bool // True for landscape
+
+ // Quality
+ PreferCSSPageSize bool
+ DisplayHeaderFooter bool
+ HeaderTemplate string
+ FooterTemplate string
+}
+
+Default A4 Options:
+Options{
+ PaperSize: A4, // 8.27 x 11.69 inches
+ Orientation: Portrait,
+ MarginTop: "1cm",
+ MarginRight: "1cm",
+ MarginBottom: "1cm",
+ MarginLeft: "1cm",
+ PrintBackground: true,
+ Scale: 1.0,
+ Landscape: false,
+}
+```
+
+## Rate Limiting
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Rate Limiting for PDF Export │
+└────────────────────────────────────────────────────────────┘
+
+RateLimiter Middleware:
+├─ 3 requests per minute per IP
+├─ Uses token bucket algorithm
+└─ Applied only to /export/pdf endpoint
+
+Implementation:
+
+type RateLimiter struct {
+ requests map[string]*bucket
+ mu sync.RWMutex
+}
+
+type bucket struct {
+ tokens int
+ lastReset time.Time
+}
+
+func (rl *RateLimiter) Allow(ip string) bool {
+ rl.mu.Lock()
+ defer rl.mu.Unlock()
+
+ bucket := rl.requests[ip]
+ if bucket == nil {
+ bucket = &bucket{
+ tokens: 3,
+ lastReset: time.Now(),
+ }
+ rl.requests[ip] = bucket
+ }
+
+ // Reset bucket every minute
+ if time.Since(bucket.lastReset) > time.Minute {
+ bucket.tokens = 3
+ bucket.lastReset = time.Now()
+ }
+
+ // Check tokens
+ if bucket.tokens <= 0 {
+ return false // Rate limited
+ }
+
+ bucket.tokens--
+ return true
+}
+
+Response when rate limited:
+{
+ "success": false,
+ "error": {
+ "code": "RATE_LIMIT_EXCEEDED",
+ "message": "Too many PDF exports. Please wait a minute."
+ }
+}
+```
+
+## PDF Performance
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ PDF Performance │
+└────────────────────────────────────────────────────────────┘
+
+Timing Breakdown:
+
+┌─────────────────────────────────────────────────────────┐
+│ Operation Time % │
+├─────────────────────────────────────────────────────────┤
+│ Request validation ~1ms 0.1% │
+│ HTML generation ~50ms 5% │
+│ Chrome launch ~200ms 20% │
+│ Page navigation ~100ms 10% │
+│ Font loading ~50ms 5% │
+│ PDF rendering ~550ms 55% │
+│ Response transmission ~50ms 5% │
+├─────────────────────────────────────────────────────────┤
+│ TOTAL ~1000ms 100% │
+└─────────────────────────────────────────────────────────┘
+
+Optimization Strategies:
+1. Keep Chrome instance warm
+ └─→ Pre-launch Chrome on startup
+ Reuse context for multiple PDFs
+
+2. Optimize HTML
+ └─→ Inline critical CSS
+ Remove unused styles
+
+3. Font optimization
+ └─→ Use web-safe fonts
+ Preload font files
+
+4. Cache templates
+ └─→ Pre-compile templates
+ Reuse parsed templates
+
+5. Parallel processing
+ └─→ Queue PDF jobs
+ Process multiple concurrently
+```
+
+## Error Scenarios
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ PDF Error Scenarios │
+└────────────────────────────────────────────────────────────┘
+
+1. Chrome Launch Failed
+ Error: chromedp: failed to allocate context
+ Cause: Chrome not installed or crashed
+ Recovery: Log error, return 500, suggest retry
+
+2. Timeout
+ Error: context deadline exceeded
+ Cause: PDF generation took > 30 seconds
+ Recovery: Cancel operation, return timeout error
+
+3. Memory Limit
+ Error: out of memory
+ Cause: Too many concurrent PDF generations
+ Recovery: Rate limiting, queue system
+
+4. Template Error
+ Error: template execution failed
+ Cause: Missing data or invalid template
+ Recovery: Fix template, ensure all data present
+
+5. Navigation Error
+ Error: navigation failed
+ Cause: Invalid HTML or data URL too large
+ Recovery: Check HTML validity, reduce size
+```
+
+## Related Diagrams
+
+- [Request Flow](./02-request-flow.md) - HTTP request lifecycle
+- [Handler Organization](./04-handler-organization.md) - Handler structure
+- [Error Handling Flow](./06-error-handling-flow.md) - Error handling
+- [Template Rendering](./07-template-rendering.md) - Template system
diff --git a/_go-learning/diagrams/README.md b/_go-learning/diagrams/README.md
new file mode 100644
index 0000000..37cc026
--- /dev/null
+++ b/_go-learning/diagrams/README.md
@@ -0,0 +1,50 @@
+# Architecture Diagrams
+
+Visual representations of the CV website architecture, data flow, and component relationships.
+
+## Available Diagrams
+
+1. [System Architecture](./01-system-architecture.md) - Overall system design
+2. [Request Flow](./02-request-flow.md) - HTTP request lifecycle
+3. [Middleware Chain](./03-middleware-chain.md) - Middleware execution order
+4. [Handler Organization](./04-handler-organization.md) - Handler file structure
+5. [Data Models](./05-data-models.md) - CV and UI data structures
+6. [Error Handling Flow](./06-error-handling-flow.md) - Error propagation and handling
+7. [Template Rendering](./07-template-rendering.md) - Template compilation and rendering
+8. [PDF Generation](./08-pdf-generation.md) - PDF export process
+
+## Diagram Format
+
+All diagrams are created using ASCII art for:
+- Easy version control (text-based)
+- Universal compatibility (no special tools needed)
+- Fast loading and rendering
+- Copy-paste friendly
+
+## Reading Diagrams
+
+```
+┌─────┐
+│ Box │ = Component or module
+└─────┘
+
+ ↓ = Data flow direction
+ →
+
+┌─┬─┐
+│A│B│ = Multiple components side by side
+└─┴─┘
+
+┌───────┐
+│ ┌───┤ = Nested components
+│ └───┘
+└───────┘
+```
+
+## Conventions
+
+- **Solid lines** (`─`, `│`): Direct dependencies
+- **Arrows** (`→`, `↓`): Data flow direction
+- **Boxes** (`┌─┐`): Components, modules, files
+- **Double lines** (`═`, `║`): Important/critical paths
+- **Dotted** (`:`, `.`): Optional or conditional paths
diff --git a/_go-learning/patterns/01-middleware-pattern.md b/_go-learning/patterns/01-middleware-pattern.md
new file mode 100644
index 0000000..d928826
--- /dev/null
+++ b/_go-learning/patterns/01-middleware-pattern.md
@@ -0,0 +1,425 @@
+# Middleware Pattern in Go
+
+## Pattern Overview
+
+The Middleware Pattern wraps HTTP handlers to add cross-cutting concerns like logging, authentication, error recovery, and request preprocessing. It follows the decorator pattern, allowing you to compose multiple middleware into a chain.
+
+## Pattern Structure
+
+```go
+// Middleware function signature
+type Middleware func(http.Handler) http.Handler
+
+// Middleware wraps a handler
+func MyMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Pre-processing (before handler)
+ // ... do something before
+
+ // Call next handler
+ next.ServeHTTP(w, r)
+
+ // Post-processing (after handler)
+ // ... do something after
+ })
+}
+```
+
+## Real Implementation from Project
+
+### Preferences Middleware
+
+```go
+// internal/middleware/preferences.go
+
+// PreferencesMiddleware reads user preference cookies and stores them in request context
+func PreferencesMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Pre-processing: Read cookies
+ prefs := &Preferences{
+ CVLength: getCookieWithDefault(r, "cv-length", "short"),
+ CVIcons: getCookieWithDefault(r, "cv-icons", "show"),
+ CVLanguage: getCookieWithDefault(r, "cv-language", "en"),
+ CVTheme: getCookieWithDefault(r, "cv-theme", "default"),
+ ColorTheme: getCookieWithDefault(r, "color-theme", "light"),
+ }
+
+ // Migrate old values
+ if prefs.CVLength == "extended" {
+ prefs.CVLength = "long"
+ }
+
+ // Store in context
+ ctx := context.WithValue(r.Context(), PreferencesKey, prefs)
+
+ // Call next handler with modified context
+ next.ServeHTTP(w, r.WithContext(ctx))
+
+ // No post-processing needed for this middleware
+ })
+}
+```
+
+### Recovery Middleware
+
+```go
+// internal/middleware/recovery.go
+
+// Recovery catches panics and returns 500 error
+func Recovery(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Setup panic recovery
+ defer func() {
+ if err := recover(); err != nil {
+ // Log panic with stack trace
+ log.Printf("PANIC: %v\n%s", err, debug.Stack())
+
+ // Return error response
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ }
+ }()
+
+ // Call next handler (protected by defer/recover)
+ next.ServeHTTP(w, r)
+ })
+}
+```
+
+### Logger Middleware
+
+```go
+// internal/middleware/logger.go
+
+// Logger logs HTTP requests and their duration
+func Logger(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Pre-processing: Start timer and log request
+ start := time.Now()
+ log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
+
+ // Wrap ResponseWriter to capture status code
+ wrapped := &responseWriter{
+ ResponseWriter: w,
+ statusCode: http.StatusOK,
+ }
+
+ // Call next handler
+ next.ServeHTTP(wrapped, r)
+
+ // Post-processing: Log duration and status
+ duration := time.Since(start)
+ log.Printf("Completed in %v (status: %d)", duration, wrapped.statusCode)
+ })
+}
+
+// Helper to capture response status
+type responseWriter struct {
+ http.ResponseWriter
+ statusCode int
+}
+
+func (rw *responseWriter) WriteHeader(code int) {
+ rw.statusCode = code
+ rw.ResponseWriter.WriteHeader(code)
+}
+```
+
+## Middleware Composition
+
+### Chaining Middleware
+
+```go
+// internal/routes/routes.go
+
+func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler) http.Handler {
+ mux := http.NewServeMux()
+
+ // Register routes
+ mux.HandleFunc("/", cvHandler.Home)
+ mux.HandleFunc("/cv", cvHandler.CVContent)
+ mux.HandleFunc("/health", healthHandler.Health)
+
+ // Compose middleware chain
+ // Execution order: Recovery → Logger → SecurityHeaders → Preferences → mux
+ handler := middleware.Recovery(
+ middleware.Logger(
+ middleware.SecurityHeaders(
+ middleware.PreferencesMiddleware(mux),
+ ),
+ ),
+ )
+
+ return handler
+}
+```
+
+### Route-Specific Middleware
+
+```go
+// Apply middleware only to specific routes
+func Setup(cvHandler *handlers.CVHandler) http.Handler {
+ mux := http.NewServeMux()
+
+ // Public routes (minimal middleware)
+ mux.HandleFunc("/", cvHandler.Home)
+ mux.HandleFunc("/health", healthHandler.Health)
+
+ // Protected PDF route (additional middleware)
+ pdfHandler := middleware.OriginChecker(
+ middleware.RateLimiter(
+ http.HandlerFunc(cvHandler.ExportPDF),
+ 3, // 3 requests per minute
+ ),
+ )
+ mux.Handle("/export/pdf", pdfHandler)
+
+ // Global middleware for all routes
+ handler := middleware.Recovery(
+ middleware.Logger(
+ middleware.PreferencesMiddleware(mux),
+ ),
+ )
+
+ return handler
+}
+```
+
+## Common Middleware Use Cases
+
+### 1. Authentication
+
+```go
+func AuthMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Get token from header
+ token := r.Header.Get("Authorization")
+
+ // Validate token
+ userID, err := validateToken(token)
+ if err != nil {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Store user ID in context
+ ctx := context.WithValue(r.Context(), UserIDKey, userID)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+}
+```
+
+### 2. CORS
+
+```go
+func CORS(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Set CORS headers
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
+
+ // Handle preflight
+ if r.Method == "OPTIONS" {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+}
+```
+
+### 3. Request Timeout
+
+```go
+func Timeout(duration time.Duration) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Create context with timeout
+ ctx, cancel := context.WithTimeout(r.Context(), duration)
+ defer cancel()
+
+ // Create channel for handler completion
+ done := make(chan struct{})
+ go func() {
+ next.ServeHTTP(w, r.WithContext(ctx))
+ close(done)
+ }()
+
+ // Wait for completion or timeout
+ select {
+ case <-done:
+ // Handler completed
+ case <-ctx.Done():
+ // Timeout occurred
+ http.Error(w, "Request Timeout", http.StatusGatewayTimeout)
+ }
+ })
+ }
+}
+```
+
+### 4. Request ID
+
+```go
+func RequestID(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Generate unique ID
+ requestID := uuid.New().String()
+
+ // Add to response header
+ w.Header().Set("X-Request-ID", requestID)
+
+ // Store in context
+ ctx := context.WithValue(r.Context(), RequestIDKey, requestID)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+}
+```
+
+## Middleware Execution Flow
+
+```
+Request
+ │
+ ▼
+┌─────────────────────────┐
+│ Recovery Middleware │ ← Outermost (catches all panics)
+│ defer/recover │
+└─────────────────────────┘
+ │
+ ▼
+┌─────────────────────────┐
+│ Logger Middleware │ ← Logs request + duration
+│ Pre: Log request │
+│ Post: Log duration │
+└─────────────────────────┘
+ │
+ ▼
+┌─────────────────────────┐
+│ Security Middleware │ ← Add security headers
+│ Set headers │
+└─────────────────────────┘
+ │
+ ▼
+┌─────────────────────────┐
+│ Preferences Middleware │ ← Innermost (closest to handler)
+│ Read cookies → context │
+└─────────────────────────┘
+ │
+ ▼
+┌─────────────────────────┐
+│ Handler │ ← Business logic
+│ Process request │
+└─────────────────────────┘
+ │
+ ▼
+Response (unwraps in reverse order)
+```
+
+## Benefits
+
+1. **Separation of Concerns**: Cross-cutting logic separate from handlers
+2. **Composability**: Chain multiple middleware together
+3. **Reusability**: Same middleware for multiple routes
+4. **Testability**: Easy to test in isolation
+5. **Maintainability**: Change behavior without touching handlers
+
+## Best Practices
+
+### ✅ DO
+
+```go
+// Keep middleware focused on one concern
+func LoggerMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Only logging logic here
+ log.Printf("[%s] %s", r.Method, r.URL.Path)
+ next.ServeHTTP(w, r)
+ })
+}
+
+// Use context for request-scoped values
+func PreferencesMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ prefs := readPreferences(r)
+ ctx := context.WithValue(r.Context(), PrefsKey, prefs)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+}
+
+// Order middleware correctly (outer to inner)
+handler := Recovery(Logger(Auth(mux)))
+```
+
+### ❌ DON'T
+
+```go
+// DON'T mix multiple concerns in one middleware
+func BadMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Too much! Logging, auth, CORS, caching...
+ log.Print(r.URL)
+ if !checkAuth(r) { return }
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ cached := getCache(r.URL.Path)
+ // ...
+ })
+}
+
+// DON'T store context in struct
+type BadMiddleware struct {
+ ctx context.Context // Wrong!
+}
+
+// DON'T modify original request (use r.WithContext)
+func BadMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ r.Header.Set("X-Foo", "bar") // Modifies original!
+ next.ServeHTTP(w, r)
+ })
+}
+```
+
+## Testing Middleware
+
+```go
+func TestPreferencesMiddleware(t *testing.T) {
+ // Create test handler that reads preferences
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ prefs := GetPreferences(r)
+ if prefs.CVLength != "long" {
+ t.Errorf("expected long, got %s", prefs.CVLength)
+ }
+ w.WriteHeader(http.StatusOK)
+ })
+
+ // Wrap with middleware
+ wrapped := PreferencesMiddleware(handler)
+
+ // Create test request with cookie
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ req.AddCookie(&http.Cookie{Name: "cv-length", Value: "long"})
+
+ // Execute
+ w := httptest.NewRecorder()
+ wrapped.ServeHTTP(w, req)
+
+ // Verify
+ if w.Code != http.StatusOK {
+ t.Errorf("expected 200, got %d", w.Code)
+ }
+}
+```
+
+## Related Patterns
+
+- **Chain of Responsibility**: Middleware is a specific implementation
+- **Decorator Pattern**: Wrapping handlers adds behavior
+- **Context Pattern**: Often used together for request-scoped data
+
+## Further Reading
+
+- [Writing Middleware in Go](https://www.alexedwards.net/blog/making-and-using-middleware)
+- [Middleware Pattern in Go](https://gowebexamples.com/advanced-middleware/)
+- [Context Pattern](./03-context-pattern.md) - Used with middleware
diff --git a/_go-learning/patterns/README.md b/_go-learning/patterns/README.md
new file mode 100644
index 0000000..8efe56e
--- /dev/null
+++ b/_go-learning/patterns/README.md
@@ -0,0 +1,219 @@
+# Go Patterns Used in This Project
+
+This directory contains documentation on the Go design patterns and idioms used throughout the CV website project.
+
+## Pattern Catalog
+
+1. **[Middleware Pattern](./01-middleware-pattern.md)** - HTTP middleware chain for cross-cutting concerns
+2. **[Handler Pattern](./02-handler-pattern.md)** - Organized HTTP handler structure
+3. **[Context Pattern](./03-context-pattern.md)** - Request-scoped values using context
+4. **[Error Wrapping](./04-error-wrapping.md)** - Structured error handling with wrapping
+5. **[Dependency Injection](./05-dependency-injection.md)** - Constructor-based dependency injection
+6. **[Template Pattern](./06-template-pattern.md)** - Cached template management
+7. **[Singleton Pattern](./07-singleton-pattern.md)** - Single instance managers (template, config)
+8. **[Factory Pattern](./08-factory-pattern.md)** - Error and response constructors
+
+## Pattern Categories
+
+### Structural Patterns
+- **Middleware Pattern** - Composable request processing
+- **Singleton Pattern** - Single instance coordination
+- **Dependency Injection** - Decoupled component initialization
+
+### Behavioral Patterns
+- **Handler Pattern** - Request routing and handling
+- **Context Pattern** - Request-scoped data propagation
+- **Template Pattern** - Flexible rendering engine
+
+### Error Handling Patterns
+- **Error Wrapping** - Context-rich error chains
+- **Typed Errors** - Domain-specific error types
+- **Factory Pattern** - Consistent error creation
+
+## Pattern Usage Map
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Pattern Usage Map │
+└────────────────────────────────────────────────────────────┘
+
+main.go
+├─→ Singleton Pattern (config, template manager)
+├─→ Dependency Injection (handler construction)
+└─→ Middleware Pattern (chain setup)
+
+internal/handlers/
+├─→ Handler Pattern (method organization)
+├─→ Error Wrapping (error handling)
+├─→ Factory Pattern (error/response creation)
+└─→ Context Pattern (preference access)
+
+internal/middleware/
+├─→ Middleware Pattern (http.Handler wrapping)
+├─→ Context Pattern (value storage)
+└─→ Error Wrapping (panic recovery)
+
+internal/templates/
+├─→ Singleton Pattern (manager instance)
+├─→ Template Pattern (rendering strategy)
+└─→ Dependency Injection (config injection)
+
+internal/models/
+├─→ Factory Pattern (model loading)
+└─→ Error Wrapping (validation errors)
+```
+
+## When to Use Each Pattern
+
+### Middleware Pattern
+✓ Cross-cutting concerns (logging, auth, CORS)
+✓ Request/response modification
+✓ Chain-of-responsibility needs
+✗ Business logic (use handlers instead)
+
+### Handler Pattern
+✓ HTTP request handling
+✓ Route-specific logic
+✓ Organizing endpoints by resource
+✗ Generic utilities (use packages instead)
+
+### Context Pattern
+✓ Request-scoped values (user, preferences)
+✓ Cancellation signals
+✓ Deadlines and timeouts
+✗ Function parameters (use explicit params)
+
+### Error Wrapping
+✓ Adding context to errors
+✓ Preserving error chains
+✓ Debug information
+✗ Simple errors (use errors.New)
+
+### Dependency Injection
+✓ Decoupling components
+✓ Testing with mocks
+✓ Configuration flexibility
+✗ Simple functions (use direct calls)
+
+### Template Pattern
+✓ Flexible rendering
+✓ HTML generation
+✓ Hot reload in development
+✗ JSON APIs (use direct encoding)
+
+### Singleton Pattern
+✓ Shared resources (DB, cache)
+✓ Configuration managers
+✓ Template engines
+✗ Stateless utilities (use packages)
+
+### Factory Pattern
+✓ Complex object creation
+✓ Consistent initialization
+✓ Error construction
+✗ Simple structs (use literals)
+
+## Anti-Patterns to Avoid
+
+### ❌ Global State
+```go
+// BAD: Mutable global variable
+var globalConfig Config
+
+// GOOD: Pass as dependency
+func NewHandler(config *Config) *Handler
+```
+
+### ❌ Panic for Flow Control
+```go
+// BAD: Using panic for expected errors
+if err != nil {
+ panic(err)
+}
+
+// GOOD: Return errors
+if err != nil {
+ return fmt.Errorf("operation failed: %w", err)
+}
+```
+
+### ❌ Ignoring Errors
+```go
+// BAD: Ignoring error
+_ = json.Unmarshal(data, &result)
+
+// GOOD: Handle error
+if err := json.Unmarshal(data, &result); err != nil {
+ return fmt.Errorf("unmarshal: %w", err)
+}
+```
+
+### ❌ Context in Structs
+```go
+// BAD: Storing context in struct
+type Handler struct {
+ ctx context.Context
+}
+
+// GOOD: Pass context as first parameter
+func (h *Handler) Handle(ctx context.Context, w, r)
+```
+
+### ❌ Naked Returns
+```go
+// BAD: Naked return with named results
+func process() (result string, err error) {
+ result = "foo"
+ return // Confusing!
+}
+
+// GOOD: Explicit return
+func process() (string, error) {
+ result := "foo"
+ return result, nil
+}
+```
+
+## Learning Path
+
+For developers new to these patterns:
+
+1. **Start with**: Handler Pattern, Error Wrapping
+2. **Then learn**: Middleware Pattern, Context Pattern
+3. **Advanced**: Dependency Injection, Template Pattern
+4. **Master**: Singleton Pattern, Factory Pattern
+
+## Resources
+
+- [Effective Go](https://golang.org/doc/effective_go) - Official Go style guide
+- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) - Common mistakes
+- [Practical Go](https://dave.cheney.net/practical-go) - Best practices
+
+## Pattern Evolution
+
+This project evolved through these pattern adoptions:
+
+### Phase 1: Basic Structure
+- Simple handlers
+- No middleware
+- Manual cookie reading
+
+### Phase 2: Middleware Introduction
+- PreferencesMiddleware added
+- Cookie handling centralized
+- Context pattern adopted
+
+### Phase 3: Type Safety
+- Request/response types
+- Validation tags
+- Typed errors
+
+### Phase 4: Error Handling
+- Error wrapping throughout
+- Domain error types
+- Centralized error handler
+
+### Phase 5: Testing
+- Dependency injection for testability
+- Mock-friendly interfaces
+- Benchmark tests