`;
}
filterApps(apps) {
if (this.filter === 'updates') return apps.filter((a) => a.update_available);
+ // A published newer release LINE, which `updates` deliberately excludes — its
+ // action is Upgrade (inside the row detail), not the row's Update button.
+ // Without this chip the state was unreachable by filtering even though it is
+ // the one most likely to need a decision: it never resolves on its own, since
+ // automatic updates only ever track the line you are already on.
+ // Excludes rows already counted under `updates` so the chip totals partition
+ // the list instead of double-counting an app that is both.
+ if (this.filter === 'newer') return apps.filter((a) => !a.update_available && a.newer_version);
if (this.filter === 'security') return apps.filter((a) => (a.cves || []).length);
return apps;
}
+ // Most actionable first. The list was previously in whatever order the updater
+ // emitted, so the handful of rows that actually want a decision sat wherever
+ // they happened to land — on a long list, below the fold.
+ //
+ // Ranked by what you can DO about a row, not by how alarming it looks: an app
+ // with an Update button outranks one with only a Details link, whatever their
+ // CVE counts, because the first is one click from resolved. Severity then
+ // orders within a rank, so the worst actionable row leads the page.
+ sortApps(apps) {
+ const sev = { critical: 0, high: 1, medium: 2, low: 3 };
+ const failed = (a) => !!(this.updater && this.updater.autoAttemptFailed && this.updater.autoAttemptFailed(a));
+ const rank = (a) => {
+ // Auto-failed first inside the update group: that build was already tried
+ // and rolled back, so unlike its neighbours it will NOT retry itself and a
+ // person has to press Update.
+ if (a.update_available) return failed(a) ? 0 : 1;
+ if (a.newer_version) return 2;
+ if ((a.cves || []).length) return 3;
+ if (!a.scanned) return 4;
+ return 5;
+ };
+ const label = (a) => String(a.displayName || a.name || '').toLowerCase();
+ return apps.slice().sort((x, y) =>
+ rank(x) - rank(y)
+ || (sev[x.worstSeverity] ?? 9) - (sev[y.worstSeverity] ?? 9)
+ || label(x).localeCompare(label(y)));
+ }
+
updateRow(a) {
const esc = (s) => this.escape(s);
const slug = esc(a.name);