From 7798cbc3e691b8ae6ca966fc4761a50337e6fe4d Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Tue, 25 Aug 2026 22:33:40 +0100 Subject: [PATCH] fix(arr): make the provider reorder honest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `visibility: hidden` on the disabled reorder button left a control in the DOM that rendered as nothing. `disabled` already blocks the click and drops it out of the tab order, so the hiding was decoration; the shared `.control:disabled` colour says the same thing on screen, and the row does not reflow because the hidden button reserved its box anyway. Exposing it surfaced a second problem: `.control-quiet:hover` sits after `.control:disabled` at the same specificity, so a disabled quiet control lit accent-bright under the cursor and invited a click that does nothing. Guarded with `:not(:disabled)` in the shared rule rather than on this one control. `refreshProviderEdges` found the buttons by `button:nth-of-type(1|2)`. Position in the row is not a contract — a third button would have quietly moved the edge logic onto the wrong control. They carry `data-move` now. Closes #255 --- web/src/settings.ts | 17 +++++++++++------ web/src/style.css | 9 ++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/web/src/settings.ts b/web/src/settings.ts index 0184b92..c28adc5 100644 --- a/web/src/settings.ts +++ b/web/src/settings.ts @@ -722,13 +722,18 @@ export function settingsMain(views: { hide: () => void }[], goHome: () => void): * A reorder control: the same `control control-quiet control-icon` the * sibling rows in this view use (#231), with a chevron off the shared * glyph set (#251) rather than the words "up" and "down". + * + * `data-move` is how `refreshProviderEdges` finds it (#255). Position in + * the row is not a contract — the day a third button joins the row, an + * ordinal selector would quietly disable the wrong control. */ - function moveButton(glyph: "chevronUp" | "chevronDown", label: string): HTMLButtonElement { + function moveButton(direction: "up" | "down", label: string): HTMLButtonElement { const button = el("button", "control control-quiet control-icon provider-move"); button.type = "button"; + button.dataset.move = direction; button.setAttribute("aria-label", label); button.title = label; - button.append(icon(glyph)); + button.append(icon(direction === "up" ? "chevronUp" : "chevronDown")); return button; } @@ -758,8 +763,8 @@ export function settingsMain(views: { hide: () => void }[], goHome: () => void): budgetInput.classList.add("provider-budget"); budgetInput.setAttribute("aria-label", `${provider} daily budget`); - const up = moveButton("chevronUp", `move ${provider} up`); - const down = moveButton("chevronDown", `move ${provider} down`); + const up = moveButton("up", `move ${provider} up`); + const down = moveButton("down", `move ${provider} down`); up.addEventListener("click", () => { const previous = item.previousElementSibling; @@ -865,8 +870,8 @@ export function settingsMain(views: { hide: () => void }[], goHome: () => void): refreshProviderEdges = () => { const items = [...list.children]; items.forEach((item, index) => { - const up = item.querySelector("button:nth-of-type(1)"); - const down = item.querySelector("button:nth-of-type(2)"); + const up = item.querySelector('[data-move="up"]'); + const down = item.querySelector('[data-move="down"]'); if (up) up.disabled = index === 0; if (down) down.disabled = index === items.length - 1; }); diff --git a/web/src/style.css b/web/src/style.css index 5d9d316..92a53aa 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -1093,7 +1093,10 @@ body { border-color: var(--line); } -.control-quiet:hover, +/* `:not(:disabled)` because this rule sits after `.control:disabled` at the + same specificity: without it a disabled quiet control lights accent-bright + under the cursor and invites a click that does nothing (#255). */ +.control-quiet:hover:not(:disabled), .control-quiet[aria-expanded="true"] { color: var(--accent-bright); border-color: var(--accent); @@ -2407,7 +2410,3 @@ select.form-input { .provider-budget { width: 5.5rem; } - -.provider-move:disabled { - visibility: hidden; -}