| 1 | # ADR 0055: Skia instrument composition pipeline (Intent → Declutter → Layout → Render) |
| 2 | |
| 3 | **Status:** Accepted |
| 4 | **Date:** 2026-04-17 |
| 5 | |
| 6 | ## Related ADRs |
| 7 | |
| 8 | | ADR | Role | |
| 9 | |-----|------| |
| 10 | | [0036](0036-cds-channel-compositor-surface-pipeline.md) | Channel → CDS → surface | |
| 11 | | [0047](0047-cockpit-instrument-descriptor-and-slot-composition.md) | Instrument / slot | |
| 12 | | [0049](0049-skia-surface-rollout-over-avalonia-host.md) | Phased Skia rollout | |
| 13 | | [0053](0053-semantic-map-control-flow-pfd.md) | Semantic map, control flow | |
| 14 | | [0056](0056-semantic-map-pipeline-adoption.md) | First consumer — intent map | |
| 15 | | [0064](0064-deck-primitives-visual-language-render-layer-and-palette.md) | Render stage, deck primitives | |
| 16 | |
| 17 | ## Summary |
| 18 | |
| 19 | - Shared Skia instrument pipeline: **Intent → Declutter → Layout → Render**. |
| 20 | - Common invariants for semantic map, deck, and future instruments. |
| 21 | - Consumers — [0056](0056-semantic-map-pipeline-adoption.md), [0057](0057-chat-surface-pipeline-adoption.md). |
| 22 | |
| 23 | --- |
| 24 | ## Context |
| 25 | |
| 26 | Skia instruments in CIDE (`workspace_navigation_map`, future instruments and overlays) are placed via the host surface compositor, but each instrument’s internal composition (data, declutter, geometry, render rules) was historically mixed into a single feature. |
| 27 | |
| 28 | That produced two systemic effects: |
| 29 | |
| 30 | 1. Blurred responsibility boundary: “surface” and “instrument internals” get conflated in discussion and code. |
| 31 | 2. Every new Skia feature risks reinventing its own mini-pipeline without shared invariants. |
| 32 | 3. With `controlFlow` per [0053](0053-semantic-map-control-flow-pfd.md), readability on a small viewport degrades without a separate declutter layer. |
| 33 | |
| 34 | --- |
| 35 | |
| 36 | ## Decision |
| 37 | |
| 38 | <a id="adr0055-p1"></a> |
| 39 | ### 1) Introduce one pipeline for all Skia instruments |
| 40 | |
| 41 | Each Skia instrument fixes an internal sequence: |
| 42 | |
| 43 | 1. **Intent**: build the instrument’s domain state model (source of truth for content). |
| 44 | 2. **Declutter**: policy filtering/aggregation and prioritization before geometry. |
| 45 | 3. **Layout**: geometric layout. |
| 46 | 4. **Render**: draw the finished scene without business logic. |
| 47 | |
| 48 | The surface compositor stays an outer layer and answers only “where the instrument is mounted” (slot/surface), not internal geometry/declutter. |
| 49 | |
| 50 | <a id="adr0055-p2"></a> |
| 51 | ### 2) Internal instrument compositor is mandatory |
| 52 | |
| 53 | Each instrument introduces a dedicated compositor (e.g. `*Compositor`) that: |
| 54 | |
| 55 | - selects a layout engine by domain detail level; |
| 56 | - applies viewport height/density policy; |
| 57 | - returns a finished scene and display parameters. |
| 58 | |
| 59 | This is the canonical path for new Skia features so logic is not duplicated across VMs and controls. |
| 60 | |
| 61 | <a id="adr0055-p3"></a> |
| 62 | ### 3) Declutter — shared policy layer, not layout `if`s |
| 63 | |
| 64 | Noise-reduction rules (density, repeat aggregation, main scenario over secondary detail — for graphs: main flow vs secondary edges) live in a separate policy layer with one interface across instruments. |
| 65 | Layout must not decide “what to hide”, only “how to lay out what was already selected”. |
| 66 | |
| 67 | <a id="adr0055-p4"></a> |
| 68 | ### 4) Layout / Render: readability invariants (HF / cockpit guidance) |
| 69 | |
| 70 | Below are **instrument-agnostic** rules for any Skia instrument with the §1 pipeline (chat timeline, scale, control-flow graph, schematic, etc.). Parenthetical examples illustrate a case; canonical control-flow detail — [0053](0053-semantic-map-control-flow-pfd.md). Cockpit attention model: [0021](0021-pfd-mfd-cockpit-attention-model.md), [0046](0046-presentation-layout-authority-and-cockpit-invariants.md). |
| 71 | |
| 72 | 1. **Attention hierarchy and draw order (Render).** The instrument defines a **fixed layer order** for the base scene: background/grid and “structural” primitives first, then main content, then labels and auxiliary markup within their zones. Attention signals from CDS/channels ([0036](0036-cds-channel-compositor-surface-pipeline.md)) — highlights, traces, health — draw **on top** of that base without reshaping domain geometry for an “alert”. Interactive focus (cursor, selection) is the top readable layer over static content. *(Example: graph — edges → nodes → labels in the band; chat — branch background → bubbles → timestamps.)* |
| 73 | |
| 74 | 2. **Noise budget: Declutter chooses *what*, Layout limits *how much fits*.** Declutter sets visible entities and aggregation; Layout applies **density and geometric ceilings** for the viewport (minimum gaps between blocks, row/column limits, reserves for auxiliary columns). Do not compensate overload with scale alone when policy already signals “too much” — reduce volume or detail level first. *(Example: graph — vertical step and band width; chat — visible message count and preview height.)* |
| 75 | |
| 76 | 3. **Detail levels (Glance / Normal / Inspect) — primarily Declutter, not geometry alone.** Domain detail level (including shared `glance` / `normal` / `inspect` from the adoption plan) sets **selection and aggregation before Layout**. Layout changes layout metrics but does not replace the decision “how many entities between Glance and Inspect”. *(Example: `CodeNavigationMapDetailLevel` for the map; another instrument — its own enum/scale, same role split.)* |
| 77 | |
| 78 | 4. **Readability floors in Glance (Render / theme).** When compressing the viewport, keep **lower bounds** on readability: glyph size, line weight, outline contrast — so “glance from the side” does not become indistinguishable noise. Numbers come from theme/instrument constants; invariant — **do not sacrifice distinguishability to fit the rect**. *(Example: minimum link thickness and node outlines; minimum list row height.)* |
| 79 | |
| 80 | 5. **Stable spatial roles (Layout).** Labels, legends, scales, and other auxiliary graphics **anchor to the main content zone** (adjacent area, fixed offset from the “data band”), not arbitrary viewport corners — lower cost to match label to object. *(Example: legend beside the graph band [0053](0053-semantic-map-control-flow-pfd.md); axis label at the edge of the same region as the data.)* |
| 81 | |
| 82 | 6. **Alerting on top of the base.** Channel compositors (TraceFlow, health, etc.) add an **overlay** to the instrument’s assembled base scene: they do not duplicate Intent/Layout of the domain model, only show attention/trace/status state on top — for any base instrument type. |
| 83 | |
| 84 | **Contract checks (pipeline tests):** on a small viewport — no **instrument-forbidden** layout-primitive overlaps (collisions, unreadable stacking) at the declared detail level; when moving to a deeper level (e.g. Glance → Inspect) useful information **must not shrink** without explicit policy degradation (empty state, data error). What counts as a collision — in each instrument’s contract. |
| 85 | |
| 86 | --- |
| 87 | |
| 88 | ## Consequences |
| 89 | |
| 90 | ### Pros |
| 91 | |
| 92 | - Clear architectural boundary: host surface vs instrument internals. |
| 93 | - One contract for all future Skia features and instruments. |
| 94 | - Declutter/layout/render can evolve independently without regressions in slot composition. |
| 95 | |
| 96 | ### Cons |
| 97 | |
| 98 | - More entities and contracts in the Skia instrument layer. |
| 99 | - Separate pipeline-level tests needed (not only layout unit tests). |
| 100 | |
| 101 | --- |
| 102 | |
| 103 | ## Non-goals |
| 104 | |
| 105 | - Do not change ADR [0036](0036-cds-channel-compositor-surface-pipeline.md) and [0047](0047-cockpit-instrument-descriptor-and-slot-composition.md): external surface composition stays as is. |
| 106 | - Do not fix here the final UI style of all edge cases and all instruments (that evolves within pipeline and policy). |
| 107 | |
| 108 | --- |
| 109 | |
| 110 | ## Adoption plan (minimum) |
| 111 | |
| 112 | 1. Fix the base contract for the shared Skia instrument pipeline. |
| 113 | 2. Canonize `CodeNavigationMapCompositor` as the first adapter of this contract. |
| 114 | 3. Extract a shared `DeclutterPolicy` interface (minimum: `glance`, `normal`, `inspect`) and instrument-specific implementations. |
| 115 | 4. Fix pipeline contract tests: |
| 116 | - stable scene composition for the same input; |
| 117 | - readability on a small viewport (no critical collisions/stacking per instrument contract); |
| 118 | - correct degradation on incomplete input (no false content). |
| 119 | |