Marketplace: pin the commit the user reviewed (final review I2)
Install and update pinned whatever the marketplace head was when the click landed, so a background refresh between review and click could pin content nobody saw (including a hook's shell commands). install_marketplace_item and update_marketplace_item now take expected_commit and refuse with "changed since you reviewed this item — review it again" unless it is still the head. The UI passes the head the selected item was read at (Browse), the head frozen with a pending hook confirm (whose commands are frozen too), and the head of the accepted diff (Installed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,13 @@ export default function BrowsePane({ mp }: { mp: MarketplaceApi }) {
|
||||
const setFilterId = useAppState((s) => s.setMarketplaceFilterProjectId);
|
||||
const [kind, setKind] = useState<KindFilter>("all");
|
||||
const [query, setQuery] = useState("");
|
||||
const [selected, setSelected] = useState<{ marketplaceId: string; item: CatalogItem } | null>(null);
|
||||
// The item is kept as it was read, with the head it was read at: an
|
||||
// install pins exactly what the detail pane shows (final review I2).
|
||||
const [selected, setSelected] = useState<{
|
||||
marketplaceId: string;
|
||||
item: CatalogItem;
|
||||
headCommit: string | null;
|
||||
} | null>(null);
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [removing, setRemoving] = useState<Marketplace | null>(null);
|
||||
|
||||
@@ -35,7 +41,7 @@ export default function BrowsePane({ mp }: { mp: MarketplaceApi }) {
|
||||
.filter((i) => kind === "all" || i.kind === kind)
|
||||
.filter((i) => q === "" || `${i.name} ${i.key} ${i.description}`.toLowerCase().includes(q))
|
||||
.sort((a, b) => KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind) || a.name.localeCompare(b.name))
|
||||
.map((item) => ({ marketplaceId: snap.marketplace_id, item })),
|
||||
.map((item) => ({ marketplaceId: snap.marketplace_id, item, headCommit: snap.head_commit })),
|
||||
);
|
||||
}, [mp.snapshots, kind, query]);
|
||||
|
||||
@@ -161,7 +167,7 @@ export default function BrowsePane({ mp }: { mp: MarketplaceApi }) {
|
||||
className={inputClass}
|
||||
/>
|
||||
<ul className="space-y-1">
|
||||
{rows.map(({ marketplaceId, item }) => {
|
||||
{rows.map(({ marketplaceId, item, headCommit }) => {
|
||||
const key = itemRefKey({ marketplace_id: marketplaceId, kind: item.kind, key: item.key });
|
||||
const isSel =
|
||||
selected?.marketplaceId === marketplaceId &&
|
||||
@@ -171,7 +177,7 @@ export default function BrowsePane({ mp }: { mp: MarketplaceApi }) {
|
||||
<li key={key}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelected({ marketplaceId, item })}
|
||||
onClick={() => setSelected({ marketplaceId, item, headCommit })}
|
||||
className={`w-full text-left px-2 py-1.5 rounded-[var(--radius-control)] text-xs ${
|
||||
isSel ? "bg-[var(--bg-tertiary)]" : "hover:bg-[var(--bg-tertiary)]"
|
||||
}`}
|
||||
@@ -197,7 +203,12 @@ export default function BrowsePane({ mp }: { mp: MarketplaceApi }) {
|
||||
|
||||
<section className="flex-1 min-w-0 p-4 overflow-auto">
|
||||
{selected ? (
|
||||
<ItemDetail mp={mp} item={selected.item} marketplaceId={selected.marketplaceId} />
|
||||
<ItemDetail
|
||||
mp={mp}
|
||||
item={selected.item}
|
||||
marketplaceId={selected.marketplaceId}
|
||||
headCommit={selected.headCommit}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-xs text-[var(--text-secondary)]">Select an item to see what it contains and install it.</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user