Files
Panama/docs/superpowers/specs/2026-08-24-system-redesign.md
T

144 lines
11 KiB
Markdown

# System redesign — the finale, 10 tabs become 8
Approved mock: `home-mocks/system.html` (scratchpad, :8642). Spec wins over mock on conflict.
Storage, Snapshots, Containers keep their shipped designs (Containers gets the root-type fix
only). TEST GRANT ACTIVE: agents may RUN contracts related to their work as they build; the
full suite stays the orchestrator's call.
## Consolidation (pinned)
System tabs become: About · Software Update · System Health (`services`) · Storage ·
Snapshots · Containers · **Date, Time & Region** (`datetime`) · Sync & Backup. The `region`
tab retires (retired-map → `datetime`). The `manual` tab retires as a TAB but `manual` stays
a routable leaf (opened from About's Manual card and existing deep links) — B determines the
mechanism (hidden-leaf list or equivalent) and C reconciles `settings-nav-contract`'s
category/leaf counts.
## Bug kills (all in scope)
`SystemSettings.quickshellVersion` hardcoded "0.3.0" → read `qs --version`; the auto-download
switch nested in the Firmware card; Health's target-less "Open Software Update" actions (add
`target: "updates"` AND extend `Health.settingsTargets` in the same change — adding one
without the other invalidates whole snapshots); `CHECK_TITLES` missing `panama.updates`
(KeyError on timeout); the `SettingsShell.healthDiagnostics` TypeError (guard the binding on
`typeof item.uiDiagnostics === "function"`); RegionPage's stale "Open appearance" handoff;
`panama-updates`' stale dnf-automatic comment; the backup **color gap**
(`layoutFromStoredDisplays` + `layoutsEqual` extended to `vrrMode`, `colorProfile`,
`bitdepth`, `sdrBrightness`, `sdrSaturation`, `mirrorOf` — optional/back-compat like
`Displays.isPersistedLayoutEntry`); Restore without confirmation; stray `settings.json.*`
temp files (clean once, and if the writer can leak them, fix the leak); dead code
(`Containers.wouldExpose`, `SettingsSync.left`, `SettingsBackup.lastAction`,
`HealthPage.statusLabel`; `DateTime.localTime/universalTime/rtcTime` become USED by the
manual-set flow rather than deleted).
## A — services & scripts (pinned APIs)
- `scripts/panama-about`: rows gain `Firmware` (DMI `bios_version` + `bios_date`, bootctl
only as fallback — it reports "n/a" here) and `Secure Boot` (mokutil, absent-tolerant); a
`Panama` row (`git describe --tags --always --dirty` + `%cr`, absent-tolerant outside a
checkout), placed right after `Operating system`. `SystemSettings.quickshellVersion` now
parsed from `qs --version`, kept as a one-shot Process started from `refresh()` and
guarded by `quickshellVersionRead` — so a plain binding on `quickshellVersion` is enough
and B needs no extra call. The literal stays as the fallback only.
- `scripts/panama-updates`: `changelog <source> <name>``{source, name, kind, text, error}`
where `kind` is `advisory` | `changelog` | `none` (dnf: `dnf5 advisory info --json
--updates --contains-pkgs=` first, else `dnf5 changelog --upgrades` with dnf's two header
lines stripped; flatpak: `remote-info --cached --log` History, which Flathub does not
populate, so in practice the honest "publishes no release notes"; firmware: the fwupd
release notes cached by `check`). Names constrained by `NAME_PATTERN` before argv.
`check` gains per-item `bytes` and per-source `downloadBytes`, present only when EVERY
item in that source was priced (dnf from `dnf5 repoquery --queryformat %{downloadsize}`,
flatpak parsed from its rendered `download-size` column — its `--json` omits the column).
`apply flatpak <app-id>` appends one ID, checked against the last scan. Stale
dnf-automatic comments fixed; `DNF_TIMER` named. `services/Updates.qml`:
`changelogFor(source, name)` → record or `null`, cached by `source/name`, serial queue,
reactive via `changelogRevision` (read it in the binding, DesktopPreferences.get pattern);
`applyFlatpakApp(id)`; `downloadBytes` + `downloadSize` (string) + `sourceDownloadSize(s)`
+ `formatBytes(n)`; `loadingChangelog`.
- `scripts/panama-doctor`: `CHECK_TITLES["panama.updates"]`; updates actions get
`target: "updates"`; snapshot gains `repairCommand` (a joined argv string) on the five ids
in REPAIR_COMMANDS only — the three in-process repairs have no command line and claim
none; new `panama-doctor check <id>` verb printing a FULL snapshot envelope holding that
one check (same schema, same summary arithmetic, so callers reuse `validSnapshot`).
`services/Health.qml`: `settingsTargets` gains "updates"; `refreshCheck(id)` (splices one
validated row in, recomputes the summary, drops a reply the newer full scan superseded)
with `refreshingId` / `refreshingCheck`; `saveReport(path)` via `tee` (default
`defaultReportPath` = `~/panama-health-report.txt`) reporting through `lastSaveResult`,
and `reportText()` for the plain-text body; `safeCheck`/`validCheck` carry `repairCommand`.
- `services/DateTime.qml`: `setTime(iso)` via `timedatectl set-time` (validated against
`timePattern` = `YYYY-MM-DD HH:MM[:SS]` AND round-tripped through Date, refused while NTP
is on AND while `statusRead` is still false — "not looked yet" is not "off"); `localTime`
/ `universalTime` / `rtcTime` wired from `TimeUSec`/`RTCTimeUSec`, advanced locally once a
second while `DateTime.tracking` is true (B sets it while the Clock card is on screen; no
process per tick).
- `scripts/panama-locale` + `services/SystemLocale.qml`: `get [cat]` / `set <cat> <locale|"">`
/ `categories` / `overrides` (one JSON object for all five) — `LC_TIME`, `LC_NUMERIC`,
`LC_MONETARY`, `LC_MEASUREMENT`, `LC_PAPER`. "Match language" is `""` and is implemented by
re-issuing `localectl set-locale` with every OTHER assignment, since localectl replaces
locale.conf with exactly what it is given. Service exposes `categories`,
`categoryValue(cat)` (`""` = match language), `categoryLabel(cat)`, `setCategory(cat,
locale)`, `categoryRevision` (read it in bindings), `busy`, and the pendingRestart
discipline.
- `scripts/panama-settings-backup`: `create [name] [homeState]` (label sanitized by
`LABEL_RE`, stored INSIDE the envelope — the filename stays the timestamp SNAPSHOT_RE
pins, since that is what ordering, pruning and restore confinement rely on), `delete
<name>` (through `snapshot_source`, the same confinement gate restore uses), list gains
`bytes` and `label`. Stale-temp sweep extended to Quickshell's QSaveFile leftovers
(`settings.json.XXXXXX`, no leading dot) with a one-hour age guard so an in-flight write
is never destroyed. `services/SettingsBackup.qml`: `create(name)`, `deleteBackup(name)`,
dead `lastAction` removed, the color-gap fix per above (`storedDisplayFields`,
`validStoredField`, both used by `layoutFromStoredDisplays` and `layoutsEqual`).
- `services/SettingsSync.qml` + helper: preview output gains `changes: [{key, from, to}]`
with both values rendered to text by the helper (`render()`, `VALUE_LIMIT` 120), capped at
`CHANGE_LIMIT` 40 with a `changeCount` for what is not shown; the import still applies
everything. Service gains `changeCount`; dead `left` removed.
- `modules/settings/SettingsShell.qml`: the healthDiagnostics guard (A owns this single
edit — B is told hands-off that binding).
## B — UI
`AboutPage.qml` showpiece per mock (hero, truthful versions block, Hardware card with
GraphicsDevices joined, Device-name → Sharing row, Manual card listing chapters with titles
READ FROM THE FILES (first heading), Read → `openSettings("manual")` + section); Design
principles card dropped (the manual carries the philosophy). `ManualPage.qml`: chapter titles
from file first-headings (make the old comment true); `onLinkActivated` routes
`panama://settings/<page>` (or equivalent scheme) links through `ShellState.openSettings`,
everything else external; keep the tabs/reader shape. `UpdatesPage.qml`: restructure per
mock (headline; System packages with changelog expanders + download size; Automatic card
with BOTH switches; Applications & firmware rows with per-app flatpak updates when present;
history kept). `DateTimePage.qml` becomes the merged tab (Clock card with manual-set
revealed when NTP off; Timezone; Language & formats card — language picker, four category
dropdowns [Match language + installed locales], first-day-of-week fact row, live preview
grid from `Qt.locale()` for the *chosen* format locales); `RegionPage.qml` DELETED (retired
route). `HealthPage.qml`: repair rows show `repairCommand` in the detail before running,
per-check Re-check button (`refreshCheck`), Save-report row, Color-profiles handoff row
REMOVED (Digital wellbeing stays), the updates action now navigating. `SyncPage.qml`:
preview diff list from `changes` (mono-ish tabular rows, del/add tones), Restore + Reset
two-stage confirms (the house danger pattern), backup rows with name field on create +
size + Delete…, reset subtitle mentions themes. `ContainersPage.qml`: root becomes
`SettingsPage` per convention (logs drill-in preserved). `SettingsRoutes.qml`: the 8-tab
strip + region retired + manual leaf mechanism (KEEP the categories array literal-shaped).
## C — periphery
Search: hostname/kernel/model/memory/uptime/serial → about; export settings/import
settings → sync; currency/formats/measurement/paper/first day → datetime; region retired
routing verified; manual entries keep working (leaf). Contracts: `settings-nav-contract`
(counts + retired map), `settings-pages-contract` (routing list: add updates/containers/
datetime, drop region), `updates-contract` (changelog verb read-only pins; per-app flatpak
apply argv), health contracts (target "updates" validity BOTH sides, repairCommand shown
before run, single-check verb full-shape, KeyError fixture), `settings-backup-contract` +
`-live` (extended fields round-trip — a stored record with color fields restores them and
`layoutsEqual` detects color-only drift; delete confinement; name sanitation),
`settings-sync-contract` (changes shape, no secrets in stringified values), `manual-contract`
(titles-from-files, in-app link routing), `gnome-handoff-contract` (region handoffs gone —
consider OWNED), NEW `system-pages-contract` if the pins don't fit existing files (C's
judgment; keep count growth honest). Backlog Phase 15. README count line. Docs/commands
regen at the end. RUN the contracts you write/touch as you go (grant active); report results.
Hard rules still: no live mutations of system state (no dnf/flatpak installs, no
timedatectl/localectl writes, no backup restores against the real store — the
scratch-daemonizing harness pattern and hermetic stubs are the sanctioned test paths, and
the grant means you may RUN those freely). Valid QML at every save. B programs against A's
pinned APIs; A updates this spec before changing them.