Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit e8421a7b1d1415fe2a147f5a021b9d4e9e210c5c
parent 1cec7f3186be84cbf11de7d9e942386003c71e22
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sat, 26 Sep 2026 18:23:00 -0400

export(e2e): every build carries the Ko-fi mark, last in the social row

site-branding's "a site build carries no Ko-fi link" becomes "every build
carries the Ko-fi mark, last in the social row", and the hub's "bare Ko-fi
link" test in official-instances asserts the same mark. Both call one
helper, expectKofiMarkLast (e2e/helpers.ts): exactly one Ko-fi link on the
page (the hub's text link is gone), href KOFI_URL, a new tab, no visible
text, an img of /kofi-symbol.svg with alt "" that is drawn, the LAST <li>
of the footer's social list, and the served file 200 image/svg+xml with
the official file's sha256. The site test also pins the fixture's GitHub
link before it in the same row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mexport/e2e-hub/official-instances.spec.ts | 17+++++++----------
Mexport/e2e/helpers.ts | 58+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mexport/e2e/site-branding.spec.ts | 23++++++++++++++++-------
3 files changed, 80 insertions(+), 18 deletions(-)

diff --git a/export/e2e-hub/official-instances.spec.ts b/export/e2e-hub/official-instances.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page, type Route } from "@playwright/test"; -import { KOFI_URL } from "../../common/lib/project"; +import { expectKofiMarkLast } from "../e2e/helpers"; // The hub's "Official Instances": one card per built-in member // (hub-sites.json), carrying the homepage's own figures for it when the hub was @@ -117,20 +117,17 @@ test.describe("hub official instances", () => { ).toHaveCount(0); }); - test("the hub footer carries a bare Ko-fi link", async ({ page }) => { + test("the hub footer carries the Ko-fi mark, once, last in the social row", async ({ + page, + }) => { await stubMember(page); await page.route("**/hub-summary.json", (r) => r.fulfill({ status: 404, body: "" }), ); await page.goto("/"); - const kofi = page - .locator("footer") - .getByRole("link", { name: "Ko-fi", exact: true }); - await expect(kofi).toHaveCount(1); - await expect(kofi).toHaveAttribute("href", KOFI_URL); - await expect(kofi).toHaveAttribute("target", "_blank"); - await expect(kofi).toHaveAttribute("rel", "noopener noreferrer"); - await expect(kofi).toHaveText("Ko-fi"); + // The same mark as every site (the operator's 2026-09-26 ruling). The + // hub's own "Ko-fi" text link is gone: one Ko-fi per footer. + await expectKofiMarkLast(page); }); test("without hub-summary.json, the card renders with no figures", async ({ diff --git a/export/e2e/helpers.ts b/export/e2e/helpers.ts @@ -1,4 +1,6 @@ -import type { Page, Route } from "@playwright/test"; +import { createHash } from "node:crypto"; +import { expect, type Page, type Route } from "@playwright/test"; +import { KOFI_URL } from "../../common/lib/project"; import { channelSubsManifest, channelTranscriptsManifest, @@ -173,3 +175,57 @@ export async function closeModal(page: Page) { .getByRole("button", { name: "Mark clip start at current time" }) .waitFor({ state: "detached" }); } + +// sha256 of Ko-fi's official `kofi_symbol.svg` (their brand-asset zip), which +// the export vendors byte-for-byte as public/kofi-symbol.svg: a brand asset is +// used as shipped, never recoloured or re-exported. +export const KOFI_SYMBOL_SHA256 = + "f5a2d5f1abb5c7f13392ecf6e7d5dffc8bef77917ab59c28a6080deab52f5078"; + +// The footer's Ko-fi mark, on every build (site and hub): exactly one Ko-fi +// link on the page, the LAST item of the footer's social row, opening Ko-fi in +// a new tab, with no visible text — only the official symbol, which is drawn +// (not a broken image) and served unmodified. +export async function expectKofiMarkLast(page: Page) { + const footer = page.locator("footer"); + const kofi = footer.getByRole("link", { name: "Ko-fi", exact: true }); + await expect(kofi).toHaveCount(1); + // One per page: no second Ko-fi link anywhere (the hub's old text link). + await expect(page.getByRole("link", { name: /ko-?fi/i })).toHaveCount(1); + await expect(page.locator(`a[href="${KOFI_URL}"]`)).toHaveCount(1); + await expect(kofi).toBeVisible(); + await expect(kofi).toHaveAttribute("href", KOFI_URL); + await expect(kofi).toHaveAttribute("target", "_blank"); + await expect(kofi).toHaveAttribute("rel", "noopener noreferrer"); + // No copy: the mark is the whole link. + expect((await kofi.innerText()).trim()).toBe(""); + + const img = kofi.locator('img[src="/kofi-symbol.svg"]'); + await expect(img).toHaveCount(1); + await expect(img).toHaveAttribute("alt", ""); + await expect + .poll(() => + img.evaluate((el) => { + const i = el as HTMLImageElement; + return i.complete && i.naturalWidth > 0; + }), + ) + .toBe(true); + + // `has` is queried from each <ul>, so it takes a page-rooted locator: `kofi` + // itself starts at <footer> and would look for a footer inside the list. + const row = footer + .locator("ul") + .filter({ has: page.getByRole("link", { name: "Ko-fi", exact: true }) }); + await expect(row).toHaveCount(1); + await expect( + row.locator(":scope > li").last().getByRole("link"), + ).toHaveAccessibleName("Ko-fi"); + + const res = await page.request.get("/kofi-symbol.svg"); + expect(res.status()).toBe(200); + expect(res.headers()["content-type"]).toMatch(/^image\/svg\+xml/); + expect(createHash("sha256").update(await res.body()).digest("hex")).toBe( + KOFI_SYMBOL_SHA256, + ); +} diff --git a/export/e2e/site-branding.spec.ts b/export/e2e/site-branding.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from "@playwright/test"; -import { KOFI_URL, PROJECT_URL } from "../../common/lib/project"; -import { installRoutes } from "./helpers"; +import { PROJECT_URL } from "../../common/lib/project"; +import { expectKofiMarkLast, installRoutes } from "./helpers"; // The export app is built per-site; its SSR branding (document title, header, // home tagline, footer social links) comes from currentSite() reading the @@ -45,13 +45,22 @@ test("footer credits the project, ungated and in the same tab", async ({ await expect(footer).toContainText("Built with Archilyzer"); }); -test("a site build carries no Ko-fi link", async ({ page }) => { +test("every build carries the Ko-fi mark, last in the social row", async ({ + page, +}) => { await installRoutes(page); await page.goto("/"); - // The project's Ko-fi is on the hub and the homepage only: an archive - // someone else hosts must not carry this project's tip jar. - await expect(page.getByRole("link", { name: "Ko-fi" })).toHaveCount(0); - await expect(page.locator(`a[href="${KOFI_URL}"]`)).toHaveCount(0); + // The operator's 2026-09-26 ruling replaced "hub and homepage only": the + // project's Ko-fi is on every site, as Ko-fi's official symbol after the + // site's own social links, with no copy around it. + await expectKofiMarkLast(page); + // The site's own links come first, in the same row. + const row = page + .locator("footer ul") + .filter({ has: page.getByRole("link", { name: "Ko-fi", exact: true }) }); + const items = row.locator(":scope > li"); + await expect(items).toHaveCount(2); + await expect(items.first().getByRole("link")).toHaveAccessibleName("GitHub"); }); test("the Downloads eyebrow appears only when something is behind it", async ({