Level-Aware About — The /about Surface Branches on Level Kind and Projects the Level's Own Graph
Accepted
Context
ADR-057 made the SITE a projection of the positioning graph: three doors, projected from spine.ncl, hook-static / prize-live, route-to-live-graph. That ADR is scoped to the difusión vestibule — the doors and their value-props.
The /about route was untouched by it. It rendered a single texts-driven template (about.j2): a personal CV (the jpl level). But the About surface is where a level presents WHAT IT IS, and that differs by the kind of level: a personal ontology (jpl) presents a CV and links; a project (ontoref) should present its own identity — history, defs (axioms/tensions/practices), graph, ADRs, and the catalog of verifiables. The same complaint that opened ADR-057 ("the site content does not correspond to the project") applies to /about: it showed a person where a project should show itself.
Three facts shaped the decision. (1) The data already exists and is queryable daemon-free: defs from core.ncl, the ADR set, the catalog of operations + validators. (2) The htmx site renders /about through a Tera template, NOT the Leptos UnifiedAboutPage — so making /about level-aware is a Tera handler + template change, not a component rewrite. (3) The site already has a hot-reload pattern for request-time graph data: content_graph.json is read by an mtime-cached loader, so a regenerated artifact takes effect with no restart. The About can consume the same way, which keeps the projection current without coupling the page to a live daemon.
Decision
The /about route is level-aware: it branches on the level's kind and, for a project, projects the level's own .ontoref/ graph. This EXTENDS ADR-057's reveal principle (hook-static / prize-live, projection-not-authoring, route-to-live-graph) to a second surface; it does not restate it. What is new here:
A projection contract per level — .ontoref/positioning/about.ncl, typed by about-schema.ncl, with a discriminant `kind = 'Personal | 'Project` and a coherence predicate (kind ⇒ the matching block present, the other absent). The contract declares SELECTIONS, not data: which axioms/tensions/practices to surface, which ADR statuses to digest, which catalog kinds — the data itself is queried from core.ncl / the ADR set / the catalog at projection time. Sparse .ontoref/ yields a partial About, never an error.
One view-model, two consumers — a single daemon-free projector (gen-about-pages) assembles a view-model from about.ncl + .ontoref/. `--out` renders a standalone static page (the marketing/web surface); `--emit-json` writes about.json, the SSR consumption artifact.
Hot-reload view-model, not a baked page — the /about handler reads about.json on an mtime check (mirroring content_graph.json). When the artifact declares kind == "project" the handler renders about_project.j2 with the view-model; otherwise (absent, malformed, or kind == "personal") it falls back to the existing texts-driven about.j2. Regenerating about.json updates live /about with no restart; only handler-LOGIC changes need a rebuild. Data changes never do.
Personal stays as it was — the personal level's About is unchanged: the same texts-driven about.j2. Level-awareness is additive; it does not touch the surface that already worked.
The live graph remains the only daemon-dependent element of the project About — the reveal prize, per ADR-057. Everything else renders from the build-time projection, daemon-free.
Constraints
- Hard The /about route MUST branch on the level kind: a project level renders the projected project About; a personal level (or absent/malformed projection) MUST fall back to the existing texts-driven personal template. The personal surface MUST stay the default and untouched.
- Hard The project About MUST be projected from .ontoref/positioning/about.ncl, which declares SELECTIONS (which defs/ADR-statuses/catalog-kinds), never inlined identity data. The data MUST be queried from core.ncl / the ADR set / the catalog at projection time so a source change regenerates the About.
- Soft The SSR /about MUST consume the projection as a hot-reload view-model artifact (about.json) read at request time on an mtime check, NOT baked into the binary. Regenerating the artifact MUST refresh /about with no restart; only handler-logic changes may require a rebuild.
Alternatives considered
- Keep /about personal; put the project About at a separate route (a content-kind page) — rejected: This is the cheaper Option A and remains valid as the standalone static web surface. Rejected as the CANONICAL answer because /about itself would not become level-aware — a visitor to a project site's real /about would still get a person. Level-awareness of the canonical route is the point.
- Project the About live from the daemon at request time (a daemon HTTP client in the handler) — rejected: Couples /about to a running daemon, breaking the hook-static/prize-live split (ADR-057) and local-fallback (ADR-029): the page goes dark when the daemon is down. The hot-reload artifact gives most of the currency benefit while keeping the page daemon-free; the live graph stays the only daemon element.
- Rewrite /about as the Leptos UnifiedAboutPage with the projection embedded — rejected: The htmx site renders /about through Tera, not the Leptos component. A Leptos rewrite is a far larger blast radius for no benefit over a Tera handler + template that reads the view-model.
- Bake the project About into the binary at build (no runtime artifact) — rejected: Every .ontoref/ change would then require a binary rebuild + redeploy to refresh identity. The mtime-cached artifact (content_graph.json pattern) decouples data refresh from binary builds — regenerate about.json, no restart.
Anti-patterns
- Showing a Person Where the Project Should Show Itself — A project level's /about renders a generic CV / personal template instead of projecting the project's own history, defs, graph, ADRs, and catalog. The About does not correspond to the level — the same gap ADR-057 closed for the doors.
- Hand-Authoring the Project About — The project About is written as site content (defs, ADR list, history) rather than projected from about.ncl + queried from .ontoref/. The copy drifts from the ontology it claims to present.
- Baking the About Into the Binary or Gating It on the Daemon — Either the project About is compiled into the server (so every .ontoref/ change needs a rebuild) or it is fetched live from the daemon at request time (so the page goes dark when the daemon is down). Both break the hot-reload / local-fallback discipline.