Forge
markdowndeeb25a2
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
26Skia 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
28That produced two systemic effects:
29
301. Blurred responsibility boundary: “surface” and “instrument internals” get conflated in discussion and code.
312. Every new Skia feature risks reinventing its own mini-pipeline without shared invariants.
323. 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
41Each Skia instrument fixes an internal sequence:
42
431. **Intent**: build the instrument’s domain state model (source of truth for content).
442. **Declutter**: policy filtering/aggregation and prioritization before geometry.
453. **Layout**: geometric layout.
464. **Render**: draw the finished scene without business logic.
47
48The 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
53Each 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
59This 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
64Noise-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.
65Layout 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
70Below 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
721. **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
742. **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
763. **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
784. **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
805. **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
826. **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
1121. Fix the base contract for the shared Skia instrument pipeline.
1132. Canonize `CodeNavigationMapCompositor` as the first adapter of this contract.
1143. Extract a shared `DeclutterPolicy` interface (minimum: `glance`, `normal`, `inspect`) and instrument-specific implementations.
1154. 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
View only · write via MCP/CIDE