Skip to content

docs: add Global Accounts Playground page (interactive wallet demo)#581

Open
patcapulong wants to merge 1 commit into
mainfrom
pat/wallet-demo-playground-docs
Open

docs: add Global Accounts Playground page (interactive wallet demo)#581
patcapulong wants to merge 1 commit into
mainfrom
pat/wallet-demo-playground-docs

Conversation

@patcapulong

Copy link
Copy Markdown
Contributor

Summary

Adds the Playground page (global-accounts/demo) that embeds the interactive Grid wallet demo, plus its nav entry and full-bleed page styling.

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

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>
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jun 15, 2026 11:27am

Request Review

@mintlify

mintlify Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Grid 🟢 Ready View Preview Jun 15, 2026, 11:30 AM

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds the Playground docs page (global-accounts/demo) that embeds an interactive Grid wallet demo via a body-mounted, position-synced iframe over a placeholder div. The implementation uses a custom React snippet, scoped CSS overrides, and a postMessage-based theme sync bridge between the docs site and the external Vercel-hosted app.

  • wallet-demo-embed.mdx mounts the iframe on <body> and continuously syncs its position to #wallet-demo-container using a requestAnimationFrame loop; it also implements bidirectional dark/light theme sync over postMessage.
  • style.css adds ~130 lines of scoped overrides to produce a full-bleed, edge-to-edge layout for the demo page while leaving the sidebar intact.
  • docs.json and demo.mdx wire up the nav entry and page shell; phone.svg provides the sidebar icon.

Confidence Score: 3/5

The 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 React.useEffect call without an explicit React import is the most immediate risk — if Mintlify's MDX pipeline doesn't polyfill React as a global, the component silently fails to mount and the demo page renders blank. Alongside that, the postMessage handler accepts messages from any origin, letting external pages toggle the docs site's dark-mode class. Both issues are concentrated in the single embed snippet, but they are the core of this PR's functionality.

mintlify/snippets/global-accounts/wallet-demo-embed.mdx — the React import and postMessage origin check both need to be addressed here.

Security Review

  • Unvalidated postMessage origin (wallet-demo-embed.mdx line ~50): The handleMessage listener does not check e.origin, so any cross-origin page can send { type: 'theme-sync' } to toggle the docs page's dark/light mode, or { type: 'theme-request' } to probe what theme the user has active.
  • No iframe sandbox attribute (wallet-demo-embed.mdx line ~12): The third-party grid-wallet-demo.vercel.app iframe runs without sandboxing, retaining rights to top-level navigation, form submission, and window.opener access.

Important Files Changed

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"
Loading
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

Comment on lines +50 to +63

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');
}
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security 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.

Comment on lines +1 to +2
export const WalletDemoEmbed = () => {
React.useEffect(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 partner 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.

Suggested change
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.

Comment on lines +25 to +39
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);
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Comment on lines +10 to +17
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;';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security 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.

Comment thread mintlify/style.css
Comment on lines +4108 to +4120
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant