Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ repos:

- id: lint-md-links
name: lint markdown links
entry: lychee --offline --no-progress --include-fragments --exclude-path node_modules --exclude-path experiments --exclude-path docs/archived-roadmap.md
entry: lychee --offline --no-progress --include-fragments --exclude-path node_modules --exclude-path experiments --exclude-path docs/archived-roadmaps/2026-07.md
language: system
files: '\.md$'
exclude: '^\.'
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ mint-cf-worker-test: wasm-stage
@echo "==> Worker smoke tests passed"

lint-md-links:
lychee --offline --no-progress --include-fragments --exclude-path node_modules --exclude-path experiments --exclude-path docs/archived-roadmap.md '**/*.md'
lychee --offline --no-progress --include-fragments --exclude-path node_modules --exclude-path experiments --exclude-path docs/archived-roadmaps/2026-07.md '**/*.md'

define run-timed
@start=$$(date +%s); \
Expand Down
9 changes: 8 additions & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,14 @@ export default defineConfig({
items: getMarkdownFiles("contributing", "contributing"),
},
{ text: "Roadmap", link: "/roadmap" },
{ text: "Archived roadmaps", link: "/archived-roadmap" },
{
Comment thread
ascerra marked this conversation as resolved.
text: "Archived roadmaps",
collapsed: true,
items: [
{ text: "Index", link: "/archived-roadmaps/" },
...getMarkdownFiles("archived-roadmaps", "archived-roadmaps").reverse(),
],
},
{ text: "Landscape", link: "/landscape" },
{
text: "Architecture Decisions",
Expand Down
18 changes: 16 additions & 2 deletions docs/.vitepress/seo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ describe("canonicalUrl", () => {
expect(canonicalUrl("index.md")).toBe("https://fullsend.sh/docs/guides/getting-started/");
});

it("resolves the old archived-roadmap URL to the dated archive index", () => {
expect(canonicalUrl("archived-roadmap.md")).toBe("https://fullsend.sh/docs/archived-roadmaps/");
});

it("resolves a directory index to a trailing-slash URL", () => {
expect(canonicalUrl("agents/index.md")).toBe("https://fullsend.sh/docs/agents/");
});
Expand Down Expand Up @@ -96,6 +100,10 @@ describe("isIndexablePage", () => {
expect(isIndexablePage("404.md")).toBe(false);
});

it("excludes the archived-roadmap redirect stub from indexing", () => {
expect(isIndexablePage("archived-roadmap.md")).toBe(false);
});

it("treats content pages as indexable", () => {
expect(isIndexablePage("index.md")).toBe(true);
expect(isIndexablePage("agents/triage.md")).toBe(true);
Expand Down Expand Up @@ -128,6 +136,8 @@ describe("isSitemapUrl", () => {
it("excludes the redirecting root and non-content URLs", () => {
expect(isSitemapUrl("")).toBe(false);
expect(isSitemapUrl("/")).toBe(false);
expect(isSitemapUrl("archived-roadmap")).toBe(false);
expect(isSitemapUrl("archived-roadmap.html")).toBe(false);
expect(isSitemapUrl("experiments/0000-experiment-template/")).toBe(false);
expect(isSitemapUrl("experiments/example/SKILL")).toBe(false);
});
Expand All @@ -139,10 +149,14 @@ describe("isSitemapUrl", () => {

describe("pageRobotsHead", () => {
it("marks the directly reachable 404 asset noindex", () => {
expect(pageRobotsHead("404.md")).toEqual([
expect(pageRobotsHead("404.md")).toEqual([["meta", { name: "robots", content: "noindex" }]]);
expect(pageRobotsHead("agents/triage.md")).toEqual([]);
});

it("marks the archived-roadmap redirect stub noindex", () => {
expect(pageRobotsHead("archived-roadmap.md")).toEqual([
["meta", { name: "robots", content: "noindex" }],
]);
expect(pageRobotsHead("agents/triage.md")).toEqual([]);
});
});

Expand Down
14 changes: 11 additions & 3 deletions docs/.vitepress/seo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const OG_IMAGE = `${DOCS_URL_BASE}img/logo.png`;
/** Canonical destinations for pages that are redirects rather than content. */
const CANONICAL_REDIRECTS: Record<string, string> = {
"index.md": "guides/getting-started/",
"archived-roadmap.md": "archived-roadmaps/",
};

/** Non-content path: template placeholder or repo-metadata (ALL-CAPS) name. */
Expand Down Expand Up @@ -101,12 +102,14 @@ export function pageSeoHead({
* VitePress injects a custom 404.md that can't be disabled.
*/
export function isIndexablePage(page: string): boolean {
return page !== "404.md" && !isNonContentPath(page);
return page !== "404.md" && page !== "archived-roadmap.md" && !isNonContentPath(page);
}

/** Robots metadata for pages that must not enter search indexes. */
export function pageRobotsHead(page: string): HeadConfig[] {
return page === "404.md" ? [["meta", { name: "robots", content: "noindex" }]] : [];
return page === "404.md" || page === "archived-roadmap.md"
? [["meta", { name: "robots", content: "noindex" }]]
: [];
}

/**
Expand All @@ -117,7 +120,12 @@ export function pageRobotsHead(page: string): HeadConfig[] {
*/
export function isSitemapUrl(url: string): boolean {
Comment thread
ascerra marked this conversation as resolved.
const normalized = url.replace(/^\/+|\/+$/g, "");
return normalized !== "" && !isNonContentPath(normalized);
return (
normalized !== "" &&
normalized !== "archived-roadmap" &&
normalized !== "archived-roadmap.html" &&
!isNonContentPath(normalized)
);
}

/** Site-wide SEO head tags that are identical on every page. */
Expand Down
17 changes: 7 additions & 10 deletions docs/.vitepress/theme/searchQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ describe("textContainsPhrases", () => {
});

it("returns true when the phrase appears in the text", () => {
expect(
textContainsPhrases("The eval scenario runner starts here.", ["eval scenario"]),
).toBe(true);
expect(textContainsPhrases("The eval scenario runner starts here.", ["eval scenario"])).toBe(
true,
);
});

it("returns false when the phrase does not appear adjacent", () => {
expect(
textContainsPhrases("The eval of each scenario is different.", ["eval scenario"]),
).toBe(false);
expect(textContainsPhrases("The eval of each scenario is different.", ["eval scenario"])).toBe(
false,
);
});

it("matches case-insensitively", () => {
Expand All @@ -110,10 +110,7 @@ describe("textContainsPhrases", () => {
).toBe(true);

expect(
textContainsPhrases("eval scenario but no harness here", [
"eval scenario",
"harness config",
]),
textContainsPhrases("eval scenario but no harness here", ["eval scenario", "harness config"]),
).toBe(false);
});

Expand Down
7 changes: 4 additions & 3 deletions docs/.vitepress/theme/searchQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ export function textContainsPhrases(text: string, phrases: string[]): boolean {
* result's concatenated title + text content. Results with no text
* content are kept (graceful degradation).
*/
export function filterByPhrases<
T extends { text?: string; title?: string; titles?: string[] },
>(results: T[], phrases: string[]): T[] {
export function filterByPhrases<T extends { text?: string; title?: string; titles?: string[] }>(
results: T[],
phrases: string[],
): T[] {
if (phrases.length === 0) return results;
return results.filter((r) => {
const content = [...(r.titles || []), r.title || "", r.text || ""].join(" ");
Expand Down
Loading
Loading