fix(#2650,#2649): drop reminders tab + pending_reminders chip
hive-c0re's broker reminder store and /api/reminders endpoint were removed in PR #2644 (reminders migrated to in-container sqlite store). The dashboard had a QU3U3D R3M1ND3RS section on the schedules tab backed by that endpoint, and a per-agent badge driven by pending_reminders (always 0 after the migration). Remove both. Per-agent reminders now surface through get_loose_ends / the todos pill on the agent page, consistent with the todos migration. - frontend/packages/dashboard/src/schedules.js: drop refreshReminders, renderReminders, applyRemindersChanged; drop appendLinkified import (no longer used); update module comment. - frontend/packages/dashboard/src/tabs.js: drop applyRemindersChanged and refreshReminders imports; remove reminders-section from managed list; remove refreshReminders call site; drop reminders_changed from SSE dispatch; simplify countdown ticker (reminder-due gone). - frontend/packages/dashboard/src/dashboard.html: remove QU3U3D R3M1ND3RS section. - frontend/packages/dashboard/src/dashboard.css: remove reminder list + row CSS. - frontend/packages/dashboard/src/common.css: remove .badge-reminder. - frontend/packages/dashboard/src/swarm.js: remove pending_reminders from fingerprint key and badge render.
This commit is contained in:
parent
ee072149c9
commit
fab2fdcc0a
6 changed files with 21 additions and 180 deletions
|
|
@ -77,10 +77,6 @@ code {
|
|||
color: var(--muted); border-color: var(--purple-dim);
|
||||
background: color-mix(in srgb, var(--muted) 8%, transparent);
|
||||
}
|
||||
.badge-reminder {
|
||||
color: var(--cyan); border-color: var(--cyan);
|
||||
text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 40%, transparent);
|
||||
}
|
||||
.badge-loose-ends {
|
||||
color: var(--purple); border-color: var(--purple);
|
||||
text-shadow: 0 0 6px color-mix(in srgb, var(--purple) 40%, transparent);
|
||||
|
|
|
|||
|
|
@ -675,42 +675,6 @@ summary:hover { color: var(--purple); }
|
|||
padding: 0.6em 0.9em;
|
||||
animation: questions-pulse 2.4s ease-in-out infinite;
|
||||
}
|
||||
.reminders {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.reminder-row {
|
||||
padding: 0.4em 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.reminder-row:last-child { border-bottom: 0; }
|
||||
.reminder-head { font-size: 0.9em; }
|
||||
.reminder-body {
|
||||
color: var(--fg);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0.3em 0;
|
||||
}
|
||||
.reminder-row.reminder-failed {
|
||||
border-left: 2px solid var(--red);
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
.reminder-error {
|
||||
color: var(--red);
|
||||
background: color-mix(in srgb, var(--red) 6%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--red) 25%, transparent);
|
||||
padding: 0.3em 0.5em;
|
||||
font-size: 0.85em;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0.2em 0;
|
||||
}
|
||||
.reminder-actions {
|
||||
display: flex;
|
||||
gap: 0.4em;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
.questions-filters {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -206,19 +206,6 @@
|
|||
<p class="meta">loading…</p>
|
||||
</div>
|
||||
|
||||
<!-- QU3U3D R3M1ND3RS: self-scheduled agent reminders. Lives
|
||||
on this tab so the operator has one place for everything
|
||||
that fires at a future time — operator-set schedules
|
||||
above, agent-self reminders here. Backed by GET
|
||||
/api/reminders; live updates via `reminders_changed` SSE;
|
||||
refreshReminders() called from refreshState + tab
|
||||
activation as safety net for disconnect windows. -->
|
||||
<h2>◆ QU3U3D R3M1ND3RS ◆</h2>
|
||||
<div class="divider">══════════════════════════════════════════════════════════════</div>
|
||||
<p class="meta">reminders agents have queued for themselves but not yet delivered. cancel to drop a stuck or unwanted entry.</p>
|
||||
<div id="reminders-section">
|
||||
<p class="meta">loading…</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ST4TS: hive-wide turn-stats aggregate lives on its own page now
|
||||
|
|
|
|||
|
|
@ -1,114 +1,24 @@
|
|||
// Dashboard SCH3DUL3S tab — reminders + scheduled-prompts.
|
||||
// Dashboard SCH3DUL3S tab — scheduled-prompts.
|
||||
//
|
||||
// Reminders (a separate sqlite table, not part of /api/state) and the
|
||||
// operator's scheduled prompts: the lists, the inline create row, the
|
||||
// per-row edit form, and the fire-now / cancel actions. Live updates
|
||||
// arrive via the `reminders_changed` / `schedules_changed` dashboard
|
||||
// events (wired into the entry's mutation dispatch table); the
|
||||
// tab-activation re-fetch is the cold-load / reconnect recovery path.
|
||||
// The operator's scheduled prompts: the list, the inline create row,
|
||||
// the per-row edit form, and the fire-now / cancel actions. Live updates
|
||||
// arrive via the `schedules_changed` dashboard event (wired into the
|
||||
// entry's mutation dispatch table); the tab-activation re-fetch is the
|
||||
// cold-load / reconnect recovery path.
|
||||
//
|
||||
// Owns its own module state (the schedules list + edit-in-progress
|
||||
// tracking); reads the shared agent roster from state.js for the
|
||||
// target-chip pickers. Render + format helpers come from util.js.
|
||||
//
|
||||
// Note: per-agent reminders moved to the in-container store in PR #2644
|
||||
// and are no longer listed here — they surface via get_loose_ends /
|
||||
// the todos pill on the agent page instead.
|
||||
|
||||
import { $, el, appendLinkified } from './common.js';
|
||||
import { $, el } from './common.js';
|
||||
import { themedConfirm, themedToast } from './modal.js';
|
||||
import { paintAtomic, epochSec, fmtAgo, fmtDuration } from './util.js';
|
||||
import { containersState } from './state.js';
|
||||
import { asyncBtn } from '@hive/shared/forms.js';
|
||||
|
||||
export async function refreshReminders() {
|
||||
const liveRoot = $('reminders-section');
|
||||
if (!liveRoot) return;
|
||||
try {
|
||||
const resp = await fetch('/api/reminders');
|
||||
if (!resp.ok) {
|
||||
paintAtomic(liveRoot, (root) => {
|
||||
root.append(el('p', { class: 'empty' }, 'reminders unavailable: http ' + resp.status));
|
||||
});
|
||||
return;
|
||||
}
|
||||
const rows = await resp.json();
|
||||
renderReminders(rows);
|
||||
} catch (err) {
|
||||
paintAtomic(liveRoot, (root) => {
|
||||
root.append(el('p', { class: 'empty' }, 'reminders fetch failed: ' + err));
|
||||
});
|
||||
}
|
||||
}
|
||||
function renderReminders(rows) {
|
||||
const liveRoot = $('reminders-section');
|
||||
if (!liveRoot) return;
|
||||
paintAtomic(liveRoot, (root) => {
|
||||
if (!rows.length) {
|
||||
root.append(el('p', { class: 'empty' }, 'no queued reminders'));
|
||||
return;
|
||||
}
|
||||
const ul = el('ul', { class: 'reminders' });
|
||||
for (const r of rows) {
|
||||
const failed = (r.attempt_count || 0) > 0;
|
||||
const li = el('li', { class: 'reminder-row' + (failed ? ' reminder-failed' : '') });
|
||||
const dueIn = epochSec(r.due_at) - Math.floor(Date.now() / 1000);
|
||||
const dueLabel = dueIn <= 0
|
||||
? `overdue ${fmtAgo(r.due_at)}`
|
||||
: `in ${fmtDuration(dueIn)}`;
|
||||
const head = el('div', { class: 'reminder-head' },
|
||||
el('span', { class: 'agent' }, r.agent), ' ',
|
||||
el('span', {
|
||||
class: 'meta reminder-due',
|
||||
title: new Date(r.due_at).toISOString(),
|
||||
'data-due-at': String(epochSec(r.due_at)),
|
||||
}, dueLabel),
|
||||
' ',
|
||||
el('span', { class: 'meta' }, `· id ${r.id}`),
|
||||
);
|
||||
if (r.file_path) {
|
||||
head.append(' ', el('span', { class: 'meta' }, '· payload → '));
|
||||
appendLinkified(head, r.file_path);
|
||||
}
|
||||
if (failed) {
|
||||
head.append(' ', el('span',
|
||||
{
|
||||
class: 'badge badge-warn',
|
||||
title: 'consecutive failed delivery attempts (capped at 5; over the cap the scheduler stops retrying until you click R3TRY or cancel)',
|
||||
},
|
||||
`⚠ ${r.attempt_count} failed`));
|
||||
}
|
||||
const body = el('div', { class: 'reminder-body' });
|
||||
appendLinkified(body, r.message);
|
||||
li.append(head, body);
|
||||
if (r.last_error) {
|
||||
li.append(el('div', { class: 'reminder-error' },
|
||||
el('span', { class: 'msg-sep' }, 'error: '),
|
||||
r.last_error,
|
||||
));
|
||||
}
|
||||
const actions = el('div', { class: 'reminder-actions' });
|
||||
if (failed) {
|
||||
// Retry resets the failure counters so the scheduler picks
|
||||
// the row up again on its next 5s tick. No data-no-refresh
|
||||
// — the resulting refreshState re-fires refreshReminders.
|
||||
const retryForm = el('form', {
|
||||
method: 'POST', action: '/api/retry-reminder/' + r.id,
|
||||
class: 'inline', 'data-async': '',
|
||||
});
|
||||
retryForm.append(el('button',
|
||||
{ type: 'submit', class: 'btn btn-restart' }, '↻ R3TRY'));
|
||||
actions.append(retryForm);
|
||||
}
|
||||
const cancelForm = el('form', {
|
||||
method: 'POST', action: '/api/cancel-reminder/' + r.id,
|
||||
class: 'inline', 'data-async': '',
|
||||
'data-confirm': `cancel reminder ${r.id} for ${r.agent}? this drops the queued delivery; no undo.`,
|
||||
});
|
||||
cancelForm.append(el('button', { type: 'submit', class: 'btn btn-deny' }, '✗ C4NC3L'));
|
||||
actions.append(cancelForm);
|
||||
li.append(actions);
|
||||
ul.append(li);
|
||||
}
|
||||
root.append(ul);
|
||||
});
|
||||
}
|
||||
// ─── scheduled prompts ─────────────────────────────────────────────────
|
||||
// Backend exposes `/api/schedules` (snapshot), `/api/schedules`
|
||||
// (POST, operator-direct submit), `/api/schedules/{id}/cancel`
|
||||
|
|
@ -1144,6 +1054,3 @@ export function applySchedulesChanged(ev) {
|
|||
schedulesState = (ev.schedules || []).slice();
|
||||
renderSchedulesList();
|
||||
}
|
||||
export function applyRemindersChanged(ev) {
|
||||
renderReminders(ev.reminders || []);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -459,7 +459,6 @@ function containerRowFingerprint(c, node, pending, opRunning, selected,
|
|||
running: c.running,
|
||||
needs_login: c.needs_login,
|
||||
needs_update: c.needs_update,
|
||||
pending_reminders: c.pending_reminders,
|
||||
active_model: c.active_model,
|
||||
port: c.port,
|
||||
pending,
|
||||
|
|
@ -672,14 +671,6 @@ function buildContainerLi(c, node, opts) {
|
|||
c.active_model));
|
||||
}
|
||||
|
||||
if (c.pending_reminders && c.pending_reminders > 0) {
|
||||
head.append(el('span',
|
||||
{
|
||||
class: 'badge badge-reminder',
|
||||
title: 'pending reminders queued for this agent — see the reminders section to view / cancel',
|
||||
},
|
||||
`⏰ ${c.pending_reminders}`));
|
||||
}
|
||||
// Pending questions where this agent is the asker (awaiting an
|
||||
// answer) or the target (owes a reply). Derived live from
|
||||
// questionsState so the badge updates instantly on QuestionAdded /
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import {
|
|||
initPermissions,
|
||||
} from './permissions.js';
|
||||
import {
|
||||
applySchedulesChanged, applyRemindersChanged,
|
||||
refreshSchedules, refreshReminders, activeScheduleCount,
|
||||
applySchedulesChanged,
|
||||
refreshSchedules, activeScheduleCount,
|
||||
} from './schedules.js';
|
||||
import {
|
||||
initCall,
|
||||
|
|
@ -105,11 +105,10 @@ window.marked = marked;
|
|||
// a request pending for an hour could still show "0s ago" without
|
||||
// this ticker. Also flips `.stale` (amber highlight) at exactly 1h
|
||||
// rather than only at the next re-render.
|
||||
// Live countdown for reminder due-at labels and schedule next-fire
|
||||
// cells. Both renderers stamp `data-due-at` on the element so this
|
||||
// single ticker keeps them fresh without triggering a full re-render.
|
||||
// `.reminder-due` -> "overdue X ago" / "in Y"
|
||||
// `.sched-due` -> same pattern
|
||||
// Live countdown for schedule next-fire cells. Renderers stamp
|
||||
// `data-due-at` on `.sched-due` elements so this single ticker keeps
|
||||
// them fresh without triggering a full re-render. Also keeps
|
||||
// `.approval-ts` stale-highlight fresh at the 1h boundary.
|
||||
setInterval(() => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
document.querySelectorAll('.approval-ts[data-requested-at]').forEach((node) => {
|
||||
|
|
@ -119,13 +118,13 @@ window.marked = marked;
|
|||
node.textContent = 'requested ' + fmtAgo(requestedAt);
|
||||
node.classList.toggle('stale', ageSec >= 3600);
|
||||
});
|
||||
document.querySelectorAll('.reminder-due[data-due-at], .sched-due[data-due-at]').forEach((node) => {
|
||||
document.querySelectorAll('.sched-due[data-due-at]').forEach((node) => {
|
||||
const dueAt = Number(node.getAttribute('data-due-at'));
|
||||
if (!Number.isFinite(dueAt)) return;
|
||||
const dueIn = dueAt - now;
|
||||
node.textContent = dueIn <= 0
|
||||
? 'overdue ' + fmtAgo(dueAt)
|
||||
: (node.classList.contains('reminder-due') ? 'in ' : '') + fmtDuration(dueIn);
|
||||
: fmtDuration(dueIn);
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
|
|
@ -139,7 +138,6 @@ window.marked = marked;
|
|||
'questions-section',
|
||||
'inbox-section',
|
||||
'approvals-section',
|
||||
'reminders-section',
|
||||
'schedules-section',
|
||||
'capabilities-section',
|
||||
'tool-groups-section',
|
||||
|
|
@ -248,7 +246,6 @@ window.marked = marked;
|
|||
// #inbox-section element to render into.)
|
||||
syncApprovalsFromSnapshot(s);
|
||||
renderApprovals();
|
||||
refreshReminders();
|
||||
refreshSchedules();
|
||||
restoreOpenDetails(openDetails);
|
||||
notifyDeltas(s);
|
||||
|
|
@ -310,7 +307,6 @@ window.marked = marked;
|
|||
// rebuild_queue_changed stays: it refreshes the SW4RM badges.
|
||||
rebuild_queue_changed: applyRebuildQueueChanged,
|
||||
schedules_changed: applySchedulesChanged,
|
||||
reminders_changed: applyRemindersChanged,
|
||||
capabilities_changed: applyCapabilitiesChanged,
|
||||
tool_groups_changed: applyToolGroupsChanged,
|
||||
};
|
||||
|
|
@ -370,8 +366,8 @@ window.marked = marked;
|
|||
renderSelectionBar(Array.from(containersState.values()));
|
||||
// Re-fetch on activation as a safety net: SSE covers live mutations,
|
||||
// re-sync covers disconnect windows / approval-path inserts that
|
||||
// don't yet emit. SCH3DUL3S also re-fetches reminders (same tab).
|
||||
if (target === 'schedules') { refreshSchedules(); refreshReminders(); }
|
||||
// don't yet emit.
|
||||
if (target === 'schedules') { refreshSchedules(); }
|
||||
// Permissions tables: SSE covers worker-applied changes
|
||||
// (capabilities_changed / tool_groups_changed); re-fetch on
|
||||
// activation as a safety net for any gap between SSE events and
|
||||
|
|
|
|||
Loading…
Reference in a new issue