Files
juanatsap 37ef0648d7 docs: add Phase 11 (self-hosted deps) and ADR-005 for HTMX/Hyperscript vendoring
Document why HTMX and Hyperscript are self-hosted instead of CDN-loaded:
zero external SPOF, faster same-origin loading, reduced CSP surface,
and controlled version upgrades.
2026-05-14 13:06:20 +01:00

8.4 KiB

Architectural Decisions

This document records key architectural decisions made for this project.

Table of Contents


ADR-001: No Data Caching

Status: Superseded by ADR-004 Date: 2025-11-30

Context

The CV data (JSON files) is loaded from disk on every request. A caching layer could reduce disk I/O and improve response times.

Decision

No caching will be implemented for CV data.

Rationale

  1. Project Size: This is a small, personal CV website with minimal traffic
  2. Simplicity: Caching adds complexity (cache invalidation, memory management, TTL configuration)
  3. Performance is Already Good: JSON file loading takes <10ms, which is acceptable
  4. Hot Reload: In development, we want fresh data on every request for testing
  5. YAGNI: We don't need caching until we have evidence of performance issues

Consequences

  • Simple, maintainable code
  • No cache invalidation bugs
  • Slightly higher disk I/O (negligible for this scale)
  • If traffic increases significantly, this decision can be revisited

ADR-002: Static Dates Instead of Git Integration

Status: Accepted Date: 2025-11-30

Context

Previously, the project had a feature to dynamically fetch project start dates from git repository first commit dates using exec.CommandContext to run git log commands.

Decision

Git command execution has been removed. Use static dates in JSON files instead.

Rationale

  1. Security Risk: Executing shell commands (even with path validation) poses injection risks
  2. Symlink Bypass: Path validation can be bypassed with symbolic links
  3. Unnecessary Complexity: Static dates in JSON are simpler and more maintainable
  4. Control: Static dates give full control over what's displayed
  5. Performance: No external process spawning

Implementation

Instead of gitRepoUrl in project data, use startDate directly:

{
  "title": "My Project",
  "startDate": "2024-06",
  "current": true
}

Consequences

  • More secure codebase
  • Simpler implementation
  • Manual date updates required when adding new projects
  • No external dependencies on git binary

ADR-003: CI/CD with GitHub Actions

Status: Implemented Date: 2025-11-30

Context

The project needs automated testing, linting, and deployment.

Decision

GitHub Actions is used for CI/CD with two workflows:

  1. test.yml - Runs on PRs and pushes to main/develop
  2. deploy.yml - Deploys to production on push to main

Workflows

Test Workflow (.github/workflows/test.yml)

Triggers: push and pull_request to main and develop branches

Steps:

  1. Checkout code
  2. Setup Go 1.25.1
  3. Install and verify dependencies
  4. Run golangci-lint
  5. Run unit tests with coverage
  6. Generate coverage report
  7. Check coverage threshold (target: 70%)
  8. Upload coverage to Codecov
  9. Run benchmarks
  10. Build binary
  11. Upload artifacts

Deploy Workflow (.github/workflows/deploy.yml)

Triggers: push to main branch or manual dispatch

Steps:

  1. SSH into production server
  2. Fix repository permissions
  3. Stash any local changes
  4. Pull latest changes
  5. Restart systemd service
  6. Verify health check

Required Secrets

  • SSH_PRIVATE_KEY - SSH private key for server access
  • SSH_HOST - Server IP or domain
  • SSH_USER - SSH username
  • SSH_PORT (optional, default: 22)
  • SERVICE_NAME (optional, default: cv)
  • REPO_PATH (optional, default: /home/txeo/Git/yo/cv)

Consequences

  • Automated quality checks on every PR
  • Consistent deployment process
  • Health check verification after deployment
  • Coverage tracking with Codecov
  • Binary artifacts available for download

ADR-004: Application-Level Data Caching

Status: Accepted Date: 2025-12-06 Supersedes: ADR-001

Context

As the CV site evolved to support multiple languages and increased usage, the original decision (ADR-001) to avoid caching was reconsidered. While the site traffic remains modest, the benefits of eliminating per-request file I/O became clear:

  1. Consistency: Every request reads the same data
  2. Performance: Eliminates disk I/O from hot paths
  3. Reliability: Fail-fast at startup catches data errors early
  4. Simplicity: No cache invalidation needed (data is static)

Decision

Implement application-level data caching with startup-time loading.

The internal/cache package provides:

  • DataCache struct holding CV and UI data for all supported languages
  • Single load at application startup
  • Thread-safe read access via sync.RWMutex
  • Language-keyed retrieval (GetCV(lang), GetUI(lang))

Implementation

// At startup (main.go)
dataCache, err := cache.New([]string{"en", "es"})
if err != nil {
    log.Fatalf("Failed to initialize data cache: %v", err)
}

// In handlers
cv := h.dataCache.GetCV(lang)
ui := h.dataCache.GetUI(lang)

Rationale

  1. Zero Per-Request I/O: Data loaded once, served from memory
  2. Fail-Fast: All data issues caught at startup, not runtime
  3. Thread-Safe: sync.RWMutex optimized for read-heavy workloads
  4. Minimal Complexity: Simple map-based storage, no TTL/invalidation
  5. Testable: 95.7% test coverage, including concurrency tests

Consequences

  • Positive:

    • Faster request handling (no disk I/O)
    • Earlier error detection (startup validation)
    • Consistent data across requests
    • Simple, well-tested implementation
  • Considerations:

    • Requires application restart to pick up data changes
    • Memory usage increases slightly (minimal - ~KB per language)
    • Deep copies required when handlers mutate data

Documentation

See 23-DATA-CACHE.md for complete API reference and usage patterns.


ADR-005: Self-Hosted Frontend Dependencies

Status: Accepted Date: 2026-05-14

Context

HTMX (1.9.10) and Hyperscript (0.9.14) were loaded from the unpkg.com CDN. This introduced a single point of failure — if unpkg goes down, the site loses all interactivity. Additionally, each CDN request adds DNS resolution, TLS negotiation, and potential redirect overhead. HTMX 1.9.10 was also two major versions behind the current 2.0.10 release.

Decision

Self-host HTMX and Hyperscript as vendored files. Remove unpkg.com from the Content Security Policy.

  • static/htmx/htmx.min.js — HTMX 2.0.10 (51KB)
  • static/hyperscript/_hyperscript.min.js — Hyperscript 0.9.91 (172KB)
  • Iconify remains on jsdelivr CDN (icon rendering, acceptable external dependency)

Rationale

  1. No external SPOF: The site functions fully even if all CDNs are down
  2. Faster loading: Same-origin assets skip DNS lookup and TLS handshake
  3. Smaller CSP surface: unpkg.com removed from script-src whitelist
  4. Version upgrade: HTMX 1.9.10 → 2.0.10 with zero breaking changes (all hx-* attributes are compatible; hx-head moved from extension to built-in)
  5. Cache alignment: Libraries cached alongside the site's own assets

Consequences

  • Positive:

    • Zero dependency on unpkg.com availability
    • Reduced CSP attack surface
    • HTMX 2.0 features available (built-in head support)
    • Faster page loads (no cross-origin requests for core libs)
  • Considerations:

    • Version updates require manually downloading new files
    • Must track current versions in documentation
    • File sizes add ~223KB to the repository (acceptable trade-off)

Documentation

See 02-MODERN-WEB-TECHNIQUES.md Phase 11 for implementation details.


How to Add New Decisions

When making significant architectural decisions, add a new section following this template:

## ADR-XXX: Title

**Status:** Proposed | Accepted | Deprecated | Superseded
**Date:** YYYY-MM-DD

### Context
What is the issue that we're seeing that is motivating this decision?

### Decision
What is the change that we're proposing?

### Rationale
Why is this the best choice?

### Consequences
What are the results of this decision?