Make Applications a real app manager, and clean up storage without the racket
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -719,3 +719,182 @@ counted the same way `panama test` collects the suite.
|
||||
`panama-settings-commands` to pick up. Verified by reading the diff — every
|
||||
new setting here is system state (NetworkManager, firewalld, CUPS, gsettings),
|
||||
not a Panama preference.
|
||||
|
||||
## Phase 10 (Applications, Storage & Snapshots) — append below
|
||||
|
||||
Spec: `2026-08-24-applications-storage-snapshots-redesign.md`. Applications
|
||||
became an app manager (installed list with search, Flatpak uninstall, permission
|
||||
summaries, per-app autostart, jump chips, the `panama apps` catalog rendered
|
||||
natively, role dropdowns plus a single-type override). Storage grew a
|
||||
proportional breakdown bar and an itemized "Clean up, honestly" card, and lost
|
||||
the duplicate container-images row that opened a terminal. Snapshots made
|
||||
retention editable and moved the file browser into its own card, which fixes
|
||||
"browse from a collapsed volume card does nothing".
|
||||
|
||||
Three agents edited the tree concurrently. Everything below was reconciled
|
||||
against the landed files at the end of the phase rather than against the spec's
|
||||
pinned shapes.
|
||||
|
||||
### New contracts (1)
|
||||
|
||||
`quickshell/app-library-contract`. The README count line moves **170 → 171**;
|
||||
`setup/readme-contract` was run and passes ("171 contracts, as documented").
|
||||
|
||||
### Run and passing
|
||||
|
||||
These four are hermetic or source-only, so they were run rather than deferred:
|
||||
|
||||
- **`quickshell/app-library-contract` — RUN END TO END, PASS.** Safe to run
|
||||
because it never reaches the machine: `env -i`, a stub directory first on
|
||||
`PATH`, recording stubs for flatpak/rpm/pkexec and screaming stubs for
|
||||
dnf/yum/sudo/rpm-ostree/gio, `HOME` and every XDG directory inside the scratch
|
||||
tree, and a fixture catalog through `PANAMA_EXTRAS_DIR`. It asserts, before
|
||||
running the helper at all, that the helper names no binary by absolute path
|
||||
and that all six commands resolve inside the stub directory. What it pins:
|
||||
- **Catalog agreement.** `setup/lib/extras-catalog`'s bash functions and the
|
||||
helper's Python parser are both run over the same fixture files and their
|
||||
answers compared entry by entry — id, label, kind — rather than either being
|
||||
read. The fixture exercises the format's corners: a labelled entry, an
|
||||
unlabelled Flathub id (label derived from the last dotted component), a bare
|
||||
dnf name, a `|`-labelled dnf name, comments, blank lines, and two indented
|
||||
continuation lines that must fold into the entry above.
|
||||
- **Ids are the catalog line verbatim.** `flatpak:org.example.Bravo`, not the
|
||||
stripped ref: the bare ref is in the refusal list, because handing that to
|
||||
`install` would be refused for every Flathub entry in the real catalog and
|
||||
the failure would read as "installing is broken".
|
||||
- **Nothing off-catalog installs**, and the empty command log is what proves
|
||||
the refusal came first. Off-catalog ids, an unknown category, a path
|
||||
escaping the catalog directory, a continuation line, and an entry that
|
||||
exists in the *other* category are all refused with nothing run.
|
||||
- **Extensions ride with their entry and nothing else does** (installing Echo
|
||||
reaches for Echo and its two plugins, and no other `org.example.*`).
|
||||
- **Removal is flatpak-only**, pinned three ways: an AST walk that fails if
|
||||
any command list in the helper pairs dnf/yum/rpm-ostree with a removal verb
|
||||
or invokes rpm for anything but a query; a second AST walk over `uninstall`
|
||||
itself; and a runtime check that no argument — including a dnf package name
|
||||
— produces a package-manager call.
|
||||
- **Permission buckets from data**: `filesystems=host` → "Full file system
|
||||
access" and `filesystems=home` → "Home folder" are checked against separate
|
||||
fixtures so the two cannot be conflated; `devices=all` → Camera,
|
||||
`sockets=pulseaudio` → Microphone, `shared=network` → Network; an
|
||||
unrecognized key must still appear in the output; and a sandboxed
|
||||
application must summarize strictly shorter than a permissive one, so the
|
||||
summary cannot be a constant.
|
||||
- **`quickshell/applications-settings-contract` — RUN END TO END, PASS**
|
||||
(source-only). The bun-extracted `roles` / `matchesRole` half survives B's
|
||||
rebuild verbatim and gained four fixtures, including the two shapes that once
|
||||
made Archives match nothing: categories arriving as a QML list, and as a
|
||||
comma-separated string.
|
||||
- **`quickshell/default-apps-contract` — RUN END TO END, PASS.** Already
|
||||
fixture-driven (its own XDG tree, stub `xdg-mime`/`xdg-settings`); the new
|
||||
half adds a fixture `mime/globs2` and two desktop entries that declare
|
||||
`MimeType`, then pins `search-types` shape, that candidates are only installed
|
||||
applications, that a one-character query is not a search, that `set-type`
|
||||
writes exactly one type, and that an unknown type, an unknown application, and
|
||||
a type this system does not have are each refused with nothing reaching
|
||||
xdg-mime.
|
||||
- **`setup/readme-contract`, `quickshell/search-routing-contract`,
|
||||
`quickshell/settings-ownership-contract` — RUN, PASS** after the nine new
|
||||
search entries.
|
||||
|
||||
### Extended contracts, new halves run in isolation (3)
|
||||
|
||||
`disks-contract` and `snapshots-contract` each keep a first half that reads the
|
||||
live machine (`panama-disks snapshot`, `panama-snapshots snapshot`), so neither
|
||||
was run end to end. The **new** halves were extracted into scratch scripts and
|
||||
run on their own, and both pass:
|
||||
|
||||
- **`quickshell/disks-contract`** (new half run, PASS). It is hermetic: `env -i`,
|
||||
`HOME` and `XDG_CACHE_HOME` inside the scratch tree, the block tree from
|
||||
`PANAMA_DISKS_LSBLK`, and recording stubs for gio/flatpak/pkexec/dnf/podman.
|
||||
- **Two proofs run before anything is cleaned**: `breakdown.path` must be the
|
||||
fixture home, and the cache segment must be the 12 × 111111 bytes this
|
||||
contract wrote a moment earlier. Only then is `clean cache` allowed to run —
|
||||
which is what makes it safe to assert that the symlink planted inside the
|
||||
fixture cache was unlinked rather than followed, and that the file it points
|
||||
at outside the cache survived.
|
||||
- **The arithmetic**: home + applications + caches + system == `usedBytes`
|
||||
exactly, `free` == `freeBytes`, every segment non-negative, and
|
||||
`exceedsUsed` false on the fixture. The two ways the measurement can be
|
||||
wrong (`complete === false`, `exceedsUsed === true`) are pinned as things
|
||||
the page says out loud.
|
||||
- **The anti-racket stance**, pinned as an absence in the cleanup card's own
|
||||
string literals: no "running out", "recommended", "act now", "junk", "safe
|
||||
to remove", "you should", no exclamation marks, and no single button that
|
||||
clears everything. The extractor takes the *tightest* card whose own title
|
||||
is the cleanup one, and scans only double-quoted literals — QML is full of
|
||||
`!` and none of it is shouting.
|
||||
- **Each cleanable does its own one thing**: `gio trash --empty` (never rm),
|
||||
`flatpak uninstall --unused --noninteractive`, and
|
||||
`pkexec dnf clean packages` — never `clean all`, never a removal. An AST
|
||||
walk pins that this is the *only* package-manager command in the whole
|
||||
helper.
|
||||
- **The unused-runtime size is borrowed, not recomputed**: with
|
||||
`PANAMA_APPLICATIONS_HELPER` pointed at a recording stub, the row must
|
||||
report what that stub said (780000) and the stub must have been asked. Two
|
||||
ideas of "unused" would show one number and free another.
|
||||
- `backing_device` reading findmnt's SOURCE rather than `st_dev` is pinned
|
||||
with the reason: btrfs gives every subvolume its own device number, and the
|
||||
first version of the breakdown lost the system-wide flatpak installation to
|
||||
exactly that.
|
||||
- **`quickshell/snapshots-contract`** (new half run, PASS). Retention is
|
||||
exercised against a recording `snapper` stub under `env -i`, after asserting
|
||||
snapper resolves inside the stub directory — the only way to run a write verb
|
||||
without changing how this machine keeps its snapshots.
|
||||
- Refusals here are read from the JSON `error` field, not an exit code: this
|
||||
helper answers a refusal with fresh state and an `error` in it. Whether a
|
||||
refusal happened *first* is read from the absence of a `set-config` in the
|
||||
log, because the helper reads the configuration list on its way back out
|
||||
either way.
|
||||
- Ceiling pinned at **50 in both places** — the helper's `RETENTION_LIMIT` and
|
||||
the service's `retentionMax` — because a dropdown offering a value the
|
||||
helper refuses fails after the user has already chosen.
|
||||
- The browser-card fix is pinned structurally: a brace-aware scan (strings and
|
||||
comments skipped) walks the QML ancestry of `Snapshots.browseEntries` and
|
||||
fails if the enclosing card is a delegate of the per-volume Repeater. This
|
||||
was checked against the OLD page first, where it correctly fails, so it is
|
||||
not a check that passes on anything.
|
||||
|
||||
### Docs updated in the same wave
|
||||
|
||||
- `services/SettingsSearch.qml` — nine entries added, all routing to leaves that
|
||||
exist in `SettingsRoutes`: **Installed applications**, **Uninstall an
|
||||
application**, **Application permissions**, **Install applications**,
|
||||
**Autostart**, **File associations** → `applications`; **Clean up storage**,
|
||||
**Application caches** → `storage`; **Snapshot retention** → `snapshots`.
|
||||
Checked by evaluating the array: 154 entries, no duplicate labels, every
|
||||
`page` a real leaf, and none of `settings-search-contract`'s 21 ranked queries
|
||||
or 9 leaf-routing queries matches a new entry, so no pinned top result moves.
|
||||
(The first draft of the Snapshot retention detail said "each volume keeps",
|
||||
which put it in the result set for the pinned `volume` query; reworded.)
|
||||
- No settings docs regenerated: this phase adds no schema keys. Every new
|
||||
setting is system state (flatpak, dnf, snapper, xdg-mime), not a Panama
|
||||
preference — verified by reading the diff.
|
||||
|
||||
### Still open before the run
|
||||
|
||||
- **`disks-contract` and `snapshots-contract` have not been run end to end.**
|
||||
Their first halves read the live machine, so they want the same quiet moment
|
||||
the other system contracts do. Nothing in the new halves depends on that
|
||||
order.
|
||||
- **`settings-search-contract` has not been run**: it starts a Quickshell
|
||||
harness. The nine new entries were checked statically as described above, but
|
||||
the schema-label sweep and the ranked queries need the live harness.
|
||||
- **`settings-pages-contract`, `settings-docs-contract`, `settings-jump-contract`
|
||||
and `settings-buttons-contract` were not run against the three rebuilt pages.**
|
||||
ApplicationsPage grew four new components (InstalledAppRow, FileTypePicker,
|
||||
SettingsChip, StorageBreakdownBar); the qmldir registration of the first three
|
||||
is pinned by `applications-settings-contract`, but nothing here has loaded the
|
||||
QML.
|
||||
- **`app-library-contract` pins the fixture seam `PANAMA_EXTRAS_DIR` and the
|
||||
permission bucket wording** ("Camera", "Microphone", "Full file system
|
||||
access", "Home folder", "Network", "Devices"). Both match the landed helper;
|
||||
changing either is meant to be a deliberate act that updates this contract,
|
||||
and will read as a surprise the first time somebody tries.
|
||||
- Run order for this phase: the hermetic and source-only ones first
|
||||
(`app-library-contract`, `applications-settings-contract`,
|
||||
`default-apps-contract`, `search-routing-contract`,
|
||||
`settings-ownership-contract`, `setup/readme-contract`), then the read-only
|
||||
system ones (`disks-contract`, `snapshots-contract`, `containers-contract`),
|
||||
then the harness ones (`settings-search-contract`, `health-ui-contract`), and
|
||||
`settings-pages-contract` last, as before.
|
||||
|
||||
Reference in New Issue
Block a user