commit 9654000b7ab3df3a0f1e9a37f3abf08b53a88925
parent bbf8c567f3e38bc74a5facdf3ffbe1c7fafeb302
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 26 Sep 2026 23:40:24 -0400
Merge export/drop-kofi — Ko-fi dropped for now (the operator, 2026-09-26): no Ko-fi link or asset on any site, the hub or the homepage; the footers restored, the vendored symbol and its service-worker rule removed, one negative check per surface
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
17 files changed, 115 insertions(+), 232 deletions(-)
diff --git a/common/lib/archive/serviceWorkerRouting.test.ts b/common/lib/archive/serviceWorkerRouting.test.ts
@@ -129,17 +129,6 @@ for (const file of ["export/service-worker/site-sw.js", "export/service-worker/s
assert.equal(sw.cached("shell-v2", "/icons/icon.svg"), undefined);
});
- test(`${file}: the footer's /kofi-symbol.svg is network-first in shell-v2 — an installed app draws it offline`, async () => {
- // Not under /icons/: it is a static file of its own, on every page's
- // footer. Without its own rule it fell through to the network, so an
- // offline launch drew an empty link where the mark should be.
- const sw = loadWorker(file);
- assert.equal(await sw.request("/kofi-symbol.svg", { online: true, network: "mark" }), "mark");
- assert.deepEqual(sw.fetched, ["/kofi-symbol.svg"]);
- assert.equal(sw.cached("shell-v2", "/kofi-symbol.svg"), "mark");
- assert.equal(await sw.request("/kofi-symbol.svg", { online: false }), "mark");
- });
-
test(`${file}: /_next/static/ stays cache-first`, async () => {
const sw = loadWorker(file);
sw.seed("shell-v2", "/_next/static/chunks/app.js", "cached chunk");
diff --git a/common/lib/project.ts b/common/lib/project.ts
@@ -32,13 +32,6 @@ export const PROJECT_WORDMARK_LEAD = "Archi";
// valid. Only then is changing this string a cosmetic follow-up.
export const PROJECT_URL = "https://archilyzer.pages.dev";
-// The project's Ko-fi page. No copy around it, anywhere. On every export build
-// (each site and the hub) it is Ko-fi's official symbol, the last item of the
-// footer's social row, accessible name "Ko-fi" (the operator's 2026-09-26
-// ruling, which replaced "hub and homepage only"). The homepage footer still
-// carries it as a bare text link, "Ko-fi".
-export const KOFI_URL = "https://ko-fi.com/archilyzer";
-
export const PROJECT_TAGLINE = "Self-hosted, searchable video-transcript archives.";
// Where a visitor gets the source. A dated snapshot tarball — there is no
diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md
@@ -10,6 +10,7 @@
- **"Persist source video" and a whole-recording fetch download the video even when it already has a transcript.** On a channel that takes YouTube's subtitles, the button — and a whole-recording request from umtool or the MCP server's `fetch_clip` with `full: true`, which run the same job — fetched only the subtitles again when the video already had a transcript or captions, and finished with no file. It now downloads the source and moves it into the saved-video store. YouTube's subtitles are fetched again first, as on any re-download; a Whisper transcript is not touched, and no audio is extracted beside a transcript. **Persist kept now** on a channel's Cleanup stage does the same for every kept video, so on such a channel it now downloads each kept video's source. The video page's Source video card offers the button on these channels too; it used to say persistence was for transcribe-handling channels only.
- **Each video keeps a history of how its metadata changed at the source.** Every download that rewrites a video's `metadata.info.json` and changes anything in it adds one entry to `metadata.history.json` beside it: the old and new value of each field that changed (title, description, duration, availability, chapters and the rest), the view, like and comment counts that moved, and which of the fields that change on every fetch (format URLs, thumbnails, caption URLs) differed, compared by fingerprint only. A caption language appearing or disappearing counts as a change. The newest 200 entries are kept. The video page shows the history under the description: "Metadata rewritten N× · last … by …: <what changed>", with each entry's old → new values when opened. The history starts with the first rewrite after this update.
- **Release notes can be cut from the command line, with or without the editor running.** `archilyzer release show` prints each changelog's latest release, its date, how many bullets wait under `[Unreleased]`, and what `next` and `next-minor` would be. `archilyzer release cut <editor|export|all> <X.Y.Z|next|next-minor> [--commit] [--date YYYY-MM-DD]` turns `## [Unreleased]` into the dated heading and prints one line per changelog, for example `editor: ## [0.10.0] - 2026-09-26 (committed 1a2b3c4d)`. `next` is the patch bump of the latest heading and `next-minor` the minor bump. `all` cuts both changelogs with one version, worked out from the higher of their two latest headings, and with `--commit` makes one `Release <workspace> <version>` commit per changelog. It cuts both or neither: both changelogs are checked before either is written, so if one cannot be cut, nothing is written or committed. Only a write or commit that fails after the editor's has gone through can leave the editor cut (and committed) without the export, and the command then says so. Run it from the checkout (`pnpm --filter yt-dlp-transcript-common exec tsx bin/archilyzer.ts release …`); no editor is needed. A running editor takes the same cut over HTTP: `pnpm ops cut-release --json '{"workspace":"all","version":"next","commit":true}'` (`POST /api/ops/cut-release`). The two commands and the **Cut release** form on `/changelog` and `/sites` share one code path, so they refuse the same things in the same words: nothing pending; a malformed version; a version that is not newer than the latest release (new for the form too); a date that is not a real day; and a commit while any file is uncommitted other than the changelog being cut (for `all`, either changelog). The form's fields are unchanged, and its version box now also accepts `next` and `next-minor`. No `package.json` version is bumped and no tag is made: the changelog heading is the version.
+- **The hub and the homepage no longer link to Ko-fi.** The "Ko-fi" link is gone from both footers.
## [0.9.0] - 2026-09-26
- **Every page now has a ground and an accent to choose, and the five theme families are gone.** The theme menu (the palette button beside the quick toggle, in the editor's sidebar and in the header of every published site, the hub and the homepage) has two groups. **Base** is System, Light, Sepia or Dark; Sepia is new, a warm paper ground for long reading. **Accent** is Signal, Brass, Vermilion, Violet, Sakura, Blue or Green, with the site's own tagged *default*; a site with a custom hex offers it first as *Site colour*. The quick toggle cycles System → Light → Sepia → Dark. A published site opens on the reader's system setting, in the accent its site form sets. The hub and the homepage open on Dark, in Signal, even with JavaScript off, and the editor follows the system, in Signal. Each accent has a value for each ground that reads at 4.5:1, and a custom hex is darkened or lightened per ground to match. A reader's accent is remembered only while it differs from the site's: picking the site's own again forgets it, so the reader follows the site if its accent changes later. Base, Archive, Selenized, Swiss and Archilyzer are gone. A choice made before this update carries over once: light stays light (Archive light becomes Sepia), dark stays dark and system stays system; the family itself is dropped. Headings are Archivo, text is IBM Plex Sans and figures are IBM Plex Mono everywhere, with one corner radius. Success, warning and other status text reads at 4.5:1 on its own tinted fill on every ground; on Light, success and warning are a shade deeper than before for it. Chart colours are fixed per ground and never follow the accent; the third is a violet, well clear of the red that marks a recording as gone. The phone's browser bar takes the page's ground, not the accent. Needs a rebuild and deploy of every site, the hub and the homepage.
diff --git a/export/CHANGELOG.md b/export/CHANGELOG.md
@@ -4,7 +4,7 @@
- **The hub lists its official instances in the homepage's order, and each wears one colour everywhere.** The cards and the scope chips follow the homepage (most transcripts first) instead of `hub-sites.json`'s alphabetical order. An archive whose site sets no accent wears its homepage card's colour, and now on every surface: its card, its chip's dot, the left edge of its search results and its channel group. Before, it had a card stripe and nothing else. The hub waits for `/hub-summary.json`, which is requested together with the list, before it lists the official instances, so they never reorder a moment later.
- **The hub's Ask AI searches only the archives the scope chips leave in.** An archive switched off on the hub's front page is not fetched on `/ask` either, so the chat answers from what the search showed. It still waits for every archive in scope before it answers. With every archive switched off, the question box is disabled and one line under it says why. Before, a question went out over no records at all.
- **A member archive with no live chat is ready about a second sooner on the hub.** Its missing subs manifest (a 404) is read as "no live chat" at once, instead of as an error and a second request. A subs manifest that fails for any other reason is asked for once more. If it fails again, the archive is searched without its live chat, and its chip reads ready.
-- **Every archive's footer ends with Ko-fi's mark.** The last icon of the footer's social row, on every site and on the hub, is Ko-fi's official symbol in Ko-fi's own colours. It links the project's Ko-fi page (`https://ko-fi.com/archilyzer`) in a new tab. Its accessible name is "Ko-fi", and no text sits beside it. The file is served as `/kofi-symbol.svg`, exactly as Ko-fi publishes it, and both service workers keep it for offline use, as they do the icons. The hub's "Ko-fi" text link is gone, so each footer has one Ko-fi link.
+- **The hub no longer links to Ko-fi.** The "Ko-fi" link is gone from its footer.
## [0.9.0] - 2026-09-26
- **A reader picks a ground and an accent; the five theme families are gone.** The header's theme menu has **Base** (System, Light, Sepia, Dark) and **Accent** (Signal, Brass, Vermilion, Violet, Sakura, Blue, Green, with the site's own tagged *default*, and *Site colour* first on a site with a custom hex). The toggle beside it cycles System → Light → Sepia → Dark; on a phone both lists are in the menu sheet. A site opens on the reader's system setting in the accent from its `site.json` (a named id such as `brass`, or a hex), rendered as `<html data-accent>` so it is right before any script runs; the hub opens on Dark in Signal, already in the server markup. A reader's accent is stored only while it differs from the site's. A stored theme from before carries over once (Archive light becomes Sepia) and the old keys are deleted. Type is Archivo, IBM Plex Sans and IBM Plex Mono; status text reads at 4.5:1 on its tinted fill on every ground; charts have fixed colours per ground; the browser bar follows the ground (a light/dark pair for a site, dark for the hub).
diff --git a/export/app/components/Footer.tsx b/export/app/components/Footer.tsx
@@ -8,7 +8,6 @@ import {
resolveSocialLinks,
} from "yt-dlp-transcript-common/lib/site";
import {
- KOFI_URL,
PROJECT_NAME,
PROJECT_URL,
} from "yt-dlp-transcript-common/lib/project";
@@ -95,16 +94,7 @@ export default function Footer() {
</a>
</span>
</span>
- {/* The social row: the operator's links (admin data, recoloured to
- currentColor by normalizeSocialSvg), then Ko-fi, always LAST.
- Ko-fi is on every build — site and hub — by the operator's
- 2026-09-26 ruling. It is the project's tip jar, not an operator
- link, so it never goes through normalizeSocialSvg: it is Ko-fi's
- official symbol, vendored byte-for-byte as public/kofi-symbol.svg
- and shown in Ko-fi's own colours. It is an <img>, not inlined,
- because the file carries a <mask id> that would collide if the
- markup were ever inlined twice. No copy: the accessible name is
- exactly "Ko-fi" and nothing is visible but the mark. */}
+ {socialLinks.length > 0 && (
<ul className="flex items-center gap-3 list-none">
{socialLinks.map((link, i) => (
<li key={`${link.url}-${i}`}>
@@ -119,28 +109,8 @@ export default function Footer() {
/>
</li>
))}
- <li>
- <a
- href={KOFI_URL}
- title="Ko-fi"
- aria-label="Ko-fi"
- target="_blank"
- rel="noopener noreferrer"
- className="inline-block w-5 h-5 hover:opacity-80 transition-opacity"
- >
- {/* A static file served as-is (images are unoptimized in this
- static export, and next/image adds nothing for an SVG). */}
- {/* eslint-disable-next-line @next/next/no-img-element */}
- <img
- src="/kofi-symbol.svg"
- alt=""
- width={20}
- height={20}
- className="block w-full h-full object-contain"
- />
- </a>
- </li>
</ul>
+ )}
</div>
</div>
{related.length > 0 && (
diff --git a/export/e2e-hub/official-instances.spec.ts b/export/e2e-hub/official-instances.spec.ts
@@ -1,5 +1,4 @@
import { expect, test, type Page, type Route } from "@playwright/test";
-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,17 +116,14 @@ test.describe("hub official instances", () => {
).toHaveCount(0);
});
- test("the hub footer carries the Ko-fi mark, once, last in the social row", async ({
- page,
- }) => {
+ test("the hub has no link to ko-fi.com", async ({ page }) => {
await stubMember(page);
await page.route("**/hub-summary.json", (r) =>
r.fulfill({ status: 404, body: "" }),
);
await page.goto("/");
- // 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);
+ await expect(page.locator("footer")).toBeVisible();
+ await expect(page.locator('a[href*="ko-fi.com"]')).toHaveCount(0);
});
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,6 +1,4 @@
-import { createHash } from "node:crypto";
-import { expect, type Page, type Route } from "@playwright/test";
-import { KOFI_URL } from "../../common/lib/project";
+import type { Page, Route } from "@playwright/test";
import {
channelSubsManifest,
channelTranscriptsManifest,
@@ -175,57 +173,3 @@ 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 { PROJECT_URL } from "../../common/lib/project";
-import { expectKofiMarkLast, installRoutes } from "./helpers";
+import { 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,22 +45,11 @@ test("footer credits the project, ungated and in the same tab", async ({
await expect(footer).toContainText("Built with Archilyzer");
});
-test("every build carries the Ko-fi mark, last in the social row", async ({
- page,
-}) => {
+test("a site page has no link to ko-fi.com", async ({ page }) => {
await installRoutes(page);
await page.goto("/");
- // 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");
+ await expect(page.locator("footer")).toBeVisible();
+ await expect(page.locator('a[href*="ko-fi.com"]')).toHaveCount(0);
});
test("the Downloads eyebrow appears only when something is behind it", async ({
diff --git a/export/public/kofi-symbol.svg b/export/public/kofi-symbol.svg
@@ -1,13 +0,0 @@
-<svg width="241" height="194" viewBox="0 0 241 194" fill="none" xmlns="http://www.w3.org/2000/svg">
-<mask id="mask0_1_219" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-1" y="0" width="242" height="194">
-<path d="M240.469 0.958984H-0.00585938V193.918H240.469V0.958984Z" fill="white"/>
-</mask>
-<g mask="url(#mask0_1_219)">
-<path d="M96.1344 193.911C61.1312 193.911 32.6597 178.256 15.9721 149.829C1.19788 124.912 -0.00585938 97.9229 -0.00585938 67.7662C-0.00585938 49.8876 5.37293 34.3215 15.5413 22.7466C24.8861 12.1157 38.1271 5.22907 52.8317 3.35378C70.2858 1.14271 91.9848 0.958984 114.545 0.958984C151.259 0.958984 161.63 1.4088 176.075 2.85328C195.29 4.76026 211.458 11.932 222.824 23.5955C234.368 35.4428 240.469 51.2624 240.469 69.3627V72.9994C240.469 103.885 219.821 129.733 191.046 136.759C188.898 141.827 186.237 146.871 183.089 151.837L183.006 151.964C172.869 167.632 149.042 193.918 103.401 193.918H96.1281L96.1344 193.911Z" fill="white"/>
-<path d="M174.568 17.9772C160.927 16.6151 151.38 16.1589 114.552 16.1589C90.908 16.1589 70.9008 16.387 54.7644 18.4334C33.3949 21.164 15.2058 37.5285 15.2058 67.7674C15.2058 98.0066 16.796 121.422 29.0741 142.107C42.9425 165.751 66.1302 178.707 96.1412 178.707H103.414C140.242 178.707 160.25 159.156 170.253 143.698C174.574 136.874 177.754 130.058 179.801 123.234C205.947 120.96 225.27 99.3624 225.27 72.9941V69.3577C225.27 40.9432 206.631 21.164 174.574 17.9772H174.568Z" fill="white"/>
-<path d="M15.1975 67.7674C15.1975 37.5285 33.3866 21.164 54.7559 18.4334C70.8987 16.387 90.906 16.1589 114.544 16.1589C151.372 16.1589 160.919 16.6151 174.559 17.9772C206.617 21.1576 225.255 40.937 225.255 69.3577V72.9941C225.255 99.3687 205.932 120.966 179.786 123.234C177.74 130.058 174.559 136.874 170.238 143.698C160.235 159.156 140.228 178.707 103.4 178.707H96.1264C66.1155 178.707 42.9277 165.751 29.0595 142.107C16.7814 121.422 15.1912 98.4563 15.1912 67.7674" fill="#202020"/>
-<path d="M32.2469 67.9899C32.2469 97.3168 34.0654 116.184 43.6127 133.689C54.5225 153.924 74.3018 161.653 96.8117 161.653H103.857C133.411 161.653 147.736 147.329 155.693 134.829C159.558 128.462 162.966 121.417 164.784 112.547L166.147 106.864H174.332C192.521 106.864 208.208 92.09 208.208 73.2166V69.8082C208.208 48.6669 195.024 37.5228 172.058 34.7987C159.102 33.6646 151.372 33.2084 114.538 33.2084C89.7602 33.2084 72.0272 33.4364 58.6152 35.4828C39.7483 38.2134 32.2407 48.8951 32.2407 67.9899" fill="white"/>
-<path d="M166.158 83.6801C166.158 86.4107 168.204 88.4572 171.841 88.4572C183.435 88.4572 189.802 81.8619 189.802 70.9523C189.802 60.0427 183.435 53.2195 171.841 53.2195C168.204 53.2195 166.158 55.2657 166.158 57.9963V83.6866V83.6801Z" fill="#202020"/>
-<path d="M54.5321 82.3198C54.5321 95.732 62.0332 107.326 71.5807 116.424C77.9478 122.562 87.9515 128.93 94.7685 133.022C96.8147 134.157 98.8611 134.841 101.136 134.841C103.866 134.841 106.134 134.157 107.959 133.022C114.782 128.93 124.779 122.562 130.919 116.424C140.694 107.332 148.195 95.7383 148.195 82.3198C148.195 67.7673 137.286 54.8115 121.599 54.8115C112.28 54.8115 105.912 59.5882 101.136 66.1772C96.8147 59.582 90.2259 54.8115 80.9001 54.8115C64.9855 54.8115 54.5256 67.7673 54.5256 82.3198" fill="#FF5A16"/>
-</g>
-</svg>
diff --git a/export/service-worker/site-sw.js b/export/service-worker/site-sw.js
@@ -8,8 +8,7 @@
* Caches:
* SHELL — app shell: hashed /_next/static/* (immutable, cache-first) + HTML
* navigations (network-first, cache fallback) + /icons/* (network-
- * first: they follow the site's accent, which a redeploy can change)
- * + the footer's /kofi-symbol.svg (network-first, the same rule).
+ * first: they follow the site's accent, which a redeploy can change).
* PAGES — every published JSON document: the per-channel shard trees
* (manifest.json + page-NNNN.json), the site-wide flat trees
* (summaries, stats) and the root documents (corpus.json, site.json,
@@ -105,9 +104,7 @@ self.addEventListener("fetch", (event) => {
event.respondWith(cacheFirst(req, SHELL));
return;
}
- // The footer's Ko-fi mark is a static file of its own (/kofi-symbol.svg,
- // on every page): the same rule, so an installed app still draws it offline.
- if (url.pathname.startsWith("/icons/") || url.pathname === "/kofi-symbol.svg") {
+ if (url.pathname.startsWith("/icons/")) {
event.respondWith(networkFirst(req, SHELL));
return;
}
diff --git a/export/service-worker/sw-hub.js b/export/service-worker/sw-hub.js
@@ -94,9 +94,7 @@ self.addEventListener("fetch", (event) => {
event.respondWith(cacheFirst(req, SHELL));
return;
}
- // The footer's Ko-fi mark is a static file of its own (/kofi-symbol.svg,
- // on every page): the same rule, so an installed app still draws it offline.
- if (url.pathname.startsWith("/icons/") || url.pathname === "/kofi-symbol.svg") {
+ if (url.pathname.startsWith("/icons/")) {
event.respondWith(networkFirst(req, SHELL));
return;
}
diff --git a/homepage/CHANGELOG.md b/homepage/CHANGELOG.md
@@ -16,6 +16,8 @@
icons and `/favicon.ico` are no longer committed: `app/icons/[file]/route.ts` and
`app/favicon.ico/route.ts` render them from `common/lib/brand.ts` at build, and
`<head>` gains `icon-32.png`. The OG image is still `/icons/icon-512.png`.
+- **The footer no longer links to Ko-fi.** The "Elsewhere" column is the operator's social
+ icons again, and a homepage with none shows no column.
## 2026-08-12
diff --git a/homepage/app/components/Footer.tsx b/homepage/app/components/Footer.tsx
@@ -5,7 +5,6 @@ import {
} from "yt-dlp-transcript-common/lib/settings";
import { resolveHomepageSocialLinks } from "yt-dlp-transcript-common/lib/homepage";
import {
- KOFI_URL,
PROJECT_NAME,
PROJECT_TAGLINE,
} from "yt-dlp-transcript-common/lib/project";
@@ -50,49 +49,26 @@ export default function Footer() {
</ul>
</nav>
- {/* "Elsewhere": the operator's social accounts, then the project's
- Ko-fi — the official mark, last in the row, no copy (the operator's
- 2026-09-26 ruling: the mark in the social section everywhere; it
- replaced the bare text link of 2026-09-25). Ko-fi is always present,
- so the row always renders. The mark is the project's own vendored
- asset in Ko-fi's official colours (never through normalizeSocialSvg,
- which recolours operator icons to currentColor). */}
- <div className="flex flex-col gap-3">
- <span className="label-machine">Elsewhere</span>
- <ul className="flex items-center gap-4 list-none">
- {socialLinks.map((link, i) => (
- <li key={`${link.url}-${i}`}>
- <a
- href={link.url}
- title={link.label}
- aria-label={link.label}
- target="_blank"
- rel="noopener noreferrer"
- className="inline-block w-5 h-5 text-[var(--faint)] hover:text-[var(--foreground)] transition-colors [&_svg]:w-full [&_svg]:h-full"
- dangerouslySetInnerHTML={{ __html: sizeSocialSvg(link.svg) }}
- />
- </li>
- ))}
- <li>
- <a
- href={KOFI_URL}
- title="Ko-fi"
- aria-label="Ko-fi"
- target="_blank"
- rel="noopener noreferrer"
- className="inline-block w-5 h-5 transition-opacity hover:opacity-80"
- >
- <img
- src="/kofi-symbol.svg"
- alt=""
- width={20}
- height={20}
- className="w-full h-full"
- />
- </a>
- </li>
- </ul>
- </div>
+ {socialLinks.length > 0 && (
+ <div className="flex flex-col gap-3">
+ <span className="label-machine">Elsewhere</span>
+ <ul className="flex items-center gap-4 list-none">
+ {socialLinks.map((link, i) => (
+ <li key={`${link.url}-${i}`}>
+ <a
+ href={link.url}
+ title={link.label}
+ aria-label={link.label}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="inline-block w-5 h-5 text-[var(--faint)] hover:text-[var(--foreground)] transition-colors [&_svg]:w-full [&_svg]:h-full"
+ dangerouslySetInnerHTML={{ __html: sizeSocialSvg(link.svg) }}
+ />
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
</div>
</footer>
);
diff --git a/homepage/e2e/marketing.spec.ts b/homepage/e2e/marketing.spec.ts
@@ -1,7 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import { test, expect } from "@playwright/test";
-import { KOFI_URL } from "../../common/lib/project";
// The home page's job is to say what Archilyzer is and offer the download.
// Nothing asserted here names a count, a site or a headline number. The numeric
@@ -126,27 +125,9 @@ test("the headings after the H1 are in Title Case", async ({ page }) => {
).toBeVisible();
});
-test("the footer's social row ends with the Ko-fi mark", async ({ page }) => {
- const footer = page.locator("footer");
- const kofi = 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");
- // The mark, not copy: an image with an empty alt inside a link whose
- // accessible name is exactly "Ko-fi", and no visible text.
- await expect(kofi).toHaveText("");
- const img = kofi.locator("img");
- await expect(img).toHaveAttribute("src", "/kofi-symbol.svg");
- await expect(img).toHaveAttribute("alt", "");
- expect(await img.evaluate((el) => (el as HTMLImageElement).naturalWidth)).toBeGreaterThan(0);
- // Last in the row (the footer has more than one list; pick the one that
- // holds the mark).
- const row = footer.locator("ul:has(a[aria-label='Ko-fi'])");
- await expect(row).toHaveCount(1);
- await expect(row.locator("li").last().locator("a[aria-label='Ko-fi']")).toHaveCount(1);
- // No other Ko-fi link anywhere on the page.
- await expect(page.locator(`a[href="${KOFI_URL}"]`)).toHaveCount(1);
+test("the home page has no link to ko-fi.com", async ({ page }) => {
+ await expect(page.locator("footer")).toBeVisible();
+ await expect(page.locator('a[href*="ko-fi.com"]')).toHaveCount(0);
});
test("branded 404 for an unknown path", async ({ page }) => {
diff --git a/homepage/public/kofi-symbol.svg b/homepage/public/kofi-symbol.svg
@@ -1,13 +0,0 @@
-<svg width="241" height="194" viewBox="0 0 241 194" fill="none" xmlns="http://www.w3.org/2000/svg">
-<mask id="mask0_1_219" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-1" y="0" width="242" height="194">
-<path d="M240.469 0.958984H-0.00585938V193.918H240.469V0.958984Z" fill="white"/>
-</mask>
-<g mask="url(#mask0_1_219)">
-<path d="M96.1344 193.911C61.1312 193.911 32.6597 178.256 15.9721 149.829C1.19788 124.912 -0.00585938 97.9229 -0.00585938 67.7662C-0.00585938 49.8876 5.37293 34.3215 15.5413 22.7466C24.8861 12.1157 38.1271 5.22907 52.8317 3.35378C70.2858 1.14271 91.9848 0.958984 114.545 0.958984C151.259 0.958984 161.63 1.4088 176.075 2.85328C195.29 4.76026 211.458 11.932 222.824 23.5955C234.368 35.4428 240.469 51.2624 240.469 69.3627V72.9994C240.469 103.885 219.821 129.733 191.046 136.759C188.898 141.827 186.237 146.871 183.089 151.837L183.006 151.964C172.869 167.632 149.042 193.918 103.401 193.918H96.1281L96.1344 193.911Z" fill="white"/>
-<path d="M174.568 17.9772C160.927 16.6151 151.38 16.1589 114.552 16.1589C90.908 16.1589 70.9008 16.387 54.7644 18.4334C33.3949 21.164 15.2058 37.5285 15.2058 67.7674C15.2058 98.0066 16.796 121.422 29.0741 142.107C42.9425 165.751 66.1302 178.707 96.1412 178.707H103.414C140.242 178.707 160.25 159.156 170.253 143.698C174.574 136.874 177.754 130.058 179.801 123.234C205.947 120.96 225.27 99.3624 225.27 72.9941V69.3577C225.27 40.9432 206.631 21.164 174.574 17.9772H174.568Z" fill="white"/>
-<path d="M15.1975 67.7674C15.1975 37.5285 33.3866 21.164 54.7559 18.4334C70.8987 16.387 90.906 16.1589 114.544 16.1589C151.372 16.1589 160.919 16.6151 174.559 17.9772C206.617 21.1576 225.255 40.937 225.255 69.3577V72.9941C225.255 99.3687 205.932 120.966 179.786 123.234C177.74 130.058 174.559 136.874 170.238 143.698C160.235 159.156 140.228 178.707 103.4 178.707H96.1264C66.1155 178.707 42.9277 165.751 29.0595 142.107C16.7814 121.422 15.1912 98.4563 15.1912 67.7674" fill="#202020"/>
-<path d="M32.2469 67.9899C32.2469 97.3168 34.0654 116.184 43.6127 133.689C54.5225 153.924 74.3018 161.653 96.8117 161.653H103.857C133.411 161.653 147.736 147.329 155.693 134.829C159.558 128.462 162.966 121.417 164.784 112.547L166.147 106.864H174.332C192.521 106.864 208.208 92.09 208.208 73.2166V69.8082C208.208 48.6669 195.024 37.5228 172.058 34.7987C159.102 33.6646 151.372 33.2084 114.538 33.2084C89.7602 33.2084 72.0272 33.4364 58.6152 35.4828C39.7483 38.2134 32.2407 48.8951 32.2407 67.9899" fill="white"/>
-<path d="M166.158 83.6801C166.158 86.4107 168.204 88.4572 171.841 88.4572C183.435 88.4572 189.802 81.8619 189.802 70.9523C189.802 60.0427 183.435 53.2195 171.841 53.2195C168.204 53.2195 166.158 55.2657 166.158 57.9963V83.6866V83.6801Z" fill="#202020"/>
-<path d="M54.5321 82.3198C54.5321 95.732 62.0332 107.326 71.5807 116.424C77.9478 122.562 87.9515 128.93 94.7685 133.022C96.8147 134.157 98.8611 134.841 101.136 134.841C103.866 134.841 106.134 134.157 107.959 133.022C114.782 128.93 124.779 122.562 130.919 116.424C140.694 107.332 148.195 95.7383 148.195 82.3198C148.195 67.7673 137.286 54.8115 121.599 54.8115C112.28 54.8115 105.912 59.5882 101.136 66.1772C96.8147 59.582 90.2259 54.8115 80.9001 54.8115C64.9855 54.8115 54.5256 67.7673 54.5256 82.3198" fill="#FF5A16"/>
-</g>
-</svg>
diff --git a/plans/STATE.md b/plans/STATE.md
@@ -35,6 +35,7 @@ below this block's homepage entry).
text link is gone. SUPERSEDES the 2026-09-25 "hub + homepage only" ruling on the operator's word
(2026-09-26: "show kofi in the social section on all sites"). Homepage untouched. Live only after
every site + the hub rebuild (runbook steps 5–6). Plan: [`kofi-mark-on-sites.md`](kofi-mark-on-sites.md).
+ **Reversed by `export/drop-kofi`** (Ko-fi dropped for now, 2026-09-26; record in `release-10.md`).
- **Slice N, the whole-recording fetch downloads anyway + metadata history — merged `6a1678a0`**
(`editor/full-fetch-media` @ `115f9656`, reviewed SHIP after fixes; full editor e2e 641/2/12 of 655,
both failures classified: `tags.spec:220` load flake 3/3 alone, `cookies-mode.spec:241` a pre-existing
@@ -155,7 +156,7 @@ report-to-video scripts on disk are `main`'s, above). Live sites:
yet: [`homepage-deploy-from-ui.md`](homepage-deploy-from-ui.md);
- https://jeralyzer.pages.dev — **the brand, in Signal**, deploy `35593886` (2026-09-26 11:06, above);
- the other four official sites at release 8's slices Z + E (17:45 / 18:10 deploys); they carry no
- Ko-fi — until release 10's rebuild, when slice O puts the mark on every site.
+ Ko-fi, and with Ko-fi dropped (`export/drop-kofi`) release 10's rebuild will not add it.
**The release 10 candidates of 2026-09-25 evening are all done** (L1 and L2 above). Next after
release 10's rollout: the homepage deploy from `/sites` ([`homepage-deploy-from-ui.md`](homepage-deploy-from-ui.md),
@@ -164,7 +165,9 @@ one small slice), Phase 4 slice 3 (`plans/one-core.md`), the LM chat-only tier c
**The 2026-09-25 evening plan is complete** (steps A–C: release 9 F + C1 live on :3001; C1b, C2, C3
on the hub and the homepage). The operator's rulings (C2's federated search as proposed; a bare Ko-fi
-link on the homepage and hub footers only — superseded 2026-09-26 by slice O, the mark on every site; the Paramount Tactical filter clear) are recorded in
+link on the homepage and hub footers only — superseded 2026-09-26 by slice O, the mark on every site,
+and then **Ko-fi dropped for now** (2026-09-26, `export/drop-kofi`: no Ko-fi link or asset on any
+site, the hub or the homepage); the Paramount Tactical filter clear) are recorded in
`release-9.md`. Paramount Tactical downloads in full at lane pace since the operator cleared its
`Quartering` include filter (19:35). See the
[release-9 rollout record](release-9.md#rollout-2026-09-25-evening--0213f6c8-live-on-3001-third-restart-of-the-day).
diff --git a/plans/release-10.md b/plans/release-10.md
@@ -1779,6 +1779,76 @@ the parent's.
`--json '{"workspace":"site","version":"next"}'` (expect a 400 naming the three workspaces).
The route has no read-only mode: `release show` is the CLI's.
+### Ko-fi dropped (2026-09-26)
+
+The operator dropped Ko-fi for now (2026-09-26). Branch
+`export/drop-kofi` off `main` `b1e8827b`, worktree `/home/user/Projects/export-drop-kofi`, one Opus
+implementer. It undoes three things: release 9 slice C1b's bare "Ko-fi" link (hub and homepage
+footers, live on both), slice O's official mark on every site's and the hub's social row (merged
+`6a5167a7`, never deployed) and the homepage's mark (`9801a034` + `b1e8827b`).
+
+- **Export footer** (every site and the hub): the social row is the operator's links alone, under
+ its old `socialLinks.length > 0` guard, so a site with none renders no empty list. Byte-identical
+ to the file before C1b apart from the brand's `BrandMark` changes.
+- **Homepage footer:** byte-identical to the file before C1b. The "Elsewhere" column renders only
+ when the operator has social links.
+- **Removed:** `KOFI_URL` (`common/lib/project.ts`) and its comment; `export/public/kofi-symbol.svg`
+ and `homepage/public/kofi-symbol.svg`; the `/kofi-symbol.svg` rule in `site-sw.js` and `sw-hub.js`
+ and the site worker's header line. `SHELL` stays `"shell-v2"` and `VERSION` `"v1"`, so no cache
+ moves; `/icons/` stays network-first. Both workers equal slice O's parent byte for byte.
+- **Nothing else to remove.** Slice O added no `_headers` entry, precache list, icon list or
+ `normalizeSocialSvg` exception, and `AGENTS.md`, `homepage/content/**` and the docs never named
+ Ko-fi.
+- **Tests:** the Ko-fi case per worker in `serviceWorkerRouting.test.ts`, `expectKofiMarkLast` and
+ `KOFI_SYMBOL_SHA256` (`export/e2e/helpers.ts` is back to slice O's parent) and the three Ko-fi
+ e2e tests are gone. One negative check per surface replaces them: once the footer is visible, no
+ `a[href*="ko-fi.com"]` on the page (`site-branding.spec.ts`, `e2e-hub/official-instances.spec.ts`,
+ `homepage/e2e/marketing.spec.ts`). **They bite:** with `b1e8827b`'s footers, `KOFI_URL` and
+ SVGs put back, all three fail (expected 0, received 1).
+- **Changelogs** (no released section touched): export `[Unreleased]` loses slice O's bullet and
+ gains "The hub no longer links to Ko-fi."; editor `[Unreleased]` (the hub and homepage
+ convention) gains "The hub and the homepage no longer link to Ko-fi."; homepage `[Unreleased]`
+ gains "The footer no longer links to Ko-fi."
+
+| sha | what |
+|---|---|
+| `70f0a9ea` | `export:` the footer's social row drops the mark and is guarded again; `public/kofi-symbol.svg` removed |
+| `a8658444` | `export:` both service workers drop the `/kofi-symbol.svg` rule; the worker test's Ko-fi case removed |
+| `741133e4` | `export(e2e):` a site page and the hub have no link to ko-fi.com; the helper and its hash removed |
+| `af516047` | `homepage:` the Elsewhere column drops the mark and renders only with social links; its SVG removed; e2e negative |
+| `a36fd3d9` | `common:` `KOFI_URL` removed |
+| `37ffc782` | `export, editor, homepage:` the `[Unreleased]` bullets |
+| _this_ | `plans:` this record; `STATE.md`'s Ko-fi ruling |
+
+**Gates**, all from the worktree root; the logs are `k-*.log` in the job's scratch dir.
+- **tsc** (`pnpm -r --no-bail --workspace-concurrency=1 exec tsc --noEmit`): clean before each of
+ the five code commits, and on the tip tree (the changelog commit is Markdown only).
+- **common 2,013/2,013** (slice P's 2,015 minus the two worker cases), 47 s; **editor unit 85/85**;
+ **`test:scripts` 174 + 1 skipped**; **mcp 269/269**.
+- **Builds:** export `next build` site mode, ok in 29 s; `INSTANCE_MODE=hub` `next build`, ok in
+ 32 s; homepage `next build` (its `public/` data copied from the primary), ok in 16 s. After each,
+ `out/icons` has its 7 files and there is no `out/kofi-symbol.svg`. `grep -rIl -i ko-fi out` finds
+ only (a) `/changelog`, which renders export's `[Unreleased]` "no longer links" bullet, and (b) on
+ the export builds, corpus data linked from the primary (`subs/friday-night-tights` live chat, a
+ viewer's `ko-fi.com` link; a `the-quartering-rumble` transcript). No `ko-fi.com` href anywhere
+ outside that data. The homepage's case-insensitive `kofi` hits are a video id in `stats/`.
+- **e2e** (queued, detached, `node scripts/worktree.mjs run -- …`; no queue wait):
+
+ | run | passed | failed | time |
+ |---|---|---|---|
+ | **export full** | **204** | **0** | **8.7 min** |
+ | **`e2e:hub` full** | **24** | **0** | **57.1 s** |
+ | **homepage full** | **27** | **0** | **50.4 s** |
+ | bite check (`b1e8827b`'s Ko-fi code, `-g ko-fi`), site + hub + homepage | 0 | 1 + 1 + 1 | — |
+
+- **Numbers tool: none.**
+
+**Left.** `plans/` keeps its Ko-fi history (release 9 C1b, slice O, `kofi-mark-on-sites.md`), as a
+record. **Rollout:** the hub and the homepage keep the Ko-fi link they were last deployed with (at
+least C1b's text link) until they are rebuilt and redeployed; no site was ever deployed with Ko-fi,
+so their release 10 rebuild simply does not add it. An installed PWA picks up the worker change on
+its next update check, and no data cache moves.
+
## Rollout
Nothing is rolled out, except that **Jeralyzer is already on the brand, in Signal** (a build-deploy