dashboard: drop the approvals queue
Y3R C4LL is the operator inbox alone: no P3NDING APPR0VALS section, no approval cards or history, no approval_resolved subscription, no approval browser notifications, and the tab count and title prefix count unread operator messages only. The H0M3 tile text drops "approvals". The deny form was the only data-prompt form, so bindAsyncForms' prompt branch, themedPrompt and its dialog styles go too, along with the approval-only CSS (.approval*, .approvals-history, .btn-deny, the status/glyph colours and .history-note).
This commit is contained in:
parent
58ef299c3c
commit
ac1caef972
13 changed files with 32 additions and 584 deletions
|
|
@ -47,11 +47,10 @@ export function asyncBtn(btn, fn) {
|
|||
// successful submit unless the form opts out with `data-no-refresh` (forms
|
||||
// whose mutation arrives faster via an SSE event).
|
||||
//
|
||||
// Confirmation (`data-confirm`) and free-text prompts (`data-prompt` /
|
||||
// `data-prompt-field`) are surfaced via the themed dialogs in `modal.js`
|
||||
// rather than native `confirm()`/`prompt()`, and errors via `themedToast`
|
||||
// Confirmation (`data-confirm`) is surfaced via the themed dialog in
|
||||
// `modal.js` rather than native `confirm()`, and errors via `themedToast`
|
||||
// rather than `alert()`, so every page gets the same in-theme experience.
|
||||
import { themedConfirm, themedPrompt, themedToast } from "./modal.js";
|
||||
import { themedConfirm, themedToast } from "./modal.js";
|
||||
|
||||
export function bindAsyncForms(onSuccess) {
|
||||
document.addEventListener("submit", async (e) => {
|
||||
|
|
@ -64,21 +63,6 @@ export function bindAsyncForms(onSuccess) {
|
|||
!(await themedConfirm({ message: f.dataset.confirm }))
|
||||
)
|
||||
return;
|
||||
if (f.dataset.prompt) {
|
||||
const ans = await themedPrompt({ message: f.dataset.prompt });
|
||||
if (ans === null) return; // operator hit Cancel
|
||||
// Drop into a hidden input named after `data-prompt-field` (or
|
||||
// 'note' by default) so the value rides along on the POST.
|
||||
const field = f.dataset.promptField || "note";
|
||||
let input = f.querySelector(`input[name="${field}"]`);
|
||||
if (!input) {
|
||||
input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = field;
|
||||
f.append(input);
|
||||
}
|
||||
input.value = ans;
|
||||
}
|
||||
const btn = f.querySelector(
|
||||
'button[type="submit"], button:not([type]), .btn-inline',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -63,31 +63,3 @@
|
|||
gap: 0.6em;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
.promptfield {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4em;
|
||||
}
|
||||
.promptlabel {
|
||||
color: var(--subtext0);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.input {
|
||||
font-family: inherit;
|
||||
font-size: 1em;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: var(--crust);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--purple-dim);
|
||||
padding: 0.4em 0.6em;
|
||||
}
|
||||
.input:focus {
|
||||
outline: 1px solid var(--green);
|
||||
}
|
||||
.textarea {
|
||||
resize: vertical;
|
||||
min-height: 4.5em;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
// (see each component's own header comment for the design rationale).
|
||||
// This file is the orchestration layer on top: `openDialog` is the
|
||||
// general primitive (any title/message/content + a row of buttons);
|
||||
// `themedConfirm`/`themedPrompt`/`themedToast` are thin wrappers built on
|
||||
// `themedConfirm`/`themedToast` are thin wrappers built on
|
||||
// it. It's the single entry point external packages import
|
||||
// (`@hive/shared/modal.js`) — the component internals aren't exported
|
||||
// individually.
|
||||
|
|
@ -107,66 +107,6 @@ export function themedConfirm(opts = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
// themedPrompt({ title, message, label, placeholder, value, confirmLabel, cancelLabel })
|
||||
// → Promise<string | null>. Themed replacement for window.prompt(): a
|
||||
// resizable <textarea> dialog that resolves to the entered string on confirm,
|
||||
// or null on cancel. Chat-box key behaviour: **Enter submits**, **Shift+Enter
|
||||
// inserts a newline** — so short answers are one keystroke while multi-line
|
||||
// reasons (e.g. an approval deny note) are still possible. Escape cancels
|
||||
// (via openDialog).
|
||||
export function themedPrompt(opts = {}) {
|
||||
const {
|
||||
title = "",
|
||||
message = "",
|
||||
label = "",
|
||||
placeholder = "",
|
||||
value = "",
|
||||
confirmLabel = "ok",
|
||||
cancelLabel = "cancel",
|
||||
} = opts;
|
||||
const input = el("textarea", {
|
||||
class: "input textarea",
|
||||
rows: "3",
|
||||
placeholder,
|
||||
});
|
||||
if (value) input.value = value;
|
||||
// Enter submits (clicks the confirm button mounted by openDialog);
|
||||
// Shift+Enter falls through to the textarea's default newline insert.
|
||||
// `closest()`/`querySelector()` stay within the shadow tree the input
|
||||
// lives in, so this resolves to the dialog's own `.box`/confirm button
|
||||
// without leaking across instances. The confirm button is selected by
|
||||
// its `variant` attribute now (hive-btn.js), not a CSS class.
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
input.closest(".box")?.querySelector('[variant="confirm"]')?.click();
|
||||
}
|
||||
});
|
||||
const content = el(
|
||||
"div",
|
||||
{ class: "promptfield" },
|
||||
label ? el("label", { class: "promptlabel" }, label) : null,
|
||||
input,
|
||||
);
|
||||
const result = openDialog({
|
||||
title,
|
||||
message,
|
||||
content,
|
||||
buttons: [
|
||||
{ label: cancelLabel, value: "__cancel__", class: "cancel" },
|
||||
{
|
||||
label: confirmLabel,
|
||||
value: "__ok__",
|
||||
class: "confirm",
|
||||
autofocus: true,
|
||||
},
|
||||
],
|
||||
}).then((v) => (v === "__ok__" ? input.value : null));
|
||||
// Prefer focusing the field over the OK button once the dialog has mounted.
|
||||
setTimeout(() => input.focus(), 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
// themedToast(message, { type, duration }) — non-blocking transient
|
||||
// notification; a lighter alternative to a modal for feedback that needs no
|
||||
// decision (errors, validation, status). Stacks in a fixed top-right
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ export function create(opts) {
|
|||
// Seq-dedupe only events of a kind that actually appeared in
|
||||
// the history replay — those are the only ones that could
|
||||
// double (once via history, once via the live buffer).
|
||||
// Mutation events (approval/container/…) are never
|
||||
// Mutation events (container/transient/…) are never
|
||||
// carried by the history endpoint; deduping them against the
|
||||
// broker-history seq would wrongly drop ones that fired
|
||||
// between a consumer's own snapshot read and this history
|
||||
|
|
|
|||
Loading…
Reference in a new issue