# PLAN — Developer Homepage **Status:** live static site · **Last reviewed:** 2026-09-18 (security & code-quality audit) ## 1. Project Identity A single-page personal portfolio (Reese Wells — self-hosting & infrastructure) with an interactive fake terminal (easter-egg commands, vim simulator, achievement system). No backend, no user accounts, no PII beyond public contact details and GPG public keys. ## 2. Assumptions & Design Principles 1. **Static first.** The site is plain HTML/CSS/JS. No runtime dependencies, no CDNs, no build-time JS frameworks — the supply chain is the repo itself. 2. **Boring deployment.** `build.sh` → hashed `dist/` → `nginx:alpine` on `:8080` (plain HTTP) → TLS terminated by the external Caddy reverse proxy. 3. **Test the artifact.** E2E tests run against the built `dist/`, not `src/`, so what is tested is exactly what is deployed. 4. **Security headers are a container property.** All hardening headers are emitted by nginx (the only thing that serves the site in production). 5. **Fun is safe.** The fake terminal's easter eggs (page-wipe, login screen, vim) are DOM-only pranks — no real network or system effects. The only real network behavior (curl/wget `fetch`) is being removed — see Phase 02. ## 3. Architectural Anchors (LOCKED DECISIONS) | COMPONENT | DECISION | RATIONALE | STATUS | |---|---|---|---| | Frontend runtime | Vanilla HTML/CSS/JS, flat `src/*.js` files, `defer` load order | No toolchain, auditable, works with `build.sh` hashing | LOCKED (pre-existing) | | Terminal modules | `terminal-commands.js` (canned data) · `terminal-achievements.js` · `terminal-vim.js` · `terminal.js` (core) | Flat modules keep `build.sh`'s root-level hashing working; single source of truth for command data | LOCKED (2026-09-18 refactor) | | Build | `build.sh`: md5 content-hash cache-busting + `sed` reference rewrites → `dist/` | md5 is a cache key, not a security hash — acceptable | LOCKED (pre-existing) | | Serving | `nginx:alpine`, `listen 8080`, plain HTTP | TLS is added by the Caddy edge in front of the container | LOCKED (pre-existing) | | Edge | Caddy reverse proxy (Route53 DNS-validated TLS) in front of the container | HSTS/TLS are edge responsibilities | LOCKED (assumed — verify per Phase 03) | | E2E tests | `@playwright/test` pinned **1.62.0** (matches cached chromium-1234), serves `dist/` via `python3 -m http.server 8123`, config `playwright.config.js` | Tests the deployed artifact; pinned to avoid browser re-downloads | LOCKED (2026-09-18, user-approved) | | Deployment | Docker image → `gitea.reeseapps.com/services/homepage` via Gitea Actions (secrets-based registry login) | Self-hosted CI | LOCKED (pre-existing) | | Persistence | `localStorage` only (achievements, key `reese-terminal-achievements`) | No backend by design | LOCKED (pre-existing) | ## 4. High-Level Architecture ``` src/ (index.html, style.css, script.js, terminal*.js, assets) │ build.sh (hash + rewrite) ▼ dist/ (index.html + hashed assets) ◄── playwright webServer (port 8123) │ Dockerfile (COPY → nginx html root) ◄── tests/e2e/*.spec.js (44 tests) ▼ nginx:alpine :8080 (HTTP, security headers, cache policy) ▲ Caddy edge (TLS, HSTS) ← reeseapps.com ``` - `src/terminal.js` — terminal core: prompt loop, history, tab completion, dispatch, expand/collapse (`expandTerminal`/`collapseTerminal`, shared with `script.js` hero click). - `src/terminal-commands.js` — `TERMINAL_COMMANDS` (canned outputs; values may be functions evaluated at dispatch), `TERMINAL_COMMAND_LIST` (tab completion), `getCommandOutput()`. - `src/terminal-vim.js` — `launchVimSimulator({content, terminal, mobileInput, filename, setVimMode})`. - `src/terminal-achievements.js` — `ACHIEVEMENTS`, localStorage persistence, toasts, hidden-section reveal, `initTerminalAchievements()`. - `src/script.js` — nav menu, IntersectionObserver fades, server-rack background generation, hero-click terminal toggle. ## 5. Validation / Verification Workflow 1. `./build.sh` — rebuild `dist/`. 2. `npm test` (Playwright) — 44 E2E tests against `dist/` on port 8123. Every phase's final pass must leave the suite green. 3. `.agents/validate.sh` (installed by the `phased-execution` skill) — must run 1 + 2 and, for Phase 03, the header checks below. 4. Container header check (Phase 03): `scripts/check-headers.sh` — builds the image, runs it on a scratch port, asserts the four security headers on `/`, `/index.html`, and a hashed asset. ## 6. Data Model No server-side data. Client-side state: | Key | Where | Shape | |---|---|---| | `reese-terminal-achievements` | localStorage | JSON array of achievement ids (e.g. `["time_flies","nice_try"]`) | Session-only state (no persistence): terminal history, `isRoot`, vim state, menu open/closed, terminal grown/collapsed. ## 7. Roadmap | Phase | Title | Source | |---|---|---| | `01_fix_history_xss` | Fix terminal history-recall XSS (M-1) | audit finding M-1 | | `02_simulate_fetch_commands` | Remove unvalidated client fetch from curl/wget (M-2) | audit finding M-2 | | `03_nginx_security_headers` | Security headers on every location (M-4) + `server_tokens off` (L-2) | audit findings M-4, L-2 | **Dismissed:** audit finding M-3 (README `0.0.0.0:8080` binding) — false positive per project owner (2026-09-18); the host firewall already restricts access and the site is fronted by the Caddy edge. **Completed in-session (not phased):** code-quality pass 2026-09-18 — module split (Q-1), dead-code removal (Q-2/3/4, `scrollOffset`), expand/collapse helpers (Q-5), rack-decoration CSS classes (Q-7), `hidden`-attribute reveal (Q-8), a11y label on terminal input (Q-9), dynamic `date` (Q-10), vim `dd` line-deletion fix, Playwright regression suite (44 tests). See `.agents/remediation_plan.md`. Remaining low-priority audit items (not yet phased): L-1 (verify HSTS at the Caddy edge — folded into Phase 03 verification), L-3 (drop `style-src 'unsafe-inline'` after moving inline `style=""` attrs to classes), L-5 (label the displayed SSH key as fictitious or show fingerprint only), L-7 (pin CI action SHAs / image signing), L-8 (build.sh verification test in CI).