dashboard: theme all remaining dialogs (replace native confirm/alert/prompt)

Follow-up to the themed-modal component: route every remaining native
browser dialog through modal.js so nothing falls back to the OS chrome.

- modal.js: add themedPrompt (input dialog) + themedToast (non-blocking
  transient notification, info/error/ok) alongside openDialog/themedConfirm.
- Migrate call sites: bindAsyncForms confirm/prompt/alerts (common.js),
  the answer-validation alert (call.js), the M0V3 reparent confirm + action
  toasts (tabs.js), and the schedule form/cancel/fire confirms + validation
  and error alerts (schedules.js).
- UX: blocking modal for confirms/prompts; non-blocking toast for transient
  errors + validation. Destructive confirms keep the danger styling.
- CSS for the toast stack + prompt input.

common.js <-> modal.js is a safe deferred import cycle (usage is call-time
only); esbuild bundles it clean.
This commit is contained in:
iris 2026-06-19 02:03:46 +02:00 committed by mara
commit 7c2de690ec
6 changed files with 141 additions and 32 deletions

View file

@ -18,7 +18,7 @@ import {
makePathLink, appendText, appendLinkified,
openStream, renderServerWarnings, bindAsyncForms,
} from './common.js';
import { themedConfirm } from './modal.js';
import { themedConfirm, themedToast } from './modal.js';
import { createTabStrip } from '@hive/shared/tabs.js';
import {
containersState, syncContainersFromSnapshot,
@ -295,10 +295,10 @@ window.marked = marked;
|| (resp.status >= 200 && resp.status < 400);
if (!ok) {
const text = await resp.text().catch(() => '');
alert('action failed: ' + resp.status + (text ? '\n\n' + text : ''));
themedToast('action failed: ' + resp.status + (text ? '\n\n' + text : ''), { type: 'error' });
}
} catch (err) {
alert('action failed: ' + err);
themedToast('action failed: ' + err, { type: 'error' });
}
}
@ -1056,7 +1056,7 @@ window.marked = marked;
const promptMsg = names.length === 1
? `move ${names[0]}${newParentLabel}?`
: `move ${names.length} agents (${names.join(', ')}) → ${newParentLabel}?`;
if (!confirm(promptMsg)) {
if (!(await themedConfirm({ message: promptMsg, danger: true }))) {
sel.selectedIndex = 0;
return;
}
@ -1105,7 +1105,7 @@ window.marked = marked;
sel.disabled = false;
sel.selectedIndex = 0;
if (failures.length) {
alert(`M0V3 completed with ${failures.length} failure${failures.length === 1 ? '' : 's'}:\n\n` + failures.join('\n'));
themedToast(`M0V3 completed with ${failures.length} failure${failures.length === 1 ? '' : 's'}:\n\n` + failures.join('\n'), { type: 'error' });
}
});
wrap.append(sel);
@ -1216,7 +1216,7 @@ window.marked = marked;
btn.disabled = false;
btn.innerHTML = original;
if (failures.length) {
alert(`${label} completed with ${failures.length} failure${failures.length === 1 ? '' : 's'}:\n\n` + failures.join('\n'));
themedToast(`${label} completed with ${failures.length} failure${failures.length === 1 ? '' : 's'}:\n\n` + failures.join('\n'), { type: 'error' });
}
// Container-lifecycle events (ContainerStateChanged /
// ContainerRemoved / RebuildQueueChanged) flow over the existing