Appearance now opens on Themes: light and dark side by side, each remembering its own choice, over galleries of ten shipped themes — Tokyo Moon and Day joined by Moon Rose, Catppuccin, Nord, Gruvbox and Everforest in both modes. A theme is a complete palette: the catalog lives in themes.json, Theme.qml reads every color token from the active record, and one render pipeline carries it to kitty, tmux, btop, GTK, Vicinae, Firefox's chrome, and the lock screen. The Theme editor builds new ones from four wells — wheel, hex, or eyedropper — with derived surfaces, a saturation slider, debounced fine-tune, and effects that save with the theme. Custom edits finally keep GNOME's accent, kitty's border, and hyprlock in sync. Wallpapers can be video: mpvpaper per output, hardware-decoded, muted and looped, supervised and respawned. Panama owns the pausing — games, battery, and a bar pill for right now — because the compositor rebuilds full-screen blur for every frame a video wallpaper draws. The lock screen gets a still frame. Titlebars stop lying. GNOME apps get close-only on your chosen side, the maximize and double-click settings are gone, the Settings window obeys the same rules, and its titlebar can be turned off entirely. Typography becomes five labeled dropdowns instead of a wall of samples. Contracts updated and written throughout (165 now); per the redesign workflow none were executed — the full sweep runs once at the end. Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
133 lines
5.5 KiB
Bash
Executable File
133 lines
5.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# The sidebar search indexes the schema, not the page names.
|
|
#
|
|
# Before this, the field filtered twelve page labels, so "gaps", "wallpaper",
|
|
# and "screenshot" all returned nothing on an app that has all three. That is
|
|
# the single clearest way a settings app feels smaller than it is, and it
|
|
# regresses invisibly: nothing breaks, results just quietly stop appearing.
|
|
#
|
|
# This pins that every schema entry is reachable by its own label, that
|
|
# shortcuts are searchable by what they do, and that results route to a page
|
|
# that actually exists.
|
|
|
|
set -euo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
harness="$repo_dir/config/dot/quickshell/settings-search-harness.qml"
|
|
|
|
fail() {
|
|
printf 'settings search contract: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
qs_for_harness() {
|
|
qs -p "$harness" "$@"
|
|
}
|
|
|
|
cleanup() {
|
|
qs_for_harness kill >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
qs_for_harness --daemonize >/dev/null
|
|
for _ in $(seq 1 40); do
|
|
qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-search-test$' && break
|
|
sleep 0.1
|
|
done
|
|
qs_for_harness ipc show 2>/dev/null | rg -q '^target settings-search-test$' || fail 'test IPC target did not start'
|
|
|
|
find_top() {
|
|
qs_for_harness ipc call settings-search-test find "$1"
|
|
}
|
|
|
|
# ── An empty query is navigation, not a search ───────────────────────────────
|
|
[[ "$(find_top '' | jq -r .count)" == "0" ]] || fail 'an empty query returned results'
|
|
|
|
# ── Real settings are findable by what they are ──────────────────────────────
|
|
while IFS='|' read -r query expect_label expect_page; do
|
|
result="$(find_top "$query")"
|
|
got_label="$(jq -r .top <<<"$result")"
|
|
got_page="$(jq -r .topPage <<<"$result")"
|
|
[[ "$got_label" == "$expect_label" ]] \
|
|
|| fail "searching '$query' put '$got_label' first, expected '$expect_label'"
|
|
[[ "$got_page" == "$expect_page" ]] \
|
|
|| fail "searching '$query' routes to '$got_page', expected '$expect_page'"
|
|
done <<'CASES'
|
|
wallpaper|Wallpaper|appearance
|
|
blur|Blur|appearance
|
|
timezone|Timezone|datetime
|
|
repeat delay|Repeat delay|shortcuts
|
|
system health|System Health|services
|
|
doctor|Copy health report|services
|
|
lock screen background|Lock screen background|appearance
|
|
password field|Password field|appearance
|
|
slideshow|Wallpaper mode|appearance
|
|
shuffle|Shuffle|appearance
|
|
per-display wallpaper|Per-display wallpaper|appearance
|
|
arrange displays|Arrange displays|displays
|
|
monitor position|Monitor position|displays
|
|
primary display|Primary display|displays
|
|
themes|Themes|appearance
|
|
theme editor|Theme editor|appearance
|
|
dark mode|Dark mode|appearance
|
|
catppuccin|Catppuccin|appearance
|
|
gruvbox|Gruvbox|appearance
|
|
video wallpaper|Video wallpaper|appearance
|
|
titlebar|Titlebar on Panama windows|appearance
|
|
CASES
|
|
|
|
! rg -Fq 'Startup & Services' "$repo_dir/config/dot/quickshell/services/SettingsSearch.qml" \
|
|
|| fail 'search index still uses the retired Startup & Services name'
|
|
|
|
# ── Shortcuts are searchable by what they do ─────────────────────────────────
|
|
[[ "$(find_top screenshot | jq -r .topPage)" == "shortcuts" ]] \
|
|
|| fail 'searching a shortcut description did not route to the shortcuts page'
|
|
|
|
# ── Every non-internal schema entry is reachable by its own label ────────────
|
|
# A setting that cannot be found by typing its name is a setting the user
|
|
# cannot discover, which is the failure this whole page structure exists to fix.
|
|
schema="$repo_dir/config/dot/quickshell/config/PreferenceSchema.qml"
|
|
missing=0
|
|
while IFS= read -r label; do
|
|
[[ -n "$label" ]] || continue
|
|
count="$(find_top "$label" | jq -r .count)"
|
|
if [[ "$count" == "0" ]]; then
|
|
printf 'settings search contract: no result for schema label "%s"\n' "$label" >&2
|
|
missing=$((missing + 1))
|
|
fi
|
|
done < <(python3 - "$schema" <<'EXTRACT'
|
|
import re, sys
|
|
# Entries marked `internal` are shell state the user never edits, so they are
|
|
# deliberately excluded from search. Extract only the user-facing labels.
|
|
text = open(sys.argv[1]).read()
|
|
for block in re.findall(r"\{\s*\n\s+key:.*?\n\s{8}\}", text, re.S):
|
|
if "internal: true" in block:
|
|
continue
|
|
match = re.search(r'label: "([^"]+)"', block)
|
|
if match:
|
|
print(match.group(1))
|
|
EXTRACT
|
|
)
|
|
[[ "$missing" -eq 0 ]] || fail "$missing schema label(s) are not findable by search"
|
|
|
|
# ── Results only ever route to leaves the shell can open ─────────────────────
|
|
# A search result carries a leaf id, never a category: it has to land on the
|
|
# exact tab holding the setting, not on whichever tab that category opens
|
|
# first. SettingsRoutes is where the leaves are declared -- a tabless category
|
|
# is one, and so is every tab.
|
|
routes="$repo_dir/config/dot/quickshell/services/SettingsRoutes.qml"
|
|
leaves="$( {
|
|
grep -oE '\{ page: "[a-z-]+", label: "[^"]*", icon: "[^"]*", tabs: \[\] \}' "$routes"
|
|
grep -oE '\{ page: "[a-z-]+", label: "[^"]*" \}' "$routes"
|
|
} | sed -E 's/\{ page: "([a-z-]+)".*/\1/' | sort -u)"
|
|
[[ -n "$leaves" ]] || fail 'no leaf pages could be read from SettingsRoutes, so this proves nothing'
|
|
for query in wallpaper blur timezone screenshot pointer lock volume gaps dictation; do
|
|
page="$(find_top "$query" | jq -r .topPage)"
|
|
grep -qx "$page" <<<"$leaves" || fail "search routed '$query' to '$page', which is not a page anyone can land on"
|
|
done
|
|
|
|
trap - EXIT
|
|
cleanup
|
|
printf 'settings search contract: PASS\n'
|