-
Notifications
You must be signed in to change notification settings - Fork 7
docs: add Global Accounts Playground page (interactive wallet demo) #581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| --- | ||
| title: "Playground" | ||
| description: "Create a Grid Global Account and watch the API calls fire in real time" | ||
| icon: "/images/icons/phone.svg" | ||
| "og:image": "/images/og/og-global-accounts.webp" | ||
| --- | ||
|
|
||
| import { WalletDemoEmbed } from '/snippets/global-accounts/wallet-demo-embed.mdx'; | ||
|
|
||
| <div id="wallet-demo-container"></div> | ||
|
|
||
| <WalletDemoEmbed /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| export const WalletDemoEmbed = () => { | ||
| React.useEffect(() => { | ||
| const isLocal = ['localhost', '127.0.0.1'].includes(window.location.hostname); | ||
| const base = isLocal ? 'http://localhost:4000' : 'https://grid-wallet-demo.vercel.app'; | ||
| const isDark = () => document.documentElement.classList.contains('dark'); | ||
|
|
||
| let host = document.getElementById('wallet-demo-host'); | ||
| if (!host) { | ||
| host = document.createElement('div'); | ||
| host.id = 'wallet-demo-host'; | ||
| host.style.cssText = 'position:fixed;z-index:1;overflow:hidden;'; | ||
| const iframe = document.createElement('iframe'); | ||
| iframe.id = 'wallet-demo-iframe'; | ||
| iframe.src = base + '/?embed=true&theme=' + (isDark() ? 'dark' : 'light'); | ||
| iframe.title = 'Grid Global Accounts — live demo'; | ||
| iframe.setAttribute('allow', 'publickey-credentials-create *; publickey-credentials-get *; clipboard-write'); | ||
| iframe.style.cssText = 'width:100%;height:100%;border:0;display:block;'; | ||
|
Comment on lines
+10
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The iframe embeds an external Vercel app ( Prompt To Fix With AIThis is a comment left during a code review.
Path: mintlify/snippets/global-accounts/wallet-demo-embed.mdx
Line: 10-17
Comment:
**No `sandbox` attribute on a third-party iframe**
The iframe embeds an external Vercel app (`grid-wallet-demo.vercel.app`) with no `sandbox` restrictions, giving it full access to `window.opener`, top-level navigation, form submission to any target, and more. Adding `sandbox="allow-scripts allow-same-origin allow-forms allow-popups"` (plus `allow-popups-to-escape-sandbox` if the app opens OAuth windows) would limit the blast radius if the hosted demo is ever compromised or replaced. Passkey `allow` directives work independently of sandbox and can be left as-is.
How can I resolve this? If you propose a fix, please make it concise. |
||
| host.appendChild(iframe); | ||
| document.body.appendChild(host); | ||
| } | ||
| if (window.__wdHideTimer) { clearTimeout(window.__wdHideTimer); window.__wdHideTimer = null; } | ||
| host.style.display = 'block'; | ||
|
|
||
| let raf = 0; | ||
| let last = { left: -1, top: -1, width: -1, height: -1 }; | ||
| const sync = () => { | ||
| const el = document.getElementById('wallet-demo-container'); | ||
| if (el && host) { | ||
| const r = el.getBoundingClientRect(); | ||
| if (r.left !== last.left || r.top !== last.top || r.width !== last.width || r.height !== last.height) { | ||
| host.style.left = r.left + 'px'; | ||
| host.style.top = r.top + 'px'; | ||
| host.style.width = r.width + 'px'; | ||
| host.style.height = r.height + 'px'; | ||
| last = { left: r.left, top: r.top, width: r.width, height: r.height }; | ||
| } | ||
| } | ||
| raf = requestAnimationFrame(sync); | ||
| }; | ||
|
Comment on lines
+25
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Prompt To Fix With AIThis is a comment left during a code review.
Path: mintlify/snippets/global-accounts/wallet-demo-embed.mdx
Line: 25-39
Comment:
**Unbounded `requestAnimationFrame` loop runs at ~60 fps while demo is visible**
The `sync` function schedules itself unconditionally with `requestAnimationFrame` on every frame, creating a loop that runs continuously at ~60 fps the entire time the demo page is open. While the change-guard (`r.left !== last.left || …`) avoids style mutations when nothing moves, the RAF callback itself still fires and reads `getBoundingClientRect()` (which forces layout) every frame. On a static layout this is a steady tax on the main thread. Consider using a `ResizeObserver` plus a `scroll` event listener instead, which fire only on actual geometry changes.
How can I resolve this? If you propose a fix, please make it concise. |
||
| raf = requestAnimationFrame(sync); | ||
|
|
||
| let ignoreNextMutation = false; | ||
| const sendTheme = () => { | ||
| const t = isDark() ? 'dark' : 'light'; | ||
| const iframe = document.getElementById('wallet-demo-iframe'); | ||
| if (iframe && iframe.contentWindow) { | ||
| iframe.contentWindow.postMessage({ type: 'theme-sync', theme: t }, '*'); | ||
| } | ||
| }; | ||
|
|
||
| const handleMessage = (e) => { | ||
| if (e.data && e.data.type === 'theme-request') { | ||
| sendTheme(); | ||
| return; | ||
| } | ||
| if (e.data && e.data.type === 'theme-sync') { | ||
| const wantsDark = e.data.theme === 'dark'; | ||
| if (isDark() !== wantsDark) { | ||
| ignoreNextMutation = true; | ||
| document.documentElement.classList.toggle('dark'); | ||
| } | ||
| } | ||
| }; | ||
|
Comment on lines
+50
to
+63
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Prompt To Fix With AIThis is a comment left during a code review.
Path: mintlify/snippets/global-accounts/wallet-demo-embed.mdx
Line: 50-63
Comment:
**Unvalidated postMessage origin allows any page to manipulate theme**
The `handleMessage` handler responds to `theme-sync` and `theme-request` messages from any window without checking `e.origin`. This means any third-party site can send `{ type: 'theme-sync', theme: 'dark' }` and silently toggle the docs page's dark/light mode class on `document.documentElement`. In a cross-site scenario (e.g., a user opens a malicious link that sends a postMessage to this page), the docs UI appearance can be flipped without user consent. At minimum, validate that `e.origin` matches `base` before acting on messages.
How can I resolve this? If you propose a fix, please make it concise. |
||
| window.addEventListener('message', handleMessage); | ||
|
|
||
| const obs = new MutationObserver(() => { | ||
| if (ignoreNextMutation) { ignoreNextMutation = false; return; } | ||
| sendTheme(); | ||
| }); | ||
| obs.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); | ||
|
|
||
| sendTheme(); | ||
| requestAnimationFrame(sendTheme); | ||
|
|
||
| return () => { | ||
| cancelAnimationFrame(raf); | ||
| window.removeEventListener('message', handleMessage); | ||
| obs.disconnect(); | ||
| window.__wdHideTimer = setTimeout(() => { | ||
| const h = document.getElementById('wallet-demo-host'); | ||
| if (h) h.style.display = 'none'; | ||
| }, 150); | ||
| }; | ||
| }, []); | ||
|
|
||
| return null; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4076,3 +4076,134 @@ html.dark:has(#flow-builder-container) #navbar button[class*="h-14"][class*="tex | |
| color: var(--ls-gray-700); | ||
| } | ||
|
|
||
|
|
||
| /* =========================================== | ||
| Wallet demo page (global-accounts/demo) | ||
| Docs sidebar stays; content column is iframe-only, edge-to-edge. | ||
| =========================================== */ | ||
|
|
||
| #content-container:has(#wallet-demo-container) { | ||
| max-width: none !important; | ||
| width: 100% !important; | ||
| margin: 0 !important; | ||
| padding: 0 !important; | ||
| } | ||
|
|
||
| /* Mintlify wraps content in max-w-5xl (1024px) — bust it for the demo */ | ||
| #content-container:has(#wallet-demo-container) > div.max-w-5xl, | ||
| #content-container:has(#wallet-demo-container) > div[class*="max-w-5xl"] { | ||
| max-width: none !important; | ||
| width: 100% !important; | ||
| margin-left: 0 !important; | ||
| margin-right: 0 !important; | ||
| gap: 0 !important; | ||
| } | ||
|
|
||
| #content-area:has(#wallet-demo-container) { | ||
| width: 100% !important; | ||
| flex: 1 1 auto !important; | ||
| min-width: 0 !important; | ||
| contain: none !important; | ||
| display: flex !important; | ||
| flex-direction: column !important; | ||
| padding: 0 !important; | ||
| overflow: hidden !important; | ||
| /* navbar (64px bar + 48px tabs = 112px) */ | ||
| min-height: calc(100dvh - 112px) !important; | ||
| } | ||
|
|
||
| /* Hide everything in the content column except the embed */ | ||
| #content-area:has(#wallet-demo-container) > header#header, | ||
| #content-area:has(#wallet-demo-container) > #pagination, | ||
| #content-area:has(#wallet-demo-container) > footer#footer, | ||
| #content-area:has(#wallet-demo-container) > div.sticky { | ||
| display: none !important; | ||
|
Comment on lines
+4108
to
+4120
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Prompt To Fix With AIThis is a comment left during a code review.
Path: mintlify/style.css
Line: 4108-4120
Comment:
**Hardcoded navbar offsets (`112px` / `120px`) are fragile**
The `112px` value (commented as "64px bar + 48px tabs") and the responsive `120px` are baked into three CSS rules and the JS `sync` loop indirectly (the container height drives the iframe height). If Mintlify changes its navbar or tab strip heights in a future update, the iframe will be clipped or leave a gap without any obvious error. Consider driving these via a CSS custom property (`--demo-navbar-offset: 112px`) defined in one place so adjustments require a single change.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| height: 0 !important; | ||
| margin: 0 !important; | ||
| padding: 0 !important; | ||
| overflow: hidden !important; | ||
| visibility: hidden !important; | ||
| } | ||
|
|
||
| #content-container:has(#wallet-demo-container) #content-side-layout { | ||
| display: none !important; | ||
| } | ||
|
|
||
| #content-area:has(#wallet-demo-container) > #content { | ||
| flex: 1 1 auto !important; | ||
| width: 100% !important; | ||
| margin: 0 !important; | ||
| padding: 0 !important; | ||
| max-width: none !important; | ||
| min-height: 0 !important; | ||
| contain: none !important; | ||
| } | ||
|
|
||
| #content:has(#wallet-demo-container) .prose, | ||
| .prose:has(#wallet-demo-container) { | ||
| max-width: none !important; | ||
| margin: 0 !important; | ||
| padding: 0 !important; | ||
| } | ||
|
|
||
| /* Placeholder that defines the embed's box. The actual iframe lives in | ||
| #wallet-demo-host on <body> (so theme toggles don't reload it) and is glued | ||
| to this box's position by the embed script. */ | ||
| #wallet-demo-container { | ||
| width: 100%; | ||
| height: calc(100dvh - 112px); | ||
| margin: 0; | ||
| padding: 0; | ||
| line-height: 0; | ||
| background: var(--ls-gray-100); | ||
| } | ||
|
|
||
| html.dark #wallet-demo-container { | ||
| background: var(--ls-gray-975); | ||
| } | ||
|
|
||
| /* Persistent iframe host — body-mounted, positioned over the placeholder. */ | ||
| #wallet-demo-host { | ||
| background: var(--ls-gray-100); | ||
| } | ||
|
|
||
| html.dark #wallet-demo-host { | ||
| background: var(--ls-gray-975); | ||
| } | ||
|
|
||
| #wallet-demo-iframe { | ||
| width: 100% !important; | ||
| height: 100% !important; | ||
| min-height: 0 !important; | ||
| border: none !important; | ||
| display: block !important; | ||
| aspect-ratio: unset !important; | ||
| } | ||
|
|
||
| /* Flatten Mintlify's iframe aspect-ratio wrapper */ | ||
| #wallet-demo-container > div, | ||
| #wallet-demo-container > [style*="aspect-ratio"] { | ||
| aspect-ratio: unset !important; | ||
| width: 100% !important; | ||
| height: 100% !important; | ||
| max-width: none !important; | ||
| margin: 0 !important; | ||
| padding: 0 !important; | ||
| } | ||
|
|
||
| @media (max-width: 1023px) { | ||
| #content-area:has(#wallet-demo-container) { | ||
| min-height: calc(100dvh - 120px) !important; | ||
| } | ||
|
|
||
| #wallet-demo-container { | ||
| height: calc(100dvh - 120px); | ||
| } | ||
| } | ||
|
|
||
| /* Prevent outer page scroll when the wallet demo is present */ | ||
| html:has(#wallet-demo-container), | ||
| html:has(#wallet-demo-container) body { | ||
| overflow: hidden !important; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React.useEffectis called directly on theReactnamespace, butReactis never imported in this file. Mintlify's MDX pipeline uses the automatic JSX transform which does not injectReactas a named global — only JSX pragma calls are shielded. If this runs in an environment whereReactis not globally polyfilled, the component will throwReferenceError: React is not definedon mount. DestructureuseEffectexplicitly to be safe across all Mintlify versions.Prompt To Fix With AI