Fold thirty-one settings pages into fifteen categories with tabs
The sidebar was a flat scan of thirty-one rows; now it reads like a settings app. Multi-subject categories (Input, Network & Sharing, Applications, Users & Accounts, Privacy & Security, System) carry an Appearance-style tab strip above the page, drawn by the shell so the leaf pages themselves are untouched. The taxonomy lives in one new file, services/SettingsRoutes.qml; the sidebar, the strip, route validation, search breadcrumbs, and both generators derive from it. ShellState.settingsPage still holds leaf ids, so every deep link, IPC call, and search result keeps working — and now lands on the exact tab. Dictation moves out of Sound onto its own page under Input, with a handoff back to Sound for the microphone. The strip scrolls when System's nine tabs outgrow a tiled window. All 161 contracts pass. Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -3,23 +3,32 @@
|
||||
# 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
|
||||
# services/SettingsRoutes.qml the taxonomy: which category the page belongs
|
||||
# to, and whether it is a tab inside one or a category
|
||||
# of its own. This is the single source of truth -- the
|
||||
# sidebar, the tab strip, ShellState's route
|
||||
# resolution and the launcher command generator all
|
||||
# derive from it, so a page absent here is a page that
|
||||
# exists nowhere
|
||||
# SettingsShell.qml the case that maps a leaf 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
|
||||
# the .qml file itself
|
||||
#
|
||||
# Nothing at runtime cross-checks the four. This does, statically.
|
||||
# ShellState no longer keeps its own allow-list: it asks SettingsRoutes to
|
||||
# resolve whatever id it is handed. That removed a fifth place to forget, and
|
||||
# this contract pins that it stays removed -- a literal list reappearing there
|
||||
# would silently disagree with the taxonomy again.
|
||||
#
|
||||
# Nothing at runtime cross-checks any of this. 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"
|
||||
routes="$repo_dir/config/dot/quickshell/services/SettingsRoutes.qml"
|
||||
shell_file="$settings_dir/SettingsShell.qml"
|
||||
qmldir="$settings_dir/qmldir"
|
||||
shell_state="$repo_dir/config/dot/quickshell/services/ShellState.qml"
|
||||
@@ -29,17 +38,49 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
for required in "$sidebar" "$shell_file" "$qmldir" "$shell_state"; do
|
||||
for required in "$routes" "$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'
|
||||
# ── Reading the taxonomy ─────────────────────────────────────────────────────
|
||||
# A category line carries an icon; a tab line does not. A category whose tabs
|
||||
# are empty is a leaf itself, which is why the two shapes are read separately.
|
||||
category_ids="$(grep -oE '\{ page: "[a-z-]+", label: "[^"]*", icon:' "$routes" \
|
||||
| sed -E 's/\{ page: "([a-z-]+)".*/\1/')"
|
||||
tabless_ids="$(grep -oE '\{ page: "[a-z-]+", label: "[^"]*", icon: "[^"]*", tabs: \[\] \}' "$routes" \
|
||||
| sed -E 's/\{ page: "([a-z-]+)".*/\1/')"
|
||||
tab_ids="$(grep -oE '\{ page: "[a-z-]+", label: "[^"]*" \}' "$routes" \
|
||||
| sed -E 's/\{ page: "([a-z-]+)".*/\1/')"
|
||||
|
||||
allowed_line="$(grep -m1 'const allowed = \[' "$shell_state")" \
|
||||
|| fail 'could not find the allow-list in ShellState'
|
||||
[[ -n "$category_ids" ]] || fail 'no categories found in SettingsRoutes -- this contract is not reading it correctly'
|
||||
[[ -n "$tab_ids" ]] || fail 'no tabs found in SettingsRoutes -- this contract is not reading it correctly'
|
||||
|
||||
# The leaves: every page a person can actually land on.
|
||||
leaves="$(printf '%s\n%s\n' "$tabless_ids" "$tab_ids" | sed '/^$/d')"
|
||||
|
||||
# ── The taxonomy addresses each leaf exactly once ─────────────────────────────
|
||||
# ShellState.settingsPage holds a leaf id, and the sidebar highlights the
|
||||
# category that owns it. A leaf in two categories makes "which row lights up"
|
||||
# depend on iteration order, and makes the breadcrumb a coin flip.
|
||||
duplicate_tabs="$(sort <<<"$tab_ids" | uniq -d)"
|
||||
[[ -z "$duplicate_tabs" ]] \
|
||||
|| fail "these tab pages appear in more than one category, so the sidebar highlight and the breadcrumb become ambiguous: $(tr '\n' ' ' <<<"$duplicate_tabs")"
|
||||
|
||||
# A category with tabs may share its id with its first tab -- "applications",
|
||||
# "users" and "privacy" do, and both readings land on the same category. A
|
||||
# category *without* tabs is a leaf, so sharing an id with a tab elsewhere
|
||||
# would put one page in two places.
|
||||
while read -r page; do
|
||||
[[ -n "$page" ]] || continue
|
||||
grep -qx "$page" <<<"$tab_ids" \
|
||||
&& fail "\"$page\" is a category with no tabs and also a tab of another category, so the same page id names two different places"
|
||||
done <<<"$tabless_ids"
|
||||
|
||||
duplicate_categories="$(sort <<<"$category_ids" | uniq -d)"
|
||||
[[ -z "$duplicate_categories" ]] \
|
||||
|| fail "these category ids are declared twice: $(tr '\n' ' ' <<<"$duplicate_categories")"
|
||||
|
||||
# ── Every leaf resolves everywhere ───────────────────────────────────────────
|
||||
while read -r page; do
|
||||
[[ -n "$page" ]] || continue
|
||||
|
||||
@@ -47,12 +88,15 @@ while read -r page; do
|
||||
# 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"
|
||||
|| fail "SettingsRoutes offers \"$page\" but SettingsShell has no case for it, so opening it shows Home"
|
||||
fi
|
||||
done <<<"$leaves"
|
||||
|
||||
grep -qF "\"$page\"" <<<"$allowed_line" \
|
||||
|| fail "\"$page\" is missing from ShellState's allow-list, so openSettings(\"$page\") silently redirects to Home"
|
||||
done <<<"$pages"
|
||||
# ── ShellState defers to the taxonomy instead of restating it ────────────────
|
||||
grep -q 'SettingsRoutes\.resolve(' "$shell_state" \
|
||||
|| fail 'showSettings() does not route through SettingsRoutes.resolve, so a category id or an unknown page has no defined destination'
|
||||
grep -q 'const allowed = \[' "$shell_state" \
|
||||
&& fail 'ShellState has grown a literal allow-list again -- it will drift from SettingsRoutes, and a page missing from it silently redirects to Home'
|
||||
|
||||
# ── Every routed component is declared and registered ────────────────────────
|
||||
# The case arms name a Component id; each must have a declaration, and the type
|
||||
@@ -86,4 +130,5 @@ while read -r page_file; do
|
||||
|| 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'
|
||||
printf 'settings nav contract: PASS (%d categories, %d leaves)\n' \
|
||||
"$(grep -c . <<<"$category_ids")" "$(grep -c . <<<"$leaves")"
|
||||
|
||||
Reference in New Issue
Block a user