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
70 lines
4.1 KiB
Markdown
70 lines
4.1 KiB
Markdown
# Settings navigation redesign — 31 flat pages become 15 categories
|
||
|
||
Approved 2026-08-23. The sidebar's 31 entries collapse into 15 categories. Multi-subject
|
||
categories render an Appearance-style tab strip above the page; single-subject categories
|
||
render no strip. Every old page id keeps working forever.
|
||
|
||
## The taxonomy
|
||
|
||
| Category (sidebar) | Tabs (leaf page ids) |
|
||
|---|---|
|
||
| Home | — (`home`) |
|
||
| Appearance | — (`appearance`, keeps its internal tabs) |
|
||
| Desktop & Dock | — (`desktop`) |
|
||
| Displays | — (`displays`) |
|
||
| Sound | — (`sound`, Dictation card moves out) |
|
||
| Notifications & Focus | — (`notifications`) |
|
||
| Input | Keyboard (`shortcuts`), Mouse & Touchpad (`mouse`), Dictation (`dictation`, new) |
|
||
| Network & Sharing | Connections (`connectivity`), Firewall (`firewall`), Sharing (`sharing`), Printers (`printers`) |
|
||
| Home & Phone | — (`home-phone`) |
|
||
| Applications | Applications (`applications`), Gaming (`gaming`), Screen Intelligence (`screen-intelligence`) |
|
||
| Users & Accounts | Users (`users`), Online Accounts (`accounts`) |
|
||
| Privacy & Security | Privacy (`privacy`), SSH Keys (`ssh-keys`) |
|
||
| Power & Lock | — (`power`) |
|
||
| Accessibility | — (`accessibility`) |
|
||
| System | About (`about`), Software Update (`updates`), System Health (`services`), Storage (`storage`), Snapshots (`snapshots`), Containers (`containers`), Date & Time (`datetime`), Region & Language (`region`), Manual (`manual`) |
|
||
|
||
## Architecture
|
||
|
||
`ShellState.settingsPage` keeps holding the **leaf** id, exactly as today. The page loader's
|
||
switch, the ownership contracts, HomePage's direct jumps, and every external caller are
|
||
untouched by the grouping itself. What changes:
|
||
|
||
- **`services/SettingsRoutes.qml` (new singleton)** — the one source of truth for the
|
||
taxonomy: `categories` (page, label, icon, tabs), `resolve(id)` (category id → first
|
||
available leaf; leaf → itself; unknown → home), `categoryOf(leaf)`, `tabsFor(leaf)`
|
||
(availability-filtered), `breadcrumb(leaf)`, `pageAvailable(leaf)` (the podman/snapper
|
||
gating, moved from the sidebar).
|
||
- **`SettingsSidebar.qml`** — destinations come from `SettingsRoutes.categories`; a row
|
||
highlights when it is the category owning the current leaf; clicking requests the
|
||
category id and `ShellState` resolves it.
|
||
- **`ShellState.showSettings`** — the literal allow-list becomes `SettingsRoutes.resolve()`.
|
||
- **`SettingsShell.qml`** — renders a `SettingsTabs` strip between the titlebar and the
|
||
page loader whenever the current leaf's category has more than one available tab.
|
||
Tab click is just `openSettings(leafId)`. Adds the `dictation` case.
|
||
- **`SettingsTabs.qml`** — the strip becomes horizontally scrollable when it overflows
|
||
(System has nine tabs; the window tiles down to 900px).
|
||
- **`SoundPage.qml` / `DictationPage.qml` (new)** — the Dictation card moves to its own
|
||
page under Input, with a labeled handoff noting the microphone is chosen in Sound.
|
||
- **`SettingsSearch.qml`** — dictation `extraEntries` re-route to `dictation`; the search
|
||
result subtitle becomes the `SettingsRoutes` breadcrumb ("System › Storage"). Because
|
||
results carry leaf ids, search now lands on the exact tab with no further work.
|
||
|
||
## Blast radius owned by this change
|
||
|
||
- `scripts/panama-settings-commands` regex-parses the sidebar's destinations array; it
|
||
moves to parsing `SettingsRoutes.qml` and regenerates the Vicinae commands (one per
|
||
leaf; `home` still skipped; `dictation` gained). `scripts/panama-settings-docs` checked
|
||
the same way.
|
||
- Test contracts updated in the same change, not after: `settings-nav-contract`,
|
||
`settings-jump-contract`, `search-routing-contract`, `gnome-handoff-contract`,
|
||
`manual-contract`, `sound-page-contract`, plus any contract found grepping page ids.
|
||
- `modules/settings/README.md` gains the navigation model; manual chapter
|
||
`05-making-it-yours.md` updated where it describes the sidebar.
|
||
|
||
## Non-goals
|
||
|
||
- No per-tab deep links into Appearance's internal tabs from search (unchanged today).
|
||
- No persistence of last-selected tab per category (Appearance already resets; same rule).
|
||
- No renaming of leaf ids, files, or schema groups.
|