diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index 5a5cc902..bae45de0 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -82,7 +82,8 @@ agent is stale. Things blocked on operator decision — the approval queue. -**P3NDING APPR0VALS** — the queue (see "Approval card" below). A +**P3NDING APPR0VALS** — the queue (see "Approval card" below). +The R3QU3ST SP4WN form lives at the top of this section. A `pending · N` / `history · N` tab pair switches between the live queue and the last 30 resolved approvals (see "Approval card" for the history row shape). @@ -135,7 +136,8 @@ page — see below.) **K3PT ST4T3** — two sub-sections on one pane: *Tombstones*: agents with kept state and **no container** (size + age + -claude-creds badge). One action: `PURG3` (wipes state + applied +claude-creds badge). Two actions: `⊕ R3V1V3` (queues a Spawn +approval; existing state is reused), `PURG3` (wipes state + applied dirs; `POST /api/purge-tombstone/{name}`). ⚠️ **Not only *destroyed* agents.** Nothing records a destroy: every diff --git a/frontend/packages/dashboard/src/call.js b/frontend/packages/dashboard/src/call.js index 67a4769c..c0de1cec 100644 --- a/frontend/packages/dashboard/src/call.js +++ b/frontend/packages/dashboard/src/call.js @@ -159,8 +159,33 @@ export function renderApprovals() { // no-op elsewhere — `approval_added` / `approval_resolved` SSE // events route through here on every page that loads the bundle. if (!root) return; + // Save spawn form input + focus state before the DOM wipe so a live + // approval_added/resolved event doesn't erase a partially-typed name + // or steal focus from the operator. + const savedSpawnName = root.querySelector('.spawnform input[name="name"]')?.value ?? ''; + const spawnHadFocus = document.activeElement === root.querySelector('.spawnform input[name="name"]'); root.replaceChildren(); + // Spawn request form: submitting it queues a Spawn approval that + // lands in this same list, so the form belongs here rather than on + // the containers list (the agent doesn't exist yet). + const spawnNameInput = el('input', { + name: 'name', + placeholder: 'new agent name (≤9 chars)', + maxlength: '9', required: '', autocomplete: 'off', + }); + if (savedSpawnName) spawnNameInput.value = savedSpawnName; + if (spawnHadFocus) spawnNameInput.focus(); + const spawn = el('form', { + method: 'POST', action: '/api/request-spawn', + class: 'spawnform', 'data-async': '', 'data-no-refresh': '', + }); + spawn.append( + spawnNameInput, + el('button', { type: 'submit', class: 'btn btn-spawn' }, '◆ R3QU3ST SP4WN'), + ); + root.append(spawn); + const pending = approvalsState.pending; const history = approvalsState.history; const active = localStorage.getItem(APPROVAL_TAB_KEY) || 'pending'; diff --git a/frontend/packages/dashboard/src/core.html b/frontend/packages/dashboard/src/core.html index f416a8b8..e2dcaf1a 100644 --- a/frontend/packages/dashboard/src/core.html +++ b/frontend/packages/dashboard/src/core.html @@ -33,7 +33,7 @@