diff --git a/frontend/packages/dashboard/src/call.js b/frontend/packages/dashboard/src/call.js index 83d205c2..967f89c4 100644 --- a/frontend/packages/dashboard/src/call.js +++ b/frontend/packages/dashboard/src/call.js @@ -252,7 +252,7 @@ export function renderApprovals() { if (savedSpawnName) spawnNameInput.value = savedSpawnName; if (spawnHadFocus) spawnNameInput.focus(); const spawn = el('form', { - method: 'POST', action: '/request-spawn', + method: 'POST', action: '/api/request-spawn', class: 'spawnform', 'data-async': '', 'data-no-refresh': '', }); spawn.append( @@ -371,13 +371,13 @@ export function renderApprovals() { // on the POST and is surfaced to the manager via // HelperEvent::ApprovalResolved { note }. const denyForm = el('form', { - method: 'POST', action: '/deny/' + a.id, + method: 'POST', action: '/api/deny/' + a.id, class: 'inline', 'data-async': '', 'data-no-refresh': '', 'data-prompt': 'reason for denying (optional, sent to manager):', }); denyForm.append(el('button', { type: 'submit', class: 'btn btn-deny' }, 'DENY')); li.append(el('div', { class: 'approval-actions' }, - form('/approve/' + a.id, 'btn-approve', '◆ APPR0VE', null, {}, { noRefresh: true }), + form('/api/approve/' + a.id, 'btn-approve', '◆ APPR0VE', null, {}, { noRefresh: true }), denyForm, )); @@ -542,7 +542,7 @@ function buildQuestionLi(q) { appendLinkified(qBody, q.question, q.question_refs); li.append(head, qBody); const f = el('form', { - method: 'POST', action: '/answer-question/' + q.id, + method: 'POST', action: '/api/answer-question/' + q.id, class: 'qform', 'data-async': '', 'data-no-refresh': '', }); const hasOptions = q.options && q.options.length; @@ -611,7 +611,7 @@ function buildQuestionLi(q) { // merge-on-submit handler attached to the main form. const cancelTargetLabel = q.target ? q.target : 'asker'; const cancelForm = el('form', { - method: 'POST', action: '/cancel-question/' + q.id, + method: 'POST', action: '/api/cancel-question/' + q.id, class: 'qform-cancel', 'data-async': '', 'data-no-refresh': '', 'data-confirm': `cancel this question? ${cancelTargetLabel} will see ` + '"[cancelled]" as the answer.', diff --git a/frontend/packages/dashboard/src/core.html b/frontend/packages/dashboard/src/core.html index 4a56f22c..51c99803 100644 --- a/frontend/packages/dashboard/src/core.html +++ b/frontend/packages/dashboard/src/core.html @@ -48,7 +48,7 @@ + live `rebuild_queue_changed` over /api/dashboard/stream. Default tab. -->

pending + running rebuilds, meta-updates, and first-spawns. one runs at a time; meta-update cascades nest under their parent. dedup: re-enqueueing a still-queued op collapses into the existing entry.

diff --git a/frontend/packages/dashboard/src/core.js b/frontend/packages/dashboard/src/core.js index e54ec604..d613a2e8 100644 --- a/frontend/packages/dashboard/src/core.js +++ b/frontend/packages/dashboard/src/core.js @@ -51,7 +51,7 @@ function renderMetaInputs(s) { } const f = el('form', { method: 'POST', - action: '/meta-update', + action: '/api/meta-update', class: 'meta-inputs-form', 'data-async': '', 'data-no-refresh': '', @@ -331,7 +331,7 @@ function renderTombstones(s) { const actions = el('div', { class: 'actions' }); const respawn = el('form', { - method: 'POST', action: '/request-spawn', + method: 'POST', action: '/api/request-spawn', class: 'inline', 'data-async': '', 'data-confirm': 'queue spawn approval for ' + t.name + '? state will be reused.', }); @@ -341,7 +341,7 @@ function renderTombstones(s) { ); actions.append(respawn); actions.append(form( - '/purge-tombstone/' + t.name, 'btn-destroy', 'PURG3', + '/api/purge-tombstone/' + t.name, 'btn-destroy', 'PURG3', 'PURGE ' + t.name + '? config history, claude creds, ' + 'and notes are all WIPED. no undo.', {}, { noRefresh: true }, @@ -545,7 +545,7 @@ async function init() { await refreshState(); - const es = openStream('/dashboard/stream'); + const es = openStream('/api/dashboard/stream'); if (es) { es.onmessage = (e) => { let ev; diff --git a/frontend/packages/dashboard/src/flow.js b/frontend/packages/dashboard/src/flow.js index 7cb808ab..d8cc681e 100644 --- a/frontend/packages/dashboard/src/flow.js +++ b/frontend/packages/dashboard/src/flow.js @@ -209,7 +209,7 @@ import { termCreate({ logEl: flow, pillAnchor: flowMain, - historyUrl: '/dashboard/history', + historyUrl: '/api/dashboard/history', // Server-side filter — only the kinds this page actually renders // or routes (sent/delivered → broker terminal, // container_state_changed/_removed → local autocomplete cache). @@ -218,7 +218,7 @@ import { // JSON-serialise is skipped entirely on irrelevant kinds. The // dashboard tabs page (tabs.js) keeps the unfiltered subscribe // since it routes every mutation kind into its derived stores. - streamUrl: '/dashboard/stream?kinds=sent,delivered,container_state_changed,container_removed', + streamUrl: '/api/dashboard/stream?kinds=sent,delivered,container_state_changed,container_removed', // Route through the SharedWorker — see docs/web-ui.md (SSE // multiplexing paragraph). Worker keys on the full URL incl. // query string, so this filtered subscribe is its own upstream @@ -383,7 +383,7 @@ import { // /op-send returns 200. The SSE channel carries the resulting // MessageEvent → the terminal renders the sent row on its own; // no /api/state refetch needed. - const resp = await fetch('/op-send', { + const resp = await fetch('/api/op-send', { method: 'POST', body: new URLSearchParams(fd), }); diff --git a/frontend/packages/dashboard/src/logs.js b/frontend/packages/dashboard/src/logs.js index 5febcaf0..7d82be4a 100644 --- a/frontend/packages/dashboard/src/logs.js +++ b/frontend/packages/dashboard/src/logs.js @@ -240,7 +240,7 @@ import { createTabStrip } from '@hive/shared/tabs.js'; if (buildRefreshTimer) clearTimeout(buildRefreshTimer); buildRefreshTimer = setTimeout(fetchBuild, 2000); }; - const es = openStream('/dashboard/stream'); + const es = openStream('/api/dashboard/stream'); if (es) { es.onmessage = (e) => { let ev; diff --git a/frontend/packages/dashboard/src/matrix-accounts.js b/frontend/packages/dashboard/src/matrix-accounts.js index e180b12d..ea597aaa 100644 --- a/frontend/packages/dashboard/src/matrix-accounts.js +++ b/frontend/packages/dashboard/src/matrix-accounts.js @@ -139,7 +139,7 @@ async function submitLogin(e) { btn.textContent = 'logging in…'; try { - const resp = await fetch('/matrix-account-login', { + const resp = await fetch('/api/matrix-account-login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(fd), diff --git a/frontend/packages/dashboard/src/schedules.js b/frontend/packages/dashboard/src/schedules.js index 3ae45889..07011a56 100644 --- a/frontend/packages/dashboard/src/schedules.js +++ b/frontend/packages/dashboard/src/schedules.js @@ -88,7 +88,7 @@ function renderReminders(rows) { // 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: '/retry-reminder/' + r.id, + method: 'POST', action: '/api/retry-reminder/' + r.id, class: 'inline', 'data-async': '', }); retryForm.append(el('button', @@ -96,7 +96,7 @@ function renderReminders(rows) { actions.append(retryForm); } const cancelForm = el('form', { - method: 'POST', action: '/cancel-reminder/' + r.id, + 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.`, }); diff --git a/frontend/packages/dashboard/src/stream-worker.js b/frontend/packages/dashboard/src/stream-worker.js index d40ca7b3..065b0e5a 100644 --- a/frontend/packages/dashboard/src/stream-worker.js +++ b/frontend/packages/dashboard/src/stream-worker.js @@ -2,7 +2,7 @@ // every server-sent event out to every connected tab via MessagePort. // // Problem this solves: every dashboard / agent tab opens its own -// `EventSource('/dashboard/stream')`. Browsers cap concurrent +// `EventSource('/api/dashboard/stream')`. Browsers cap concurrent // connections per host (~6), and Firefox throttles / disconnects // background tabs when many are open. The result: tabs silently // drop the SSE, fall behind, and only catch up on focus. @@ -15,8 +15,8 @@ // Wire protocol (port.postMessage payloads): // // tab → worker -// { kind: 'subscribe', url: '/dashboard/stream' } -// { kind: 'unsubscribe', url: '/dashboard/stream' } +// { kind: 'subscribe', url: '/api/dashboard/stream' } +// { kind: 'unsubscribe', url: '/api/dashboard/stream' } // // worker → tab // { kind: 'open', url: '...' } relayed from EventSource.onopen, diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index c35e036f..16db5f26 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -364,16 +364,16 @@ window.marked = marked; // Show only actions that are applicable in the current state. if (c.running) { dropdown.append( - menuItem('↺ R3ST4RT', { action: '/restart/', confirm: `restart ${c.name}?` }), - menuItem('■ ST0P', { action: '/kill/', confirm: `stop ${c.name}?`, confirmLabel: '■ stop', graceful: true }), + menuItem('↺ R3ST4RT', { action: '/api/restart/', confirm: `restart ${c.name}?` }), + menuItem('■ ST0P', { action: '/api/kill/', confirm: `stop ${c.name}?`, confirmLabel: '■ stop', graceful: true }), ); } else { dropdown.append( - menuItem('▶ ST4RT', { action: '/start/', confirm: `start ${c.name}?` }), + menuItem('▶ ST4RT', { action: '/api/start/', confirm: `start ${c.name}?` }), ); } dropdown.append( - menuItem('↻ R3BU1LD', { action: '/rebuild/', confirm: `rebuild ${c.name}? hot-reloads the container.` }), + menuItem('↻ R3BU1LD', { action: '/api/rebuild/', confirm: `rebuild ${c.name}? hot-reloads the container.` }), menuSep(), // Deep-link to the AGENT log tab pre-filtered to this container. // The ?agent= param is read by logs.js on load and pre-selects this @@ -387,11 +387,11 @@ window.marked = marked; dropdown.append( menuSep(), menuItem('DESTR0Y', { - action: '/destroy/', + action: '/api/destroy/', confirm: `destroy ${c.name}? container removed; state + creds kept.`, }), menuItem('PURG3', { - action: '/destroy/', + action: '/api/destroy/', body: { purge: 'on' }, confirm: `PURGE ${c.name}? WIPES container, config history, claude creds, and notes. no undo.`, }), @@ -722,7 +722,7 @@ window.marked = marked; } if (c.needs_update) { head.append(form( - '/rebuild/' + c.name, 'badge badge-warn btn-inline', 'needs update ↻', + '/api/rebuild/' + c.name, 'badge badge-warn btn-inline', 'needs update ↻', 'rebuild ' + c.name + '? hot-reloads the container.', {}, { noRefresh: true }, )); @@ -802,7 +802,7 @@ window.marked = marked; if (anyStale) { root.append(form( - '/update-all', 'btn-rebuild', '↻ UPD4TE 4LL', + '/api/update-all', 'btn-rebuild', '↻ UPD4TE 4LL', 'rebuild every stale container?', {}, { noRefresh: true }, )); @@ -1001,32 +1001,32 @@ window.marked = marked; } addBulkButton(actions, 'btn-restart', '↺ R3ST4RT', allRunning, selected, { - action: '/restart/', + action: '/api/restart/', confirm: (names) => `restart ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`, disabledTitle: why('↺ R3ST4RT', stoppedNames.map((n) => `\`${n}\` is stopped`)), }); addBulkButton(actions, 'btn-stop', '■ ST0P', allRunning, selected, { - action: '/kill/', + action: '/api/kill/', confirm: (names) => `stop ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`, confirmLabel: '■ stop', graceful: true, disabledTitle: why('■ ST0P', stoppedNames.map((n) => `\`${n}\` is already stopped`)), }); addBulkButton(actions, 'btn-start', '▶ ST4RT', allStopped, selected, { - action: '/start/', + action: '/api/start/', confirm: (names) => `start ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`, disabledTitle: why('▶ ST4RT', runningNames.map((n) => `\`${n}\` is already running`)), }); addBulkButton(actions, 'btn-rebuild', '↻ R3BU1LD', true, selected, { - action: '/rebuild/', + action: '/api/rebuild/', confirm: (names) => `rebuild ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? hot-reloads each container.`, }); addBulkButton(actions, 'btn-destroy', 'DESTR0Y', true, selected, { - action: '/destroy/', + action: '/api/destroy/', confirm: (names) => `destroy ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? containers are removed; state + creds kept.`, }); addBulkButton(actions, 'btn-destroy', 'PURG3', true, selected, { - action: '/destroy/', + action: '/api/destroy/', body: { purge: 'on' }, confirm: (names) => `PURGE ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? containers, config history, claude creds, and notes are all WIPED. no undo.`, }); @@ -1487,7 +1487,7 @@ window.marked = marked; // lost during the disconnect window (same pattern as flow.js's // onStreamOpen). // - // Both /dashboard.html and /flow.html subscribe to `/dashboard/stream` + // Both /dashboard.html and /flow.html subscribe to `/api/dashboard/stream` // and filter client-side — the dashboard ignores broker traffic // and the inbox ignores mutation events. const MUTATION_HANDLERS = { @@ -1514,7 +1514,7 @@ window.marked = marked; // paragraph) for the design + Firefox throttling motivation. // `openStream` returns an EventSource-shaped facade with a graceful // direct-EventSource fallback when SharedWorker isn't supported. - const es = openStream('/dashboard/stream'); + const es = openStream('/api/dashboard/stream'); es.onmessage = (e) => { let ev; try { ev = JSON.parse(e.data); } catch { return; }