Codex — Site Build Plan
The rendering layer. Reads content/, produces a learner-facing site. Built on Neutron (TS) with markdown + math + tier filtering. Persists state to Nucleus once we wire that in (later).
Read OVERVIEW.md §10 (platform strategy) and docs/specs/UNIT_SPEC.md (canonical content format) before this.
1. Architecture decisions
| Decision | Choice | Why |
|---|---|---|
| Framework | Neutron (TS) | Tyler's framework; dogfood pressure-tests it |
| Location | tystack/typescript/apps/codex/ |
Sibling of existing apps; benefits from shared toolchain |
| Content source | codex/content/ symlinked into src/content/units/ |
Single source of truth at codex repo root; site is a view |
| Renderer | Preact SSR via Neutron's existing pipeline | Matches site app pattern |
| Math rendering | KaTeX via remark-math + rehype-katex |
SSR-friendly, lighter than MathJax, standard |
| Code highlighting | Neutron's built-in (syntaxHighlight: css-variables) |
Already configured in site app |
| Routing | File-based, Astro-style | Follows apps/site/ pattern |
| Persistence | Nucleus eventually (Phase D) | Reading progress; for v0.1 use localStorage |
| Build adapter | adapterStatic for v0.1 |
No SSR runtime needed for browse-only experience |
2. Routes
/ — Codex landing
/about — what Codex is, who it's for, tier model
/units — full unit index (filterable)
/u/[id] — single unit page (e.g. /u/03.09.02)
/concepts — browse concept catalog
/concepts/[id] — single concept page
/sources — reference archive index (browse what we cite)
/sources/[source] — list passages from one source
/dag — dependency graph visualisation (v0.5)
/search — RAG-backed search (v0.5, gated on RAG running)
For v0.1 pilot: /, /about, /units, /u/[id], /concepts. The rest are stubs.
3. Content collection schema
Zod schema at src/content/config.ts mirroring docs/specs/UNIT_SPEC.md frontmatter exactly.
defineCollection({
schema: z.object({
id: z.string().regex(/^\d{2}\.\d{2}\.\d{2}$/),
title: z.string(),
slug: z.string(),
section: z.string(),
chapter: z.string(),
concept_catalog_id: z.string(),
prerequisites: z.array(z.string()),
successors: z.array(z.string()).default([]),
tier_anchors: z.object({
beginner: z.union([z.string(), z.literal("deferred")]),
intermediate: z.union([z.string(), z.literal("deferred")]),
master: z.union([z.string(), z.literal("deferred")]),
}),
tiers_present: z.array(z.enum(["beginner", "intermediate", "master"])),
pending_prereqs: z.boolean().default(false),
references: z.array(z.object({
source: z.string(),
path: z.string(),
locator: z.string().optional(),
pending: z.boolean().optional(),
pointer: z.string().optional(),
})),
lean_module: z.string().optional(),
lean_status: z.enum(["full", "partial", "none"]),
lean_mathlib_gap: z.string().optional(),
human_reviewer: z.string().optional(),
estimated_time: z.record(z.string()),
status: z.enum(["draft", "review", "approved", "shipped"]),
produced_by: z.string(),
reviewed_by: z.array(z.string()).default([]),
}),
});
If frontmatter doesn't match the schema, build fails. (Same belt-and-suspenders as the rubric validator.)
4. Tier filter mechanism
Single source of truth = the markdown body with [Beginner] / [Intermediate+] / [Master] markers on ## headings. The renderer filters at render time.
Implementation:
- Markdown is parsed into HTML at build time (via remark + rehype) with section markers preserved as
data-tierattributes on<section>wrappers around each H2 group. - A small Preact island reads
localStorage.codex_tier(beginner|intermediate|master), defaults to whichever is the lowest-index tier intiers_present, and togglesdisplay: noneon sections whosedata-tierdoesn't include the current tier. - Tier toggle UI in the global header — three radio buttons.
- For pilot units with
tiers_present: [master], the toggle is non-interactive (greyed out, "this unit is Master-only" tooltip).
5. Cross-reference resolution
[ref: source locator]inline citations → render as superscript clickable footnotes; clicking opens a side panel with the citation details and a link to the archive file (or a "pending source" badge ifpending: true).[unit-id]cross-references → if the target unit hasstatus: shipped, render as<a href="/u/<id>">title</a>; otherwise render as<span class="pending">unit-id (pending)</span>with a tooltip.
Both resolved at build time via remark plugins so the static HTML is correct.
6. Math rendering
remark-math + rehype-katex in neutron.config.ts:
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
export default defineConfig({
runtime: "preact",
adapter: adapterStatic({ precompress: true }),
markdown: {
remarkPlugins: [remarkMath],
rehypePlugins: [[rehypeKatex, { strict: "ignore" }]],
syntaxHighlight: { theme: "css-variables" },
},
});
Plus KaTeX CSS in the layout: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css">.
7. Component inventory
| Component | Purpose |
|---|---|
<Header> |
Tier toggle + nav |
<TierBadge> |
"Master-only" / "All tiers" pill in unit pages |
<UnitMeta> |
Sidebar with prereqs, anchors, references, lean status |
<TierSection> |
Wraps a section with data-tier for client-side filtering |
<Citation> |
Inline cite renderer (ref) |
<UnitLink> |
Cross-ref to another unit, with pending state |
<Callout> |
"Counterexamples to common slips" / "Connections" / etc. |
<LeanCode> |
Lean code block with appropriate styling |
<TierToggleIsland> |
Client-side island; the only JS in v0.1 |
8. Phases
Phase A — Scaffolding (this session)
- Create app dir; copy site app's
package.json,neutron.config.ts,vite.config.ts,tsconfig.jsonas starting point. - Install math plugins.
- Symlink
codex/content/→apps/codex/src/content/units/. - Define content collection schema.
- Verify
pnpm buildsucceeds.
Phase B — Pages
- Landing page (
index.tsx). - Unit page (
u/[id].tsx) with full UNIT_SPEC layout. - Layout with tier toggle island.
- Unit index (
units/index.tsx).
Phase C — Polish
- Citation footnote panel.
- Cross-ref resolution (with pending badge).
- Concept catalog browse.
- CSS theme (typography, math, code blocks).
Phase D — Site v1 features
- Reading-progress persistence (Nucleus once leak-free Nucleus deployed).
- DAG visualisation.
- RAG-backed search (gated on RAG running).
- User accounts (v1.5).
9. Validation gate
The site build is gated on all unit content passing validate_unit.py. Add to package.json build script:
"build": "python ../../../codex/scripts/validate_all_units.py && neutron-ts build"
(We'll write validate_all_units.py as a thin wrapper around validate_unit.py.)
10. Risks / gotchas
| Risk | Mitigation |
|---|---|
KaTeX doesn't render some LaTeX (e.g., \xhookrightarrow) |
Fall back to inline raw LaTeX with a CSS class; track gaps |
| Symlink across repos fails on Windows | Document; for now we're on macOS, not a blocker |
| Neutron content collection doesn't follow symlinks | Test in Phase A; if blocked, copy on build instead |
| Cross-ref target unit not yet published | Fail gracefully — render as "(pending)" badge |
| Tier filter requires JS (loses progressive enhancement) | Default page renders all sections of tiers_present; JS only hides per user pref |
[Master] markers in markdown might conflict with link syntax [text] |
Only match section-heading-level markers; use a more specific regex |
11. Success criteria for Phase A (visual confirmation)
pnpm devstarts a local server.- Visiting
/u/03.09.02renders the Clifford algebra unit. - LaTeX displays correctly (inline
$\mathrm{Cl}(V, q)$and display$$ ... $$). - ABS classification table renders.
- Frontmatter metadata visible in sidebar (prereqs, anchors, references, lean status).
- Tier toggle widget in header (works in this case = "Master-only" indicator since unit is Master-only).
- Citations and cross-references render as styled spans (clickability is Phase C).
This gives Tyler a real visual to react to.
Phase A starts now. Subsequent phases wait on review of Phase A output.