agent: "mark all read" button on the inbox flyout (#559 frontend half)
Pairs with damocles PR #566 (broker primitive + dashboard `POST /api/agent/{name}/mark-all-read` route). The agent's per-container inbox side-panel now gets a header row with a `✓ mark all read` button that: - confirms via a one-line dialog (the action is destructive: any pending broker message for this agent is acked, the harness won't receive a wake-prompt for them) - POSTs to the host dashboard (cross-origin, same pattern as the existing operator-answer flow on this page) - surfaces `{ marked: N }` in an inline status pill, then triggers a `refreshState` so any state-derived surfaces re-read fresh - stays out of the way when the inbox is empty (only renders above a non-empty rows list) Note: `recent_for` returns the most-recent-N messages regardless of ack state, so clicking does NOT visually empty the rows list. The status pill ("✓ marked N as read") is the operator-facing confirmation; the next `turn_start` will show `0 unread` in its badge. Tooltip on the button calls this out so the operator isn't surprised the row list stays put. CSS mirrors the existing answer-form button family (mauve hover on bg-elev background) so it reads as a peer affordance, with a border-bottom separating it from the message list.
This commit is contained in:
parent
11db710ddf
commit
fe2f9c2396
2 changed files with 100 additions and 0 deletions
|
|
@ -522,6 +522,42 @@ pre.diff {
|
|||
.agent-inbox .answer-form button:disabled { opacity: 0.5; cursor: default; }
|
||||
.agent-inbox .answer-status { color: var(--muted); align-self: center; }
|
||||
|
||||
/* #559: "mark all read" header row sits above the recent-messages
|
||||
list in the inbox side-panel flyout. Same look as the answer-form
|
||||
button (mauve hover, bg-elev background) so they read as part of
|
||||
the same affordance family. */
|
||||
.agent-inbox .inbox-mark-all-row {
|
||||
display: flex;
|
||||
gap: 0.6em;
|
||||
align-items: center;
|
||||
padding: 0.4em 0.2em 0.6em;
|
||||
margin-bottom: 0.5em;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.agent-inbox .inbox-mark-all-btn {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
background: var(--bg-elev);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
padding: 0.3em 0.7em;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.agent-inbox .inbox-mark-all-btn:hover:not(:disabled) {
|
||||
border-color: var(--purple);
|
||||
color: var(--purple);
|
||||
}
|
||||
.agent-inbox .inbox-mark-all-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
.agent-inbox .inbox-mark-status {
|
||||
color: var(--muted);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.last-turn {
|
||||
color: var(--muted);
|
||||
font-size: 0.8em;
|
||||
|
|
|
|||
|
|
@ -706,6 +706,58 @@ window.marked = marked;
|
|||
return wrap;
|
||||
}
|
||||
|
||||
/** #559: "mark all read" affordance for the agent's inbox flyout.
|
||||
* Returns a DOM row containing a button + an inline status pill.
|
||||
* POSTs to the host dashboard's `/api/agent/{name}/mark-all-read`
|
||||
* (damocles PR #566) and surfaces the `{ marked: N }` count back to
|
||||
* the operator. Re-runs `onCleared` on success so the caller can
|
||||
* refresh whatever state it owns. */
|
||||
function buildInboxMarkAllRow(label, onCleared) {
|
||||
const status = el('span', { class: 'inbox-mark-status' });
|
||||
const btn = el('button', {
|
||||
type: 'button',
|
||||
class: 'inbox-mark-all-btn',
|
||||
title: 'mark every queued message for this agent as read — '
|
||||
+ 'drains the host broker\'s pending + delivered-unacked rows. '
|
||||
+ 'history shown here is the most-recent-N regardless of state, '
|
||||
+ 'so the list itself stays visible.',
|
||||
}, '✓ mark all read');
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!dashboardBase) {
|
||||
status.textContent = 'dashboard url unknown';
|
||||
return;
|
||||
}
|
||||
if (!label) {
|
||||
status.textContent = 'agent label unknown';
|
||||
return;
|
||||
}
|
||||
if (!window.confirm(
|
||||
`mark every queued message for ${label} as read? `
|
||||
+ `the message history shown stays; only the unread queue is drained.`
|
||||
)) return;
|
||||
btn.disabled = true;
|
||||
status.textContent = 'clearing…';
|
||||
try {
|
||||
const resp = await fetch(
|
||||
dashboardBase + 'api/agent/' + encodeURIComponent(label) + '/mark-all-read',
|
||||
{ method: 'POST' });
|
||||
if (resp.ok) {
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
const n = Number(data.marked) || 0;
|
||||
status.textContent = '✓ marked ' + n + ' as read';
|
||||
if (typeof onCleared === 'function') onCleared();
|
||||
} else {
|
||||
status.textContent = 'failed: http ' + resp.status;
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
status.textContent = 'failed: ' + err;
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
return el('div', { class: 'inbox-mark-all-row' }, btn, status);
|
||||
}
|
||||
|
||||
function buildInboxList(rows) {
|
||||
const wrap = el('div', { class: 'agent-inbox' });
|
||||
if (!rows.length) {
|
||||
|
|
@ -713,6 +765,18 @@ window.marked = marked;
|
|||
'inbox empty.'));
|
||||
return wrap;
|
||||
}
|
||||
// #559: "mark all read" header row drains the host broker's
|
||||
// pending + delivered-unacked rows for this agent (damocles PR
|
||||
// #566). Visible rows here are the most-recent-N regardless of
|
||||
// ack state, so the list itself doesn't visually empty on click —
|
||||
// the status pill confirms the drain count, and the next
|
||||
// turn_start's "unread" badge will read zero.
|
||||
wrap.append(buildInboxMarkAllRow(currentLabel, () => {
|
||||
// Refresh state so any UI surface that DOES depend on
|
||||
// delivery state (eg future per-status filters) picks up
|
||||
// the new shape.
|
||||
refreshState();
|
||||
}));
|
||||
const list = el('ul');
|
||||
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(5, 19);
|
||||
for (const m of rows) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue