Forge
markdowndeeb25a2
1# ADR 0053: Intent map and control flow on PFD (control flow)
2
3**Status:** Accepted · Implemented
4**Date:** 2026-04-17
5
6## Related ADRs
7
8| ADR / artifact | Role |
9|----------------|------|
10| [0021](0021-pfd-mfd-cockpit-attention-model.md) | PFD as instruments, attention zones |
11| [0051](0051-intent-based-attention-routing-toml.md) | Attention routing |
12| [0055](0055-skia-instrument-composition-pipeline.md) | Skia pipeline |
13| [0056](0056-semantic-map-pipeline-adoption.md) | Pipeline adoption on the map |
14| [0039](0039-workspace-navigation-affordances.md) | MCP, subgraph |
15| [0065](0065-instrument-categories-domain-taxonomy.md) | Code intent map |
16| `CodeNavigationMapSubgraph*` | Subgraph models in code |
17
18## Summary
19
20- **Intent map** on PFD: control flow, subgraph, KISS navigation around a code anchor.
21- Shared Skia pipeline and cursor semantics ([0056](0056-semantic-map-pipeline-adoption.md)).
22- Not to be confused with GitMap ([0062](0062-git-submodules-semantic-map-subgraph.md)) or the full solution tree.
23
24---
25## Context
26
27The semantic map in the PFD zone today relies on a **dependency graph** (symbols, calls, links between artifacts). When editing **a single method** `A()`, it is useful to see not only “who connects to whom”, but also the **execution route** inside the method: conditional calls, shared tail after branches.
28
29Example intent:
30
31```csharp
32void A() {
33 if (cond) B();
34 else C();
35 D();
36}
37```
38
39On the map (simplified): **B** on the conditional path, **C** on the alternate branch, **D** as a merge point / shared continuation — without turning the screen into a full CFG or a copy of editor text.
40
41Aviation analogy: **waypoints** and transition conditions on a navigation display, not a printout of every procedural page.
42
43---
44
45## Goals
46
471. Visualize **control-flow intent** inside the selected method where it helps scanning (Flight / “thin” mode), not syntax for its own sake.
482. Preserve **KISS**: do not overload the PFD with condition text and decorations by default.
493. Prepare a **data contract** (subgraph / JSON / MCP) so edges and, when needed, nodes carry **link type** and optional hints for hover / drill-down.
50
51## Non-goals (first phase)
52
53- Full rendering of the **entire** Roslyn CFG on the mini-map.
54- Duplicating the **body** of `if (…)` as a permanent label on the PFD.
55- Visualizing every local assignment and small noise inside branches.
56
57---
58
59## Display principles (keep the UI clean)
60
611. **Semantic compression**
62 By default: a **branch icon** (diamond / “?”) or a **thin fork** on the edge, **without** long predicate text — for **`if` and for `switch` / pattern matching** alike (see [§ Switch…](#adr0053-switch)).
63 **Condition detail** — on gaze dwell / hover / a separate layer (Skia tooltip), optional.
64
652. **Only meaningful “external” route points**
66 Priority: calls to **other methods** and heavy operations the user scans for. Local noise (`i++`, empty `return` with no map meaning) — do not lift onto the graph without an explicit policy.
67
683. **Thin lines and metaphors**
69 - **Loop** (`for` / `while`): see [§ Loops: loop on the edge](#adr0053-loop-edge).
70 - **`try` / `catch`**: later — a “protected” segment style (e.g. “umbrella” / unstable node outline) once vocabulary is agreed.
71
724. **Vertical “flight plan”**
73 Meaningful step nodes top to bottom, branches **to the side**, without IDE-style blocks filling the screen.
74
75<a id="adr0053-loop-edge"></a>
76
77### Loops (for / while): loop on the edge
78
79**We do not draw** a separate “loop graph” like a classic flowchart (diamond + back arrow duplicating nodes). **We draw a loop on the link line** to a meaningful step inside the method (e.g. call `B()` in a `for`/`while` body): the line to the node is **not straight** but makes an **elegant coil** around the direction toward that node — in spirit like a **holding orbit** on a nav display, not “yet another rectangle”.
80
81User meaning: **immediately obvious** — “this call spins here”, without reading code in Forward and without unrolling iterations on the map.
82
83**Render (direction):** thin **neon** lines on the dark PFD background, **smooth curves** (Bezier / cubic splines in Skia), visually part of the **cockpit** layer, not an old flowchart. The central line is the conditional “main flow” of method `A()`; the coil is **on the edge** to a dependent step, not a decorative layer over the whole graph.
84
85**Optional (when analysis allows a heuristic):** loop “weight” or expected iteration count — **coil density** (tighter spiral, slightly brighter/thicker line, etc.), without promising exact `n` on the PFD.
86
87**Contract:** an edge with loop semantics carries a flag like `Loop` / `LoopCall` plus optional style metadata (see table below); coil coordinates are **derived from layout** (StarGraph / force / other engine) so curves do not cover neighboring nodes — Bezier control-point tuning is a separate task.
88
89<a id="adr0053-switch"></a>
90
91### Switch, `case`, and pattern matching
92
93**Include** in the same semantics as `if` / `else`: this is flow branching, not a “special case off the map”. **Do not expand** all branches as a full table on the PFD.
94
95- **Few meaningful outcomes (guide 2–4):** fan of edges from one fork point to meaningful steps; **`case` / guard** labels — optional, on hover or a second layer.
96- **Many branches or empty-only cases:** **compression** — one multi-way branch node/icon; outward only calls that matter; the rest — aggregate (“other branches” / `default`) or hidden until drill-down.
97- **Pattern matching** (`switch` expression, `when`): do not duplicate long expressions on permanent labels; same principle as for `if` predicates.
98
99Contract link: edge type like **`MultiBranch`** / **`ConditionalCall`** from the common fork ancestor to a step; branch detail in optional metadata.
100
101---
102
103## Data and contract (direction)
104
105Existing subgraph models (`CodeNavigationMapSubgraphNode`, `CodeNavigationMapSubgraphEdge`) are extended meaningfully, for example:
106
107| Idea | Purpose |
108|------|---------|
109| Edge type | `Call`, `ConditionalCall`, `Merge`, `MultiBranch` (several outcomes: `switch`, `if` chain, pattern matching), `Loop` / `LoopCall` (loop on edge to a step inside a loop; not to be confused with low-level CFG `LoopBack` if needed separately) |
110| Short label | Optional; do not duplicate full `cond` text. |
111| Condition detail | Optional, for tooltip / second layer; may be compressed or deferred. |
112
113Exact field names and JSON — **to be fixed** after a generator prototype (Roslyn / Control Flow Analysis + call filtering).
114
115Analysis source in the stack: **Roslyn** (`ControlFlowAnalysis` and binding calls to branches), without mandatory reliance on text-only grep.
116
117---
118
119## Agreements (draft)
120
121### Predicate on edge vs icon only
122
123The boundary between “show a **short predicate**” and “**icon only**” is set by **user settings** (app / workspace — specific key and merge with bundle to be fixed at adoption), **not only** by a hard tie to a UI mode like Flight.
124
125The **agent** path (MCP, context / navigation subgraph request) uses the **same** detail level: in product terms the agent is also a **user** of this view and does not live in a separate hidden mode bypassing settings, except with an **explicit** call-parameter override (one-request override).
126
127### Map type: `controlFlow` vs “classic”
128
129A separate **UiMode** preset (e.g. Flight only) **is not required** for a single CF-aware map: **which semantic map view to build** is the **user’s** choice, orthogonal to cockpit mode. **Flight** remains the PFD layout polygon; **map level/type** is set separately.
130
131Draft TOML shape (values and merge with bundle / workspace — at adoption):
132
133```toml
134[semantic_map]
135level = "controlFlow" # control flow inside a method (CF-aware subgraph)
136# level = "file" # classic dependency / file slice (baseline intent map)
137```
138
139The same switch should be **reachable by the agent** (field in MCP / echo in subgraph request), without separate hidden semantics.
140
141### Subgraph JSON versioning (MCP / CLI)
142
143**Backward compatibility when adding fields is not yet guaranteed:** the product **has no users yet**; subgraph JSON and MCP/CLI may **change shape in sync with code** without a migration policy. When stable consumers appear outside the repo (integrations, long-lived agent contract), introduce explicit **schema versioning** and compatibility rules **in a separate decision** (ADR / MCP-PROTOCOL add-on).
144
145---
146
147## Next steps (draft)
148
1491. Fix the minimum set of edge **kinds** and call-filtering rules.
1502. Prototype: one method under the cursor → simplified flow → the same JSON the intent map on the PFD already consumes.
1513. Render: line and node styles on PFD / Skia without mandatory condition text on permanent labels.
152
153## Backlog intent (add incrementally)
154
155To keep the map intent-focused and avoid turning into a full CFG, introduce extensions in portions:
156
1571. **Early/abrupt exits**: `ThrowExit`, `Break`, `Continue`.
1582. **Async boundaries**: `AwaitBoundary` (pause/resume point of the flow).
1593. **Short circuit**: `ShortCircuit` for `&&` / `||` in guard conditions.
1604. **Pattern guard**: explicit semantics for `when` in pattern matching / switch.
1615. **Exceptions**: coarse `ExceptionFlow` for `catch`/`finally` without expanding full exception-CFG.
1626. **Iterators**: `YieldExit` (`yield return` / `yield break`) as a separate exit type.
1637. **Detail policy**: separate “operator noise” and “external steps” via declutter policy (e.g. helper calls inside arguments).
164
165*(Sections below may be added: JSON examples, screenshots, performance limits, links to external discussions.)*
166
View only · write via MCP/CIDE