From b97e5c59a62d3cc652c4616e3165933ec106a6b0 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 15 Sep 2026 13:34:52 +0000 Subject: [PATCH] fix(site): never read the latest CID from manifest.versions[last] (#30) The model page already took the latest CID from the indexer's on-chain contenthash; drop the dead fallback to manifest.versions[0], fail visibly if the indexer returns no versions, and model the self-referential placeholder in the Qwen fixture so a regression test can prove the placeholder is never linked. Co-authored-by: Jeff Lau --- apps/site/src/pages/model.tsx | 19 ++++++++---- apps/site/test/fixtures/name-qwen.json | 2 +- apps/site/test/model.test.tsx | 40 ++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/apps/site/src/pages/model.tsx b/apps/site/src/pages/model.tsx index 66fd6e9..9c2083e 100644 --- a/apps/site/src/pages/model.tsx +++ b/apps/site/src/pages/model.tsx @@ -24,13 +24,14 @@ function decodeNameParam(raw: string | undefined): string | null { } } -/** Latest CID: the version whose name matches the manifest, else the last listed. */ -function latestVersion(detail: NameDetail): NameVersion { +/** + * Latest CID comes from the indexer's on-chain `contenthash` record, never from + * `manifest.versions[last].cid`: a manifest cannot contain its own CID (issue #30). + */ +function latestVersion(detail: NameDetail): NameVersion | null { const match = detail.versions.find((v) => v.name === detail.manifest.name); if (match !== undefined) return match; - const last = detail.versions[detail.versions.length - 1]; - if (last !== undefined) return last; - return detail.manifest.versions[0]; + return detail.versions[detail.versions.length - 1] ?? null; } function ModelHeader({ detail }: { detail: NameDetail }) { @@ -96,6 +97,14 @@ export function ModelPage() { if (data === null) return ; const latest = latestVersion(data); + if (latest === null) { + return ( + + ); + } return (
diff --git a/apps/site/test/fixtures/name-qwen.json b/apps/site/test/fixtures/name-qwen.json index ccfec22..f6c2b22 100644 --- a/apps/site/test/fixtures/name-qwen.json +++ b/apps/site/test/fixtures/name-qwen.json @@ -132,7 +132,7 @@ { "version": "1.0.0", "name": "v1-0-0.qwen--qwen2-5-7b-instruct.mirrors.enspack.eth", - "cid": "bafkreis5hm6e3jayscwqnqudbrnems5iuqts36dojc5hgq5yuvxl4vg42c", + "cid": "bafkreiduwtplaceholderdraftbytescidnotresolvable2a3b4c5d6e7f8g", "createdAt": "2026-09-14T00:00:00Z" } ] diff --git a/apps/site/test/model.test.tsx b/apps/site/test/model.test.tsx index 3f95ff8..507bd26 100644 --- a/apps/site/test/model.test.tsx +++ b/apps/site/test/model.test.tsx @@ -1,8 +1,9 @@ import { cleanup, fireEvent, screen, waitFor, within } from "@testing-library/react"; import { afterEach, describe, expect, it } from "vitest"; -import { ensAppUrl } from "../src/lib/format.js"; +import { FixtureIndexClient } from "../src/lib/client.js"; +import { ensAppUrl, ipfsUrl } from "../src/lib/format.js"; import { ModelPage } from "../src/pages/model.js"; -import { renderAt } from "./helpers.js"; +import { demoFixtures, renderAt } from "./helpers.js"; afterEach(() => { cleanup(); @@ -95,6 +96,41 @@ describe("ModelPage", () => { }); }); + it("takes the latest CID from the indexer, never from manifest.versions[last] (issue #30)", async () => { + const detail = demoFixtures.details[QWEN]; + if (detail === undefined) throw new Error("missing qwen fixture"); + const indexerCid = detail.versions[0]?.cid; + const selfCid = detail.manifest.versions[detail.manifest.versions.length - 1]?.cid; + if (indexerCid === undefined || selfCid === undefined) throw new Error("fixture shape"); + expect(selfCid).not.toBe(indexerCid); + + renderName(QWEN); + await screen.findByRole("heading", { name: "Qwen2.5-7B-Instruct" }); + + expect(document.querySelector(`a[href="${ipfsUrl(indexerCid)}"]`)).not.toBeNull(); + expect(document.querySelector(`a[href="${ipfsUrl(selfCid)}"]`)).toBeNull(); + expect(screen.getByText(/"lockfileVersion": 1/)).toHaveTextContent(indexerCid); + expect(screen.getByText(/"lockfileVersion": 1/)).not.toHaveTextContent(selfCid); + // The placeholder may only appear inside the raw manifest JSON viewer. + for (const el of screen.getAllByText(new RegExp(selfCid))) { + expect(el.closest("details")).not.toBeNull(); + } + }); + + it("shows an error when the indexer returns a name without versions", async () => { + const detail = demoFixtures.details[QWEN]; + if (detail === undefined) throw new Error("missing qwen fixture"); + const client = new FixtureIndexClient({ + ...demoFixtures, + details: { [QWEN]: { ...detail, versions: [] } }, + }); + renderAt(`/name/${encodeURIComponent(QWEN)}`, , { + routePattern: "/name/:name", + client, + }); + expect(await screen.findByRole("alert")).toHaveTextContent("Could not load name"); + }); + it("marks Jeff's latest version and links the older version name", async () => { renderName(JEFF); expect(await screen.findByRole("heading", { name: "tiny-random" })).toBeInTheDocument();