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([ +