From 50077a0c318663c830d9758d5517bd6a51e7574c Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Sun, 23 Aug 2026 14:11:15 -0400 Subject: [PATCH] Fix the second dead settings button, and pin the class shut A sweep for controls wired to targets the backend does not handle -- the shape of the dictation bug -- found one more: "Start Orca" on the Accessibility page called openApplication("orca") with no "orca" in the command map, so it set an error and launched nothing. orca ships in hyprland-packages; it now runs. That is the whole count. Every service-method call across 84 settings pages resolves, every helper subcommand a service invokes is implemented, every openGnomePanel handoff is allow-listed. Two dead buttons existed in the entire settings app -- the dictation Download and this -- and both are fixed. settings-buttons-contract pins the class: every openApplication id and openGnomePanel panel a QML button passes must be present in SystemSettings' dispatch maps, both of which return false silently on an unknown name. Verified it catches the orca button when the fix is reverted. And a dependency-contract exception the ffmpeg fix needed: ffmpeg is provided by a swap (ffmpeg-free -> ffmpeg), never a list entry, because listing it is the conflict that fix removed -- so panama-transcode's use of it is satisfied without a package name to point at. Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh --- README.md | 2 +- .../quickshell/services/SystemSettings.qml | 7 ++- .../quickshell/declared-dependencies-contract | 9 ++++ tests/quickshell/settings-buttons-contract | 52 +++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100755 tests/quickshell/settings-buttons-contract diff --git a/README.md b/README.md index bf775d3..b69cde7 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ docs/ Settings reference, and the design specs behind the work ## Tests -159 of them, under `tests/`. Run the lot, or a subset by pattern: +160 of them, under `tests/`. Run the lot, or a subset by pattern: ```sh panama test # everything diff --git a/config/dot/quickshell/services/SystemSettings.qml b/config/dot/quickshell/services/SystemSettings.qml index a778514..578cdd2 100644 --- a/config/dot/quickshell/services/SystemSettings.qml +++ b/config/dot/quickshell/services/SystemSettings.qml @@ -644,7 +644,12 @@ Singleton { "rustdesk": ["rustdesk"], "kdeconnect": ["kdeconnect-app"], "mission-center": ["flatpak", "run", "io.missioncenter.MissionCenter"], - "bluebubbles": ["flatpak", "run", "app.bluebubbles.BlueBubbles"] + "bluebubbles": ["flatpak", "run", "app.bluebubbles.BlueBubbles"], + // The Accessibility page's "Start Orca" button. It was calling + // openApplication("orca") with no "orca" here, so it set an error + // and launched nothing -- a dead button. orca ships in + // hyprland-packages, so this just runs it. + "orca": ["orca"] }; const command = commands[id]; if (!command) { diff --git a/tests/quickshell/declared-dependencies-contract b/tests/quickshell/declared-dependencies-contract index 18d1467..e57775f 100755 --- a/tests/quickshell/declared-dependencies-contract +++ b/tests/quickshell/declared-dependencies-contract @@ -56,6 +56,14 @@ SELF_INSTALLED='^(bun|claude|node|npm|pnpm)$' # Anything added here needs that same property: absence must be loud. OPTIONAL='^(docker)$' +# Provided by a package swap rather than a list entry. ffmpeg cannot be a list +# entry: RPM Fusion's ffmpeg CONFLICTS with the ffmpeg-free that Fedora ships +# preinstalled, so `dnf install ffmpeg` fails the whole transaction. The codec +# section swaps ffmpeg-free -> ffmpeg with --allowerasing instead. Either way +# /usr/bin/ffmpeg exists -- ffmpeg-free provides it too -- so panama-transcode +# always has it; it is simply never a name in a package list. +SWAPPED='^(ffmpeg)$' + # jq programs are quoted arguments, but the scanner is line-based and cannot # tell a filter from a command. `not` is a jq builtin appearing inside one. JQ_BUILTINS='^(not|empty|error|env|input|inputs)$' @@ -116,6 +124,7 @@ while read -r script; do [[ "$cmd" =~ $SESSION ]] && continue [[ "$cmd" =~ $SELF_INSTALLED ]] && continue [[ "$cmd" =~ $OPTIONAL ]] && continue + [[ "$cmd" =~ $SWAPPED ]] && continue [[ "$cmd" =~ $JQ_BUILTINS ]] && continue pkg="$(package_for "$cmd")" diff --git a/tests/quickshell/settings-buttons-contract b/tests/quickshell/settings-buttons-contract new file mode 100755 index 0000000..d20d91a --- /dev/null +++ b/tests/quickshell/settings-buttons-contract @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# A settings button that calls a target the backend does not handle is a dead +# control: it looks identical to a working one until someone presses it and +# nothing happens. Two dispatch maps in SystemSettings.qml decide whether a +# button lands -- openApplication(id) and openGnomePanel(panel) -- and both +# silently return false on an unknown name. This pins every id and panel a QML +# button passes to being present in those maps. +# +# It exists because two such buttons shipped: "Start Orca" called +# openApplication("orca") with no "orca" in the map, and the dictation card +# called a helper subcommand that did not exist. This catches the first shape; +# the helper-subcommand shape is caught by each service's own contract. + +set -uo pipefail + +repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +qs="$repo_dir/config/dot/quickshell" +sysset="$qs/services/SystemSettings.qml" + +fail() { printf 'settings buttons contract: %s\n' "$1" >&2; exit 1; } +[[ -r "$sysset" ]] || fail "missing $sysset" + +python3 - "$qs" "$sysset" <<'PY' +import re, sys, pathlib +qs, sysset = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2]) +text = sysset.read_text() + +# openApplication's command map keys. +m = re.search(r'function openApplication.*?const commands = \{(.*?)\};', text, re.S) +app_ids = set(re.findall(r'"([a-z0-9-]+)"\s*:', m.group(1))) if m else set() + +# openGnomePanel's allow-list. +m = re.search(r'isGnomePanelAllowed.*?return \[(.*?)\]\.indexOf', text, re.S) +panels = set(re.findall(r'"([a-z-]+)"', m.group(1))) if m else set() + +problems = [] +for page in sorted((qs/'modules').rglob('*.qml')): + t = page.read_text() + for app_id in re.findall(r'openApplication\("([^"]+)"\)', t): + if app_id not in app_ids: + problems.append(f'{page.name}: openApplication("{app_id}") -- not in the command map, so the button does nothing') + for panel in re.findall(r'openGnomePanel\("([a-z-]+)"', t): + if panel not in panels: + problems.append(f'{page.name}: openGnomePanel("{panel}") -- not allow-listed, so the handoff opens nothing') + +if problems: + print(f"settings buttons contract: {len(problems)} dead button(s)") + for p in problems: print(" -", p) + sys.exit(1) +print(f"settings buttons contract: ok ({len(app_ids)} app ids, {len(panels)} panels, every button resolves)") +PY