From 625e2959bca4e009021cd57b7caa8030609ee81e Mon Sep 17 00:00:00 2001 From: Wassim Bougarfa <12980387+wassimoo@users.noreply.github.com> Date: Wed, 6 May 2026 15:58:43 +0200 Subject: [PATCH 1/4] feat: introduce SameDeploymentLink component for deployment-aware navigation --- AGENTS.md | 43 ++++++++++-- src/components/SameDeploymentLink.tsx | 90 ++++++++++++++++++++++++++ src/components/Shared/kratos/index.mdx | 4 +- src/theme/MDXComponents.js | 2 + src/utils/deploymentFromPathname.ts | 13 ++++ 5 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 src/components/SameDeploymentLink.tsx create mode 100644 src/utils/deploymentFromPathname.ts diff --git a/AGENTS.md b/AGENTS.md index 1090222d40..ccd4e0ac7e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,7 +38,7 @@ OR ### Shared Source Content -- Location: `/src/components/shared//...` +- Location: `/src/components/Shared//...` - Purpose: - Reusable, deployment-agnostic content - Written once, used across deployments @@ -53,7 +53,7 @@ Locations: Shell files: -- Import content from `/src/components/shared/...` +- Import content from `/src/components/Shared/...` - Represent a page for a specific deployment - Are the files added to sidebars @@ -101,12 +101,12 @@ If content is specific to **self-hosted (OEL/OSS)**: **OEL is canonical** ### 2. Update content -- Edit shared source when possible:`/src/components/shared//...` +- Edit shared source when possible: `/src/components/Shared//...` - Avoid duplicating content across deployments **Example (shared source):** -`/src/components/shared/kratos/index.mdx` +`/src/components/Shared/kratos/index.mdx` ```mdx Ory Kratos Identities is an API-first identity and user management system... @@ -141,7 +141,7 @@ sidebar_label: Introduction -import MyPartial from "@site/src/components/shared/kratos/index.mdx" +import MyPartial from "@site/src/components/Shared/kratos/index.mdx" ``` @@ -174,7 +174,7 @@ import MyPartial from "@site/src/components/shared/kratos/index.mdx" ### Summary -- Edit → `/src/components/shared/...` +- Edit → `/src/components/Shared/...` - Expose → `docs//...` shell files - Navigate → via sidebars - De-duplicate → with canonical URLs @@ -190,5 +190,34 @@ import MyPartial from "@site/src/components/shared/kratos/index.mdx" ## When in Doubt - Check the sidebar first -- Look for `/src/components/shared/...` usage +- Look for `/src/components/Shared/...` usage - If unclear, ask the Docs team before making structural changes + +## MDX paths, imports, and internal links + +- **Use `@site` for imports (not links)**: use `@site/...` when importing + snippets/partials (for example `@site/src/components/Shared/...`) to avoid + brittle relative import paths. Do not use `@site` as a way to “link” to docs + pages. +- **Don’t link to shared partials**: shared files in `src/components/Shared/...` + are implementation details; link to the deployment shell page(s) in + `docs//...` instead. +- **Use `SameDeploymentLink` when linking across deployments**: if a doc exists + under `docs/network/...`, `docs/oel/...`, and `docs/oss/...`, use + `SameDeploymentLink` so readers stay on their current deployment. In this repo + it’s registered globally via `src/theme/MDXComponents.js`, so you can use it + in MDX without importing it. + +Example: + +```mdx + + Ory Kratos introduction + + +{/* Optional per-deployment overrides */} + + + Kratos intro (deployment-aware) + +``` diff --git a/src/components/SameDeploymentLink.tsx b/src/components/SameDeploymentLink.tsx new file mode 100644 index 0000000000..e390827abc --- /dev/null +++ b/src/components/SameDeploymentLink.tsx @@ -0,0 +1,90 @@ +import React, { useEffect, type ReactNode } from "react" +import Link from "@docusaurus/Link" +import { useLocation } from "@docusaurus/router" +import { useAllDocsData } from "@docusaurus/plugin-content-docs/client" +import { + getDocsDeploymentSegment, + type DocsDeploymentSegment, +} from "@site/src/utils/deploymentFromPathname" + +type SameDeploymentLinkProps = { + to: string + // Optional overrides for the link target based on the deployment segment + network?: string + oel?: string + oss?: string + children: ReactNode +} + +const DEFAULT_DOCS_PLUGIN_ID = "default" + +/** + * Internal docs link that keeps the reader on the current deployment (Network / OEL / OSS). + */ +export default function SameDeploymentLink({ + to, + network, + oel, + oss, + children, +}: SameDeploymentLinkProps): JSX.Element { + const { pathname } = useLocation() + const segment = getDocsDeploymentSegment(pathname) + const stripLeadingSlashes = (value: string) => value.replace(/^\/+/, "") + const normalizePath = (value: string) => `/${stripLeadingSlashes(value)}` + + // Detect whether the provided path already includes a deployment segment. + // If it does, we replace it with the current one (drop-in replacement behavior). + // If it doesn't, we prefix the current deployment segment (legacy behavior). + const rewriteToCurrentDeployment = (value: string): string => { + const p = normalizePath(value) + + // Handle legacy/self-hosted prefixes explicitly. + if (p.startsWith("/self-hosted/oel/")) { + return `/oel/${p.slice("/self-hosted/oel/".length)}` + } + + const m = p.match(/^\/(network|oel|oss)(\/.*)?$/) + if (m) { + const rest = m[2] ?? "" + return `/${segment}${rest}` + } + + return `/${segment}${p}` + } + + const suffixDefault = stripLeadingSlashes(to) + const overrides: Partial> = { + network, + oel, + oss, + } + const suffixOverride = overrides[segment] + const suffixEffective = suffixOverride + ? stripLeadingSlashes(suffixOverride) + : suffixDefault + const href = rewriteToCurrentDeployment(suffixEffective) + const allDocs = useAllDocsData() + + useEffect(() => { + if (process.env.NODE_ENV !== "development") return + const plugin = allDocs[DEFAULT_DOCS_PLUGIN_ID] + const version = + plugin?.versions.find((v) => v.isLast) ?? plugin?.versions[0] + if (!version?.docs?.length) return + + const docId = stripLeadingSlashes(href) + const found = version.docs.some((d) => d.id === docId) + if (!found) { + const overrideMsg = suffixOverride + ? ` (override prop "${segment}" was used)` + : "" + console.warn( + `[SameDeploymentLink] No doc with id "${docId}". ` + + `Resolved href="${href}" from to="${to}" under deployment "${segment}" did not match any page in this build${overrideMsg}.`, + ) + } + }, [allDocs, href, segment, suffixOverride, to]) + + return {children} +} diff --git a/src/components/Shared/kratos/index.mdx b/src/components/Shared/kratos/index.mdx index 42e0425573..530a10ade4 100644 --- a/src/components/Shared/kratos/index.mdx +++ b/src/components/Shared/kratos/index.mdx @@ -24,8 +24,8 @@ modern software applications have to deal with: Ory Identities calls user accounts "identities". The terms "user accounts", "users", and "identities" are used interchangeably in the Ory documentation. -Read [more here](../../../../docs/network/kratos/quickstarts/identity-model) to -learn more about identities in Ory. +Read more +here to learn more about identities in Ory. ::: diff --git a/src/theme/MDXComponents.js b/src/theme/MDXComponents.js index 0b86d05a4d..ff02a7dfa1 100644 --- a/src/theme/MDXComponents.js +++ b/src/theme/MDXComponents.js @@ -4,12 +4,14 @@ import Tabs from "@theme/Tabs" import TabItem from "@theme/TabItem" import AjaxWarning from "./AjaxWarning" import ConsoleLink from "../components/ConsoleLink/console-link" +import SameDeploymentLink from "../components/SameDeploymentLink" export default { // Re-use the default mapping ...MDXComponents, AjaxWarning, ConsoleLink, + SameDeploymentLink, Tabs, TabItem, } diff --git a/src/utils/deploymentFromPathname.ts b/src/utils/deploymentFromPathname.ts new file mode 100644 index 0000000000..bc1a381c82 --- /dev/null +++ b/src/utils/deploymentFromPathname.ts @@ -0,0 +1,13 @@ +export type DocsDeploymentSegment = "network" | "oel" | "oss" + +export function getDocsDeploymentSegment( + pathname: string, +): DocsDeploymentSegment { + if (pathname.includes("/oel/") || pathname.includes("/self-hosted/oel/")) { + return "oel" + } + if (pathname.includes("/oss/")) { + return "oss" + } + return "network" +} From 768d2c118d1f22c79efa5d86395202d66e95656e Mon Sep 17 00:00:00 2001 From: Wassim Bougarfa <12980387+wassimoo@users.noreply.github.com> Date: Wed, 6 May 2026 15:59:01 +0200 Subject: [PATCH 2/4] chore: update CODEOWNERS to include @wassimoo for code examples and general ownership --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3c4e7f1d05..820a640b06 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ -* @vinckr @aeneasr @zepatrik @piotrmsc @unatasha8 +* @vinckr @aeneasr @zepatrik @piotrmsc @unatasha8 @wassimoo *.md @vinckr @aeneasr @unatasha8 *.mdx @vinckr @aeneasr @unatasha8 -/code-examples/ @aeneasr @zepatrik @piotrmsc +/code-examples/ @aeneasr @zepatrik @piotrmsc @wassimoo From 1e2c20b8d5cad1e5c8ba3595ffd881c88445a3a0 Mon Sep 17 00:00:00 2001 From: Wassim Bougarfa <12980387+wassimoo@users.noreply.github.com> Date: Wed, 6 May 2026 16:27:32 +0200 Subject: [PATCH 3/4] refactor: simplify SameDeploymentLink component --- src/components/SameDeploymentLink.tsx | 62 +++++++++++---------------- src/utils/deploymentFromPathname.ts | 13 ------ 2 files changed, 25 insertions(+), 50 deletions(-) delete mode 100644 src/utils/deploymentFromPathname.ts diff --git a/src/components/SameDeploymentLink.tsx b/src/components/SameDeploymentLink.tsx index e390827abc..e52d9cdda0 100644 --- a/src/components/SameDeploymentLink.tsx +++ b/src/components/SameDeploymentLink.tsx @@ -2,10 +2,8 @@ import React, { useEffect, type ReactNode } from "react" import Link from "@docusaurus/Link" import { useLocation } from "@docusaurus/router" import { useAllDocsData } from "@docusaurus/plugin-content-docs/client" -import { - getDocsDeploymentSegment, - type DocsDeploymentSegment, -} from "@site/src/utils/deploymentFromPathname" + +type DocsDeploymentSegment = "network" | "oel" | "oss" type SameDeploymentLinkProps = { to: string @@ -18,6 +16,11 @@ type SameDeploymentLinkProps = { const DEFAULT_DOCS_PLUGIN_ID = "default" +const DEPLOYMENT_SEGMENT_PATTERN = /\/(network|oel|oss)(?:\/|$)/ + +const stripLeadingSlashes = (value: string) => value.replace(/^\/+/, "") +const normalizePath = (value: string) => `/${stripLeadingSlashes(value)}` + /** * Internal docs link that keeps the reader on the current deployment (Network / OEL / OSS). */ @@ -29,45 +32,30 @@ export default function SameDeploymentLink({ children, }: SameDeploymentLinkProps): JSX.Element { const { pathname } = useLocation() - const segment = getDocsDeploymentSegment(pathname) - const stripLeadingSlashes = (value: string) => value.replace(/^\/+/, "") - const normalizePath = (value: string) => `/${stripLeadingSlashes(value)}` + const segment = + (pathname.match(DEPLOYMENT_SEGMENT_PATTERN)?.[1] as + | DocsDeploymentSegment + | undefined) ?? "network" - // Detect whether the provided path already includes a deployment segment. - // If it does, we replace it with the current one (drop-in replacement behavior). - // If it doesn't, we prefix the current deployment segment (legacy behavior). const rewriteToCurrentDeployment = (value: string): string => { - const p = normalizePath(value) - - // Handle legacy/self-hosted prefixes explicitly. - if (p.startsWith("/self-hosted/oel/")) { - return `/oel/${p.slice("/self-hosted/oel/".length)}` + const path = normalizePath(value) + const match = path.match(DEPLOYMENT_SEGMENT_PATTERN) + if (match) { + // Replace explicit deployment segment with the current one. + return path.replace(DEPLOYMENT_SEGMENT_PATTERN, `/${segment}/`) } - - const m = p.match(/^\/(network|oel|oss)(\/.*)?$/) - if (m) { - const rest = m[2] ?? "" - return `/${segment}${rest}` - } - - return `/${segment}${p}` + // Prefix when no deployment segment is present. + return `/${segment}${path}` } - const suffixDefault = stripLeadingSlashes(to) - const overrides: Partial> = { - network, - oel, - oss, - } - const suffixOverride = overrides[segment] - const suffixEffective = suffixOverride - ? stripLeadingSlashes(suffixOverride) - : suffixDefault - const href = rewriteToCurrentDeployment(suffixEffective) + const overrideForCurrentDeployment = { network, oel, oss }[segment] + const href = overrideForCurrentDeployment + ? normalizePath(overrideForCurrentDeployment) + : rewriteToCurrentDeployment(to) const allDocs = useAllDocsData() useEffect(() => { - if (process.env.NODE_ENV !== "development") return + if (!DEPLOYMENT_SEGMENT_PATTERN.test(href)) return const plugin = allDocs[DEFAULT_DOCS_PLUGIN_ID] const version = plugin?.versions.find((v) => v.isLast) ?? plugin?.versions[0] @@ -76,7 +64,7 @@ export default function SameDeploymentLink({ const docId = stripLeadingSlashes(href) const found = version.docs.some((d) => d.id === docId) if (!found) { - const overrideMsg = suffixOverride + const overrideMsg = overrideForCurrentDeployment ? ` (override prop "${segment}" was used)` : "" console.warn( @@ -84,7 +72,7 @@ export default function SameDeploymentLink({ `Resolved href="${href}" from to="${to}" under deployment "${segment}" did not match any page in this build${overrideMsg}.`, ) } - }, [allDocs, href, segment, suffixOverride, to]) + }, [allDocs, href, overrideForCurrentDeployment, segment, to]) return {children} } diff --git a/src/utils/deploymentFromPathname.ts b/src/utils/deploymentFromPathname.ts deleted file mode 100644 index bc1a381c82..0000000000 --- a/src/utils/deploymentFromPathname.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type DocsDeploymentSegment = "network" | "oel" | "oss" - -export function getDocsDeploymentSegment( - pathname: string, -): DocsDeploymentSegment { - if (pathname.includes("/oel/") || pathname.includes("/self-hosted/oel/")) { - return "oel" - } - if (pathname.includes("/oss/")) { - return "oss" - } - return "network" -} From 9c5be3a6bd6792d909a2895a7a1865d5135a5d70 Mon Sep 17 00:00:00 2001 From: Wassim Bougarfa <12980387+wassimoo@users.noreply.github.com> Date: Wed, 6 May 2026 16:41:33 +0200 Subject: [PATCH 4/4] fix: reflect deployment model context in sidebar picker --- src/contexts/QuickstartsDeploymentContext.tsx | 15 ++++++++++- src/theme/DocRoot/index.js | 4 ++- src/utils/docsDeploymentFromPathname.ts | 27 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/utils/docsDeploymentFromPathname.ts diff --git a/src/contexts/QuickstartsDeploymentContext.tsx b/src/contexts/QuickstartsDeploymentContext.tsx index 3efd689256..9c1f1bcf97 100644 --- a/src/contexts/QuickstartsDeploymentContext.tsx +++ b/src/contexts/QuickstartsDeploymentContext.tsx @@ -1,4 +1,10 @@ -import React, { createContext, useContext, useState, useCallback } from "react" +import React, { + createContext, + useContext, + useState, + useCallback, + useEffect, +} from "react" export type QuickstartsDeploymentId = "network" | "oel" | "oss" @@ -19,6 +25,13 @@ export function QuickstartsDeploymentProvider({ }) { const [deployment, setDeploymentState] = useState(initialDeployment) + + // Keep context in sync with explicitly segmented URLs (e.g. /docs/oel/...). + useEffect(() => { + if (initialDeployment === "network") return + setDeploymentState(initialDeployment) + }, [initialDeployment]) + const setDeployment = useCallback((id: QuickstartsDeploymentId) => { setDeploymentState(id) }, []) diff --git a/src/theme/DocRoot/index.js b/src/theme/DocRoot/index.js index bcff5a4b8e..55afef7c73 100644 --- a/src/theme/DocRoot/index.js +++ b/src/theme/DocRoot/index.js @@ -15,6 +15,7 @@ import { QuickstartsDeploymentProvider, useQuickstartsDeployment, } from "@site/src/contexts/QuickstartsDeploymentContext" +import { docsDeploymentFromPathname } from "@site/src/utils/docsDeploymentFromPathname" const QUICKSTARTS_SIDEBAR = "quickstartsSidebar" @@ -79,13 +80,14 @@ export default function DocRootWrapper(props) { } const { docElement, sidebarName, sidebarItems } = currentDocRouteMetadata const pathname = props.location?.pathname ?? "" + const deploymentFromPath = docsDeploymentFromPathname(pathname) const versionMetadata = useDocsVersion() ?? {} const docsSidebars = versionMetadata.docsSidebars ?? {} return (
- +