# PROJECT_STATUS.md

**Current phase:** Phase 3 — implementation **complete** (all milestones ✅)
**Branch strategy:** feature branches → `main` (never committed directly to `main`)
**Last updated:** 2026-07-04

---

## Milestones

| # | Milestone | Status |
|---|---|---|
| 0 | Repo, tooling, tracking | ✅ |
| 1 | Astro base config + token/style layer | ✅ |
| 2 | i18n layer (locales, slug map, dictionaries) | ✅ |
| 3 | Content schemas + EN/NO data | ✅ |
| 4 | UI atoms + core layout (Header, Footer, SEO) | ✅ |
| 5 | Home page sections | ✅ |
| 6 | Case-study + capability templates | ✅ |
| 7 | Remaining pages (about, experience, contact, cv, colophon, 404) | ✅ |
| 8 | Behaviour modules (theme, nav, observer, lightbox, form) | ✅ |
| 9 | SEO layer (meta, hreflang, JSON-LD, OG images, sitemap) | ✅ |
| 10 | Contact relay (.NET 9) | ✅ |
| 11 | Tests (vitest + playwright) | ✅ |
| 12 | Docker + CI | ✅ |
| 13 | Verification pass | ✅ |

## Verification results

- **Build:** 21 pages + 20 OG images + sitemap/robots, static, clean.
- **Lint / types:** ESLint + Prettier clean; `tsc --noEmit` clean; `astro check` wired.
- **Unit tests (vitest):** 23 passing — dictionary parity, slug-map bijection, content-schema
  validation, JSON-LD builders.
- **E2E (playwright):** 23 passing — both locales render, language switch maps page↔page
  (incl. JS-disabled), CV downloads resolve, contact form happy-path + honeypot, and
  **axe a11y with zero serious/critical violations across 5 templates × 2 themes**.
- **Docker:** both images build; relay `/healthz` → 200; site container serves EN + NO with
  CSP header and the `/Linkedin` 301 redirect.
- **Contrast:** all body/label text ≥ 4.5:1 in both themes (axe-verified; `ink-faint` and the
  light accent were raised to meet AA).

## Environment

- Node v22.23.1 · pnpm 9.15.9 · .NET SDK 10.0.201 (relay targets `net9.0`) · Docker 29.3.1.

## Deviations from spec (with rationale)

1. **Content as typed TS modules + zod, not Astro content-collection entries.** The nested
   `{en,no}` model serves the spec's "facts once, prose twice" goal better than parallel
   per-locale entries; same build-time zod validation and CI-testable getters.
2. **Sitemap hand-rolled from the slug map** (localised slugs don't fit `@astrojs/sitemap`'s
   parallel-slug assumption; keeps one route contract).
3. **OG images via resvg + hand-built SVG, not satori** (satori's parser can't read the
   vendored variable fonts). Deterministic offline builds; fonts vendored in-repo.
4. **Relay targets `net9.0` built with the .NET 10 SDK** (only SDK available); runs on the
   `aspnet:9.0` runtime in Docker.
5. **Site nginx config co-located in `site/`** (not `deploy/`) so the image build context
   stays `site/`. `deploy/` holds the compose stacks + env template as specified.
6. **Theme-init is an external script** (`/theme-init.js`) so the CSP can use `script-src
   'self'` with no inline hash.

## Deployed — live

- **Live on the `mediaserver` host at `http://<host>:1337`.** Stack: `deploy-site-1`
  (nginx static, publishes 1337) + `deploy-relay-1` (contact relay, internal). Verified: all
  routes + `/no/`, case studies, CV PDFs, images, `/Linkedin` 301, `/api/contact` proxy.
- **Auto-deploy:** a 1-minute cron on the host (`deploy/autodeploy.sh`) pulls `main` and
  `docker compose up -d --build` on change. `.gitea/workflows/deploy.yml` also exists for the
  Gitea runner (activate once its job image has the Docker CLI + socket).
- Repo cloned at `/home/pi/resumesite` (remote carries the deploy token — rotate/scrub if desired).

## Owner action remaining

- [ ] **Point Cloudflare / your TLS proxy at host:1337** to serve it on `cesnimda.co.uk`
      (retire the WordPress apex router as part of the cutover).
- [ ] **Relay SMTP** — add creds to `/home/pi/resumesite/deploy/.env` (`SMTP_HOST/USER/PASSWORD`)
      so the contact form sends; until then it degrades to the visible e-mail (POST returns 502).
- [ ] **Native-Norwegian review** of all NO content, incl. the NO CV.
- [ ] **InboxIntel screenshots** — mockups are in; swap for live captures if/when available.
- [ ] **Legacy WordPress URL inventory** → confirm the 410 map in `nginx.conf`.

## Resolved

- JobTrack / InboxIntel repos are **private** → repo links intentionally omitted; case studies
  describe the architecture without linking code.
