diff --git a/.github/workflows/docs-code-ci.yml b/.github/workflows/docs-code-ci.yml
index d43f35d4fc..41e15ba503 100644
--- a/.github/workflows/docs-code-ci.yml
+++ b/.github/workflows/docs-code-ci.yml
@@ -85,5 +85,8 @@ jobs:
- name: Visual smoke
run: npm run docs:visual
+ - name: Mermaid in WebKit
+ run: npm run docs:visual -- --mermaid-only --webkit
+
- name: Community invitation in Chromium and WebKit
run: npm run docs:visual:community
diff --git a/package-lock.json b/package-lock.json
index 3f1bada290..e4264a457c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,7 +18,7 @@
"lucide": "1.56.0",
"markdown-it": "15.0.2",
"markdown-it-anchor": "10.0.0",
- "mermaid": "11.17.2",
+ "mermaid": "12.1.0",
"pagefind": "1.5.2",
"playwright": "^1.64.0",
"tsx": "4.23.15",
@@ -47,10 +47,45 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@chevrotain/cst-dts-gen": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-13.2.0.tgz",
+ "integrity": "sha512-bA9dvWlhAUcHkNwU+8w9yiUxJPlj/Lm3VqS7MD+NbIR/7LmpNLuK/AWNFdCo/T3SK5ND0/UHf3IJvl/Jn/i6yg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/gast": "13.2.0",
+ "@chevrotain/types": "13.2.0"
+ }
+ },
+ "node_modules/@chevrotain/gast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-13.2.0.tgz",
+ "integrity": "sha512-bCARdAsG+eEbOveSXcOAC7pVxA04I1ionzeMOwIYf5NX7SaS7zgn4Dgug2BSqoL+iyKeMNSorQjsFSHJHsCNwg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/types": "13.2.0"
+ }
+ },
+ "node_modules/@chevrotain/regexp-to-ast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-13.2.0.tgz",
+ "integrity": "sha512-kxpUkLtiaK09ph+dib+Yvm+3sWSnuwlo25gqfKRgTPNmF6H7uW4gVkMjaRdtnN783S9sM6vUNWh9CsxYX4QwKw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/@chevrotain/types": {
- "version": "11.1.2",
- "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz",
- "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==",
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-13.2.0.tgz",
+ "integrity": "sha512-7qrYHRlKfWf2AvBfJi5eVOrKAkbxvMFsy/iIHTGIiApNcEecCaVIdR+mIQhyCBsTDJxBz4JWXHMo6krbhR9hVg==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/utils": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-13.2.0.tgz",
+ "integrity": "sha512-SzKYY68rseOrb9KILVzPsl6SjkQtdWw01SRGB5aCVI5kFcHltQsOYV3JB0Ffst72DgkvHCyDQ0NLwxtmCCf97Q==",
"dev": true,
"license": "Apache-2.0"
},
@@ -554,13 +589,16 @@
}
},
"node_modules/@mermaid-js/parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.1.tgz",
- "integrity": "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-2.0.1.tgz",
+ "integrity": "sha512-j37dj/h6p6pots1L7pLPPNuABEEu9WP4OYg9pW1g+nYG331j0+3canE9OagtYdytYvdSAMG+2fKU5cxep6LgjA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@chevrotain/types": "~11.1.2"
+ "@chevrotain/types": "~13.2.0"
+ },
+ "engines": {
+ "node": ">=22.12.0"
}
},
"node_modules/@openclaw/carapace": {
@@ -1468,6 +1506,23 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/chevrotain": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-13.2.0.tgz",
+ "integrity": "sha512-GTouk5YzgBxlTPCaDn5q0q2EJKkrYmnPF+HmF8v0Qo4r3iyPG7HJivkN1wthCXZcm3sIcZFpGTRovLPNdJNRCA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/cst-dts-gen": "13.2.0",
+ "@chevrotain/gast": "13.2.0",
+ "@chevrotain/regexp-to-ast": "13.2.0",
+ "@chevrotain/types": "13.2.0",
+ "@chevrotain/utils": "13.2.0"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
"node_modules/collapse-white-space": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
@@ -2205,6 +2260,13 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
+ "node_modules/elkjs": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz",
+ "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==",
+ "dev": true,
+ "license": "EPL-2.0"
+ },
"node_modules/entities": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz",
@@ -2425,16 +2487,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/fastdom": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.12.tgz",
- "integrity": "sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "strictdom": "^1.0.1"
- }
- },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3022,17 +3074,18 @@
"license": "MIT"
},
"node_modules/mermaid": {
- "version": "11.17.2",
- "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.2.tgz",
- "integrity": "sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==",
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-12.1.0.tgz",
+ "integrity": "sha512-wlVCp+8eTupfCeeFvoZNNiTuHrvag0P2jz/ILgb/f/6jkVokUefOcujefi8qUe/j2asHiSePncVsz/xzzA80LQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.1.2",
"@iconify/utils": "^3.0.2",
- "@mermaid-js/parser": "^1.2.1",
+ "@mermaid-js/parser": "^2.0.1",
"@types/d3": "^7.4.3",
"@upsetjs/venn.js": "^2.0.0",
+ "chevrotain": "~13.2.0",
"cytoscape": "^3.34.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
@@ -3040,9 +3093,9 @@
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.14",
"dayjs": "^1.11.21",
- "dompurify": "^3.3.3",
+ "dompurify": "^3.4.12",
+ "elkjs": "^0.9.3",
"es-toolkit": "^1.45.1",
- "fastdom": "1.0.12",
"katex": "^0.16.47",
"khroma": "^2.1.0",
"marked": "^16.3.0",
@@ -3050,6 +3103,9 @@
"stylis": "^4.3.6",
"ts-dedent": "^2.2.0",
"uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0"
+ },
+ "engines": {
+ "node": ">=22.12.0"
}
},
"node_modules/micromark": {
@@ -4003,13 +4059,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/strictdom": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz",
- "integrity": "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/stringify-entities": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
diff --git a/package.json b/package.json
index 08bd13fabb..ff9e7ebe87 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"lucide": "1.56.0",
"markdown-it": "15.0.2",
"markdown-it-anchor": "10.0.0",
- "mermaid": "11.17.2",
+ "mermaid": "12.1.0",
"pagefind": "1.5.2",
"playwright": "^1.64.0",
"tsx": "4.23.15",
diff --git a/scripts/docs-site/elements-fixture.mjs b/scripts/docs-site/elements-fixture.mjs
index 87b7e0f715..32226ff982 100644
--- a/scripts/docs-site/elements-fixture.mjs
+++ b/scripts/docs-site/elements-fixture.mjs
@@ -412,6 +412,13 @@ flowchart LR
A["Ready
state"] --> B["Rendered"]
\`\`\`
+
+stateDiagram-v2
+ [*] --> Ready
+ Ready --> Complete
+ Complete --> [*]
+
+
flowchart LR
Broken -->
diff --git a/scripts/docs-site/site-js.mjs b/scripts/docs-site/site-js.mjs
index ef7382611f..2947abf692 100644
--- a/scripts/docs-site/site-js.mjs
+++ b/scripts/docs-site/site-js.mjs
@@ -219,7 +219,8 @@ function mermaidOptions(){
const node=color("--docs-diagram-node")||color("--paper");
const border=color("--docs-diagram-border")||color("--line-strong");
const line=color("--docs-diagram-line")||color("--muted");
- return {startOnLoad:false,securityLevel:"strict",theme:"base",fontFamily,
+ // Keep the docs appearance stable when Mermaid changes its diagram defaults.
+ return {startOnLoad:false,securityLevel:"strict",theme:"base",layout:"dagre",look:"classic",fontFamily,
sequence:{width:128,height:48,actorMargin:64,messageMargin:44,actorFontSize:14,actorFontWeight:600,messageFontSize:13,noteFontSize:13},
themeVariables:{darkMode:root.dataset.theme!=="light",fontFamily,fontSize:"14px",background:color("--paper"),primaryColor:node,primaryTextColor:color("--ink"),primaryBorderColor:border,secondaryColor:node,tertiaryColor:color("--paper"),mainBkg:node,lineColor:line,textColor:color("--text"),nodeBorder:border,clusterBkg:color("--paper"),clusterBorder:border,edgeLabelBackground:color("--paper"),actorBorder:border,actorBkg:node,actorTextColor:color("--ink"),actorLineColor:border,signalColor:color("--brand"),signalTextColor:color("--text"),labelBoxBkgColor:node,labelBoxBorderColor:border,labelTextColor:color("--ink"),noteBkgColor:node,noteBorderColor:border,noteTextColor:color("--text")}}
}
diff --git a/scripts/docs-site/visual-smoke.mjs b/scripts/docs-site/visual-smoke.mjs
index 0f075cacd6..fb15aa873c 100644
--- a/scripts/docs-site/visual-smoke.mjs
+++ b/scripts/docs-site/visual-smoke.mjs
@@ -2,11 +2,14 @@
import fs from "node:fs";
import http from "node:http";
import path from "node:path";
-import { chromium } from "playwright";
+import { chromium, webkit } from "playwright";
const accordionOnly = process.argv.includes("--accordion-only");
-if (process.argv.slice(2).some((arg) => arg !== "--accordion-only")) {
- throw new Error("usage: visual-smoke.mjs [--accordion-only]");
+const mermaidOnly = process.argv.includes("--mermaid-only");
+const useWebkit = process.argv.includes("--webkit");
+if (process.argv.slice(2).some((arg) => !["--accordion-only", "--mermaid-only", "--webkit"].includes(arg))
+ || (accordionOnly && mermaidOnly) || (useWebkit && !mermaidOnly)) {
+ throw new Error("usage: visual-smoke.mjs [--accordion-only | --mermaid-only [--webkit]]");
}
const root = process.cwd();
@@ -36,10 +39,10 @@ const server = http.createServer((req, res) => {
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const base = `http://127.0.0.1:${server.address().port}`;
-const browser = await chromium.launch({ headless: true });
+const browser = await (useWebkit ? webkit : chromium).launch({ headless: true });
try {
- const checks = accordionOnly ? [checkAccordionNavigation] : [
+ const checks = accordionOnly ? [checkAccordionNavigation] : mermaidOnly ? [checkMermaid] : [
checkLanguageSelection,
checkAccordionNavigation,
checkLandingNavigation,
@@ -48,6 +51,7 @@ try {
checkHeaderSurface,
checkFooterSocials,
checkDesktop,
+ checkMermaid,
checkTocScrollspy,
checkTocRailScrolling,
checkCompactToc,
@@ -57,7 +61,7 @@ try {
checkLightMode,
];
for (const check of checks) await check();
- console.log(`docs ${accordionOnly ? "accordion" : "visual"} smoke ok: screenshots in ${path.relative(root, artifacts)}`);
+ console.log(`docs ${accordionOnly ? "accordion" : mermaidOnly ? "mermaid" : "visual"} smoke ok: screenshots in ${path.relative(root, artifacts)}`);
} finally {
await browser.close();
server.close();
@@ -529,42 +533,81 @@ async function checkFooterSocials() {
} finally { await page.close(); }
}
-async function checkDesktop() {
- const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
- await page.goto(`${base}/__elements`, { waitUntil: "networkidle" });
- await page.screenshot({ path: path.join(artifacts, "elements-desktop-dark.png"), fullPage: true });
- await page.evaluate(() => scrollTo(0, 760));
- await page.screenshot({ path: path.join(artifacts, "elements-desktop-scrolled.png"), fullPage: false });
+async function expectMermaidDiagrams(page) {
+ for (const [source, label] of [
+ ["sequenceDiagram", "healthy"],
+ ["flowchart LR", "Rendered"],
+ ["stateDiagram-v2", "Complete"],
+ ]) {
+ // Expand controls contain SVG icons too; the direct child is the rendered diagram.
+ const svg = page.locator('.oc-mermaid.is-rendered[data-mermaid*="' + source + '"] > svg').first();
+ await svg.waitFor({ state: "visible", timeout: 10000 });
+ if (!(await svg.textContent()).includes(label)) {
+ throw new Error(source + " rendered without its expected labels");
+ }
+ }
+}
- const overlap = await page.evaluate(() => {
- const header = document.querySelector(".site-header")?.getBoundingClientRect();
- const sidebar = document.querySelector(".sidebar")?.getBoundingClientRect();
- const toc = document.querySelector(".toc")?.getBoundingClientRect();
- return {
- headerTop: header?.top,
- sidebarClear: header && sidebar ? sidebar.top >= header.bottom - 1 : false,
- tocClear: header && toc ? toc.top >= header.bottom - 1 : false,
- };
- });
- if (overlap.headerTop !== 0 || !overlap.sidebarClear || !overlap.tocClear) {
- throw new Error(`desktop sticky shell overlap: ${JSON.stringify(overlap)}`);
+async function navigateMermaidPage(page, route) {
+ await page.evaluate((href) => {
+ const link = document.createElement("a");
+ link.href = href;
+ link.textContent = "Mermaid navigation target";
+ document.querySelector(".doc").prepend(link);
+ }, route);
+ await page.getByRole("link", { name: "Mermaid navigation target", exact: true }).click();
+ await page.waitForURL((url) => url.pathname.replace(/\/$/, "") === route.replace(/\/$/, ""));
+ if (!await page.evaluate(() => window.__mermaidNavigationProbe === true)) {
+ throw new Error("Mermaid navigation fell back to a full page load");
}
+}
- await expectVisible(page, ".oc-code [data-code-copy]", "code copy button");
- await expectVisible(page, ".page-status .page-status-beta", "page status badge");
- await expectVisible(page, ".oc-code.has-line-numbers .code-line.is-highlighted", "line-highlighted code");
- await expectVisible(page, ".oc-badge.oc-badge-orange", "badge");
- await expectVisible(page, ".oc-panel", "panel");
- await expectVisible(page, ".oc-prompt [data-prompt-copy]", "prompt copy");
- await expectVisible(page, ".oc-tile-group .oc-tile", "tiles");
+async function checkMermaid() {
+ const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
+ try {
+ await page.goto(base + "/__elements", { waitUntil: "networkidle" });
+ await checkMermaidContent(page);
+ const before = await page.locator(".oc-mermaid.is-rendered > svg").evaluateAll((svgs) => svgs.map((svg) => svg.id));
+ const theme = await page.locator("html").getAttribute("data-theme");
+ await page.locator(".header-row [data-theme-toggle]").click();
+ await page.waitForFunction(({ ids, previousTheme }) => {
+ const svgs = [...document.querySelectorAll(".oc-mermaid.is-rendered > svg")];
+ return document.documentElement.dataset.theme !== previousTheme
+ && svgs.length === ids.length && svgs.every((svg, i) => svg.id !== ids[i]);
+ }, { ids: before, previousTheme: theme });
+ await expectMermaidDiagrams(page);
+ await page.locator(".oc-mermaid.is-error pre code").waitFor({ state: "visible", timeout: 10000 });
+ await page.evaluate(() => { window.__mermaidNavigationProbe = true; });
+ await page.locator("[data-mermaid-expand]").first().click();
+ await page.locator("[data-mermaid-overlay-canvas] svg").waitFor({ state: "visible", timeout: 10000 });
+ // Use the normal link handler so the cached module and overlay lifecycle survive PJAX.
+ await page.keyboard.press("Escape");
+ await navigateMermaidPage(page, "/");
+ if (await page.locator("[data-mermaid-overlay].open").count()
+ || await page.locator("body.has-mermaid-overlay").count()) {
+ throw new Error("Mermaid overlay state leaked across page navigation");
+ }
+ await navigateMermaidPage(page, "/__elements");
+ await expectMermaidDiagrams(page);
+ await page.locator(".oc-mermaid.is-error pre code").waitFor({ state: "visible", timeout: 10000 });
+ await page.locator("[data-mermaid-expand]").first().click();
+ await page.locator("[data-mermaid-overlay-canvas] svg").waitFor({ state: "visible", timeout: 10000 });
+ await page.keyboard.press("Escape");
+ } finally {
+ await page.close();
+ }
+}
+
+async function checkMermaidContent(page) {
await expectVisible(page, ".oc-mermaid", "mermaid block");
- await page.locator(".oc-mermaid.is-rendered svg").first().waitFor({ state: "visible", timeout: 10000 });
+ await expectMermaidDiagrams(page);
await page.waitForFunction(() =>
[...document.querySelectorAll(".oc-mermaid.is-rendered")]
.some((block) => block.getAttribute("data-mermaid")?.includes("
") && block.querySelector("svg"))
);
await page.locator("[data-mermaid-expand]").first().click();
await page.locator("[data-mermaid-overlay].open").waitFor({ state: "visible" });
+ await page.locator("[data-mermaid-overlay-canvas] svg").waitFor({ state: "visible", timeout: 10000 });
await page.keyboard.press("Tab");
const mermaidOverlayFocus = await page.evaluate(() => ({
activeInOverlay: Boolean(document.querySelector("[data-mermaid-overlay].open")?.contains(document.activeElement)),
@@ -585,6 +628,7 @@ async function checkDesktop() {
}
await page.locator("[data-mermaid-expand]").first().click();
await page.locator("[data-mermaid-overlay].open").waitFor({ state: "visible" });
+ await page.locator("[data-mermaid-overlay-canvas] svg").waitFor({ state: "visible", timeout: 10000 });
await page.evaluate(() => history.pushState({ docs: true }, "", `${location.pathname}#fixture-components`));
await page.goBack();
await page.locator("[data-mermaid-overlay].open").waitFor({ state: "hidden" });
@@ -600,6 +644,7 @@ async function checkDesktop() {
}
await page.locator("[data-mermaid-expand]").first().click();
await page.locator("[data-mermaid-overlay].open").waitFor({ state: "visible" });
+ await page.locator("[data-mermaid-overlay-canvas] svg").waitFor({ state: "visible", timeout: 10000 });
await page.keyboard.press("Meta+K");
if (await page.locator(".search-modal.open").count()) {
throw new Error("mermaid overlay allowed the search shortcut");
@@ -613,14 +658,46 @@ async function checkDesktop() {
throw new Error(`mermaid overlay did not close on Escape: ${JSON.stringify(mermaidOverlayClose)}`);
}
await page.locator(".oc-mermaid.is-error pre code").first().waitFor({ state: "visible", timeout: 10000 });
+ // Mermaid 12 reports the parser error; render IDs detect artifacts independent of that wording.
const mermaidErrorLeak = await page.evaluate(() => ({
bodyText: document.body.innerText.includes("Syntax error in text"),
leakedSvg: [...document.querySelectorAll("svg")]
- .some((svg) => !svg.closest("[data-mermaid]") && svg.textContent?.includes("Syntax error in text")),
+ .some((svg) => !svg.closest("[data-mermaid]")
+ && (svg.id.startsWith("oc_mermaid_") || svg.textContent?.includes("Syntax error in text"))),
}));
if (mermaidErrorLeak.bodyText || mermaidErrorLeak.leakedSvg) {
throw new Error(`mermaid error artifact leaked into shell: ${JSON.stringify(mermaidErrorLeak)}`);
}
+}
+
+async function checkDesktop() {
+ const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
+ await page.goto(`${base}/__elements`, { waitUntil: "networkidle" });
+ await page.screenshot({ path: path.join(artifacts, "elements-desktop-dark.png"), fullPage: true });
+ await page.evaluate(() => scrollTo(0, 760));
+ await page.screenshot({ path: path.join(artifacts, "elements-desktop-scrolled.png"), fullPage: false });
+
+ const overlap = await page.evaluate(() => {
+ const header = document.querySelector(".site-header")?.getBoundingClientRect();
+ const sidebar = document.querySelector(".sidebar")?.getBoundingClientRect();
+ const toc = document.querySelector(".toc")?.getBoundingClientRect();
+ return {
+ headerTop: header?.top,
+ sidebarClear: header && sidebar ? sidebar.top >= header.bottom - 1 : false,
+ tocClear: header && toc ? toc.top >= header.bottom - 1 : false,
+ };
+ });
+ if (overlap.headerTop !== 0 || !overlap.sidebarClear || !overlap.tocClear) {
+ throw new Error(`desktop sticky shell overlap: ${JSON.stringify(overlap)}`);
+ }
+
+ await expectVisible(page, ".oc-code [data-code-copy]", "code copy button");
+ await expectVisible(page, ".page-status .page-status-beta", "page status badge");
+ await expectVisible(page, ".oc-code.has-line-numbers .code-line.is-highlighted", "line-highlighted code");
+ await expectVisible(page, ".oc-badge.oc-badge-orange", "badge");
+ await expectVisible(page, ".oc-panel", "panel");
+ await expectVisible(page, ".oc-prompt [data-prompt-copy]", "prompt copy");
+ await expectVisible(page, ".oc-tile-group .oc-tile", "tiles");
const componentSkin = await page.evaluate(() => {
const code = getComputedStyle(document.querySelector(".oc-code"));
const pre = getComputedStyle(document.querySelector(".oc-code pre"));