docs: add Global Accounts Playground page (interactive wallet demo)#581
docs: add Global Accounts Playground page (interactive wallet demo)#581patcapulong wants to merge 1 commit into
Conversation
Adds the /global-accounts/demo page that embeds the live wallet demo (hosted at grid-wallet-demo.vercel.app) via a position-synced iframe, plus the nav entry and the full-bleed page styling. Split out of the large app-code branch so Mintlify builds a preview. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
Greptile SummaryAdds the Playground docs page (
Confidence Score: 3/5The page shell, CSS, and nav wiring are straightforward, but the embed script has two issues that could affect correctness and safety before this ships. The mintlify/snippets/global-accounts/wallet-demo-embed.mdx — the React import and postMessage origin check both need to be addressed here.
|
| Filename | Overview |
|---|---|
| mintlify/snippets/global-accounts/wallet-demo-embed.mdx | Core embed logic: missing React import risks runtime errors, unvalidated postMessage origin allows any site to toggle docs theme, and a continuous RAF loop reads layout every frame. |
| mintlify/style.css | Adds full-bleed layout overrides scoped to #wallet-demo-container; hardcoded 112px/120px navbar offsets are fragile but functionally correct today. |
| mintlify/global-accounts/demo.mdx | Minimal page shell with correct frontmatter; mounts the container div and imports the embed snippet. |
| mintlify/docs.json | Adds global-accounts/demo to the Overview nav group; change is correct and consistent with existing navigation structure. |
| mintlify/images/icons/phone.svg | Clean inline SVG phone icon using currentColor; no issues. |
Sequence Diagram
sequenceDiagram
participant User
participant DocsPage as Mintlify Docs Page
participant EmbedScript as WalletDemoEmbed (body)
participant DemoApp as grid-wallet-demo.vercel.app (iframe)
User->>DocsPage: Navigate to global-accounts/demo
DocsPage->>EmbedScript: Mount WalletDemoEmbed component
EmbedScript->>EmbedScript: "Create #wallet-demo-host div on body"
EmbedScript->>DemoApp: "Load iframe src with ?embed=true&theme=light|dark"
EmbedScript->>EmbedScript: "Start RAF loop — sync host position to #wallet-demo-container"
DemoApp-->>EmbedScript: "postMessage { type: 'theme-request' }"
EmbedScript-->>DemoApp: "postMessage { type: 'theme-sync', theme }"
User->>DocsPage: Toggle dark mode
DocsPage->>EmbedScript: MutationObserver fires (class change on html)
EmbedScript-->>DemoApp: "postMessage { type: 'theme-sync', theme }"
DemoApp-->>EmbedScript: "postMessage { type: 'theme-sync', theme }"
Note over EmbedScript: ignoreNextMutation flag prevents loop
User->>DocsPage: Navigate away from demo page
DocsPage->>EmbedScript: useEffect cleanup runs
EmbedScript->>EmbedScript: Cancel RAF, remove listener, disconnect observer
EmbedScript->>EmbedScript: "setTimeout 150ms — hide #wallet-demo-host"
Prompt To Fix All With AI
Fix the following 5 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 5
mintlify/snippets/global-accounts/wallet-demo-embed.mdx:50-63
**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.
### Issue 2 of 5
mintlify/snippets/global-accounts/wallet-demo-embed.mdx:1-2
`React.useEffect` is called directly on the `React` namespace, but `React` is never imported in this file. Mintlify's MDX pipeline uses the automatic JSX transform which does not inject `React` as a named global — only JSX pragma calls are shielded. If this runs in an environment where `React` is not globally polyfilled, the component will throw `ReferenceError: React is not defined` on mount. Destructure `useEffect` explicitly to be safe across all Mintlify versions.
```suggestion
import { useEffect } from 'react';
export const WalletDemoEmbed = () => {
useEffect(() => {
```
### Issue 3 of 5
mintlify/snippets/global-accounts/wallet-demo-embed.mdx:25-39
**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.
### Issue 4 of 5
mintlify/snippets/global-accounts/wallet-demo-embed.mdx:10-17
**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.
### Issue 5 of 5
mintlify/style.css:4108-4120
**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.
Reviews (1): Last reviewed commit: "docs: add the Global Accounts Playground..." | Re-trigger Greptile
|
|
||
| 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'); | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this 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.
Prompt To Fix With AI
This 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.| export const WalletDemoEmbed = () => { | ||
| React.useEffect(() => { |
There was a problem hiding this comment.
React.useEffect is called directly on the React namespace, but React is never imported in this file. Mintlify's MDX pipeline uses the automatic JSX transform which does not inject React as a named global — only JSX pragma calls are shielded. If this runs in an environment where React is not globally polyfilled, the component will throw ReferenceError: React is not defined on mount. Destructure useEffect explicitly to be safe across all Mintlify versions.
| export const WalletDemoEmbed = () => { | |
| React.useEffect(() => { | |
| import { useEffect } from 'react'; | |
| export const WalletDemoEmbed = () => { | |
| useEffect(() => { |
Prompt To Fix With AI
This is a comment left during a code review.
Path: mintlify/snippets/global-accounts/wallet-demo-embed.mdx
Line: 1-2
Comment:
`React.useEffect` is called directly on the `React` namespace, but `React` is never imported in this file. Mintlify's MDX pipeline uses the automatic JSX transform which does not inject `React` as a named global — only JSX pragma calls are shielded. If this runs in an environment where `React` is not globally polyfilled, the component will throw `ReferenceError: React is not defined` on mount. Destructure `useEffect` explicitly to be safe across all Mintlify versions.
```suggestion
import { useEffect } from 'react';
export const WalletDemoEmbed = () => {
useEffect(() => {
```
How can I resolve this? If you propose a fix, please make it concise.| 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); | ||
| }; |
There was a problem hiding this 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.
Prompt To Fix With AI
This 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.| 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;'; |
There was a problem hiding this 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.
Prompt To Fix With AI
This 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.| 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; |
There was a problem hiding this 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.
Prompt To Fix With AI
This 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!
Summary
Adds the Playground page (
global-accounts/demo) that embeds the interactive Grid wallet demo, plus its nav entry and full-bleed page styling.#wallet-demo-containerwith docs theme sync.pat/wallet-demo-country-banks, PR Interactive Grid Global Accounts wallet demo #580) so Mintlify can build a clean preview for review.Purpose
Staging preview so the team can try the demo in docs context and give feedback before we ship. Not for merge yet.
Made with Cursor