Skip to content

feat(theme): the agent layer (llms.txt, page actions) and home components - #4

Merged
best merged 4 commits into
masterfrom
theme-0.8.0-agent-layer
Nov 4, 2026
Merged

best merged 4 commits into
masterfrom
theme-0.8.0-agent-layer

Conversation

@best

@best best commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Theme 0.8.0-rc.1: the layer for agents, page actions, and the landing components

Branch theme-0.8.0-agent-layer. Four commits, no push, no npm publish (release approval is the maintainer's). All checks from the repository's AGENTS.md were run on the committed state this session and pass: pnpm lint, pnpm build, pnpm docs:build, pnpm test (272 tests).

What

defineOpenLinkOSConfig now gives every family site a layer agents can read, doc pages get page actions, and the package ships the five landing components Beacon's homepage (WP6) is to be built from. Both packages are bumped to 0.8.0-rc.1 with changelog entries; nothing is published.

1. The layer for agents (on by default, llms: false to turn off)

  • /llms.txt — an index in the llmstxt.org format: one section per sidebar group, pages in sidebar order by their sidebar title, each with its frontmatter description or first paragraph, linking to the page as Markdown. layout: home pages are left out (designed pages, not reading material), though their .md is still served.
  • /llms-full.txt — every page of the locale whole, each with its title and address.
  • Every page as Markdown — the page's own address with .md appended returns its Markdown source, in the dev server and in the build: /guide/quickstart.md, /zh/guide/quickstart.md, /index.md. Rewrites are honoured (the .md sits next to the built page, not the source path).
  • The build stops if the site's public/ provides one of these files; with llms: false they are the site's own again.

Served by the theme's Vite plugin: emitted as assets on build, and in vitepress dev by the existing middleware, which passes module loads (?import, Sec-Fetch-Dest) through to Vite and serves plain document requests (verified with curl: plain /guide.md → text/markdown; charset=utf-8, /guide.md?import → the compiled module).

2. Page actions

At the top of every doc page, in the page's language, from the theme's string tables (PAGE_ACTIONS in src/config/strings.ts):

  • Copy page (复制本页) — fetches the page's .md address and copies it; the label flips to Copied/已复制 for two seconds.
  • Open in Claude (在 Claude 中打开) — https://claude.ai/new?q= with Read <address>.md and answer questions about it. prefilled. The q parameter is documented by Anthropic; the prompt is prefilled, not sent.
  • Open in ChatGPT (在 ChatGPT 中打开) — the same at chatgpt.com/?q=, which is not an officially documented parameter (see Unverified facts).

Plain DOM, no Vue import: the theme's compiled code still never imports Vue (the existing never imports vue test is untouched and passes). The toolbar goes in after the page settles — VitePress hydrates under Suspense and drops nodes inserted too early, so a MutationObserver re-checks once the tree has been quiet for 400 ms, and the router hook re-points the links on every navigation. Verified in a browser against the built site (vitepress preview): the group renders with all three actions and correct hrefs on /components (English) and, with the language cookie pinned to zh, on /zh/components — 复制本页, 在 Claude 中打开, 在 ChatGPT 中打开, lang="zh-CN", the links carrying the Chinese prompt (阅读

.md,回答我关于它的问题。). Note for whoever verifies: the browser's shared openlinkos-lang=en cookie redirects /zh/ pages back to English; re-pin it (openlinkos-lang=zh) to see the Chinese page. The copy action was exercised on the Chinese page: the click fetches /zh/components.md and calls navigator.clipboard.writeText with the page's Markdown (2 689 characters, starting at its frontmatter) — see Unverified facts for the one step this environment cannot settle.

3. Landing components — @openlinkos/vitepress-theme/components

OlHero (title, lead, actions, a command prop with a copy button, a media slot on the right, under the words below 960 px), OlSteps (a numbered timeline with media per step), OlCast (an asciinema recording: poster is all a reader without JavaScript, or with prefers-reduced-motion: reduce, ever sees; with JavaScript and motion, the play button loads the site's own copy of asciinema-player — player and optional player-css props — and the recording starts on click, never by itself; play is decided after mount, so hydration never mismatches), OlLogos (a row of names, logos or text), OlBand (the closing call to action on the brand colour, links styled as buttons, class="ghost" for a quiet one). Both paths of OlCast were exercised in a browser on the built playground: the automation webview itself reports prefers-reduced-motion: reduce, and the component correctly shows the poster alone (no play button in the DOM); with the media query answered no-preference and the component re-mounted through two SPA navigations, the play button renders ("Play recording") and a click loads the player script and stylesheet and mounts AsciinemaPlayer (an .ap-wrapper.ap-hud appears in the host, the poster is removed, no error). A programmatic check at 390 px found no horizontal overflow (scrollWidth 375 within the 390 viewport, no element outside it).

They ship as single-file components for the site's own Vite build to compile. The existing invariant (compiled dist JS never imports vue) is preserved: the barrel re-exports ./OlHero.vue & co., which only the site's compiler touches. For that path vue >= 3.5 is now a peer dependency — under pnpm a vue import from inside the package otherwise fails to resolve during the site's build (reproduced, then fixed by the peer declaration; pnpm build/site builds are green again). @openlinkos/ui is a dependency (workspace:*): the theme loads tokens.css on every page, the head sets data-brand (before first paint, inline), and light-dark() follows VitePress's .dark — so the components take every colour, size and radius from the design system, per brand, in light and dark. A :root { color-scheme: light } / html.dark { color-scheme: dark } guard keeps a forced light theme from following the system into dark.

4. Playground

A bilingual /components page (with sourceHash) exercising all five components — the cast demo uses a hand-written asciicast (public/demo.cast) and its poster (public/cast-poster.svg), with the player loaded from unpkg at click time; a real site serves its own copy, as Beacon already does. The page is in the nav and sidebar of both locales. biome.json skips noUnusedVariables for **/*.vue only: biome cannot see template usage of script bindings, so the rule is a false positive on every SFC.

Components

  • packages/vitepress-theme/src/config/llms.ts — generation (index, full file, page paths).
  • packages/vitepress-theme/src/config/plugin.ts — emission on build, serving in dev, reserved-file check.
  • packages/vitepress-theme/src/theme/actions.ts — the page actions toolbar.
  • packages/vitepress-theme/src/components/ — OlHero.vue, OlSteps.vue, OlCast.vue, OlLogos.vue, OlBand.vue, types.ts, barrel + shim; copied to dist/components/ by scripts/build.ts.
  • packages/vitepress-theme/src/config/strings.ts — PAGE_ACTIONS and COMPONENTS strings, English and Chinese.
  • packages/vitepress-theme/src/theme/style.css — tokens import, colour-scheme guard, action styles.
  • packages/vitepress-theme/src/config/head.ts — the data-brand inline script.
  • Tests: test/llms.test.ts (5 unit tests), test/build.test.ts (+3 integration tests over the built playground, including llms: false and the reserved-file clash).
  • playground/components.md + zh/components.md, playground/public/demo.cast, playground/public/cast-poster.svg, theme registration and nav/sidebar entries.
  • packages/*/package.json at 0.8.0-rc.1, both changelogs, the theme README, pnpm-lock.yaml, biome.json.

llms design

English is the source of truth (the bilingual check exists because translations lag, via sourceHash), so the two languages get symmetric but separate files rather than one mixed index:

  • /llms.txt and /llms-full.txt list only the English pages; /zh/llms.txt and /zh/llms-full.txt only the Chinese ones, at /zh/ addresses.
  • Each index's header block points to the other (> 简体中文 (Simplified Chinese): …/zh/llms.txt, > English: …/llms.txt), so an agent landing on either finds both; an agent that follows a /zh/ page gets Chinese, one that starts at the root gets English.
  • Per-page Markdown is uniform across locales — any address plus .md works — so the page layer needs no language logic at all.

An alternative — one /llms.txt covering both languages — was rejected: it would double the index an agent has to read and mix pages whose Chinese may lag their English. Serving Chinese translations from the English index would mislead agents about which text is authoritative.

Unverified facts

  • ChatGPT ?q= is a community-discovered parameter, not officially documented by OpenAI; it prefills the composer today but may change. Claude's ?q= is documented.
  • The copy action's final step: the click handler was exercised end-to-end on /zh/components (the fetch of /zh/components.md returns the page's Markdown and navigator.clipboard.writeText is called with it), but the automation webview never settles the OS clipboard write, so the label flip to Copied/已复制 was not observed in a browser; the label logic (Copied for two seconds, then back; "Copy failed" on error) is covered by review only. Clipboard writes need a secure context (localhost or HTTPS), which every real deployment is.
  • OlCast's play path was verified with the media query patched to no-preference (the automation webview forces prefers-reduced-motion: reduce, and under it the component correctly renders the poster alone); with motion allowed the click loaded asciinema-player from unpkg and mounted the player. Not verified: the recording playing for its full duration (the demo cast is 9 lines), and playback behind the site's own self-hosted copy of the player rather than unpkg.
  • Screenshots (packages/vitepress-theme/screenshots/, committed to the branch): 390-dark-top.png, 390-light-top.png, 390-light-band.png, 1440-dark-hero.png, 1440-light-hero.png — retaken fresh from the rebuilt playground this session; all four width/mode combinations show no layout defects. What they do not cover: the Chinese page, OlSteps' media column at 390 px (the band shot covers OlBand; the steps sit above it in 390-light-top's scroll path), and OlLogos at 1440 px (it is below the fold of 1440-light-hero.png).
  • vitepress preview serves a stale file list after a rebuild (new hashed chunks 404 until it is restarted) — a property of its sirv setup, not of this change, but it cost an hour of phantom debugging in an earlier session and is worth knowing when verifying.

Releasing

Per the repository's AGENTS.md: tag v0.8.0-rc.1 after review; npm publication needs maintainer approval in the npm environment. Nothing was pushed or published from this workstation. After the rc is out, Beacon's docs (WP6) can consume the components; upgrading Origin's docs and openlinkos.com should be appearance-neutral apart from the new files and the actions row.

@best
best merged commit 6b54918 into master Nov 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant