Archilyzer · Source

archilyzer

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

commit 5b777de29efa14b36d1fe834855ff6ca5fad1b68
parent 15fa8be09a1fddd89e519e661d03dd360b40b32d
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 30 Sep 2026 18:57:59 -0400

export: the Use with AI page is removed; the header's, the menu's, the footer's and Ask AI's Use with AI go to the homepage's AI and MCP doc

Same label, same tab, a plain <a> to AI_DOC_URL, on every site and the hub. The specs that used
the page as a route outside the workspace (first-search, restore-no-refire) leave for /changelog/
through the app router, a client navigation as before; responsive checks /changelog/. New
use-with-ai-link specs (site and hub): every link carries the doc URL, /use-with-ai/ is a 404.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mexport/app/(workspace)/ask/page.tsx | 6+++---
Mexport/app/components/Footer.tsx | 4+++-
Mexport/app/components/Header.tsx | 36++++++++++++++++++++++++------------
Mexport/app/components/MobileMenu.tsx | 25++++++++++++++++++-------
Aexport/e2e-hub/use-with-ai-link.spec.ts | 48++++++++++++++++++++++++++++++++++++++++++++++++
Mexport/e2e/first-search.spec.ts | 26++++++++++++++++++++------
Mexport/e2e/responsive.spec.ts | 2+-
Mexport/e2e/restore-no-refire.spec.ts | 29++++++++++++++++++-----------
Aexport/e2e/use-with-ai-link.spec.ts | 49+++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 184 insertions(+), 41 deletions(-)

diff --git a/export/app/(workspace)/ask/page.tsx b/export/app/(workspace)/ask/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import Link from "next/link"; +import { AI_DOC_URL } from "yt-dlp-transcript-common/lib/project"; import { currentSite } from "../../lib/site"; import { instanceMode } from "../../lib/mode"; import AskHub from "../../ask/AskHub"; @@ -34,9 +34,9 @@ export default function AskPage() { browser, sends the relevant excerpts to your chosen AI, and answers with citations. Nothing is hosted here — your key and the requests stay between your browser and the provider. See{" "} - <Link href="/use-with-ai" className="text-brand hover:underline"> + <a href={AI_DOC_URL} className="text-brand hover:underline"> Use with AI - </Link>{" "} + </a>{" "} for other ways to use {isHub ? "every archive on this hub" : "this archive"}. </p> </header> diff --git a/export/app/components/Footer.tsx b/export/app/components/Footer.tsx @@ -5,6 +5,7 @@ import { resolveSocialLinks, } from "yt-dlp-transcript-common/lib/site"; import { + AI_DOC_URL, PROJECT_NAME, PROJECT_URL, } from "yt-dlp-transcript-common/lib/project"; @@ -68,8 +69,9 @@ export default function Footer() { · </span> )} + {/* The homepage's AI and MCP doc, in the same tab (release 16). */} <a - href="/use-with-ai" + href={AI_DOC_URL} className="underline underline-offset-2 hover:text-foreground transition-colors" > Use with AI diff --git a/export/app/components/Header.tsx b/export/app/components/Header.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { getSettings } from "yt-dlp-transcript-common/lib/settings"; import { resolveSocialLinks } from "yt-dlp-transcript-common/lib/site"; import { headerSocialLinks } from "yt-dlp-transcript-common/lib/socialLinks"; -import { INSTANCES_URL } from "yt-dlp-transcript-common/lib/project"; +import { AI_DOC_URL, INSTANCES_URL } from "yt-dlp-transcript-common/lib/project"; import { ThemeToggle } from "yt-dlp-transcript-common/components/ThemeToggle"; import { SocialLinks } from "yt-dlp-transcript-common/components/SocialLinks"; import { SocialScroll } from "yt-dlp-transcript-common/components/SocialScroll"; @@ -82,12 +82,14 @@ export default function Header() { // The inline nav from lg. Ask AI is new: the chat was only reachable from // the workspace control or from Use with AI, which is not where anyone looks // for it. - const navLinks = [ + // Use with AI is the homepage's AI and MCP doc (release 16 slice DX): off + // this site, so a plain <a> in the same tab, not a client navigation. + const navLinks: { href: string; label: string; external?: boolean }[] = [ { href: "/", label: "Search" }, { href: "/ask/", label: "Ask AI" }, ...(showDuplicates ? [{ href: "/duplicates", label: "Duplicates" }] : []), ...(showDownloads ? [{ href: "/downloads", label: "Downloads" }] : []), - { href: "/use-with-ai", label: "Use with AI" }, + { href: AI_DOC_URL, label: "Use with AI", external: true }, ]; // The sheet also takes Offline on a PWA-shipping site — gated exactly as // Footer.tsx gates it, so the two never disagree about whether this instance @@ -121,15 +123,25 @@ export default function Header() { </Link> <nav className="hidden lg:flex shrink-0 items-center gap-4 text-sm font-medium"> - {navLinks.map((l) => ( - <Link - key={l.href} - href={l.href} - className="text-foreground hover:text-brand transition-colors" - > - {l.label} - </Link> - ))} + {navLinks.map((l) => + l.external ? ( + <a + key={l.href} + href={l.href} + className="text-foreground hover:text-brand transition-colors" + > + {l.label} + </a> + ) : ( + <Link + key={l.href} + href={l.href} + className="text-foreground hover:text-brand transition-colors" + > + {l.label} + </Link> + ), + )} </nav> <div className="flex min-w-0 items-center gap-5 lg:ml-auto"> diff --git a/export/app/components/MobileMenu.tsx b/export/app/components/MobileMenu.tsx @@ -26,7 +26,9 @@ export default function MobileMenu({ links, instancesUrl, }: { - links: { href: string; label: string }[]; + // `external`: off this site (Use with AI, the homepage's AI and MCP doc), so + // a plain <a> in the same tab rather than a client navigation. + links: { href: string; label: string; external?: boolean }[]; // The Archilyzer home's Official Instances (common/lib/project.ts // INSTANCES_URL); absent on the hub, which lists the instances itself. instancesUrl?: string; @@ -60,12 +62,21 @@ export default function MobileMenu({ <nav className="mt-2 flex flex-col px-2"> {links.map((l) => ( <SheetClose asChild key={l.href}> - <Link - href={l.href} - className="rounded-md px-2 py-3 text-base font-medium text-foreground transition-colors hover:bg-accent" - > - {l.label} - </Link> + {l.external ? ( + <a + href={l.href} + className="rounded-md px-2 py-3 text-base font-medium text-foreground transition-colors hover:bg-accent" + > + {l.label} + </a> + ) : ( + <Link + href={l.href} + className="rounded-md px-2 py-3 text-base font-medium text-foreground transition-colors hover:bg-accent" + > + {l.label} + </Link> + )} </SheetClose> ))} {instancesUrl && ( diff --git a/export/e2e-hub/use-with-ai-link.spec.ts b/export/e2e-hub/use-with-ai-link.spec.ts @@ -0,0 +1,48 @@ +import { expect, test, type Locator, type Route } from "@playwright/test"; +import { AI_DOC_URL } from "../../common/lib/project"; + +// Release 16 slice DX, on the hub: no /use-with-ai page; the header's, the +// footer's and Ask AI's "Use with AI" go to the homepage's AI and MCP doc +// (AI_DOC_URL), in the same tab, as plain anchors — the same links a site has. + +async function fulfillJson(route: Route, body: unknown) { + await route.fulfill({ + status: 200, + contentType: "application/json", + headers: { "access-control-allow-origin": "*" }, + body: JSON.stringify(body), + }); +} + +async function expectDocLink(link: Locator) { + await expect(link).toHaveAttribute("href", AI_DOC_URL); + await expect(link).not.toHaveAttribute("target", /./); +} + +test.beforeEach(async ({ page }) => { + // No members: the shelf stays empty and nothing reaches a real origin. + await page.route("**/hub-sites.json", (r) => fulfillJson(r, [])); + await page.route("**/hub-summary.json", (r) => r.fulfill({ status: 404, body: "" })); +}); + +test("the hub's header and footer Use with AI go to the homepage doc", async ({ page }) => { + await page.goto("/"); + await expectDocLink( + page.getByRole("banner").getByRole("link", { name: "Use with AI", exact: true }), + ); + await expectDocLink( + page.getByRole("contentinfo").getByRole("link", { name: "Use with AI", exact: true }), + ); +}); + +test("the hub's Ask AI links Use with AI to the homepage doc", async ({ page }) => { + await page.goto("/ask/"); + await expectDocLink( + page.getByRole("main").getByRole("link", { name: "Use with AI", exact: true }), + ); +}); + +test("the hub has no /use-with-ai page", async ({ page }) => { + const res = await page.request.get("/use-with-ai/"); + expect(res.status()).toBe(404); +}); diff --git a/export/e2e/first-search.spec.ts b/export/e2e/first-search.spec.ts @@ -47,6 +47,24 @@ async function expectClearScreen(page: Page) { await expect(page.getByText(HINT)).toBeVisible(); } +// A route outside the workspace, reached as a client navigation in the same +// page life: the search session unmounts with the workspace and mounts again on +// Back. Until release 16 the header's Use with AI link made that navigation; +// it now leaves the site, and the footer's Changelog is a plain <a> (a new page +// life), so the spec asks the app router itself — `window.next.router`, which +// Next sets for debugging in development and production alike. +async function leaveForChangelog(page: Page) { + await page.evaluate(() => { + ( + window as unknown as { next: { router: { push(href: string): void } } } + ).next.router.push("/changelog/"); + }); + // A dev server compiles /changelog on its first visit. + await expect(page).toHaveURL(/\/changelog\/?$/, { timeout: 20_000 }); + await expect(page.getByRole("heading", { level: 1, name: "Changelog" })).toBeVisible(); + await expect(page.getByTestId("query-builder")).toHaveCount(0); +} + async function expectAllVideos(page: Page) { await expect(page.getByTestId("results-summary")).toHaveText("All videos (3)"); await expect(page.getByTestId("browse-hint")).toBeVisible(); @@ -124,13 +142,9 @@ test.describe("a clear screen until the first Search", () => { await showAll(page); await expectAllVideos(page); // A route outside the workspace: the search session unmounts with it. - await page - .getByRole("banner") - .getByRole("link", { name: "Use with AI", exact: true }) - .click(); - await expect(page).toHaveURL(/\/use-with-ai\/?$/); + await leaveForChangelog(page); await page.goBack(); - await expect(page).not.toHaveURL(/use-with-ai/); + await expect(page).not.toHaveURL(/changelog/); await expectAllVideos(page); }); diff --git a/export/e2e/responsive.spec.ts b/export/e2e/responsive.spec.ts @@ -74,7 +74,7 @@ test.describe("phone layout", () => { "/ask/", "/downloads/", "/duplicates/", - "/use-with-ai/", + "/changelog/", ]) { test(`no horizontal overflow on ${route}`, async ({ page }) => { await page.goto(route); diff --git a/export/e2e/restore-no-refire.spec.ts b/export/e2e/restore-no-refire.spec.ts @@ -57,13 +57,20 @@ async function seedStoredSearch(page: Page) { } // A route outside the workspace unmounts the search session; coming back -// mounts it again in the same page life. -async function leaveForUseWithAi(page: Page) { - await page - .getByRole("banner") - .getByRole("link", { name: "Use with AI", exact: true }) - .click(); - await expect(page).toHaveURL(/use-with-ai/, { timeout: 20_000 }); +// mounts it again in the same page life. Until release 16 the header's Use +// with AI link made that client navigation; it now leaves the site, and the +// footer's Changelog is a plain <a> (a new page life), so the spec asks the app +// router itself — `window.next.router`, which Next sets for debugging in +// development and production alike. +async function leaveForChangelog(page: Page) { + await page.evaluate(() => { + ( + window as unknown as { next: { router: { push(href: string): void } } } + ).next.router.push("/changelog/"); + }); + await expect(page).toHaveURL(/\/changelog\/?$/, { timeout: 20_000 }); + await expect(page.getByRole("heading", { level: 1, name: "Changelog" })).toBeVisible(); + await expect(page.getByTestId("query-builder")).toHaveCount(0); } async function expectHeld(page: Page) { @@ -160,9 +167,9 @@ test.describe("restored search waits for the visitor", () => { // …and the stored query is in the box, held. await expectHeld(page); - await leaveForUseWithAi(page); + await leaveForChangelog(page); await page.goBack(); - await expect(page).not.toHaveURL(/use-with-ai/); + await expect(page).not.toHaveURL(/changelog/); // A second mount in the same visit: still held, still the listing. await expectHeld(page); await expect(page.getByTestId("results-summary")).toHaveText("All videos (1)"); @@ -182,12 +189,12 @@ test.describe("restored search waits for the visitor", () => { await expect(page.getByTestId("results-summary")).toContainText("All videos"); await expect(leafInput(page)).toHaveValue(""); - await leaveForUseWithAi(page); + await leaveForChangelog(page); await page .getByRole("banner") .getByRole("link", { name: "Search", exact: true }) .click(); - await expect(page).not.toHaveURL(/use-with-ai/, { timeout: 20_000 }); + await expect(page).not.toHaveURL(/changelog/, { timeout: 20_000 }); // The plain search page reads the stored session: the query is held, and // the listing shows because the visitor asked earlier in this visit. await expectHeld(page); diff --git a/export/e2e/use-with-ai-link.spec.ts b/export/e2e/use-with-ai-link.spec.ts @@ -0,0 +1,49 @@ +import { expect, test, type Locator } from "@playwright/test"; +import { AI_DOC_URL } from "../../common/lib/project"; +import { installRoutes } from "./helpers"; + +// Release 16 slice DX: a site has no /use-with-ai page. Its "Use with AI" +// links — the header's nav (inline from `lg`, in the slide-out menu below it), +// the footer's and the one on Ask AI — go to the homepage's AI and MCP doc +// (AI_DOC_URL), in the same tab, as plain anchors: the doc is on another +// origin, so there is no client navigation to make. The label is unchanged. + +async function expectDocLink(link: Locator) { + await expect(link).toHaveAttribute("href", AI_DOC_URL); + await expect(link).not.toHaveAttribute("target", /./); +} + +test.beforeEach(async ({ page }) => { + await installRoutes(page); +}); + +test("the header's and the footer's Use with AI go to the homepage doc", async ({ page }) => { + await page.goto("/"); + await expectDocLink( + page.getByRole("banner").getByRole("link", { name: "Use with AI", exact: true }), + ); + await expectDocLink( + page.getByRole("contentinfo").getByRole("link", { name: "Use with AI", exact: true }), + ); +}); + +test("the slide-out menu's Use with AI goes to the homepage doc", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/"); + await page.getByRole("button", { name: "Open menu" }).click(); + await expectDocLink( + page.getByRole("dialog").getByRole("link", { name: "Use with AI", exact: true }), + ); +}); + +test("Ask AI's Use with AI goes to the homepage doc", async ({ page }) => { + await page.goto("/ask/"); + await expectDocLink( + page.getByRole("main").getByRole("link", { name: "Use with AI", exact: true }), + ); +}); + +test("the page is gone", async ({ page }) => { + const res = await page.request.get("/use-with-ai/"); + expect(res.status()).toBe(404); +});