Marketplace: match an update to the install pinned at its commit (PR review #8)

InstalledPane's updateFor now requires u.pinned === i.commit, so an install
of the same item at another (unchanged) commit no longer borrows another
install's update and shows a phantom one with an empty diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 13:13:11 -07:00
co-authored by Claude Opus 5.5
parent fe15f541b9
commit 8ec033f923
2 changed files with 28 additions and 1 deletions
@@ -83,6 +83,31 @@ describe("InstalledPane", () => {
);
});
it("PR review #8: an update belongs only to the install pinned at its commit", () => {
const C = "c".repeat(40);
// The same agent is installed globally at A and in p1 at C. Only the A
// install has an update (A → B); C is unchanged at head.
useAppState.setState({
projects: [
{
id: "p1",
name: "api",
status: "running",
marketplace_installs: [{ marketplace_id: "m1", kind: "agent", key: "rev", commit: C }],
marketplace_disabled: [],
},
] as unknown as Project[],
});
const mp = api({
updates: [{ item: { marketplace_id: "m1", kind: "agent", key: "rev" }, pinned: A, head: B }],
});
render(<InstalledPane mp={mp} />);
const global = screen.getByTestId("installed-global");
expect(within(global).getByRole("button", { name: "Review update for rev" })).toBeInTheDocument();
const proj = screen.getByTestId("installed-project-p1");
expect(within(proj).queryByRole("button", { name: "Review update for rev" })).not.toBeInTheDocument();
});
it("I2: accepts the head that was reviewed even if the update list moves on", async () => {
const C = "c".repeat(40);
const item = { marketplace_id: "m1", kind: "agent" as const, key: "rev" };
@@ -32,13 +32,15 @@ export default function InstalledPane({ mp }: { mp: MarketplaceApi }) {
const nameOf = (id: string) => marketplaces.find((m) => m.id === id)?.name ?? id;
const globalInstalls = appSettings?.global_marketplace_installs ?? [];
/** The update for this very install: same item *and* pinned at the same
* commit (PR review #8) — updates are listed per (item, pinned commit). */
const updateFor = (i: MarketplaceInstall) =>
mp.updates.find(
(u) =>
u.item.marketplace_id === i.marketplace_id &&
u.item.kind === i.kind &&
u.item.key === i.key &&
u.head !== i.commit,
u.pinned === i.commit,
);
/** Hooks only (spec §3, preflight F8): the rendered commands at head, so the