ADR-0137: A top-level Extensions menu; each extension configures itself
- Status: Accepted
- Date: 2026-09-23
- Deciders: jka
- Decision: D130
Context
ADR-0135 gave a provider a settings window and put it in Preferences ▸ Agent. The maintainer had proposed a top-level Extensions menu instead, was given a counter-argument, and took it. Once the code existed, two things that counter-argument never addressed were plain:
- The pane mixed two different things. rio's own agent settings — the agent mode, the compare-complex-edits toggle, the prompt layers, the command allow-list, the change-with-agent toggle — sat interleaved with the settings of one particular provider.
- It did not scale. The Agent pane ended in two loops over the installed providers, one emitting an API-key button and one a settings button, so every provider installed added two entries to a pane that is rio's rather than theirs. Even with a single provider installed, that provider had two separate doors to its own configuration.
The counter-argument's load-bearing objection was also false, which is why the wrong call was made and is worth recording. It held that such a menu would need a menu-contribution mechanism, which belongs to the still-deferred plugin UI surface (ADR-0018). It needs nothing of the sort: the menu is filled from the enumeration of installed providers the frontend already keeps and already uses to fill its provider cascade. The second objection — that extension settings span two homes, providers in the core and highlighters and modes in the frontend — is answered by a menu that merges two enumerations rather than caring where either comes from. And the Notepad++ analogy answered a claim nobody had made: the request was for settings windows, not for command contributions.
Decision
The line everything is held to: what the extension declares lives in the extension's own window; what rio owns lives in Preferences. That line is worth more than the menu it produced. By it, an API key is the provider's — one provider's key is meaningless to another — while the per-provider prompt layer (ADR-0079) and the per-provider scope of the command allow-list (ADR-0084) are rio's own mechanisms merely keyed by a provider's name, and stay in Preferences ▸ Agent.
ADR-0085 is unharmed, and its reach is now stated: it governs where rio's settings gather, not an extension's.
The menu. Extensions sits between Settings and Help — rio's own configuration first,
then what has been added to it, then Help last. It leads with Extensions ▸
Browse…, the installer, which therefore leaves the Settings menu
(ADR-0067). That entry was labelled Extensions… when
this record was written and was renamed days later: the menu, the entry and the window it
opens all carried the same noun, and only one of them was naming anything, so the entry
names the act instead.
Below a separator it carries one door per installed extension that has something to
configure, and a disabled placeholder when none has, so the menu is never empty. Every
entry below that separator names an installed extension, so the manual can teach the
form of such a path but never a path itself; the first entry is the only one a guard can
hold against the widgets.
It is a merge, not a provider special case. The doors come from the provider list and from a registry a frontend-side extension can add to when it loads, in the idiom the editing-mode registry already uses (ADR-0038). The registry is empty today; the merge is the point, and it is the answer to the two-homes objection above.
The menu stays bounded by construction. ADR-0092 states that no menu in rio is data-driven and unbounded, and this one is data-driven. Past a fixed number of doors it emits a single entry that opens the bounded picker Switch to Tab… and Theme… already use (ADR-0074).
The API key becomes a row in the provider's own window — a credentials group rendered first, with a masked field, a reveal, Save, Clear, and a note saying whether a key is stored. The separate key dialog is deleted; folding it in left it with no caller, and a second door is the drift ADR-0085 exists to end. One window therefore carries two interaction contracts: a key commits on an explicit Save, while a declared option applies on Return or on losing focus. That is the right way round — a key is pasted and committed deliberately — and the window's opening line says so rather than leaving it to be discovered.
A provider that holds a key earns a window on the key alone, declaring no options at all. Gating the window on declared options would leave such a provider nowhere to set the one thing it has. Both doors — the menu, and the row in the Extensions window for a provider just installed — ask the same question, after a review found them briefly disagreeing: with the row still gated on declared options, exactly the provider the new question exists for was reachable from one door and not the other.
Alternatives considered
- Keeping the settings in Preferences ▸ Agent, as ADR-0135 decided. Rejected on the two grounds above: it mixes rio's settings with one extension's, and it grows by two entries for every provider installed.
- The key as a button in the window that opens the old dialog. A door to a door, and the weaker answer to the mixing complaint: a provider's configuration would still be in two places.
- Leaving Extensions… in the Settings menu and giving the new menu only the per-extension doors. Rejected: "my extensions" would then be split across two menus. ADR-0067's own reasoning is untouched and is still why the item is not back under View — it is a management window, not a pane toggle. What changed is that a better home appeared.
- Accepting that the menu is short in practice rather than bounding it. Rejected: ADR-0092 is absolute, and a bound that holds by construction is cheaper than a bet on how many extensions anyone installs.
- Placing the menu before Settings, which the first draft did. The maintainer chose after: rio's own configuration first, then what has been added to it.
Consequences
- The menubar is File · Edit · View · Find · Compare · Settings · Extensions · Help.
- A provider's whole configuration — its key and everything it declares — is behind one door, and installing another provider adds one menu entry rather than two buttons in rio's own pane.
- Both providers' not-configured and authentication-failure messages named Preferences ▸ Agent and now name the provider's own door, which is the correction ADR-0085 made in the other direction.
- Nothing in the provider contract moved, so
provider-apidoes not change. Only where a frontend draws a provider's settings changed. - No extension kind but
providerhas a settings door. The registry is there and empty because no mode, theme or highlighter has anything to configure; inventing the need would be the speculative half of the deferred plugin UI surface. - The guard that holds the manual's menu references against the real menubar had to be
repaired. It resolved a quoted
Menu ▸ Itemby reading the text before the separator as the menu's name, which was unambiguous until a menubar menu and a Preferences category came to share the name Extensions — at which point a window path was read from its middle and reported against a menu it was never about. A segment inside a path must now follow one that resolved too, so the head of the chain decides whether the path names a menubar menu at all. A first attempt applied that rule to every segment and silently stopped reporting nearly everything it had been reporting: a guard that passes by seeing less is worse than the drift it was watching for, so a change to one is checked against the findings it already made, not only against the case that prompted it.