diff --git a/docs/web-ui.md b/docs/web-ui.md index f225677e..397122fc 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -292,12 +292,12 @@ state + applied dirs; `POST /purge-tombstone/{name}`). Anything that fires at a future time. Operator-set schedules are created inline in the table (last row); agent self-paced -reminders surface at the bottom as a sibling list — they -share enough conceptual ground to live together. +reminders surface at the bottom as a sibling list (#460 — +they share enough conceptual ground to live together). **N3W SCH3DUL3 / QU3U3D SCH3DUL3S** — operator-managed -scheduled prompts. **Single-table layout**: each schedule is -one ``; columns are +scheduled prompts (#444 / #459). **Single-table layout** +(#535): each schedule is one ``; columns are `# | src | next | every | owner | body | …agents… | actions`. Agent columns are dynamic — `operator` + `manager` + every live container + any extra name that appears as a target on @@ -308,24 +308,26 @@ headers tilt -45° via CSS so each column reads as a narrow ~28px strip; per-agent cells render as: - **active target** → `` that cancels just - that one target on click + that one target on click (replaces the per-row ✕ from the + pre-#535 targets sub-table) - **cancelled target** → muted `✕` glyph (no button — re-adding goes through the edit form's targets multi-select) - **not a target** → empty cell Per-schedule action column: a `↯ fire now` button sends an -out-of-band manual pulse to every active target (recurring -schedules keep their cadence; one-shots are consumed after -the manual fire), an `✎ edit` button expands an inline edit -form as a colspan'd row directly under the schedule's row -(body / description / interval / next-fire / targets all -editable; targets are a multi-select diff'd against the -original active set so unchecked-was-active = `targets_remove`, -checked-not-originally-active = `targets_add`; submit PATCHes -`/api/schedules/{id}`), and a `✕` button cancels the whole -schedule (`POST /api/schedules/{id}/cancel`). +out-of-band manual pulse to every active target (#467 — +recurring schedules keep their cadence; one-shots are +consumed after the manual fire), an `✎ edit` button expands +an inline edit form as a colspan'd row directly under the +schedule's row (#474 — body / description / interval / +next-fire / targets all editable; targets are a multi-select +diff'd against the original active set so unchecked-was- +active = `targets_remove`, checked-not-originally-active = +`targets_add`; submit PATCHes `/api/schedules/{id}`), and a +`✕` button cancels the whole schedule +(`POST /api/schedules/{id}/cancel`). -The table's last row is a permanent inline creation row: +The table's last row is a permanent inline creation row (#564): inputs live directly in table cells (targets as checkboxes, body textarea that expands on focus, datetime-local pre-filled to 5 minutes from now, mini d/h/m/s number inputs (blank or @@ -345,7 +347,7 @@ button hard-deletes (`POST /cancel-reminder/{id}`) and a `R3TRY` button re-arms one whose delivery failed (`POST /retry-reminder/{id}`). Backed by `GET /api/reminders`. Lives in the SCH3DUL3S tab alongside operator schedules so the -operator has one place for everything time-fired. +operator has one place for everything time-fired (#460). ### M4TR1X page (`/matrix/`, optional) diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index ab7e3f60..c3436323 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -407,9 +407,14 @@ window.marked = marked; function renderContainers(s) { const root = $('containers-section'); // #containers-section only exists on /index.html. tabs.js is the - // bundle for that page only (flow.html loads flow.js instead). - // Belt-and-suspenders for any future page that adds tabs.js - // without a #containers-section — matches the + // bundle for that page only (#406 step 3 — /flow.html loads + // flow.js instead), but historical context: pre-split the + // `container_state_changed` SSE handler routed through + // `applyContainerStateChanged → renderContainersFromState` on + // every page that loaded the single combined bundle, and the + // guard prevented a `root is null` throw on /flow.html (#399). + // Today it's belt-and-suspenders for any future page that adds + // tabs.js without a #containers-section. Matches the // no-op-when-target-absent convention the other renderers // (renderTombstones, etc.) follow. if (!root) return; @@ -1073,7 +1078,7 @@ window.marked = marked; const root = $('tombstones-section'); // #tombstones-section only lives on /index.html (SYST3M tab); // no-op on /flow.html and any other page that loads the shared - // bundle without the dashboard's tab panes. + // bundle without the dashboard's tab panes (#399). if (!root) return; root.innerHTML = ''; if (!s.tombstones || !s.tombstones.length) { @@ -1213,7 +1218,7 @@ window.marked = marked; // no-op when missing (#406 step 3 — only /index.html loads // tabs.js, but kept as belt-and-suspenders for any future page // adding it without that section). `question_added` / - // `question_resolved` SSE events route through here. + // `question_resolved` SSE events route through here (#399). if (!root) return; root.innerHTML = ''; const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19); @@ -1267,7 +1272,8 @@ window.marked = marked; if (q.deadline_at) { // Tag the chip with its deadline so the global 1s ticker // (set up just below this function) can refresh the text - // without re-rendering the whole questions section. + // without re-rendering the whole questions section + // (issue #335). const ttlEl = el('span', { class: 'q-ttl', 'data-deadline': String(q.deadline_at), }); @@ -1410,7 +1416,7 @@ window.marked = marked; } // Single page-wide ticker that refreshes every TTL chip in place - // each second. Renderers stamp `data-deadline` on the + // each second (issue #335). Renderers stamp `data-deadline` on the // chip; this just updates `textContent`, no re-render of the // questions section. No-op when no chips are on screen, so the // cost is negligible. @@ -1547,7 +1553,8 @@ window.marked = marked; const root = $('approvals-section'); // #approvals-section only lives on /index.html (Y3R C4LL tab); // no-op elsewhere — `approval_added` / `approval_resolved` SSE - // events route through here on every page that loads the bundle. + // events route through here on every page that loads the bundle + // (#399). if (!root) return; root.innerHTML = ''; @@ -1975,7 +1982,7 @@ window.marked = marked; } // Tick once per second to refresh "running Xs" badges in place - // (mirrors the question-TTL ticker pattern above). + // (mirrors the question-TTL ticker pattern from #335). setInterval(() => { for (const span of document.querySelectorAll('.rqe-when[data-rqe-elapsed]')) { const started = parseInt(span.dataset.rqeElapsed, 10); @@ -2095,8 +2102,8 @@ window.marked = marked; // Local cache lets `refreshTabCounts` show the active count without // re-fetching every second. let schedulesState = []; - // Schedule ids whose inline edit form is currently open. The set - // survives across refreshSchedules() calls so a state + // #474 — schedule ids whose inline edit form is currently open. + // The set survives across refreshSchedules() calls so a state // poll doesn't yank the form out from under the operator. Per-id // mid-edit carry sits in `scheduleEditCarry` so unsaved typing // also rides the refresh. @@ -2144,8 +2151,8 @@ window.marked = marked; ...children); } - // Interval composer — shared between the new-schedule form and - // the edit-schedule form. Builds the preset chip row + + // Interval composer (#466) — shared between the new-schedule form + // and the edit-schedule form (#474). Builds the preset chip row + // d/h/m/s sub-fields + live preview, and returns helpers to read // and write the value. // @@ -2294,8 +2301,8 @@ window.marked = marked; return scheduleField('targets', box); } - // Inline-create carry-state — survives paintAtomic re-renders. - // The schedules table refreshes on tab activate + after every mutation, + // #564: inline-create state — survives paintAtomic re-renders. We + // refresh the schedules table on tab activate + after every mutation, // and each refresh rebuilds the DOM via `paintAtomic`. The bottom // create row's inputs would lose mid-typing values without this // carry. `readNewScheduleCarryFromDOM` is called BEFORE every @@ -2355,9 +2362,9 @@ window.marked = marked; function renderSchedulesList() { const liveRoot = $('schedules-section'); if (!liveRoot) return; - // Capture any mid-typed inline-create state BEFORE paintAtomic - // blows the row away so a refresh doesn't yank the operator's - // half-filled form. The carry is replayed by + // #564: capture any mid-typed inline-create state BEFORE + // paintAtomic blows the row away so a refresh doesn't yank the + // operator's half-filled form. The carry is replayed by // `renderInlineCreateRow` below. readNewScheduleCarryFromDOM(liveRoot.querySelector('.schedules-table-create-row')); paintAtomic(liveRoot, (root) => { @@ -2377,16 +2384,17 @@ window.marked = marked; tbody.append(renderScheduleEditRow(s, agents)); } } - // Always-visible inline create row at the bottom of the table - // — fill cells + click + to POST. See - // docs/web-ui.md::SCH3DUL3S tab for the layout rationale. + // #564: always-visible inline create row at the bottom of the + // table — fill cells + click + to POST. Folds the old + // `#schedule-new-section` form into the same surface as the + // schedules list so creation and display share one mental model. tbody.append(renderInlineCreateRow(agents)); table.append(tbody); root.append(table); }); } - // Inline create row. Each column carries an input matching its - // display semantics (datetime-local for next-fire, mini d/h/m/s + // #564: inline create row. Each column carries an input matching + // its display semantics (datetime-local for next-fire, mini d/h/m/s // number inputs for every, textarea for body, checkbox per agent // column for targets). Submitting POSTs `/api/schedules` and clears // the carry on success; the next `refreshSchedules` redraws the @@ -2753,7 +2761,7 @@ window.marked = marked; return tr; } - // Inline edit form. Renders inside the schedule row when the + // #474 — inline edit form. Renders inside the schedule row when the // row's `✎ edit` button is toggled on. Pre-filled with current // values; submit PATCHes /api/schedules/{id}. Targets stay // immutable (per damocles's backend; the workaround for retargeting @@ -2824,8 +2832,8 @@ window.marked = marked; form_.addEventListener('input', saveCarry); form_.addEventListener('change', saveCarry); - // Targets multi-select. Shares the chip-box pattern with the - // new-schedule form via `buildTargetChips`; + // Targets multi-select (#474 fast-follow). Shares the chip-box + // pattern with the new-schedule form via `buildTargetChips`; // cancelled tombstones aren't listed (re-adding them flows // through `targets_add`, which the backend replace-on-conflict // drops the tombstone for). Submit diffs against the original