Two findings from the Greptile review on #1032. Both are shared code copied across the playgrounds, so they must be fixed together in one follow-up PR.
1. Mobile history entries accumulate
Every transition into the mobile playground view pushes a history entry. The Configure/back control only changes React state and leaves the entry on the stack. Repeating Explore → Configure stacks same-document entries, so the user must press Back several times to leave the page. The popstate handler also switches to Configure for every pop without checking that the entry belongs to this view.
Verbatim copies:
components/grid-statements-demo/src/app/page.tsx:217-226
components/grid-cards-demo/src/app/page.tsx:99-108
components/grid-wallet-demo/src/app/page.tsx:99-108
components/grid-visualizer/src/app/page.tsx:81-90 (same pattern; view names are results/wizard)
Proposed fix:
- In
goConfigure (and the Visualizer equivalent), call history.back() when history.state?.mobileView === 'playground' instead of only setting state. Let the popstate handler do the state change.
- In
onPop, only switch to Configure when the popped state is not the playground entry (guard on event.state?.mobileView).
Greptile thread: #1032 (comment)
2. Iframe postMessage trusts all senders
The docs embed snippets accept theme-request, nav-request, and theme-sync messages based only on e.data.type. They do not check e.source or e.origin, and they reply with targetOrigin = '*'. Impact is limited to a docs-page theme flip and extra sync traffic. No data is exposed.
Byte-identical handleMessage blocks:
mintlify/snippets/cards/statements-demo-embed.mdx:77-94
mintlify/snippets/cards/cards-demo-embed.mdx:77-94
mintlify/snippets/global-accounts/wallet-demo-embed.mdx:68-85
Outbound postMessage(..., '*') calls that must change with them:
mintlify/snippets/cards/statements-demo-embed.mdx:45,73 (and the same lines in the Cards and Wallet snippets)
components/grid-statements-demo/src/hooks/useTheme.ts:51,63
components/grid-cards-demo/src/hooks/useTheme.ts:51,63
components/grid-wallet-demo/src/hooks/useTheme.ts:51,63
Proposed fix:
- Snippets: accept a message only when
e.source === iframe.contentWindow. Compute const targetOrigin = new URL(base).origin from the iframe src and use it for every postMessage.
useTheme.ts: check e.source === window.parent and, where the docs origin is known, e.origin; reply to document.referrer origin (or a configured docs origin) instead of '*'.
Note: src/hooks/useTheme.ts is parity-pinned. components/grid-statements-demo/scripts/check-chrome-parity.mjs requires the Statements copy to be byte-identical to Cards, and the Wallet copy is the same file. Change all three copies in the same commit or the parity check fails.
Greptile thread: #1032 (comment)
Links
Two findings from the Greptile review on #1032. Both are shared code copied across the playgrounds, so they must be fixed together in one follow-up PR.
1. Mobile history entries accumulate
Every transition into the mobile playground view pushes a history entry. The Configure/back control only changes React state and leaves the entry on the stack. Repeating Explore → Configure stacks same-document entries, so the user must press Back several times to leave the page. The
popstatehandler also switches to Configure for every pop without checking that the entry belongs to this view.Verbatim copies:
components/grid-statements-demo/src/app/page.tsx:217-226components/grid-cards-demo/src/app/page.tsx:99-108components/grid-wallet-demo/src/app/page.tsx:99-108components/grid-visualizer/src/app/page.tsx:81-90(same pattern; view names areresults/wizard)Proposed fix:
goConfigure(and the Visualizer equivalent), callhistory.back()whenhistory.state?.mobileView === 'playground'instead of only setting state. Let thepopstatehandler do the state change.onPop, only switch to Configure when the popped state is not the playground entry (guard onevent.state?.mobileView).Greptile thread: #1032 (comment)
2. Iframe postMessage trusts all senders
The docs embed snippets accept
theme-request,nav-request, andtheme-syncmessages based only one.data.type. They do not checke.sourceore.origin, and they reply withtargetOrigin = '*'. Impact is limited to a docs-page theme flip and extra sync traffic. No data is exposed.Byte-identical
handleMessageblocks:mintlify/snippets/cards/statements-demo-embed.mdx:77-94mintlify/snippets/cards/cards-demo-embed.mdx:77-94mintlify/snippets/global-accounts/wallet-demo-embed.mdx:68-85Outbound
postMessage(..., '*')calls that must change with them:mintlify/snippets/cards/statements-demo-embed.mdx:45,73(and the same lines in the Cards and Wallet snippets)components/grid-statements-demo/src/hooks/useTheme.ts:51,63components/grid-cards-demo/src/hooks/useTheme.ts:51,63components/grid-wallet-demo/src/hooks/useTheme.ts:51,63Proposed fix:
e.source === iframe.contentWindow. Computeconst targetOrigin = new URL(base).originfrom the iframesrcand use it for everypostMessage.useTheme.ts: checke.source === window.parentand, where the docs origin is known,e.origin; reply todocument.referrerorigin (or a configured docs origin) instead of'*'.Note:
src/hooks/useTheme.tsis parity-pinned.components/grid-statements-demo/scripts/check-chrome-parity.mjsrequires the Statements copy to be byte-identical to Cards, and the Wallet copy is the same file. Change all three copies in the same commit or the parity check fails.Greptile thread: #1032 (comment)
Links