plugins: UI polish, intentional scope rules
- Added a shortcut "Open Advanced" icon next to the dropdown (visible only in Advanced mode) to quickly open the Switch modal. - Fixed the Switch modal so the "Configure Plugin" button only appears if the plugin actually has settings. - Redesigned scope rule creation in the Switch modal: switching project/agent profiles no longer auto-creates .toggle-1 files silently. Users must now intentionally click a + button to create a rule for that scope before the switch becomes active.
Alessandro committed
Feb 25, 2026 at 19:45 UTC
1e4f170da169764387921fa224da8bd16f54a6d3
3 files changed
+81
-24
webui/components/plugins/list/plugin-list.html
+38
-9
@@ -116,16 +116,26 @@
116
117
<div class="plugin-footer-row">
118
<div class="plugin-description" x-text="plugin.description || 'No description provided.'"></div>
119
- <select class="plugin-status-select"
120
- @change="$store.pluginListStore.updateToggle(plugin, $event.target.value)"
121
- @click.stop
122
- :disabled="plugin.always_enabled">
123
- <option value="enabled" :selected="plugin.toggle_state === 'enabled'">ON</option>
124
- <option value="disabled" :selected="plugin.toggle_state === 'disabled'">OFF</option>
125
- <template x-if="plugin.per_project_config || plugin.per_agent_config">
126
- <option value="advanced" :selected="plugin.toggle_state === 'advanced'">Advanced</option>
119
+ <div class="plugin-status-group">
120
+ <template x-if="plugin.toggle_state === 'advanced'">
121
+ <button type="button"
122
+ class="button icon-button"
123
+ title="Open Advanced"
124
+ @click="$store.pluginListStore.openPluginAdvancedToggle(plugin)">
125
+ <span class="icon material-symbols-outlined">rule_settings</span>
126
+ </button>
127
</template>
128
- </select>
128
+ <select class="plugin-status-select"
129
+ @change="$store.pluginListStore.updateToggle(plugin, $event.target.value)"
130
+ @click.stop
131
+ :disabled="plugin.always_enabled">
132
+ <option value="enabled" :selected="plugin.toggle_state === 'enabled'">ON</option>
133
+ <option value="disabled" :selected="plugin.toggle_state === 'disabled'">OFF</option>
134
+ <template x-if="plugin.per_project_config || plugin.per_agent_config">
135
+ <option value="advanced" :selected="plugin.toggle_state === 'advanced'">Advanced</option>
136
+ </template>
137
+ </select>
138
+ </div>
139
</div>
140
</div>
141
</template>
@@ -240,6 +250,25 @@
250
margin-top: 0.5rem;
251
}
252
253
+ .plugin-status-group {
254
+ display: flex;
255
+ align-items: center;
256
+ gap: 0.5rem;
257
+ flex: 0 0 auto;
258
+ }
259
+
260
+ .plugin-status-group .button {
261
+ padding: 0.3rem 0.5rem;
262
+ height: 2.2rem;
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ }
267
+
268
+ .plugin-status-group .button .icon {
269
+ font-size: 1.1rem;
270
+ }
271
+
272
.plugin-status-select {
273
padding: 0.3rem 2rem 0.3rem 0.6rem;
274
font-size: 0.9rem;
webui/components/plugins/toggle/plugin-toggle-advanced.html
+32
-6
@@ -60,10 +60,18 @@
60
</label>
61
62
<div class="plugin-settings-toolbar-item plugin-status-toggle-item">
63
- <label class="toggle">
63
+ <template x-if="!$store.pluginToggle.hasExplicitRuleForScope && !$store.pluginToggle.alwaysEnabled">
64
+ <button type="button"
65
+ class="button icon-button"
66
+ title="Add explicit rule for this scope"
67
+ @click="$store.pluginToggle.addRule()">
68
+ <span class="icon material-symbols-outlined">add</span>
69
+ </button>
70
+ </template>
71
+ <label class="toggle" :class="{ 'disabled-appearance': !$store.pluginToggle.hasExplicitRuleForScope && !$store.pluginToggle.alwaysEnabled }">
72
<input type="checkbox"
73
:checked="$store.pluginToggle.status === 'enabled'"
66
- :disabled="$store.pluginToggle.alwaysEnabled || $store.pluginToggle.isSaving"
74
+ :disabled="$store.pluginToggle.alwaysEnabled || $store.pluginToggle.isSaving || !$store.pluginToggle.hasExplicitRuleForScope"
75
@change="$store.pluginToggle.setEnabled($event.target.checked)">
76
<span class="toggler"></span>
77
</label>
@@ -91,10 +99,12 @@
99
100
<!-- Footer -->
101
<div class="modal-footer" data-modal-footer>
94
- <button type="button" class="btn btn-ok footer-btn-left" @click="$store.pluginToggle.openConfigWithScope()">
95
- <span class="icon material-symbols-outlined">settings</span>
96
- Configure Plugin
97
- </button>
102
+ <template x-if="$store.pluginToggle.hasConfigScreen">
103
+ <button type="button" class="btn btn-ok footer-btn-left" @click="$store.pluginToggle.openConfigWithScope()">
104
+ <span class="icon material-symbols-outlined">settings</span>
105
+ Configure Plugin
106
+ </button>
107
+ </template>
108
<button class="btn btn-cancel" @click="window.closeModal?.()">
109
Close
110
</button>
@@ -228,6 +238,22 @@
238
flex: 0 0 auto;
239
}
240
241
+ .plugin-status-toggle-item .button {
242
+ padding: 0.3rem 0.5rem;
243
+ height: 2.2rem;
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: center;
247
+ }
248
+
249
+ .plugin-status-toggle-item .button .icon {
250
+ font-size: 1.1rem;
251
+ }
252
+
253
+ .disabled-appearance {
254
+ opacity: 0.6;
255
+ }
256
+
257
.plugin-toggle-status-text {
258
font-weight: 600;
259
color: var(--color-text-primary);
webui/components/plugins/toggle/plugin-toggle-store.js
+11
-9
@@ -23,6 +23,7 @@ const model = {
23
perProjectConfig: true,
24
perAgentConfig: true,
25
explicitPath: null,
26
+ hasExplicitRuleForScope: false,
27
configs: [],
28
29
async open(plugin) {
@@ -39,14 +40,11 @@ const model = {
40
this.alwaysEnabled = typeof plugin === 'object' ? !!plugin.always_enabled : false;
41
this.perProjectConfig = typeof plugin === 'object' ? !!plugin.per_project_config : true;
42
this.perAgentConfig = typeof plugin === 'object' ? !!plugin.per_agent_config : true;
43
+ this.hasConfigScreen = typeof plugin === 'object' ? !!plugin.has_config_screen : false;
44
45
try {
46
await Promise.all([this.loadProjects(), this.loadAgentProfiles()]);
47
await this.loadConfigs();
46
- // Auto-save ON for the default scope (Global + All profiles)
47
- if (!this.explicitPath && !this.alwaysEnabled && this.pluginName) {
48
- await this.setEnabled(true);
49
- }
48
} finally {
49
this.isLoading = false;
50
}
@@ -61,6 +59,8 @@ const model = {
59
this.perProjectConfig = true;
60
this.perAgentConfig = true;
61
this.alwaysEnabled = false;
62
+ this.hasConfigScreen = false;
63
+ this.hasExplicitRuleForScope = false;
64
},
65
66
async loadProjects() {
@@ -175,6 +175,7 @@ const model = {
175
calculateStatus() {
176
if (this.alwaysEnabled) {
177
this.explicitPath = null;
178
+ this.hasExplicitRuleForScope = true;
179
this.status = 'enabled';
180
return;
181
}
@@ -187,9 +188,11 @@ const model = {
188
189
if (explicit) {
190
this.explicitPath = explicit.path;
191
+ this.hasExplicitRuleForScope = true;
192
this.status = explicit.path.endsWith(".toggle-1") ? 'enabled' : 'disabled';
193
} else {
194
this.explicitPath = null;
195
+ this.hasExplicitRuleForScope = false;
196
this.status = 'enabled'; // default when no explicit config
197
}
198
},
@@ -223,17 +226,16 @@ const model = {
226
async onScopeChanged() {
227
this.calculateStatus();
228
226
- // Auto-save immediately so the displayed default state is persisted
227
- if (!this.explicitPath && !this.alwaysEnabled && this.pluginName) {
228
- await this.setEnabled(true);
229
- }
230
-
229
// Sync scope with settings store so its loadSettings picks up the right context
230
settingsStore.projectName = this.projectName || "";
231
settingsStore.agentProfileKey = this.agentProfileKey || "";
232
await settingsStore.loadSettings();
233
},
234
235
+ async addRule() {
236
+ await this.setEnabled(this.status === 'enabled');
237
+ },
238
+
239
projectLabel(key) {
240
if (!key) return "Global";
241
const found = (this.projects || []).find(p => p.key === key);