diff --git a/docs/specifications/website.md b/docs/specifications/website.md index 802ed3f..e639989 100644 --- a/docs/specifications/website.md +++ b/docs/specifications/website.md @@ -157,6 +157,13 @@ advertise an enabled web flasher; it MAY explicitly label firmware HIL as pending. Its mechanically rendered PNG and authored SVG MUST remain paired by reviewed content and exact-dimension tests. +Every changed social-card byte set MUST use a new content-versioned public +pathname before its metadata is deployed. Replacing a PNG or SVG at an existing +pathname is forbidden because the public CDN may retain the previous bytes +after an origin release changes. Tests MUST bind the metadata URL, local +pathname, dimensions, and reviewed SHA-256; production verification MUST fetch +that exact URL and compare the deployed bytes. + The support route MUST link directly to the preferred GitHub bug template at `https://github.com/PyBLE-dev/PyBLE/issues/new?template=bug.yml`. Installer intake MUST request the exact profile ID, board/model and module marking, flash diff --git a/tools/web/public/social/pyble-beta-og-1200x630.svg b/tools/web/public/social/pyble-beta-og-48d458bd-1200x630.svg similarity index 100% rename from tools/web/public/social/pyble-beta-og-1200x630.svg rename to tools/web/public/social/pyble-beta-og-48d458bd-1200x630.svg diff --git a/tools/web/public/social/pyble-beta-og-1200x630.png b/tools/web/public/social/pyble-beta-og-7e7e037d-1200x630.png similarity index 100% rename from tools/web/public/social/pyble-beta-og-1200x630.png rename to tools/web/public/social/pyble-beta-og-7e7e037d-1200x630.png diff --git a/tools/web/src/lib/site.ts b/tools/web/src/lib/site.ts index 7425d18..e08e4ba 100644 --- a/tools/web/src/lib/site.ts +++ b/tools/web/src/lib/site.ts @@ -53,7 +53,7 @@ export function absoluteUrl(path: string): string { } export const socialImage = { - url: absoluteUrl("/social/pyble-beta-og-1200x630.png"), + url: absoluteUrl("/social/pyble-beta-og-7e7e037d-1200x630.png"), width: 1200, height: 630, alt: "Actual PyBLE iPad app showing GPIO 48 NeoPixel Blocks and generated MicroPython code", diff --git a/tools/web/src/test/site-contract.test.tsx b/tools/web/src/test/site-contract.test.tsx index 03a06c3..d35da57 100644 --- a/tools/web/src/test/site-contract.test.tsx +++ b/tools/web/src/test/site-contract.test.tsx @@ -77,7 +77,9 @@ describe("public-site contract", () => { }); it("publishes a real-app large social card and local TestFlight card", async () => { - const socialUrl = "https://pyble.dev/social/pyble-beta-og-1200x630.png"; + const socialPngName = "pyble-beta-og-7e7e037d-1200x630.png"; + const socialSvgName = "pyble-beta-og-48d458bd-1200x630.svg"; + const socialUrl = `https://pyble.dev/social/${socialPngName}`; expect(rootMetadata.openGraph?.images).toEqual([ { url: socialUrl, @@ -100,11 +102,8 @@ describe("public-site contract", () => { const publicDirectory = join(process.cwd(), "public"); const [socialPng, socialSvg, qrCardPng, qrCardSvg] = await Promise.all([ - readFile(join(publicDirectory, "social", "pyble-beta-og-1200x630.png")), - readFile( - join(publicDirectory, "social", "pyble-beta-og-1200x630.svg"), - "utf8", - ), + readFile(join(publicDirectory, "social", socialPngName)), + readFile(join(publicDirectory, "social", socialSvgName), "utf8"), readFile(join(publicDirectory, "social", "pyble-testflight-qr-1080.png")), readFile( join(publicDirectory, "social", "pyble-testflight-qr-1080.svg"), @@ -118,9 +117,20 @@ describe("public-site contract", () => { expect(socialSvg).toContain("Everyday coding over BLE."); expect(socialSvg).toContain("FIRMWARE HIL PENDING"); expect(socialSvg).not.toContain("WEB FLASHER"); - expect(createHash("sha256").update(socialPng).digest("hex")).toBe( + const socialPngSha256 = createHash("sha256") + .update(socialPng) + .digest("hex"); + const socialSvgSha256 = createHash("sha256") + .update(socialSvg) + .digest("hex"); + expect(socialPngSha256).toBe( "7e7e037d9bd2e58e2e66f516bfd6f1b753bda472c402b8130f8a8a6dd8f19ba9", ); + expect(socialSvgSha256).toBe( + "48d458bd5a6ee1e754bad7d7f9a7261361c0d8a3b13c4fc9cc8a9f5670e04b5d", + ); + expect(socialPngName).toContain(socialPngSha256.slice(0, 8)); + expect(socialSvgName).toContain(socialSvgSha256.slice(0, 8)); expect([qrCardPng.readUInt32BE(16), qrCardPng.readUInt32BE(20)]).toEqual([ 1080, 1080, ]);