ADR 0086: UI theme - canon in TOML, JSON as MCP transport (strangler from Themes/*.json)¶
Status: Proposed
Date: 2026-04-21
Related ADRs¶
| ADR | Role |
|---|---|
| 0028 | settings.toml, user path |
| 0029 | canon on disk, UI as a façade |
| 0008 | MCP contracts |
| 0079 | overlays; orthogonal to brush colors |
Outside ADR¶
| Document | Role |
|---|---|
| MCP-PROTOCOL.md | ide_get_ui_theme, ide_set_ui_theme - JSON |
Context¶
Now applying the palette to Application.Resources comes from JSON: UiThemeApply.Apply(themeJson) is the same format that MCP returns and accepts (ide_get_ui_theme / ide_set_ui_theme). The "light/dark/..." presets read Themes/*.json files next to the exe or inline text (UiThemeApply). Default brushes also live in App.axaml (CascadeTheme.*).
Along the product line (0028, 0029) man and repository rely on TOML as a single canon of settings. Two parallel “sources of truth” for colors (JSON on disk + TOML for everything else) worsen discoverability and agent ↔ user parity: the agent edits the JSON string in the tool, the human does not.
Decision (intention)¶
- Canon for a person and for a repo: description of the UI theme (brushes
CascadeTheme.*, compatible withUiThemeApply.Keys) - first of all in the usersettings.toml(or an agreed section name, for example[ui_theme]with flat or nested keys under the same semantic groups as in JSON today:main_window,editor,menu, …). The comments in the file are part of the DX, not a separate "secret" JSON.
- JSON does not disappear like the MCP contract:
ide_get_ui_theme/ide_set_ui_themeremains transport for the agent: on reading - serialization of resolved themes (merge defaults + TOML + optional overlays); on the record - use the same merge pipeline as when saving TOML from the UI. This way, “agent on equal terms” parity is maintained without requiring a human to edit the JSON manually.
- Strangler:
Themes/*.jsonfiles and built-in presets are temporary sources of defaults until the presets are transferred to the built-in TOML/resources or generated from one table; new brushes (such as the command palette) first end up inApp.axamland the TOML schema, not just in a separate JSON without a key inUiThemeApply.
- Implementation in stages: (a) TOML section parser → the same internal dictionary that is currently being built from JSON, →
UiThemeApply.Applywithout duplicating theSet(res, key, hex)logic; (b) loading at startup: mergeApp.axaml→ preset →settings.toml[ui_theme]; (c) the Theme menu and MCP call the same service; (d) MCP documentation: “canon on disk - TOML; JSON - snapshot."
Consequences¶
- One look at
settings.toml- you can see both the AI mode and the palette; fewer questions “where to change the background.” - MCP agents continue to work via JSON without changing the semantics of the
ide_get_ui_themeresponse. - Migration/compatibility will be required: if only old
Themes/*.jsonis available - behavior as now until the user saves the theme in TOML.
Rejected / deferred alternatives¶
- JSON only as canon - contradicts 0029 for user preferences; Leave it as wire format only.
- Breaking MCP and replacing it with a TOML string in the body of the tool in one step - high tax on agents and scripts; We don’t do it without a protocol version.
Open questions¶
- Exact form of TOML section: flat
ui_theme.editor_backgroundvs nested tables[ui_theme.editor]/background- align with snake_case and merge from 0028. - Is an explicit
ui_theme.preset = "dark"needed for partial field overlay. - Versioning of the
ide_get_ui_themeresponse (if thecanonical_path: settings.tomlfield is added) - if necessary, outside of this ADR.