mirror of
https://github.com/vercel/chat.git
synced 2026-09-14 18:32:29 +08:00
b7e4bbfbb0
## Summary
Upgrades the chat-sdk.dev docs app from `@vercel/geistdocs` **1.20.4 →
1.22.0** (published 2026-08-21, Apache-2.0) and `next` **16.2.11 →
16.3.1**, following the bundled 1.22.0 template as the source of truth.
The target release includes all of the behavior-changing PRs for this
round:
- vercel/geistdocs#245 — require Next.js 16.3, scaffold 16.3.1, drop the
dev filesystem-cache flag (shipped in 1.21.1)
- vercel/geistdocs#246 — Cache Components across Geistdocs
- vercel/geistdocs#249 — Partial Prefetching + instant docs navigation
- vercel/geistdocs#250 — stable Next 16.3 APIs, retryable page/Ask AI
error boundaries, full prefetch of package links, no generic page shell
- vercel/geistdocs#251 — tree sidebar preserves scroll position on
folder toggles
## Adapter and configuration changes
- `next.config.ts`: `cacheComponents: true`, `partialPrefetching: true`;
removed `experimental.turbopackFileSystemCacheForDev` (default in 16.3).
Redirects, `/sitemap.xml` rewrite, and image config unchanged.
- New `lib/geistdocs/root-params.ts`; all layouts read `[lang]` via
`next/root-params` instead of `params`. Route handlers keep
route-context `params`.
- Root layout gains `generateStaticParams` returning every configured
language (`en`) — home (`/`), `/adapters`, and `/resources` now
prerender statically (previously dynamic).
- Route adapters no longer re-export `revalidate`/`dynamic` from package
factories (`agents.md`, `sitemap.md`, `llms.mdx`), and custom routes
drop their own `revalidate` exports (`llms.txt`, `llms-full.txt`,
`adapters.mdx`, `rss.xml`, `resources`).
- `llms.mdx` adopts the template form: `sources: [geistdocsSource]` +
`notFound: {}`, enabling smart agent-readable 404/410 responses with
real HTTP statuses.
- `rss.xml` migrated to the template's `"use cache"` +
`cacheLife("max")` form with `getPublicPath` base-path handling.
- `resources` page: `revalidate = 86400` → `"use cache"` +
`cacheLife("days")` (same 1-day lifetime).
- App-owned data fetching moved off `next: { revalidate }` (unsupported
under Cache Components): GitHub README fetches and homepage OSS stats
now use `"use cache"` + `cacheLife("hours")`.
- Homepage Shiki highlighting (`Demo`, `CodePreview`, `highlightCode`)
runs inside `"use cache"` scopes — Shiki reads `Date.now()` internally,
which otherwise fails prerendering.
- App-owned links to statically generated docs pages get
`prefetch={true}` (platform grid, feature matrix, adapter slug list,
"Visit Documentation") per the template's agent guidance; package-owned
sidebar/prev-next links already prefetch fully in 1.22.0.
- `Analytics`/`SpeedInsights` moved into
`components/geistdocs/provider.tsx` per the template.
- CSS: `styles/geistdocs.css` now imports `@vercel/geistdocs/theme.css`
(self-sourcing package dist/streamdown) instead of layering on
`styles.css` from `global.css`; updated the mobile breadcrumb selector
for the new package DOM; kept the site-specific shadcn tokens, dark
background-scale override, prose, TOC, and streamdown fixes.
- Added `apps/docs/AGENTS.md` capturing the packaged-architecture
conventions (cache-components rules, root-params, markdown contract,
proxy mappings).
## PR #251 (tree sidebar scroll) verification
The fix is package-internal (`manualToggleRef` in `SidebarTree`); no
consumer change is needed. This site's sidebars render no collapsible
folder rows (content uses spread folders, `...api` etc.), so I verified
the shipped behavior against the bundled 1.22.0 template with
`sidebarMode="tree"` enabled locally: expanding/collapsing a folder
preserves the exact sidebar scroll position (772 → 772), and a route
change into a collapsed folder still scrolls the active item into view.
8/8 checks pass.
## Static-generation coverage
Production build: 290/290 static pages generated. Every intended
parameter tuple is prerendered with complete content (verified H1/body
in emitted HTML):
- `/en` home, `/en/adapters` listing, `/en/resources` — now fully static
(were `ƒ` on main)
- `/en/docs/*` — 45 pages, complete static HTML + one generic
`[[...slug]]` fallback entry (allowed)
- `/en/adapters/{official,community,vendor-official}/*` — 44 detail
pages + `/en/adapters.mdx/*` markdown for all 44
- `/en/sitemap.md` — SSG
Intentional contract differences (match the 1.22.0 template's own build
output):
- OG image routes (`/og/[...slug]`, adapter `*/og`) render on demand
under Cache Components instead of build-time SSG; Next 16.3 caches the
rendered image per route. URLs and content types verified unchanged.
- `llms.txt`, `llms-full.txt`, `llms.mdx`, `rss.xml`, `agents.md` remain
on-demand route handlers (same as main); `agents.md` reads the request
origin by package design.
- Unknown HTML routes: browsers receive the docs shell with 200 before
not-found UI resolves; crawlers get a real 404. Machine-readable unknown
routes return the new smart 404 body with real 404 status and
`X-Robots-Tag: noindex`.
## Lockfile
`pnpm-lock.yaml` delta: the `docs` importer's `@vercel/geistdocs`
(1.20.4 → 1.22.0) and `next` (16.2.11 → 16.3.1) bumps, their
peer-context re-resolutions, and one mechanical re-keying of `@swc/core`
peer contexts to include `@swc/helpers` across existing entries (no
version changes outside the docs app). Verified with `pnpm install
--frozen-lockfile`.
## Test results
- `pnpm install --frozen-lockfile` ✓
- `pnpm check` ✓ (1 pre-existing warning in untouched
`lib/read-more.ts`)
- `pnpm typecheck` — 43/43 ✓
- `pnpm knip` ✓, `pnpm konsistent` ✓
- `pnpm test` — 47/47 turbo tasks ✓
- Clean production build (removed `.next`/`.source`) ✓ 290/290
- Production-server (`next start`) contract checks: HTML docs,
`.md`/`.mdx`, `Accept: text/markdown`, agent-UA negotiation, `llms.txt`,
`llms-full.txt`, `sitemap.md`, `agents.md`, `/.well-known/mcp.json`
(intentional 404), `rss.xml`, `robots.txt`, `/sitemap.xml` rewrite, OG
images, `AGENTS.md`, all redirects (308s), `/api/search` JSON and not
rewritten as markdown, `/docs.md` section root ✓
- Browser checks (Playwright, Chrome, `next start`) — 16/16: instant
sidebar + prev/next client navigation with complete content and no
loading shell, single visible H1 (Activity-preserved routes stay
hidden), Copy Page, page actions menu, search → result navigation, Ask
AI panel with suggestions, theme switch (dark applies the site's
background scale), mobile navbar menu and docs sheet navigation, unknown
page shows not-found UI, zero console/page errors and failed requests
- Ask AI scoped failure: with no local AI Gateway credentials the chat
surfaces the error, the surrounding page stays intact, and resubmission
retries cleanly
- Visual parity screenshots vs production (home/docs/adapters, light +
dark) match
## Preview
- Preview:
https://chat-git-richardhaines-geistdocs-122-upgrade.vercel.sh —
deployment **Ready**, build passed on Vercel.
- The preview sits behind Vercel SSO (Fork Protection), so automated
route checks aren't possible without a bypass token; please spot-check
through SSO: `/docs/getting-started`, `/docs/getting-started.md`,
`/adapters`, `/llms.txt`, and a client navigation between docs pages.
Signed-off-by: molebox <rich@vercel.com>
130 lines
3.4 KiB
TypeScript
130 lines
3.4 KiB
TypeScript
import { cacheLife } from "next/cache";
|
|
import { codeToTokens, type ThemedToken } from "shiki";
|
|
|
|
/**
|
|
* Syntax theme that mirrors @vercel/geist's CodeBlock by emitting geist design
|
|
* tokens (`--ds-*`) as the token colors. Because these CSS variables flip with
|
|
* the active theme, the same highlighting adapts to light and dark mode.
|
|
*/
|
|
const GEIST_SYNTAX_THEME = {
|
|
name: "geist",
|
|
type: "light" as const,
|
|
fg: "var(--ds-gray-1000)",
|
|
bg: "transparent",
|
|
settings: [
|
|
{ settings: { foreground: "var(--ds-gray-1000)" } },
|
|
{
|
|
scope: ["comment", "punctuation.definition.comment", "string.comment"],
|
|
settings: { foreground: "var(--ds-gray-900)" },
|
|
},
|
|
{
|
|
scope: [
|
|
"constant",
|
|
"constant.numeric",
|
|
"constant.language",
|
|
"constant.language.boolean",
|
|
"entity.name.constant",
|
|
"variable.other.constant",
|
|
"variable.other.enummember",
|
|
"variable.language",
|
|
"support.constant",
|
|
],
|
|
settings: { foreground: "var(--ds-blue-900)" },
|
|
},
|
|
{
|
|
scope: [
|
|
"entity.name.function",
|
|
"meta.function-call",
|
|
"meta.function-call.method",
|
|
"variable.function",
|
|
"support.function",
|
|
"keyword.other.special-method",
|
|
"entity.other.attribute-name",
|
|
],
|
|
settings: { foreground: "var(--ds-purple-900)" },
|
|
},
|
|
{
|
|
scope: [
|
|
"keyword",
|
|
"keyword.control",
|
|
"keyword.operator.new",
|
|
"keyword.operator.expression",
|
|
"keyword.operator.logical",
|
|
"storage",
|
|
"storage.type",
|
|
"storage.modifier",
|
|
],
|
|
settings: { foreground: "var(--ds-pink-900)" },
|
|
},
|
|
{
|
|
scope: [
|
|
"string",
|
|
"string.template",
|
|
"string.quoted",
|
|
"punctuation.definition.string",
|
|
],
|
|
settings: { foreground: "var(--ds-green-900)" },
|
|
},
|
|
{
|
|
scope: [
|
|
"meta.template.expression",
|
|
"string.regexp",
|
|
"support.constant.property-value",
|
|
],
|
|
settings: { foreground: "var(--ds-green-900)" },
|
|
},
|
|
{
|
|
scope: ["variable.parameter", "meta.function.parameters"],
|
|
settings: { foreground: "var(--ds-amber-900)" },
|
|
},
|
|
{
|
|
scope: [
|
|
"entity.name.type",
|
|
"entity.name.class",
|
|
"entity.other.inherited-class",
|
|
"support.type",
|
|
"support.class",
|
|
],
|
|
settings: { foreground: "var(--ds-blue-900)" },
|
|
},
|
|
{
|
|
scope: ["entity.name.tag", "punctuation.definition.tag"],
|
|
settings: { foreground: "var(--ds-green-900)" },
|
|
},
|
|
{
|
|
scope: [
|
|
"punctuation",
|
|
"punctuation.accessor",
|
|
"meta.brace",
|
|
"keyword.operator",
|
|
],
|
|
settings: { foreground: "var(--ds-gray-1000)" },
|
|
},
|
|
{
|
|
scope: ["variable", "meta.definition.variable.name", "support.variable"],
|
|
settings: { foreground: "var(--ds-gray-1000)" },
|
|
},
|
|
{
|
|
scope: ["markup.underline.link", "string.other.link"],
|
|
settings: { foreground: "var(--ds-green-900)" },
|
|
},
|
|
],
|
|
};
|
|
|
|
export const highlightCode = async (
|
|
code: string,
|
|
lang: "tsx" | "typescript" = "typescript"
|
|
): Promise<ThemedToken[][]> => {
|
|
// Shiki reads unstable values (e.g. Date.now) internally; cache the result
|
|
// so pages using this helper stay prerenderable under Cache Components.
|
|
"use cache";
|
|
cacheLife("max");
|
|
|
|
const { tokens } = await codeToTokens(code, {
|
|
lang,
|
|
theme: GEIST_SYNTAX_THEME,
|
|
});
|
|
|
|
return tokens;
|
|
};
|