feat: content layer — zod schemas, validated bilingual data, locale view models

- schema.ts: zod schemas per DATA_MODEL (profile, projects, experience, skills, meta)
- nested {en,no} model: invariant facts once, prose twice; validated at build
- content: profile, skills, experience (with apprentice->developer progression),
  JobTrack + InboxIntel case studies with data-driven arch diagrams, homelab capability page
- per-page SEO meta (EN/NO)
- content.ts: locale-resolved view models; CV PDFs wired
- deviation from literal Astro-collections logged in PROJECT_STATUS with rationale

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-04 01:05:21 +02:00
parent 63db8bf254
commit 2184a1892f
12 changed files with 1602 additions and 1 deletions
+21 -1
View File
@@ -36,7 +36,27 @@ Legend: ⬜ not started · 🟡 in progress · ✅ done · ⛔ blocked
## Deviations from spec (with rationale)
_None yet. Any deviation gets logged here per the engineering rules._
1. **Content stored as typed TS modules validated by zod, not Astro content-collection
entries** (DATA_MODEL / I18N_SPEC §3 described "content collections with per-locale
entries"). Rationale: the content is highly structured (not markdown documents) and the
nested `{en,no}` single-entry model serves the spec's own stated goal — "facts exist
once, prose exists twice" — *better* than Astro's parallel per-locale entries, which
can't express author-once invariance natively. All required properties are preserved:
zod schema validation at build (`src/lib/schema.ts`), structural EN/NO parity by
construction, and CI-testable getters (`src/lib/content.ts`). Same validation guarantee,
cleaner authoring, no duplication.
2. **Sitemap hand-rolled from the slug map** instead of `@astrojs/sitemap`. Rationale: the
slug map is the single route contract (ARCHITECTURE A5) and localised slugs (prosjekter
vs projects) don't fit the integration's parallel-slug assumption. Hand-rolling keeps one
source of truth and correct hreflang alternates.
## Prerequisite substitutions in place (interim, non-blocking)
- Portrait + project screenshots use a `placeholder:` scheme (styled skeletons) until the
seeded-data capture pass and real headshot are provided. Real assets drop straight into
the same content refs.
- CV download targets are the current PDFs (`public/cv/…`); the ATS-safe rework replaces
the files at the same URLs.
## Open questions / blocked-on-user