Skip to content

overhaul: 04 content model - #18

Open
CS-5 wants to merge 1 commit into
overhaul/03-primitivesfrom
overhaul/04-content-model
Open

overhaul: 04 content model#18
CS-5 wants to merge 1 commit into
overhaul/03-primitivesfrom
overhaul/04-content-model

Conversation

@CS-5

@CS-5 CS-5 commented Aug 28, 2026

Copy link
Copy Markdown
Member

Layer 4 of the overhaul stack, on overhaul/03-primitives. plan/04-content-model.md.

Content moves out of code into zod-validated markdown. This is the objective-2 layer: adding a sponsor should take two minutes and one small PR — it now takes a logo file and five lines of frontmatter.

Migrated

Collection Count From
sponsors 10 (7 active, 3 retired) legacy/data/sponsors.ts
faq 6 the openhouse page's inline array
events 2 /openhouse and /programs/frc/kickoff
frcRobots 6 the robots page's JSX slides
frcTeamPhotos / fllTeamPhotos 13 + 1 14 static imports in about/components.tsx
news scaffold new (D18)

Sponsor migration is lossless. Active: JLG (Platinum), The WorkShope, Y.B. Welding, Journalytic, Volvo (Gold), Orrstown, Manitowoc (Bronze). Retired as active: false: Wellspan, VFW, Fives — all three were commented-out blocks in the legacy file, so we can still say who supported us in 2022 instead of losing it to a code comment.

One entry I deliberately did not migrate: Test Sponsor. It's the fourth commented-out block, but it's a test fixture — no logo, example.com URL. Migrating it would have invented a sponsor. Flagging in case you'd rather it exist as a fixture.

Copy comes across verbatim, typos included ("beings", "atriculate", "visibilty") — revision is Phase 12 per D8.

src/data/site.ts replaces every hardcoded constant the legacy pages carried: the four URLs from data/config.ts, both Google Calendar IDs, the GA4 measurement ID, the workspace address and directions link, socials, contact email, plus a programs map keyed by theme name.

docs/content.md is the editing guide — add and retire a sponsor, add an FAQ, update or hide an event, add a robot and a team photo, each with a copy-paste template, plus a "sharp edges" section.

Verified

Validation genuinely bites. Setting a sponsor's level to Titanium:

[InvalidContentEntryDataError] sponsors → jlg data does not match collection schema.
  level: Invalid option: expected one of "Platinum"|"Gold"|"Silver"|"Bronze"|"Friend"

…and pnpm build exits 1. pnpm check && pnpm build green.

Three things I changed from the brief, each for a reason

1. Collection names avoid slashesfrcRobots, not frc/robots. The content still nests under frc/ and fll/ on disk exactly as D18 requires; only the collection key changed. Astro writes each collection's editor JSON schema to .astro/collections/<name>.schema.json without creating intermediate directories, so a slashed name warned on every build and silently lost frontmatter autocomplete for those three collections.

2. news/template.md is a real entry with draft: true, not a glob-excluded _TEMPLATE.md. An excluded template drifts from the schema unnoticed, and it leaves the collection empty — which warns on every build. As an entry it's schema-validated and still never renders.

3. events carries flat locationName/locationAddress instead of the brief's nested location: { name, address }, per D2's flat-schema rule. Also added displayDate (legacy's kickoff page kept a hand-written date string next to the timestamp, and that reads better in copy than anything we'd format) and directionsUrl.

One gap worth knowing about

A mistyped content reference logs an error but exits 0. Astro reports:

[ERROR] [content] Invalid content reference: entry "openhouse" in collection "events"
(field: faq[0]) references "nonexistent-faq-entry" in collection "faq", but that entry does not exist.

…and then builds successfully. So a typo'd FAQ slug in an event would silently drop that answer from the page. It's documented as a sharp edge in docs/content.md, and Phase 08 — which actually consumes these references — should throw on a missing one rather than rendering a short list.

Assets

Moved only what the collections reference: sponsor logos, team photos, the five robot photos, two event heroes. public/image is down from 36 MB to 13 MB as a side effect. The full inventory and prune is Phase 09's job.

Also carried the Phase 04 guardrail into AGENTS.md as review guidance rather than a lint rule (the brief allowed either): never inline a content array where a collection exists, and new repeating content earns a collection. A lint rule that recognizes "an array that should be a collection" would be guesswork.


Generated by Claude Code

Everything that changes over time moves out of code and into zod-validated
markdown. Adding a sponsor is now a logo file plus five lines of frontmatter.

Migrated: 10 sponsors (7 active, 3 as active: false), 6 FAQ answers, both
seasonal events, 6 robots, 14 team photos. Copy comes across verbatim, typos
included — revision is Phase 12 (D8). Former sponsors are records with
active: false rather than commented-out code, so we can still say who
supported us in 2022.

src/data/site.ts replaces every hardcoded constant the legacy pages carried:
the four external URLs from data/config.ts, both Google Calendar IDs, the GA4
measurement ID, the workspace address and directions link, socials and contact.

Schemas stay flat on purpose (D2) — strings, enums, booleans, dates, numbers,
images — which is what keeps a git-backed CMS a later addition rather than a
restructuring. events therefore carries locationName/locationAddress instead
of a nested object.

Two things worth knowing:

Collection names avoid slashes (frcRobots, not frc/robots) while the content
still nests under frc/ and fll/ on disk as D18 requires. Astro writes each
collection's editor JSON schema to .astro/collections/<name>.schema.json
without creating intermediate directories, so a slashed name warned on every
build and silently dropped frontmatter autocomplete.

news/template.md is a real entry with draft: true rather than a glob-excluded
_TEMPLATE.md. An excluded template drifts from the schema unnoticed and leaves
the collection empty, which warns on every build; as an entry it is validated
and still never renders.

"Test Sponsor" was deliberately not migrated — it is the one commented-out
legacy entry that is a test fixture, not a former sponsor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRfxMh7FLjQtDbb1BEsCbR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant