Unify WebUI icons and fix message collapsing
Introduce the x-icon custom element and migrate first-party WebUI and bundled plugin markup while retaining legacy Material icon compatibility. Defer icon-font readiness until the splash-installed document is parsed so Firefox does not leave icons transparent. Centralize chat message collapse handling, avoid false Show More controls on short user messages, and align grouped-message pagination styling. Update DOX and add regression coverage for the icon and collapse behavior.
frdel committed
Jul 31, 2026 at 13:32 UTC
93d1131ce1b94972e91310eb708050a7aba57da8
133 files changed
+1013
-656
plugins/AGENTS.md
+1
@@ -35,6 +35,7 @@
35
- Frontend plugin HTML extensions live under `extensions/webui/<point>/`, include a root Alpine scope, and use `x-move-*` directives when targeting static breakpoints.
36
- Frontend plugin JS extensions live under `extensions/webui/<point>/` and export a default function.
37
- Plugin UI must use the A0 notification system for errors, warnings, success, and info instead of inline success/error boxes.
38
+- Bundled plugin WebUI must author Material Symbols as empty `<x-icon name="lowercase_snake_case"></x-icon>` elements and use `:name` for Alpine-driven icon names. Legacy `.material-symbols-outlined` and `.material-icons-outlined` spans remain runtime-compatible for community plugins, but bundled plugins must not introduce new ligature-text spans.
39
- Banners and discovery cards are provided through Python `banners` extensions by appending dictionaries with unique `id`, `type`, `priority`, and display fields to the `banners` list.
40
- Alert banner types are `info`, `warning`, and `error`; discovery card types are `hero` and `feature`.
41
- Banner/card fields may include `title`, `html`, `description`, `thumbnail`, `icon`, `cta_text`, `cta_action`, and `dismissible` depending on type.
plugins/_browser/extensions/webui/get_tool_message_handler/browser-tool-handler.js
+1
-1
@@ -349,7 +349,7 @@ function renderBrowserScreenshotKvp(kvpsTable, resolveBrowserPayload, label, sta
349
button.innerHTML = `
350
<img class="browser-screenshot-kvp-image" alt="" draggable="false">
351
<span class="browser-screenshot-kvp-placeholder" aria-hidden="true">
352
- <span class="material-symbols-outlined">image_search</span>
352
+ <x-icon name="image_search"></x-icon>
353
</span>
354
`;
355
button.addEventListener("click", async (event) => {
plugins/_browser/webui/browser-panel.html
+23
-23
@@ -22,7 +22,7 @@
22
:aria-selected="$store.browserPage.isActiveBrowser(browser).toString()"
23
:title="$store.browserPage.browserTabTooltip(browser)"
24
@click="$store.browserPage.selectBrowser(browser.id, browser.context_id)">
25
- <span class="material-symbols-outlined browser-tab-icon" aria-hidden="true">language</span>
25
+ <x-icon class="browser-tab-icon" aria-hidden="true" name="language"></x-icon>
26
<span class="browser-tab-title" x-text="$store.browserPage.browserTabTitle(browser)"></span>
27
</button>
28
<button type="button" class="browser-tab-close"
@@ -31,14 +31,14 @@
31
:disabled="$store.browserPage.isClosingBrowser(browser.id, browser.context_id)"
32
@pointerdown.stop
33
@click.stop="$store.browserPage.closeBrowser(browser.id, browser.context_id)">
34
- <span class="material-symbols-outlined">close</span>
34
+ <x-icon name="close"></x-icon>
35
</button>
36
</div>
37
</template>
38
<button type="button" class="browser-new-tab" title="New Browser" aria-label="New Browser"
39
:disabled="$store.browserPage.isBusy()"
40
@click="$store.browserPage.openNewBrowser()">
41
- <span class="material-symbols-outlined">add</span>
41
+ <x-icon name="add"></x-icon>
42
</button>
43
</div>
44
@@ -49,15 +49,15 @@
49
<div class="browser-navigation">
50
<button class="btn btn-icon-action" title="Back" @click="$store.browserPage.command('back')"
51
:disabled="!$store.browserPage.activeBrowserId || $store.browserPage.isBusy()">
52
- <span class="material-symbols-outlined">arrow_back</span>
52
+ <x-icon name="arrow_back"></x-icon>
53
</button>
54
<button class="btn btn-icon-action" title="Forward" @click="$store.browserPage.command('forward')"
55
:disabled="!$store.browserPage.activeBrowserId || $store.browserPage.isBusy()">
56
- <span class="material-symbols-outlined">arrow_forward</span>
56
+ <x-icon name="arrow_forward"></x-icon>
57
</button>
58
<button class="btn btn-icon-action" title="Reload" @click="$store.browserPage.command('reload')"
59
:disabled="!$store.browserPage.activeBrowserId || $store.browserPage.isBusy()">
60
- <span class="material-symbols-outlined">refresh</span>
60
+ <x-icon name="refresh"></x-icon>
61
</button>
62
</div>
63
@@ -67,8 +67,8 @@
67
:class="{ 'is-active': $store.browserPage.annotating }"
68
:disabled="!$store.browserPage.canAnnotate() && !$store.browserPage.annotating"
69
@click="$store.browserPage.toggleAnnotationMode()">
70
- <span class="material-symbols-outlined" aria-hidden="true"
71
- x-text="$store.browserPage.annotating ? 'rate_review' : 'edit_note'"></span>
70
+ <x-icon aria-hidden="true"
71
+ :name="$store.browserPage.annotating ? 'rate_review' : 'edit_note'"></x-icon>
72
<span x-text="$store.browserPage.annotating ? 'Annotating' : 'Annotate'"></span>
73
</button>
74
<div class="browser-extension-menu" @click.outside="$store.browserPage.closeExtensionsMenu()"
@@ -77,7 +77,7 @@
77
aria-label="Browser settings" @click.stop="$store.browserPage.toggleExtensionsMenu()"
78
:aria-expanded="$store.browserPage.extensionMenuOpen.toString()"
79
:class="{ 'is-active': $store.browserPage.status?.extensions?.active }">
80
- <span class="material-symbols-outlined">tune</span>
80
+ <x-icon name="tune"></x-icon>
81
</button>
82
<div class="browser-extension-dropdown" x-show="$store.browserPage.extensionMenuOpen" x-transition
83
style="display: none;">
@@ -96,7 +96,7 @@
96
</div>
97
<div class="browser-extension-section-title">Chrome Extensions</div>
98
<button type="button" class="dropdown-item" @click="$store.browserPage.createExtensionWithAgent()">
99
- <span class="material-symbols-outlined">add_circle</span>
99
+ <x-icon name="add_circle"></x-icon>
100
<span>Create New Extension with A0</span>
101
</button>
102
<div class="browser-extension-url">
@@ -108,20 +108,20 @@
108
<button type="button" class="btn btn-ok" @click="$store.browserPage.installExtensionFromUrl()"
109
:disabled="$store.browserPage.extensionActionLoading"
110
:aria-busy="$store.browserPage.extensionActionLoading.toString()">
111
- <span class="material-symbols-outlined"
111
+ <x-icon
112
:class="{ spinning: $store.browserPage.extensionActionLoading }"
113
- x-text="$store.browserPage.extensionActionLoading ? 'progress_activity' : 'download'"></span>
113
+ :name="$store.browserPage.extensionActionLoading ? 'progress_activity' : 'download'"></x-icon>
114
<span x-text="$store.browserPage.extensionActionLoading ? 'Installing…' : 'Install URL'">Install URL</span>
115
</button>
116
<button type="button" class="btn btn-field"
117
@click="$store.browserPage.askAgentInstallExtension()">
118
- <span class="material-symbols-outlined">psychology_alt</span>
118
+ <x-icon name="psychology_alt"></x-icon>
119
<span x-text="$store.browserPage.extensionAssistantActionLabel()">Scan with A0</span>
120
</button>
121
</div>
122
<div class="browser-extension-warning" x-show="$store.browserPage.hasExtensionInstallUrl()"
123
x-transition style="display: none;">
124
- <span class="material-symbols-outlined">warning</span>
124
+ <x-icon name="warning"></x-icon>
125
<span>
126
Extensions run inside the Docker browser sandbox, but malicious or buggy extensions can still
127
damage that environment. Review what you install.
@@ -133,8 +133,8 @@
133
style="display: none;">
134
<div class="browser-extension-list-header">
135
<span>Installed extensions</span>
136
- <span class="material-symbols-outlined spinning"
137
- x-show="$store.browserPage.extensionsListLoading">progress_activity</span>
136
+ <x-icon class="spinning"
137
+ x-show="$store.browserPage.extensionsListLoading" name="progress_activity"></x-icon>
138
</div>
139
<template x-for="extension in $store.browserPage.extensionsList" :key="extension.path">
140
<div class="browser-extension-row" :title="extension.path">
@@ -150,7 +150,7 @@
150
:aria-label="$store.browserPage.extensionOpenTitle(extension)"
151
:disabled="!extension.enabled || $store.browserPage.isBusy()"
152
@click.stop="$store.browserPage.openExtensionUi(extension)">
153
- <span class="material-symbols-outlined">open_in_new</span>
153
+ <x-icon name="open_in_new"></x-icon>
154
<span>Open</span>
155
</button>
156
<label class="browser-extension-toggle">
@@ -164,7 +164,7 @@
164
</template>
165
</div>
166
<button type="button" class="dropdown-item" @click="$store.browserPage.openExtensionsSettings()">
167
- <span class="material-symbols-outlined">tune</span>
167
+ <x-icon name="tune"></x-icon>
168
<span>Settings</span>
169
</button>
170
<div class="browser-extension-message" x-show="$store.browserPage.extensionActionMessage"
@@ -176,7 +176,7 @@
176
</div>
177
178
<form class="browser-address-form" aria-label="Browser navigation" @submit.prevent="$store.browserPage.go()">
179
- <span class="material-symbols-outlined browser-address-icon">language</span>
179
+ <x-icon class="browser-address-icon" name="language"></x-icon>
180
<input class="browser-address" aria-label="Browser address" name="browser_address" x-model="$store.browserPage.address"
181
@focus="$store.browserPage.onAddressFocus()" @blur="$store.browserPage.onAddressBlur()"
182
@keydown.enter.prevent="$store.browserPage.go()"
@@ -251,7 +251,7 @@
251
<span>Annotations</span>
252
<button type="button" class="browser-annotation-clear" title="Clear annotations"
253
aria-label="Clear annotations" @click="$store.browserPage.clearVisibleAnnotations()">
254
- <span class="material-symbols-outlined">delete</span>
254
+ <x-icon name="delete"></x-icon>
255
</button>
256
</div>
257
<div class="browser-annotation-chips">
@@ -261,7 +261,7 @@
261
<span class="browser-annotation-chip-text" x-text="annotation.comment"></span>
262
<button type="button" title="Remove annotation" aria-label="Remove annotation"
263
@click="$store.browserPage.removeAnnotationComment(annotation.id)">
264
- <span class="material-symbols-outlined">close</span>
264
+ <x-icon name="close"></x-icon>
265
</button>
266
</div>
267
</template>
@@ -275,7 +275,7 @@
275
</div>
276
<template x-if="!$store.browserPage.hasFrame() && !$store.browserPage.isBusy()">
277
<div class="browser-empty">
278
- <span class="material-symbols-outlined">captive_portal</span>
278
+ <x-icon name="captive_portal"></x-icon>
279
<button class="btn btn-field" @click="$store.browserPage.command('open')">Open
280
Browser</button>
281
</div>
@@ -286,7 +286,7 @@
286
:class="{ 'is-active': $store.browserPage.isBusy() || $store.browserPage.error, 'has-error': $store.browserPage.error && !$store.browserPage.isBusy() }">
287
<template x-if="$store.browserPage.isBusy()">
288
<div class="browser-status" :title="$store.browserPage.loadingMessage()">
289
- <span class="material-symbols-outlined spinning">progress_activity</span>
289
+ <x-icon class="spinning" name="progress_activity"></x-icon>
290
<span x-text="$store.browserPage.loadingMessage()">Loading</span>
291
</div>
292
</template>
plugins/_browser/webui/browser-store.js
+3
-3
@@ -2866,7 +2866,7 @@ const model = {
2866
newAction.className = "browser-header-actions surface-modal-new-action";
2867
newAction.innerHTML = `
2868
<button type="button" class="browser-header-new-button surface-modal-new-button" title="New Browser" aria-label="New Browser">
2869
- <span class="material-symbols-outlined" aria-hidden="true">add</span>
2869
+ <x-icon aria-hidden="true" name="add"></x-icon>
2870
<span>New</span>
2871
</button>
2872
`;
@@ -2886,12 +2886,12 @@ const model = {
2886
const focusButton = globalThis.document.createElement("button");
2887
focusButton.type = "button";
2888
focusButton.className = "surface-button browser-modal-focus-button";
2889
- focusButton.innerHTML = '<span class="material-symbols-outlined" aria-hidden="true">fullscreen</span>';
2889
+ focusButton.innerHTML = '<x-icon aria-hidden="true" name="fullscreen"></x-icon>';
2890
const updateFocusButton = (active) => {
2891
const label = active ? "Restore size" : "Focus mode";
2892
focusButton.setAttribute("aria-label", label);
2893
focusButton.setAttribute("title", label);
2894
- focusButton.querySelector(".material-symbols-outlined").textContent = active ? "fullscreen_exit" : "fullscreen";
2894
+ focusButton.querySelector("x-icon").name = active ? "fullscreen_exit" : "fullscreen";
2895
};
2896
const setFocusMode = (enabled) => {
2897
if (enabled) {
plugins/_browser/webui/config.html
+11
-12
@@ -45,7 +45,7 @@
45
class="browser-config-note browser-config-success"
46
x-show="$store.browserConfig.config.runtime_backend !== 'host_required'"
47
>
48
- <span class="material-symbols-outlined">deployed_code</span>
48
+ <x-icon name="deployed_code"></x-icon>
49
<span>Internal Docker browser selected. Host-browser CLI settings are ignored for Browser tool runs.</span>
50
</div>
51
@@ -153,7 +153,7 @@
153
class="browser-config-note"
154
x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent'"
155
>
156
- <span class="material-symbols-outlined">info</span>
156
+ <x-icon name="info"></x-icon>
157
<span>
158
For an already-open browser, Chrome, Edge, Brave, Vivaldi, and Chromium use chrome://inspect/#remote-debugging; Opera uses opera://inspect/#remote-debugging. Enable "Allow remote debugging for this browser instance", then restart or reconnect A0 CLI if the browser does not appear. If needed, launch the browser with --remote-debugging-port=9222 and --user-data-dir=<profile-dir>, then use localhost:9222 or the full ws://.../devtools/browser/... endpoint here or in A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS.
159
</span>
@@ -163,7 +163,7 @@
163
class="browser-config-warning"
164
x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent'"
165
>
166
- <span class="material-symbols-outlined">warning</span>
166
+ <x-icon name="warning"></x-icon>
167
<span>
168
Existing profile lets the agent interact with the browser instance you authorize,
169
including signed-in sites, cookies, tabs, downloads, and page content.
@@ -184,7 +184,7 @@
184
</label>
185
186
<div class="browser-config-note">
187
- <span class="material-symbols-outlined" :class="{ spinning: $store.browserConfig.hostBrowserStatusLoading }" x-text="$store.browserConfig.hostBrowserStatusLoading ? 'progress_activity' : 'captive_portal'"></span>
187
+ <x-icon :class="{ spinning: $store.browserConfig.hostBrowserStatusLoading }" :name="$store.browserConfig.hostBrowserStatusLoading ? 'progress_activity' : 'captive_portal'"></x-icon>
188
<span x-text="$store.browserConfig.browserRuntimeStatusLabel()"></span>
189
</div>
190
</div>
@@ -269,7 +269,7 @@
269
</div>
270
271
<div class="browser-config-warning">
272
- <span class="material-symbols-outlined">warning</span>
272
+ <x-icon name="warning"></x-icon>
273
<span>
274
Extensions run inside the Docker browser sandbox, but malicious or buggy extensions can
275
still damage that environment. Keep only the extensions you trust enabled.
@@ -279,7 +279,7 @@
279
<div class="browser-config-extension-list">
280
<div class="browser-config-subtitle">
281
<span>Installed extensions</span>
282
- <span class="material-symbols-outlined spinning" x-show="$store.browserConfig.extensionsLoading">progress_activity</span>
282
+ <x-icon class="spinning" x-show="$store.browserConfig.extensionsLoading" name="progress_activity"></x-icon>
283
</div>
284
<template x-if="!$store.browserConfig.extensionsLoading && !$store.browserConfig.extensionsList.length">
285
<div class="browser-config-empty">No installed extensions found.</div>
@@ -308,21 +308,20 @@
308
:aria-label="'Delete ' + (extension.name || 'extension')"
309
@click.stop="$store.browserConfig.deleteExtension(extension)"
310
>
311
- <span
312
- class="material-symbols-outlined"
311
+ <x-icon
312
:class="{ spinning: $store.browserConfig.extensionDeleteLoadingPath === extension.path }"
314
- x-text="$store.browserConfig.extensionDeleteLoadingPath === extension.path ? 'progress_activity' : 'delete'"
315
- ></span>
313
+ :name="$store.browserConfig.extensionDeleteLoadingPath === extension.path ? 'progress_activity' : 'delete'"
314
+ ></x-icon>
315
</button>
316
</span>
317
</div>
318
</template>
319
<div class="browser-config-note browser-config-success" x-show="$store.browserConfig.extensionsMessage">
321
- <span class="material-symbols-outlined">check_circle</span>
320
+ <x-icon name="check_circle"></x-icon>
321
<span x-text="$store.browserConfig.extensionsMessage"></span>
322
</div>
323
<div class="browser-config-note" x-show="$store.browserConfig.extensionsError">
325
- <span class="material-symbols-outlined">error</span>
324
+ <x-icon name="error"></x-icon>
325
<span x-text="$store.browserConfig.extensionsError"></span>
326
</div>
327
</div>
plugins/_chat_compaction/extensions/webui/chat-input-bottom-actions-start/compact-button.html
+2
-2
@@ -38,7 +38,7 @@
38
<div class="cmpct-header">
39
<h3>Compact Chat History</h3>
40
<button class="cmpct-close" @click="$store.compactStore?.closeModal()">
41
- <span class="material-symbols-outlined">close</span>
41
+ <x-icon name="close"></x-icon>
42
</button>
43
</div>
44
@@ -85,7 +85,7 @@
85
</div>
86
87
<div class="cmpct-info">
88
- <span class="material-symbols-outlined">check_circle</span>
88
+ <x-icon name="check_circle"></x-icon>
89
<span>The context will be replaced with a compacted summary. The original conversation will be backed up.</span>
90
</div>
91
</div>
plugins/_chat_compaction/webui/compact-modal.html
+3
-3
@@ -9,7 +9,7 @@
9
<div class="modal-header">
10
<h3>Compact Chat History</h3>
11
<button class="modal-close" @click="$store.compactStore?.closeModal()">
12
- <span class="material-symbols-outlined">close</span>
12
+ <x-icon name="close"></x-icon>
13
</button>
14
</div>
15
@@ -63,13 +63,13 @@
63
64
<template x-if="!$store.compactStore?.canCompact">
65
<div class="stats-info">
66
- <span class="material-symbols-outlined">info</span>
66
+ <x-icon name="info"></x-icon>
67
<span>Conversation is too short to compact (minimum 1,000 tokens).</span>
68
</div>
69
</template>
70
71
<div class="stats-warning">
72
- <span class="material-symbols-outlined">warning</span>
72
+ <x-icon name="warning"></x-icon>
73
<span>This action cannot be undone. The original conversation will be replaced with a summary.</span>
74
</div>
75
</div>
plugins/_chat_naming/extensions/webui/sidebar-row-actions-menu/rename.html
+1
-1
@@ -11,7 +11,7 @@
11
$store.sidebar.rowMenuOpenId,
12
$store.sidebar.rowMenuKind,
13
)">
14
- <span class="material-symbols-outlined">edit</span>
14
+ <x-icon name="edit"></x-icon>
15
<span x-text="$store.sidebar.rowMenuKind === 'task' ? 'Rename Task' : 'Rename Chat'"></span>
16
</button>
17
</div>
plugins/_chat_naming/webui/rename.html
+3
-3
@@ -27,14 +27,14 @@
27
@click="$store.chatNaming.generate()"
28
:disabled="$store.chatNaming.loading || $store.chatNaming.generating
29
|| $store.chatNaming.saving">
30
- <span class="material-symbols-outlined"
31
- x-text="$store.chatNaming.generating ? 'progress_activity' : 'auto_awesome'"></span>
30
+ <x-icon
31
+ :name="$store.chatNaming.generating ? 'progress_activity' : 'auto_awesome'"></x-icon>
32
<span x-text="$store.chatNaming.generating ? 'Generating...' : 'Generate'"></span>
33
</button>
34
<button type="button" class="btn"
35
@click="$store.chatNaming.openSettings()"
36
:disabled="$store.chatNaming.loading || $store.chatNaming.saving">
37
- <span class="material-symbols-outlined">settings</span>
37
+ <x-icon name="settings"></x-icon>
38
<span>Settings</span>
39
</button>
40
</div>
plugins/_commands/extensions/webui/chat-input-box-start/commands-menu.html
+2
-2
@@ -11,7 +11,7 @@
11
<div class="commands-slash-menu" x-show="$store.commandsSlash.menuVisible" x-transition.opacity.duration.120ms>
12
<template x-if="$store.commandsSlash.loading">
13
<div class="commands-slash-loading">
14
- <span class="material-symbols-outlined spinning">progress_activity</span>
14
+ <x-icon class="spinning" name="progress_activity"></x-icon>
15
<span>Loading slash commands...</span>
16
</div>
17
</template>
@@ -49,7 +49,7 @@
49
class="commands-slash-create"
50
@mousedown.prevent
51
@click.prevent="$store.commandsSlash.openCreateCommand()">
52
- <span class="material-symbols-outlined">add</span>
52
+ <x-icon name="add"></x-icon>
53
<span x-text="$store.commandsSlash.emptyStateLabel"></span>
54
</button>
55
</div>
plugins/_commands/webui/editor.html
+1
-1
@@ -97,7 +97,7 @@
97
98
<template x-if="$store.commandsManager.editor.path">
99
<div class="commands-editor-path">
100
- <span class="material-symbols-outlined">description</span>
100
+ <x-icon name="description"></x-icon>
101
<span x-text="$store.commandsManager.editor.path"></span>
102
</div>
103
</template>
plugins/_commands/webui/main.html
+9
-9
@@ -30,15 +30,15 @@
30
31
<div class="commands-toolbar-actions">
32
<button type="button" class="button secondary" @click="$store.commandsManager.refresh()">
33
- <span class="material-symbols-outlined">refresh</span>
33
+ <x-icon name="refresh"></x-icon>
34
<span>Refresh</span>
35
</button>
36
<button type="button" class="button secondary" @click="$store.commandsManager.browseScopeFolder()">
37
- <span class="material-symbols-outlined">folder_open</span>
37
+ <x-icon name="folder_open"></x-icon>
38
<span>Browse Scope Folder</span>
39
</button>
40
<button type="button" class="button primary" @click="$store.commandsManager.openCreateCommand()">
41
- <span class="material-symbols-outlined">add</span>
41
+ <x-icon name="add"></x-icon>
42
<span>Create Command</span>
43
</button>
44
</div>
@@ -46,7 +46,7 @@
46
</div>
47
48
<div class="commands-loading" x-show="$store.commandsManager.loading">
49
- <span class="material-symbols-outlined spinning">progress_activity</span>
49
+ <x-icon class="spinning" name="progress_activity"></x-icon>
50
<span>Loading commands...</span>
51
</div>
52
@@ -70,13 +70,13 @@
70
71
<div class="commands-card-actions">
72
<button type="button" class="button icon" title="Edit command" @click="$store.commandsManager.openEditCommand(command)">
73
- <span class="material-symbols-outlined">edit</span>
73
+ <x-icon name="edit"></x-icon>
74
</button>
75
<button type="button" class="button icon" title="Duplicate command" @click="$store.commandsManager.duplicateCommand(command)">
76
- <span class="material-symbols-outlined">content_copy</span>
76
+ <x-icon name="content_copy"></x-icon>
77
</button>
78
<button type="button" class="button icon danger" title="Delete command" @click.stop="$confirmClick($event, () => $store.commandsManager.deleteCommand(command))">
79
- <span class="material-symbols-outlined">delete</span>
79
+ <x-icon name="delete"></x-icon>
80
</button>
81
</div>
82
</div>
@@ -94,7 +94,7 @@
94
Create YAML-configured slash commands here and they will appear from <code>/</code> in chat.
95
</div>
96
<button type="button" class="button primary" @click="$store.commandsManager.openCreateCommand()">
97
- <span class="material-symbols-outlined">add</span>
97
+ <x-icon name="add"></x-icon>
98
<span>Create Slash Command</span>
99
</button>
100
</div>
@@ -125,7 +125,7 @@
125
:title="$store.commandsManager.scopedOverride(command) ? 'Edit override' : 'Create editable override'"
126
:aria-label="$store.commandsManager.scopedOverride(command) ? `Edit /${command.name} override` : `Create editable /${command.name} override`"
127
@click="$store.commandsManager.editBuiltinCommand(command)">
128
- <span class="material-symbols-outlined">edit</span>
128
+ <x-icon name="edit"></x-icon>
129
</button>
130
</div>
131
plugins/_desktop/extensions/webui/right-canvas-toolbar-start/desktop-new-menu.html
+6
-6
@@ -13,25 +13,25 @@
13
:aria-expanded="open.toString()"
14
@click.stop="open = !open"
15
>
16
- <span class="material-symbols-outlined" aria-hidden="true">add</span>
16
+ <x-icon aria-hidden="true" name="add"></x-icon>
17
<span>New</span>
18
- <span class="material-symbols-outlined office-new-chevron" aria-hidden="true">expand_more</span>
18
+ <x-icon class="office-new-chevron" aria-hidden="true" name="expand_more"></x-icon>
19
</button>
20
<div class="office-new-menu" role="menu" x-show="open" @click.stop style="display: none;">
21
<button type="button" class="office-new-menu-item" role="menuitem" @click="open = false; $store.desktop?.runNewMenuAction('open')">
22
- <span class="material-symbols-outlined" aria-hidden="true">folder_open</span>
22
+ <x-icon aria-hidden="true" name="folder_open"></x-icon>
23
<span>Open</span>
24
</button>
25
<button type="button" class="office-new-menu-item" role="menuitem" @click="open = false; $store.desktop?.runNewMenuAction('writer')">
26
- <span class="material-symbols-outlined" aria-hidden="true">description</span>
26
+ <x-icon aria-hidden="true" name="description"></x-icon>
27
<span>Writer</span>
28
</button>
29
<button type="button" class="office-new-menu-item" role="menuitem" @click="open = false; $store.desktop?.runNewMenuAction('spreadsheet')">
30
- <span class="material-symbols-outlined" aria-hidden="true">table_chart</span>
30
+ <x-icon aria-hidden="true" name="table_chart"></x-icon>
31
<span>Spreadsheet</span>
32
</button>
33
<button type="button" class="office-new-menu-item" role="menuitem" @click="open = false; $store.desktop?.runNewMenuAction('presentation')">
34
- <span class="material-symbols-outlined" aria-hidden="true">co_present</span>
34
+ <x-icon aria-hidden="true" name="co_present"></x-icon>
35
<span>Presentation</span>
36
</button>
37
</div>
plugins/_desktop/webui/desktop-panel.html
+8
-8
@@ -11,7 +11,7 @@
11
<div class="office-shell">
12
<div class="office-document-header" x-show="$store.desktop.hasActiveFile()" style="display: none;">
13
<div class="office-document-title" :title="$store.desktop.tabLabel($store.desktop.session)">
14
- <span class="material-symbols-outlined office-document-icon" aria-hidden="true" x-text="$store.desktop.tabIcon($store.desktop.session)"></span>
14
+ <x-icon class="office-document-icon" aria-hidden="true" :name="$store.desktop.tabIcon($store.desktop.session)"></x-icon>
15
<span class="office-document-name" x-text="$store.desktop.tabTitle($store.desktop.session)"></span>
16
<span class="office-document-dirty" x-show="$store.desktop.dirty" aria-hidden="true">*</span>
17
</div>
@@ -25,7 +25,7 @@
25
:disabled="$store.desktop.saving"
26
@click="$store.desktop.save()"
27
>
28
- <span class="material-symbols-outlined" :class="{ spinning: $store.desktop.saving }" x-text="$store.desktop.saving ? 'progress_activity' : 'save'"></span>
28
+ <x-icon :class="{ spinning: $store.desktop.saving }" :name="$store.desktop.saving ? 'progress_activity' : 'save'"></x-icon>
29
</button>
30
31
<div class="office-file-actions" x-data="{ open: false }" @click.outside="open = false" @keydown.escape.window="open = false">
@@ -39,15 +39,15 @@
39
:disabled="$store.desktop.saving"
40
@click.stop="open = !open"
41
>
42
- <span class="material-symbols-outlined">more_vert</span>
42
+ <x-icon name="more_vert"></x-icon>
43
</button>
44
<div class="office-new-menu office-file-menu" role="menu" x-show="open" @click.stop>
45
<button type="button" class="office-new-menu-item" role="menuitem" :disabled="$store.desktop.saving" @click="open = false; $store.desktop.renameActiveFile()">
46
- <span class="material-symbols-outlined" aria-hidden="true">edit</span>
46
+ <x-icon aria-hidden="true" name="edit"></x-icon>
47
<span>Rename</span>
48
</button>
49
<button type="button" class="office-new-menu-item" role="menuitem" :disabled="$store.desktop.loading" @click="open = false; $store.desktop.closeActiveFile()">
50
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
50
+ <x-icon aria-hidden="true" name="close"></x-icon>
51
<span>Close File</span>
52
</button>
53
</div>
@@ -55,7 +55,7 @@
55
</div>
56
57
<div class="office-state-line" x-show="$store.desktop.message || $store.desktop.error || $store.desktop.loading" style="display: none;">
58
- <span class="material-symbols-outlined" :class="{ spinning: $store.desktop.loading }" x-text="$store.desktop.loading ? 'progress_activity' : ($store.desktop.error ? 'error' : 'check_circle')"></span>
58
+ <x-icon :class="{ spinning: $store.desktop.loading }" :name="$store.desktop.loading ? 'progress_activity' : ($store.desktop.error ? 'error' : 'check_circle')"></x-icon>
59
<span x-text="$store.desktop.error || $store.desktop.message || 'Working'"></span>
60
</div>
61
@@ -74,10 +74,10 @@
74
</div>
75
76
<div class="office-desktop-empty" x-show="$store.desktop.shouldShowDesktopEmptyState()" style="display: none;">
77
- <span class="material-symbols-outlined" aria-hidden="true">power_settings_new</span>
77
+ <x-icon aria-hidden="true" name="power_settings_new"></x-icon>
78
<span class="office-desktop-empty-title">Desktop is shut down</span>
79
<button type="button" class="office-icon-button office-command-button" @click="$store.desktop.restartDesktopSession()">
80
- <span class="material-symbols-outlined" aria-hidden="true">restart_alt</span>
80
+ <x-icon aria-hidden="true" name="restart_alt"></x-icon>
81
<span class="office-button-label">Restart Desktop</span>
82
</button>
83
</div>
plugins/_desktop/webui/desktop-store.js
+8
-8
@@ -2393,25 +2393,25 @@ const model = {
2393
root.className = "office-header-actions surface-modal-new-action";
2394
root.innerHTML = `
2395
<button type="button" class="office-header-new-button surface-modal-new-button" aria-haspopup="menu" aria-expanded="false">
2396
- <span class="material-symbols-outlined" aria-hidden="true">add</span>
2396
+ <x-icon aria-hidden="true" name="add"></x-icon>
2397
<span>New</span>
2398
- <span class="material-symbols-outlined office-new-chevron" aria-hidden="true">expand_more</span>
2398
+ <x-icon class="office-new-chevron" aria-hidden="true" name="expand_more"></x-icon>
2399
</button>
2400
<div class="office-new-menu" role="menu" hidden>
2401
<button type="button" class="office-new-menu-item" role="menuitem" data-office-new-action="open">
2402
- <span class="material-symbols-outlined" aria-hidden="true">folder_open</span>
2402
+ <x-icon aria-hidden="true" name="folder_open"></x-icon>
2403
<span>Open</span>
2404
</button>
2405
<button type="button" class="office-new-menu-item" role="menuitem" data-office-new-action="writer">
2406
- <span class="material-symbols-outlined" aria-hidden="true">description</span>
2406
+ <x-icon aria-hidden="true" name="description"></x-icon>
2407
<span>Writer</span>
2408
</button>
2409
<button type="button" class="office-new-menu-item" role="menuitem" data-office-new-action="spreadsheet">
2410
- <span class="material-symbols-outlined" aria-hidden="true">table_chart</span>
2410
+ <x-icon aria-hidden="true" name="table_chart"></x-icon>
2411
<span>Spreadsheet</span>
2412
</button>
2413
<button type="button" class="office-new-menu-item" role="menuitem" data-office-new-action="presentation">
2414
- <span class="material-symbols-outlined" aria-hidden="true">co_present</span>
2414
+ <x-icon aria-hidden="true" name="co_present"></x-icon>
2415
<span>Presentation</span>
2416
</button>
2417
</div>
@@ -2548,12 +2548,12 @@ const model = {
2548
const focusButton = globalThis.document.createElement("button");
2549
focusButton.type = "button";
2550
focusButton.className = "surface-button office-modal-focus-button";
2551
- focusButton.innerHTML = '<span class="material-symbols-outlined" aria-hidden="true">fullscreen</span>';
2551
+ focusButton.innerHTML = '<x-icon aria-hidden="true" name="fullscreen"></x-icon>';
2552
const updateFocusButton = (active) => {
2553
const label = active ? "Restore size" : "Focus mode";
2554
focusButton.setAttribute("aria-label", label);
2555
focusButton.setAttribute("title", label);
2556
- focusButton.querySelector(".material-symbols-outlined").textContent = active ? "fullscreen_exit" : "fullscreen";
2556
+ focusButton.querySelector("x-icon").name = active ? "fullscreen_exit" : "fullscreen";
2557
};
2558
updateFocusButton(false);
2559
placeSurfaceModalHeaderAction(header, focusButton, "window");
plugins/_discovery/extensions/webui/onboarding-success-end/discovery-cards.html
+6
-6
@@ -26,7 +26,7 @@
26
<img :src="card.thumbnail" :alt="card.title" loading="lazy">
27
</template>
28
<template x-if="!card.thumbnail && card.icon">
29
- <span class="material-symbols-outlined discovery-feature-icon" x-text="card.icon"></span>
29
+ <x-icon class="discovery-feature-icon" :name="card.icon"></x-icon>
30
</template>
31
</div>
32
<h4 class="discovery-feature-title" x-text="card.title"></h4>
@@ -36,7 +36,7 @@
36
type="button"
37
@click.stop="$store.discoveryStore.executeCta(card.cta_action)">
38
<span x-text="card.cta_text"></span>
39
- <span class="material-symbols-outlined">arrow_forward</span>
39
+ <x-icon name="arrow_forward"></x-icon>
40
</button>
41
</article>
42
</template>
@@ -56,7 +56,7 @@
56
@click.stop="$store.discoveryStore.dismissCard(card.id)"
57
:aria-label="`Dismiss ${card.title}`"
58
title="Dismiss">
59
- <span class="material-symbols-outlined">close</span>
59
+ <x-icon name="close"></x-icon>
60
</button>
61
62
<div class="discovery-hero-content">
@@ -66,7 +66,7 @@
66
type="button"
67
@click.stop="$store.discoveryStore.executeCta(card.cta_action)">
68
<span x-text="card.cta_text"></span>
69
- <span class="material-symbols-outlined">arrow_forward</span>
69
+ <x-icon name="arrow_forward"></x-icon>
70
</button>
71
</div>
72
@@ -75,7 +75,7 @@
75
<img :src="card.thumbnail" :alt="card.title" loading="lazy">
76
</template>
77
<template x-if="!card.thumbnail && card.icon">
78
- <span class="material-symbols-outlined discovery-hero-icon" x-text="card.icon"></span>
78
+ <x-icon class="discovery-hero-icon" :name="card.icon"></x-icon>
79
</template>
80
</div>
81
</article>
@@ -112,7 +112,7 @@
112
<button type="button"
113
class="discovery-undismiss-btn"
114
@click="$store.discoveryStore.undismissCards()">
115
- <span class="material-symbols-outlined">undo</span>
115
+ <x-icon name="undo"></x-icon>
116
<span>Show dismissed suggestions</span>
117
</button>
118
</div>
plugins/_discovery/extensions/webui/welcome-actions-end/discovery-cards.html
+5
-5
@@ -21,7 +21,7 @@
21
@click.stop="$store.discoveryStore.dismissFeatureCards()"
22
aria-label="Dismiss Connect Channels"
23
title="Dismiss">
24
- <span class="material-symbols-outlined">close</span>
24
+ <x-icon name="close"></x-icon>
25
</button>
26
</header>
27
@@ -35,7 +35,7 @@
35
<img :src="card.thumbnail" :alt="card.title" loading="lazy">
36
</template>
37
<template x-if="!card.thumbnail && card.icon">
38
- <span class="material-symbols-outlined discovery-feature-icon" x-text="card.icon"></span>
38
+ <x-icon class="discovery-feature-icon" :name="card.icon"></x-icon>
39
</template>
40
</span>
41
<span class="discovery-feature-title" x-text="`${card.cta_text || 'Connect'} ${card.title}`"></span>
@@ -52,7 +52,7 @@
52
@click.stop="$store.discoveryStore.dismissCard(card.id)"
53
:aria-label="`Dismiss ${card.title}`"
54
title="Dismiss">
55
- <span class="material-symbols-outlined">close</span>
55
+ <x-icon name="close"></x-icon>
56
</button>
57
58
<header class="discovery-account-header">
@@ -64,7 +64,7 @@
64
type="button"
65
@click="$store.discoveryStore.executeCta(card.cta_action)">
66
<span x-text="card.cta_text"></span>
67
- <span class="material-symbols-outlined">arrow_forward</span>
67
+ <x-icon name="arrow_forward"></x-icon>
68
</button>
69
</header>
70
@@ -105,7 +105,7 @@
105
<button type="button"
106
class="discovery-undismiss-btn"
107
@click="$store.discoveryStore.undismissCards()">
108
- <span class="material-symbols-outlined">undo</span>
108
+ <x-icon name="undo"></x-icon>
109
<span>Show dismissed suggestions</span>
110
</button>
111
</div>
plugins/_editor/webui/editor-panel.html
+31
-31
@@ -23,7 +23,7 @@
23
:title="$store.editor.tabLabel(tab)"
24
@click="$store.editor.selectTab(tab.tab_id)"
25
>
26
- <span class="material-symbols-outlined editor-tab-icon" aria-hidden="true" x-text="$store.editor.tabIcon(tab)"></span>
26
+ <x-icon class="editor-tab-icon" aria-hidden="true" :name="$store.editor.tabIcon(tab)"></x-icon>
27
<span class="editor-tab-title" x-text="$store.editor.tabTitle(tab)"></span>
28
</button>
29
<button
@@ -34,7 +34,7 @@
34
@pointerdown.stop
35
@click.stop="$store.editor.closeTab(tab.tab_id)"
36
>
37
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
37
+ <x-icon aria-hidden="true" name="close"></x-icon>
38
</button>
39
</div>
40
</template>
@@ -46,12 +46,12 @@
46
:disabled="$store.editor.loading || $store.editor.saving"
47
@click="$store.editor.runNewMenuAction('markdown')"
48
>
49
- <span class="material-symbols-outlined" aria-hidden="true">add</span>
49
+ <x-icon aria-hidden="true" name="add"></x-icon>
50
</button>
51
</div>
52
53
<div class="editor-close-confirm" x-show="$store.editor.hasPendingClose()" style="display: none;" role="status">
54
- <span class="material-symbols-outlined editor-close-confirm-icon" aria-hidden="true">warning</span>
54
+ <x-icon class="editor-close-confirm-icon" aria-hidden="true" name="warning"></x-icon>
55
<div class="editor-close-confirm-copy">
56
<span class="editor-close-confirm-title" x-text="$store.editor.pendingCloseTitle()"></span>
57
<span class="editor-close-confirm-message" x-text="$store.editor.pendingCloseMessage()"></span>
@@ -88,47 +88,47 @@
88
:aria-label="$store.editor.viewModeTitle()"
89
@click="$store.editor.toggleViewMode()"
90
>
91
- <span class="material-symbols-outlined" aria-hidden="true" x-text="$store.editor.viewModeIcon()"></span>
91
+ <x-icon aria-hidden="true" :name="$store.editor.viewModeIcon()"></x-icon>
92
</button>
93
94
<div class="editor-tool-group editor-history-tools" x-show="$store.editor.isTextDocument()" style="display: none;">
95
<button type="button" class="editor-icon-button" title="Undo" aria-label="Undo" :disabled="$store.editor.previewEditing || !$store.editor.canUndo()" @click="$store.editor.undo()">
96
- <span class="material-symbols-outlined">undo</span>
96
+ <x-icon name="undo"></x-icon>
97
</button>
98
<button type="button" class="editor-icon-button" title="Redo" aria-label="Redo" :disabled="$store.editor.previewEditing || !$store.editor.canRedo()" @click="$store.editor.redo()">
99
- <span class="material-symbols-outlined">redo</span>
99
+ <x-icon name="redo"></x-icon>
100
</button>
101
</div>
102
103
<div class="editor-tool-group editor-source-tools" x-show="$store.editor.isTextDocument() && $store.editor.isSourceMode()" style="display: none;">
104
<button type="button" class="editor-icon-button" title="Bold" aria-label="Bold" @click="$store.editor.format('bold')">
105
- <span class="material-symbols-outlined">format_bold</span>
105
+ <x-icon name="format_bold"></x-icon>
106
</button>
107
<button type="button" class="editor-icon-button" title="Italic" aria-label="Italic" @click="$store.editor.format('italic')">
108
- <span class="material-symbols-outlined">format_italic</span>
108
+ <x-icon name="format_italic"></x-icon>
109
</button>
110
<button type="button" class="editor-icon-button" title="List" aria-label="List" @click="$store.editor.format('list')">
111
- <span class="material-symbols-outlined">format_list_bulleted</span>
111
+ <x-icon name="format_list_bulleted"></x-icon>
112
</button>
113
<button type="button" class="editor-icon-button" title="Numbered list" aria-label="Numbered list" @click="$store.editor.format('numbered')">
114
- <span class="material-symbols-outlined">format_list_numbered</span>
114
+ <x-icon name="format_list_numbered"></x-icon>
115
</button>
116
<button type="button" class="editor-icon-button" title="Table" aria-label="Table" @click="$store.editor.format('table')">
117
- <span class="material-symbols-outlined">table</span>
117
+ <x-icon name="table"></x-icon>
118
</button>
119
</div>
120
<div class="editor-tool-group editor-preview-tools" x-show="$store.editor.isTextDocument() && $store.editor.isPreviewMode()" style="display: none;">
121
<button type="button" class="editor-icon-button" title="Edit document" aria-label="Edit document" x-show="!$store.editor.previewEditing" @click="$store.editor.startPreviewEdit()">
122
- <span class="material-symbols-outlined">edit_note</span>
122
+ <x-icon name="edit_note"></x-icon>
123
</button>
124
<button type="button" class="editor-icon-button is-primary" title="Apply document edit" aria-label="Apply document edit" x-show="$store.editor.previewEditing" @click="$store.editor.applyPreviewEdit()">
125
- <span class="material-symbols-outlined">check</span>
125
+ <x-icon name="check"></x-icon>
126
</button>
127
<button type="button" class="editor-icon-button" title="Cancel document edit" aria-label="Cancel document edit" x-show="$store.editor.previewEditing" @click="$store.editor.cancelPreviewEdit()">
128
- <span class="material-symbols-outlined">close</span>
128
+ <x-icon name="close"></x-icon>
129
</button>
130
<button type="button" class="editor-icon-button" title="Search" aria-label="Search" :disabled="$store.editor.previewEditing" @click="$store.editor.openSearch()">
131
- <span class="material-symbols-outlined">search</span>
131
+ <x-icon name="search"></x-icon>
132
</button>
133
</div>
134
<span class="editor-toolbar-spacer"></span>
@@ -142,7 +142,7 @@
142
:disabled="$store.editor.saving"
143
@click="$store.editor.save()"
144
>
145
- <span class="material-symbols-outlined" :class="{ spinning: $store.editor.saving }" x-text="$store.editor.saving ? 'progress_activity' : 'save'"></span>
145
+ <x-icon :class="{ spinning: $store.editor.saving }" :name="$store.editor.saving ? 'progress_activity' : 'save'"></x-icon>
146
</button>
147
148
<button
@@ -153,7 +153,7 @@
153
:disabled="$store.editor.saving"
154
@click="$store.editor.saveAs()"
155
>
156
- <span class="material-symbols-outlined">save_as</span>
156
+ <x-icon name="save_as"></x-icon>
157
</button>
158
159
<div class="editor-file-actions" x-data="{ open: false }" @click.outside="open = false" @keydown.escape.window="open = false">
@@ -167,23 +167,23 @@
167
:disabled="$store.editor.saving"
168
@click.stop="open = !open"
169
>
170
- <span class="material-symbols-outlined">more_vert</span>
170
+ <x-icon name="more_vert"></x-icon>
171
</button>
172
<div class="editor-new-menu editor-file-menu" role="menu" x-show="open" @click.stop>
173
<button type="button" class="editor-new-menu-item" role="menuitem" :disabled="$store.editor.saving" @click="open = false; $store.editor.downloadActiveFile()">
174
- <span class="material-symbols-outlined" aria-hidden="true">download</span>
174
+ <x-icon aria-hidden="true" name="download"></x-icon>
175
<span>Download</span>
176
</button>
177
<button type="button" class="editor-new-menu-item" role="menuitem" :disabled="$store.editor.saving" @click="open = false; $store.editor.renameActiveFile()">
178
- <span class="material-symbols-outlined" aria-hidden="true">edit</span>
178
+ <x-icon aria-hidden="true" name="edit"></x-icon>
179
<span>Rename</span>
180
</button>
181
<button type="button" class="editor-new-menu-item" role="menuitem" :disabled="$store.editor.loading" @click="open = false; $store.editor.closeActiveFile()">
182
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
182
+ <x-icon aria-hidden="true" name="close"></x-icon>
183
<span>Close File</span>
184
</button>
185
<button type="button" class="editor-new-menu-item" role="menuitem" :disabled="$store.editor.loading || $store.editor.visibleTabs().length === 0" @click="open = false; $store.editor.closeAllFiles()">
186
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
186
+ <x-icon aria-hidden="true" name="close"></x-icon>
187
<span>Close All</span>
188
</button>
189
</div>
@@ -192,7 +192,7 @@
192
</div>
193
194
<div class="editor-search-bar" x-show="$store.editor.searchOpen" style="display: none;">
195
- <span class="material-symbols-outlined" aria-hidden="true">search</span>
195
+ <x-icon aria-hidden="true" name="search"></x-icon>
196
<input
197
type="search"
198
data-editor-search
@@ -204,18 +204,18 @@
204
/>
205
<span class="editor-search-count" x-text="$store.editor.searchCountLabel()"></span>
206
<button type="button" class="editor-icon-button" title="Previous match" aria-label="Previous match" :disabled="$store.editor.searchMatches.length === 0" @click="$store.editor.previousSearchMatch()">
207
- <span class="material-symbols-outlined">keyboard_arrow_up</span>
207
+ <x-icon name="keyboard_arrow_up"></x-icon>
208
</button>
209
<button type="button" class="editor-icon-button" title="Next match" aria-label="Next match" :disabled="$store.editor.searchMatches.length === 0" @click="$store.editor.nextSearchMatch()">
210
- <span class="material-symbols-outlined">keyboard_arrow_down</span>
210
+ <x-icon name="keyboard_arrow_down"></x-icon>
211
</button>
212
<button type="button" class="editor-icon-button" title="Close search" aria-label="Close search" @click="$store.editor.closeSearch()">
213
- <span class="material-symbols-outlined">close</span>
213
+ <x-icon name="close"></x-icon>
214
</button>
215
</div>
216
217
<div class="editor-state-line" x-show="$store.editor.message || $store.editor.error || $store.editor.loading" style="display: none;">
218
- <span class="material-symbols-outlined" :class="{ spinning: $store.editor.loading }" x-text="$store.editor.loading ? 'progress_activity' : ($store.editor.error ? 'error' : 'check_circle')"></span>
218
+ <x-icon :class="{ spinning: $store.editor.loading }" :name="$store.editor.loading ? 'progress_activity' : ($store.editor.error ? 'error' : 'check_circle')"></x-icon>
219
<span x-text="$store.editor.error || $store.editor.message || 'Working'"></span>
220
</div>
221
@@ -267,15 +267,15 @@
267
<div class="editor-empty" x-show="!$store.editor.session && !$store.editor.loading" style="display: none;">
268
<div class="editor-empty-actions">
269
<button type="button" class="editor-icon-button editor-command-button" @click="$store.editor.runNewMenuAction('open')">
270
- <span class="material-symbols-outlined" aria-hidden="true">folder_open</span>
270
+ <x-icon aria-hidden="true" name="folder_open"></x-icon>
271
<span class="editor-button-label">Open</span>
272
</button>
273
<button type="button" class="editor-icon-button editor-command-button" @click="$store.editor.runNewMenuAction('markdown')">
274
- <span class="material-symbols-outlined" aria-hidden="true">article</span>
274
+ <x-icon aria-hidden="true" name="article"></x-icon>
275
<span class="editor-button-label">Markdown</span>
276
</button>
277
<button type="button" class="editor-icon-button editor-command-button" @click="$store.editor.runNewMenuAction('text')">
278
- <span class="material-symbols-outlined" aria-hidden="true">description</span>
278
+ <x-icon aria-hidden="true" name="description"></x-icon>
279
<span class="editor-button-label">Text</span>
280
</button>
281
</div>
plugins/_editor/webui/editor-store.js
+5
-5
@@ -1738,21 +1738,21 @@ const model = {
1738
root.className = "editor-header-actions surface-modal-new-action";
1739
root.innerHTML = `
1740
<button type="button" class="editor-header-new-button surface-modal-new-button" aria-haspopup="menu" aria-expanded="false">
1741
- <span class="material-symbols-outlined" aria-hidden="true">add</span>
1741
+ <x-icon aria-hidden="true" name="add"></x-icon>
1742
<span>New</span>
1743
- <span class="material-symbols-outlined editor-new-chevron" aria-hidden="true">expand_more</span>
1743
+ <x-icon class="editor-new-chevron" aria-hidden="true" name="expand_more"></x-icon>
1744
</button>
1745
<div class="editor-new-menu" role="menu" hidden>
1746
<button type="button" class="editor-new-menu-item" role="menuitem" data-editor-new-action="open">
1747
- <span class="material-symbols-outlined" aria-hidden="true">folder_open</span>
1747
+ <x-icon aria-hidden="true" name="folder_open"></x-icon>
1748
<span>Open</span>
1749
</button>
1750
<button type="button" class="editor-new-menu-item" role="menuitem" data-editor-new-action="markdown">
1751
- <span class="material-symbols-outlined" aria-hidden="true">article</span>
1751
+ <x-icon aria-hidden="true" name="article"></x-icon>
1752
<span>Markdown</span>
1753
</button>
1754
<button type="button" class="editor-new-menu-item" role="menuitem" data-editor-new-action="text">
1755
- <span class="material-symbols-outlined" aria-hidden="true">description</span>
1755
+ <x-icon aria-hidden="true" name="description"></x-icon>
1756
<span>Text</span>
1757
</button>
1758
</div>
plugins/_email_integration/webui/config.html
+7
-7
@@ -41,10 +41,10 @@
41
<button class="btn btn-action delete"
42
@click.stop="$confirmClick($event, () => $store.emailConfig.removeHandler(idx))"
43
title="Remove inbox">
44
- <span class="material-symbols-outlined">delete</span>
44
+ <x-icon name="delete"></x-icon>
45
</button>
46
- <span class="material-symbols-outlined email-card-chevron"
47
- :class="{ 'is-open': $store.emailConfig.editing === idx }">expand_more</span>
46
+ <x-icon class="email-card-chevron"
47
+ :class="{ 'is-open': $store.emailConfig.editing === idx }" name="expand_more"></x-icon>
48
</div>
49
</div>
50
@@ -96,8 +96,8 @@
96
</template>
97
<template
98
x-if="provider.logo !== 'microsoft365' && (provider.logo === 'icloud' || provider.logo === 'custom-imap')">
99
- <span class="material-symbols-outlined"
100
- x-text="provider.logoText"></span>
99
+ <x-icon
100
+ :name="provider.logoText"></x-icon>
101
</template>
102
<template
103
x-if="provider.logo !== 'microsoft365' && provider.logo !== 'icloud' && provider.logo !== 'custom-imap'">
@@ -289,8 +289,8 @@
289
<details class="email-advanced" x-show="$store.emailConfig.hasProvider(handler)">
290
<summary>
291
<span>Advanced</span>
292
- <span class="material-symbols-outlined email-advanced-chevron"
293
- aria-hidden="true">keyboard_arrow_down</span>
292
+ <x-icon class="email-advanced-chevron"
293
+ aria-hidden="true" name="keyboard_arrow_down"></x-icon>
294
</summary>
295
<div class="email-advanced-body">
296
<div class="field">
plugins/_goal/extensions/webui/chat-input-progress-start/goal-strip.html
+6
-6
@@ -9,7 +9,7 @@
9
x-destroy="$store.goalBar.cleanup()">
10
<template x-if="$store.goalBar && $store.goalBar.visible">
11
<div class="goal-strip" :class="`is-${$store.goalBar.goal?.status || 'active'}`">
12
- <span class="material-symbols-outlined goal-strip-icon" x-text="$store.goalBar.statusIcon" aria-hidden="true"></span>
12
+ <x-icon class="goal-strip-icon" :name="$store.goalBar.statusIcon" aria-hidden="true"></x-icon>
13
14
<template x-if="!$store.goalBar.editing">
15
<div class="goal-strip-copy">
@@ -28,26 +28,26 @@
28
@keydown.escape.prevent="$store.goalBar.cancelEdit()"
29
aria-label="Goal objective" />
30
<button type="button" class="goal-strip-action" title="Save goal" @click="$store.goalBar.saveEdit()" :disabled="$store.goalBar.saving">
31
- <span class="material-symbols-outlined" aria-hidden="true">check</span>
31
+ <x-icon aria-hidden="true" name="check"></x-icon>
32
</button>
33
<button type="button" class="goal-strip-action" title="Cancel" @click="$store.goalBar.cancelEdit()" :disabled="$store.goalBar.saving">
34
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
34
+ <x-icon aria-hidden="true" name="close"></x-icon>
35
</button>
36
</div>
37
</template>
38
39
<div class="goal-strip-actions" x-show="!$store.goalBar.editing">
40
<button type="button" class="goal-strip-action" title="Edit goal" @click="$store.goalBar.startEdit()" :disabled="$store.goalBar.saving">
41
- <span class="material-symbols-outlined" aria-hidden="true">edit</span>
41
+ <x-icon aria-hidden="true" name="edit"></x-icon>
42
</button>
43
<button type="button" class="goal-strip-action"
44
:title="$store.goalBar.goal?.status === 'active' ? 'Pause goal' : 'Resume goal'"
45
@click="$store.goalBar.pauseOrResume()"
46
:disabled="$store.goalBar.saving">
47
- <span class="material-symbols-outlined" aria-hidden="true" x-text="$store.goalBar.goal?.status === 'active' ? 'pause_circle' : 'play_circle'"></span>
47
+ <x-icon aria-hidden="true" :name="$store.goalBar.goal?.status === 'active' ? 'pause_circle' : 'play_circle'"></x-icon>
48
</button>
49
<button type="button" class="goal-strip-action" title="Delete goal" @click="$confirmClick($event, () => $store.goalBar.deleteGoal())" :disabled="$store.goalBar.saving">
50
- <span class="material-symbols-outlined" aria-hidden="true">delete</span>
50
+ <x-icon aria-hidden="true" name="delete"></x-icon>
51
</button>
52
</div>
53
</div>
plugins/_memory/extensions/webui/_sidebar-quick-actions-main-start/memory-entry.html
+1
-1
@@ -2,6 +2,6 @@
2
<div x-data>
3
<button x-move-after=".config-button#dashboard" class="config-button" id="memory-dash" @click="openModal('/plugins/_memory/webui/memory-dashboard.html')"
4
title="Memory">
5
- <span class="material-symbols-outlined">psychology</span>
5
+ <x-icon name="psychology"></x-icon>
6
</button>
7
</div>
\ No newline at end of file
plugins/_memory/extensions/webui/sidebar-quick-actions-dropdown-start/memory-entry.html
+1
-1
@@ -6,7 +6,7 @@
6
id="memory-dash-dropdown"
7
title="Memory"
8
@click="openModal('/plugins/_memory/webui/memory-dashboard.html'); dropdownOpen = false">
9
- <span class="material-symbols-outlined">psychology</span>
9
+ <x-icon name="psychology"></x-icon>
10
<span>Memory</span>
11
</button>
12
</div>
plugins/_memory/webui/memory-dashboard.html
+16
-16
@@ -87,7 +87,7 @@
87
<div class="data-status-bar" x-show="!$store.memoryDashboardStore.loading">
88
<div class="status-info">
89
<span class="status-item">
90
- <span class="material-symbols-outlined">database</span>
90
+ <x-icon name="database"></x-icon>
91
Total: <strong x-text="$store.memoryDashboardStore.totalDbCount"></strong>
92
</span>
93
<span class="status-separator">•</span>
@@ -108,7 +108,7 @@
108
<div class="status-pagination" x-show="$store.memoryDashboardStore.totalPages > 1">
109
<button class="btn btn-icon" @click="$store.memoryDashboardStore.prevPage()"
110
:disabled="$store.memoryDashboardStore.currentPage === 1" title="Previous Page">
111
- <span class="material-symbols-outlined">chevron_left</span>
111
+ <x-icon name="chevron_left"></x-icon>
112
</button>
113
<div class="page-input-group">
114
<label>Page</label>
@@ -123,7 +123,7 @@
123
<button class="btn btn-icon" @click="$store.memoryDashboardStore.nextPage()"
124
:disabled="$store.memoryDashboardStore.currentPage === $store.memoryDashboardStore.totalPages"
125
title="Next Page">
126
- <span class="material-symbols-outlined">chevron_right</span>
126
+ <x-icon name="chevron_right"></x-icon>
127
</button>
128
</div>
129
</div>
@@ -137,27 +137,27 @@
137
<div class="mass-actions">
138
<button class="btn btn-mass copy" @click="$store.memoryDashboardStore.bulkCopyMemories()"
139
title="Copy Selected Content">
140
- <span class="material-symbols-outlined">content_copy</span>
140
+ <x-icon name="content_copy"></x-icon>
141
Copy
142
</button>
143
144
<button class="btn btn-mass export"
145
@click="$store.memoryDashboardStore.bulkExportMemories()"
146
title="Export Selected Memories">
147
- <span class="material-symbols-outlined">download</span>
147
+ <x-icon name="download"></x-icon>
148
Export
149
</button>
150
151
<button class="btn btn-mass delete"
152
@click="$confirmClick($event, () => $store.memoryDashboardStore.bulkDeleteMemories())"
153
title="Delete Selected Memories">
154
- <span class="material-symbols-outlined">delete</span>
154
+ <x-icon name="delete"></x-icon>
155
Delete
156
</button>
157
158
<button class="btn btn-mass clear" @click="$store.memoryDashboardStore.clearSelection()"
159
title="Clear Selection">
160
- <span class="material-symbols-outlined">close</span>
160
+ <x-icon name="close"></x-icon>
161
Clear
162
</button>
163
</div>
@@ -232,13 +232,13 @@
232
<button class="btn btn-action copy"
233
@click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.formatMemoryForCopy(memory))"
234
title="Copy Memory">
235
- <span class="material-symbols-outlined">content_copy</span>
235
+ <x-icon name="content_copy"></x-icon>
236
</button>
237
238
<button class="btn btn-action delete"
239
@click="$confirmClick($event, () => $store.memoryDashboardStore.deleteMemory(memory))"
240
title="Delete Memory">
241
- <span class="material-symbols-outlined">delete</span>
241
+ <x-icon name="delete"></x-icon>
242
</button>
243
</div>
244
</td>
@@ -260,27 +260,27 @@
260
<div class="mass-actions">
261
<button class="btn btn-mass copy" @click="$store.memoryDashboardStore.bulkCopyMemories()"
262
title="Copy Selected Content">
263
- <span class="material-symbols-outlined">content_copy</span>
263
+ <x-icon name="content_copy"></x-icon>
264
Copy
265
</button>
266
267
<button class="btn btn-mass export"
268
@click="$store.memoryDashboardStore.bulkExportMemories()"
269
title="Export Selected Memories">
270
- <span class="material-symbols-outlined">download</span>
270
+ <x-icon name="download"></x-icon>
271
Export
272
</button>
273
274
<button class="btn btn-mass delete"
275
@click="$confirmClick($event, () => $store.memoryDashboardStore.bulkDeleteMemories())"
276
title="Delete Selected Memories">
277
- <span class="material-symbols-outlined">delete</span>
277
+ <x-icon name="delete"></x-icon>
278
Delete
279
</button>
280
281
<button class="btn btn-mass clear" @click="$store.memoryDashboardStore.clearSelection()"
282
title="Clear Selection">
283
- <span class="material-symbols-outlined">close</span>
283
+ <x-icon name="close"></x-icon>
284
Clear
285
</button>
286
</div>
@@ -291,7 +291,7 @@
291
<div class="data-status-bar data-status-bar-bottom" x-show="!$store.memoryDashboardStore.loading">
292
<div class="status-info">
293
<span class="status-item">
294
- <span class="material-symbols-outlined">database</span>
294
+ <x-icon name="database"></x-icon>
295
Total: <strong x-text="$store.memoryDashboardStore.totalDbCount"></strong>
296
</span>
297
<span class="status-separator">•</span>
@@ -312,7 +312,7 @@
312
<div class="status-pagination" x-show="$store.memoryDashboardStore.totalPages > 1">
313
<button class="btn btn-icon" @click="$store.memoryDashboardStore.prevPage()"
314
:disabled="$store.memoryDashboardStore.currentPage === 1" title="Previous Page">
315
- <span class="material-symbols-outlined">chevron_left</span>
315
+ <x-icon name="chevron_left"></x-icon>
316
</button>
317
<div class="page-input-group">
318
<label>Page</label>
@@ -327,7 +327,7 @@
327
<button class="btn btn-icon" @click="$store.memoryDashboardStore.nextPage()"
328
:disabled="$store.memoryDashboardStore.currentPage === $store.memoryDashboardStore.totalPages"
329
title="Next Page">
330
- <span class="material-symbols-outlined">chevron_right</span>
330
+ <x-icon name="chevron_right"></x-icon>
331
</button>
332
</div>
333
</div>
plugins/_memory/webui/memory-detail-modal.html
+6
-6
@@ -24,20 +24,20 @@
24
25
<button class="btn btn-action-header copy-all" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.formatMemoryForCopy($store.memoryDashboardStore.detailMemory))"
26
title="Copy Complete Memory with Metadata">
27
- <span class="material-symbols-outlined">copy_all</span> All
27
+ <x-icon name="copy_all"></x-icon> All
28
</button>
29
<button class="btn btn-action-header copy-content" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.detailMemory?.content_full)"
30
title="Copy Content Only">
31
- <span class="material-symbols-outlined">content_copy</span> Content
31
+ <x-icon name="content_copy"></x-icon> Content
32
</button>
33
<div style="width:2em"></div>
34
<button class="btn btn-action-header edit" @click="$store.memoryDashboardStore.enableEditMode()"
35
title="Edit Memory">
36
- <span class="material-symbols-outlined">edit_document</span>
36
+ <x-icon name="edit_document"></x-icon>
37
</button>
38
<button class="btn btn-action-header delete danger" @click="$confirmClick($event, () => $store.memoryDashboardStore.deleteMemory($store.memoryDashboardStore.detailMemory))"
39
title="Delete Memory">
40
- <span class="material-symbols-outlined">delete</span>
40
+ <x-icon name="delete"></x-icon>
41
</button>
42
</div>
43
</template>
@@ -46,11 +46,11 @@
46
<div style="display: flex; gap: 0.5rem;">
47
<button class="btn btn-action-header cancel" @click="$store.memoryDashboardStore.cancelEditMode()"
48
title="Cancel Edit">
49
- <span class="material-symbols-outlined">close</span>
49
+ <x-icon name="close"></x-icon>
50
</button>
51
<button class="btn btn-action-header confirm" @click="$store.memoryDashboardStore.confirmEditMode()"
52
title="Confirm Changes">
53
- <span class="material-symbols-outlined">check</span>
53
+ <x-icon name="check"></x-icon>
54
</button>
55
56
</div>
plugins/_model_config/extensions/webui/chat-input-progress-start/model-switcher.html
+10
-10
@@ -21,10 +21,10 @@
21
<button class="btn-icon-action model-switcher-btn"
22
:class="{ 'has-override': !!$store.modelConfig.switcherOverride }"
23
@click="showDropdown = !showDropdown; showProfileDropdown = false">
24
- <span class="material-symbols-outlined" style="font-size: 16px;">neurology</span>
24
+ <x-icon style="font-size: 16px;" name="neurology"></x-icon>
25
<span class="model-switcher-label" x-text="$store.modelConfig.getSwitcherLabel()"></span>
26
- <span class="material-symbols-outlined" style="font-size: 0.7rem;"
27
- x-text="showDropdown ? 'expand_less' : 'expand_more'"></span>
26
+ <x-icon style="font-size: 0.7rem;"
27
+ :name="showDropdown ? 'expand_less' : 'expand_more'"></x-icon>
28
</button>
29
<!-- Dropdown (opens upward) -->
30
<div class="model-switcher-dropdown" x-show="showDropdown" x-transition.opacity style="display: none;">
@@ -34,7 +34,7 @@
34
$store.modelConfig.clearOverrideSwitch($store.chats?.selected || '');
35
showDropdown = false;
36
">
37
- <span class="material-symbols-outlined" style="font-size: 15px;">undo</span>
37
+ <x-icon style="font-size: 15px;" name="undo"></x-icon>
38
<span>Use scoped preset</span>
39
</div>
40
</template>
@@ -98,7 +98,7 @@
98
);
99
showDropdown = false;
100
">
101
- <span class="material-symbols-outlined" style="font-size: 14px;">tune</span>
101
+ <x-icon style="font-size: 14px;" name="tune"></x-icon>
102
<span>Edit Presets</span>
103
</div>
104
</div>
@@ -110,11 +110,11 @@
110
<button class="btn-icon-action agent-profile-btn"
111
title="Active agent profile"
112
@click="showProfileDropdown = !showProfileDropdown; showDropdown = false">
113
- <span class="material-symbols-outlined" style="font-size: 15px;">assignment_ind</span>
113
+ <x-icon style="font-size: 15px;" name="assignment_ind"></x-icon>
114
<span class="agent-profile-label"
115
x-text="$store.chats.selectedContext.agent_profile_label || $store.chats.selectedContext.agent_profile"></span>
116
- <span class="material-symbols-outlined" style="font-size: 0.7rem;"
117
- x-text="showProfileDropdown ? 'expand_less' : 'expand_more'"></span>
116
+ <x-icon style="font-size: 0.7rem;"
117
+ :name="showProfileDropdown ? 'expand_less' : 'expand_more'"></x-icon>
118
</button>
119
120
<div class="agent-profile-dropdown" x-show="showProfileDropdown" x-transition.opacity style="display: none;">
@@ -122,7 +122,7 @@
122
$store.modelConfig.createAgentProfileChat($store.chats?.selected || '')
123
.then(ok => { if (ok) showProfileDropdown = false; });
124
">
125
- <span class="material-symbols-outlined" style="font-size: 15px;">add</span>
125
+ <x-icon style="font-size: 15px;" name="add"></x-icon>
126
<span>Create new Agent Profile</span>
127
</button>
128
@@ -155,7 +155,7 @@
155
openModal('settings/settings.html');
156
showProfileDropdown = false;
157
">
158
- <span class="material-symbols-outlined" style="font-size: 14px;">settings</span>
158
+ <x-icon style="font-size: 14px;" name="settings"></x-icon>
159
<span>Agent Config</span>
160
</button>
161
</div>
plugins/_model_config/webui/api-keys.html
+4
-4
@@ -55,12 +55,12 @@
55
</div>
56
57
<div x-show="error" class="plugin-settings-error" style="margin-top: 1rem;">
58
- <span class="material-symbols-outlined">error</span>
58
+ <x-icon name="error"></x-icon>
59
<span x-text="error"></span>
60
</div>
61
62
<div x-show="loading" style="text-align:center; padding: 20px;">
63
- <span class="material-symbols-outlined spinning">progress_activity</span>
63
+ <x-icon class="spinning" name="progress_activity"></x-icon>
64
</div>
65
66
<div x-show="!loading">
@@ -76,14 +76,14 @@
76
autocomplete="off"
77
@input="$store.modelConfig.setApiKeyValue(provider.value, $el.value)"
78
style="padding-right:32px;" />
79
- <span class="material-symbols-outlined eye-toggle"
79
+ <x-icon class="eye-toggle"
80
@click="
81
showKey = !showKey;
82
if (showKey && !$store.modelConfig.apiKeyValues[provider.value] && provider.has_key) {
83
reveal(provider.value);
84
}
85
"
86
- x-text="showKey ? 'visibility' : 'visibility_off'"></span>
86
+ :name="showKey ? 'visibility' : 'visibility_off'"></x-icon>
87
</div>
88
</div>
89
</template>
plugins/_model_config/webui/main.html
+4
-4
@@ -35,13 +35,13 @@
35
</select>
36
</label>
37
<button type="button" class="button" @click="addPreset()">
38
- <span class="icon material-symbols-outlined">add</span>
38
+ <x-icon class="icon" name="add"></x-icon>
39
Add
40
</button>
41
<button type="button" class="button"
42
x-show="canDeleteSelected"
43
@click="$confirmClick($event, () => removeSelectedPreset())">
44
- <span class="icon material-symbols-outlined">delete</span>
44
+ <x-icon class="icon" name="delete"></x-icon>
45
Delete
46
</button>
47
</div>
@@ -96,12 +96,12 @@
96
97
<div class="preset-editor-secondary-actions">
98
<button type="button" class="text-button" @click="$store.modelConfig.openApiKeysFromSummary()">
99
- <span class="material-symbols-outlined">key</span>
99
+ <x-icon name="key"></x-icon>
100
API Keys
101
</button>
102
<button type="button" class="text-button"
103
@click="$confirmClick($event, () => resetPresets())">
104
- <span class="material-symbols-outlined">restart_alt</span>
104
+ <x-icon name="restart_alt"></x-icon>
105
Restore bundled presets
106
</button>
107
</div>
plugins/_model_config/webui/model-field.html
+9
-9
@@ -59,8 +59,8 @@
59
:disabled="searching"
60
title="Search available models"
61
aria-label="Search available models">
62
- <span class="material-symbols-outlined" :style="searching && 'opacity:0'">search</span>
63
- <span class="material-symbols-outlined model-search-spinner" :style="!searching && 'opacity:0'">progress_activity</span>
62
+ <x-icon :style="searching && 'opacity:0'" name="search"></x-icon>
63
+ <x-icon class="model-search-spinner" :style="!searching && 'opacity:0'" name="progress_activity"></x-icon>
64
</button>
65
<div class="model-search-results" x-show="open && results.length > 0" x-transition.opacity>
66
<template x-for="m in grouped().matched" :key="'m_'+m">
@@ -91,7 +91,7 @@
91
autocomplete="off"
92
@input="$store.modelConfig.setApiKeyValue(_prov, $el.value)"
93
style="padding-right:32px;" />
94
- <span class="material-symbols-outlined eye-toggle"
94
+ <x-icon class="eye-toggle"
95
@click="
96
showKey = !showKey;
97
const prov = _prov;
@@ -99,7 +99,7 @@
99
$store.modelConfig.revealApiKey(prov).then(v => { if (v) $store.modelConfig.apiKeyValues[prov] = v; });
100
}
101
"
102
- x-text="showKey ? 'visibility' : 'visibility_off'"></span>
102
+ :name="showKey ? 'visibility' : 'visibility_off'"></x-icon>
103
</div>
104
</div>
105
</template>
@@ -115,7 +115,7 @@
115
<input :type="showKey ? 'text' : 'password'" x-model="model.api_key" autocomplete="off"
116
:placeholder="$store.modelConfig.apiKeyStatus[_prov] ? '••••••••••••' : ''"
117
style="padding-right:32px;" />
118
- <span class="material-symbols-outlined eye-toggle"
118
+ <x-icon class="eye-toggle"
119
@click="
120
showKey = !showKey;
121
if (showKey && !model.api_key && $store.modelConfig.apiKeyStatus[_prov]) {
@@ -125,7 +125,7 @@
125
model.api_key = ''; _revealed = '';
126
}
127
"
128
- x-text="showKey ? 'visibility' : 'visibility_off'"></span>
128
+ :name="showKey ? 'visibility' : 'visibility_off'"></x-icon>
129
</div>
130
</div>
131
</template>
@@ -162,9 +162,9 @@
162
<!-- Advanced Settings (collapsed by default) -->
163
<div class="advanced-section" x-data="{ advOpen: false }">
164
<div class="advanced-toggle" @click="advOpen = !advOpen">
165
- <span class="material-symbols-outlined advanced-toggle-icon"
166
- :style="advOpen ? 'transform:rotate(90deg)' : ''"
167
- >chevron_right</span>
165
+ <x-icon class="advanced-toggle-icon"
166
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"
167
+ ></x-icon>
168
<span>Advanced Settings</span>
169
</div>
170
plugins/_model_config/webui/models-summary.html
+1
-1
@@ -19,7 +19,7 @@
19
x-data="$store.modelConfig.createGlobalPresetSelector()"
20
x-init="await init($store.chats?.selected || '')">
21
<div x-show="loading" class="settings-model-loading">
22
- <span class="material-symbols-outlined spinning">progress_activity</span>
22
+ <x-icon class="spinning" name="progress_activity"></x-icon>
23
Loading model preset...
24
</div>
25
<div x-show="!loading">
plugins/_model_config/webui/preset-overview.html
+4
-4
@@ -18,7 +18,7 @@
18
<div class="model-preset-rows" aria-live="polite">
19
<template x-for="model in modelRows" :key="model.title">
20
<div class="model-preset-row">
21
- <span class="material-symbols-outlined model-preset-icon" x-text="model.icon"></span>
21
+ <x-icon class="model-preset-icon" :name="model.icon"></x-icon>
22
<span class="model-preset-role" x-text="model.title"></span>
23
<span class="model-preset-identity">
24
<span class="model-preset-provider" x-text="model.provider"></span>
@@ -31,15 +31,15 @@
31
32
<div class="model-preset-actions">
33
<button type="button" class="button" @click="editPresets()">
34
- <span class="icon material-symbols-outlined">tune</span>
34
+ <x-icon class="icon" name="tune"></x-icon>
35
Edit Presets
36
</button>
37
<button type="button" class="button" @click="apiKeys()">
38
- <span class="icon material-symbols-outlined">key</span>
38
+ <x-icon class="icon" name="key"></x-icon>
39
API Keys
40
</button>
41
<button type="button" class="button" x-show="showScopedSettings" @click="scopedSettings()">
42
- <span class="icon material-symbols-outlined">account_tree</span>
42
+ <x-icon class="icon" name="account_tree"></x-icon>
43
Per-project / agent
44
</button>
45
</div>
plugins/_oauth/webui/config.html
+11
-11
@@ -22,7 +22,7 @@
22
<p>Each account can be connected independently.</p>
23
</div>
24
<button class="oauth-icon-button" type="button" @click="$store.oauthConfig.loadStatus()" title="Refresh status" aria-label="Refresh status">
25
- <span class="material-symbols-outlined">refresh</span>
25
+ <x-icon name="refresh"></x-icon>
26
</button>
27
</div>
28
@@ -34,7 +34,7 @@
34
@click="$store.oauthConfig.selectProvider(card.provider_id)"
35
>
36
<span class="oauth-mark" :class="{ connected: card.connected }">
37
- <span class="material-symbols-outlined" x-text="card.connected ? 'check' : card.mark"></span>
37
+ <x-icon :name="card.connected ? 'check' : card.mark"></x-icon>
38
</span>
39
<div class="oauth-provider-row-copy">
40
<strong x-text="card.display_name"></strong>
@@ -49,7 +49,7 @@
49
:disabled="$store.oauthConfig.providerPrimaryDisabled(card.provider_id)"
50
x-show="!card.connected"
51
>
52
- <span class="material-symbols-outlined" x-text="$store.oauthConfig.connectingProvider === card.provider_id ? 'progress_activity' : ($store.oauthConfig.providerSetupReady(card.provider_id) ? 'login' : 'tune')"></span>
52
+ <x-icon :name="$store.oauthConfig.connectingProvider === card.provider_id ? 'progress_activity' : ($store.oauthConfig.providerSetupReady(card.provider_id) ? 'login' : 'tune')"></x-icon>
53
<span x-text="$store.oauthConfig.providerPrimaryLabel(card.provider_id)"></span>
54
</button>
55
<button
@@ -59,7 +59,7 @@
59
:disabled="$store.oauthConfig.disconnectingProvider === card.provider_id"
60
x-show="card.connected"
61
>
62
- <span class="material-symbols-outlined" x-text="$store.oauthConfig.disconnectingProvider === card.provider_id ? 'progress_activity' : 'link_off'"></span>
62
+ <x-icon :name="$store.oauthConfig.disconnectingProvider === card.provider_id ? 'progress_activity' : 'link_off'"></x-icon>
63
<span x-text="$store.oauthConfig.disconnectingProvider === card.provider_id ? 'Disconnecting' : 'Disconnect'"></span>
64
</button>
65
</div>
@@ -165,7 +165,7 @@
165
<span>Enter this code</span>
166
<strong x-text="$store.oauthConfig.providerDevice(card.provider_id)?.user_code"></strong>
167
<button class="text-button" type="button" @click="$store.oauthConfig.cancelConnect(card.provider_id)">
168
- <span class="material-symbols-outlined">close</span>
168
+ <x-icon name="close"></x-icon>
169
<span>Cancel</span>
170
</button>
171
</div>
@@ -177,11 +177,11 @@
177
placeholder="Paste callback URL, query string, or code"
178
/>
179
<button type="button" class="oauth-connect secondary" @click="$store.oauthConfig.submitManualCallback(card.provider_id)">
180
- <span class="material-symbols-outlined">check</span>
180
+ <x-icon name="check"></x-icon>
181
<span>Submit</span>
182
</button>
183
<button type="button" class="oauth-connect secondary" @click="$store.oauthConfig.cancelConnect(card.provider_id)">
184
- <span class="material-symbols-outlined">close</span>
184
+ <x-icon name="close"></x-icon>
185
<span>Cancel</span>
186
</button>
187
</div>
@@ -189,7 +189,7 @@
189
<div class="oauth-auth-attempt" x-show="$store.oauthConfig.providerDevice(card.provider_id) && !$store.oauthConfig.providerDevice(card.provider_id)?.user_code && !card.supports_manual_callback">
190
<span>Sign-in started</span>
191
<button class="text-button" type="button" @click="$store.oauthConfig.cancelConnect(card.provider_id)">
192
- <span class="material-symbols-outlined">close</span>
192
+ <x-icon name="close"></x-icon>
193
<span>Cancel</span>
194
</button>
195
</div>
@@ -219,7 +219,7 @@
219
<template x-for="slot in $store.oauthConfig.modelSlots" :key="slot.key">
220
<div class="oauth-model-card">
221
<div class="oauth-model-head">
222
- <span class="oauth-model-icon material-symbols-outlined" x-text="slot.icon"></span>
222
+ <x-icon class="oauth-model-icon" :name="slot.icon"></x-icon>
223
<div class="oauth-model-title">
224
<strong x-text="slot.title"></strong>
225
<span x-show="$store.oauthConfig.slotStatusLabel(slot.key)" x-text="$store.oauthConfig.slotStatusLabel(slot.key)"></span>
@@ -233,7 +233,7 @@
233
x-show="slot.key === 'utility_model' && $store.oauthConfig.slotUsesOauth('chat_model')"
234
@click="$store.oauthConfig.copyMainToUtility()"
235
>
236
- <span class="material-symbols-outlined">content_copy</span>
236
+ <x-icon name="content_copy"></x-icon>
237
</button>
238
</div>
239
</div>
@@ -270,7 +270,7 @@
270
@click="$store.oauthConfig.openModelDropdown(slot.key, $el)"
271
:disabled="!$store.oauthConfig.slotCanUseModels(slot.key) || $store.oauthConfig.loadingModelsProvider === $store.oauthConfig.modelSlot(slot.key).provider"
272
>
273
- <span class="material-symbols-outlined" x-text="$store.oauthConfig.loadingModelsProvider === $store.oauthConfig.modelSlot(slot.key).provider ? 'progress_activity' : 'search'"></span>
273
+ <x-icon :name="$store.oauthConfig.loadingModelsProvider === $store.oauthConfig.modelSlot(slot.key).provider ? 'progress_activity' : 'search'"></x-icon>
274
</button>
275
</div>
276
plugins/_office/extensions/webui/lib/document-actions.js
+7
-8
@@ -143,9 +143,9 @@ export function buildDocumentFileCard(document = {}) {
143
card.setAttribute("aria-label", canvasActionTitle(document));
144
card.setAttribute("title", canvasActionTitle(document));
145
146
- const icon = globalThis.document.createElement("span");
147
- icon.className = "material-symbols-outlined document-file-card-icon";
148
- icon.textContent = documentIcon(document);
146
+ const icon = globalThis.document.createElement("x-icon");
147
+ icon.className = "document-file-card-icon";
148
+ icon.name = documentIcon(document);
149
card.appendChild(icon);
150
151
const meta = globalThis.document.createElement("span");
@@ -205,17 +205,16 @@ export function createDocumentActionButton(icon, label, handler = null, options
205
button.setAttribute("title", options.title || label);
206
207
if (icon) {
208
- const iconEl = globalThis.document.createElement("span");
209
- iconEl.className = "material-symbols-outlined";
210
- iconEl.textContent = icon;
208
+ const iconEl = globalThis.document.createElement("x-icon");
209
+ iconEl.name = icon;
210
button.appendChild(iconEl);
211
}
212
213
if (typeof handler === "function") {
214
button.addEventListener("click", async (event) => {
215
event.stopPropagation();
217
- const iconEl = button.querySelector(".material-symbols-outlined");
218
- const originalIcon = iconEl?.textContent || "";
216
+ const iconEl = button.querySelector("x-icon");
217
+ const originalIcon = iconEl?.name || "";
218
try {
219
await handler();
220
if (originalIcon) showButtonFeedback(button, true, originalIcon);
plugins/_office/webui/office-panel.html
+5
-5
@@ -10,7 +10,7 @@
10
<div class="office-panel" x-create="$store.office.onMount($el, xAttrs($el) || {})" x-destroy="$store.office.cleanup()">
11
<div class="office-shell">
12
<div class="office-state-line" x-show="$store.office.message || $store.office.error || $store.office.loading" style="display: none;">
13
- <span class="material-symbols-outlined" :class="{ spinning: $store.office.loading }" x-text="$store.office.loading ? 'progress_activity' : ($store.office.error ? 'error' : 'check_circle')"></span>
13
+ <x-icon :class="{ spinning: $store.office.loading }" :name="$store.office.loading ? 'progress_activity' : ($store.office.error ? 'error' : 'check_circle')"></x-icon>
14
<span x-text="$store.office.error || $store.office.message || 'Working'"></span>
15
</div>
16
@@ -18,19 +18,19 @@
18
<div class="office-empty" x-show="!$store.office.loading" style="display: none;">
19
<div class="office-empty-actions">
20
<button type="button" class="office-icon-button office-command-button" @click="$store.office.runNewMenuAction('open')">
21
- <span class="material-symbols-outlined" aria-hidden="true">folder_open</span>
21
+ <x-icon aria-hidden="true" name="folder_open"></x-icon>
22
<span class="office-button-label">Open</span>
23
</button>
24
<button type="button" class="office-icon-button office-command-button" @click="$store.office.runNewMenuAction('writer')">
25
- <span class="material-symbols-outlined" aria-hidden="true">description</span>
25
+ <x-icon aria-hidden="true" name="description"></x-icon>
26
<span class="office-button-label">Writer</span>
27
</button>
28
<button type="button" class="office-icon-button office-command-button" @click="$store.office.runNewMenuAction('spreadsheet')">
29
- <span class="material-symbols-outlined" aria-hidden="true">table_chart</span>
29
+ <x-icon aria-hidden="true" name="table_chart"></x-icon>
30
<span class="office-button-label">Calc</span>
31
</button>
32
<button type="button" class="office-icon-button office-command-button" @click="$store.office.runNewMenuAction('presentation')">
33
- <span class="material-symbols-outlined" aria-hidden="true">co_present</span>
33
+ <x-icon aria-hidden="true" name="co_present"></x-icon>
34
<span class="office-button-label">Impress</span>
35
</button>
36
</div>
plugins/_office/webui/office-store.js
+6
-6
@@ -197,25 +197,25 @@ const model = {
197
root.className = "office-header-actions";
198
root.innerHTML = `
199
<button type="button" class="office-header-new-button" aria-haspopup="menu" aria-expanded="false">
200
- <span class="material-symbols-outlined" aria-hidden="true">add</span>
200
+ <x-icon aria-hidden="true" name="add"></x-icon>
201
<span>New</span>
202
- <span class="material-symbols-outlined office-new-chevron" aria-hidden="true">expand_more</span>
202
+ <x-icon class="office-new-chevron" aria-hidden="true" name="expand_more"></x-icon>
203
</button>
204
<div class="office-new-menu" role="menu" hidden>
205
<button type="button" class="office-new-menu-item" role="menuitem" data-office-new-action="open">
206
- <span class="material-symbols-outlined" aria-hidden="true">folder_open</span>
206
+ <x-icon aria-hidden="true" name="folder_open"></x-icon>
207
<span>Open</span>
208
</button>
209
<button type="button" class="office-new-menu-item" role="menuitem" data-office-new-action="writer">
210
- <span class="material-symbols-outlined" aria-hidden="true">description</span>
210
+ <x-icon aria-hidden="true" name="description"></x-icon>
211
<span>Writer</span>
212
</button>
213
<button type="button" class="office-new-menu-item" role="menuitem" data-office-new-action="spreadsheet">
214
- <span class="material-symbols-outlined" aria-hidden="true">table_chart</span>
214
+ <x-icon aria-hidden="true" name="table_chart"></x-icon>
215
<span>Spreadsheet</span>
216
</button>
217
<button type="button" class="office-new-menu-item" role="menuitem" data-office-new-action="presentation">
218
- <span class="material-symbols-outlined" aria-hidden="true">co_present</span>
218
+ <x-icon aria-hidden="true" name="co_present"></x-icon>
219
<span>Presentation</span>
220
</button>
221
</div>
plugins/_onboarding/webui/onboarding.html
+10
-10
@@ -615,7 +615,7 @@
615
:class="{ active: $store.onboarding.providerMode === 'cloud' }"
616
:aria-selected="$store.onboarding.providerMode === 'cloud' ? 'true' : 'false'"
617
@click="$store.onboarding.setProviderMode('cloud')">
618
- <span class="material-symbols-outlined" aria-hidden="true">cloud</span>
618
+ <x-icon aria-hidden="true" name="cloud"></x-icon>
619
<span class="mode-copy">
620
<span class="mode-title">Cloud</span>
621
<span class="mode-sub">Connect with an API key</span>
@@ -628,7 +628,7 @@
628
:class="{ active: $store.onboarding.providerMode === 'account' }"
629
:aria-selected="$store.onboarding.providerMode === 'account' ? 'true' : 'false'"
630
@click="$store.onboarding.setProviderMode('account')">
631
- <span class="material-symbols-outlined" aria-hidden="true">account_circle</span>
631
+ <x-icon aria-hidden="true" name="account_circle"></x-icon>
632
<span class="mode-copy">
633
<span class="mode-title">Account</span>
634
<span class="mode-sub">Codex, Copilot, Gemini, Grok</span>
@@ -640,7 +640,7 @@
640
:class="{ active: $store.onboarding.providerMode === 'local' }"
641
:aria-selected="$store.onboarding.providerMode === 'local' ? 'true' : 'false'"
642
@click="$store.onboarding.setProviderMode('local')">
643
- <span class="material-symbols-outlined" aria-hidden="true">memory</span>
643
+ <x-icon aria-hidden="true" name="memory"></x-icon>
644
<span class="mode-copy">
645
<span class="mode-title">Local</span>
646
<span class="mode-sub">Runs on your machine</span>
@@ -667,7 +667,7 @@
667
<template x-for="provider in $store.onboarding.oauthProviderCards()" :key="`account-${provider.provider_id}`">
668
<button type="button" class="oauth-account-card" :class="{ connected: provider.connected }" @click="$store.onboarding.selectOAuthProvider(provider.provider_id)">
669
<span class="oauth-account-mark" :class="{ connected: provider.connected }">
670
- <span class="material-symbols-outlined" x-text="$store.onboarding.oauthMark(provider)"></span>
670
+ <x-icon :name="$store.onboarding.oauthMark(provider)"></x-icon>
671
</span>
672
<span class="oauth-account-copy">
673
<span class="oauth-account-title" x-text="$store.onboarding.oauthCardTitle(provider)"></span>
@@ -724,7 +724,7 @@
724
title="Refresh model list"
725
@click="$store.onboarding.loadModels('chat_model')"
726
:disabled="$store.onboarding.modelDropdown.chat_model.loading">
727
- <span class="material-symbols-outlined" x-text="$store.onboarding.modelDropdown.chat_model.loading ? 'progress_activity' : 'search'"></span>
727
+ <x-icon :name="$store.onboarding.modelDropdown.chat_model.loading ? 'progress_activity' : 'search'"></x-icon>
728
</button>
729
<div class="model-dropdown" x-show="$store.onboarding.modelDropdown.chat_model.open && !$store.onboarding.modelDropdown.chat_model.loading" x-transition.opacity>
730
<template x-for="model in $store.onboarding.filteredModels('chat_model')" :key="model">
@@ -740,7 +740,7 @@
740
<div class="oauth-connect-main">
741
<div class="oauth-status-group">
742
<span class="oauth-status-mark" :class="{ connected: $store.onboarding.oauthConnected($store.onboarding.selectedProviderId) }">
743
- <span class="material-symbols-outlined" x-text="$store.onboarding.oauthConnected($store.onboarding.selectedProviderId) ? 'check_circle' : $store.onboarding.oauthMark($store.onboarding.oauthProviderStatus($store.onboarding.selectedProviderId))"></span>
743
+ <x-icon :name="$store.onboarding.oauthConnected($store.onboarding.selectedProviderId) ? 'check_circle' : $store.onboarding.oauthMark($store.onboarding.oauthProviderStatus($store.onboarding.selectedProviderId))"></x-icon>
744
</span>
745
<span class="oauth-status-copy">
746
<span class="oauth-status-label" x-text="$store.onboarding.selectedProviderName()"></span>
@@ -755,7 +755,7 @@
755
</button>
756
<button type="button" class="btn btn-cancel" x-show="$store.onboarding.oauthConnecting" @click="$store.onboarding.cancelOauthConnect()">Cancel</button>
757
<span class="status-pill connected" x-show="$store.onboarding.oauthConnected($store.onboarding.selectedProviderId)">
758
- <span class="material-symbols-outlined">check_circle</span>
758
+ <x-icon name="check_circle"></x-icon>
759
<span>Connected</span>
760
</span>
761
</div>
@@ -781,7 +781,7 @@
781
</label>
782
</div>
783
<div class="oauth-device-note" x-show="$store.onboarding.oauthDevice">
784
- <span class="material-symbols-outlined">key</span>
784
+ <x-icon name="key"></x-icon>
785
<span x-show="$store.onboarding.oauthDevice?.user_code">Device code: <b x-text="$store.onboarding.oauthDevice?.user_code"></b></span>
786
<span x-show="!$store.onboarding.oauthDevice?.user_code">Finish sign-in in the opened browser tab.</span>
787
</div>
@@ -840,7 +840,7 @@
840
title="Refresh model list"
841
@click="$store.onboarding.loadModels('utility_model')"
842
:disabled="$store.onboarding.modelDropdown.utility_model.loading">
843
- <span class="material-symbols-outlined" x-text="$store.onboarding.modelDropdown.utility_model.loading ? 'progress_activity' : 'search'"></span>
843
+ <x-icon :name="$store.onboarding.modelDropdown.utility_model.loading ? 'progress_activity' : 'search'"></x-icon>
844
</button>
845
<div class="model-dropdown" x-show="$store.onboarding.modelDropdown.utility_model.open && !$store.onboarding.modelDropdown.utility_model.loading" x-transition.opacity>
846
<template x-for="model in $store.onboarding.filteredModels('utility_model')" :key="model">
@@ -861,7 +861,7 @@
861
<div class="onboarding-advanced-link" x-show="!$store.onboarding.isStep('ready')">
862
<button type="button" class="advanced-settings-toggle" @click="$store.onboarding.openAdvancedSettings()">
863
<span>Advanced Settings</span>
864
- <span class="material-symbols-outlined">keyboard_arrow_right</span>
864
+ <x-icon name="keyboard_arrow_right"></x-icon>
865
</button>
866
</div>
867
</div>
plugins/_orchestrator/webui/config.html
+4
-4
@@ -242,7 +242,7 @@
242
:disabled="$store.orchestratorStore.loading"
243
@click="$store.orchestratorStore.refresh()"
244
>
245
- <span class="material-symbols-outlined" aria-hidden="true" x-text="$store.orchestratorStore.loading ? 'progress_activity' : 'refresh'"></span>
245
+ <x-icon aria-hidden="true" :name="$store.orchestratorStore.loading ? 'progress_activity' : 'refresh'"></x-icon>
246
</button>
247
</div>
248
@@ -263,7 +263,7 @@
263
x-show="agent.installed && !agent.auth?.connected && agent.supports_device_login && (!$store.orchestratorStore.login || $store.orchestratorStore.login.agentId !== agent.id)"
264
@click.stop="$store.orchestratorStore.startLogin(agent.id)"
265
>
266
- <span class="material-symbols-outlined" aria-hidden="true">login</span>
266
+ <x-icon aria-hidden="true" name="login"></x-icon>
267
<span>Connect</span>
268
</button>
269
<button
@@ -273,10 +273,10 @@
273
:disabled="$store.orchestratorStore.disconnectingAgent === agent.id"
274
@click.stop="$store.orchestratorStore.disconnect(agent.id)"
275
>
276
- <span class="material-symbols-outlined" aria-hidden="true" x-text="$store.orchestratorStore.disconnectingAgent === agent.id ? 'progress_activity' : 'link_off'"></span>
276
+ <x-icon aria-hidden="true" :name="$store.orchestratorStore.disconnectingAgent === agent.id ? 'progress_activity' : 'link_off'"></x-icon>
277
<span x-text="$store.orchestratorStore.disconnectingAgent === agent.id ? 'Disconnecting' : 'Disconnect'"></span>
278
</button>
279
- <span class="material-symbols-outlined ta-expand" aria-hidden="true">expand_more</span>
279
+ <x-icon class="ta-expand" aria-hidden="true" name="expand_more"></x-icon>
280
</div>
281
</summary>
282
plugins/_pin_to_top/extensions/webui/sidebar-row-actions-menu/pin-to-top.html
+2
-2
@@ -8,8 +8,8 @@
8
<div x-data x-init="$store.pinToTop.init()">
9
<button type="button" class="dropdown-item" role="menuitem"
10
@click="$store.pinToTop.toggleFromMenu($store.sidebar.rowMenuOpenId, $store.sidebar.rowMenuKind); $store.sidebar.rowMenuClose()">
11
- <span class="material-symbols-outlined"
12
- x-text="$store.pinToTop.isMenuItemPinned($store.sidebar.rowMenuOpenId, $store.sidebar.rowMenuKind) ? 'keep_off' : 'push_pin'"></span>
11
+ <x-icon
12
+ :name="$store.pinToTop.isMenuItemPinned($store.sidebar.rowMenuOpenId, $store.sidebar.rowMenuKind) ? 'keep_off' : 'push_pin'"></x-icon>
13
<span
14
x-text="$store.pinToTop.isMenuItemPinned($store.sidebar.rowMenuOpenId, $store.sidebar.rowMenuKind) ? 'Unpin from Top' : 'Pin to Top'"></span>
15
</button>
plugins/_plugin_installer/extensions/webui/plugins-list-dropdown-end/plugin-hub-button.html
+1
-1
@@ -4,7 +4,7 @@
4
class="dropdown-item"
5
title="Open in Plugin Hub"
6
@click="$store.pluginInstallStore.openPluginHubDetailByKey(plugin.pluginHub.key); actionsOpen = false">
7
- <span class="material-symbols-outlined">storefront</span>
7
+ <x-icon name="storefront"></x-icon>
8
<span>Plugin Hub</span>
9
</button>
10
</template>
plugins/_plugin_installer/extensions/webui/plugins-list-header-buttons/install-buttons.html
+1
-1
@@ -6,6 +6,6 @@
6
<button type="button"
7
class="button confirm"
8
@click="openModal('/plugins/_plugin_installer/webui/main.html')">
9
- <span class="icon material-symbols-outlined">add</span> Install
9
+ <x-icon class="icon" name="add"></x-icon> Install
10
</button>
11
</span>
plugins/_plugin_installer/webui/install-detail.html
+17
-17
@@ -17,7 +17,7 @@
17
@error="$store.pluginInstallStore.detailThumbnailUrl = null">
18
</template>
19
<template x-if="!$store.pluginInstallStore.getDetailThumbnailUrl()">
20
- <span class="material-symbols-outlined pi-hero-placeholder">extension</span>
20
+ <x-icon class="pi-hero-placeholder" name="extension"></x-icon>
21
</template>
22
</div>
23
<div class="pi-hero-info">
@@ -27,7 +27,7 @@
27
<div class="pi-status-badges">
28
<template x-if="$store.pluginInstallStore.selectedPlugin.suspended">
29
<span class="pi-card-suspended-pill">
30
- <span class="material-symbols-outlined">priority_high</span>
30
+ <x-icon name="priority_high"></x-icon>
31
<span>Suspended</span>
32
</span>
33
</template>
@@ -46,7 +46,7 @@
46
</span>
47
</template>
48
<span class="pi-hero-stars">
49
- <span class="material-symbols-outlined">star</span>
49
+ <x-icon name="star"></x-icon>
50
<span x-text="($store.pluginInstallStore.selectedPlugin.stars || 0)"></span>
51
<template x-if="$store.pluginInstallStore.selectedPlugin.github">
52
<a :href="$store.pluginInstallStore.selectedPlugin.github"
@@ -69,36 +69,36 @@
69
<template x-if="$store.pluginInstallStore.installedPluginInfo.has_main_screen">
70
<button type="button" class="button"
71
@click="$store.pluginInstallStore.handleOpenPlugin()">
72
- <span class="icon material-symbols-outlined">dashboard</span> Open
72
+ <x-icon class="icon" name="dashboard"></x-icon> Open
73
</button>
74
</template>
75
<template x-if="$store.pluginInstallStore.installedPluginInfo.has_config_screen">
76
<button type="button" class="button"
77
@click="$store.pluginInstallStore.handleOpenConfig()">
78
- <span class="icon material-symbols-outlined">settings</span> Config
78
+ <x-icon class="icon" name="settings"></x-icon> Config
79
</button>
80
</template>
81
<template x-if="$store.pluginInstallStore.installedPluginInfo.has_readme">
82
<button type="button" class="button"
83
@click="$store.pluginInstallStore.handleOpenDoc('readme')">
84
- <span class="icon material-symbols-outlined">description</span> README
84
+ <x-icon class="icon" name="description"></x-icon> README
85
</button>
86
</template>
87
<template x-if="$store.pluginInstallStore.installedPluginInfo.has_license">
88
<button type="button" class="button"
89
@click="$store.pluginInstallStore.handleOpenDoc('license')">
90
- <span class="icon material-symbols-outlined">gavel</span> License
90
+ <x-icon class="icon" name="gavel"></x-icon> License
91
</button>
92
</template>
93
<template x-if="$store.pluginInstallStore.installedPluginInfo.has_execute_script">
94
<button type="button" class="button"
95
@click="$store.pluginInstallStore.handleOpenExecute()">
96
- <span class="icon material-symbols-outlined">terminal</span> Script
96
+ <x-icon class="icon" name="terminal"></x-icon> Script
97
</button>
98
</template>
99
<button type="button" class="button"
100
@click="$store.pluginInstallStore.handleOpenInfo()">
101
- <span class="icon material-symbols-outlined">info</span> Info
101
+ <x-icon class="icon" name="info"></x-icon> Info
102
</button>
103
</div>
104
</template>
@@ -108,7 +108,7 @@
108
109
<template x-if="$store.pluginInstallStore.selectedPlugin.suspended">
110
<div class="pi-suspension-banner">
111
- <span class="material-symbols-outlined">priority_high</span>
111
+ <x-icon name="priority_high"></x-icon>
112
<div class="pi-suspension-banner-copy">
113
<div class="pi-suspension-banner-title">Plugin has been suspended for the following reasons:</div>
114
<div class="pi-suspension-banner-explanation"
@@ -144,7 +144,7 @@
144
</span>
145
</template>
146
<template x-if="!$store.pluginInstallStore.loading">
147
- <span class="material-symbols-outlined">download</span>
147
+ <x-icon name="download"></x-icon>
148
</template>
149
<template x-if="!$store.pluginInstallStore.loading">
150
<span>Install</span>
@@ -162,7 +162,7 @@
162
</span>
163
</template>
164
<template x-if="!$store.pluginInstallStore.loading">
165
- <span class="material-symbols-outlined">system_update</span>
165
+ <x-icon name="system_update"></x-icon>
166
</template>
167
<template x-if="!$store.pluginInstallStore.loading">
168
<span>Update</span>
@@ -178,7 +178,7 @@
178
<span x-text="$store.pluginInstallStore.loadingMessage || 'Uninstalling...'"></span>
179
</span>
180
<span x-show="!$store.pluginInstallStore.loading">
181
- <span class="material-symbols-outlined">delete</span>
181
+ <x-icon name="delete"></x-icon>
182
</span>
183
<span x-show="!$store.pluginInstallStore.loading">
184
<span>Uninstall</span>
@@ -187,7 +187,7 @@
187
</template>
188
<template x-if="$store.pluginInstallStore.selectedPlugin.discussion">
189
<a :href="$store.pluginInstallStore.selectedPlugin.discussion" target="_blank" class="pi-btn-discussion">
190
- <span class="material-symbols-outlined">forum</span>
190
+ <x-icon name="forum"></x-icon>
191
<span>Discussion</span>
192
</a>
193
</template>
@@ -252,19 +252,19 @@
252
<div class="pi-developer-links">
253
<template x-if="$store.pluginInstallStore.selectedPlugin.github">
254
<a :href="$store.pluginInstallStore.selectedPlugin.github" target="_blank" class="pi-dev-link">
255
- <span class="material-symbols-outlined">code</span>
255
+ <x-icon name="code"></x-icon>
256
<span>GitHub Repository</span>
257
</a>
258
</template>
259
<template x-if="$store.pluginInstallStore.selectedPlugin.key">
260
<a :href="$store.pluginInstallStore.getIndexUrl($store.pluginInstallStore.selectedPlugin.key)" target="_blank" class="pi-dev-link">
261
- <span class="material-symbols-outlined">inventory_2</span>
261
+ <x-icon name="inventory_2"></x-icon>
262
<span>Plugin Index</span>
263
</a>
264
</template>
265
<template x-if="$store.pluginInstallStore.getReportUrl($store.pluginInstallStore.selectedPlugin)">
266
<a :href="$store.pluginInstallStore.getReportUrl($store.pluginInstallStore.selectedPlugin)" target="_blank" class="pi-dev-link pi-report-link">
267
- <span class="material-symbols-outlined">report</span>
267
+ <x-icon name="report"></x-icon>
268
<span>Report malicious plugin</span>
269
</a>
270
</template>
plugins/_plugin_installer/webui/install-git.html
+2
-2
@@ -39,7 +39,7 @@
39
</template>
40
<template x-if="!$store.pluginInstallStore.loading">
41
<span>
42
- <span class="icon material-symbols-outlined">terminal</span> Clone & Install
42
+ <x-icon class="icon" name="terminal"></x-icon> Clone & Install
43
</span>
44
</template>
45
</button>
@@ -48,7 +48,7 @@
48
49
<div x-show="$store.pluginInstallStore.result" class="pi-result">
50
<div class="pi-result-icon">
51
- <span class="material-symbols-outlined">check_circle</span>
51
+ <x-icon name="check_circle"></x-icon>
52
</div>
53
<div class="pi-result-text">
54
<strong x-text="'Plugin installed: ' + ($store.pluginInstallStore.result?.title || $store.pluginInstallStore.result?.plugin_name || '')"></strong>
plugins/_plugin_installer/webui/install-index.html
+13
-13
@@ -23,7 +23,7 @@
23
24
<div class="pi-index-toolbar">
25
<label class="pi-search-field">
26
- <span class="material-symbols-outlined">search</span>
26
+ <x-icon name="search"></x-icon>
27
<input type="text"
28
class="pi-search-input"
29
x-model="$store.pluginInstallStore.search"
@@ -47,7 +47,7 @@
47
title="Reload Plugin Hub"
48
@click="$store.pluginInstallStore.reloadIndex()"
49
:disabled="$store.pluginInstallStore.loading">
50
- <span class="material-symbols-outlined">refresh</span>
50
+ <x-icon name="refresh"></x-icon>
51
</button>
52
</div>
53
@@ -68,7 +68,7 @@
68
</div>
69
70
<div x-show="$store.pluginInstallStore.loading" class="pi-state-card pi-state-loading">
71
- <span class="material-symbols-outlined">progress_activity</span>
71
+ <x-icon name="progress_activity"></x-icon>
72
<div>
73
<div class="pi-state-title">Loading Plugin Hub</div>
74
<div class="pi-state-text" x-text="$store.pluginInstallStore.loadingMessage || 'Loading plugins...'"></div>
@@ -85,7 +85,7 @@
85
<img :src="plugin.thumbnail" :alt="plugin.title" loading="lazy">
86
</template>
87
<template x-if="!plugin.thumbnail">
88
- <span class="material-symbols-outlined pi-card-placeholder">extension</span>
88
+ <x-icon class="pi-card-placeholder" name="extension"></x-icon>
89
</template>
90
</div>
91
@@ -97,7 +97,7 @@
97
</div>
98
99
<span class="pi-card-stars">
100
- <span class="material-symbols-outlined">star</span>
100
+ <x-icon name="star"></x-icon>
101
<span x-text="plugin.stars || 0"></span>
102
</span>
103
@@ -118,7 +118,7 @@
118
<div class="pi-card-bubbles pi-card-bubbles-tags">
119
<template x-if="$store.pluginInstallStore.getBrowsePrimaryTag(plugin)">
120
<span class="pi-card-bubble pi-card-bubble-tag">
121
- <span class="material-symbols-outlined">sell</span>
121
+ <x-icon name="sell"></x-icon>
122
<span class="pi-card-bubble-text"
123
x-text="$store.pluginInstallStore.getBrowsePrimaryTag(plugin)"></span>
124
</span>
@@ -128,25 +128,25 @@
128
<div class="pi-card-bubbles pi-card-bubbles-status">
129
<template x-if="plugin.suspended">
130
<span class="pi-card-bubble pi-card-bubble-suspended">
131
- <span class="material-symbols-outlined">priority_high</span>
131
+ <x-icon name="priority_high"></x-icon>
132
<span class="pi-card-bubble-text">Suspended</span>
133
</span>
134
</template>
135
<template x-if="plugin.installed">
136
<span class="pi-card-bubble pi-card-bubble-installed">
137
- <span class="material-symbols-outlined">check_circle</span>
137
+ <x-icon name="check_circle"></x-icon>
138
<span class="pi-card-bubble-text">Installed</span>
139
</span>
140
</template>
141
<template x-if="plugin.has_update">
142
<span class="pi-card-bubble pi-card-bubble-update">
143
- <span class="material-symbols-outlined">update</span>
143
+ <x-icon name="update"></x-icon>
144
<span class="pi-card-bubble-text">New version</span>
145
</span>
146
</template>
147
<template x-if="$store.pluginInstallStore.isNewPlugin(plugin)">
148
<span class="pi-card-bubble pi-card-bubble-new">
149
- <span class="material-symbols-outlined">new_releases</span>
149
+ <x-icon name="new_releases"></x-icon>
150
<span class="pi-card-bubble-text">New</span>
151
</span>
152
</template>
@@ -159,7 +159,7 @@
159
160
<div x-show="$store.pluginInstallStore.index && !$store.pluginInstallStore.loading && $store.pluginInstallStore.filteredPlugins.length === 0"
161
class="pi-state-card pi-state-empty">
162
- <span class="material-symbols-outlined">search_off</span>
162
+ <x-icon name="search_off"></x-icon>
163
<div>
164
<div class="pi-state-title">No plugins match this view</div>
165
<div class="pi-state-text">
@@ -172,7 +172,7 @@
172
<button class="button"
173
:disabled="$store.pluginInstallStore.page <= 1"
174
@click="$store.pluginInstallStore.setPage($store.pluginInstallStore.page - 1)">
175
- <span class="material-symbols-outlined">chevron_left</span>
175
+ <x-icon name="chevron_left"></x-icon>
176
</button>
177
<span class="pi-page-info"
178
x-text="'Page ' + $store.pluginInstallStore.page + ' of ' + $store.pluginInstallStore.totalPages">
@@ -180,7 +180,7 @@
180
<button class="button"
181
:disabled="$store.pluginInstallStore.page >= $store.pluginInstallStore.totalPages"
182
@click="$store.pluginInstallStore.setPage($store.pluginInstallStore.page + 1)">
183
- <span class="material-symbols-outlined">chevron_right</span>
183
+ <x-icon name="chevron_right"></x-icon>
184
</button>
185
</div>
186
</div>
plugins/_plugin_installer/webui/install-zip.html
+3
-3
@@ -13,7 +13,7 @@
13
<div class="pi-upload-section">
14
<label for="plugin-zip-file" class="pi-upload-btn button confirm"
15
:class="{ 'pi-has-file': $store.pluginInstallStore.zipFile }">
16
- <span class="icon material-symbols-outlined">upload_file</span>
16
+ <x-icon class="icon" name="upload_file"></x-icon>
17
<span x-text="$store.pluginInstallStore.zipFileName || 'Select Plugin ZIP File'"></span>
18
</label>
19
<input type="file" id="plugin-zip-file" accept=".zip" style="display:none"
@@ -27,7 +27,7 @@
27
<button class="button confirm"
28
@click="$store.pluginInstallStore.installZip()"
29
:disabled="$store.pluginInstallStore.loading">
30
- <span class="icon material-symbols-outlined">download</span> Install Plugin
30
+ <x-icon class="icon" name="download"></x-icon> Install Plugin
31
</button>
32
<x-extension id="install-zip-actions"></x-extension>
33
</div>
@@ -38,7 +38,7 @@
38
39
<div x-show="$store.pluginInstallStore.result" class="pi-result">
40
<div class="pi-result-icon">
41
- <span class="material-symbols-outlined">check_circle</span>
41
+ <x-icon name="check_circle"></x-icon>
42
</div>
43
<div class="pi-result-text">
44
<strong x-text="'Plugin installed: ' + ($store.pluginInstallStore.result?.title || $store.pluginInstallStore.result?.plugin_name || '')"></strong>
plugins/_plugin_installer/webui/main.html
+3
-3
@@ -16,19 +16,19 @@
16
<li class="nav-item" role="presentation">
17
<button class="nav-link" :class="{ active: $store.pluginInstallStore.activeTab === 'store' }"
18
type="button" role="tab" @click="$store.pluginInstallStore.setTab('store')">
19
- <span class="material-symbols-outlined pi-tab-icon">store</span> Browse
19
+ <x-icon class="pi-tab-icon" name="store"></x-icon> Browse
20
</button>
21
</li>
22
<li class="nav-item" role="presentation">
23
<button class="nav-link" :class="{ active: $store.pluginInstallStore.activeTab === 'git' }"
24
type="button" role="tab" @click="$store.pluginInstallStore.setTab('git')">
25
- <span class="material-symbols-outlined pi-tab-icon">terminal</span> Git
25
+ <x-icon class="pi-tab-icon" name="terminal"></x-icon> Git
26
</button>
27
</li>
28
<li class="nav-item" role="presentation">
29
<button class="nav-link" :class="{ active: $store.pluginInstallStore.activeTab === 'zip' }"
30
type="button" role="tab" @click="$store.pluginInstallStore.setTab('zip')">
31
- <span class="material-symbols-outlined pi-tab-icon">upload_file</span> ZIP
31
+ <x-icon class="pi-tab-icon" name="upload_file"></x-icon> ZIP
32
</button>
33
</li>
34
</ul>
plugins/_plugin_validator/extensions/webui/install-git-actions/validate-button.html
+1
-1
@@ -8,6 +8,6 @@
8
title="Validate this plugin repository before installing"
9
@click="$store.pluginValidator.openModal({ source: 'git', gitUrl: ($store.pluginInstallStore.gitUrl || '').trim() })"
10
:disabled="$store.pluginInstallStore.loading || !($store.pluginInstallStore.gitUrl || '').trim()">
11
- <span class="icon material-symbols-outlined">fact_check</span> Validate
11
+ <x-icon class="icon" name="fact_check"></x-icon> Validate
12
</button>
13
</span>
plugins/_plugin_validator/extensions/webui/install-zip-actions/validate-button.html
+1
-1
@@ -8,6 +8,6 @@
8
title="Validate this ZIP plugin before installing"
9
@click="$store.pluginValidator.openModal({ source: 'zip', zipFile: $store.pluginInstallStore.zipFile, zipFileName: $store.pluginInstallStore.zipFileName })"
10
:disabled="$store.pluginInstallStore.loading || !$store.pluginInstallStore.zipFile">
11
- <span class="icon material-symbols-outlined">fact_check</span> Validate
11
+ <x-icon class="icon" name="fact_check"></x-icon> Validate
12
</button>
13
</span>
plugins/_plugin_validator/webui/plugin-validator.html
+4
-4
@@ -20,7 +20,7 @@
20
:aria-selected="$store.pluginValidator.source === 'local'"
21
aria-controls="plugin-validator-local-panel"
22
@click="$store.pluginValidator.setSource('local')">
23
- <span class="material-symbols-outlined pv-tab-icon">folder</span> Local
23
+ <x-icon class="pv-tab-icon" name="folder"></x-icon> Local
24
</button>
25
</li>
26
<li class="nav-item" role="presentation">
@@ -32,7 +32,7 @@
32
:aria-selected="$store.pluginValidator.source === 'git'"
33
aria-controls="plugin-validator-git-panel"
34
@click="$store.pluginValidator.setSource('git')">
35
- <span class="material-symbols-outlined pv-tab-icon">terminal</span> Git
35
+ <x-icon class="pv-tab-icon" name="terminal"></x-icon> Git
36
</button>
37
</li>
38
<li class="nav-item" role="presentation">
@@ -44,7 +44,7 @@
44
:aria-selected="$store.pluginValidator.source === 'zip'"
45
aria-controls="plugin-validator-zip-panel"
46
@click="$store.pluginValidator.setSource('zip')">
47
- <span class="material-symbols-outlined pv-tab-icon">upload_file</span> ZIP
47
+ <x-icon class="pv-tab-icon" name="upload_file"></x-icon> ZIP
48
</button>
49
</li>
50
</ul>
@@ -88,7 +88,7 @@
88
<label for="plugin-validator-zip-file"
89
class="pv-upload-btn button confirm"
90
:class="{ 'pv-has-file': $store.pluginValidator.zipFile }">
91
- <span class="icon material-symbols-outlined">upload_file</span>
91
+ <x-icon class="icon" name="upload_file"></x-icon>
92
<span x-text="$store.pluginValidator.zipFileName || 'Select Plugin ZIP File'"></span>
93
</label>
94
<input type="file"
plugins/_skills/extensions/webui/initFw_end/skills-menu-injector.js
+1
-1
@@ -11,7 +11,7 @@ function buildButton() {
11
button.className = "chat-bottom-menu-item";
12
button.id = BUTTON_ID;
13
button.innerHTML = `
14
- <span class="material-symbols-outlined" aria-hidden="true">menu_book</span>
14
+ <x-icon aria-hidden="true" name="menu_book"></x-icon>
15
<span>Skills</span>
16
`;
17
plugins/_skills/webui/config.html
+6
-6
@@ -17,7 +17,7 @@
17
18
<div class="skills-toolbar">
19
<label class="skills-search">
20
- <span class="material-symbols-outlined">search</span>
20
+ <x-icon name="search"></x-icon>
21
<input
22
type="text"
23
x-model.trim="search"
@@ -27,7 +27,7 @@
27
28
<div class="skills-actions">
29
<button type="button" class="button" @click="loadCatalog()" :disabled="loadingCatalog || mutatingChat">
30
- <span class="icon material-symbols-outlined">refresh</span>
30
+ <x-icon class="icon" name="refresh"></x-icon>
31
Refresh
32
</button>
33
</div>
@@ -56,7 +56,7 @@
56
aria-label="Open skill"
57
@click="openSkill(entry)"
58
>
59
- <span class="icon material-symbols-outlined">article</span>
59
+ <x-icon class="icon" name="article"></x-icon>
60
</button>
61
</div>
62
</div>
@@ -71,7 +71,7 @@
71
<div class="skills-panel-subtitle" x-text="allSkillsSubtitle()"></div>
72
73
<div class="skills-loading" x-show="loadingCatalog">
74
- <span class="material-symbols-outlined spinning">progress_activity</span>
74
+ <x-icon class="spinning" name="progress_activity"></x-icon>
75
<span>Loading skills...</span>
76
</div>
77
@@ -116,7 +116,7 @@
116
:disabled="isVisibilityDisabled()"
117
@click.prevent.stop="toggleVisibility(skill)"
118
>
119
- <span class="icon material-symbols-outlined" x-text="visibilityButtonIcon(skill)"></span>
119
+ <x-icon class="icon" :name="visibilityButtonIcon(skill)"></x-icon>
120
</button>
121
<button
122
type="button"
@@ -125,7 +125,7 @@
125
aria-label="Open skill"
126
@click.prevent.stop="openSkill(skill)"
127
>
128
- <span class="icon material-symbols-outlined">article</span>
128
+ <x-icon class="icon" name="article"></x-icon>
129
</button>
130
</div>
131
</div>
plugins/_telegram_integration/webui/config.html
+5
-5
@@ -37,10 +37,10 @@
37
x-text="$store.telegramConfig.botStatusLabel(bot)"></span>
38
<button class="btn btn-action delete"
39
@click.stop="$confirmClick($event, () => $store.telegramConfig.removeBot(idx))" title="Remove bot">
40
- <span class="material-symbols-outlined">delete</span>
40
+ <x-icon name="delete"></x-icon>
41
</button>
42
- <span class="material-symbols-outlined tg-card-chevron"
43
- :class="{ 'is-open': $store.telegramConfig.editing === idx }">expand_more</span>
42
+ <x-icon class="tg-card-chevron"
43
+ :class="{ 'is-open': $store.telegramConfig.editing === idx }" name="expand_more"></x-icon>
44
</div>
45
</div>
46
@@ -264,8 +264,8 @@
264
<details class="tg-advanced">
265
<summary>
266
<span>Advanced</span>
267
- <span class="material-symbols-outlined tg-advanced-chevron"
268
- aria-hidden="true">keyboard_arrow_down</span>
267
+ <x-icon class="tg-advanced-chevron"
268
+ aria-hidden="true" name="keyboard_arrow_down"></x-icon>
269
</summary>
270
<div class="tg-advanced-body">
271
<div class="field" x-show="bot.mode === 'webhook'">
plugins/_time_travel/extensions/webui/sidebar-quick-actions-dropdown-start/time-travel-entry.html
+1
-1
@@ -6,7 +6,7 @@
6
id="time-travel-dropdown"
7
title="Time Travel"
8
@click="$store.timeTravel?.onOpen?.(); ensureModalOpen('/plugins/_time_travel/webui/main.html'); $store.sidebar.menuClose()">
9
- <span class="material-symbols-outlined">history</span>
9
+ <x-icon name="history"></x-icon>
10
<span>Time Travel</span>
11
</button>
12
</div>
plugins/_time_travel/webui/time-travel-panel.html
+21
-21
@@ -10,7 +10,7 @@
10
<div class="time-travel-shell">
11
<div class="time-travel-toolbar">
12
<div class="time-travel-workspace-picker" :title="$store.timeTravel.workspacePath || $store.timeTravel.workspaceOptionLabel($store.timeTravel.selectedWorkspace())">
13
- <span class="material-symbols-outlined" aria-hidden="true">folder_open</span>
13
+ <x-icon aria-hidden="true" name="folder_open"></x-icon>
14
<select
15
aria-label="Workspace"
16
:value="$store.timeTravel.selectedWorkspaceId"
@@ -29,21 +29,21 @@
29
</div>
30
<span class="time-travel-spacer"></span>
31
<button type="button" class="time-travel-icon-button" title="Snapshot" aria-label="Snapshot" @click="$store.timeTravel.manualSnapshot()" :disabled="$store.timeTravel.busy || $store.timeTravel.loading || $store.timeTravel.isLocked()">
32
- <span class="material-symbols-outlined">add_a_photo</span>
32
+ <x-icon name="add_a_photo"></x-icon>
33
</button>
34
<button type="button" class="time-travel-icon-button" title="Refresh" aria-label="Refresh" @click="$store.timeTravel.refresh({ keepSelection: true, reloadWorkspaces: true })" :disabled="$store.timeTravel.loading">
35
- <span class="material-symbols-outlined" :class="{ spinning: $store.timeTravel.loading }">refresh</span>
35
+ <x-icon :class="{ spinning: $store.timeTravel.loading }" name="refresh"></x-icon>
36
</button>
37
</div>
38
39
<div class="time-travel-status" x-show="$store.timeTravel.loading || $store.timeTravel.error || $store.timeTravel.busy" style="display: none;">
40
- <span class="material-symbols-outlined" :class="{ spinning: $store.timeTravel.loading || $store.timeTravel.busy }" x-text="$store.timeTravel.error ? 'error' : 'progress_activity'"></span>
40
+ <x-icon :class="{ spinning: $store.timeTravel.loading || $store.timeTravel.busy }" :name="$store.timeTravel.error ? 'error' : 'progress_activity'"></x-icon>
41
<span x-text="$store.timeTravel.error || ($store.timeTravel.busy ? 'Working...' : 'Loading history...')"></span>
42
</div>
43
44
<template x-if="$store.timeTravel.isLocked() && !$store.timeTravel.loading">
45
<div class="time-travel-locked">
46
- <span class="material-symbols-outlined">lock</span>
46
+ <x-icon name="lock"></x-icon>
47
<strong>Unavailable</strong>
48
<span x-text="$store.timeTravel.payload?.workspace?.error || 'Time Travel is available only inside /a0/usr.'"></span>
49
</div>
@@ -53,7 +53,7 @@
53
<div class="time-travel-body">
54
<aside class="time-travel-timeline">
55
<div class="time-travel-filter">
56
- <span class="material-symbols-outlined">filter_list</span>
56
+ <x-icon name="filter_list"></x-icon>
57
<input
58
type="search"
59
placeholder="Filter files"
@@ -72,7 +72,7 @@
72
:title="row.message"
73
>
74
<span class="time-travel-row-mark">
75
- <span class="material-symbols-outlined" x-text="row.kind === 'present' ? (row.dirty ? 'edit_note' : 'check_circle') : (row.is_current ? 'radio_button_checked' : 'commit')"></span>
75
+ <x-icon :name="row.kind === 'present' ? (row.dirty ? 'edit_note' : 'check_circle') : (row.is_current ? 'radio_button_checked' : 'commit')"></x-icon>
76
</span>
77
<span class="time-travel-row-main">
78
<span class="time-travel-row-title" x-text="row.message"></span>
@@ -84,7 +84,7 @@
84
</div>
85
86
<button type="button" class="time-travel-load-more" x-show="$store.timeTravel.payload?.has_more" style="display: none;" @click="$store.timeTravel.loadMore()" :disabled="$store.timeTravel.loading">
87
- <span class="material-symbols-outlined">expand_more</span>
87
+ <x-icon name="expand_more"></x-icon>
88
<span>More</span>
89
</button>
90
</aside>
@@ -97,11 +97,11 @@
97
</div>
98
<div class="time-travel-actions" x-show="$store.timeTravel.selectedCommit()" style="display: none;">
99
<button type="button" class="time-travel-tool-button" title="Travel" @click="$store.timeTravel.openPreview('travel')" :disabled="$store.timeTravel.selectedCommit()?.is_current || $store.timeTravel.busy">
100
- <span class="material-symbols-outlined">move_down</span>
100
+ <x-icon name="move_down"></x-icon>
101
<span>Travel</span>
102
</button>
103
<button type="button" class="time-travel-tool-button" title="Revert" @click="$store.timeTravel.openPreview('revert')" :disabled="$store.timeTravel.busy">
104
- <span class="material-symbols-outlined">undo</span>
104
+ <x-icon name="undo"></x-icon>
105
<span>Revert</span>
106
</button>
107
</div>
@@ -111,7 +111,7 @@
111
<section class="time-travel-files">
112
<template x-if="$store.timeTravel.selectedFiles().length === 0">
113
<div class="time-travel-empty">
114
- <span class="material-symbols-outlined">hourglass_empty</span>
114
+ <x-icon name="hourglass_empty"></x-icon>
115
<span>No file changes</span>
116
</div>
117
</template>
@@ -138,28 +138,28 @@
138
<span class="time-travel-diff-path" :title="$store.timeTravel.fileTitle($store.timeTravel.selectedFile())" x-text="$store.timeTravel.fileTitle($store.timeTravel.selectedFile()) || 'Diff'"></span>
139
<span class="time-travel-spacer"></span>
140
<button type="button" class="time-travel-icon-button is-small" title="Open containing folder" aria-label="Open containing folder" @click="$store.timeTravel.openContainingFolder($store.timeTravel.selectedFile())" :disabled="!$store.timeTravel.selectedFile()">
141
- <span class="material-symbols-outlined">folder_open</span>
141
+ <x-icon name="folder_open"></x-icon>
142
</button>
143
<button type="button" class="time-travel-icon-button is-small" title="Copy path" aria-label="Copy path" @click="$store.timeTravel.copyPath($store.timeTravel.selectedFile())" :disabled="!$store.timeTravel.selectedFile()">
144
- <span class="material-symbols-outlined">content_copy</span>
144
+ <x-icon name="content_copy"></x-icon>
145
</button>
146
</div>
147
148
<div class="time-travel-diff-status" x-show="$store.timeTravel.diffLoading || $store.timeTravel.diffError" style="display: none;">
149
- <span class="material-symbols-outlined" :class="{ spinning: $store.timeTravel.diffLoading }" x-text="$store.timeTravel.diffError ? 'error' : 'progress_activity'"></span>
149
+ <x-icon :class="{ spinning: $store.timeTravel.diffLoading }" :name="$store.timeTravel.diffError ? 'error' : 'progress_activity'"></x-icon>
150
<span x-text="$store.timeTravel.diffError || 'Loading diff...'"></span>
151
</div>
152
153
<template x-if="$store.timeTravel.selectedDiff?.binary">
154
<div class="time-travel-empty">
155
- <span class="material-symbols-outlined">data_object</span>
155
+ <x-icon name="data_object"></x-icon>
156
<span>Binary file changed</span>
157
</div>
158
</template>
159
160
<template x-if="$store.timeTravel.selectedDiff?.too_large && !$store.timeTravel.selectedDiff?.binary">
161
<div class="time-travel-empty">
162
- <span class="material-symbols-outlined">text_snippet</span>
162
+ <x-icon name="text_snippet"></x-icon>
163
<span>Diff exceeds the 1 MB render limit</span>
164
</div>
165
</template>
@@ -183,15 +183,15 @@
183
<div class="time-travel-preview-backdrop" x-show="$store.timeTravel.previewOpen" style="display: none;">
184
<section class="time-travel-preview" role="dialog" aria-modal="true" aria-label="Time Travel preview">
185
<header>
186
- <span class="material-symbols-outlined" x-text="$store.timeTravel.preview?.operation === 'travel' ? 'move_down' : 'undo'"></span>
186
+ <x-icon :name="$store.timeTravel.preview?.operation === 'travel' ? 'move_down' : 'undo'"></x-icon>
187
<strong x-text="$store.timeTravel.preview?.operation === 'travel' ? 'Travel Preview' : 'Revert Preview'"></strong>
188
<button type="button" class="time-travel-icon-button is-small" title="Close" aria-label="Close" @click="$store.timeTravel.closePreview()" :disabled="$store.timeTravel.busy">
189
- <span class="material-symbols-outlined">close</span>
189
+ <x-icon name="close"></x-icon>
190
</button>
191
</header>
192
<div class="time-travel-preview-body">
193
<div class="time-travel-preview-status" x-show="$store.timeTravel.previewLoading || $store.timeTravel.previewError" style="display: none;">
194
- <span class="material-symbols-outlined" :class="{ spinning: $store.timeTravel.previewLoading }" x-text="$store.timeTravel.previewError ? 'error' : 'progress_activity'"></span>
194
+ <x-icon :class="{ spinning: $store.timeTravel.previewLoading }" :name="$store.timeTravel.previewError ? 'error' : 'progress_activity'"></x-icon>
195
<span x-text="$store.timeTravel.previewError || 'Building preview...'"></span>
196
</div>
197
<div class="time-travel-preview-summary">
@@ -214,11 +214,11 @@
214
</div>
215
<footer>
216
<button type="button" class="time-travel-tool-button" @click="$store.timeTravel.closePreview()" :disabled="$store.timeTravel.busy">
217
- <span class="material-symbols-outlined">close</span>
217
+ <x-icon name="close"></x-icon>
218
<span>Cancel</span>
219
</button>
220
<button type="button" class="time-travel-tool-button is-primary" @click="$store.timeTravel.confirmPreview()" :disabled="Boolean($store.timeTravel.busy || $store.timeTravel.previewLoading || $store.timeTravel.previewError)">
221
- <span class="material-symbols-outlined" :class="{ spinning: $store.timeTravel.busy }" x-text="$store.timeTravel.busy ? 'progress_activity' : ($store.timeTravel.preview?.operation === 'travel' ? 'move_down' : 'undo')"></span>
221
+ <x-icon :class="{ spinning: $store.timeTravel.busy }" :name="$store.timeTravel.busy ? 'progress_activity' : ($store.timeTravel.preview?.operation === 'travel' ? 'move_down' : 'undo')"></x-icon>
222
<span x-text="$store.timeTravel.preview?.operation === 'travel' ? 'Travel' : 'Revert'"></span>
223
</button>
224
</footer>
plugins/_whatsapp_integration/webui/config.html
+2
-2
@@ -264,8 +264,8 @@
264
<details class="wa-advanced">
265
<summary>
266
<span>Advanced</span>
267
- <span class="material-symbols-outlined wa-advanced-chevron"
268
- aria-hidden="true">keyboard_arrow_down</span>
267
+ <x-icon class="wa-advanced-chevron"
268
+ aria-hidden="true" name="keyboard_arrow_down"></x-icon>
269
</summary>
270
<div class="wa-advanced-body">
271
<div class="wa-info-box">
tests/test_sidebar_row_actions.py
+1
-1
@@ -11,7 +11,7 @@ def test_chat_rows_have_hover_scoped_overflow_actions() -> None:
11
12
assert html.count('aria-label="More chat actions"') == 2
13
assert 'class="btn-icon-action chat-list-action-btn"' in html
14
- assert "<span class=\"material-symbols-outlined\">more_vert</span>" in html
14
+ assert '<x-icon name="more_vert"></x-icon>' in html
15
assert html.count("$store.sidebar.rowMenuToggle(") == 2
16
17
tests/test_webui_icons.py
new
+111
@@ -0,0 +1,111 @@
1
+import re
2
+from pathlib import Path
3
+
4
+
5
+PROJECT_ROOT = Path(__file__).resolve().parents[1]
6
+WEBUI_ROOT = PROJECT_ROOT / "webui"
7
+PLUGINS_ROOT = PROJECT_ROOT / "plugins"
8
+LEGACY_ICON_SPAN = re.compile(
9
+ r"<span\b(?=[^>]*\b(?:material-symbols-outlined|material-icons-outlined)\b)",
10
+ re.IGNORECASE,
11
+)
12
+
13
+
14
+def first_party_icon_sources() -> list[Path]:
15
+ roots = [WEBUI_ROOT]
16
+ roots.extend(
17
+ path
18
+ for path in sorted(PLUGINS_ROOT.iterdir())
19
+ if path.is_dir() and path.name.startswith("_")
20
+ )
21
+ return [
22
+ path
23
+ for root in roots
24
+ for path in root.rglob("*")
25
+ if path.is_file()
26
+ and path.suffix in {".html", ".js"}
27
+ and not {"vendor", "node_modules"}.intersection(path.relative_to(root).parts)
28
+ ]
29
+
30
+
31
+def iter_x_icons(source: str):
32
+ cursor = 0
33
+ while True:
34
+ start = source.find("<x-icon", cursor)
35
+ if start < 0:
36
+ return
37
+
38
+ quote = ""
39
+ end = start + len("<x-icon")
40
+ while end < len(source):
41
+ char = source[end]
42
+ if quote:
43
+ if char == quote and source[end - 1] != "\\":
44
+ quote = ""
45
+ elif char in {'"', "'"}:
46
+ quote = char
47
+ elif char == ">":
48
+ break
49
+ end += 1
50
+
51
+ close = source.find("</x-icon>", end + 1)
52
+ assert end < len(source) and close >= 0
53
+ yield source[start : end + 1], source[end + 1 : close]
54
+ cursor = close + len("</x-icon>")
55
+
56
+
57
+def test_first_party_webui_uses_empty_named_x_icon_elements() -> None:
58
+ legacy_offenders: list[str] = []
59
+ malformed: list[str] = []
60
+ icon_count = 0
61
+
62
+ for path in first_party_icon_sources():
63
+ source = path.read_text(encoding="utf-8")
64
+ if LEGACY_ICON_SPAN.search(source):
65
+ legacy_offenders.append(str(path.relative_to(PROJECT_ROOT)))
66
+
67
+ for start_tag, content in iter_x_icons(source):
68
+ icon_count += 1
69
+ if (
70
+ content.strip()
71
+ or re.search(r"\sx-text\s*=", start_tag)
72
+ or not re.search(r"(?:^|\s)(?::name|name)\s*=", start_tag)
73
+ ):
74
+ malformed.append(
75
+ f"{path.relative_to(PROJECT_ROOT)}: {start_tag[:160]}"
76
+ )
77
+
78
+ assert icon_count >= 500
79
+ assert legacy_offenders == []
80
+ assert malformed == []
81
+
82
+
83
+def test_x_icon_is_font_backed_and_keeps_legacy_plugin_compatibility() -> None:
84
+ icons_js = (WEBUI_ROOT / "js" / "icons.js").read_text(encoding="utf-8")
85
+ icon_css = (WEBUI_ROOT / "vendor" / "google" / "google-icons.css").read_text(
86
+ encoding="utf-8"
87
+ )
88
+ index_html = (WEBUI_ROOT / "index.html").read_text(encoding="utf-8")
89
+
90
+ assert '<script type="module" src="/js/icons.js"></script>' in index_html
91
+ assert 'customElements.define(' in icons_js
92
+ assert 'return ["name"]' in icons_js
93
+ assert 'this.classList.add("material-symbols-outlined")' in icons_js
94
+ assert ".material-symbols-outlined" in icons_js
95
+ assert ".material-icons-outlined" in icons_js
96
+ assert "maskImage" not in icons_js
97
+ assert "/vendor/icons/" not in icons_js
98
+
99
+ assert "x-icon," in icon_css
100
+ assert ".material-symbols-outlined," in icon_css
101
+ assert ".material-icons-outlined" in icon_css
102
+ assert "width: 1em !important" in icon_css
103
+ assert "overflow: hidden !important" in icon_css
104
+ assert (
105
+ "document.fonts.load('24px \"Material Symbols Outlined\"')"
106
+ in index_html
107
+ )
108
+ assert (
109
+ 'document.addEventListener("DOMContentLoaded", loadMaterialIcons, { once: true })'
110
+ in index_html
111
+ )
tests/test_webui_message_window.py
+73
@@ -17,6 +17,7 @@ PROCESS_GROUP_DOM_JS = (
17
/ "process-group"
18
/ "process-group-dom.js"
19
)
20
+MESSAGE_COLLAPSE_JS = PROJECT_ROOT / "webui" / "js" / "message-collapse.js"
21
22
23
def test_message_window_keeps_tail_and_pages_bidirectionally():
@@ -223,6 +224,9 @@ def test_user_messages_share_collapse_behavior_without_clipping_attachments():
224
225
assert 'contentSelector = ":scope > .message-body"' in messages
226
assert 'collapseContent?.classList.add("message-collapse-content")' in messages
227
+ assert 'refreshCollapsibleMessageOverflow(history)' in messages
228
+ assert 'refreshCollapsibleMessageOverflow(entry.target)' in messages
229
+ assert 'measureMessageCollapseOverflow(collapseContent' in messages
230
assert '":scope > .message-text",\n );' in messages
231
assert "attachmentsContainer.classList.add(\"attachments-container\")" in messages
232
assert ".message.message-collapsible .message-collapse-content" in message_css
@@ -232,6 +236,69 @@ def test_user_messages_share_collapse_behavior_without_clipping_attachments():
236
assert "order: 1" in action_button_css
237
238
239
+def test_message_collapse_ignores_hidden_replay_geometry_and_real_short_text():
240
+ if not shutil.which("node"):
241
+ pytest.skip("Node.js is required to execute the collapse regression.")
242
+
243
+ source = MESSAGE_COLLAPSE_JS.read_bytes()
244
+ module_url = "data:text/javascript;base64," + base64.b64encode(source).decode(
245
+ "ascii"
246
+ )
247
+ script = f"""
248
+import {{ measureMessageCollapseOverflow }} from {module_url!r};
249
+
250
+function assert(condition, message) {{
251
+ if (!condition) throw new Error(message);
252
+}}
253
+
254
+let historyWidth = 0;
255
+const history = {{
256
+ get clientWidth() {{ return historyWidth || 24; }},
257
+}};
258
+const content = {{
259
+ isConnected: true,
260
+ clientWidth: 210,
261
+ clientHeight: 34,
262
+ scrollHeight: 280,
263
+ getBoundingClientRect: () => ({{ width: 210 }}),
264
+ closest: (selector) => selector === "#chat-history" ? history : null,
265
+}};
266
+globalThis.getComputedStyle = (element) => ({{
267
+ fontSize: "16px",
268
+ paddingLeft: element === history ? "12px" : "0px",
269
+ paddingRight: element === history ? "12px" : "0px",
270
+}});
271
+
272
+assert(
273
+ measureMessageCollapseOverflow(content) === null,
274
+ "zero-width replay staging must not mark short text as overflowing",
275
+);
276
+
277
+historyWidth = 800;
278
+content.scrollHeight = 34;
279
+assert(
280
+ measureMessageCollapseOverflow(content) === false,
281
+ "a laid-out one-line message must not expose Show More",
282
+);
283
+
284
+content.clientHeight = 240;
285
+content.scrollHeight = 420;
286
+assert(
287
+ measureMessageCollapseOverflow(content) === true,
288
+ "a body taller than the collapsed preview must expose Show More",
289
+);
290
+assert(
291
+ measureMessageCollapseOverflow(content, {{ expanded: true }}) === true,
292
+ "expanded long bodies must retain their Show Less control",
293
+);
294
+"""
295
+ subprocess.run(
296
+ ["node", "--input-type=module", "-e", script],
297
+ check=True,
298
+ text=True,
299
+ )
300
+
301
+
302
def test_process_groups_are_atomic_and_page_steps_in_fifties():
303
messages = (PROJECT_ROOT / "webui" / "js" / "messages.js").read_text(
304
encoding="utf-8"
@@ -260,6 +327,12 @@ def test_process_groups_are_atomic_and_page_steps_in_fifties():
327
assert ".show-utility-messages .process-group.utility-only" in group_css
328
assert ".process-group.utility-only[hidden]" not in group_css
329
assert ".process-group-show-more" in group_css
330
+ show_more_css = group_css.split(".process-group-show-more {", 1)[1].split(
331
+ "}", 1
332
+ )[0]
333
+ assert "text-decoration: none" in show_more_css
334
+ assert "opacity: 0.7" in show_more_css
335
+ assert "text-decoration: underline" not in show_more_css
336
337
338
def test_detail_preferences_await_materialization_and_select_current_step():
tests/test_webui_offline_assets.py
+3
@@ -142,6 +142,9 @@ def test_material_icon_font_is_preloaded_and_layout_stable() -> None:
142
assert "height: 1em !important" in icon_css
143
assert "overflow: hidden !important" in icon_css
144
assert "html:not(.material-icons-ready)" in icon_css
145
+ assert "x-icon," in icon_css
146
+ assert ".material-symbols-outlined," in icon_css
147
+ assert ".material-icons-outlined" in icon_css
148
preload = (
149
'<link rel="preload" href="/vendor/google/google-icons.woff2" '
150
'as="font" type="font/woff2" crossorigin>'
tests/test_webui_startup_assets.py
+5
@@ -107,6 +107,7 @@ def test_only_the_icon_guard_stylesheet_blocks_application_paint() -> None:
107
108
assert index_html.count('<link rel="stylesheet"') == 1
109
assert '<link rel="stylesheet" href="vendor/google/google-icons.css">' in index_html
110
+ assert '<script type="module" src="/js/icons.js"></script>' in index_html
111
assert index_html.count('rel="preload" as="style"') == 19
112
assert index_html.count("onload=\"this.onload=null;this.rel='stylesheet'\"") == 19
113
assert 'id="startup-splash"' not in index_html
@@ -129,6 +130,10 @@ def test_startup_splash_is_handed_to_the_index_and_fades_when_ready() -> None:
130
assert 'document.addEventListener("webui-extensions-loaded"' in index_html
131
assert 'overlay.classList.add("startup-transition-leaving")' in index_html
132
assert 'document.fonts.load(\'24px "Material Symbols Outlined"\')' in index_html
133
+ assert (
134
+ 'document.addEventListener("DOMContentLoaded", loadMaterialIcons, { once: true })'
135
+ in index_html
136
+ )
137
assert 'localStorage.getItem("darkMode") === "false"' in index_html
138
assert "webuiExtensions: {{webui_extension_manifest}}" in index_html
139
assert 'manifestExtensionPaths("html", extensionPoint)' in extensions_js
webui/AGENTS.md
+1
-1
@@ -24,7 +24,7 @@
24
- Frontend extension breakpoints use `<x-extension id="...">` and are loaded through `/js/extensions.js`.
25
- `sw.js` owns same-origin HTML/CSS/JavaScript caching. The self-contained splash fetches `/ui/index` and the gzip-compressed `/ui/asset-bundle` in parallel, installs the versioned worker, waits until the bundle is usable in memory, and then installs the rendered index into the current `/` document without navigation. The worker persists the whole prepared bundle as one Cache Storage response in the background and removes obsolete version caches. Eligible text bodies up to 512 KiB each may be embedded; unknown or runtime-computed text URLs use one ordinary backend request and are cached as the native response when their transferred body is at most 256 KiB. The worker does not parse imports, call a secondary graph endpoint, or perform compression. Worker, cache, bundle, and timeout failures fall through to the already-started backend document or ordinary backend asset request. Media, images, fonts, and manifests use normal browser HTTP caching.
26
- `splash.html` keeps its logo, styling, and bootstrap logic self-contained. Before replacing the document, it carries its current overlay markup into the rendered index. `index.html` owns the matching inline critical overlay style and fades that same visual overlay after initial component/extension readiness, with a bounded failure timeout; it must not contain duplicate static overlay markup. Icon-font preloading begins when the index is placed behind the overlay, continues independently behind square transparent placeholders, and must neither compete with the startup bundle nor delay the application reveal. `safe.html` must remain self-contained: its first phase unregisters all service workers for the origin, then replaces itself with the directly rendered `/safe` phase without fetching the bundle or registering a worker. The direct phase repeats unregistration defensively but does not initialize a worker; returning to `/` may install the current worker again. The rendered index removes only the internal safe-mode query marker from `/safe` in browser history. Except for the tiny blocking icon guard stylesheet needed to prevent ligature layout shifts, initial application stylesheets remain non-render-blocking.
27
-- Material Symbols use the local WOFF2 font with a TTF fallback. Their shared vendor CSS constrains every ligature box to a clipped 1:1 em square and keeps it transparent until the font is confirmed, so slow or failed fonts cannot expose icon-name text or shift surrounding layout.
27
+- `js/icons.js` owns the native `<x-icon>` element. First-party WebUI and bundled-plugin markup must use `<x-icon name="lowercase_snake_case"></x-icon>` for static Material Symbols and `:name="expression"` for Alpine-driven names; do not put ligature text inside the element or author new `.material-symbols-outlined` spans. The element uses the single local WOFF2 font rather than per-icon SVG requests, validates names, defaults unlabeled icons to `aria-hidden="true"`, and retains the legacy Material class internally so established selectors continue to apply. The shared vendor CSS constrains both `<x-icon>` and legacy ligature spans to a clipped 1:1 em square and keeps them transparent until the icon face is confirmed after the installed application document finishes parsing. `.material-symbols-outlined` and `.material-icons-outlined` spans remain supported for third-party plugin compatibility but are not the first-party authoring API.
28
- Component HTML loaded by the shared loader may include `<title>`, module scripts, body content, and scoped styles; modal content uses the same loader path.
29
- Do not bypass WebSocket origin/auth/CSRF assumptions from frontend code.
30
- Avoid editing vendored files unless intentionally updating the vendor asset.
webui/components/AGENTS.md
+1
@@ -27,6 +27,7 @@
27
- Store state can be read as `$store.name` in templates and through direct module imports in JavaScript; avoid `window.Alpine.store()` lookups in component code.
28
- Use `globalThis.xAttrs(element)` only for explicit parent `<x-component>` attribute inheritance.
29
- Use shared API, modal, notification, confirmation, and store helpers instead of ad hoc globals.
30
+- Author Material Symbols as empty `<x-icon name="lowercase_snake_case"></x-icon>` elements. Bind changing icons through `:name`, keep accessible text on the owning control, and do not add new ligature-text spans or `x-text` icon bindings.
31
- Components that use polling directives may use `x-every-second`, `x-every-minute`, or `x-every-hour` only while mounted.
32
- Use `$confirmClick` for destructive two-click confirmations where the existing UI pattern fits.
33
- Name component files `feature-name.html` and stores `feature-store.js` or `feature-name-store.js`.
webui/components/canvas/right-canvas.html
+6
-6
@@ -35,7 +35,7 @@
35
:aria-pressed="$store.rightCanvas.isOpen.toString()"
36
@click="$store.rightCanvas.toggleCanvas()"
37
>
38
- <span class="material-symbols-outlined">dock_to_right</span>
38
+ <x-icon name="dock_to_right"></x-icon>
39
</button>
40
41
<div class="right-canvas-rail-separator" aria-hidden="true"></div>
@@ -53,7 +53,7 @@
53
<img class="right-canvas-surface-image" :src="surface.image" alt="" aria-hidden="true">
54
</template>
55
<template x-if="!surface.image">
56
- <span class="material-symbols-outlined" x-text="surface.icon"></span>
56
+ <x-icon :name="surface.icon"></x-icon>
57
</template>
58
</button>
59
</template>
@@ -79,7 +79,7 @@
79
<img class="right-canvas-surface-image" :src="surface.image" alt="" aria-hidden="true">
80
</template>
81
<template x-if="!surface.image">
82
- <span class="material-symbols-outlined" aria-hidden="true" x-text="surface.icon"></span>
82
+ <x-icon aria-hidden="true" :name="surface.icon"></x-icon>
83
</template>
84
<span class="right-canvas-tab-label" x-text="surface.title"></span>
85
</button>
@@ -97,7 +97,7 @@
97
x-show="$store.rightCanvas.currentSurfaceCanUndock()"
98
@click="$store.rightCanvas.undockActiveSurface()"
99
>
100
- <span class="material-symbols-outlined">open_in_new</span>
100
+ <x-icon name="open_in_new"></x-icon>
101
</button>
102
<button
103
type="button"
@@ -106,7 +106,7 @@
106
aria-label="Close canvas"
107
@click="$store.rightCanvas.close()"
108
>
109
- <span class="material-symbols-outlined">close</span>
109
+ <x-icon name="close"></x-icon>
110
</button>
111
<x-extension id="right-canvas-toolbar-end"></x-extension>
112
</div>
@@ -117,7 +117,7 @@
117
<template x-if="$store.rightCanvas.panelSurfaces.length === 0">
118
<div class="right-canvas-empty-state">
119
<x-extension id="right-canvas-empty-state"></x-extension>
120
- <span class="material-symbols-outlined">space_dashboard</span>
120
+ <x-icon name="space_dashboard"></x-icon>
121
<span>Canvas</span>
122
</div>
123
</template>
webui/components/chat/input/bottom-actions.html
+3
-3
@@ -45,7 +45,7 @@
45
class="chat-bottom-menu-item"
46
@click.stop="$refs.chatFolderInput?.click()"
47
>
48
- <span class="material-symbols-outlined" aria-hidden="true">drive_folder_upload</span>
48
+ <x-icon aria-hidden="true" name="drive_folder_upload"></x-icon>
49
<span>Attach folder</span>
50
</button>
51
@@ -54,7 +54,7 @@
54
class="chat-bottom-menu-item"
55
@click="$store.mcpServersStore.openFromComposer($store.chats.selectedContext?.project?.name || ''); $store.chatInput.closeChatMoreMenu()"
56
>
57
- <span class="material-symbols-outlined" aria-hidden="true">hub</span>
57
+ <x-icon aria-hidden="true" name="hub"></x-icon>
58
<span>MCP Servers</span>
59
</button>
60
@@ -64,7 +64,7 @@
64
@click="$confirmClick($event, () => { $store.chats.resetChat(); $store.chatInput.closeChatMoreMenu() })"
65
:disabled="!$store.chats.selected"
66
>
67
- <span class="material-symbols-outlined" aria-hidden="true">delete_sweep</span>
67
+ <x-icon aria-hidden="true" name="delete_sweep"></x-icon>
68
<span>Clear Chat</span>
69
</button>
70
webui/components/chat/input/chat-bar-input.html
+3
-3
@@ -17,7 +17,7 @@
17
@keydown.escape.window="$store.chatInput.closeChatMoreMenu()">
18
<button type="button" class="chat-more-trigger" @click.stop="$store.chatInput.toggleChatMoreMenu()"
19
:aria-expanded="$store.chatInput.chatMoreMenuOpen.toString()" aria-label="More actions">
20
- <span class="material-symbols-outlined" aria-hidden="true">add</span>
20
+ <x-icon aria-hidden="true" name="add"></x-icon>
21
</button>
22
<div class="chat-more-panel" x-show="$store.chatInput.chatMoreMenuOpen" x-transition style="display: none;">
23
<x-component path="chat/input/bottom-actions.html"></x-component>
@@ -39,7 +39,7 @@
39
:class="{ 'progress-active': $store.chatInput.progressActive }" aria-hidden="true" style="display: none;">
40
</div>
41
<button id="expand-button" @click="$store.fullScreenInputModal.openModal()" aria-label="Expand input">
42
- <span class="material-symbols-outlined" aria-hidden="true">fullscreen</span>
42
+ <x-icon aria-hidden="true" name="fullscreen"></x-icon>
43
</button>
44
</div>
45
@@ -48,7 +48,7 @@
48
<button class="chat-button" id="send-button" @click="$store.chatInput.activateSendButton()"
49
:class="$store.chatInput.sendButtonClass" :title="$store.chatInput.sendButtonTitle"
50
:aria-label="$store.chatInput.sendButtonTitle">
51
- <span class="material-symbols-outlined" x-text="$store.chatInput.sendButtonIcon"></span>
51
+ <x-icon :name="$store.chatInput.sendButtonIcon"></x-icon>
52
</button>
53
</div>
54
</div>
webui/components/chat/input/input-store.js
+2
-2
@@ -37,8 +37,8 @@ function convertIconMarkersToHtml(value) {
37
html += escapeHTML(text.slice(lastIndex, offset));
38
const tooltip = unescapeIconTooltip(tooltipBlock) || iconName;
39
html += (
40
- `<span class="icon material-symbols-outlined chat-input-progress-icon" ` +
41
- `title="${escapeHTML(tooltip)}">${escapeHTML(iconName)}</span>`
40
+ `<x-icon class="icon chat-input-progress-icon" ` +
41
+ `title="${escapeHTML(tooltip)}" name="${escapeHTML(iconName)}"></x-icon>`
42
);
43
lastIndex = offset + match.length;
44
return match;
webui/components/chat/input/progress.html
+1
-1
@@ -35,7 +35,7 @@
35
x-show="speaking"
36
>
37
<span id="stop-speech" @click="window.ttsService?.stop?.()" style="cursor: pointer" title="Stop Speech" aria-label="Stop Speech">
38
- <span class="icon material-symbols-outlined">volume_off</span>
38
+ <x-icon class="icon" name="volume_off"></x-icon>
39
</span>
40
</h4>
41
<x-extension id="chat-nav-after"></x-extension>
webui/components/chat/message-queue/message-queue.html
+4
-4
@@ -14,10 +14,10 @@
14
</span>
15
<div class="queue-header-actions">
16
<button class="queue-header-btn" @click="$store.messageQueue.sendAll()" title="Send all">
17
- <span class="material-symbols-outlined">keyboard_double_arrow_right</span>
17
+ <x-icon name="keyboard_double_arrow_right"></x-icon>
18
</button>
19
<button class="queue-header-btn delete" @click="$store.messageQueue.clearQueue()" title="Clear all">
20
- <span class="material-symbols-outlined">delete_sweep</span>
20
+ <x-icon name="delete_sweep"></x-icon>
21
</button>
22
</div>
23
</div>
@@ -47,10 +47,10 @@
47
</div>
48
<div class="queue-item-actions">
49
<button class="queue-action-btn send" @click="$store.messageQueue.sendItem(item.id)" title="Send now">
50
- <span class="material-symbols-outlined">send</span>
50
+ <x-icon name="send"></x-icon>
51
</button>
52
<button class="queue-action-btn delete" @click="$store.messageQueue.removeItem(item.id)" title="Remove">
53
- <span class="material-symbols-outlined">close</span>
53
+ <x-icon name="close"></x-icon>
54
</button>
55
</div>
56
</div>
webui/components/chat/model-setup-gate.html
+5
-5
@@ -10,7 +10,7 @@
10
<section class="model-gate" x-create="$store.modelGate.onCardCreate()">
11
<template x-if="$store.modelGate.connected">
12
<div class="model-gate-chip">
13
- <span class="material-symbols-outlined" aria-hidden="true">check_circle</span>
13
+ <x-icon aria-hidden="true" name="check_circle"></x-icon>
14
<span x-text="$store.modelGate.introText"></span>
15
</div>
16
</template>
@@ -25,7 +25,7 @@
25
class="model-gate-option"
26
@click="$store.modelGate.openAdvancedModelConfiguration()">
27
<span class="model-gate-option-title">
28
- <span class="material-symbols-outlined" aria-hidden="true">tune</span>
28
+ <x-icon aria-hidden="true" name="tune"></x-icon>
29
<span>Choose models</span>
30
</span>
31
<span class="model-gate-option-sub">Select Main and Utility models</span>
@@ -38,7 +38,7 @@
38
:class="{ selected: $store.modelGate.choice === 'cloud' }"
39
@click="$store.modelGate.openOnboarding('cloud')">
40
<span class="model-gate-option-title">
41
- <span class="material-symbols-outlined" aria-hidden="true">cloud</span>
41
+ <x-icon aria-hidden="true" name="cloud"></x-icon>
42
<span>Cloud provider</span>
43
</span>
44
<span class="model-gate-option-sub">Connect with an API key</span>
@@ -48,7 +48,7 @@
48
:class="{ selected: $store.modelGate.choice === 'account' }"
49
@click="$store.modelGate.openOnboarding('account')">
50
<span class="model-gate-option-title">
51
- <span class="material-symbols-outlined" aria-hidden="true">account_circle</span>
51
+ <x-icon aria-hidden="true" name="account_circle"></x-icon>
52
<span>AI account</span>
53
</span>
54
<span class="model-gate-option-sub">Codex, Copilot, Gemini, Grok</span>
@@ -58,7 +58,7 @@
58
:class="{ selected: $store.modelGate.choice === 'local' }"
59
@click="$store.modelGate.openOnboarding('local')">
60
<span class="model-gate-option-title">
61
- <span class="material-symbols-outlined" aria-hidden="true">memory</span>
61
+ <x-icon aria-hidden="true" name="memory"></x-icon>
62
<span>Local model</span>
63
</span>
64
<span class="model-gate-option-sub">Runs on your machine</span>
webui/components/messages/AGENTS.md
+1
@@ -21,6 +21,7 @@
21
- Preference-driven process detail modes must await the same materialization path as manual expansion and accept an explicit chat-history target for off-screen window staging. `STEP` opens only the current non-utility step at the live tail; historical windows must not invent a current step at their boundary.
22
- Keep oversized standalone replay bodies and key/value tables in a bounded preview state until the user expands them; collapsing must remove the full body again.
23
- Message-window boundaries must not split process groups. Groups with more than 50 steps initially render their newest 50 steps and prepend earlier steps in 50-step increments through the group-local `Show more` control while retaining stable full-group header metrics.
24
+- The process-group `Show more` paging control uses the same understated, non-underlined typography and hover-opacity treatment as message-body expansion controls.
25
- A root response may attach only to a substantive process render unit. Utility-prefixed units remain visible even while utility steps are hidden; standalone utility-only groups remain separate and hidden while utility messages are disabled, and completed groups must not absorb later utility records. Determine this from full-log render metadata, not partially mounted DOM children.
26
27
## Work Guidance
webui/components/messages/action-buttons/simple-action-buttons.js
+8
-4
@@ -1,5 +1,7 @@
1
// Message Action Buttons - DOM helpers for message action buttons
2
3
+import { ICON_SELECTOR, setIconName } from "/js/icons.js";
4
+
5
const ACTION_ICON_MAP = {
6
detail: "open_in_full",
7
speak: "volume_up",
@@ -45,14 +47,14 @@ export async function copyToClipboard(text) {
47
* Show visual feedback on a button (success/error state)
48
*/
49
export function showButtonFeedback(button, success, originalIcon) {
48
- const icon = button.querySelector(".material-symbols-outlined");
50
+ const icon = button.querySelector(ICON_SELECTOR);
51
if (!icon) return;
52
51
- icon.textContent = success ? "check" : "error";
53
+ setIconName(icon, success ? "check" : "error");
54
button.classList.add(success ? "success" : "error");
55
56
setTimeout(() => {
55
- icon.textContent = originalIcon;
57
+ setIconName(icon, originalIcon);
58
button.classList.remove("success", "error");
59
}, 1000);
60
}
@@ -78,7 +80,9 @@ export function createActionButton(icon, text = "", handler = null) {
80
}
81
82
if (iconName) {
81
- button.innerHTML = `<span class="material-symbols-outlined">${iconName}</span>`;
83
+ const iconElement = document.createElement("x-icon");
84
+ iconElement.name = iconName;
85
+ button.appendChild(iconElement);
86
} else if (text) {
87
button.textContent = text;
88
}
webui/components/messages/process-group/process-group.css
+6
-3
@@ -388,6 +388,8 @@
388
.process-group-show-more {
389
align-self: flex-start;
390
appearance: none;
391
+ display: inline-flex;
392
+ align-items: center;
393
border: 0;
394
background: transparent;
395
color: var(--color-text-muted);
@@ -395,13 +397,14 @@
397
font-family: var(--font-family-main);
398
font-size: var(--font-size-xs);
399
padding: var(--spacing-xs) 0;
398
- text-decoration: underline;
399
- text-underline-offset: 0.15em;
400
+ opacity: 0.7;
401
+ text-decoration: none;
402
+ transition: opacity 0.15s ease;
403
}
404
405
.process-group-show-more:hover,
406
.process-group-show-more:focus-visible {
404
- color: var(--color-text);
407
+ opacity: 1;
408
}
409
410
/* Individual Process Step */
webui/components/modals/file-browser/file-browser.html
+22
-22
@@ -45,7 +45,7 @@
45
aria-label="Navigate up"
46
title="Navigate up"
47
>
48
- <span class="material-symbols-outlined" aria-hidden="true">arrow_upward</span>
48
+ <x-icon aria-hidden="true" name="arrow_upward"></x-icon>
49
<span class="nav-button-label">Up</span>
50
</button>
51
<div class="path-input-shell" :class="{ 'has-error': $store.fileBrowser.pathError }">
@@ -67,7 +67,7 @@
67
:disabled="$store.fileBrowser.isPathSubmitting || $store.fileBrowser.isLoading"
68
aria-label="Go to directory"
69
>
70
- <span class="material-symbols-outlined">arrow_forward</span>
70
+ <x-icon name="arrow_forward"></x-icon>
71
</button>
72
</div>
73
</form>
@@ -77,7 +77,7 @@
77
</div>
78
<div class="file-browser-toolbar">
79
<div class="file-search-shell">
80
- <span class="material-symbols-outlined file-search-icon" aria-hidden="true">search</span>
80
+ <x-icon class="file-search-icon" aria-hidden="true" name="search"></x-icon>
81
<input
82
type="search"
83
class="file-search-input"
@@ -94,7 +94,7 @@
94
aria-label="Clear search"
95
title="Clear search"
96
>
97
- <span class="material-symbols-outlined">close</span>
97
+ <x-icon name="close"></x-icon>
98
</button>
99
</div>
100
@@ -107,7 +107,7 @@
107
aria-label="New file"
108
title="New file"
109
>
110
- <span class="material-symbols-outlined" aria-hidden="true">note_add</span>
110
+ <x-icon aria-hidden="true" name="note_add"></x-icon>
111
</button>
112
<button
113
type="button"
@@ -117,7 +117,7 @@
117
aria-label="New folder"
118
title="New folder"
119
>
120
- <span class="material-symbols-outlined" aria-hidden="true">create_new_folder</span>
120
+ <x-icon aria-hidden="true" name="create_new_folder"></x-icon>
121
</button>
122
</div>
123
</div>
@@ -125,7 +125,7 @@
125
<div class="data-status-bar data-status-bar-standalone file-status-bar">
126
<div class="status-info">
127
<span class="status-item">
128
- <span class="material-symbols-outlined">folder</span>
128
+ <x-icon name="folder"></x-icon>
129
Total: <strong x-text="$store.fileBrowser.browser.entries.length"></strong>
130
</span>
131
<span class="status-separator">•</span>
@@ -154,7 +154,7 @@
154
:disabled="$store.fileBrowser.isBulkBusy"
155
title="Copy Selected Paths"
156
>
157
- <span class="material-symbols-outlined">content_copy</span>
157
+ <x-icon name="content_copy"></x-icon>
158
Copy Paths
159
</button>
160
@@ -165,7 +165,7 @@
165
:disabled="$store.fileBrowser.isBulkBusy"
166
title="Download Selected Items"
167
>
168
- <span class="material-symbols-outlined">folder_zip</span>
168
+ <x-icon name="folder_zip"></x-icon>
169
Download ZIP
170
</button>
171
@@ -176,7 +176,7 @@
176
:disabled="$store.fileBrowser.isBulkBusy"
177
title="Delete Selected Items"
178
>
179
- <span class="material-symbols-outlined">delete</span>
179
+ <x-icon name="delete"></x-icon>
180
Delete
181
</button>
182
@@ -187,7 +187,7 @@
187
:disabled="$store.fileBrowser.isBulkBusy"
188
title="Clear Selection"
189
>
190
- <span class="material-symbols-outlined">close</span>
190
+ <x-icon name="close"></x-icon>
191
Clear
192
</button>
193
</div>
@@ -240,7 +240,7 @@
240
:title="$store.fileBrowser.surfaceActionTitle(file)"
241
aria-label="Open in Editor"
242
>
243
- <span class="material-symbols-outlined" aria-hidden="true" x-text="$store.fileBrowser.surfaceActionIcon(file)"></span>
243
+ <x-icon aria-hidden="true" :name="$store.fileBrowser.surfaceActionIcon(file)"></x-icon>
244
</button>
245
246
<!-- Secondary single-item actions (Rename/...) are grouped under a dropdown -->
@@ -257,7 +257,7 @@
257
aria-label="More actions"
258
title="More actions"
259
>
260
- <span class="material-symbols-outlined">more_vert</span>
260
+ <x-icon name="more_vert"></x-icon>
261
</button>
262
263
<template x-teleport="body">
@@ -276,7 +276,7 @@
276
@click="$store.fileBrowser.openInSurface(file)"
277
:title="$store.fileBrowser.surfaceActionTitle(file)"
278
>
279
- <span class="material-symbols-outlined" x-text="$store.fileBrowser.surfaceActionIcon(file)"></span>
279
+ <x-icon :name="$store.fileBrowser.surfaceActionIcon(file)"></x-icon>
280
<span x-text="$store.fileBrowser.surfaceActionLabel(file)"></span>
281
</button>
282
@@ -286,7 +286,7 @@
286
x-show="!file.is_dir && file.size <= 1048576 && !$store.fileBrowser.canOpenInSurface(file)"
287
@click="$store.fileBrowser.openFileEditor(file)"
288
>
289
- <span class="material-symbols-outlined">file_open</span>
289
+ <x-icon name="file_open"></x-icon>
290
<span>Edit</span>
291
</button>
292
@@ -296,7 +296,7 @@
296
x-show="file.is_dir"
297
@click="$store.fileBrowser.downloadFile(file)"
298
>
299
- <span class="material-symbols-outlined">folder_zip</span>
299
+ <x-icon name="folder_zip"></x-icon>
300
<span>Download ZIP</span>
301
</button>
302
@@ -306,7 +306,7 @@
306
x-show="!file.is_dir && $store.fileBrowser.isArchive(file.name)"
307
@click="$store.fileBrowser.extractArchive(file)"
308
>
309
- <span class="material-symbols-outlined">unarchive</span>
309
+ <x-icon name="unarchive"></x-icon>
310
<span>Extract</span>
311
</button>
312
@@ -315,17 +315,17 @@
315
class="dropdown-item"
316
@click="$store.fileBrowser.openRenameModal(file)"
317
>
318
- <span class="material-symbols-outlined">drive_file_rename_outline</span>
318
+ <x-icon name="drive_file_rename_outline"></x-icon>
319
<span>Rename</span>
320
</button>
321
</div>
322
</template>
323
</div>
324
<button class="btn-icon-action" x-show="!file.is_dir" @click.stop="$store.fileBrowser.downloadFile(file)" title="Download file">
325
- <span class="material-symbols-outlined">download</span>
325
+ <x-icon name="download"></x-icon>
326
</button>
327
<button class="btn-icon-action danger" @click.stop="$confirmClick($event, () => $store.fileBrowser.deleteFile(file))" title="Delete item">
328
- <span class="material-symbols-outlined">delete</span>
328
+ <x-icon name="delete"></x-icon>
329
</button>
330
</div>
331
</div>
@@ -340,7 +340,7 @@
340
<div class="no-files">
341
<div>No matching files found</div>
342
<button class="btn btn-cancel btn-clear-search" @click="$store.fileBrowser.clearSearch()">
343
- <span class="material-symbols-outlined">close</span>
343
+ <x-icon name="close"></x-icon>
344
Clear
345
</button>
346
</div>
@@ -358,7 +358,7 @@
358
<div class="file-browser-picker-actions" x-show="$store.fileBrowser.isPickerMode()" style="display: none;">
359
<template x-if="$store.fileBrowser.isSaveAsPicker()">
360
<div class="picker-filename-field">
361
- <span class="material-symbols-outlined" aria-hidden="true">article</span>
361
+ <x-icon aria-hidden="true" name="article"></x-icon>
362
<input
363
type="text"
364
class="picker-filename-input"
webui/components/modals/image-viewer/image-viewer.html
+3
-3
@@ -45,13 +45,13 @@
45
<!-- Fixed zoom controls - OUTSIDE the canvas -->
46
<div class="zoom-controls">
47
<button type="button" @click="$store.imageViewer.zoomOut()" class="button zoom-btn">
48
- <span class="icon material-symbols-outlined">zoom_out</span>
48
+ <x-icon class="icon" name="zoom_out"></x-icon>
49
</button>
50
<button type="button" @click="$store.imageViewer.resetZoom()" class="button zoom-btn">
51
- <span class="icon material-symbols-outlined">home</span>
51
+ <x-icon class="icon" name="home"></x-icon>
52
</button>
53
<button type="button" @click="$store.imageViewer.zoomIn()" class="button zoom-btn">
54
- <span class="icon material-symbols-outlined">zoom_in</span>
54
+ <x-icon class="icon" name="zoom_in"></x-icon>
55
</button>
56
</div>
57
</div>
webui/components/modals/markdown/markdown-modal.html
+1
-1
@@ -14,7 +14,7 @@
14
class="md-modal-root">
15
16
<div x-show="$store.markdownModal.error" class="md-modal-error">
17
- <span class="material-symbols-outlined">error</span>
17
+ <x-icon name="error"></x-icon>
18
<span x-text="$store.markdownModal.error"></span>
19
</div>
20
webui/components/modals/process-step-detail/process-step-detail.html
+3
-3
@@ -26,18 +26,18 @@
26
<button class="btn btn-action-header copy-all"
27
@click.stop="$store.stepDetail.copyToClipboard($store.stepDetail.formatStepForCopy($store.stepDetail.selectedStepForDetail))"
28
title="Copy Complete Step with Metadata">
29
- <span class="material-symbols-outlined">copy_all</span> All
29
+ <x-icon name="copy_all"></x-icon> All
30
</button>
31
<button class="btn btn-action-header copy-content"
32
@click.stop="$store.stepDetail.copyToClipboard($store.stepDetail.getStepPrimaryContent($store.stepDetail.selectedStepForDetail))"
33
title="Copy Raw LLM Response">
34
- <span class="material-symbols-outlined">content_copy</span> Content
34
+ <x-icon name="content_copy"></x-icon> Content
35
</button>
36
<button class="btn btn-action-header toggle-raw"
37
@click.stop="showRaw = !showRaw"
38
:class="{ 'active': showRaw }"
39
title="Toggle Raw JSON View">
40
- <span class="material-symbols-outlined">data_object</span>
40
+ <x-icon name="data_object"></x-icon>
41
</button>
42
</div>
43
</div>
webui/components/modals/scheduler/scheduler-task-editor.html
+6
-6
@@ -143,7 +143,7 @@
143
<span x-text="$store.schedulerStore.formatDate(time)"></span>
144
<button @click.prevent="$store.schedulerStore.editingTask.plan.todo.splice(index, 1)"
145
class="scheduler-todo-remove">
146
- <span class="material-symbols-outlined">close</span>
146
+ <x-icon name="close"></x-icon>
147
</button>
148
</div>
149
</template>
@@ -159,7 +159,7 @@
159
placeholder="Select date and time">
160
<button @click.prevent="$store.schedulerStore.addPlannedTime('create')"
161
class="btn btn-ok scheduler-add-todo-button">
162
- <span class="material-symbols-outlined">add</span>
162
+ <x-icon name="add"></x-icon>
163
Add Time
164
</button>
165
</div>
@@ -180,7 +180,7 @@
180
<button class="btn btn-action generate-token"
181
@click="$store.schedulerStore.editingTask.token = $store.schedulerStore.generateRandomToken()"
182
title="Generate Token">
183
- <span class="material-symbols-outlined">refresh</span>
183
+ <x-icon name="refresh"></x-icon>
184
Generate
185
</button>
186
</div>
@@ -348,7 +348,7 @@
348
<span x-text="$store.schedulerStore.formatDate(time)"></span>
349
<button @click.prevent="$store.schedulerStore.editingTask.plan.todo.splice(index, 1)"
350
class="scheduler-todo-remove">
351
- <span class="material-symbols-outlined">close</span>
351
+ <x-icon name="close"></x-icon>
352
</button>
353
</div>
354
</template>
@@ -364,7 +364,7 @@
364
placeholder="Select date and time">
365
<button @click.prevent="$store.schedulerStore.addPlannedTime('edit')"
366
class="btn btn-ok scheduler-add-todo-button">
367
- <span class="material-symbols-outlined">add</span>
367
+ <x-icon name="add"></x-icon>
368
Add Time
369
</button>
370
</div>
@@ -385,7 +385,7 @@
385
<button class="btn btn-action generate-token"
386
@click="$store.schedulerStore.editingTask.token = $store.schedulerStore.generateRandomToken()"
387
title="Generate Token">
388
- <span class="material-symbols-outlined">refresh</span>
388
+ <x-icon name="refresh"></x-icon>
389
Generate
390
</button>
391
</div>
webui/components/modals/scheduler/scheduler-task-list.html
+12
-12
@@ -34,7 +34,7 @@
34
</div>
35
36
<button class="btn btn-ok btn-new-task" @click="$store.schedulerStore.startCreateTask()">
37
- <span class="material-symbols-outlined">add</span>
37
+ <x-icon name="add"></x-icon>
38
New Task
39
</button>
40
</div>
@@ -43,7 +43,7 @@
43
<div class="data-status-bar" x-show="!$store.schedulerStore.isLoading">
44
<div class="status-info">
45
<span class="status-item">
46
- <span class="material-symbols-outlined">schedule</span>
46
+ <x-icon name="schedule"></x-icon>
47
Total: <strong x-text="$store.schedulerStore.tasks.length"></strong>
48
</span>
49
<span class="status-separator">•</span>
@@ -56,7 +56,7 @@
56
<div class="status-pagination" x-show="$store.schedulerStore.totalPages > 1">
57
<button class="btn btn-icon" @click="$store.schedulerStore.prevPage()"
58
:disabled="$store.schedulerStore.currentPage === 1" title="Previous Page">
59
- <span class="material-symbols-outlined">chevron_left</span>
59
+ <x-icon name="chevron_left"></x-icon>
60
</button>
61
<div class="page-input-group">
62
<label>Page</label>
@@ -71,7 +71,7 @@
71
<button class="btn btn-icon" @click="$store.schedulerStore.nextPage()"
72
:disabled="$store.schedulerStore.currentPage === $store.schedulerStore.totalPages"
73
title="Next Page">
74
- <span class="material-symbols-outlined">chevron_right</span>
74
+ <x-icon name="chevron_right"></x-icon>
75
</button>
76
</div>
77
</div>
@@ -81,7 +81,7 @@
81
x-show="!$store.schedulerStore.isLoading && $store.schedulerStore.filteredTasks.length === 0">
82
<div>No tasks found</div>
83
<button class="btn btn-ok" @click="$store.schedulerStore.startCreateTask()">
84
- <span class="material-symbols-outlined">add</span>
84
+ <x-icon name="add"></x-icon>
85
Create your first task
86
</button>
87
</div>
@@ -135,19 +135,19 @@
135
<div class="actions-wrapper">
136
<button class="btn btn-action"
137
@click="$store.schedulerStore.runTask(task.uuid)" title="Run Task">
138
- <span class="material-symbols-outlined">play_arrow</span>
138
+ <x-icon name="play_arrow"></x-icon>
139
</button>
140
<button class="btn btn-action"
141
@click="$store.schedulerStore.resetTaskState(task.uuid)" title="Reset State">
142
- <span class="material-symbols-outlined">refresh</span>
142
+ <x-icon name="refresh"></x-icon>
143
</button>
144
<button class="btn btn-action"
145
@click="$store.schedulerStore.startEditTask(task.uuid)" title="Edit Task">
146
- <span class="material-symbols-outlined">edit</span>
146
+ <x-icon name="edit"></x-icon>
147
</button>
148
<button class="btn btn-action delete"
149
@click="$confirmClick($event, () => $store.schedulerStore.deleteTask(task.uuid))" title="Delete Task">
150
- <span class="material-symbols-outlined">delete</span>
150
+ <x-icon name="delete"></x-icon>
151
</button>
152
</div>
153
</td>
@@ -162,7 +162,7 @@
162
x-show="!$store.schedulerStore.isLoading && $store.schedulerStore.paginatedTasks.length > 5 && $store.schedulerStore.totalPages > 1">
163
<div class="status-info">
164
<span class="status-item">
165
- <span class="material-symbols-outlined">schedule</span>
165
+ <x-icon name="schedule"></x-icon>
166
Total: <strong x-text="$store.schedulerStore.tasks.length"></strong>
167
</span>
168
<span class="status-separator">•</span>
@@ -174,7 +174,7 @@
174
<div class="status-pagination">
175
<button class="btn btn-icon" @click="$store.schedulerStore.prevPage()"
176
:disabled="$store.schedulerStore.currentPage === 1" title="Previous Page">
177
- <span class="material-symbols-outlined">chevron_left</span>
177
+ <x-icon name="chevron_left"></x-icon>
178
</button>
179
<div class="page-input-group">
180
<label>Page</label>
@@ -189,7 +189,7 @@
189
<button class="btn btn-icon" @click="$store.schedulerStore.nextPage()"
190
:disabled="$store.schedulerStore.currentPage === $store.schedulerStore.totalPages"
191
title="Next Page">
192
- <span class="material-symbols-outlined">chevron_right</span>
192
+ <x-icon name="chevron_right"></x-icon>
193
</button>
194
</div>
195
</div>
webui/components/notifications/notification-icons.html
+1
-1
@@ -16,7 +16,7 @@
16
@click="$store.notificationStore.openModal()"
17
title="View Notifications">
18
<div class="notification-icon">
19
- <span class="material-symbols-outlined">notifications</span>
19
+ <x-icon name="notifications"></x-icon>
20
</div>
21
<span x-show="$store.notificationStore.unreadPrioCount > 0"
22
class="notification-badge"
webui/components/notifications/notification-modal.html
+2
-2
@@ -17,7 +17,7 @@
17
<button class="notification-action" @click="$store.notificationStore?.clearAll()"
18
:disabled="!$store.notificationStore || $store.notificationStore.getDisplayNotifications().length === 0"
19
title="Clear All">
20
- <span class="material-symbols-outlined">delete</span> Clear All
20
+ <x-icon name="delete"></x-icon> Clear All
21
</button>
22
</div>
23
</div>
@@ -71,7 +71,7 @@
71
<div class="notification-empty"
72
x-show="!$store.notificationStore || $store.notificationStore.getDisplayNotifications().length === 0">
73
<div class="notification-empty-icon">
74
- <span class="material-symbols-outlined">notifications</span>
74
+ <x-icon name="notifications"></x-icon>
75
</div>
76
<p>No notifications to display</p>
77
</div>
webui/components/notifications/notification-store.js
+1
-1
@@ -451,7 +451,7 @@ const model = {
451
progress: "hourglass_empty",
452
};
453
const iconName = icons[type] || "info";
454
- return `<span class="material-symbols-outlined">${iconName}</span>`;
454
+ return `<x-icon name="${iconName}"></x-icon>`;
455
},
456
457
// Create notification via backend (will appear via polling)
webui/components/notifications/notification-toast-stack.html
+1
-1
@@ -44,7 +44,7 @@
44
<button class="toast-dismiss"
45
@click.stop="$store.notificationStore.dismissToast(toast.toastId)"
46
title="Dismiss">
47
- <span class="material-symbols-outlined">close</span>
47
+ <x-icon name="close"></x-icon>
48
</button>
49
</div>
50
</template>
webui/components/plugins/list/plugin-execute-modal.html
+5
-6
@@ -14,7 +14,7 @@
14
15
<div class="plugin-execute-idle"
16
x-show="!$store.pluginExecuteStore.running && $store.pluginExecuteStore.exitCode === null && !$store.pluginExecuteStore.output">
17
- <span class="material-symbols-outlined idle-icon">terminal</span>
17
+ <x-icon class="idle-icon" name="terminal"></x-icon>
18
<div class="idle-text">
19
<span class="idle-headline">Press <strong>Run</strong> to execute <strong x-text="$store.pluginExecuteStore.pluginDisplayName"></strong></span>
20
<span class="idle-sub">This will run the plugin's <code>execute.py</code> script.</span>
@@ -35,15 +35,14 @@
35
</div>
36
37
<div class="plugin-execute-status" x-show="$store.pluginExecuteStore.running">
38
- <span class="material-symbols-outlined spin">progress_activity</span>
38
+ <x-icon class="spin" name="progress_activity"></x-icon>
39
<span>Running execute.py...</span>
40
</div>
41
42
<div class="plugin-execute-exit" x-show="!$store.pluginExecuteStore.running && $store.pluginExecuteStore.exitCode !== null">
43
- <span class="material-symbols-outlined"
43
+ <x-icon
44
:class="$store.pluginExecuteStore.exitCode === 0 ? 'exit-ok' : 'exit-err'"
45
- x-text="$store.pluginExecuteStore.exitCode === 0 ? 'check_circle' : 'error'">
46
- </span>
45
+ :name="$store.pluginExecuteStore.exitCode === 0 ? 'check_circle' : 'error'"></x-icon>
46
<span x-text="$store.pluginExecuteStore.exitCode === 0 ? 'Completed successfully (exit 0)' : 'Exited with code ' + $store.pluginExecuteStore.exitCode"></span>
47
</div>
48
@@ -59,7 +58,7 @@
58
x-data
59
:disabled="$store.pluginExecuteStore?.running"
60
@click="$store.pluginExecuteStore?.run()">
62
- <span class="icon material-symbols-outlined">play_arrow</span>
61
+ <x-icon class="icon" name="play_arrow"></x-icon>
62
<span x-text="$store.pluginExecuteStore?.exitCode !== null || $store.pluginExecuteStore?.output ? 'Rerun' : 'Run'">Run</span>
63
</button>
64
<button class="btn btn-cancel footer-btn" @click="window.closeModal?.()">Close</button>
webui/components/plugins/list/plugin-list.html
+10
-10
@@ -45,7 +45,7 @@
45
aria-controls="plugins-plugin-hub"
46
:aria-selected="$store.pluginListStore.activeTab === 'pluginHub'"
47
@click="$store.pluginListStore.setTab('pluginHub')">
48
- <span class="material-symbols-outlined pi-tab-icon">store</span> Browse
48
+ <x-icon class="pi-tab-icon" name="store"></x-icon> Browse
49
</button>
50
</li>
51
</ul>
@@ -65,7 +65,7 @@
65
title="Refresh"
66
@click="$store.pluginListStore.refresh()"
67
:disabled="$store.pluginListStore.loading">
68
- <span class="icon material-symbols-outlined">refresh</span> Refresh
68
+ <x-icon class="icon" name="refresh"></x-icon> Refresh
69
</button>
70
71
<x-extension id="plugins-list-header-buttons"></x-extension>
@@ -102,7 +102,7 @@
102
class="button"
103
title="Open"
104
@click="$store.pluginListStore.openPlugin(plugin)">
105
- <span class="icon material-symbols-outlined">dashboard</span> Open
105
+ <x-icon class="icon" name="dashboard"></x-icon> Open
106
</button>
107
</template>
108
<template x-if="plugin.has_config_screen">
@@ -110,7 +110,7 @@
110
class="button"
111
title="Config"
112
@click="$store.pluginListStore.openPluginConfig(plugin)">
113
- <span class="icon material-symbols-outlined">settings</span> Config
113
+ <x-icon class="icon" name="settings"></x-icon> Config
114
</button>
115
</template>
116
<template x-if="plugin.has_execute_script">
@@ -118,7 +118,7 @@
118
class="button"
119
title="Execute"
120
@click="$store.pluginListStore.openPluginExecute(plugin)">
121
- <span class="icon material-symbols-outlined">terminal</span> Execute
121
+ <x-icon class="icon" name="terminal"></x-icon> Execute
122
</button>
123
</template>
124
<button type="button"
@@ -126,7 +126,7 @@
126
title="Info"
127
aria-label="Info"
128
@click="$store.pluginListStore.openPluginInfo(plugin)">
129
- <span class="icon material-symbols-outlined">info</span>
129
+ <x-icon class="icon" name="info"></x-icon>
130
</button>
131
<div class="dropdown plugin-actions-dropdown"
132
@click.outside="actionsOpen = false"
@@ -137,7 +137,7 @@
137
aria-label="More actions"
138
@click.stop="actionsOpen = !actionsOpen"
139
:aria-expanded="actionsOpen.toString()">
140
- <span class="material-symbols-outlined">more_vert</span>
140
+ <x-icon name="more_vert"></x-icon>
141
</button>
142
<div class="dropdown-menu"
143
x-show="actionsOpen"
@@ -149,7 +149,7 @@
149
class="dropdown-item"
150
title="README"
151
@click="$store.pluginListStore.openPluginDoc(plugin, 'readme'); actionsOpen = false">
152
- <span class="material-symbols-outlined">description</span>
152
+ <x-icon name="description"></x-icon>
153
<span>README</span>
154
</button>
155
</template>
@@ -158,7 +158,7 @@
158
class="dropdown-item"
159
title="License"
160
@click="$store.pluginListStore.openPluginDoc(plugin, 'license'); actionsOpen = false">
161
- <span class="material-symbols-outlined">gavel</span>
161
+ <x-icon name="gavel"></x-icon>
162
<span>License</span>
163
</button>
164
</template>
@@ -168,7 +168,7 @@
168
class="dropdown-item plugin-dropdown-delete"
169
title="Delete"
170
@click="$confirmClick($event, () => { $store.pluginListStore.deletePlugin(plugin); actionsOpen = false; })">
171
- <span class="material-symbols-outlined">delete</span>
171
+ <x-icon name="delete"></x-icon>
172
<span>Delete</span>
173
</button>
174
</template>
webui/components/plugins/plugin-configs.html
+4
-4
@@ -17,12 +17,12 @@
17
<div x-create="context.loadConfigList()" class="plugin-configs-container">
18
19
<div x-show="context.configsError" class="plugin-configs-error">
20
- <span class="material-symbols-outlined">error</span>
20
+ <x-icon name="error"></x-icon>
21
<span x-text="context.configsError"></span>
22
</div>
23
24
<div x-show="context.isListingConfigs" class="plugin-configs-loading">
25
- <span class="material-symbols-outlined spinning">progress_activity</span>
25
+ <x-icon class="spinning" name="progress_activity"></x-icon>
26
<span>Loading configurations...</span>
27
</div>
28
@@ -49,11 +49,11 @@
49
50
<div class="plugin-configs-actions">
51
<button type="button" class="button" @click="context.switchToConfig(cfg.project_name || '', cfg.agent_profile || '')">
52
- <span class="icon material-symbols-outlined">list</span>
52
+ <x-icon class="icon" name="list"></x-icon>
53
Show
54
</button>
55
<button type="button" class="button cancel icon-button" title="Delete" @click="$confirmClick($event, () => context.deleteConfig(cfg.project_name || '', cfg.agent_profile || ''))">
56
- <span class="icon material-symbols-outlined">delete</span>
56
+ <x-icon class="icon" name="delete"></x-icon>
57
</button>
58
</div>
59
</div>
webui/components/plugins/plugin-info.html
+3
-3
@@ -83,7 +83,7 @@
83
aria-expanded="false"
84
aria-controls="plugin-info-readme-collapse">
85
<span>README</span>
86
- <span class="material-symbols-outlined plugin-info-readme-toggle-icon">expand_more</span>
86
+ <x-icon class="plugin-info-readme-toggle-icon" name="expand_more"></x-icon>
87
</button>
88
<div class="collapse" id="plugin-info-readme-collapse">
89
<div class="plugin-info-readme-body">
@@ -110,14 +110,14 @@
110
class="button"
111
title="Open plugin files"
112
@click="$store.pluginListStore.openPluginFolder($store.pluginListStore.selectedPlugin)">
113
- <span class="icon material-symbols-outlined">folder_open</span> Files
113
+ <x-icon class="icon" name="folder_open"></x-icon> Files
114
</button>
115
<template x-if="$store.pluginListStore.selectedPlugin.pluginHub?.key">
116
<button type="button"
117
class="button"
118
title="Open in Plugin Hub"
119
@click="$store.pluginListStore.openPluginHub($store.pluginListStore.selectedPlugin)">
120
- <span class="icon material-symbols-outlined">storefront</span> Hub
120
+ <x-icon class="icon" name="storefront"></x-icon> Hub
121
</button>
122
</template>
123
</div>
webui/components/plugins/plugin-settings.html
+4
-4
@@ -88,27 +88,27 @@
88
</label>
89
90
<button type="button" class="button plugin-settings-toolbar-button" title="Show existing configurations" @click="context.openConfigListModal()">
91
- <span class="icon material-symbols-outlined">list</span>
91
+ <x-icon class="icon" name="list"></x-icon>
92
</button>
93
94
</div>
95
</div>
96
97
<div x-show="context.scopeMismatchMessage" class="plugin-settings-scope-info">
98
- <span class="material-symbols-outlined">info</span>
98
+ <x-icon name="info"></x-icon>
99
<span x-text="context.scopeMismatchMessage"></span>
100
</div>
101
</div>
102
103
<!-- Error -->
104
<div x-show="context.error" class="plugin-settings-error">
105
- <span class="material-symbols-outlined">error</span>
105
+ <x-icon name="error"></x-icon>
106
<span x-text="context.error"></span>
107
</div>
108
109
<!-- Loading -->
110
<div x-show="context.isLoading" class="plugin-settings-loading">
111
- <span class="material-symbols-outlined spinning">progress_activity</span>
111
+ <x-icon class="spinning" name="progress_activity"></x-icon>
112
<span>Loading settings...</span>
113
</div>
114
webui/components/plugins/toggle/plugin-toggles.html
+4
-4
@@ -11,12 +11,12 @@
11
<div x-create="$store.pluginToggle.loadConfigs()" class="plugin-configs-container">
12
13
<div x-show="$store.pluginToggle.error" class="plugin-configs-error">
14
- <span class="material-symbols-outlined">error</span>
14
+ <x-icon name="error"></x-icon>
15
<span x-text="$store.pluginToggle.error"></span>
16
</div>
17
18
<div x-show="$store.pluginToggle.isLoading" class="plugin-configs-loading">
19
- <span class="material-symbols-outlined spinning">progress_activity</span>
19
+ <x-icon class="spinning" name="progress_activity"></x-icon>
20
<span>Loading configurations...</span>
21
</div>
22
@@ -47,11 +47,11 @@
47
48
<div class="plugin-configs-actions">
49
<button type="button" class="button" @click="$store.pluginToggle.switchToConfig(cfg.project_name || '', cfg.agent_profile || '')">
50
- <span class="icon material-symbols-outlined">edit</span>
50
+ <x-icon class="icon" name="edit"></x-icon>
51
Edit
52
</button>
53
<button type="button" class="button cancel icon-button" title="Delete" @click="$confirmClick($event, () => $store.pluginToggle.deleteConfig(cfg.path))">
54
- <span class="icon material-symbols-outlined">delete</span>
54
+ <x-icon class="icon" name="delete"></x-icon>
55
</button>
56
</div>
57
</div>
webui/components/projects/project-edit-basic-data.html
+7
-7
@@ -21,7 +21,7 @@
21
<div class="projects-input-with-button-wrapper">
22
<input class="projects-form-input projects-disabled" type="text" x-model="$store.projects.selectedProject.name" disabled>
23
<button class="button icon-button" @click="$store.projects.browseSelected()">
24
- <span class="icon material-symbols-outlined">folder</span>
24
+ <x-icon class="icon" name="folder"></x-icon>
25
<span>Browse</span>
26
</button>
27
</div>
@@ -63,7 +63,7 @@
63
<template x-if="!$store.projects.selectedProject._meta.creating && $store.projects.selectedProject.git_status?.is_git_repo">
64
<div class="projects-form-group git-status-section">
65
<label class="projects-form-label">
66
- <span class="material-symbols-outlined git-icon">commit</span>
66
+ <x-icon class="git-icon" name="commit"></x-icon>
67
Git Status
68
</label>
69
<div class="git-status-card">
@@ -71,13 +71,13 @@
71
<template x-if="$store.projects.selectedProject.git_status.remote_url">
72
<div class="git-status-row">
73
<span class="git-status-label">
74
- <span class="material-symbols-outlined">link</span>
74
+ <x-icon name="link"></x-icon>
75
Repository
76
</span>
77
<div class="git-status-value-with-action">
78
<span class="git-status-value git-url" x-text="$store.projects.selectedProject.git_status.remote_url"></span>
79
<a class="git-action-link" :href="$store.projects.selectedProject.git_status.remote_url" target="_blank" rel="noopener noreferrer" title="Open in browser">
80
- <span class="material-symbols-outlined">open_in_new</span>
80
+ <x-icon name="open_in_new"></x-icon>
81
</a>
82
</div>
83
</div>
@@ -86,7 +86,7 @@
86
<!-- Branch -->
87
<div class="git-status-row">
88
<span class="git-status-label">
89
- <span class="material-symbols-outlined">fork_right</span>
89
+ <x-icon name="fork_right"></x-icon>
90
Branch
91
</span>
92
<span class="git-status-value git-branch" x-text="$store.projects.selectedProject.git_status.current_branch"></span>
@@ -95,7 +95,7 @@
95
<!-- Status -->
96
<div class="git-status-row">
97
<span class="git-status-label">
98
- <span class="material-symbols-outlined">info</span>
98
+ <x-icon name="info"></x-icon>
99
Status
100
</span>
101
<span class="git-status-value"
@@ -118,7 +118,7 @@
118
<template x-if="$store.projects.selectedProject.git_status.last_commit">
119
<div class="git-status-row">
120
<span class="git-status-label">
121
- <span class="material-symbols-outlined">history</span>
121
+ <x-icon name="history"></x-icon>
122
Last Commit
123
</span>
124
<div class="git-commit-info">
webui/components/projects/project-edit-file-structure.html
+1
-1
@@ -85,7 +85,7 @@
85
</div>
86
87
<button class="button icon-button" x-on:click="$store.projects.testFileStructure()">
88
- <span class="icon material-symbols-outlined">account_tree</span>
88
+ <x-icon class="icon" name="account_tree"></x-icon>
89
<span>Test output</span>
90
</button>
91
webui/components/projects/project-edit-instructions.html
+1
-1
@@ -55,7 +55,7 @@
55
<span class="projects-form-description">Currently there are <strong><span
56
x-text="$store.projects.selectedProject.instruction_files_count || 0"></span></strong> instruction files.</span>
57
<button class="button icon-button" x-on:click="$store.projects.browseInstructionFiles()">
58
- <span class="icon material-symbols-outlined">folder</span>
58
+ <x-icon class="icon" name="folder"></x-icon>
59
<span>Browse</span>
60
</button>
61
</div>
webui/components/projects/project-edit-llm.html
+1
-1
@@ -23,7 +23,7 @@
23
</select>
24
<button type="button" class="button icon-button project-llm-edit-presets"
25
@click="$store.projects.editSelectedProjectPresets()">
26
- <span class="icon material-symbols-outlined">tune</span>
26
+ <x-icon class="icon" name="tune"></x-icon>
27
<span>Edit Presets</span>
28
</button>
29
</div>
webui/components/projects/project-edit-mcp.html
+1
-1
@@ -21,7 +21,7 @@
21
</p>
22
</div>
23
<button type="button" class="button" @click="$store.mcpServersStore.openProjectConfig($store.projects.selectedProject)">
24
- <span class="icon material-symbols-outlined">hub</span>
24
+ <x-icon class="icon" name="hub"></x-icon>
25
MCP Servers
26
</button>
27
</div>
webui/components/projects/project-edit-skills.html
+2
-2
@@ -16,11 +16,11 @@
16
17
<div class="skills-actions">
18
<button type="button" class="button" @click="$store.projects.openSelectedProjectSkillsImport()">
19
- <span class="icon material-symbols-outlined">upload</span>
19
+ <x-icon class="icon" name="upload"></x-icon>
20
Import Skills
21
</button>
22
<button type="button" class="button" @click="$store.projects.openSelectedProjectSkillsFolder()">
23
- <span class="icon material-symbols-outlined">folder_open</span>
23
+ <x-icon class="icon" name="folder_open"></x-icon>
24
Open Folder
25
</button>
26
</div>
webui/components/projects/project-edit.html
+2
-2
@@ -35,8 +35,8 @@
35
36
<div class="settings-advanced-section project-advanced-section" x-data="{ advOpen: false }">
37
<button type="button" class="settings-advanced-toggle" @click="advOpen = !advOpen">
38
- <span class="material-symbols-outlined settings-advanced-toggle-icon"
39
- :style="advOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
38
+ <x-icon class="settings-advanced-toggle-icon"
39
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
40
<span>Advanced Settings</span>
41
</button>
42
webui/components/projects/project-list.html
+8
-8
@@ -23,15 +23,15 @@
23
<strong x-text="$store.chats.selectedContext?.project?.name"></strong>
24
</span>
25
<button type="button" class="button cancel icon-button" title="Deactivate" @click="$store.projects.deactivateProject()">
26
- <span class="icon material-symbols-outlined">close</span>
26
+ <x-icon class="icon" name="close"></x-icon>
27
</button>
28
<button type="button" class="button icon-button" title="Edit" @click="$store.projects.openEditModal($store.chats.selectedContext?.project?.name)">
29
- <span class="icon material-symbols-outlined">edit</span>
29
+ <x-icon class="icon" name="edit"></x-icon>
30
</button>
31
</div>
32
<button @click="$store.projects.openCreateModal()" type="button"
33
class="button projects-create-btn-top">
34
- <span class="icon material-symbols-outlined">add</span> Create project
34
+ <x-icon class="icon" name="add"></x-icon> Create project
35
</button>
36
</div>
37
@@ -48,21 +48,21 @@
48
<div class="projects-project-card-actions">
49
<template x-if="$store.chats.selectedContext && project.name === $store.chats.selectedContext?.project?.name">
50
<button type="button" class="button cancel projects-project-card-state-action" title="Deactivate" @click="$store.projects.deactivateProject()">
51
- <span class="icon material-symbols-outlined">close</span>
51
+ <x-icon class="icon" name="close"></x-icon>
52
<span class="projects-project-card-action-text">Deactivate</span>
53
</button>
54
</template>
55
<template x-if="$store.chats.selectedContext && project.name !== $store.chats.selectedContext?.project?.name">
56
<button type="button" class="button confirm projects-project-card-state-action" title="Activate" @click="$store.projects.activateProject(project.name)">
57
- <span class="icon material-symbols-outlined">play_arrow</span>
57
+ <x-icon class="icon" name="play_arrow"></x-icon>
58
<span class="projects-project-card-action-text">Activate</span>
59
</button>
60
</template>
61
<button type="button" class="button icon-button" title="Edit" @click="$store.projects.openEditModal(project.name)">
62
- <span class="icon material-symbols-outlined">edit</span>
62
+ <x-icon class="icon" name="edit"></x-icon>
63
</button>
64
<button type="button" class="button cancel icon-button" title="Delete" @click="$confirmClick($event, () => $store.projects.deleteProject(project.name))">
65
- <span class="icon material-symbols-outlined">delete</span>
65
+ <x-icon class="icon" name="delete"></x-icon>
66
</button>
67
</div>
68
</div>
@@ -75,7 +75,7 @@
75
<div class="projects-project-card-title">There are no projects yet</div>
76
<div class="projects-no-projects-actions">
77
<button @click="$store.projects.openCreateModal()" type="button"
78
- class="button"><span class="icon material-symbols-outlined">add</span>Create project</button>
78
+ class="button"><x-icon class="icon" name="add"></x-icon>Create project</button>
79
</div>
80
</div>
81
</template>
webui/components/projects/project-selector.html
+5
-5
@@ -15,29 +15,29 @@
15
<button @click="open = !open" type="button" class="button project-dropdown-button">
16
<span class="project-color-ball" :style="$store.chats.selectedContext.project.color ? { backgroundColor: $store.chats.selectedContext.project.color } : { border: '1px solid var(--color-border)' }"></span>
17
<span x-text="$store.chats.selectedContext.project.title"></span>
18
- <span class="icon material-symbols-outlined">arrow_drop_down</span>
18
+ <x-icon class="icon" name="arrow_drop_down"></x-icon>
19
</button>
20
</template>
21
<template x-if="!$store.chats.selectedContext.project?.name">
22
<button @click="open = !open" type="button" class="button project-dropdown-button">
23
No project
24
- <span class="icon material-symbols-outlined">arrow_drop_down</span>
24
+ <x-icon class="icon" name="arrow_drop_down"></x-icon>
25
</button>
26
</template>
27
28
<div x-show="open" x-init="$store.projects.loadProjectsList()" class="dropdown-menu" style="display: none;" x-transition>
29
<button type="button" @click="$store.projects.openProjectsModal(); open = false;" class="dropdown-item">
30
- <span class="icon material-symbols-outlined">snippet_folder</span>
30
+ <x-icon class="icon" name="snippet_folder"></x-icon>
31
<span>Projects</span>
32
</button>
33
34
<button type="button" x-show="$store.chats.selectedContext.project?.name" @click="$store.projects.editActiveProject(); open = false;" class="dropdown-item">
35
- <span class="icon material-symbols-outlined">edit</span>
35
+ <x-icon class="icon" name="edit"></x-icon>
36
<span>Edit <span x-text="$store.chats.selectedContext.project?.title"></span></span>
37
</button>
38
39
<button type="button" x-show="$store.chats.selectedContext.project?.name" @click="$store.projects.deactivateProject(); open = false;" class="dropdown-item">
40
- <span class="icon material-symbols-outlined">close</span>
40
+ <x-icon class="icon" name="close"></x-icon>
41
<span>Deactivate</span>
42
</button>
43
webui/components/settings/agent/agent-settings.html
+3
-3
@@ -35,19 +35,19 @@
35
</li>
36
<li>
37
<a href="#section-locale">
38
- <span class="material-symbols-outlined" aria-hidden="true">language</span>
38
+ <x-icon aria-hidden="true" name="language"></x-icon>
39
<span>Locale</span>
40
</a>
41
</li>
42
<li>
43
<a href="#section-interface">
44
- <span class="material-symbols-outlined" aria-hidden="true">dashboard_customize</span>
44
+ <x-icon aria-hidden="true" name="dashboard_customize"></x-icon>
45
<span>Interface</span>
46
</a>
47
</li>
48
<li>
49
<a href="#section-agent-plugins">
50
- <span class="material-symbols-outlined" aria-hidden="true">extension</span>
50
+ <x-icon aria-hidden="true" name="extension"></x-icon>
51
<span>Plugins</span>
52
</a>
53
</li>
webui/components/settings/agent/agent.html
+2
-2
@@ -30,8 +30,8 @@
30
31
<div class="settings-advanced-section" x-data="{ advOpen: false }">
32
<button type="button" class="settings-advanced-toggle" @click="advOpen = !advOpen">
33
- <span class="material-symbols-outlined settings-advanced-toggle-icon"
34
- :style="advOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
33
+ <x-icon class="settings-advanced-toggle-icon"
34
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
35
<span>Advanced Settings</span>
36
</button>
37
webui/components/settings/agent/interface.html
+3
-3
@@ -16,7 +16,7 @@
16
<template x-for="control in $store.settings.uiControls" :key="control.id">
17
<div class="ui-visibility-row">
18
<div class="ui-visibility-control">
19
- <span class="material-symbols-outlined" aria-hidden="true" x-text="control.icon"></span>
19
+ <x-icon aria-hidden="true" :name="control.icon"></x-icon>
20
<div>
21
<div class="field-title" x-text="control.label"></div>
22
<div class="ui-visibility-state" x-text="$store.settings.uiControlVisibilityLabel(control.id)"></div>
@@ -33,7 +33,7 @@
33
:title="$store.settings.isUiControlVisible(control.id, 'mobile') ? 'Shown on mobile' : 'Hidden on mobile'"
34
@click="$store.settings.toggleUiControl(control.id, 'mobile')"
35
>
36
- <span class="material-symbols-outlined" aria-hidden="true">smartphone</span>
36
+ <x-icon aria-hidden="true" name="smartphone"></x-icon>
37
<span>Mobile</span>
38
</button>
39
<button
@@ -45,7 +45,7 @@
45
:title="$store.settings.isUiControlVisible(control.id, 'desktop') ? 'Shown on desktop' : 'Hidden on desktop'"
46
@click="$store.settings.toggleUiControl(control.id, 'desktop')"
47
>
48
- <span class="material-symbols-outlined" aria-hidden="true">desktop_windows</span>
48
+ <x-icon aria-hidden="true" name="desktop_windows"></x-icon>
49
<span>Desktop</span>
50
</button>
51
</div>
webui/components/settings/agent/workdir.html
+2
-2
@@ -61,8 +61,8 @@
61
<template x-if="$store.settings.settings.workdir_show">
62
<div class="settings-advanced-section" x-data="{ advOpen: false }">
63
<button type="button" class="settings-advanced-toggle" @click="advOpen = !advOpen">
64
- <span class="material-symbols-outlined settings-advanced-toggle-icon"
65
- :style="advOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
64
+ <x-icon class="settings-advanced-toggle-icon"
65
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
66
<span>Advanced Settings</span>
67
</button>
68
webui/components/settings/backup/self-update.html
+2
-2
@@ -21,8 +21,8 @@
21
<div class="settings-update-card"
22
:class="{'settings-update-card-attention': $store.settings.hasUpdateAttention}">
23
<div class="settings-update-card-icon" aria-hidden="true">
24
- <span class="material-symbols-outlined"
25
- x-text="$store.settings.hasUpdateAttention ? 'release_alert' : 'system_update_alt'"></span>
24
+ <x-icon
25
+ :name="$store.settings.hasUpdateAttention ? 'release_alert' : 'system_update_alt'"></x-icon>
26
</div>
27
<div class="settings-update-card-copy">
28
<div class="settings-update-card-title" x-text="$store.settings.updateAttentionTitle"></div>
webui/components/settings/developer/dev.html
+2
-2
@@ -56,8 +56,8 @@
56
57
<div class="settings-advanced-section" x-data="{ advOpen: false }">
58
<button type="button" class="settings-advanced-toggle" @click="advOpen = !advOpen">
59
- <span class="material-symbols-outlined settings-advanced-toggle-icon"
60
- :style="advOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
59
+ <x-icon class="settings-advanced-toggle-icon"
60
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
61
<span>Advanced Settings</span>
62
</button>
63
webui/components/settings/external/auth.html
+2
-2
@@ -39,8 +39,8 @@
39
40
<div class="settings-advanced-section" x-data="{ advOpen: false }">
41
<button type="button" class="settings-advanced-toggle" @click="advOpen = !advOpen">
42
- <span class="material-symbols-outlined settings-advanced-toggle-icon"
43
- :style="advOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
42
+ <x-icon class="settings-advanced-toggle-icon"
43
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
44
<span>Advanced Settings</span>
45
</button>
46
webui/components/settings/external/external-settings.html
+1
-1
@@ -47,7 +47,7 @@
47
</li>
48
<li>
49
<a href="#section-external-plugins">
50
- <span class="material-symbols-outlined" aria-hidden="true">extension</span>
50
+ <x-icon aria-hidden="true" name="extension"></x-icon>
51
<span>Plugins</span>
52
</a>
53
</li>
webui/components/settings/external/litellm.html
+2
-2
@@ -14,8 +14,8 @@
14
15
<div class="settings-advanced-section" x-data="{ advOpen: false }">
16
<button type="button" class="settings-advanced-toggle" @click="advOpen = !advOpen">
17
- <span class="material-symbols-outlined settings-advanced-toggle-icon"
18
- :style="advOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
17
+ <x-icon class="settings-advanced-toggle-icon"
18
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
19
<span>Advanced Settings</span>
20
</button>
21
webui/components/settings/external/secrets.html
+2
-2
@@ -14,8 +14,8 @@
14
15
<div class="settings-advanced-section" x-data="{ advOpen: false }">
16
<button type="button" class="settings-advanced-toggle" @click="advOpen = !advOpen">
17
- <span class="material-symbols-outlined settings-advanced-toggle-icon"
18
- :style="advOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
17
+ <x-icon class="settings-advanced-toggle-icon"
18
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
19
<span>Advanced Settings</span>
20
</button>
21
webui/components/settings/external/self-update-modal.html
+5
-6
@@ -92,11 +92,10 @@
92
</div>
93
94
<div class="self-update-quick-arrow" :class="$store.selfUpdateStore.quickComparisonIconClass" aria-hidden="true">
95
- <span
96
- class="material-symbols-outlined"
95
+ <x-icon
96
:class="{ spinning: $store.selfUpdateStore.isCheckingStatus }"
98
- x-text="$store.selfUpdateStore.quickComparisonIcon"
99
- ></span>
97
+ :name="$store.selfUpdateStore.quickComparisonIcon"
98
+ ></x-icon>
99
</div>
100
101
<div class="self-update-quick-version-card self-update-quick-version-card-accent">
@@ -212,7 +211,7 @@
211
aria-controls="self-update-howto-collapse"
212
>
213
<span>How it works?</span>
215
- <span class="material-symbols-outlined self-update-panel-toggle-icon">expand_more</span>
214
+ <x-icon class="self-update-panel-toggle-icon" name="expand_more"></x-icon>
215
</button>
216
<div class="collapse" id="self-update-howto-collapse">
217
<div class="self-update-panel-body self-update-copy">
@@ -261,7 +260,7 @@
260
:class="$store.selfUpdateStore.getLastStatusBadgeClass($store.selfUpdateStore.info?.last_status?.status)"
261
x-text="$store.selfUpdateStore.getLastStatusLabel($store.selfUpdateStore.info?.last_status?.status)"
262
></span>
264
- <span class="material-symbols-outlined self-update-panel-toggle-icon">expand_more</span>
263
+ <x-icon class="self-update-panel-toggle-icon" name="expand_more"></x-icon>
264
</span>
265
</button>
266
<div class="collapse" id="self-update-last-attempt-collapse">
webui/components/settings/mcp/client/mcp-server-scan.html
+2
-2
@@ -69,8 +69,8 @@
69
70
<div class="mcp-scan-result" x-show="$store.mcpServersStore.scanResult">
71
<div class="mcp-scan-heading">
72
- <span class="material-symbols-outlined" aria-hidden="true"
73
- x-text="$store.mcpServersStore.scanResult?.risk_level === 'ok' ? 'verified' : ($store.mcpServersStore.scanResult?.risk_level === 'error' ? 'error' : 'warning')"></span>
72
+ <x-icon aria-hidden="true"
73
+ :name="$store.mcpServersStore.scanResult?.risk_level === 'ok' ? 'verified' : ($store.mcpServersStore.scanResult?.risk_level === 'error' ? 'error' : 'warning')"></x-icon>
74
<span x-text="$store.mcpServersStore.scanRiskLabel"></span>
75
</div>
76
<div class="mcp-scan-warnings">
webui/components/settings/mcp/client/mcp-server-tools.html
+3
-3
@@ -20,20 +20,20 @@
20
<div class="mcp-tools-actions">
21
<span class="mcp-tools-count" x-text="$store.mcpServersStore.serverDetailToolsCountLabel"></span>
22
<button type="button" class="button confirm" :disabled="$store.mcpServersStore.applying" @click="$store.mcpServersStore.applyNow()">
23
- <span class="material-symbols-outlined" aria-hidden="true">check</span>
23
+ <x-icon aria-hidden="true" name="check"></x-icon>
24
<span x-text="$store.mcpServersStore.applying ? 'Applying' : 'Apply'"></span>
25
</button>
26
</div>
27
</div>
28
29
<label class="mcp-tool-search">
30
- <span class="material-symbols-outlined" aria-hidden="true">search</span>
30
+ <x-icon aria-hidden="true" name="search"></x-icon>
31
<input type="search" x-model.debounce.150ms="$store.mcpServersStore.toolSearch" placeholder="Search tools" />
32
<button type="button" class="mcp-tool-search-clear"
33
x-show="$store.mcpServersStore.toolSearch"
34
@click="$store.mcpServersStore.clearToolSearch()"
35
title="Clear search">
36
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
36
+ <x-icon aria-hidden="true" name="close"></x-icon>
37
</button>
38
</label>
39
webui/components/settings/mcp/client/mcp-servers.html
+15
-15
@@ -24,24 +24,24 @@
24
<div class="mcp-view-tabs">
25
<button type="button" :class="{ active: $store.mcpServersStore.activeView === 'visual' }"
26
@click="$store.mcpServersStore.setActiveView('visual')">
27
- <span class="material-symbols-outlined" aria-hidden="true">dashboard_customize</span>
27
+ <x-icon aria-hidden="true" name="dashboard_customize"></x-icon>
28
<span>Manager</span>
29
</button>
30
<button type="button" :class="{ active: $store.mcpServersStore.activeView === 'raw' }"
31
@click="$store.mcpServersStore.setActiveView('raw')">
32
- <span class="material-symbols-outlined" aria-hidden="true">data_object</span>
32
+ <x-icon aria-hidden="true" name="data_object"></x-icon>
33
<span>Raw JSON</span>
34
</button>
35
</div>
36
37
<label class="mcp-search" x-show="$store.mcpServersStore.activeView === 'visual'" style="display: none;">
38
- <span class="material-symbols-outlined" aria-hidden="true">search</span>
38
+ <x-icon aria-hidden="true" name="search"></x-icon>
39
<input type="search" x-model.debounce.150ms="$store.mcpServersStore.serverSearch" placeholder="Search MCP servers" />
40
<button type="button" class="mcp-search-clear"
41
x-show="$store.mcpServersStore.serverSearchActive"
42
@click="$store.mcpServersStore.clearServerSearch()"
43
title="Clear search">
44
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
44
+ <x-icon aria-hidden="true" name="close"></x-icon>
45
</button>
46
</label>
47
</div>
@@ -49,10 +49,10 @@
49
50
<div class="mcp-manager-actions">
51
<button type="button" class="button" title="Refresh status" @click="$store.mcpServersStore.loadStatus()">
52
- <span class="material-symbols-outlined" aria-hidden="true">refresh</span>
52
+ <x-icon aria-hidden="true" name="refresh"></x-icon>
53
</button>
54
<button type="button" class="button confirm" :disabled="$store.mcpServersStore.applying" @click="$store.mcpServersStore.applyNow()">
55
- <span class="material-symbols-outlined" aria-hidden="true">check</span>
55
+ <x-icon aria-hidden="true" name="check"></x-icon>
56
<span x-text="$store.mcpServersStore.applying ? 'Applying' : 'Apply'"></span>
57
</button>
58
</div>
@@ -81,7 +81,7 @@
81
</div>
82
<div class="mcp-config-actions">
83
<button type="button" class="mcp-icon-button" title="Edit" @click="$store.mcpServersStore.editConfigServer(entry.name)">
84
- <span class="material-symbols-outlined" aria-hidden="true">edit</span>
84
+ <x-icon aria-hidden="true" name="edit"></x-icon>
85
</button>
86
<div class="mcp-toggle-group">
87
<label class="toggle plugin-status-toggle mcp-status-toggle"
@@ -97,7 +97,7 @@
97
<button type="button" class="mcp-icon-button" title="Remove"
98
:disabled="$store.mcpServersStore.applying"
99
@click="$confirmClick($event, () => $store.mcpServersStore.removeConfigServer(entry.name))">
100
- <span class="material-symbols-outlined" aria-hidden="true">delete</span>
100
+ <x-icon aria-hidden="true" name="delete"></x-icon>
101
</button>
102
</div>
103
</article>
@@ -154,7 +154,7 @@
154
<div class="mcp-add-header">
155
<h3>Add MCP server</h3>
156
<button type="button" class="mcp-icon-button" title="Clear form" @click="$store.mcpServersStore.resetForm()">
157
- <span class="material-symbols-outlined" aria-hidden="true">backspace</span>
157
+ <x-icon aria-hidden="true" name="backspace"></x-icon>
158
</button>
159
</div>
160
@@ -206,7 +206,7 @@
206
</div>
207
208
<button type="button" class="mcp-advanced-toggle" @click="$store.mcpServersStore.advancedOpen = !$store.mcpServersStore.advancedOpen">
209
- <span class="material-symbols-outlined" :style="$store.mcpServersStore.advancedOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
209
+ <x-icon :style="$store.mcpServersStore.advancedOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
210
<span>Advanced settings</span>
211
</button>
212
@@ -242,11 +242,11 @@
242
243
<div class="mcp-add-actions">
244
<button type="button" class="button" @click="$store.mcpServersStore.openScanModal()">
245
- <span class="material-symbols-outlined" aria-hidden="true">radar</span>
245
+ <x-icon aria-hidden="true" name="radar"></x-icon>
246
<span>Scan with Agent Zero</span>
247
</button>
248
<button type="button" class="button confirm" @click="$store.mcpServersStore.addServerFromForm()">
249
- <span class="material-symbols-outlined" aria-hidden="true">add_circle</span>
249
+ <x-icon aria-hidden="true" name="add_circle"></x-icon>
250
<span>Add to config</span>
251
</button>
252
</div>
@@ -259,14 +259,14 @@
259
<div class="mcp-raw-actions">
260
<button type="button" class="button" onclick="openModal('settings/mcp/client/example.html')">Examples</button>
261
<button type="button" class="button" @click="$store.mcpServersStore.formatJson()">
262
- <span class="material-symbols-outlined" aria-hidden="true">format_indent_increase</span>
262
+ <x-icon aria-hidden="true" name="format_indent_increase"></x-icon>
263
<span>Reformat</span>
264
</button>
265
<button type="button" class="button" title="Refresh status" @click="$store.mcpServersStore.loadStatus()">
266
- <span class="material-symbols-outlined" aria-hidden="true">refresh</span>
266
+ <x-icon aria-hidden="true" name="refresh"></x-icon>
267
</button>
268
<button type="button" class="button confirm" :disabled="$store.mcpServersStore.applying" @click="$store.mcpServersStore.applyNow()">
269
- <span class="material-symbols-outlined" aria-hidden="true">check</span>
269
+ <x-icon aria-hidden="true" name="check"></x-icon>
270
<span x-text="$store.mcpServersStore.applying ? 'Applying' : 'Apply'"></span>
271
</button>
272
</div>
webui/components/settings/mcp/mcp_client.html
+2
-2
@@ -29,8 +29,8 @@
29
30
<div class="settings-advanced-section" x-data="{ advOpen: false }">
31
<button type="button" class="settings-advanced-toggle" @click="advOpen = !advOpen">
32
- <span class="material-symbols-outlined settings-advanced-toggle-icon"
33
- :style="advOpen ? 'transform:rotate(90deg)' : ''">chevron_right</span>
32
+ <x-icon class="settings-advanced-toggle-icon"
33
+ :style="advOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
34
<span>Advanced Settings</span>
35
</button>
36
webui/components/settings/mcp/mcp_server.html
+2
-2
@@ -56,10 +56,10 @@
56
57
<div class="mcp-internal-tools">
58
<label class="mcp-internal-search">
59
- <span class="material-symbols-outlined" aria-hidden="true">search</span>
59
+ <x-icon aria-hidden="true" name="search"></x-icon>
60
<input type="search" x-model.debounce.150ms="internalToolSearch" placeholder="Search internal tools" />
61
<button type="button" x-show="internalToolSearch" @click="internalToolSearch = ''" title="Clear search">
62
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
62
+ <x-icon aria-hidden="true" name="close"></x-icon>
63
</button>
64
</label>
65
webui/components/settings/plugins/plugins-subsection.html
+1
-1
@@ -39,7 +39,7 @@
39
class="button"
40
title="Config"
41
@click="context.openPluginConfig(plugin)">
42
- <span class="icon material-symbols-outlined">settings</span> Config
42
+ <x-icon class="icon" name="settings"></x-icon> Config
43
</button>
44
</div>
45
</div>
webui/components/settings/settings.html
+8
-8
@@ -14,13 +14,13 @@
14
15
<!-- Loading state -->
16
<div x-show="$store.settings.isLoading && !$store.settings.settings" class="settings-loading">
17
- <span class="material-symbols-outlined spinning">progress_activity</span>
17
+ <x-icon class="spinning" name="progress_activity"></x-icon>
18
<span>Loading settings...</span>
19
</div>
20
21
<!-- Error state -->
22
<div x-show="$store.settings.error && !$store.settings.settings" class="settings-error">
23
- <span class="material-symbols-outlined">error</span>
23
+ <x-icon name="error"></x-icon>
24
<span x-text="$store.settings.error"></span>
25
<button class="btn btn-retry" @click="$store.settings.onOpen()">Retry</button>
26
</div>
@@ -30,7 +30,7 @@
30
<!-- Tab Navigation -->
31
<aside class="settings-tabs-container" aria-label="Settings categories">
32
<div class="settings-search" role="search">
33
- <span class="material-symbols-outlined" aria-hidden="true">search</span>
33
+ <x-icon aria-hidden="true" name="search"></x-icon>
34
<input
35
type="search"
36
x-model.debounce.100ms="$store.settings.searchQuery"
@@ -46,7 +46,7 @@
46
@click="$store.settings.clearSearch()"
47
aria-label="Clear search"
48
>
49
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
49
+ <x-icon aria-hidden="true" name="close"></x-icon>
50
</button>
51
</div>
52
@@ -67,14 +67,14 @@
67
'settings-tab-attention': $store.settings.navItemHasAttention(item)
68
}"
69
@click="$store.settings.toggleNavGroup(item.id)">
70
- <span class="material-symbols-outlined" aria-hidden="true" x-text="item.icon"></span>
70
+ <x-icon aria-hidden="true" :name="item.icon"></x-icon>
71
<span class="settings-tab-label" x-text="item.label"></span>
72
<span class="settings-tab-meta">
73
<span class="settings-attention-dot"
74
x-show="$store.settings.navItemHasAttention(item)"
75
aria-hidden="true"></span>
76
- <span class="material-symbols-outlined settings-tab-chevron"
77
- aria-hidden="true">expand_more</span>
76
+ <x-icon class="settings-tab-chevron"
77
+ aria-hidden="true" name="expand_more"></x-icon>
78
</span>
79
</button>
80
@@ -90,7 +90,7 @@
90
:aria-current="$store.settings.activeSection === section.id ? 'true' : null"
91
:href="`#${section.id}`"
92
@click="$store.settings.scrollToSection(section.id, $event)">
93
- <span class="material-symbols-outlined" aria-hidden="true" x-text="section.icon"></span>
93
+ <x-icon aria-hidden="true" :name="section.icon"></x-icon>
94
<span class="settings-tab-label" x-text="section.label"></span>
95
<span class="settings-attention-dot"
96
x-show="$store.settings.sectionItemHasAttention(section)"
webui/components/settings/skills/list.html
+5
-5
@@ -29,14 +29,14 @@
29
</label>
30
31
<label class="skills-search">
32
- <span class="material-symbols-outlined" aria-hidden="true">search</span>
32
+ <x-icon aria-hidden="true" name="search"></x-icon>
33
<input type="search" x-model.debounce.150ms="$store.skillsListStore.skillSearch"
34
placeholder="Search skills" />
35
<button type="button" class="skills-search-clear"
36
x-show="$store.skillsListStore.skillSearchActive"
37
@click="$store.skillsListStore.clearSkillSearch()"
38
title="Clear search">
39
- <span class="material-symbols-outlined" aria-hidden="true">close</span>
39
+ <x-icon aria-hidden="true" name="close"></x-icon>
40
</button>
41
</label>
42
@@ -44,7 +44,7 @@
44
<button type="button" class="button confirm" title="Refresh"
45
@click="$store.skillsListStore.loadSkills()"
46
:disabled="$store.skillsListStore.loading">
47
- <span class="icon material-symbols-outlined">refresh</span> Refresh
47
+ <x-icon class="icon" name="refresh"></x-icon> Refresh
48
</button>
49
</div>
50
</div>
@@ -79,11 +79,11 @@
79
<div class="skill-actions">
80
<button type="button" class="button" title="Open in browser"
81
@click="$store.skillsListStore.openSkill(skill)">
82
- <span class="icon material-symbols-outlined">folder_open</span> Open
82
+ <x-icon class="icon" name="folder_open"></x-icon> Open
83
</button>
84
<button type="button" class="button cancel icon-button" title="Delete"
85
@click="$confirmClick($event, () => $store.skillsListStore.deleteSkill(skill))">
86
- <span class="icon material-symbols-outlined">delete</span>
86
+ <x-icon class="icon" name="delete"></x-icon>
87
</button>
88
</div>
89
</div>
webui/components/settings/skills/scan.html
+2
-2
@@ -26,12 +26,12 @@
26
<div class="skills-scan-actions">
27
<button type="button" class="button" @click="$store.skillsScanStore.openForInstalledSkills()"
28
:disabled="$store.skillsScanStore.sectionLoading">
29
- <span class="material-symbols-outlined" aria-hidden="true">inventory_2</span>
29
+ <x-icon aria-hidden="true" name="inventory_2"></x-icon>
30
<span>Installed Skills</span>
31
</button>
32
<button type="button" class="button confirm" @click="$store.skillsScanStore.openForManualTarget()"
33
:disabled="$store.skillsScanStore.sectionLoading">
34
- <span class="material-symbols-outlined" aria-hidden="true">radar</span>
34
+ <x-icon aria-hidden="true" name="radar"></x-icon>
35
<span>Open Scanner</span>
36
</button>
37
</div>
webui/components/settings/skills/skill-scan.html
+2
-2
@@ -46,7 +46,7 @@
46
47
<div class="scan-actions">
48
<button type="button" class="button" @click="$store.skillsScanStore.copyScanPrompt()">
49
- <span class="material-symbols-outlined" aria-hidden="true">content_copy</span>
49
+ <x-icon aria-hidden="true" name="content_copy"></x-icon>
50
<span>Copy Prompt</span>
51
</button>
52
<button type="button" class="button confirm" :disabled="$store.skillsScanStore.agentScanning" @click="$store.skillsScanStore.runAgentScan()">
@@ -54,7 +54,7 @@
54
<span x-show="!$store.skillsScanStore.agentScanning">Run Scan</span>
55
</button>
56
<button type="button" class="button" x-show="$store.skillsScanStore.scanCtxId" @click="$store.skillsScanStore.openScanChatInNewWindow()">
57
- <span class="material-symbols-outlined" aria-hidden="true">open_in_new</span>
57
+ <x-icon aria-hidden="true" name="open_in_new"></x-icon>
58
<span>Open in Chat</span>
59
</button>
60
</div>
webui/components/settings/skills/skills-settings.html
+1
-1
@@ -23,7 +23,7 @@
23
</li>
24
<li>
25
<a href="#section-skills-scan">
26
- <span class="material-symbols-outlined" aria-hidden="true">radar</span>
26
+ <x-icon aria-hidden="true" name="radar"></x-icon>
27
<span>Scan Skills</span>
28
</a>
29
</li>
webui/components/settings/tunnel/tunnel-section.html
+12
-12
@@ -17,7 +17,7 @@
17
>
18
<header class="remote-link-hero">
19
<div class="remote-link-hero-icon" aria-hidden="true">
20
- <span class="material-symbols-outlined">share</span>
20
+ <x-icon name="share"></x-icon>
21
</div>
22
<div>
23
<div class="section-title">Remote Control</div>
@@ -28,7 +28,7 @@
28
</header>
29
30
<div class="remote-link-safety-note">
31
- <span class="material-symbols-outlined" aria-hidden="true">lock</span>
31
+ <x-icon aria-hidden="true" name="lock"></x-icon>
32
<span>Use sign-in before sharing remote control access. The URL can reach your instance while access is active.</span>
33
</div>
34
@@ -54,20 +54,20 @@
54
</div>
55
56
<div class="remote-link-loading" x-show="$store.tunnelStore.isLoading" style="display: none;">
57
- <span class="material-symbols-outlined spin" aria-hidden="true">progress_activity</span>
57
+ <x-icon class="spin" aria-hidden="true" name="progress_activity"></x-icon>
58
<span x-text="$store.tunnelStore.loadingText || 'Preparing remote control...'"></span>
59
</div>
60
61
<div class="microsoft-login-box" x-show="$store.tunnelStore.loginActionVisible" x-transition style="display: none;">
62
<div class="microsoft-login-header">
63
- <span class="material-symbols-outlined" aria-hidden="true">key</span>
63
+ <x-icon aria-hidden="true" name="key"></x-icon>
64
<div>
65
<div class="microsoft-login-title" x-text="$store.tunnelStore.loginActionTitle"></div>
66
<div class="microsoft-login-copy" x-text="$store.tunnelStore.loginActionCopy"></div>
67
</div>
68
</div>
69
<a class="microsoft-login-link" x-show="$store.tunnelStore.microsoftLoginUrl" :href="$store.tunnelStore.microsoftLoginUrl" target="_blank" rel="noopener noreferrer">
70
- <span class="material-symbols-outlined" aria-hidden="true">open_in_new</span>
70
+ <x-icon aria-hidden="true" name="open_in_new"></x-icon>
71
<span x-text="$store.tunnelStore.microsoftLoginUrl"></span>
72
</a>
73
<div class="microsoft-login-code-row" x-show="$store.tunnelStore.microsoftLoginCode">
@@ -78,7 +78,7 @@
78
@click="$store.tunnelStore.copyLoginCode()"
79
:class="$store.tunnelStore.codeCopied ? 'copied' : ''"
80
>
81
- <span class="material-symbols-outlined" aria-hidden="true" x-text="$store.tunnelStore.codeCopied ? 'check' : 'content_copy'"></span>
81
+ <x-icon aria-hidden="true" :name="$store.tunnelStore.codeCopied ? 'check' : 'content_copy'"></x-icon>
82
<span x-text="$store.tunnelStore.codeCopied ? 'Copied' : 'Copy code'"></span>
83
</button>
84
</div>
@@ -103,7 +103,7 @@
103
'copy-error': $store.tunnelStore.copyState === 'error'
104
}"
105
>
106
- <span class="material-symbols-outlined" aria-hidden="true" x-text="$store.tunnelStore.copyLinkIcon"></span>
106
+ <x-icon aria-hidden="true" :name="$store.tunnelStore.copyLinkIcon"></x-icon>
107
<span x-text="$store.tunnelStore.copyLinkLabel"></span>
108
</button>
109
<button
@@ -112,7 +112,7 @@
112
@click="$store.tunnelStore.refreshLink()"
113
:disabled="$store.tunnelStore.isLoading"
114
>
115
- <span class="material-symbols-outlined" aria-hidden="true">refresh</span>
115
+ <x-icon aria-hidden="true" name="refresh"></x-icon>
116
<span>New URL</span>
117
</button>
118
</div>
@@ -126,7 +126,7 @@
126
</div>
127
</div>
128
<div class="remote-link-status-panel">
129
- <span class="material-symbols-outlined" aria-hidden="true">verified</span>
129
+ <x-icon aria-hidden="true" name="verified"></x-icon>
130
<div>
131
<div class="remote-link-status-title">Remote Control is active</div>
132
<div class="remote-link-status-copy">
@@ -143,14 +143,14 @@
143
@click="$store.tunnelStore.stopTunnel()"
144
:disabled="$store.tunnelStore.isLoading"
145
>
146
- <span class="material-symbols-outlined" aria-hidden="true">stop_circle</span>
146
+ <x-icon aria-hidden="true" name="stop_circle"></x-icon>
147
<span>Stop Remote Control</span>
148
</button>
149
</div>
150
</div>
151
152
<div class="remote-link-empty" x-show="!$store.tunnelStore.linkGenerated">
153
- <span class="material-symbols-outlined" aria-hidden="true">link</span>
153
+ <x-icon aria-hidden="true" name="link"></x-icon>
154
<strong>No Remote Control yet</strong>
155
<span>Create one when you want to reach Agent Zero from another device or share access temporarily.</span>
156
<button
@@ -159,7 +159,7 @@
159
@click="$store.tunnelStore.generateLink()"
160
:disabled="$store.tunnelStore.isLoading"
161
>
162
- <span class="material-symbols-outlined" aria-hidden="true">share</span>
162
+ <x-icon aria-hidden="true" name="share"></x-icon>
163
<span>Start Remote Control</span>
164
</button>
165
</div>
webui/components/sidebar/chats/chats-list.html
+7
-7
@@ -15,7 +15,7 @@
15
<h3 class="section-header">Chats</h3>
16
<!-- New Chat Button -->
17
<button id="newChat" class="btn-icon-action chat-list-action-btn" @click="$store.chats.newChat()" aria-label="New Chat" title="New Chat" data-bs-placement="top" data-bs-trigger="hover">
18
- <span class="material-symbols-outlined">chat_add_on</span>
18
+ <x-icon name="chat_add_on"></x-icon>
19
</button>
20
<x-extension id="chats-header-controls"></x-extension>
21
</div>
@@ -34,8 +34,8 @@
34
:aria-expanded="$store.chats.isExpanded(context.id)"
35
:aria-label="$store.chats.isExpanded(context.id) ? 'Collapse chat children' : 'Expand chat children'"
36
@click.stop="$store.chats.toggleChildren(context.id)">
37
- <span class="material-symbols-outlined"
38
- x-text="$store.chats.isExpanded(context.id) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'"></span>
37
+ <x-icon
38
+ :name="$store.chats.isExpanded(context.id) ? 'keyboard_arrow_up' : 'keyboard_arrow_down'"></x-icon>
39
</button>
40
<div class="chat-list-button">
41
<span :class="{'project-color-ball': true, 'heartbeat': context.running}"
@@ -44,13 +44,13 @@
44
x-text="$store.chats.displayName(context)"></span>
45
</div>
46
<button class="btn-icon-action chat-list-action-btn" title="Close chat" @click.stop="$confirmClick($event, () => $store.chats.killChat(context.id))">
47
- <span class="material-symbols-outlined">close</span>
47
+ <x-icon name="close"></x-icon>
48
</button>
49
<button type="button" class="btn-icon-action chat-list-action-btn" title="More chat actions"
50
aria-label="More chat actions" aria-haspopup="menu"
51
:aria-expanded="($store.sidebar.rowMenuOpenId === `chat:${context.id}`).toString()"
52
@click.stop="$store.sidebar.rowMenuToggle(`chat:${context.id}`, 'chat', $event.currentTarget)">
53
- <span class="material-symbols-outlined">more_vert</span>
53
+ <x-icon name="more_vert"></x-icon>
54
</button>
55
</div>
56
<ul class="chat-child-list" x-show="$store.chats.isExpanded(context.id) && $store.chats.hasChildren(context.id)">
@@ -66,13 +66,13 @@
66
x-text="$store.chats.displayName(child)"></span>
67
</div>
68
<button class="btn-icon-action chat-list-action-btn" title="Close chat" @click.stop="$confirmClick($event, () => $store.chats.killChat(child.id))">
69
- <span class="material-symbols-outlined">close</span>
69
+ <x-icon name="close"></x-icon>
70
</button>
71
<button type="button" class="btn-icon-action chat-list-action-btn" title="More chat actions"
72
aria-label="More chat actions" aria-haspopup="menu"
73
:aria-expanded="($store.sidebar.rowMenuOpenId === `chat:${child.id}`).toString()"
74
@click.stop="$store.sidebar.rowMenuToggle(`chat:${child.id}`, 'chat', $event.currentTarget)">
75
- <span class="material-symbols-outlined">more_vert</span>
75
+ <x-icon name="more_vert"></x-icon>
76
</button>
77
</div>
78
</li>
webui/components/sidebar/tasks/tasks-list.html
+4
-4
@@ -43,20 +43,20 @@
43
<button class="btn-icon-action"
44
@click.stop="$store.tasks.openDetail(task.id)"
45
title="View task details">
46
- <span class="material-symbols-outlined">Info</span>
46
+ <x-icon name="info"></x-icon>
47
</button>
48
<button class="btn-icon-action" title="Clear task chat"
49
@click.stop="$confirmClick($event, () => $store.tasks.reset(task.id))">
50
- <span class="material-symbols-outlined">delete_sweep</span>
50
+ <x-icon name="delete_sweep"></x-icon>
51
</button>
52
<button class="btn-icon-action" title="Delete task" @click.stop="$confirmClick($event, () => $store.tasks.deleteTask(task.id))">
53
- <span class="material-symbols-outlined">close</span>
53
+ <x-icon name="close"></x-icon>
54
</button>
55
<button type="button" class="btn-icon-action" title="More task actions"
56
aria-label="More task actions" aria-haspopup="menu"
57
:aria-expanded="($store.sidebar.rowMenuOpenId === `task:${task.id}`).toString()"
58
@click.stop="$store.sidebar.rowMenuToggle(`task:${task.id}`, 'task', $event.currentTarget)">
59
- <span class="material-symbols-outlined">more_vert</span>
59
+ <x-icon name="more_vert"></x-icon>
60
</button>
61
</div>
62
</div>
webui/components/sidebar/top-section/header-icons.html
+20
-20
@@ -34,19 +34,19 @@
34
<div class="icons-section" id="hide-button">
35
<!-- Sidebar Toggle Button -->
36
<button id="toggle-sidebar" class="toggle-sidebar-button" @click="$store.sidebar.toggle()" aria-label="Toggle Sidebar" aria-expanded="false">
37
- <span class="material-symbols-outlined" aria-hidden="true">menu</span>
37
+ <x-icon aria-hidden="true" name="menu"></x-icon>
38
</button>
39
40
<button class="config-button header-action-button" id="header-dashboard" @click="deselectChat()" title="Dashboard" aria-label="Dashboard" tabindex="-1">
41
- <span class="material-symbols-outlined" aria-hidden="true">home</span>
41
+ <x-icon aria-hidden="true" name="home"></x-icon>
42
</button>
43
44
<button class="config-button header-action-button" id="header-plugins" @click="openModal('components/plugins/list/plugin-list.html')" title="Plugins" aria-label="Plugins" tabindex="-1">
45
- <span class="material-symbols-outlined" aria-hidden="true">extension</span>
45
+ <x-icon aria-hidden="true" name="extension"></x-icon>
46
</button>
47
48
<button class="config-button header-action-button" id="header-settings" @click="openModal('settings/settings.html')" title="Settings" aria-label="Settings" tabindex="-1">
49
- <span class="material-symbols-outlined" aria-hidden="true">settings</span>
49
+ <x-icon aria-hidden="true" name="settings"></x-icon>
50
</button>
51
52
<button
@@ -58,7 +58,7 @@
58
aria-label="More options"
59
tabindex="-1"
60
>
61
- <span class="material-symbols-outlined" aria-hidden="true" :class="$store.sidebar.menuOpen ? 'rotated' : ''">expand_more</span>
61
+ <x-icon aria-hidden="true" :class="$store.sidebar.menuOpen ? 'rotated' : ''" name="expand_more"></x-icon>
62
</button>
63
</div>
64
@@ -74,37 +74,37 @@
74
<div class="dropdown-header">Navigation</div>
75
76
<button class="dropdown-item" @click="deselectChat(); $store.sidebar.menuClose()">
77
- <span class="material-symbols-outlined">home</span>
77
+ <x-icon name="home"></x-icon>
78
<span>Dashboard</span>
79
</button>
80
81
<button class="dropdown-item" @click="$store.projects.openProjectsModal(); $store.sidebar.menuClose()">
82
- <span class="material-symbols-outlined">snippet_folder</span>
82
+ <x-icon name="snippet_folder"></x-icon>
83
<span>Projects</span>
84
</button>
85
86
<button id="sidebar-files-dropdown" class="dropdown-item" @click="$store.chatInput.browseFiles(); $store.sidebar.menuClose()">
87
- <span class="material-symbols-outlined">folder</span>
87
+ <x-icon name="folder"></x-icon>
88
<span>Files</span>
89
</button>
90
91
<button class="dropdown-item" @click="ensureModalOpen('settings/tunnel/remote-link.html'); $store.sidebar.menuClose()">
92
- <span class="material-symbols-outlined">share</span>
92
+ <x-icon name="share"></x-icon>
93
<span>Remote Control</span>
94
</button>
95
96
<button class="dropdown-item" @click="open('https://space-agent.ai/', '_blank', 'noopener,noreferrer'); $store.sidebar.menuClose()">
97
- <span class="material-symbols-outlined">rocket_launch</span>
97
+ <x-icon name="rocket_launch"></x-icon>
98
<span>Space Agent</span>
99
</button>
100
101
<button class="dropdown-item" @click="openModal('components/modals/scheduler/scheduler-modal.html'); $store.sidebar.menuClose()">
102
- <span class="material-symbols-outlined">schedule</span>
102
+ <x-icon name="schedule"></x-icon>
103
<span>Tasks</span>
104
</button>
105
106
<button class="dropdown-item" @click="openModal('settings/settings.html'); $store.sidebar.menuClose()">
107
- <span class="material-symbols-outlined">settings</span>
107
+ <x-icon name="settings"></x-icon>
108
<span>Settings</span>
109
</button>
110
@@ -118,30 +118,30 @@
118
@click="$store.sidebar.toggleSection('chatActions')"
119
>
120
<span class="quick-actions-accordion-label">
121
- <span class="material-symbols-outlined">forum</span>
121
+ <x-icon name="forum"></x-icon>
122
<span>Chat Actions</span>
123
</span>
124
- <span class="material-symbols-outlined quick-actions-accordion-chevron">expand_more</span>
124
+ <x-icon class="quick-actions-accordion-chevron" name="expand_more"></x-icon>
125
</button>
126
127
<div class="quick-actions-accordion-panel" x-show="$store.sidebar.isSectionOpen('chatActions')" style="display: none;">
128
<button class="dropdown-item" @click="$store.chats.newChat(); $store.sidebar.menuClose()">
129
- <span class="material-symbols-outlined">chat_add_on</span>
129
+ <x-icon name="chat_add_on"></x-icon>
130
<span>New Chat</span>
131
</button>
132
133
<button class="dropdown-item" @click="$store.chats.loadChats(); $store.sidebar.menuClose()">
134
- <span class="material-symbols-outlined">folder_open</span>
134
+ <x-icon name="folder_open"></x-icon>
135
<span>Load Chat</span>
136
</button>
137
138
<button class="dropdown-item" x-show="$store.chats.selected" @click="$store.chats.saveChat(); $store.sidebar.menuClose()">
139
- <span class="material-symbols-outlined">save</span>
139
+ <x-icon name="save"></x-icon>
140
<span>Save Chat</span>
141
</button>
142
143
<button class="dropdown-item" x-show="$store.chats.selected" @click="$confirmClick($event, () => { $store.chats.resetChat(); $store.sidebar.menuClose() })">
144
- <span class="material-symbols-outlined">delete_sweep</span>
144
+ <x-icon name="delete_sweep"></x-icon>
145
<span>Clear Chat</span>
146
</button>
147
</div>
@@ -149,12 +149,12 @@
149
<div class="dropdown-separator"></div>
150
151
<button class="dropdown-item" x-show="$store.chats.loggedIn" @click="$confirmClick($event, () => { $store.chats.logout(); $store.sidebar.menuClose() })">
152
- <span class="material-symbols-outlined">logout</span>
152
+ <x-icon name="logout"></x-icon>
153
<span>Logout</span>
154
</button>
155
156
<button class="dropdown-item" @click="$confirmClick($event, () => { $store.chats.restart(); $store.sidebar.menuClose() })">
157
- <span class="material-symbols-outlined">restart_alt</span>
157
+ <x-icon name="restart_alt"></x-icon>
158
<span>Restart A0</span>
159
</button>
160
webui/components/welcome/welcome-screen.html
+20
-20
@@ -31,7 +31,7 @@
31
<template x-for="banner in $store.welcomeStore.sortedBanners" :key="banner.id">
32
<article class="welcome-banner" :class="$store.welcomeStore.getBannerClass(banner.type)">
33
<div class="welcome-banner-icon">
34
- <span class="material-symbols-outlined" x-text="$store.welcomeStore.getBannerIcon(banner.type)"></span>
34
+ <x-icon :name="$store.welcomeStore.getBannerIcon(banner.type)"></x-icon>
35
</div>
36
<div class="welcome-banner-content">
37
<div class="welcome-banner-title" x-text="banner.title"></div>
@@ -45,7 +45,7 @@
45
@click.stop="$store.welcomeStore.executeBannerAction(banner.cta_action)"
46
:aria-label="banner.cta_text">
47
<span x-text="banner.cta_text"></span>
48
- <span class="material-symbols-outlined" aria-hidden="true">arrow_forward</span>
48
+ <x-icon aria-hidden="true" name="arrow_forward"></x-icon>
49
</button>
50
<button class="welcome-banner-dismiss"
51
type="button"
@@ -53,7 +53,7 @@
53
@click.stop="$store.welcomeStore.dismissBanner(banner.id)"
54
title="Dismiss"
55
aria-label="Dismiss">
56
- <span class="material-symbols-outlined">close</span>
56
+ <x-icon name="close"></x-icon>
57
</button>
58
</article>
59
</template>
@@ -65,7 +65,7 @@
65
x-show="$store.welcomeStore.hasDismissedBanners"
66
:disabled="$store.welcomeStore.bannersLoading"
67
@click="$store.welcomeStore.undismissBanners()">
68
- <span class="material-symbols-outlined">undo</span>
68
+ <x-icon name="undo"></x-icon>
69
<span>Show dismissed notices</span>
70
</button>
71
</section>
@@ -75,60 +75,60 @@
75
<div class="welcome-actions">
76
<x-extension id="welcome-actions-start"></x-extension>
77
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('projects')">
78
- <span class="material-symbols-outlined welcome-action-icon">folder</span>
78
+ <x-icon class="welcome-action-icon" name="folder"></x-icon>
79
<span class="welcome-action-copy">
80
<span class="welcome-action-title">Projects</span>
81
<span class="welcome-action-description">Organize and track your work.</span>
82
</span>
83
- <span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
83
+ <x-icon class="welcome-action-arrow" name="chevron_right"></x-icon>
84
</button>
85
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('memory')">
86
- <span class="material-symbols-outlined welcome-action-icon">memory</span>
86
+ <x-icon class="welcome-action-icon" name="memory"></x-icon>
87
<span class="welcome-action-copy">
88
<span class="welcome-action-title">Memory</span>
89
<span class="welcome-action-description">Access saved context and knowledge.</span>
90
</span>
91
- <span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
91
+ <x-icon class="welcome-action-arrow" name="chevron_right"></x-icon>
92
</button>
93
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('scheduler')">
94
- <span class="material-symbols-outlined welcome-action-icon">schedule</span>
94
+ <x-icon class="welcome-action-icon" name="schedule"></x-icon>
95
<span class="welcome-action-copy">
96
<span class="welcome-action-title">Tasks</span>
97
<span class="welcome-action-description">View and manage your tasks.</span>
98
</span>
99
- <span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
99
+ <x-icon class="welcome-action-arrow" name="chevron_right"></x-icon>
100
</button>
101
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('files')">
102
- <span class="material-symbols-outlined welcome-action-icon">folder_open</span>
102
+ <x-icon class="welcome-action-icon" name="folder_open"></x-icon>
103
<span class="welcome-action-copy">
104
<span class="welcome-action-title">Files</span>
105
<span class="welcome-action-description">Search and manage your files.</span>
106
</span>
107
- <span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
107
+ <x-icon class="welcome-action-arrow" name="chevron_right"></x-icon>
108
</button>
109
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('settings')">
110
- <span class="material-symbols-outlined welcome-action-icon">settings</span>
110
+ <x-icon class="welcome-action-icon" name="settings"></x-icon>
111
<span class="welcome-action-copy">
112
<span class="welcome-action-title">Settings</span>
113
<span class="welcome-action-description">Configure Agent Zero to your preferences.</span>
114
</span>
115
- <span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
115
+ <x-icon class="welcome-action-arrow" name="chevron_right"></x-icon>
116
</button>
117
<button type="button" class="welcome-action-card" @click="$store.welcomeStore.executeAction('plugins')">
118
- <span class="material-symbols-outlined welcome-action-icon">extension</span>
118
+ <x-icon class="welcome-action-icon" name="extension"></x-icon>
119
<span class="welcome-action-copy">
120
<span class="welcome-action-title">Plugins</span>
121
<span class="welcome-action-description">Extend Agent Zero with integrations.</span>
122
</span>
123
- <span class="material-symbols-outlined welcome-action-arrow">chevron_right</span>
123
+ <x-icon class="welcome-action-arrow" name="chevron_right"></x-icon>
124
</button>
125
<button type="button" class="welcome-action-card is-wide" @click="$store.welcomeStore.executeAction('website')">
126
- <span class="material-symbols-outlined welcome-action-icon">language</span>
126
+ <x-icon class="welcome-action-icon" name="language"></x-icon>
127
<span class="welcome-action-copy">
128
<span class="welcome-action-title">Visit Website</span>
129
<span class="welcome-action-description">Explore Agent Zero online and resources.</span>
130
</span>
131
- <span class="material-symbols-outlined welcome-action-arrow">open_in_new</span>
131
+ <x-icon class="welcome-action-arrow" name="open_in_new"></x-icon>
132
</button>
133
</div>
134
</section>
@@ -148,7 +148,7 @@
148
@click.stop="$store.welcomeStore.refreshBanners(true)"
149
title="Refresh"
150
aria-label="Refresh System Resources">
151
- <span class="material-symbols-outlined">refresh</span>
151
+ <x-icon name="refresh"></x-icon>
152
</button>
153
<button class="welcome-panel-icon-button"
154
type="button"
@@ -156,7 +156,7 @@
156
@click.stop="$store.welcomeStore.dismissBanner($store.welcomeStore.systemResourceBanner?.id || 'system-resources')"
157
title="Dismiss"
158
aria-label="Dismiss System Resources">
159
- <span class="material-symbols-outlined">close</span>
159
+ <x-icon name="close"></x-icon>
160
</button>
161
</div>
162
</header>
webui/index.html
+24
-12
@@ -62,6 +62,7 @@
62
<link rel="icon" type="image/svg+xml" href="public/favicon.svg">
63
<link rel="preload" href="/vendor/google/google-icons.woff2" as="font" type="font/woff2" crossorigin>
64
<link rel="stylesheet" href="vendor/google/google-icons.css">
65
+ <script type="module" src="/js/icons.js"></script>
66
<script>
67
(() => {
68
const root = document.documentElement
@@ -86,14 +87,25 @@
87
revealApplication()
88
}, { once: true })
89
89
- const fontLoad = document.fonts?.load
90
- ? document.fonts.load('24px "Material Symbols Outlined"')
91
- : Promise.resolve([])
92
- fontLoad.then((faces) => {
93
- if (faces.length > 0) root.classList.add("material-icons-ready")
94
- }).catch((error) => {
95
- console.warn("Material Symbols font unavailable.", error)
96
- })
90
+ const loadMaterialIcons = () => {
91
+ const fontLoad = document.fonts?.load
92
+ ? document.fonts.load('24px "Material Symbols Outlined"')
93
+ : Promise.resolve([])
94
+ fontLoad.then((faces) => {
95
+ if (faces.length > 0) root.classList.add("material-icons-ready")
96
+ }).catch((error) => {
97
+ console.warn("Material Symbols font unavailable.", error)
98
+ })
99
+ }
100
+
101
+ // Firefox can resolve FontFaceSet.load() with no matching faces when
102
+ // it runs while the splash is replacing the document. Wait until
103
+ // the replacement document is parsed and its font set is stable.
104
+ if (document.readyState === "loading") {
105
+ document.addEventListener("DOMContentLoaded", loadMaterialIcons, { once: true })
106
+ } else {
107
+ loadMaterialIcons()
108
+ }
109
110
setTimeout(() => revealApplication(true), 8000)
111
})()
@@ -212,16 +224,16 @@
224
<!-- Chat Navigation Buttons (floating over chat area) -->
225
<div id="chat-nav-buttons" aria-label="Chat navigation" x-cloak>
226
<button class="btn-icon-action" title="Scroll to top" @click="$store.chatNavigation.scrollToTop()">
215
- <span class="material-symbols-outlined">vertical_align_top</span>
227
+ <x-icon name="vertical_align_top"></x-icon>
228
</button>
229
<button class="btn-icon-action" title="Previous user message" @click="$store.chatNavigation.scrollToPrevUserMessage()">
218
- <span class="material-symbols-outlined">keyboard_arrow_up</span>
230
+ <x-icon name="keyboard_arrow_up"></x-icon>
231
</button>
232
<button class="btn-icon-action" title="Next user message" @click="$store.chatNavigation.scrollToNextUserMessage()">
221
- <span class="material-symbols-outlined">keyboard_arrow_down</span>
233
+ <x-icon name="keyboard_arrow_down"></x-icon>
234
</button>
235
<button class="btn-icon-action" title="Scroll to bottom" @click="$store.chatNavigation.scrollToBottom()">
224
- <span class="material-symbols-outlined">vertical_align_bottom</span>
236
+ <x-icon name="vertical_align_bottom"></x-icon>
237
</button>
238
</div>
239
</div>
webui/js/AGENTS.md
+4
@@ -15,7 +15,9 @@
15
- `modals.js` owns the stacked modal shell, `openModal`, `closeModal`, `scrollModal`, footer relocation, backdrop, and modal z-index behavior.
16
- `surfaces.js` owns shared surface registration, right-canvas/modal mode routing, surface modal action rails, and reusable draggable/focus modal chrome.
17
- `initFw.js` owns Alpine bootstrap and custom lifecycle directives such as `x-create`, `x-destroy`, and periodic `x-every-*` hooks.
18
+- `icons.js` owns `<x-icon>`, icon-name validation, the name property/attribute bridge, and helpers that read or update both the first-party custom element and legacy plugin ligature spans.
19
- `messages.js` owns native message/process-step rendering, safe Markdown and HTML conversion, and KaTeX delimiter handling.
20
+- `message-collapse.js` owns the layout-safe overflow measurement shared by collapsible user messages and responses.
21
- `message-window.js` owns the bounded, tail-first raw-log window used by message rendering.
22
- `loading-indicators.js` owns reusable DOM factories for shared loading visuals.
23
- `scroller.js` owns bottom-following snapshots and cancellation of pending scroll effects.
@@ -24,6 +26,7 @@
26
## Local Contracts
27
28
- Use ES modules and browser-compatible JavaScript.
29
+- Create dynamic Material Symbols with `document.createElement("x-icon")` and assign `.name`; generated HTML uses an empty `<x-icon name="..."></x-icon>`. Shared helpers that can receive third-party plugin DOM must continue accepting legacy `.material-symbols-outlined` and `.material-icons-outlined` spans.
30
- Route JSON and fetch calls through `api.js` unless a caller has a specific nonstandard transport contract.
31
- `callJsonApi()` is for JSON request/response flows and must preserve CSRF/auth behavior.
32
- `fetchApi()` must continue adding CSRF headers, retrying 403 CSRF refresh paths, and redirecting to `/login` when required.
@@ -57,6 +60,7 @@
60
- Context switches, log GUID resets, and full log snapshots must reset both message DOM and message-window cache state.
61
- Context switches clear stale history immediately but defer the chat loading splash for 300 ms so fast loads do not flash; slower loads fade it in and dismiss it only after the matching context snapshot finishes rendering. Stale snapshots and timers must not affect a newer switch's splash.
62
- Long primary-agent responses start collapsed only during chat replay; long user-message text starts collapsed during both live sends and replay. Both use the shared collapsible-message behavior, but user attachments remain outside its clipped content target.
63
+- Collapse controls must appear only after a measurable message body exceeds the 15em preview. Treat hidden or zero-width chat geometry as indeterminate, remeasure replayed messages after their staging-to-live swap, and remeasure observed groups when their layout changes.
64
- Info log entries with `kvps.finished` complete the active process group and clear its running treatment.
65
66
## Work Guidance
webui/js/confirmClick.js
+9
-8
@@ -1,6 +1,8 @@
1
// Inline button two-click confirmation for destructive actions.
2
// First click arms, second click confirms, timeout resets.
3
4
+import { ICON_SELECTOR, getIconName, setIconName } from "./icons.js";
5
+
6
const CONFIRM_TIMEOUT = 2000;
7
const CONFIRM_CLASS = 'confirming';
8
const CONFIRM_ICON = 'check';
@@ -21,13 +23,13 @@ export function confirmClick(event, action) {
23
buttonStates.delete(button);
24
action();
25
} else {
24
- const iconEl = button.querySelector('.material-symbols-outlined, .material-icons-outlined');
26
+ const iconEl = button.querySelector(ICON_SELECTOR);
27
const isIconButton = iconEl && button.textContent.trim() === iconEl.textContent.trim();
28
29
const newState = {
30
confirming: true,
31
isIconButton,
30
- originalIcon: iconEl?.textContent?.trim(),
32
+ originalIcon: getIconName(iconEl),
33
originalHTML: isIconButton ? null : button.innerHTML,
34
timeoutId: setTimeout(() => {
35
resetButton(button, newState);
@@ -40,13 +42,13 @@ export function confirmClick(event, action) {
42
43
if (isIconButton && iconEl) {
44
// Icon-only button: just swap icon
43
- iconEl.textContent = CONFIRM_ICON;
45
+ setIconName(iconEl, CONFIRM_ICON);
46
} else {
47
// Text button: show icon + optional "Confirm" text
48
const originalText = button.textContent.trim();
49
const confirmContent = originalText.length >= 4
48
- ? `<span class="material-symbols-outlined">${CONFIRM_ICON}</span>${CONFIRM_TEXT}`
49
- : `<span class="material-symbols-outlined">${CONFIRM_ICON}</span>`;
50
+ ? `<x-icon name="${CONFIRM_ICON}"></x-icon>${CONFIRM_TEXT}`
51
+ : `<x-icon name="${CONFIRM_ICON}"></x-icon>`;
52
button.innerHTML = confirmContent;
53
}
54
}
@@ -56,9 +58,9 @@ export function confirmClick(event, action) {
58
function resetButton(button, state) {
59
button.classList.remove(CONFIRM_CLASS);
60
if (state.isIconButton) {
59
- const iconEl = button.querySelector('.material-symbols-outlined, .material-icons-outlined');
61
+ const iconEl = button.querySelector(ICON_SELECTOR);
62
if (iconEl && state.originalIcon) {
61
- iconEl.textContent = state.originalIcon;
63
+ setIconName(iconEl, state.originalIcon);
64
}
65
} else if (state.originalHTML) {
66
button.innerHTML = state.originalHTML;
@@ -71,4 +73,3 @@ export function registerAlpineMagic() {
73
globalThis.Alpine.magic('confirmClick', () => confirmClick);
74
}
75
}
74
-
webui/js/confirmDialog.js
+1
-1
@@ -30,7 +30,7 @@ export function showConfirmDialog(options) {
30
dialog.className = 'confirm-dialog';
31
dialog.innerHTML = `
32
<div class="confirm-dialog-header">
33
- <span class="confirm-dialog-icon material-symbols-outlined" style="color: ${typeConfig.color}">${typeConfig.icon}</span>
33
+ <x-icon class="confirm-dialog-icon" style="color: ${typeConfig.color}" name="${typeConfig.icon}"></x-icon>
34
<span class="confirm-dialog-title">${title}</span>
35
</div>
36
<div class="confirm-dialog-body">${message}</div>
webui/js/icons.js
new
+70
@@ -0,0 +1,70 @@
1
+const ICON_TAG = "x-icon";
2
+const ICON_NAME_PATTERN = /^[a-z0-9][a-z0-9_]*$/;
3
+
4
+export const ICON_SELECTOR = `${ICON_TAG}, .material-symbols-outlined, .material-icons-outlined`;
5
+
6
+export function normalizeIconName(value) {
7
+ const name = String(value ?? "").trim();
8
+ return ICON_NAME_PATTERN.test(name) ? name : "";
9
+}
10
+
11
+export function getIconName(element) {
12
+ if (!element) return "";
13
+ if (element.localName === ICON_TAG) {
14
+ return normalizeIconName(element.getAttribute("name"));
15
+ }
16
+ return normalizeIconName(element.textContent);
17
+}
18
+
19
+export function setIconName(element, value) {
20
+ if (!element) return;
21
+ const name = normalizeIconName(value);
22
+ if (element.localName === ICON_TAG) {
23
+ if (name) element.setAttribute("name", name);
24
+ else element.removeAttribute("name");
25
+ return;
26
+ }
27
+ element.textContent = name;
28
+}
29
+
30
+if (!customElements.get(ICON_TAG)) {
31
+ customElements.define(
32
+ ICON_TAG,
33
+ class extends HTMLElement {
34
+ static get observedAttributes() {
35
+ return ["name"];
36
+ }
37
+
38
+ get name() {
39
+ return normalizeIconName(this.getAttribute("name"));
40
+ }
41
+
42
+ set name(value) {
43
+ setIconName(this, value);
44
+ }
45
+
46
+ connectedCallback() {
47
+ // Preserve selectors used by older third-party plugin styles while
48
+ // first-party markup uses the semantic custom element.
49
+ this.classList.add("material-symbols-outlined");
50
+ if (
51
+ !this.hasAttribute("aria-hidden") &&
52
+ !this.hasAttribute("aria-label") &&
53
+ !this.hasAttribute("aria-labelledby")
54
+ ) {
55
+ this.setAttribute("aria-hidden", "true");
56
+ }
57
+ this.render();
58
+ }
59
+
60
+ attributeChangedCallback() {
61
+ this.render();
62
+ }
63
+
64
+ render() {
65
+ const name = this.name;
66
+ if (this.textContent !== name) this.textContent = name;
67
+ }
68
+ },
69
+ );
70
+}
webui/js/message-collapse.js
new
+43
@@ -0,0 +1,43 @@
1
+const COLLAPSE_MAX_HEIGHT_EM = 15;
2
+const UNMEASURABLE_WIDTH_PX = 1;
3
+
4
+function pixels(value) {
5
+ const parsed = Number.parseFloat(value || "0");
6
+ return Number.isFinite(parsed) ? parsed : 0;
7
+}
8
+
9
+function hasUsableContentWidth(element, style = getComputedStyle(element)) {
10
+ const contentWidth =
11
+ element.clientWidth - pixels(style.paddingLeft) - pixels(style.paddingRight);
12
+ return contentWidth > UNMEASURABLE_WIDTH_PX;
13
+}
14
+
15
+/**
16
+ * Measure whether a message body exceeds its collapsed preview.
17
+ *
18
+ * Returns null while the surrounding chat has no usable layout. Replay can be
19
+ * rendered in a hidden or zero-width staging tree, where ordinary text wraps
20
+ * once per character and would otherwise produce a false overflow result.
21
+ */
22
+export function measureMessageCollapseOverflow(
23
+ content,
24
+ { expanded = false, lazy = false } = {},
25
+) {
26
+ if (lazy) return true;
27
+ if (!content?.isConnected) return null;
28
+
29
+ const history = content.closest?.("#chat-history");
30
+ if (history && !hasUsableContentWidth(history)) {
31
+ return null;
32
+ }
33
+
34
+ const style = getComputedStyle(content);
35
+ if (!hasUsableContentWidth(content, style)) return null;
36
+
37
+ const fontSize = Number.parseFloat(style.fontSize || "16") || 16;
38
+ const maxHeight = expanded
39
+ ? fontSize * COLLAPSE_MAX_HEIGHT_EM
40
+ : content.clientHeight;
41
+
42
+ return content.scrollHeight > maxHeight;
43
+}
webui/js/messages.js
+45
-23
@@ -26,6 +26,7 @@ import { callJsExtensions } from "/js/extensions.js";
26
import { addBlankTargetsToLinks } from "/js/html-links.js";
27
import { sanitizeHtml } from "/js/safe-markdown.js";
28
import { createThreeBubbleLoader } from "/js/loading-indicators.js";
29
+import { measureMessageCollapseOverflow } from "./message-collapse.js";
30
31
// Delay before collapsing previous steps when a new step is added
32
const STEP_COLLAPSE_DELAY = {
@@ -333,6 +334,7 @@ async function renderMessageWindow({
334
}
335
336
await nextAnimationFrame();
337
+ refreshCollapsibleMessageOverflow(history);
338
if (anchor) {
339
anchorRestored = restoreMessageWindowAnchor(history, anchor) ||
340
anchorRestored;
@@ -888,7 +890,12 @@ function messageWindowNow() {
890
function refreshMessageWindowResizeObserver(history) {
891
if (!history || typeof ResizeObserver === "undefined") return;
892
if (!_messageWindowResizeObserver) {
891
- _messageWindowResizeObserver = new ResizeObserver(() => {
893
+ _messageWindowResizeObserver = new ResizeObserver((entries) => {
894
+ entries.forEach((entry) =>
895
+ refreshCollapsibleMessageOverflow(entry.target),
896
+ );
897
+
898
+ const liveHistory = getChatHistoryEl();
899
if (
900
_messageWindowSuppressScrollEvents ||
901
_messageWindowRenderPromise ||
@@ -898,9 +905,9 @@ function refreshMessageWindowResizeObserver(history) {
905
return;
906
}
907
901
- cancelPendingScroll(history);
902
- history.scrollTop = history.scrollHeight;
903
- _lastMessageWindowScrollTop = history.scrollTop;
908
+ cancelPendingScroll(liveHistory);
909
+ liveHistory.scrollTop = liveHistory.scrollHeight;
910
+ _lastMessageWindowScrollTop = liveHistory.scrollTop;
911
});
912
}
913
@@ -2929,10 +2936,10 @@ function createProcessGroup(id) {
2936
<span class="group-title">Processing...</span>
2937
<span class="step-badge GEN">GEN</span>
2938
<span class="group-metrics">
2932
- <span class="metric-time" title="Start time"><span class="material-symbols-outlined">schedule</span><span class="metric-value">--:--</span></span>
2933
- <span class="metric-steps display-none" title="Steps"><span class="material-symbols-outlined">footprint</span><span class="metric-value">0</span></span>
2934
- <span class="metric-notifications" title="Warnings/Info/Hint" hidden><span class="material-symbols-outlined">priority_high</span><span class="metric-value">0</span></span>
2935
- <span class="metric-duration display-none" title="Duration"><span class="material-symbols-outlined">timer</span><span class="metric-value">--</span></span>
2939
+ <span class="metric-time" title="Start time"><x-icon name="schedule"></x-icon><span class="metric-value">--:--</span></span>
2940
+ <span class="metric-steps display-none" title="Steps"><x-icon name="footprint"></x-icon><span class="metric-value">0</span></span>
2941
+ <span class="metric-notifications" title="Warnings/Info/Hint" hidden><x-icon name="priority_high"></x-icon><span class="metric-value">0</span></span>
2942
+ <span class="metric-duration display-none" title="Duration"><x-icon name="timer"></x-icon><span class="metric-value">--</span></span>
2943
2944
</span>
2945
`;
@@ -3124,7 +3131,7 @@ export function convertIcons(html, classes = "") {
3131
/icon:\/\/([a-zA-Z0-9_]+)(\[(?:\\.|[^\]])*\])?/g,
3132
(match, iconName, tooltipBlock) => {
3133
if (!tooltipBlock) {
3127
- return `<span class="icon material-symbols-outlined ${classes}">${iconName}</span>`;
3134
+ return `<x-icon class="icon ${classes}" name="${iconName}"></x-icon>`;
3135
}
3136
3137
const tooltipRaw = tooltipBlock
@@ -3135,7 +3142,7 @@ export function convertIcons(html, classes = "") {
3142
3143
const tooltip = escapeHTML(tooltipRaw);
3144
3138
- return `<span class="icon material-symbols-outlined ${classes}" title="${tooltip}" data-bs-placement="top" data-bs-trigger="hover">${iconName}</span>`;
3145
+ return `<x-icon class="icon ${classes}" title="${tooltip}" data-bs-placement="top" data-bs-trigger="hover" name="${iconName}"></x-icon>`;
3146
},
3147
);
3148
}
@@ -3402,20 +3409,35 @@ function setupCollapsible(
3409
3410
actionButtons.filter(Boolean).forEach((b) => container.appendChild(b));
3411
3405
- // Detect overflow after render
3412
+ const refreshOverflow = () => {
3413
+ const hasOverflow = measureMessageCollapseOverflow(collapseContent, {
3414
+ expanded: messageDiv.classList.contains("expanded"),
3415
+ lazy: messageDiv.classList.contains("lazy-content"),
3416
+ });
3417
+ if (hasOverflow === null) return false;
3418
+ messageDiv.classList.toggle("has-overflow", hasOverflow);
3419
+ return true;
3420
+ };
3421
+ messageDiv.__refreshCollapseOverflow = refreshOverflow;
3422
+
3423
+ // Detect overflow after render. Window replays are measured again after the
3424
+ // staged DOM has moved into the live, correctly sized chat history.
3425
requestAnimationFrame(() => {
3407
- const fontSize = parseFloat(
3408
- getComputedStyle(collapseContent || document.documentElement).fontSize ||
3409
- "16",
3410
- );
3411
- const maxHeight = messageDiv.classList.contains("expanded")
3412
- ? fontSize * 15
3413
- : collapseContent?.clientHeight || 0;
3414
- messageDiv.classList.toggle(
3415
- "has-overflow",
3416
- messageDiv.classList.contains("lazy-content") ||
3417
- (collapseContent?.scrollHeight || 0) > maxHeight,
3418
- );
3426
+ if (messageDiv.__refreshCollapseOverflow === refreshOverflow) {
3427
+ refreshOverflow();
3428
+ }
3429
+ });
3430
+}
3431
+
3432
+function refreshCollapsibleMessageOverflow(root) {
3433
+ if (!root) return;
3434
+ const messages = root.matches?.(".message-collapsible")
3435
+ ? [root]
3436
+ : root.querySelectorAll?.(".message-collapsible") || [];
3437
+ messages.forEach((message) => {
3438
+ if (typeof message.__refreshCollapseOverflow === "function") {
3439
+ message.__refreshCollapseOverflow();
3440
+ }
3441
});
3442
}
3443
webui/js/surfaces.js
+11
-7
@@ -1,3 +1,5 @@
1
+import { setIconName } from "./icons.js";
2
+
3
export const SURFACE_MODE_DOCKED = "canvas";
4
export const SURFACE_MODE_FLOATING = "modal";
5
export const SURFACE_MODAL_GROUP = "surfaces";
@@ -501,8 +503,8 @@ export function setupFloatingSurfaceModalChrome(options = {}) {
503
focusButton.setAttribute("aria-label", label);
504
focusButton.setAttribute("title", label);
505
focusButton.classList.toggle("is-active", active);
504
- const icon = focusButton.querySelector(".material-symbols-outlined");
505
- if (icon) icon.textContent = active ? "fullscreen_exit" : "fullscreen";
506
+ const icon = focusButton.querySelector("x-icon");
507
+ setIconName(icon, active ? "fullscreen_exit" : "fullscreen");
508
};
509
const setFocusMode = (enabled) => {
510
const active = Boolean(enabled);
@@ -565,7 +567,7 @@ export function setupFloatingSurfaceModalChrome(options = {}) {
567
focusButton.className = ["surface-button", "surface-modal-focus-button", focusButtonClass]
568
.filter(Boolean)
569
.join(" ");
568
- focusButton.innerHTML = '<span class="material-symbols-outlined" aria-hidden="true">fullscreen</span>';
570
+ focusButton.innerHTML = '<x-icon aria-hidden="true" name="fullscreen"></x-icon>';
571
const onFocusClick = () => setFocusMode(!inner.classList.contains("is-focus-mode"));
572
updateFocusButton(false);
573
focusButton.addEventListener("click", onFocusClick);
@@ -625,10 +627,9 @@ function createModalSurfaceButton(surface, metadata, modal) {
627
image.setAttribute("aria-hidden", "true");
628
button.appendChild(image);
629
} else {
628
- const icon = document.createElement("span");
629
- icon.className = "material-symbols-outlined";
630
+ const icon = document.createElement("x-icon");
631
icon.setAttribute("aria-hidden", "true");
631
- icon.textContent = surface.icon || "web_asset";
632
+ icon.name = surface.icon || "web_asset";
633
button.appendChild(icon);
634
}
635
@@ -681,7 +682,10 @@ function configureModalDockButton(modal, metadata) {
682
button.type = "button";
683
button.className = "surface-dock-button modal-dock-button";
684
button.setAttribute("aria-label", metadata.title);
684
- button.innerHTML = `<span class="material-symbols-outlined" aria-hidden="true">${metadata.icon}</span>`;
685
+ const icon = document.createElement("x-icon");
686
+ icon.setAttribute("aria-hidden", "true");
687
+ icon.name = metadata.icon;
688
+ button.appendChild(icon);
689
button.addEventListener("click", async () => {
690
if (button.disabled) return;
691
button.disabled = true;
webui/vendor/google/AGENTS.md
+3
-2
@@ -6,13 +6,14 @@
6
7
## Ownership
8
9
-- `google-icons.css` owns the font-face and icon class definitions.
9
+- `google-icons.css` owns the font-face plus the shared `<x-icon>` and legacy icon-class definitions.
10
- `google-icons.woff2` is the primary compressed font binary; `google-icons.ttf` is its compatibility fallback.
11
12
## Local Contracts
13
14
- Keep the WOFF2 and TTF fonts equivalent and keep their CSS references synchronized.
15
-- Icon ligature elements must remain exactly one em wide and high, clip fallback text, and stay transparent until the Font Loading API confirms the Material Symbols face. A failed font may leave a blank square but must never expose a layout-breaking ligature name.
15
+- `<x-icon>` and legacy icon ligature elements must remain exactly one em wide and high, clip fallback text, and stay transparent until the Font Loading API confirms the Material Symbols face after the application document finishes parsing. A failed font may leave a blank square but must never expose a layout-breaking ligature name.
16
+- Keep `.material-symbols-outlined` and `.material-icons-outlined` rendering compatible for third-party plugins even though first-party markup uses `<x-icon>`.
17
- Do not add unrelated remote font assets or tracking references.
18
19
## Work Guidance
webui/vendor/google/google-icons.css
+2
@@ -8,6 +8,7 @@
8
url(./google-icons.ttf) format('truetype');
9
}
10
11
+x-icon,
12
.material-symbols-outlined,
13
.material-icons-outlined {
14
font-family: 'Material Symbols Outlined';
@@ -33,6 +34,7 @@
34
direction: ltr;
35
}
36
37
+html:not(.material-icons-ready) x-icon,
38
html:not(.material-icons-ready) .material-symbols-outlined,
39
html:not(.material-icons-ready) .material-icons-outlined {
40
color: transparent !important;