#!/usr/bin/env bash set -euo pipefail repo_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" settings="$repo_dir/config/dot/quickshell/modules/settings" theme="$repo_dir/config/dot/quickshell/config/Theme.qml" scheme="$repo_dir/config/dot/quickshell/services/ColorScheme.qml" search="$repo_dir/config/dot/quickshell/services/SettingsSearch.qml" fail() { printf 'accent controls contract: %s\n' "$1" >&2 exit 1 } for component in AccentPicker AccentEditor ThemeProfilePicker; do [[ -f "$settings/$component.qml" ]] || fail "$component is missing" rg -Fq "$component 1.0 $component.qml" "$settings/qmldir" \ || fail "$component is not registered in the settings module" done for label in \ 'Primary hue' 'Primary saturation' 'Primary value' \ 'Secondary hue' 'Secondary saturation' 'Secondary value'; do rg -Fq "$label" "$settings/AccentEditor.qml" \ || fail "HSV control is missing the visible label $label" done rg -Fq 'ValueSlider {' "$settings/AccentEditor.qml" \ || fail 'advanced accents do not reuse ValueSlider' rg -Fq 'Accessible.role: Accessible.Slider' "$settings/AccentEditor.qml" \ || fail 'HSV controls do not expose slider semantics' rg -Fq 'Accessible.name: root.label' "$settings/AccentEditor.qml" \ || fail 'HSV controls are not programmatically labelled' rg -Fq 'Accessible.description:' "$settings/AccentEditor.qml" \ || fail 'HSV controls do not expose their numeric value as a non-hue signal' rg -Fq 'activeFocusOnTab: true' "$settings/AccentEditor.qml" \ || fail 'HSV controls cannot receive keyboard focus' rg -Fq 'Keys.onPressed:' "$settings/AccentEditor.qml" \ || fail 'HSV controls cannot be adjusted from the keyboard' rg -Fq 'activeFocus ? Theme.accentSecondary' "$settings/AccentEditor.qml" \ || fail 'HSV controls have no visible keyboard focus treatment' rg -Fq '["hyprpicker", "--format=hex", "--lowercase-hex", "--quiet", "--no-fancy"]' \ "$settings/AccentEditor.qml" \ || fail 'screen picking does not use the validated hyprpicker hex invocation' for target in 'Pick primary from screen' 'Pick secondary from screen'; do rg -Fq "$target" "$settings/AccentEditor.qml" \ || fail "screen picker action is missing $target" done rg -Fq 'model: Object.keys(Theme.accents)' "$settings/AccentPicker.qml" \ || fail 'curated swatches are no longer the fast path' rg -Fq 'ThemeProfiles.useCuratedAccent(entry.modelData)' "$settings/AccentPicker.qml" \ || fail 'curated swatches do not select a profile-backed accent' rg -Fq 'readonly property string current: ThemeProfiles.activeAccentName' "$settings/AccentPicker.qml" \ || fail 'swatch selection does not follow the active profile' rg -Fq 'Accessible.name: entry.pair.label + " accent"' "$settings/AccentPicker.qml" \ || fail 'swatches rely on hue without a programmatic name' rg -Fq 'activeFocusOnTab: true' "$settings/AccentPicker.qml" \ || fail 'swatches cannot receive keyboard focus' rg -Fq 'Keys.onReturnPressed:' "$settings/AccentPicker.qml" \ || fail 'swatches cannot be selected from the keyboard' rg -Fq 'model: ThemeProfiles.profiles' "$settings/ThemeProfilePicker.qml" \ || fail 'profile picker does not show shipped and saved profiles' rg -Fq 'ThemeProfiles.selectProfile(' "$settings/ThemeProfilePicker.qml" \ || fail 'profile switching is not wired' rg -Fq 'ThemeProfiles.saveProfile(' "$settings/ThemeProfilePicker.qml" \ || fail 'profile saving is not wired' rg -Fq 'maximumLength: 40' "$settings/ThemeProfilePicker.qml" \ || fail 'profile names are not visibly bounded to the model limit' rg -Fq 'ThemeProfiles.deleteProfile(' "$settings/ThemeProfilePicker.qml" \ || fail 'custom profile deletion is not wired' rg -Fq 'activeFocusOnTab:' "$settings/ThemeProfilePicker.qml" \ || fail 'profile actions cannot receive keyboard focus' rg -Fq 'Keys.onReturnPressed:' "$settings/ThemeProfilePicker.qml" \ || fail 'profile actions cannot be triggered from the keyboard' for component in ThemeProfilePicker AccentPicker AccentEditor; do rg -Fq "$component {" "$settings/AppearancePage.qml" \ || fail "Appearance does not include $component" done rg -Fq 'ThemeProfiles.activeProfile' "$theme" \ || fail 'Theme roles do not react to the selected profile' rg -Fq 'root.hyprColor(Theme.accent)' "$scheme" \ || fail 'the focused border start does not follow Theme.accent' rg -Fq 'root.hyprColor(Theme.accentSecondary)' "$scheme" \ || fail 'the focused border end does not follow Theme.accentSecondary' for term in 'Theme profiles' 'Advanced accent' 'Pick colour from screen'; do rg -Fq "$term" "$search" || fail "Settings search is missing $term" done if rg -n 'NumberAnimation|ColorAnimation|SequentialAnimation|ParallelAnimation|loops:[[:space:]]*Animation\.Infinite' \ "$settings/AccentEditor.qml" "$settings/AccentPicker.qml" "$settings/ThemeProfilePicker.qml"; then fail 'theme controls introduce continuously repainting or decorative animation' fi state_home="$(mktemp -d /tmp/panama-accent-controls.XXXXXX)" harness="$repo_dir/config/dot/quickshell/accent-controls-harness.qml" qs_for_test() { XDG_CONFIG_HOME="$state_home/config" XDG_STATE_HOME="$state_home/state" \ QS_DISABLE_CRASH_HANDLER=1 qs -p "$harness" "$@" } cleanup() { qs_for_test kill >/dev/null 2>&1 || true rm -rf "$state_home" } trap cleanup EXIT qs_for_test --daemonize >/dev/null for _ in $(seq 1 40); do qs_for_test ipc show 2>/dev/null | rg -q '^target accent-controls-test$' && break sleep 0.1 done qs_for_test ipc show 2>/dev/null | rg -q '^target accent-controls-test$' \ || fail 'headless AccentEditor harness did not start' before="$(qs_for_test ipc call accent-controls-test status)" jq -e '.id == "moon" and .shipped == true' <<<"$before" >/dev/null \ || fail 'headless editor did not begin on Moon' after="$(qs_for_test ipc call accent-controls-test adjust primary h 0)" jq -e '.shipped == false and .accent != "#82aaff" and .secondary == "#b172b0"' \ <<<"$after" >/dev/null \ || fail 'an HSV adjustment did not create a custom profile with the unchanged secondary colour' trap - EXIT cleanup printf 'accent controls contract: PASS\n'