Presentation-Spec as the Config-Driven Rendering Surface — One Declared Panel Model, Many Surfaces, No Engine Fork to Extend
Accepted
Context
The constellation-memory work produces several things a project will want to SEE: a chronicle/timeline (component B), a roadmap (backlog roadmap + positioning roadmap, ADR-051), a criteria view (ADR-049), history and dependency graphs. The naive path is to write a bespoke Tera page, handler, and route per view, cloning graph.html each time and hard-coding the data source and the link wiring in Rust. That path has two costs the protocol has repeatedly refused. First, it puts presentation logic in Rust, so a DOMAIN project that wants its own panel (a difusion roadmap, a custom board) must fork the engine — violating Protocol-not-Runtime and the voluntary, additive adoption model (ADR-029). Second, it makes the rendered surface drift from the declared knowledge: the panel becomes a second source of truth in Rust rather than a projection of NCL. ADR-046 established the precedent that resolves this — a level, a UI panel, and an MCP tool are all the same declared substrate-view consumed via the surfaces field, not bespoke code — and the existing graph.html already renders an NCL-derived graph generically through the NclCache pipeline (NCL → JSON → {{json|safe}} → Cytoscape). ADR-034 (Proposed) generalizes the catalog with a kind discriminator so non-Rust operation kinds are declarable. What is missing is the analogous declarative model for PRESENTATION: a spec the engine reads generically so chronicle/roadmap/criteria/graph panels render across CLI/UI/GraphQL/md without per-panel Rust, and a domain project extends by shipping its own panel NCL file.
Decision
Establish a presentation-spec (schema .ontoref/reflection/schemas/presentation.ncl) as the single declarative panel model the engine reads generically, so adding a panel is dropping an NCL file, never editing Rust. Four mechanisms. (1) ONE PANEL MODEL — a Panel declares { id, title, kind ([| 'Timeline, 'Gantt, 'Graph, 'Table, 'Board |]), data_source ({op, params} naming a catalog operation, ADR-024/030), surfaces (which interfaces expose it), reference_links ([{from_field, to_kind, resolver}] for click-through wiring), filters, actors }; the engine renders any panel from this declaration through the existing NclCache → JSON pipeline (graph.html precedent) with no panel-specific code. (2) GENERIC RENDER, NO PER-PANEL RUST — the render path branches on kind, not on panel identity; the day a specific panel needs bespoke Rust is the day the model has failed, so a new panel-*.ncl appears in CLI, UI, and GraphQL with zero Rust changes (the keystone verification). (3) DUAL-PATH BRIDGE, ONE LIST — panels may be declared either as dedicated .ontoref/presentation/panel-*.ncl files OR referenced from config.ncl quick_actions via a panel_ref, OR surfaced from a substrate-view surface; all three NORMALIZE to one panel list, so there is exactly one resolved set of panels regardless of declaration site. (4) DOMAIN EXTENSION WITHOUT FORK — a consumer project adds panels by shipping its own .ontoref/presentation/*.ncl; the protocol's engine renders them unchanged, and a project that ships none gets the base panels and pays nothing. The data_source MUST name a declared catalog operation, so a panel is a projection of declared knowledge, never an independent Rust query.
Constraints
- Hard The engine renders any panel generically from its presentation-spec declaration through the NclCache→JSON pipeline; the render path branches on kind, never on panel identity, so a new panel-*.ncl requires zero Rust changes
- Hard Every Panel.data_source names a declared catalog operation (ADR-024/030); a panel is a projection of declared knowledge and never an independent Rust query
- Hard Panels declared as dedicated panel-*.ncl, referenced via quick_actions.panel_ref, or surfaced from a substrate-view surface all normalize to exactly one resolved panel list; no declaration site forms a separate registry
- Soft A consumer project adds panels solely by shipping .ontoref/presentation/*.ncl rendered by the unchanged engine; a project that ships none gets the base panels and carries no mandatory presentation schema
Alternatives considered
- A bespoke Tera page + handler + route per view, cloning graph.html each time — rejected: Puts presentation logic in Rust, forcing a domain project to fork the engine to add a panel (violating Protocol-not-Runtime and additive adoption) and letting the rendered surface drift from declared NCL. The spec keeps panels declarative and generically rendered.
- Let each panel's data_source be an arbitrary Rust query rather than a declared catalog operation — rejected: Makes the panel an independent source of truth in Rust, drifting from the declared knowledge graph. Requiring a declared catalog operation (ADR-024/030) keeps every panel a projection of ontology.
- Allow only dedicated panel-*.ncl files; drop the quick_actions / view-surface paths — rejected: Ignores that panels are legitimately referenced from quick_actions and substrate-view surfaces today; forbidding those paths fragments the surface. The dual-path bridge normalizes all three into one list instead of forbidding two.
- Branch the render path on panel id with per-panel special cases — rejected: Reintroduces per-panel Rust by the back door and guarantees drift as panels accumulate. Branching only on kind keeps the engine generic; a new panel needs no engine change, a new visualization needs only a new kind.
- Make the presentation-spec mandatory for all adopters — rejected: Raises the adoption floor, collapsing formalization-vs-adoption toward mandatory gates. A project that ships no panels gets the base set and pays nothing; the spec is opt-in expressiveness.
Anti-patterns
- Per-panel Rust — A panel's data or click-through links are hard-coded in Rust, or the render path branches on panel identity, so adding or changing a panel requires editing the engine and the surface drifts from declared NCL.
- Panel as an independent query — A panel's data_source is an arbitrary Rust query rather than a declared catalog operation, making the panel a second source of truth that can diverge from the knowledge graph.
- Divergent panel registries — Panels declared via dedicated files, quick_actions, and view surfaces are resolved by separate code paths into separate lists, so the set of panels depends on where you look.
- Presentation mandatory at the floor — The presentation-spec is required of every adopter, raising the adoption floor instead of adding opt-in expressiveness at the top of the curve.
Related ADRs
ADR-046 · ADR-034 · ADR-024 · ADR-030 · ADR-029 · ADR-018 · ADR-049 · ADR-051