From dfa5204e0a27c82ec154203c0d430290a34c93a3 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Tue, 2 Jun 2026 15:33:13 -0700 Subject: [PATCH] Repair settings: dropdown for fixed-choice settings (canonical source_selection) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The canonical source_selection setting was rendering as a free-text box — easy to typo an invalid mode. Added a generic choice mechanism so it's a dropdown: - RepairJob.setting_options: {key: [allowed values]} (default {} — opt-in). - CanonicalVersionResolveJob declares source_selection's three modes. - repair_worker.get_all_job_info() includes setting_options in the job payload. - enrichment.js renders a .value as a string, so no change needed there. Generic — any future job can get dropdowns the same way. Jobs that don't declare setting_options are untouched (empty dict -> existing input rendering). Tests: source_selection exposes the 3 options and its default is one of them. 23 repair-job/worker + canonical tests pass (other jobs unaffected). --- core/repair_jobs/base.py | 3 +++ core/repair_jobs/canonical_version_resolve.py | 4 ++++ core/repair_worker.py | 3 +++ tests/test_canonical_version_job.py | 8 ++++++++ webui/static/enrichment.js | 12 ++++++++++++ 5 files changed, 30 insertions(+) diff --git a/core/repair_jobs/base.py b/core/repair_jobs/base.py index 4ac7f52b..d0564545 100644 --- a/core/repair_jobs/base.py +++ b/core/repair_jobs/base.py @@ -100,6 +100,9 @@ class RepairJob(ABC): default_enabled: bool = False default_interval_hours: int = 24 default_settings: Dict[str, Any] = {} + # Optional {setting_key: [allowed values]} — the UI renders a dropdown for + # these instead of a free-text box. Keys not listed render by value type. + setting_options: Dict[str, list] = {} auto_fix: bool = False @abstractmethod diff --git a/core/repair_jobs/canonical_version_resolve.py b/core/repair_jobs/canonical_version_resolve.py index ea7b91a3..61b28655 100644 --- a/core/repair_jobs/canonical_version_resolve.py +++ b/core/repair_jobs/canonical_version_resolve.py @@ -101,6 +101,10 @@ class CanonicalVersionResolveJob(RepairJob): # source matches the files best, regardless of which it is). 'source_selection': 'active_preferred', } + # Render source_selection as a dropdown (not a text box) in the settings UI. + setting_options = { + 'source_selection': ['active_preferred', 'active_only', 'best_fit'], + } auto_fix = True def _get_settings(self, context: JobContext) -> dict: diff --git a/core/repair_worker.py b/core/repair_worker.py index 87ba8abb..289ad3cd 100644 --- a/core/repair_worker.py +++ b/core/repair_worker.py @@ -371,6 +371,9 @@ class RepairWorker: 'interval_hours': config['interval_hours'], 'settings': config['settings'], 'default_settings': job.default_settings.copy(), + # Per-setting choice lists so the UI can render a dropdown + # instead of a free-text box (e.g. canonical source_selection). + 'setting_options': dict(getattr(job, 'setting_options', {}) or {}), 'last_run': last_run, 'next_run': next_run, 'is_running': self._current_job_id == job_id, diff --git a/tests/test_canonical_version_job.py b/tests/test_canonical_version_job.py index f033ecb8..075fa461 100644 --- a/tests/test_canonical_version_job.py +++ b/tests/test_canonical_version_job.py @@ -59,6 +59,14 @@ def test_source_selection_defaults_to_active_preferred(): assert CanonicalVersionResolveJob.default_settings["source_selection"] == "active_preferred" +def test_source_selection_exposes_dropdown_options(): + # The UI renders a ${optionsHtml} + `; + } const inputType = typeof val === 'boolean' ? 'checkbox' : typeof val === 'number' ? 'number' : 'text'; const inputVal = inputType === 'checkbox' ?