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
109 changes: 109 additions & 0 deletions mintlify/css-perf-patch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Rewrites three of Mintlify's code-block CSS rules whose selectors start
// with a bare `:has(` — an unanchored subject that, in Chrome/Blink,
// permanently flags the document root as ":has-affected" the first time a
// matching code block renders. From then on, every forced style recalc in
// the tab walks the entire document (~30x slower), which turns mermaid's
// ~280 getBBox measurements during navigation into a multi-second freeze
// (hover effects dead; scroll survives on the compositor thread).
//
// In Mintlify's DOM the ":has() parent" is always expressible as a preceding
// sibling instead, which Blink matches cheaply. Verified pixel-identical and
// computed-style-identical across all Global Accounts pages, light + dark.
//
// Rules are only touched on an exact selector match, so if Mintlify ships
// different CSS this becomes a no-op — the look can never break; worst case
// the perf fix silently stops applying.
//
// Coverage caveat: custom JS runs after first paint, so a tab whose FIRST
// page is one that renders standalone code blocks (e.g. implementation-
// overview) is poisoned before this runs, and that can't be undone. All
// pages rendered after this script runs (every SPA navigation) are safe.
// The complete fix is Mintlify correcting these selectors upstream — remove
// 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',
};

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.cssRules && rule.cssRules.length) {
patchRules(rule); // @media / @layer / @supports groups
continue;
}
var sel = rule.selectorText;
if (!sel || sel.indexOf(':has(') === -1) continue;
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]);
}
}
if (!hit) continue;
try {
var css = rule.cssText;
var body = css.slice(css.indexOf('{'));
owner.deleteRule(i);
owner.insertRule(out.join(',') + ' ' + body, i);
} catch (e) {
/* leave the rule as-is */
}
}
}

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

// Scan every frame while the page is still settling (idempotent: once a
// rule is rewritten its selector no longer matches the map)...
var settleAt = 0;
function loop(now) {
scanAll();
if (document.readyState === 'complete') {
if (!settleAt) settleAt = now;
if (now - settleAt > 2000) return watch();
}
requestAnimationFrame(loop);
}

// ...then only re-scan when new stylesheets appear (SPA navigations).
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') {
scanAll();
return;
}
}
}
}).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
44 changes: 30 additions & 14 deletions mintlify/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4096,18 +4096,29 @@ html.dark #flow-builder-container {
}
}

/* Prevent outer page scroll when flow-builder is present */
html:has(#flow-builder-container),
html:has(#flow-builder-container) body {
/* Prevent outer page scroll when flow-builder is present.

PERF: these page-scoped rules key off html.ls-page-flow-builder — a class
set from the pathname by sidebar-toggle.js (on load and on SPA navigation)
— NOT html:has(#flow-builder-container). A root-anchored :has() marks the
document root ":has-affected" in Blink, after which every forced style
recalc walks the whole document; pages that build DOM incrementally (e.g.
mermaid diagrams) then freeze navigation for seconds. (head.raw also
carries a pre-paint class setter, but the hosted renderer currently strips
head.raw scripts, so the JS is the effective mechanism; the sub-second
window before it runs on a full load of these two embed pages is not
noticeable.) */
html.ls-page-flow-builder,
html.ls-page-flow-builder body {
overflow: hidden !important;
}

/* Flow Builder mobile breadcrumb — hide text + chevron, keep hamburger, show (0, 0, 0) */
html:has(#flow-builder-container) #navbar button[class*="h-14"][class*="text-left"] > *:not(:first-child) {
html.ls-page-flow-builder #navbar button[class*="h-14"][class*="text-left"] > *:not(:first-child) {
display: none !important;
}

html:has(#flow-builder-container) #navbar button[class*="h-14"][class*="text-left"]::after {
html.ls-page-flow-builder #navbar button[class*="h-14"][class*="text-left"]::after {
content: "(0, 0, 0)";
margin-left: auto;
font-family: "Suisse Intl Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
Expand All @@ -4116,7 +4127,7 @@ html:has(#flow-builder-container) #navbar button[class*="h-14"][class*="text-lef
color: var(--ls-gray-400);
}

html.dark:has(#flow-builder-container) #navbar button[class*="h-14"][class*="text-left"]::after {
html.dark.ls-page-flow-builder #navbar button[class*="h-14"][class*="text-left"]::after {
color: var(--ls-gray-700);
}

Expand Down Expand Up @@ -4252,19 +4263,20 @@ html.dark #wallet-demo-host {
}
}

/* Prevent outer page scroll when the wallet demo is present */
html:has(#wallet-demo-container),
html:has(#wallet-demo-container) body {
/* Prevent outer page scroll when the wallet demo is present.
PERF: class-keyed, not html:has() — see the flow-builder note above. */
html.ls-page-wallet-demo,
html.ls-page-wallet-demo body {
overflow: hidden !important;
}

/* Wallet demo mobile breadcrumb — hide text + chevron, keep hamburger, show
(0, 0, 0) on the right (matches the flow-builder tab). */
html:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text-left"] > *:not(:first-child) {
html.ls-page-wallet-demo #navbar button[class*="h-14"][class*="text-left"] > *:not(:first-child) {
display: none !important;
}

html:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text-left"]::after {
html.ls-page-wallet-demo #navbar button[class*="h-14"][class*="text-left"]::after {
content: "(0, 0, 0)";
margin-left: auto;
font-family: "Suisse Intl Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
Expand All @@ -4273,7 +4285,7 @@ html:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text-left
color: var(--ls-gray-400);
}

html.dark:has(#wallet-demo-container) #navbar button[class*="h-14"][class*="text-left"]::after {
html.dark.ls-page-wallet-demo #navbar button[class*="h-14"][class*="text-left"]::after {
color: var(--ls-gray-700);
}

Expand Down Expand Up @@ -4823,8 +4835,12 @@ html.ls-nav-collapsed .ls-nav-rail::after {
/* Custom-layout pages (frontmatter mode: "custom", e.g. the flow builder) have
no docs sidebar — Mintlify tags them with .is-custom — so there's nothing to
toggle. The JS won't inject the rail there either; this is the no-flash guard
during SPA navigation. */
html:has(.is-custom) .ls-nav-rail {
during SPA navigation. PERF: class-keyed, not html:has() — the class is set
only by sidebar-toggle.js, from the DOM, once the body exists. That's
sufficient: on a full load of a custom page the rail is never injected in
the first place (hasVisibleSidebar() is false), so this rule only matters
for SPA navigation, where the JS is already running. */
html.ls-page-custom .ls-nav-rail {
display: none !important;
}
Comment thread
patcapulong marked this conversation as resolved.

Expand Down
Loading