Marketplace: explain, don't offer, updates that would be refused (PR re-review)

ItemUpdate gains invalid_at_head: compute_updates records why the item
cannot be installed at head (catalog invalid, or gone) — the rule
update_marketplace_item applies — read once per marketplace from the
snapshot at head, else from the catalog parsed at head. The Installed row
shows that reason instead of a Review button, and the update counts in the
Marketplace tab and Settings count only applicable updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 13:22:01 -07:00
co-authored by Claude Opus 5.5
parent 3dfdafc9ca
commit 60c03baf62
9 changed files with 158 additions and 10 deletions
@@ -25,7 +25,19 @@ describe("MarketplaceSettings", () => {
} as unknown as AppSettings,
});
listMarketplaceUpdates.mockResolvedValue([
{ item: { marketplace_id: "m1", kind: "agent", key: "a" }, pinned: "a".repeat(40), head: "b".repeat(40) },
{
item: { marketplace_id: "m1", kind: "agent", key: "a" },
pinned: "a".repeat(40),
head: "b".repeat(40),
invalid_at_head: null,
},
// Not applicable (re-review round 2): not counted as available.
{
item: { marketplace_id: "m1", kind: "hook", key: "h" },
pinned: "a".repeat(40),
head: "b".repeat(40),
invalid_at_head: 'unknown hook event "PreFoo"',
},
]);
});
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import { useAppState } from "../../store/appState";
import { listMarketplaceUpdates } from "../../lib/tauri-commands";
import { applicableUpdates } from "../../lib/marketplace";
import Button from "../ui/Button";
const plural = (n: number, one: string, many: string) => `${n} ${n === 1 ? one : many}`;
@@ -14,7 +15,7 @@ export default function MarketplaceSettings() {
let cancelled = false;
listMarketplaceUpdates()
.then((u) => {
if (!cancelled) setUpdateCount(u.length);
if (!cancelled) setUpdateCount(applicableUpdates(u).length);
})
.catch(() => {
if (!cancelled) setUpdateCount(null);