From bc1c91004434866b45e61cd7a250c2d480855323 Mon Sep 17 00:00:00 2001 From: iris Date: Thu, 28 May 2026 12:36:01 +0200 Subject: [PATCH] =?UTF-8?q?tabs:=20extract=20scheduleField()=20helper=20?= =?UTF-8?q?=E2=80=94=20collapse=208=20label/caption=20sites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every field on the new- and edit-schedule forms (plus buildIntervalComposer and buildTargetChips) builds the same wrapper shape: el('label', { class: 'schedule-field' }, el('span', { class: 'schedule-field-label' }, '…')) then appends an input. Eight identical-modulo-text sites. Extracting one small helper: function scheduleField(labelText, ...children) { return el('label', { class: 'schedule-field' }, el('span', { class: 'schedule-field-label' }, labelText), ...children); } drops the per-site cost to a single `form_.append(scheduleField('caption', input));` line. -10 LOC net; no behaviour change. --- frontend/packages/dashboard/src/tabs.js | 50 ++++++++++--------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index 8dcaed8b..d4729cee 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -2004,6 +2004,18 @@ window.marked = marked; } return n; } + // Label + caption wrapper shared by every field on the schedule + // forms — ``. Variadic + // children land inside the label after the caption span, which is + // what every caller wants (the input + any preset/parts/preview + // sub-rows live next to the caption). + function scheduleField(labelText, ...children) { + return el('label', { class: 'schedule-field' }, + el('span', { class: 'schedule-field-label' }, labelText), + ...children); + } + // 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 @@ -2017,8 +2029,7 @@ window.marked = marked; namePrefix = 'interval_', initialSeconds = 0, } = {}) { - const wrapper = el('label', { class: 'schedule-field' }, - el('span', { class: 'schedule-field-label' }, label)); + const wrapper = scheduleField(label); const presets = [ ['1m', 60], ['5m', 300], ['15m', 900], ['30m', 1800], ['1h', 3600], ['6h', 21600], ['12h', 43200], @@ -2142,8 +2153,6 @@ window.marked = marked; // explicitly uncheckable. New-schedule callers pass `[]`. for (const n of extraNames) if (!candidates.includes(n)) candidates.push(n); - const label = el('label', { class: 'schedule-field' }, - el('span', { class: 'schedule-field-label' }, 'targets')); const box = el('div', { class: 'schedule-targets' }); for (const name of candidates) { const id_ = idPrefix + name; @@ -2154,8 +2163,7 @@ window.marked = marked; box.append(el('label', { class: 'schedule-target-chip', for: id_ }, cb, el('span', {}, name))); } - label.append(box); - return label; + return scheduleField('targets', box); } function renderScheduleNewForm() { @@ -2181,17 +2189,12 @@ window.marked = marked; checked: new Set(carry.targets), })); - const bodyLabel = el('label', { class: 'schedule-field' }, - el('span', { class: 'schedule-field-label' }, 'prompt body')); const bodyInput = el('textarea', { name: 'body', rows: '4', required: 'required', placeholder: 'message delivered to each target at fire time', }); bodyInput.value = carry.body; - bodyLabel.append(bodyInput); - form_.append(bodyLabel); + form_.append(scheduleField('prompt body', bodyInput)); - const firstFireLabel = el('label', { class: 'schedule-field' }, - el('span', { class: 'schedule-field-label' }, 'first fire')); const firstFireInput = el('input', { type: 'datetime-local', name: 'first_fire', required: 'required', }); @@ -2200,8 +2203,7 @@ window.marked = marked; // immediately on a stale-clock accident). firstFireInput.value = carry.first_fire || isoForDatetimeLocal(new Date(Date.now() + 5 * 60 * 1000)); - firstFireLabel.append(firstFireInput); - form_.append(firstFireLabel); + form_.append(scheduleField('first fire', firstFireInput)); // Interval composer (#466). See `buildIntervalComposer` below — // shared between the new-schedule form and the edit-schedule form @@ -2227,14 +2229,11 @@ window.marked = marked; } intervalCx.updatePreview(); - const descLabel = el('label', { class: 'schedule-field' }, - el('span', { class: 'schedule-field-label' }, 'description (optional)')); const descInput = el('input', { type: 'text', name: 'description', placeholder: 'shown on the schedule card', }); descInput.value = carry.description; - descLabel.append(descInput); - form_.append(descLabel); + form_.append(scheduleField('description (optional)', descInput)); const actions = el('div', { class: 'schedule-actions' }); const submit = el('button', { type: 'submit', class: 'btn btn-spawn' }, '+ qu3ue prompt'); @@ -2470,32 +2469,23 @@ window.marked = marked; const carry = scheduleEditCarry.get(s.id) || {}; - const bodyLabel = el('label', { class: 'schedule-field' }, - el('span', { class: 'schedule-field-label' }, 'body')); const bodyInput = el('textarea', { name: 'body', rows: '4', required: 'required' }); bodyInput.value = carry.body !== undefined ? carry.body : s.body; - bodyLabel.append(bodyInput); - form_.append(bodyLabel); + form_.append(scheduleField('body', bodyInput)); - const descLabel = el('label', { class: 'schedule-field' }, - el('span', { class: 'schedule-field-label' }, 'description (blank to clear)')); const descInput = el('input', { type: 'text', name: 'description' }); descInput.value = carry.description !== undefined ? carry.description : (s.description || ''); - descLabel.append(descInput); - form_.append(descLabel); + form_.append(scheduleField('description (blank to clear)', descInput)); - const firstFireLabel = el('label', { class: 'schedule-field' }, - el('span', { class: 'schedule-field-label' }, 'next fire')); const firstFireInput = el('input', { type: 'datetime-local', name: 'next_fire', required: 'required', }); firstFireInput.value = carry.next_fire !== undefined ? carry.next_fire : isoForDatetimeLocal(new Date(s.next_fire_at_unix * 1000)); - firstFireLabel.append(firstFireInput); - form_.append(firstFireLabel); + form_.append(scheduleField('next fire', firstFireInput)); const intervalCx = buildIntervalComposer({ label: 'interval (blank / all-zero = flip to one-shot)',