From 422d6622a2faddfa66e2a550a4fa761c79531699 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Wed, 28 Oct 2026 18:38:13 +0800 Subject: [PATCH 1/2] Organize document URLs by category and collection with compact navigation --- apps/docs/scripts/auth-cli.ts | 25 ++++- apps/docs/src/components/Shell.tsx | 25 +++-- apps/docs/src/lib/collections.ts | 2 +- apps/docs/src/lib/docs.ts | 20 ++-- apps/docs/src/pages/docs/[...slug].tsx | 24 +++-- apps/docs/src/styles/globals.css | 39 ++++++++ content/catalog.json | 132 +++++++++++++++++++++---- content/docs/01-getting-started.md | 2 +- 8 files changed, 221 insertions(+), 48 deletions(-) diff --git a/apps/docs/scripts/auth-cli.ts b/apps/docs/scripts/auth-cli.ts index c4c704b..b6a02f3 100644 --- a/apps/docs/scripts/auth-cli.ts +++ b/apps/docs/scripts/auth-cli.ts @@ -182,15 +182,32 @@ async function main() { const searchResponse = await request("/api/search", { headers }); assert.equal(searchResponse.status, 200); const searchIndex = await searchResponse.json(); - assert.ok(searchIndex.some((doc: { slug: string; searchText: string }) => doc.slug === "pretext/04-chapter" && doc.searchText.includes("getSegmentMetrics"))); + assert.ok(searchIndex.some((doc: { slug: string; searchText: string }) => doc.slug === "source-studies/pretext/measurement-cache" && doc.searchText.includes("getSegmentMetrics"))); + for (const doc of searchIndex as { slug: string; sourceSlug: string }[]) { + assert.ok(!doc.slug.split("/").some((part) => /^\d+-/.test(part)), doc.slug); + const legacy = await request(`/docs/${doc.sourceSlug}`, { headers }); + assert.equal(legacy.status, 308, doc.sourceSlug); + assert.equal(new URL(legacy.headers.get("location")!, base).pathname, `/docs/${doc.slug}`); + } let home = ""; for (const route of routes) { const response = await request(route, { headers }); - assert.equal(response.status, 200, route); - if (route === "/") home = await response.text(); + const alias = (searchIndex as { sourceSlug: string }[]).some((doc) => route === `/docs/${doc.sourceSlug}`); + assert.equal(response.status, alias ? 308 : 200, route); + if (alias) continue; + const html = await response.text(); + if (route === "/") home = html; + if (route.startsWith("/docs/")) { + assert.ok(html.includes('aria-label="文档选择"'), route); + assert.ok(html.includes(''), route); + assert.ok(!html.includes('class="nav-back"'), route); + const ids = [...html.matchAll(/ match[1]); + assert.equal(new Set(ids).size, ids.length, `Unique heading anchors: ${route}`); + for (const id of ids) assert.ok(html.includes(`href="#${id}"`), `TOC anchor: ${route}#${id}`); + } } assert.ok(home.includes("资料库")); - assert.ok(home.includes("/docs/pretext/00-overview")); + assert.ok(home.includes("/docs/source-studies/pretext/overview")); const assets = new Set( [...home.matchAll(/(?:src|href)="(\/_next\/static\/[^" ]+)"/g)].map((m) => m[1].replaceAll("&", "&"), diff --git a/apps/docs/src/components/Shell.tsx b/apps/docs/src/components/Shell.tsx index ef4880f..533ea44 100644 --- a/apps/docs/src/components/Shell.tsx +++ b/apps/docs/src/components/Shell.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { useRouter } from "next/router"; import { Icon } from "@/components/Icon"; import { useEffect, useRef, useState, type ReactNode } from "react"; import type { DocLink } from "@/lib/docs"; @@ -36,9 +37,6 @@ export function Navigation({ return (