Mosaic one model, many lenses

ADR 0004: Renderers are thin — they print, they do not decide

  • Status: accepted
  • Date: 2026-09-23

Context

If a renderer derives a fact (a route, a default, a name), it becomes a hidden second source of truth. The system then has as many "models" as it has lenses.

Decision

The public entry point of mosaic-render is render(plan: &ResolvedPlan, user_src: &BTreeMap<String, String>) -> Rendered. It takes the resolved plan and nothing else. The dependency graph enforces the direction: render -> core -> grammar; core never depends on render. A test parses crates/mosaic-core/Cargo.toml and fails the build if a mosaic-render dependency ever appears.

Consequences

  • Adding a lens cannot introduce a new decision point.
  • The plan type is the entire contract between the compiler and the lenses; it is small enough to read in one sitting.

Proof

cargo test -p mosaic-render --test thin_renderers → core_never_depends_on_render (dependency-graph proof) and render_entry_takes_a_resolved_plan (signature proof, compiled).