Tier 0: render what the services already decided, honestly

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-25 00:24:30 -04:00
parent be0e55214b
commit 8b1205e4b8
38 changed files with 1474 additions and 260 deletions
+77 -12
View File
@@ -45,9 +45,57 @@ Singleton {
+ Number(root.firmware?.count ?? 0)
readonly property int securityCount: Number(root.dnf?.securityCount ?? 0)
// Whether the advisory query answered at all. It only ever shrinks the
// alarm -- the page says "nothing security-critical" when the count is
// zero -- so a failed query returning zero would be reassurance nobody
// measured.
readonly property bool securityKnown: root.dnf?.securityKnown !== false
readonly property bool rebootNeeded: root.kernel?.rebootNeeded === true
// The last time EVERY source answered. The helper carries this forward
// rather than restamping it on a partial failure, so it never describes a
// check that did not complete.
readonly property bool everChecked: root.checkedAt > 0
// ── What is not known, and why ──────────────────────────────────────────
//
// The three sources fail independently, so they carry their own errors.
// A source that could not answer has an UNKNOWN count, which is not a count
// of zero -- and the whole distance between those two is the distance
// between "Up to date" and a lie. `total` sums what was actually counted,
// so it is only the whole truth when nothing failed.
readonly property var failedSources: {
const failed = [];
for (const source of ["dnf", "flatpak", "firmware"]) {
const record = source === "dnf" ? root.dnf
: source === "flatpak" ? root.flatpak : root.firmware;
if (String(record?.error ?? "") !== "")
failed.push(source);
}
return failed;
}
readonly property bool anySourceFailed: root.failedSources.length > 0
function sourceError(source: string): string {
const record = source === "dnf" ? root.dnf
: source === "flatpak" ? root.flatpak
: source === "firmware" ? root.firmware : null;
return String(record?.error ?? "");
}
// "System packages", or "System packages and Firmware" -- named so the
// headline can say which source it could not reach rather than leaving
// someone to work it out from three cards.
function describeFailedSources(): string {
const names = root.failedSources.map(source => root.sourceLabel(source));
if (names.length <= 1)
return names.join("");
return names.slice(0, -1).join(", ") + " and " + names[names.length - 1];
}
// How much there is to fetch, when every pending item was priced. The
// helper omits the figure for a source it could only partly price, and a
// partial total presented as the whole download understates it -- which is
@@ -117,9 +165,20 @@ Singleton {
}
// A count nobody has verified is not a count. Saying "up to date" on the
// strength of a check that never ran is the one wrong answer that looks
// reassuring.
// strength of a check that never ran -- or one that failed -- is the one
// wrong answer that looks reassuring.
//
// A source that could not answer is checked FIRST and by name. "Up to date"
// is a claim about all three, and it cannot be made while one of them is
// unknown, however many the other two counted.
function summary(): string {
if (root.anySourceFailed) {
const which = root.describeFailedSources();
return root.total > 0
? root.total + " update" + (root.total === 1 ? "" : "s")
+ " found, but " + which + " could not be checked"
: which + " could not be checked";
}
if (!root.everChecked)
return "Not checked yet";
if (root.total === 0)
@@ -225,22 +284,28 @@ Singleton {
readonly property bool loadingChangelog: changelogProcess.running
// Returns the record for an item, or null while one is being fetched.
// Starting the fetch is a side effect on purpose: the page asks for a
// changelog by rendering one, and there is nothing else to ask.
// Reading and requesting are two functions on purpose. The first version
// started the fetch as a side effect of being rendered, and mutating the
// queue from inside a binding evaluation is a binding loop: the queue
// write bumps a property the binding reads, which re-evaluates the
// binding, forty-odd times per page open. Bindings call changelogFor
// (pure); the expand action calls requestChangelog (imperative).
function changelogFor(source: string, name: string): var {
root.changelogRevision;
const key = source + "/" + name;
const known = root.changelogs[key];
if (known !== undefined)
return known;
const known = root.changelogs[source + "/" + name];
return known !== undefined ? known : null;
}
function requestChangelog(source: string, name: string): void {
if (!source || !name)
return null;
if (changelogProcess.key === key
return;
const key = source + "/" + name;
if (root.changelogs[key] !== undefined
|| changelogProcess.key === key
|| root.changelogQueue.some(entry => entry.source + "/" + entry.name === key))
return null;
return;
root.changelogQueue = root.changelogQueue.concat([{ source: source, name: name }]);
root.pumpChangelogs();
return null;
}
function pumpChangelogs(): void {