diff --git a/.agents/skills/website-docs-url-mapping/SKILL.md b/.agents/skills/website-docs-url-mapping/SKILL.md
new file mode 100644
index 000000000..35f4fffa0
--- /dev/null
+++ b/.agents/skills/website-docs-url-mapping/SKILL.md
@@ -0,0 +1,69 @@
+---
+name: website-docs-url-mapping
+description: Modify URL mapping rules in the website-docs Gatsby site by editing gatsby/url-resolver/config.ts and gatsby/link-resolver/config.ts, updating Jest tests, updating gatsby/URL_MAPPING_ARCHITECTURE.md, and reviewing gatsby-plugin-react-i18next matchPath when URL prefixes or languages change.
+---
+
+# Website-docs URL Mapping
+
+## Overview
+
+Modify the mapping rules between docs source paths and site URLs, and resolve internal Markdown links, while keeping tests and docs in sync.
+
+## Workflow
+
+### 1) Gather Requirements (Clarify First)
+
+Ask the user for “input → expected output” examples (at least 3 of each; more is better):
+- Page URLs: `sourcePath/slug -> pageUrl` (whether to omit default language `/en/`, and trailing slash expectations)
+- Link resolution: `(linkPath, currentPageUrl) -> resolvedUrl` (include edge cases: hash, no leading slash, relative paths, etc.)
+
+### 2) Review Existing Design (Align Terms and Current Behavior)
+
+Open and quickly locate the rules sections:
+- `gatsby/URL_MAPPING_ARCHITECTURE.md` (Configuration Rules)
+- `gatsby/url-resolver/README.md` (pattern/alias/filenameTransform)
+- `gatsby/link-resolver/README.md` (direct vs path-based, conditions/pathConditions)
+
+### 3) Edit URL Resolver (Page URLs)
+
+Edit: `gatsby/url-resolver/config.ts`
+- `pathMappings` are matched in order (first match wins); new rules usually go before more general ones.
+- `sourcePattern` supports `{var}` and `{...var}`; `conditions` use extracted variables to decide applicability.
+- Use `filenameTransform` to handle `_index` / `_docHome` (ignore filename or switch `targetPattern`).
+- If branch display logic changes, update `aliases` as well (e.g. `{branch:branch-alias-tidb}`).
+
+### 4) Edit Link Resolver (Markdown Links)
+
+Edit: `gatsby/link-resolver/config.ts`
+- Direct mapping: only `linkPattern` (does not depend on the current page)
+- Path-based mapping: `pathPattern + linkPattern`, constrained by `pathConditions`
+- Use `namespaceTransform` for namespace migrations (e.g. `develop -> developer`)
+- Watch `defaultLanguage` omission logic and `url-resolver.trailingSlash` (tests should cover both)
+
+### 5) Update/Add Tests (Prevent Regressions)
+
+- `gatsby/url-resolver/__tests__/url-resolver.test.ts`
+- `gatsby/link-resolver/__tests__/link-resolver.test.ts`
+
+Coverage (at minimum, every new/changed rule has assertions):
+- New rule match vs fallback (ordering)
+- `en/zh/ja` + whether `/en/` is omitted
+- `_index` vs non-`_index`
+- Links: preserve hash, no leading slash, path depth, multi-segment prefixes, etc.
+
+### 6) Run Tests
+
+- Full suite: `yarn test`
+- Or run a single test file first: `yarn test gatsby/url-resolver/__tests__/url-resolver.test.ts`, `yarn test gatsby/link-resolver/__tests__/link-resolver.test.ts`
+
+### 7) Update Architecture Doc (Keep It In Sync)
+
+Edit: `gatsby/URL_MAPPING_ARCHITECTURE.md`
+- Update interpretations under “URL Resolver Configuration Rules” and “Link Resolver Configuration Rules”
+- Keep rule numbering/order consistent with `config.ts`, and update input/output examples
+
+### 8) Check i18n Routing (Often Required When URL Prefixes Change)
+
+Review: `gatsby-config.js` → `gatsby-plugin-react-i18next`:
+- Ensure `languages` matches supported site languages (currently `en/zh/ja`)
+- Ensure `pages[].matchPath` includes any new/renamed top-level path prefixes (e.g. `developer`, `best-practices`, `api`, `releases`, and repo keys in `docs/docs.json`)
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..f9cb49efd
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,46 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+
+- `src/`: Gatsby site code (React components, templates, state, theme, and styles).
+- `gatsby/`: build-time utilities (page creation, link/url resolvers, custom plugins) and unit tests.
+- `docs/`: documentation content (git submodule pointing to `pingcap/docs-staging`).
+- `locale/`: i18n dictionaries (`locale/{en,zh,ja}/translation.json`).
+- `static/`, `src/media/`, `images/`: static assets used by the site/README.
+- Generated (do not commit): `.cache/`, `public/`, `coverage/`.
+
+## Build, Test, and Development Commands
+
+- `yarn`: install dependencies and apply `patches/` via `patch-package`.
+- `git submodule update --init --depth 1 --remote`: fetch/update the docs submodule content.
+- `yarn start` (or `yarn dev`): run local development server (Gatsby develop).
+- `yarn build`: create a production build.
+- `yarn serve`: serve the production build locally.
+- `yarn clean`: remove Gatsby build caches (`.cache/`, `public/`).
+- `yarn test`: run Jest with coverage for code under `gatsby/`.
+
+## Coding Style & Naming Conventions
+
+- Indentation: 2 spaces (see `.editorconfig`); keep TypeScript `strict` passing.
+- Formatting: Prettier runs via Husky + lint-staged on commit (`.husky/pre-commit`).
+- Components: PascalCase folders/files (e.g. `src/components/Layout/`); utilities/hooks: camelCase.
+- Styles: prefer CSS Modules (`*.module.css`); shared CSS in `src/styles/*.css`.
+- Imports: `tsconfig.json` sets `baseUrl: "./src"` (absolute imports like `shared/utils/...` are preferred).
+
+## Testing Guidelines
+
+- Framework: Jest + `ts-jest` (`jest.config.js`).
+- Location/pattern: `gatsby/**/__tests__/**/*.test.{ts,tsx,js,jsx}`.
+- Add/adjust tests when changing resolver logic or Gatsby build utilities.
+
+## Commit & Pull Request Guidelines
+
+- Commit messages follow a Conventional Commits pattern (common types: `feat:`, `fix:`, `refactor:`, `chore:`).
+- PRs should include: clear description + rationale, linked issue(s), and screenshots for UI changes.
+- Before requesting review, ensure `yarn test` and `yarn build` pass locally.
+
+## Security & Configuration Tips
+
+- Put local-only env vars in `.env.development` (e.g. `GATSBY_ALGOLIA_APPLICATION_ID`, `GATSBY_ALGOLIA_API_KEY`).
+- If GitHub API rate-limits during development, set `GITHUB_AUTHORIZATION_TOKEN=...` when running commands.
+- Never commit `.env*` files (they are gitignored).
diff --git a/gatsby-config.js b/gatsby-config.js
index a0d7fdae5..9558c88ef 100644
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -79,7 +79,9 @@ module.exports = {
getLanguageFromPath: false,
},
{
- matchPath: `/:lang?/(${Object.keys(docs.docs).join("|")})/(.*)`,
+ matchPath: `/:lang?/(${Object.keys(docs.docs).join(
+ "|"
+ )}|developer|best-practices|api|ai|releases)/(.*)`,
getLanguageFromPath: true,
},
{
diff --git a/gatsby-node.js b/gatsby-node.js
index 6d01d9062..184065bb0 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -8,7 +8,7 @@ const {
createDocSearch,
create404,
} = require("./gatsby/create-pages");
-const { createExtraType } = require("./gatsby/create-types");
+const { createFrontmatter, createNavs } = require("./gatsby/create-types");
const {
createConditionalToc,
} = require("./gatsby/plugin/conditional-toc/conditional-toc");
@@ -26,6 +26,7 @@ exports.createPages = async ({ graphql, actions }) => {
};
exports.createSchemaCustomization = (options) => {
- createExtraType(options);
+ createFrontmatter(options);
+ createNavs(options);
createConditionalToc(options);
};
diff --git a/gatsby-ssr.js b/gatsby-ssr.js
index c294d8d28..c38020b96 100644
--- a/gatsby-ssr.js
+++ b/gatsby-ssr.js
@@ -95,7 +95,113 @@ const fulfillCloudPlanScript = `
})();
`;
-export const onRenderBody = ({ setPostBodyComponents, setHeadComponents }) => {
+const headerPrehydrateScript = `
+(function() {
+ try {
+ var ROOT_SELECTOR = "[data-pc-docs-header-root]";
+ var LEFT_CLUSTER_SELECTOR = "[data-pc-docs-header-left-cluster]";
+ var LOGO_MEASURE_SELECTOR = "[data-pc-docs-header-logo-measure]";
+ var CSS_VAR_TRANSLATE_X = "--pc-docs-header-translate-x";
+ var CSS_VAR_LOGO_SCALE = "--pc-docs-header-logo-scale";
+ var LOGO_GAP = 24;
+ var FIRST_ROW_HEIGHT = 56;
+
+ function clamp(value, min, max) {
+ return Math.min(max, Math.max(min, value));
+ }
+
+ function sync(root) {
+ if (!root || !root.style) {
+ return false;
+ }
+
+ var y = window.scrollY || 0;
+ var progress = clamp(y / FIRST_ROW_HEIGHT, 0, 1);
+ var logoScale = 1 - progress * 0.2;
+
+ var leftCluster = root.querySelector(LEFT_CLUSTER_SELECTOR);
+ var logoMeasure = root.querySelector(LOGO_MEASURE_SELECTOR);
+
+ if (!leftCluster || !logoMeasure) {
+ return false;
+ }
+
+ var leftClusterWidth = leftCluster.getBoundingClientRect().width || 0;
+ var logoWidth = logoMeasure.getBoundingClientRect().width || 0;
+
+ // Always update logo scale; translateX depends on measured widths.
+ root.style.setProperty(CSS_VAR_LOGO_SCALE, "" + logoScale);
+
+ if (!leftClusterWidth || !logoWidth) {
+ return false;
+ }
+
+ var menuWidth = Math.max(0, leftClusterWidth - logoWidth);
+ var translateX = progress * (menuWidth + logoWidth * logoScale + LOGO_GAP);
+
+ root.style.setProperty(CSS_VAR_TRANSLATE_X, translateX + "px");
+ return true;
+ }
+
+ function trySync() {
+ var root = document.querySelector(ROOT_SELECTOR);
+ if (!root) {
+ return false;
+ }
+
+ // Desktop only: xs uses a different layout and doesn't consume translateX.
+ var isDesktop = false;
+ try {
+ isDesktop = window.matchMedia && window.matchMedia("(min-width: 900px)").matches;
+ } catch (e) {
+ isDesktop = false;
+ }
+ if (!isDesktop) {
+ root.style.setProperty(CSS_VAR_TRANSLATE_X, "0px");
+ root.style.setProperty(CSS_VAR_LOGO_SCALE, "1");
+ return true;
+ }
+
+ return sync(root);
+ }
+
+ if (trySync()) {
+ return;
+ }
+
+ var observer = new MutationObserver(function() {
+ if (trySync()) {
+ observer.disconnect();
+ }
+ });
+
+ observer.observe(document.documentElement, { childList: true, subtree: true });
+
+ // Fallback retries (in case layout isn't ready when the observer fires).
+ var retries = 0;
+ (function retry() {
+ if (trySync()) {
+ observer.disconnect();
+ return;
+ }
+ retries += 1;
+ if (retries >= 20) {
+ observer.disconnect();
+ return;
+ }
+ setTimeout(retry, 50);
+ })();
+ } catch (e) {
+ // no-op
+ }
+})();
+`;
+
+export const onRenderBody = ({
+ setPostBodyComponents,
+ setHeadComponents,
+ setPreBodyComponents,
+}) => {
setHeadComponents([
{
crossOrigin="anonymous"
/>,
]);
+ setPreBodyComponents([
+ ,
+ ]);
setPostBodyComponents([
,