Forge
markdowndeeb25a2
1<!-- English translation of adr/0050-declarative-instrument-zone-placement-toml.md. Canonical Russian: ../../adr/0050-declarative-instrument-zone-placement-toml.md -->
2
3#ADR 0050: Declarative map "tool → zone/slot" in TOML
4
5**Status:** Accepted · Implemented
6**Date:** 2026-04-16
7
8## Related ADRs
9
10| ADR/document | Role |
11|----------------|------|
12| [0017](0017-multi-window-workspace-and-agent-surfaces.md) | `presentation` - display topology, not slots |
13| [0047](0047-cockpit-instrument-descriptor-and-slot-composition.md) | `instrument_id` + `slot_id` |
14| [0028](0028-user-settings-toml-localappdata-and-secrets.md) | Personal `settings.toml` |
15| [0010](0010-ui-modes-toml-configuration.md) | UiModes, bundles |
16| [0036](0036-cds-channel-compositor-surface-pipeline.md) | CDS, surface tools |
17| [`cds-contract-v0.md`](../../design/cds-contract-v0.md) | CDS drawing |
18| [`[code_navigation]` in samples](../samples/settings.toml) | Layer analogy: bundle + repo + user |
19
20## Summary
21
22- Declarative map **tool → zone/slot** in TOML.
23- Merge bundle/repo/user; `InstrumentPlacementRuntime` + compositor.
24
25
26### Implementation snapshot
27
28| Element | Meaning |
29|---------|----------|
30| TOML | `[instrument_routing]`, merge bundle/repo/user |
31| Runtime | `InstrumentPlacementRuntime`, `MainWindowHostSurfaceCompositor` |
32
33---
34## Context
35
36Today **where is which cockpit tool** (decision tree vs Semantic Map vs mount preview, etc.) in the main window is set by **composer logic** (`MainWindowHostSurfaceCompositor`, `DefaultSurfaceSlotInstrumentBindingProvider`, placement rules) and Avalonia bindings. The **`presentation`** line specifies **how many anchors and on what screens**, but **not** the declarative choice "in slot `pfd` show tool A or B" for a specific repository or user.
37
38Need: **separate presentation topology** from **filling attention slots** and allow the team or user to **override the instrument map** without code edits - in the spirit of the cockpit data already used for CDS ([0047](0047-cockpit-instrument-descriptor-and-slot-composition.md)).
39
40## Solution
41
42**Adopt** a separate **configurable layer** - **tool placement map** for the main cockpit slots, stored in **TOML** and merged according to fixed priority rules.
43
44**Invariant 1 - border with `presentation`:** line **`presentation`** / **`[presentation_grammar]`** ([0017](0017-multi-window-workspace-and-agent-surfaces.md)) still only defines **topology** (how many groups of `(...)`, which anchors are on which display, optional column weights). The tool map **doesn't** set the number of monitors and **doesn't** replace anchors; runtime matches **semantic slots** (`pfd_primary`, `mfd_primary`) with specific **`surface_id` + `slot_id`** pairs within the product (including when changing the MFD host window), without the requirement to write `surface_id` in the user/repository TOML.
45
46**Invariant 2 - public values (alias):** in `workspace.toml` and in `[display.instrument_routing]` **human-readable** tokens are specified, which runtime leads to canonical **`instrument_id`** (`CockpitStandardInstrumentIds`):
47
48| Meaning in TOML | Canonical `instrument_id` |
49|----------------|------------------------------|
50| `solution_explorer` | `solution_explorer_tree` |
51| `workspace_map` | `workspace_navigation_map` |
52| `ide_health` | `ide_health_status_v1` |
53
54It is also acceptable to specify **already canonical** `instrument_id` (as in CDS), if you need a copy-paste from the diagnostics.
55
56**Invariant 3 - merge layers and conflict of one key:**
57
58Three data sources (from general to specific):
59
601. Built-in **bundle** IDE (grocery card default).
612. **Repository** layer - **`.cascade/workspace.toml`** with merge on top of the bundle ([0021](0021-pfd-mfd-cockpit-attention-model.md) §2.1).
623. **User** layer - **`[display.instrument_routing]`** in `%LocalAppData%\CascadeIDE\settings.toml` ([0028](0028-user-settings-toml-localappdata-and-secrets.md)).
63
64**Within one table layer** `[instrument_routing]` keys are unique (`pfd_primary`, `mfd_primary`).
65
66**Between sources** default: **user layer is stronger than repository layer, repository layer is stronger than bundle** (`user > repo > bundle`) for the same slot semantics.
67
68**Flag in custom `settings.toml`:** **`prefer_repo_instruments_placement`** (bool). If **`true`** for matching keys, the **repository/bundle** layer wins; if **`false`** or if the key is not specified - **`user > repo`**.
69**Invariant 4 - validation:** unknown slot key, unknown alias/`instrument_id` - **explicit diagnostics** when loading settings (validation `[display]`); for workspace - debug log when a line is ignored.
70
71**Invariant 5 - without `surface_id` in public TOML:** user and repository set only **`[instrument_routing]`** / **`[display.instrument_routing]`**; mapping to `main_window_docked_grid` / `main_window_plus_mfd_host_top_level` surfaces is done in `InstrumentPlacementRuntime`.
72
73### Public v1 contract
74
75**Bundle/repo** (`UiModes/workspace.toml`, `.cascade/workspace.toml`):```toml
76[instrument_routing]
77pfd_primary = "solution_explorer"
78mfd_primary = "workspace_map"
79```
80**User** (`settings.toml`):```toml
81[display.instrument_routing]
82pfd_primary = "workspace_map"
83```
84- `pfd_primary` and `mfd_primary` are product level keys.
85- Values ​​are **alias** from the table above or the canonical `instrument_id`.
86
87Low-level array `[[instrument_placement_rules]]` with fields `surface_id` / `slot_id` **not used** in public contract v1 (no external consumers; DX - only table above).
88
89## Why not just code
90
91- **Repositories** differ in what is considered "main" in the PFD (map vs tree vs both in different presets).
92- **Flight** experiments and product modes should switch the map with **data** rather than branches with different composers.
93- **Agents and observability:** CDS already projects a list of tools ([0036](0036-cds-channel-compositor-surface-pipeline.md)); a single source in TOML makes it easier to reconcile “what’s claimed” and “what’s in the snapshot.”
94
95## Alternatives (rejected as v1 main path)
96
97| Alternative | Why not the basic choice |
98|--------------|------------------------|
99| Only changes to `MainWindowHostSurfaceCompositor` | There is no user and repo layer without a fork. |
100| Extend only `presentation` with tool literals | Mixing display topology and widget selection; the line will become unreadable and break the parser's meaning. |
101| JSON only in settings | TOML is already canon for IDEs and workspaces; one style is preferable. |
102
103## Consequences
104
105- Host composer reads **effective map** after merge.
106- Tests: unit to merge dictionary, alias resolver, scenario “another tool in PFD with the same `presentation`”.
107- Documentation: samples next to `settings.toml`.
108
109## Implementation status
110
111**Implemented**:
112- bundle/repo: `UiModes/workspace.toml` + `.cascade/workspace.toml` via `UiWorkspaceTomlMerger` and `UiModeCatalog.ApplyRepositoryWorkspaceTomlOverlay`;
113- user: `[display.instrument_routing]` and `prefer_repo_instruments_placement` in `%LocalAppData%\CascadeIDE\settings.toml`;
114- single runtime: `InstrumentRoutingAliasResolver`, `InstrumentPlacementRuntime`, `MainWindowHostSurfaceCompositor`.
115
116## Open questions
117
118- **Numerical priority** for a string is not required in v1: a **merge** layer between the bundle/repo/user is sufficient; if fine-tuning is necessary in one file, later, in a separate field.
119- Is it necessary to version the section schema (`schema_version` inside the block) for migrations.
120- Connection with the **mount layer** (`InstrumentMountPolicyRules`, `use_skia_instrument_mount`): **orthogonal** - style remains about the visual, the map from this ADR is about *which* `instrument_id` is in the slot.
View only · write via MCP/CIDE