diff --git a/dashboard/web/app.js b/dashboard/web/app.js
index ddd1a93..5e1e61c 100644
--- a/dashboard/web/app.js
+++ b/dashboard/web/app.js
@@ -16,8 +16,8 @@
function repoCard(title,url,links){return `${esc(title)}
${esc(url)}`}
function renderRepositories(){const d=state.data;if(!d)return;const u=d.repository_url,dist=encodeURIComponent(d.apt_distribution);$('repo-cards').innerHTML=repoCard('Debian / Ubuntu',`${u}/apt`,[['GPG public key',`${u}/repo-key.gpg`],['APT InRelease',`${u}/apt/dists/${dist}/InRelease`],['Build info',`${u}/DEBIAN-BUILD-INFO.txt`]])+repoCard('RHEL / AlmaLinux / Rocky Linux',`${u}/rpm/x86_64`,[['GPG public key',`${u}/repo-key.gpg`],['RPM repomd.xml',`${u}/rpm/x86_64/repodata/repomd.xml`],['Metadata signature',`${u}/rpm/x86_64/repodata/repomd.xml.asc`],['Build info',`${u}/RHEL-BUILD-INFO.txt`]])+repoCard('SUSE / openSUSE',`${u}/suse/x86_64`,[['GPG public key',`${u}/repo-key.gpg`],['RPM repomd.xml',`${u}/suse/x86_64/repodata/repomd.xml`],['Metadata signature',`${u}/suse/x86_64/repodata/repomd.xml.asc`],['Build info',`${u}/SUSE-BUILD-INFO.txt`]]);$('metadata').innerHTML=['debian','rhel','suse'].map(ch=>{const b=d.builds[ch]||{};return `
`}).join('')}
function renderAll(){renderOverview();renderPackages();renderInstall();renderRepositories()}
- async function refresh(){const btn=$('refresh');btn.disabled=true;try{const r=await fetch('/api/v1/packages',{cache:'no-store'});if(!r.ok)throw new Error(`HTTP ${r.status}`);state.data=await r.json();renderAll();$('last-sync').textContent=`Last refresh: ${fmtDate(state.data.generated_at)}`;$('status-text').textContent=state.data.ready?'Repository ready':'Waiting for first build'}catch(e){$('status-text').textContent='Repository unavailable';console.error(e)}finally{btn.disabled=false}}
- function setTheme(theme){document.documentElement.dataset.theme=theme;localStorage.setItem('rustbox-repo-theme',theme)}
+ async function refresh(){const btn=$('refresh'),icon=btn.querySelector('.refresh-icon');btn.disabled=true;icon?.classList.add('spinning');try{const r=await fetch('/api/v1/packages',{cache:'no-store'});if(!r.ok)throw new Error(`HTTP ${r.status}`);state.data=await r.json();renderAll();$('last-sync').textContent=`Last refresh: ${fmtDate(state.data.generated_at)}`;$('status-text').textContent=state.data.ready?'Repository ready':'Waiting for first build'}catch(e){$('status-text').textContent='Repository unavailable';console.error(e)}finally{btn.disabled=false;icon?.classList.remove('spinning')}}
+ function setTheme(theme){document.documentElement.dataset.theme=theme;const next=theme==='dark'?'light':'dark';const btn=$('theme-toggle');btn.setAttribute('aria-label',`Switch to ${next} theme`);btn.title=`Switch to ${next} theme`;localStorage.setItem('rustbox-repo-theme',theme)}
document.querySelectorAll('[data-view]').forEach(b=>b.addEventListener('click',()=>navigate(b.dataset.view)));
document.querySelectorAll('[data-go]').forEach(b=>b.addEventListener('click',()=>navigate(b.dataset.go)));
document.querySelectorAll('[data-install]').forEach(b=>b.addEventListener('click',()=>{state.install=b.dataset.install;renderInstall()}));
diff --git a/dashboard/web/index.html b/dashboard/web/index.html
index 53219d9..2953e13 100644
--- a/dashboard/web/index.html
+++ b/dashboard/web/index.html
@@ -22,7 +22,15 @@
diff --git a/dashboard/web/style.css b/dashboard/web/style.css
index b14b67f..536255d 100644
--- a/dashboard/web/style.css
+++ b/dashboard/web/style.css
@@ -1,3 +1,3 @@
-:root{color-scheme:dark;--bg:#121513;--panel:#181c19;--panel2:#1e231f;--panel3:#242a25;--sidebar:#101310;--line:#313832;--line2:#485149;--text:#edf0ec;--muted:#9da79f;--subtle:#7c877f;--accent:#8fb9a4;--accent2:#a4c9b6;--good:#a7c892;--field:#121713;--hover:#202621;--shadow:rgba(0,0,0,.32);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
-html[data-theme="light"]{color-scheme:light;--bg:#f2f4f1;--panel:#fff;--panel2:#f7f8f6;--panel3:#edf1ec;--sidebar:#e9eee9;--line:#cbd2cb;--line2:#aeb8af;--text:#202622;--muted:#647068;--subtle:#7a847d;--accent:#507b67;--accent2:#416b58;--good:#587a49;--field:#fff;--hover:#e8ede8;--shadow:rgba(31,39,33,.16)}
-*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-size:14px}.shell{min-height:100vh;display:grid;grid-template-columns:228px minmax(0,1fr)}.sidebar{position:sticky;top:0;height:100vh;background:var(--sidebar);border-right:1px solid var(--line);padding:18px 12px;display:flex;flex-direction:column}.brand{display:flex;align-items:center;padding:7px 8px 24px}.brand strong,.brand span{display:block}.brand span{color:var(--muted);font-size:11px;margin-top:2px}.sidebar nav{display:grid;gap:3px}.nav{border:0;background:transparent;color:var(--muted);text-align:left;padding:10px 12px;border-radius:5px;cursor:pointer;font-weight:600}.nav:hover{background:var(--hover);color:var(--text)}.nav.active{background:var(--panel3);color:var(--text);box-shadow:inset 2px 0 var(--accent)}.sidebar-foot{margin-top:auto;border-top:1px solid var(--line);padding:15px 8px 2px;color:var(--muted);display:flex;align-items:center;gap:8px;font-size:12px}main{min-width:0}.topbar{height:72px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 24px;background:color-mix(in srgb,var(--bg) 84%,transparent);backdrop-filter:blur(10px);position:sticky;top:0;z-index:5}.topbar h1{font-size:18px;margin:0}.topbar p{margin:3px 0 0;color:var(--subtle);font-size:11px}.toolbar{display:flex;gap:7px}.toolbar button,.panel button,.install-tabs button{border:1px solid var(--line);background:var(--panel);color:var(--text);min-height:32px;padding:0 11px;border-radius:5px;cursor:pointer}.toolbar button:hover,.panel button:hover,.install-tabs button:hover{border-color:var(--line2);background:var(--hover)}.view{display:none;padding:24px;max-width:1320px;margin:0 auto}.view.active{display:block}.hero{display:grid;grid-template-columns:minmax(0,1fr) 240px;gap:18px;background:linear-gradient(135deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:7px;padding:28px}.hero h2{font-size:30px;margin:7px 0 8px}.hero p{color:var(--muted);max-width:700px;margin:0;line-height:1.55}.eyebrow{color:var(--accent2);font-size:11px;font-weight:800;letter-spacing:.14em}.version-box{background:var(--field);border:1px solid var(--line);border-radius:6px;padding:18px;display:flex;flex-direction:column;justify-content:center}.version-box span{color:var(--subtle);font-size:10px;letter-spacing:.1em}.version-box strong{font-size:27px;margin:5px 0}.version-box small{color:var(--muted)}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:12px 0}.cards article{background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:17px}.cards span,.cards small{display:block;color:var(--muted)}.cards strong{display:block;font-size:22px;margin:5px 0}.panel{background:var(--panel);border:1px solid var(--line);border-radius:6px;margin-top:12px;overflow:hidden}.panel-head{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)}.panel-head h3{margin:0;font-size:14px}.panel-head p{margin:4px 0 0;color:var(--muted)}.package-row,.table-row{display:grid;grid-template-columns:minmax(220px,1fr) 150px 90px 110px 150px;gap:12px;align-items:center;padding:12px 16px;border-bottom:1px solid var(--line)}.package-row:last-child,.table-row:last-child{border-bottom:0}.package-row:hover,.table-row:hover{background:var(--hover)}.pkg-name{font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.muted{color:var(--muted)}.pill{display:inline-flex;align-items:center;border:1px solid var(--line2);border-radius:99px;padding:3px 7px;font-size:10px;color:var(--muted);width:max-content}.download{color:var(--accent2);text-decoration:none;font-weight:650}.filters{display:grid;grid-template-columns:minmax(240px,1fr) 190px 170px;gap:8px;padding:12px;border-bottom:1px solid var(--line)}input,select{width:100%;height:34px;background:var(--field);color:var(--text);border:1px solid var(--line);border-radius:5px;padding:0 10px;outline:none}input:focus,select:focus{border-color:var(--accent)}.install-tabs{display:flex;gap:7px;flex-wrap:wrap}.install-tabs button.active{background:var(--accent);border-color:var(--accent);color:#102018;font-weight:700}.command-panel pre{margin:0;background:#0c0f0d;color:#e8efe9;padding:18px;overflow:auto;font:12px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace}.note{margin-top:12px;border:1px solid var(--line);background:var(--panel2);padding:14px;border-radius:6px;color:var(--muted)}code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.repo-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.repo-card{background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:16px}.repo-card h3{margin:0 0 8px}.repo-card>a{color:var(--accent2);word-break:break-all}.repo-links{display:grid;margin-top:12px;border-top:1px solid var(--line)}.repo-links a{padding:9px 0;color:var(--muted);text-decoration:none;border-bottom:1px solid var(--line)}.metadata{display:grid;grid-template-columns:repeat(3,1fr)}.meta-block{padding:16px}.meta-row{display:flex;justify-content:space-between;gap:18px;padding:8px 0;border-bottom:1px solid var(--line)}.meta-row span{color:var(--muted)}.toast{position:fixed;right:18px;bottom:18px;background:var(--panel3);border:1px solid var(--line2);box-shadow:0 12px 35px var(--shadow);padding:10px 14px;border-radius:6px;opacity:0;transform:translateY(8px);pointer-events:none;transition:.18s}.toast.show{opacity:1;transform:none}.empty{padding:28px 16px;color:var(--muted);text-align:center}@media(max-width:860px){.shell{grid-template-columns:1fr}.sidebar{position:static;height:auto;border-right:0;border-bottom:1px solid var(--line);padding:10px}.brand{padding:4px 5px 10px}.sidebar nav{grid-template-columns:repeat(4,1fr)}.nav{text-align:center;padding:9px 5px;font-size:12px}.sidebar-foot{display:none}.topbar{top:0;height:62px;padding:0 14px}.view{padding:14px}.hero{grid-template-columns:1fr;padding:20px}.cards{grid-template-columns:1fr}.repo-grid,.metadata{grid-template-columns:1fr}.filters{grid-template-columns:1fr}.package-row,.table-row{grid-template-columns:minmax(0,1fr) 90px 72px}.package-row>*:nth-child(4),.package-row>*:nth-child(5),.table-row>*:nth-child(4),.table-row>*:nth-child(5){display:none}.hero h2{font-size:24px}}
+:root{color-scheme:dark;--bg:#101724;--panel:#172133;--panel2:#1c2940;--panel3:#23344e;--sidebar:#0d1522;--line:#2c3c55;--line2:#425673;--text:#f1f5fa;--muted:#aab6c8;--subtle:#8291a8;--accent:#62c9bd;--accent2:#7dd8ce;--accent-ink:#0b2a27;--good:#8bd5a0;--field:#111b2b;--hover:#202f46;--code-bg:#0b1220;--code-text:#e7eef8;--shadow:rgba(3,8,18,.34);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
+html[data-theme="light"]{color-scheme:light;--bg:#f4f8fc;--panel:#fff;--panel2:#f7fafc;--panel3:#eaf2f8;--sidebar:#edf4f9;--line:#d6e1eb;--line2:#b5c6d5;--text:#243247;--muted:#66778d;--subtle:#8290a3;--accent:#2f8078;--accent2:#236f69;--accent-ink:#fff;--good:#43875c;--field:#fff;--hover:#e8f1f7;--code-bg:#172235;--code-text:#f1f5fa;--shadow:rgba(42,64,84,.14)}
+*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-size:14px}.shell{min-height:100vh;display:grid;grid-template-columns:228px minmax(0,1fr)}.sidebar{position:sticky;top:0;height:100vh;background:var(--sidebar);border-right:1px solid var(--line);padding:18px 12px;display:flex;flex-direction:column}.brand{display:flex;align-items:center;padding:7px 8px 24px}.brand strong,.brand span{display:block}.brand span{color:var(--muted);font-size:11px;margin-top:2px}.sidebar nav{display:grid;gap:3px}.nav{border:0;background:transparent;color:var(--muted);text-align:left;padding:10px 12px;border-radius:5px;cursor:pointer;font-weight:600}.nav:hover{background:var(--hover);color:var(--text)}.nav.active{background:var(--panel3);color:var(--text);box-shadow:inset 2px 0 var(--accent)}.sidebar-foot{margin-top:auto;border-top:1px solid var(--line);padding:15px 8px 2px;color:var(--muted);display:flex;align-items:center;gap:8px;font-size:12px}main{min-width:0}.topbar{height:72px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 24px;background:color-mix(in srgb,var(--bg) 84%,transparent);backdrop-filter:blur(10px);position:sticky;top:0;z-index:5}.topbar h1{font-size:18px;margin:0}.topbar p{margin:3px 0 0;color:var(--subtle);font-size:11px}.toolbar{display:flex;gap:7px}.toolbar button,.panel button,.install-tabs button{border:1px solid var(--line);background:var(--panel);color:var(--text);min-height:32px;padding:0 11px;border-radius:5px;cursor:pointer}.toolbar button:hover,.panel button:hover,.install-tabs button:hover{border-color:var(--line2);background:var(--hover)}.toolbar .icon-button{width:34px;height:34px;min-height:34px;padding:0;display:inline-flex;align-items:center;justify-content:center}.icon-button svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.theme-icon-moon{display:none}html[data-theme="light"] .theme-icon-sun{display:none}html[data-theme="light"] .theme-icon-moon{display:block}.refresh-icon.spinning{animation:repo-spin .65s linear infinite}@keyframes repo-spin{to{transform:rotate(360deg)}}.view{display:none;padding:24px;max-width:1320px;margin:0 auto}.view.active{display:block}.hero{display:grid;grid-template-columns:minmax(0,1fr) 240px;gap:18px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--panel2) 86%,var(--accent) 14%));border:1px solid var(--line);border-radius:7px;padding:28px}.hero h2{font-size:30px;margin:7px 0 8px}.hero p{color:var(--muted);max-width:700px;margin:0;line-height:1.55}.eyebrow{color:var(--accent2);font-size:11px;font-weight:800;letter-spacing:.14em}.version-box{background:var(--field);border:1px solid var(--line);border-radius:6px;padding:18px;display:flex;flex-direction:column;justify-content:center}.version-box span{color:var(--subtle);font-size:10px;letter-spacing:.1em}.version-box strong{font-size:27px;margin:5px 0}.version-box small{color:var(--muted)}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:12px 0}.cards article{background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:17px}.cards span,.cards small{display:block;color:var(--muted)}.cards strong{display:block;font-size:22px;margin:5px 0}.panel{background:var(--panel);border:1px solid var(--line);border-radius:6px;margin-top:12px;overflow:hidden}.panel-head{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)}.panel-head h3{margin:0;font-size:14px}.panel-head p{margin:4px 0 0;color:var(--muted)}.package-row,.table-row{display:grid;grid-template-columns:minmax(220px,1fr) 150px 90px 110px 150px;gap:12px;align-items:center;padding:12px 16px;border-bottom:1px solid var(--line)}.package-row:last-child,.table-row:last-child{border-bottom:0}.package-row:hover,.table-row:hover{background:var(--hover)}.pkg-name{font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.muted{color:var(--muted)}.pill{display:inline-flex;align-items:center;border:1px solid var(--line2);border-radius:99px;padding:3px 7px;font-size:10px;color:var(--muted);width:max-content}.download{color:var(--accent2);text-decoration:none;font-weight:650}.filters{display:grid;grid-template-columns:minmax(240px,1fr) 190px 170px;gap:8px;padding:12px;border-bottom:1px solid var(--line)}input,select{width:100%;height:34px;background:var(--field);color:var(--text);border:1px solid var(--line);border-radius:5px;padding:0 10px;outline:none}input:focus,select:focus{border-color:var(--accent)}.install-tabs{display:flex;gap:7px;flex-wrap:wrap}.install-tabs button.active{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);font-weight:700}.command-panel pre{margin:0;background:var(--code-bg);color:var(--code-text);padding:18px;overflow:auto;font:12px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace}.note{margin-top:12px;border:1px solid var(--line);background:var(--panel2);padding:14px;border-radius:6px;color:var(--muted)}code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.repo-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.repo-card{background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:16px}.repo-card h3{margin:0 0 8px}.repo-card>a{color:var(--accent2);word-break:break-all}.repo-links{display:grid;margin-top:12px;border-top:1px solid var(--line)}.repo-links a{padding:9px 0;color:var(--muted);text-decoration:none;border-bottom:1px solid var(--line)}.metadata{display:grid;grid-template-columns:repeat(3,1fr)}.meta-block{padding:16px}.meta-row{display:flex;justify-content:space-between;gap:18px;padding:8px 0;border-bottom:1px solid var(--line)}.meta-row span{color:var(--muted)}.toast{position:fixed;right:18px;bottom:18px;background:var(--panel3);border:1px solid var(--line2);box-shadow:0 12px 35px var(--shadow);padding:10px 14px;border-radius:6px;opacity:0;transform:translateY(8px);pointer-events:none;transition:.18s}.toast.show{opacity:1;transform:none}.empty{padding:28px 16px;color:var(--muted);text-align:center}@media(max-width:860px){.shell{grid-template-columns:1fr}.sidebar{position:static;height:auto;border-right:0;border-bottom:1px solid var(--line);padding:10px}.brand{padding:4px 5px 10px}.sidebar nav{grid-template-columns:repeat(4,1fr)}.nav{text-align:center;padding:9px 5px;font-size:12px}.sidebar-foot{display:none}.topbar{top:0;height:62px;padding:0 14px}.view{padding:14px}.hero{grid-template-columns:1fr;padding:20px}.cards{grid-template-columns:1fr}.repo-grid,.metadata{grid-template-columns:1fr}.filters{grid-template-columns:1fr}.package-row,.table-row{grid-template-columns:minmax(0,1fr) 90px 72px}.package-row>*:nth-child(4),.package-row>*:nth-child(5),.table-row>*:nth-child(4),.table-row>*:nth-child(5){display:none}.hero h2{font-size:24px}}
diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py
index 9a070e6..22f3d7c 100644
--- a/tests/test_dashboard.py
+++ b/tests/test_dashboard.py
@@ -38,12 +38,16 @@ def test_catalog_uses_three_linux_distribution_families():
assert len(catalog["packages"]) == 3
-def test_dashboard_is_english_text_only():
+def test_dashboard_is_english_with_utility_svg_icons_only():
html = (ROOT / "dashboard/web/index.html").read_text()
js = (ROOT / "dashboard/web/app.js").read_text()
assert 'lang="en"' in html
assert "brand-icon" not in html
assert "favicon" not in html
assert "status-dot" not in html
+ assert "theme-icon-sun" in html
+ assert "theme-icon-moon" in html
+ assert "refresh-icon" in html
+ assert html.count("