Files
Panama/tests/quickshell/manual-contract
T
Gabriel Brown 202b5b89ac Write the manual for the person using the desktop
docs/ is engineering artifacts -- design specs, plans, an upstream
ledger -- and none of it is written for the person who has to live
here. Five chapters that are: coming from another desktop, the
keyboard, windows and workspaces, when something breaks, and making it
yours.

Rendered inside Settings rather than opened in a browser, so a chapter
that says "the Displays page reverts after fifteen seconds" is one
click from the Displays page. Qt's markdown renderer does the work;
one chapter per Text, because Text has an implicit texture size limit
and a document long enough to hit it goes blank rather than
complaining.

The chapters live beside the shell in manual/ rather than at the
repository root, which departs from the plan. The reason is the path:
the shell finds them through Quickshell.shellDir, which is correct
wherever the repository is, whereas walking upward out of the shell
directory is only correct by accident. The contract fails that pattern
if it comes back.

The contract also pins the set both ways -- every chapter listed
exists, every chapter that exists is listed -- because a renamed file
shows an error card where a chapter should be, which reads as a broken
manual rather than as a moved file.
2026-08-22 01:11:46 -04:00

98 lines
4.9 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"
sidebar="$repo_dir/config/dot/quickshell/modules/settings/SettingsSidebar.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 in all five places ─────────────────────────────────────────
grep -q '{ page: "manual"' "$sidebar" || note 'the manual has no sidebar entry'
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'
grep -q '"manual"' "$state" || note 'the manual is not in the allowed settings pages, so openSettings would redirect to Home'
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[@]}"