Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 276 additions & 0 deletions mintlify/css-perf-patch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// Neutralizes a Chrome/Blink pathology triggered by Mintlify's `:has()` CSS.
//
// THE BUG: three of Mintlify's code-block rules have selectors that start
// with a bare `:has(` (unanchored subject). The first time a matching
// standalone code block renders, Blink permanently marks the document root
// ":has-affected". While such marks exist AND `:has()` rules are present in
// active stylesheets, every forced style recalc walks the entire document
// (~30x slower: ~0.1ms -> ~20ms per recalc). Mermaid's ~280 getBBox
// measurements during navigation then freeze the page for many seconds
// (hovers dead; scroll survives on the compositor thread).
//
// Verified properties (see PR #697 for the experiments):
// - the marks survive rule removal, content replacement, and forced
// recalcs — they last for the life of the tab;
// - but they only COST while `:has()` rules are present: with every
// :has rule removed, recalcs drop back to ~0.1ms instantly;
// - partial removal does not help — any remaining :has rule keeps the
// expensive path armed.
//
// TWO DEFENSES, both in this file:
//
// 1. PREVENTION (always on): rewrite the three bare-:has selectors to
// preceding-sibling equivalents the moment stylesheets appear. In
// Mintlify's DOM the ":has() parent" is always a preceding sibling, so
// this is visually identical (verified pixel-identical and computed-
// style-identical across all Global Accounts pages, light + dark).
// Exact-match only: if Mintlify ships different CSS this is a no-op.
//
// 2. CURE (poisoned tabs only): custom JS runs after first paint, so a tab
// whose FIRST page renders a matching code block is marked before we can
// prevent it. For those tabs we detect the poisoned state (one cheap
// probe) and, around each SPA navigation, temporarily stash-and-delete
// ALL :has rules (fighting mid-navigation stylesheet re-injection), then
// restore them in place — same owner sheet, same index, so cascade and
// @layer order are preserved. Cosmetic :has styling (sidebar chevron
// hiding, code-block corner effects) is simplified for the few seconds
// of the transition on those rare tabs; steady-state look is untouched.
//
// The complete fix is Mintlify correcting the three selectors upstream —
// delete this file when they do.
(function () {
var MAP = {
':has([data-floating-buttons]) > [data-component-part="code-block-root"] pre > code':
'[data-floating-buttons] ~ [data-component-part="code-block-root"] pre > code',
':has([data-component-part="code-block-header"]) > [data-component-part="code-block-root"] pre > code':
'[data-component-part="code-block-header"] ~ [data-component-part="code-block-root"] pre > code',
':has([data-component-part="code-group-tab-bar"]) [data-component-part="code-block-root"] pre > code':
'[data-component-part="code-group-tab-bar"] ~ [data-component-part="code-block-root"] pre > code, ' +
'[data-component-part="code-group-tab-bar"] ~ * [data-component-part="code-block-root"] pre > code',
};

// Rewrites a selector list via MAP. Returns null when nothing matched.
function mapSelectorList(sel) {
if (!sel || sel.indexOf(':has(') === -1) return null;
var parts = sel.split(',');
var out = [];
var hit = false;
for (var j = 0; j < parts.length; j++) {
var trimmed = parts[j].trim();
if (MAP[trimmed]) {
out.push(MAP[trimmed]);
hit = true;
} else {
out.push(parts[j]);
}
}
return hit ? out.join(',') : null;
}

/* ------------------------- defense 1: prevention ------------------------ */

function patchRules(owner) {
var list;
try {
list = owner.cssRules;
} catch (e) {
return; // cross-origin sheet
}
if (!list) return;
for (var i = list.length - 1; i >= 0; i--) {
var rule = list[i];
if (!rule.selectorText && rule.cssRules && rule.cssRules.length) {
patchRules(rule); // @media / @layer / @supports groups
continue;
}
var mapped = mapSelectorList(rule.selectorText);
if (!mapped) continue;
try {
var css = rule.cssText;
var body = css.slice(css.indexOf('{'));
owner.deleteRule(i);
owner.insertRule(mapped + ' ' + body, i);
} catch (e) {
/* leave the rule as-is */
}
}
}

/* --------------------------- defense 2: cure ---------------------------- */

var poisoned = false;
var poisonKnown = false; // verdict is only trustworthy once the page has rendered
var curing = false;
var stash = []; // { owner, index, cssText } in deletion order (desc index per owner)
var restoreTimer = 0;

function probeCost() {
var root = document.documentElement;
void root.offsetHeight;
var d = document.createElement('div');
document.body.appendChild(d);
var t0 = performance.now();
void root.offsetHeight;
var cost = performance.now() - t0;
d.remove();
void root.offsetHeight;
return cost;
}

function detectPoison() {
if (!document.body) return;
try {
var runs = [probeCost(), probeCost(), probeCost()].sort(function (a, b) { return a - b; });
poisoned = runs[1] > 4; // healthy ~0.1-1ms, poisoned ~9-20ms
poisonKnown = true;
} catch (e) {
poisoned = false;
}
}

function stashDeleteIn(owner) {
var list;
try {
list = owner.cssRules;
} catch (e) {
return;
}
if (!list) return;
for (var i = list.length - 1; i >= 0; i--) {
var rule = list[i];
var sel = rule.selectorText;
if (sel && sel.indexOf(':has(') !== -1) {
try {
stash.push({ owner: owner, index: i, cssText: rule.cssText });
owner.deleteRule(i);
continue;
} catch (e) {
/* keep rule */
}
}
if (rule.cssRules && rule.cssRules.length) stashDeleteIn(rule);
}
}

function stashDeleteAll() {
for (var i = 0; i < document.styleSheets.length; i++) {
stashDeleteIn(document.styleSheets[i]);
}
}

function restoreStash() {
// Reinsert in reverse deletion order: per owner that yields ascending
// indexes, so each recorded index is valid again at insertion time.
for (var i = stash.length - 1; i >= 0; i--) {
var entry = stash[i];
var cssText = entry.cssText;
// Never restore a bare-:has original — apply the prevention rewrite.
var brace = cssText.indexOf('{');
var mapped = mapSelectorList(cssText.slice(0, brace).trim());
if (mapped) cssText = mapped + ' ' + cssText.slice(brace);
try {
var max = entry.owner.cssRules ? entry.owner.cssRules.length : 0;
entry.owner.insertRule(cssText, Math.min(entry.index, max));
} catch (e) {
/* owner sheet gone or rule now invalid — skip */
}
}
stash = [];
curing = false;
}

function startCure() {
if (!poisoned || curing) return;
curing = true;
stashDeleteAll();
clearTimeout(restoreTimer);
restoreTimer = setTimeout(restoreStash, 4000);
}

function extendCure() {
// another navigation while a window is open: re-sweep + push restore out
stashDeleteAll();
clearTimeout(restoreTimer);
restoreTimer = setTimeout(restoreStash, 4000);
}

function onNavigate() {
// The scheduled detection waits for the page to settle, but a user can
// navigate before that (land on a poisoning page, click within ~2s).
// By navigation time the landing page has rendered, so a probe taken
// right now is trustworthy — spend ~100ms to check rather than risk a
// multi-second freeze.
if (!poisonKnown) detectPoison();
if (!poisoned) return;
if (curing) extendCure();
else startCure();
}

// Navigation triggers: link clicks (earliest), history traversal, and a
// pathname watcher as fallback for programmatic navigation.
document.addEventListener(
'click',
function (e) {
var a = e.target && e.target.closest && e.target.closest('a[href]');
if (!a) return;
var href = a.getAttribute('href') || '';
if (href.charAt(0) === '/' && href.indexOf('//') !== 0) onNavigate();
},
true
);
window.addEventListener('popstate', onNavigate);
var lastPath = location.pathname;

/* ------------------------------ scheduling ------------------------------ */

function sweep() {
if (curing) {
stashDeleteAll(); // mid-navigation sheet re-injection brings rules back
} else {
for (var i = 0; i < document.styleSheets.length; i++) {
patchRules(document.styleSheets[i]);
}
}
if (location.pathname !== lastPath) {
lastPath = location.pathname;
onNavigate();
}
}

// Sweep every frame while the page settles (idempotent), then only when
// new stylesheets appear (SPA navigations). Poison detection runs once,
// after the page has settled.
var settleAt = 0;
function loop(now) {
sweep();
if (document.readyState === 'complete') {
if (!settleAt) settleAt = now;
if (now - settleAt > 2000) {
detectPoison();
return watch();
}
}
requestAnimationFrame(loop);
}

function watch() {
new MutationObserver(function (muts) {
for (var m = 0; m < muts.length; m++) {
var added = muts[m].addedNodes;
for (var n = 0; n < added.length; n++) {
var tag = added[n].tagName;
if (tag === 'STYLE' || tag === 'LINK') {
sweep();
return;
}
}
}
if (location.pathname !== lastPath) {
lastPath = location.pathname;
onNavigate();
}
}).observe(document.documentElement, { childList: true, subtree: true });
}

loop(performance.now());
})();
2 changes: 1 addition & 1 deletion mintlify/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@
},
"footer": {},
"head": {
"raw": "<style>@font-face{font-family:\"Suisse Intl\";src:url(\"/fonts/suisse-intl/SuisseIntl-Regular.woff2\") format(\"woff2\");font-weight:400;font-style:normal;font-display:swap;ascent-override:85%;descent-override:15%;line-gap-override:0%}@font-face{font-family:\"Suisse Intl\";src:url(\"/fonts/suisse-intl/SuisseIntl-Book.woff2\") format(\"woff2\");font-weight:450;font-style:normal;font-display:swap;ascent-override:85%;descent-override:15%;line-gap-override:0%}@font-face{font-family:\"Suisse Intl\";src:url(\"/fonts/suisse-intl/SuisseIntl-Medium.woff2\") format(\"woff2\");font-weight:500;font-style:normal;font-display:swap;ascent-override:85%;descent-override:15%;line-gap-override:0%}@font-face{font-family:\"Suisse Intl\";src:url(\"/fonts/suisse-intl/SuisseIntl-Medium.woff2\") format(\"woff2\");font-weight:700;font-style:normal;font-display:swap;ascent-override:85%;descent-override:15%;line-gap-override:0%}@font-face{font-family:\"Suisse Intl Mono\";src:url(\"/fonts/suisse-intl-mono/SuisseIntlMono-Regular-WebXL.woff2\") format(\"woff2\");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:\"Suisse Intl Mono\";src:url(\"/fonts/suisse-intl-mono/SuisseIntlMono-Bold-WebXL.woff2\") format(\"woff2\");font-weight:700;font-style:normal;font-display:swap}*{font-feature-settings:\"salt\" on}</style><style>#header:empty,#page-title:empty{display:none!important}</style><script>if(location.pathname==='/'||location.pathname==='/index'||location.pathname===''){document.write('<style>header#header,#page-title,#page-context-menu-button,#page-context-menu,.eyebrow,#pagination,[class*=\"space-y-2\"]>*{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important;margin:0!important;padding:0!important}</style>');document.documentElement.classList.add('is-homepage');}</script><style>html.is-homepage #pagination,body.is-homepage #pagination,html.is-homepage #footer,body.is-homepage #footer,html.is-homepage header#header,body.is-homepage header#header,html.is-homepage #page-title,body.is-homepage #page-title,html.is-homepage #page-context-menu-button,body.is-homepage #page-context-menu-button,html.is-homepage #page-context-menu,body.is-homepage #page-context-menu,html.is-homepage .eyebrow,body.is-homepage .eyebrow{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important}</style><script>if(location.pathname==='/flow-builder'){document.write('<style>header#header,#page-title,#page-context-menu-button,#page-context-menu,.eyebrow,#pagination{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important;margin:0!important;padding:0!important}</style>');}</script><style>html:has(#flow-builder-container) header#header,html:has(#flow-builder-container) #page-title,html:has(#flow-builder-container) .eyebrow,html:has(#flow-builder-container) #page-context-menu-button,html:has(#flow-builder-container) #page-context-menu,html:has(#flow-builder-container) #pagination{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important;margin:0!important;padding:0!important}</style><script>(function(){try{if(localStorage.getItem('ls-nav-collapsed')==='1'){document.documentElement.classList.add('ls-nav-collapsed');}}catch(e){}})();</script>",
"raw": "<style>@font-face{font-family:\"Suisse Intl\";src:url(\"/fonts/suisse-intl/SuisseIntl-Regular.woff2\") format(\"woff2\");font-weight:400;font-style:normal;font-display:swap;ascent-override:85%;descent-override:15%;line-gap-override:0%}@font-face{font-family:\"Suisse Intl\";src:url(\"/fonts/suisse-intl/SuisseIntl-Book.woff2\") format(\"woff2\");font-weight:450;font-style:normal;font-display:swap;ascent-override:85%;descent-override:15%;line-gap-override:0%}@font-face{font-family:\"Suisse Intl\";src:url(\"/fonts/suisse-intl/SuisseIntl-Medium.woff2\") format(\"woff2\");font-weight:500;font-style:normal;font-display:swap;ascent-override:85%;descent-override:15%;line-gap-override:0%}@font-face{font-family:\"Suisse Intl\";src:url(\"/fonts/suisse-intl/SuisseIntl-Medium.woff2\") format(\"woff2\");font-weight:700;font-style:normal;font-display:swap;ascent-override:85%;descent-override:15%;line-gap-override:0%}@font-face{font-family:\"Suisse Intl Mono\";src:url(\"/fonts/suisse-intl-mono/SuisseIntlMono-Regular-WebXL.woff2\") format(\"woff2\");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:\"Suisse Intl Mono\";src:url(\"/fonts/suisse-intl-mono/SuisseIntlMono-Bold-WebXL.woff2\") format(\"woff2\");font-weight:700;font-style:normal;font-display:swap}*{font-feature-settings:\"salt\" on}</style><style>#header:empty,#page-title:empty{display:none!important}</style><script>if(location.pathname==='/'||location.pathname==='/index'||location.pathname===''){document.write('<style>header#header,#page-title,#page-context-menu-button,#page-context-menu,.eyebrow,#pagination,[class*=\"space-y-2\"]>*{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important;margin:0!important;padding:0!important}</style>');document.documentElement.classList.add('is-homepage');}</script><style>html.is-homepage #pagination,body.is-homepage #pagination,html.is-homepage #footer,body.is-homepage #footer,html.is-homepage header#header,body.is-homepage header#header,html.is-homepage #page-title,body.is-homepage #page-title,html.is-homepage #page-context-menu-button,body.is-homepage #page-context-menu-button,html.is-homepage #page-context-menu,body.is-homepage #page-context-menu,html.is-homepage .eyebrow,body.is-homepage .eyebrow{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important}</style><script>if(location.pathname==='/flow-builder'){document.write('<style>header#header,#page-title,#page-context-menu-button,#page-context-menu,.eyebrow,#pagination{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important;margin:0!important;padding:0!important}</style>');}</script><script>(function(){var p=location.pathname.replace(/\\/+$/,'')||'/';var c=document.documentElement.classList;if(p==='/flow-builder'){c.add('ls-page-flow-builder');}if(p==='/global-accounts/demo'){c.add('ls-page-wallet-demo');}})();</script><style>html.ls-page-flow-builder header#header,html.ls-page-flow-builder #page-title,html.ls-page-flow-builder .eyebrow,html.ls-page-flow-builder #page-context-menu-button,html.ls-page-flow-builder #page-context-menu,html.ls-page-flow-builder #pagination{display:none!important;visibility:hidden!important;height:0!important;overflow:hidden!important;margin:0!important;padding:0!important}</style><script>(function(){try{if(localStorage.getItem('ls-nav-collapsed')==='1'){document.documentElement.classList.add('ls-nav-collapsed');}}catch(e){}})();</script>",
"links": [
{
"rel": "preload",
Expand Down
36 changes: 31 additions & 5 deletions mintlify/sidebar-toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,26 @@
});
}

// Page-scoped classes on <html> — the cheap replacement for html:has(...)
// selectors in style.css / head.raw. A :has() anchored at the root gets
// re-evaluated on DOM mutations anywhere in the document, which froze
// navigation for seconds on pages that build DOM incrementally (mermaid
// diagrams). This is the effective setter on both full loads and SPA
// navigations (head.raw carries a pre-paint copy, but the hosted renderer
// currently strips head.raw scripts).
function updatePageClasses() {
var root = document.documentElement;
var path = location.pathname.replace(/\/+$/, '') || '/';
root.classList.toggle('ls-page-flow-builder', path === '/flow-builder');
root.classList.toggle('ls-page-wallet-demo', path === '/global-accounts/demo');
// Custom-layout pages (frontmatter mode: "custom") — detected from the
// DOM so future custom pages are covered without listing paths here.
root.classList.toggle('ls-page-custom', !!document.querySelector('.is-custom'));
}

function sync() {
var root = document.documentElement;
updatePageClasses();
// Navigation/first paint must never animate (only deliberate toggles do —
// see the click handler). Clear the animate flag, and snap the rail button
// for this navigation-driven state change so its icon doesn't ghost in/out
Expand All @@ -249,17 +267,19 @@
}

// SPA navigation: Mintlify swaps content without a full reload. On a path
// change, re-sync. Otherwise only re-add the rail if Mintlify wiped it — a
// cheap guard so we don't read layout every frame. Custom-layout pages are
// handled by sync() on navigation plus the CSS :has(.is-custom) rule, so the
// rail never lingers visibly even without per-frame polling here.
// change, re-sync. Otherwise only schedule the rAF-debounced ensure pass
// when something is actually out of sync: the rail/footer button got wiped,
// or the .is-custom marker (which mounts after the pathname flips) doesn't
// match the ls-page-custom class yet — a cheap guard so we don't read
// layout every frame.
var lastPath = location.pathname;
var ensureScheduled = false;
function scheduleEnsure() {
if (ensureScheduled) return;
ensureScheduled = true;
requestAnimationFrame(function () {
ensureScheduled = false;
updatePageClasses();
ensureRail();
ensureFooterBtn();
});
Expand All @@ -268,7 +288,13 @@
if (location.pathname !== lastPath) {
lastPath = location.pathname;
sync();
} else if (
return;
}
var customStale =
!!document.querySelector('.is-custom') !==
document.documentElement.classList.contains('ls-page-custom');
if (
customStale ||
!rail ||
!document.body.contains(rail) ||
!footerBtn ||
Expand Down
Loading
Loading