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
112 lines
5.8 KiB
Bash
Executable File
112 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# The manual.
|
|
#
|
|
# docs/ in this repository is engineering artifacts: design specs, plans, an
|
|
# upstream ledger. None of it is written for the person using the desktop, and
|
|
# that person is the one with questions. The manual is the answer, and it is
|
|
# rendered inside Settings so a chapter can point at a settings page and have
|
|
# that mean something.
|
|
#
|
|
# What must hold:
|
|
#
|
|
# 1. Every chapter the page lists exists, and every chapter file is listed.
|
|
# A renamed file shows an error card in place of a chapter, which looks
|
|
# like the manual is broken rather than like somebody moved a file.
|
|
# 2. Chapters render one at a time. Text has an implicit texture size limit,
|
|
# and a document long enough to hit it goes blank rather than complaining.
|
|
# 3. Links leave the desktop rather than doing nothing.
|
|
# 4. The page is registered everywhere a settings page has to be, or it
|
|
# silently redirects to Home.
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
manual_dir="$repo_dir/config/dot/quickshell/manual"
|
|
page="$repo_dir/config/dot/quickshell/modules/settings/ManualPage.qml"
|
|
routes="$repo_dir/config/dot/quickshell/services/SettingsRoutes.qml"
|
|
shell_ui="$repo_dir/config/dot/quickshell/modules/settings/SettingsShell.qml"
|
|
qmldir="$repo_dir/config/dot/quickshell/modules/settings/qmldir"
|
|
state="$repo_dir/config/dot/quickshell/services/ShellState.qml"
|
|
search="$repo_dir/config/dot/quickshell/services/SettingsSearch.qml"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
|
|
[[ -d "$manual_dir" ]] || { printf 'manual contract: %s is missing\n' "$manual_dir" >&2; exit 1; }
|
|
|
|
# ── 1. The chapters on disk and the chapters listed are the same set ─────────
|
|
|
|
mapfile -t on_disk < <(find "$manual_dir" -maxdepth 1 -name '*.md' -printf '%f\n' | sort)
|
|
(( ${#on_disk[@]} > 0 )) || note 'the manual has no chapters'
|
|
|
|
mapfile -t listed < <(grep -oE 'file: "[^"]+\.md"' "$page" | sed 's/file: "//; s/"//' | sort)
|
|
(( ${#listed[@]} > 0 )) || note 'the manual page lists no chapters'
|
|
|
|
for file in "${on_disk[@]}"; do
|
|
printf '%s\n' "${listed[@]}" | grep -qx "$file" \
|
|
|| note "$file exists but the manual page never shows it"
|
|
done
|
|
for file in "${listed[@]}"; do
|
|
[[ -r "$manual_dir/$file" ]] \
|
|
|| note "the manual page lists $file, which does not exist, so that chapter renders an error"
|
|
done
|
|
|
|
# A chapter that is only a heading is a chapter somebody forgot to write.
|
|
for file in "${on_disk[@]}"; do
|
|
lines="$(grep -c . "$manual_dir/$file" || true)"
|
|
(( lines > 10 )) || note "$file has $lines lines; it reads as unfinished"
|
|
head -1 "$manual_dir/$file" | grep -q '^# ' \
|
|
|| note "$file does not begin with a heading, so it has no title of its own"
|
|
done
|
|
|
|
# ── 2 & 3. How it renders ────────────────────────────────────────────────────
|
|
|
|
grep -q 'textFormat: Text.MarkdownText' "$page" \
|
|
|| note 'chapters are not rendered as markdown, so the source appears verbatim'
|
|
grep -q 'root.chapters\[root.current\]' "$page" \
|
|
|| note 'the page does not render one chapter at a time; a single long Text goes blank rather than erroring'
|
|
grep -q 'onLinkActivated' "$page" \
|
|
|| note 'links in the manual do nothing when clicked'
|
|
grep -q 'onLoadFailed' "$page" \
|
|
|| note 'a chapter that cannot be read fails silently instead of saying so'
|
|
|
|
# The chapters are reached through the shell directory, not by walking upward
|
|
# out of it: that path is only correct when the repository is where it usually
|
|
# is, and the shell directory is a symlink.
|
|
grep -q 'Quickshell.shellDir + "/manual/"' "$page" \
|
|
|| note 'the manual is not located through the shell directory, so it would break on a clone elsewhere'
|
|
grep -q '\.\./\.\./\.\.' "$page" \
|
|
&& note 'the manual path walks upward out of the shell directory, which is only correct by accident'
|
|
|
|
# ── 4. Registered everywhere a settings page has to be ───────────────────────
|
|
# The manual is a tab of the System category rather than a sidebar row of its
|
|
# own -- it is reference material, not a control surface, and it belongs beside
|
|
# About for the same reason. SettingsRoutes is what makes it reachable at all:
|
|
# a leaf missing from the taxonomy cannot be opened, searched, or linked to.
|
|
|
|
grep -q '{ page: "manual", label: ' "$routes" || note 'the manual is not a leaf in SettingsRoutes, so nothing can navigate to it'
|
|
python3 - "$routes" <<'PY' || note 'the manual is no longer a tab of the System category, so it has drifted out of the group it belongs to'
|
|
import re, sys
|
|
|
|
text = open(sys.argv[1], encoding="utf-8").read()
|
|
block = re.search(r'\{ page: "system",.*?tabs: \[(.*?)\] \}', text, re.S)
|
|
raise SystemExit(0 if block and '{ page: "manual"' in block.group(1) else 1)
|
|
PY
|
|
grep -q 'case "manual": return manualPage;' "$shell_ui" || note 'SettingsShell does not route to the manual'
|
|
grep -q 'Component { id: manualPage; ManualPage {} }' "$shell_ui" || note 'SettingsShell never declares the manual component'
|
|
grep -q '^ManualPage 1.0 ManualPage.qml$' "$qmldir" || note 'ManualPage is not registered in the settings qmldir'
|
|
# ShellState keeps no page list of its own any more; it resolves whatever it is
|
|
# handed through SettingsRoutes. That is what makes the taxonomy check above
|
|
# sufficient, so it is worth pinning that it stays that way.
|
|
grep -q 'SettingsRoutes.resolve(' "$state" || note 'ShellState does not resolve pages through SettingsRoutes, so openSettings("manual") has no defined destination'
|
|
grep -q 'page: "manual"' "$search" || note 'the manual is not searchable from the settings search box'
|
|
|
|
if (( ${#findings[@]} > 0 )); then
|
|
printf 'manual contract: %d finding(s)\n' "${#findings[@]}" >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'manual contract: PASS (%d chapters)\n' "${#on_disk[@]}"
|