Finish the wonderland: System told truthfully, in eight tabs instead of ten

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-24 23:31:52 -04:00
parent 9ffaf45a4d
commit be0e55214b
57 changed files with 5040 additions and 925 deletions
@@ -7,12 +7,12 @@
// So the manual is prose, in chapters, rendered here rather than opened in a
// browser. Keeping it in Settings means it is reachable from the same place
// as everything else it talks about, and a link to a settings page can just
// be a settings page.
// be a settings page -- see onLinkActivated below, where that stopped being a
// figure of speech.
//
// The chapters live beside the shell in manual/, not at the repository root.
// They are read at runtime through Quickshell.shellDir, which resolves whether
// or not the repository is where it usually is; a path walked upward out of
// the shell directory does not.
// The chapter list and the chapter titles both come from ManualChapters, which
// reads each file's own first heading. This page used to carry a hand-written
// copy of those titles under a comment claiming they were read from the files.
//
// One Text per chapter, never the whole manual at once: Text has an implicit
// texture size limit, and a document long enough to hit it fails by going
@@ -30,16 +30,7 @@ SettingsPage {
title: "Manual"
lede: "How this desktop works, for the person using it."
// Chapter files are numbered so their order is their filename. The title
// shown here is the first heading of each, read from the file, so a
// chapter cannot be renamed in one place and not the other.
readonly property var chapters: [
{ file: "01-coming-from-another-desktop.md", label: "Coming from another desktop" },
{ file: "02-the-keyboard.md", label: "The keyboard" },
{ file: "03-windows-and-workspaces.md", label: "Windows and workspaces" },
{ file: "04-when-something-breaks.md", label: "When something breaks" },
{ file: "05-making-it-yours.md", label: "Making it yours" }
]
readonly property var chapters: contents.titled
property int current: 0
@@ -56,10 +47,12 @@ SettingsPage {
}
}
ManualChapters { id: contents }
SettingsTabs {
tabs: root.chapters.map((chapter, index) => ({
value: String(index),
label: chapter.label
label: chapter.title
}))
current: String(root.current)
onSelected: value => root.current = parseInt(value, 10)
@@ -90,7 +83,30 @@ SettingsPage {
// size; a document read once needs to be easy on the first pass.
lineHeight: 1.35
onLinkActivated: link => Qt.openUrlExternally(link)
// A link that names a settings page opens that page instead of a
// browser, which is the whole reason the manual lives inside
// Settings. Anything else -- a project URL, a wiki -- still leaves
// the desktop.
//
// The scheme is Panama's own, so a chapter can never accidentally
// hand an http link to openSettings, and an unknown page cannot be
// smuggled in either: SettingsRoutes.resolve turns anything it does
// not recognise into Home rather than a blank loader. A fragment,
// where there is one, is the section within the page.
onLinkActivated: link => {
const target = String(link ?? "");
const scheme = "panama://settings/";
if (target.indexOf(scheme) !== 0) {
Qt.openUrlExternally(target);
return;
}
const rest = target.slice(scheme.length);
const hash = rest.indexOf("#");
if (hash < 0)
ShellState.openSettings(rest);
else
ShellState.openSettingsSection(rest.slice(0, hash), rest.slice(hash + 1));
}
}
}
}