diff --git a/config/dot/quickshell/modules/settings/ApplicationsPage.qml b/config/dot/quickshell/modules/settings/ApplicationsPage.qml index 0dea3cb..b86b9f6 100644 --- a/config/dot/quickshell/modules/settings/ApplicationsPage.qml +++ b/config/dot/quickshell/modules/settings/ApplicationsPage.qml @@ -142,6 +142,40 @@ SettingsPage { } } + // GNOME's Search panel, answered honestly. + // + // It configures which applications provide results in gnome-shell's + // overview and which folders are indexed. gnome-shell does not run here, + // so those settings would do nothing. Under Panama searching is the + // launcher's job, and Vicinae carries its own preferences -- reimplementing + // them here would give two places to change one thing. + SettingsCard { + title: "Search" + subtitle: "Applications, files, the calculator, clipboard history, emoji, and open windows are all searched from the launcher." + + TextRow { + label: "Launcher" + detail: SystemSettings.vicinaeActive + ? "Running as a user service" + : "Not running — Super+Shift+R opens the fallback launcher" + value: "Vicinae" + } + + TextRow { + label: "Open search" + detail: "Three keys open it, because Super+A and Super+R were GNOME's app grid and run dialog" + value: "Super+Space" + } + + ActionRow { + label: "Change these shortcuts" + detail: "Every launcher chord is rebindable, including clipboard history and emoji" + action: "Open keyboard" + divider: false + onTriggered: ShellState.openSettings("shortcuts") + } + } + SettingsCard { title: "User autostart" subtitle: "These desktop entries live in your user configuration. Select a row to toggle it." diff --git a/tests/quickshell/settings-nav-contract.sh b/tests/quickshell/settings-nav-contract.sh new file mode 100755 index 0000000..1eaff61 --- /dev/null +++ b/tests/quickshell/settings-nav-contract.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +# Adding a settings page means editing four separate files, and missing one +# fails quietly rather than loudly: +# +# SettingsSidebar.qml the row you click +# SettingsShell.qml the case that maps that row to a component, AND the +# Component declaration itself +# ShellState.qml the allow-list openSettings() checks -- a page missing +# here silently redirects to Home, so a deep link or a +# search result lands on the wrong page with no error +# modules/settings/qmldir the component registration -- without it the page +# is "not a type" and the whole settings window fails +# to load, taking every other page with it +# +# Nothing at runtime cross-checks the four. This does, statically. + +set -euo pipefail + +repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +settings_dir="$repo_dir/config/dot/quickshell/modules/settings" +sidebar="$settings_dir/SettingsSidebar.qml" +shell_file="$settings_dir/SettingsShell.qml" +qmldir="$settings_dir/qmldir" +shell_state="$repo_dir/config/dot/quickshell/services/ShellState.qml" + +fail() { + printf 'settings nav contract: %s\n' "$1" >&2 + exit 1 +} + +for required in "$sidebar" "$shell_file" "$qmldir" "$shell_state"; do + [[ -r "$required" ]] || fail "cannot read $required" +done + +# ── Every sidebar row resolves everywhere ──────────────────────────────────── +pages="$(grep -oE '\{ page: "[a-z-]+"' "$sidebar" | sed 's/.*"\(.*\)"/\1/')" +[[ -n "$pages" ]] || fail 'no pages found in the sidebar -- this contract is not reading it correctly' + +allowed_line="$(grep -m1 'const allowed = \[' "$shell_state")" \ + || fail 'could not find the allow-list in ShellState' + +while read -r page; do + [[ -n "$page" ]] || continue + + # Home is the switch's default arm rather than a case, since it is also + # where an unknown page falls back to. + if [[ "$page" != "home" ]]; then + grep -qE "case \"$page\": return [a-zA-Z]+;" "$shell_file" \ + || fail "the sidebar offers \"$page\" but SettingsShell has no case for it, so clicking it shows Home" + fi + + grep -qF "\"$page\"" <<<"$allowed_line" \ + || fail "\"$page\" is missing from ShellState's allow-list, so openSettings(\"$page\") silently redirects to Home" +done <<<"$pages" + +# ── Every routed component is declared and registered ──────────────────────── +# The case arms name a Component id; each must have a declaration, and the type +# it instantiates must appear in qmldir. +while read -r component; do + [[ -n "$component" ]] || continue + + declaration="$(grep -oE "Component \{ id: $component; [A-Za-z]+ \{\} \}" "$shell_file")" \ + || fail "SettingsShell routes to \"$component\" but never declares it" + + type_name="$(sed -E 's/.*; ([A-Za-z]+) \{\} \}/\1/' <<<"$declaration")" + grep -qE "^$type_name [0-9.]+ $type_name\.qml$" "$qmldir" \ + || fail "$type_name is not registered in modules/settings/qmldir -- it will fail to load as \"not a type\", and the whole settings window fails with it" + + [[ -r "$settings_dir/$type_name.qml" ]] \ + || fail "$type_name is registered in qmldir but $type_name.qml does not exist" +done < <({ + grep -oE 'case "[a-z-]+": return [a-zA-Z]+;' "$shell_file" | sed -E 's/.*return ([a-zA-Z]+);/\1/' + grep -oE 'default: return [a-zA-Z]+;' "$shell_file" | sed -E 's/.*return ([a-zA-Z]+);/\1/' +} | sort -u) + +# ── Every page file is reachable ───────────────────────────────────────────── +# A page nobody can navigate to is dead code that still has to compile. The +# scaffold SettingsPage.qml is the one file here that is a base class rather +# than a page. +while read -r page_file; do + type_name="$(basename "$page_file" .qml)" + [[ "$type_name" == "SettingsPage" ]] && continue + + grep -qE "; $type_name \{\} \}" "$shell_file" \ + || fail "$type_name.qml exists but nothing in SettingsShell instantiates it" +done < <(find "$settings_dir" -maxdepth 1 -name '*Page.qml') + +printf 'settings nav contract: PASS\n'