feat(webui): Volume settings section and admin boost-cap editor

Adds a server-backed Volume card to page settings with horn, TTS, and
microphone sliders. Each slider is a 0-100% share of the ceiling the
server resolved for that user, and the card shows the resulting
multiplier plus whether the user is on the normal global limit or a
raised VIP limit. A slider whose ceiling is zero renders disabled rather
than pretending to do something.

The admin section gains sliders for the VIP boost hard caps beside the
existing global gains. Both editors now render from one GAIN_FIELDS list
through a shared GainSlider instead of six copied slider blocks.

Includes the rebuilt bundle so the served UI matches the source.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Saul5662
2026-07-29 04:10:26 +01:00
co-authored by Claude
parent 09c1257578
commit 0e1ad8c6a0
11 changed files with 407 additions and 273 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -67
View File
@@ -11,74 +11,9 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
<!-- place analytics tags here and they will be injected into <head> of index.html at build time of the web UI. -->
<!-- these tags are loaded PAGE-WIDE, this means /, /spectate, /mini, etc. -->
<script>
/*
Build-time analytics adapter for the rover UI.
React only calls window.roverAnalytics.track/identify. Keeping the Umami
adapter here means analytics can still be removed, replaced, or configured
by changing this injected file instead of rebuilding app logic around a
specific analytics provider.
*/
(function () {
var pendingCalls = [];
var flushTimer = null;
function callUmami(method, args) {
if (!window.umami || typeof window.umami[method] !== 'function') return false;
window.umami[method].apply(window.umami, args);
return true;
}
function flushPendingCalls() {
if (!pendingCalls.length) return;
if (!window.umami) return;
pendingCalls = pendingCalls.filter(function (call) {
return !callUmami(call.method, call.args);
});
if (!pendingCalls.length && flushTimer) {
window.clearInterval(flushTimer);
flushTimer = null;
}
}
function enqueue(method, args) {
if (callUmami(method, args)) return;
pendingCalls.push({ method: method, args: args });
/*
The React app may fire route/session events before Umami's deferred
script has executed. Queueing preserves those early events while still
letting the whole adapter no-op harmlessly if the script is blocked.
*/
if (!flushTimer) {
flushTimer = window.setInterval(flushPendingCalls, 500);
}
}
window.roverAnalytics = {
track: function (name, data) {
enqueue('track', typeof data === 'undefined' ? [name] : [name, data]);
},
identify: function (data) {
enqueue('identify', [data || {}]);
},
};
window.addEventListener('load', flushPendingCalls);
})();
</script>
<!-- otterlytics testing for blocking local -->
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
<title>Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-Dsq-cQoD.js"></script>
<script type="module" crossorigin src="/assets/index-B8HPO9Qz.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C3R_zn0v.css">
</head>
<body>