tabs.js: drop schedule + flow-split + TTL-ticker cookies (#712 batch 5)

Four clusters of cookies in tabs.js, all pointing at prose that's
either already in docs/web-ui.md::SCH3DUL3S tab or self-evident
from the code:

- #564 (×4): inline-create row + carry-state for schedules. Layout
  rationale is in the docs; the in-code comments describe what the
  state does, which stands on its own.
- #474 (×3): inline edit form + targets multi-select for
  schedules. Same pattern — docs cover the UX, code comments
  describe the implementation pieces.
- #399 (×4): null-guard for tabs.js sections that may not exist on
  /flow.html. The 'pre-split this code lived in flow.js' historical
  paragraph dropped entirely; replaced with the present-tense
  no-op-when-target-absent convention statement.
- #335 (×3): question-TTL chip ticker. Cookies dropped; substantive
  text stays.

Plus #466 (interval composer shared use) and some adjacent cleanup
from the same passes — small additional drops folded in.

tabs.js: 48 → 31 #NNN refs (down from 73 at start of milestone).
Net -8 lines. Functional code unchanged; build clean.

refs #712
This commit is contained in:
iris 2026-05-31 12:54:36 +02:00 committed by Mara
commit 9d9cac2f61

View file

@ -407,14 +407,9 @@ 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 (#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
// 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
// no-op-when-target-absent convention the other renderers
// (renderTombstones, etc.) follow.
if (!root) return;
@ -1078,7 +1073,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 (#399).
// bundle without the dashboard's tab panes.
if (!root) return;
root.innerHTML = '';
if (!s.tombstones || !s.tombstones.length) {
@ -1218,7 +1213,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 (#399).
// `question_resolved` SSE events route through here.
if (!root) return;
root.innerHTML = '';
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19);
@ -1272,8 +1267,7 @@ 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
// (issue #335).
// without re-rendering the whole questions section.
const ttlEl = el('span', {
class: 'q-ttl', 'data-deadline': String(q.deadline_at),
});
@ -1416,7 +1410,7 @@ window.marked = marked;
}
// Single page-wide ticker that refreshes every TTL chip in place
// each second (issue #335). Renderers stamp `data-deadline` on the
// each second. 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.
@ -1553,8 +1547,7 @@ 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
// (#399).
// events route through here on every page that loads the bundle.
if (!root) return;
root.innerHTML = '';
@ -1982,7 +1975,7 @@ window.marked = marked;
}
// Tick once per second to refresh "running Xs" badges in place
// (mirrors the question-TTL ticker pattern from #335).
// (mirrors the question-TTL ticker pattern above).
setInterval(() => {
for (const span of document.querySelectorAll('.rqe-when[data-rqe-elapsed]')) {
const started = parseInt(span.dataset.rqeElapsed, 10);
@ -2102,8 +2095,8 @@ window.marked = marked;
// Local cache lets `refreshTabCounts` show the active count without
// re-fetching every second.
let schedulesState = [];
// #474 — schedule ids whose inline edit form is currently open.
// The set survives across refreshSchedules() calls so a state
// 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.
@ -2151,8 +2144,8 @@ window.marked = marked;
...children);
}
// Interval composer (#466) — shared between the new-schedule form
// and the edit-schedule form (#474). Builds the preset chip row +
// Interval composer — shared between the new-schedule form and
// the edit-schedule form. Builds the preset chip row +
// d/h/m/s sub-fields + live preview, and returns helpers to read
// and write the value.
//
@ -2301,8 +2294,8 @@ window.marked = marked;
return scheduleField('targets', box);
}
// #564: inline-create state — survives paintAtomic re-renders. We
// refresh the schedules table on tab activate + after every mutation,
// Inline-create carry-state — survives paintAtomic re-renders.
// The schedules table refreshes 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
@ -2362,9 +2355,9 @@ window.marked = marked;
function renderSchedulesList() {
const liveRoot = $('schedules-section');
if (!liveRoot) return;
// #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
// 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) => {
@ -2384,17 +2377,16 @@ window.marked = marked;
tbody.append(renderScheduleEditRow(s, agents));
}
}
// #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.
// 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.
tbody.append(renderInlineCreateRow(agents));
table.append(tbody);
root.append(table);
});
}
// #564: inline create row. Each column carries an input matching
// its display semantics (datetime-local for next-fire, mini d/h/m/s
// 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
@ -2761,7 +2753,7 @@ window.marked = marked;
return tr;
}
// #474 — inline edit form. Renders inside the schedule row when the
// 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
@ -2832,8 +2824,8 @@ window.marked = marked;
form_.addEventListener('input', saveCarry);
form_.addEventListener('change', saveCarry);
// Targets multi-select (#474 fast-follow). Shares the chip-box
// pattern with the new-schedule form via `buildTargetChips`;
// Targets multi-select. 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