From 5c38b0ae33ee31a3b8b57a23613da72e6da2a5ad Mon Sep 17 00:00:00 2001 From: saifmohamedsv Date: Sun, 30 Aug 2026 13:30:33 +0300 Subject: [PATCH 1/3] =?UTF-8?q?feat(docs):=20AI-friendly=20docs=20?= =?UTF-8?q?=E2=80=94=20llms.txt,=20llms-full.txt,=20Copy-for-AI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the docs site machine/LLM-friendly, all generated from the hook registry so nothing can drift: - app/llms.txt — concise llmstxt.org index (H1 + summary + install + key pages + every hook as a link with one-liner) - app/llms-full.txt — whole-library dump via the shared Markdown renderer - app/docs/[slug]/llms.txt — per-hook Markdown twin of each hook page - lib/hook-markdown.ts — THE single hook→Markdown renderer (registry + hook-docs + hook-sources), reused by all three surfaces + the button - lib/llms.ts — index/full builders - components/hook-ai-actions.tsx — "Copy for AI" (reuses CopyButton) + View as Markdown + Open in ChatGPT/Claude, wired into hook-page.tsx - copy-button.tsx — optional labeled pill variant (backward compatible) - robots.ts / sitemap.ts / layout.tsx — advertise the llms files - docs index — "Copy all hooks for AI" affordance Gate green (lint + typecheck + build). Derives from HOOKS; no hardcoded list or count. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_011u9hmtMzmEShNGeBvGXYGL --- apps/docs/app/docs/[slug]/llms.txt/route.ts | 31 +++++++++ apps/docs/app/docs/page.tsx | 34 ++++++++++ apps/docs/app/layout.tsx | 9 +++ apps/docs/app/llms-full.txt/route.ts | 14 ++++ apps/docs/app/llms.txt/route.ts | 14 ++++ apps/docs/app/robots.ts | 4 +- apps/docs/app/sitemap.ts | 12 ++++ apps/docs/components/copy-button.tsx | 25 +++++++ apps/docs/components/hook-ai-actions.tsx | 59 ++++++++++++++++ apps/docs/components/hook-page.tsx | 11 ++- apps/docs/lib/hook-markdown.ts | 75 +++++++++++++++++++++ apps/docs/lib/llms.ts | 52 ++++++++++++++ 12 files changed, 336 insertions(+), 4 deletions(-) create mode 100644 apps/docs/app/docs/[slug]/llms.txt/route.ts create mode 100644 apps/docs/app/llms-full.txt/route.ts create mode 100644 apps/docs/app/llms.txt/route.ts create mode 100644 apps/docs/components/hook-ai-actions.tsx create mode 100644 apps/docs/lib/hook-markdown.ts create mode 100644 apps/docs/lib/llms.ts diff --git a/apps/docs/app/docs/[slug]/llms.txt/route.ts b/apps/docs/app/docs/[slug]/llms.txt/route.ts new file mode 100644 index 0000000..fc019e2 --- /dev/null +++ b/apps/docs/app/docs/[slug]/llms.txt/route.ts @@ -0,0 +1,31 @@ +import { hookToMarkdown } from "@/lib/hook-markdown"; +import { HOOKS, getHook } from "@/lib/hooks-registry"; + +/* /docs//llms.txt — the machine-readable twin of every hook page: that + one hook rendered through the shared Markdown renderer. Prerendered for every + hook alongside its HTML page. */ +export function generateStaticParams() { + return HOOKS.map(({ slug }) => ({ slug })); +} + +export async function GET( + _request: Request, + { params }: { params: Promise<{ slug: string }> }, +): Promise { + const { slug } = await params; + const hook = getHook(slug); + + if (!hook) { + return new Response("Not found\n", { + status: 404, + headers: { "Content-Type": "text/plain; charset=utf-8" }, + }); + } + + return new Response(`${hookToMarkdown(hook)}\n`, { + headers: { + "Content-Type": "text/plain; charset=utf-8", + "Cache-Control": "public, max-age=0, must-revalidate", + }, + }); +} diff --git a/apps/docs/app/docs/page.tsx b/apps/docs/app/docs/page.tsx index 16b5458..3d2c79c 100644 --- a/apps/docs/app/docs/page.tsx +++ b/apps/docs/app/docs/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { CodeBlock } from "@/components/code-block"; import { HookCard } from "@/components/hook-card"; +import { ArrowRightIcon } from "@/components/icons"; import { InstallCommand } from "@/components/install-command"; import { CATEGORY_LABELS, @@ -46,6 +47,39 @@ export default function DocsIndexPage() { can try it before you install it.

+
+
+

Building with an LLM?

+

+ Every hook — signatures, usage and source — as plain Markdown for AI + assistants and coding agents, generated from this site so it never + drifts. +

+
+ +
+

Quick start

  1. diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index 797a71d..be2216c 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -27,6 +27,15 @@ export const metadata: Metadata = { default: "hookli — simple React hooks", }, description: TAGLINE, + alternates: { + // Advertise the machine-readable llmstxt.org representations of the site. + types: { + "text/plain": [ + { url: "/llms.txt", title: "hookli llms.txt" }, + { url: "/llms-full.txt", title: "hookli llms-full.txt" }, + ], + }, + }, openGraph: { type: "website", siteName: "hookli", diff --git a/apps/docs/app/llms-full.txt/route.ts b/apps/docs/app/llms-full.txt/route.ts new file mode 100644 index 0000000..f90b4d2 --- /dev/null +++ b/apps/docs/app/llms-full.txt/route.ts @@ -0,0 +1,14 @@ +import { buildLlmsFull } from "@/lib/llms"; + +/* /llms-full.txt — the whole library in one Markdown document (every hook via + the shared renderer). No request input, so it is prerendered at build time. */ +export const dynamic = "force-static"; + +export function GET(): Response { + return new Response(buildLlmsFull(), { + headers: { + "Content-Type": "text/plain; charset=utf-8", + "Cache-Control": "public, max-age=0, must-revalidate", + }, + }); +} diff --git a/apps/docs/app/llms.txt/route.ts b/apps/docs/app/llms.txt/route.ts new file mode 100644 index 0000000..e8c126a --- /dev/null +++ b/apps/docs/app/llms.txt/route.ts @@ -0,0 +1,14 @@ +import { buildLlmsIndex } from "@/lib/llms"; + +/* /llms.txt — the concise, llmstxt.org-standard index. No request input, so it + is prerendered to a static file at build time. */ +export const dynamic = "force-static"; + +export function GET(): Response { + return new Response(buildLlmsIndex(), { + headers: { + "Content-Type": "text/plain; charset=utf-8", + "Cache-Control": "public, max-age=0, must-revalidate", + }, + }); +} diff --git a/apps/docs/app/robots.ts b/apps/docs/app/robots.ts index 77a1b9b..31e4bd3 100644 --- a/apps/docs/app/robots.ts +++ b/apps/docs/app/robots.ts @@ -3,7 +3,9 @@ import { SITE_URL } from "@/lib/site"; export default function robots(): MetadataRoute.Robots { return { - rules: { userAgent: "*", allow: "/" }, + // The llms.txt files are explicitly surfaced (already covered by "/", but + // listing them advertises the AI-friendly index and full dump to crawlers). + rules: { userAgent: "*", allow: ["/", "/llms.txt", "/llms-full.txt"] }, sitemap: `${SITE_URL}/sitemap.xml`, }; } diff --git a/apps/docs/app/sitemap.ts b/apps/docs/app/sitemap.ts index c64f69f..3af12d8 100644 --- a/apps/docs/app/sitemap.ts +++ b/apps/docs/app/sitemap.ts @@ -24,5 +24,17 @@ export default function sitemap(): MetadataRoute.Sitemap { changeFrequency: "yearly", priority: 0.4, }, + { + url: `${SITE_URL}/llms.txt`, + lastModified, + changeFrequency: "monthly", + priority: 0.3, + }, + { + url: `${SITE_URL}/llms-full.txt`, + lastModified, + changeFrequency: "monthly", + priority: 0.3, + }, ]; } diff --git a/apps/docs/components/copy-button.tsx b/apps/docs/components/copy-button.tsx index 38ad1ea..6edf5e4 100644 --- a/apps/docs/components/copy-button.tsx +++ b/apps/docs/components/copy-button.tsx @@ -6,10 +6,14 @@ import { CheckIcon, CopyIcon } from "@/components/icons"; export function CopyButton({ text, label = "Copy to clipboard", + copyLabel, className = "", }: { text: string; label?: string; + /* When set, renders the labeled pill variant with this visible text (e.g. + "Copy for AI") instead of the default icon-only square. */ + copyLabel?: string; className?: string; }) { const [copied, setCopied] = useState(false); @@ -32,6 +36,27 @@ export function CopyButton({ timeoutRef.current = setTimeout(() => setCopied(false), 2000); } + if (copyLabel !== undefined) { + return ( + + ); + } + return (