modelcontextprotocol / modelcontextprotocol/ext-apps
autoResize misses absolutely-positioned portal growth (popover opens → no size-changed)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.9k
- Forks
- 387
- Avg merge
- 3h 21m
- Merged PRs (30d)
- 6
Description
Problem
With autoResize: true (the useApp() default), opening any portal-based popover — a date-picker calendar, a Select dropdown, a tooltip — never emits ui/notifications/size-changed, so the host iframe stays at the pre-popover height and the popover is clipped.
Two independent gaps in setupSizeChangedNotifications (v1.7.4):
- Trigger gap: the
ResizeObserverwatches onlydocument.documentElementanddocument.bodyborder-boxes. Component libraries (Mantine, Radix, MUI, Floating UI…) render popovers into an absolutely-positioned portal appended to<body>— typically inside a zero-height positioned wrapper. Neither observed border-box changes, so no measurement is ever scheduled. - Measurement gap: even when a measure does run, it reports
documentElement.getBoundingClientRect().heightunder a transientheight: max-content— which excludes out-of-flow content. An absolutely-positioned dropdown extending past the in-flow content does not contribute, so the reported height is unchanged anyway. (#619's proposedbody.scrollHeightmeasure would fix this half, but not gap 1.)
Repro
Any React app embedded via the SDK:
const { app } = useApp({ appInfo, capabilities: {} }); // autoResize default
// render a Mantine <DatePickerInput /> near the bottom of the content
Click the input → calendar opens in a portal, extends ~300px past the content → no size-changed notification → host iframe clips the calendar. Verified against a host that resizes the iframe to every reported height (our wire-contract harness drives AppBridge directly).
Workaround we ship
A MutationObserver on body funnelled through the public API:
useEffect(() => {
if (!app) return;
const report = () =>
app.sendSizeChanged({
width: Math.ceil(window.innerWidth),
height: Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
),
});
const mo = new MutationObserver(report);
mo.observe(document.body, { childList: true, subtree: true, attributes: true });
return () => mo.disconnect();
}, [app]);
scrollHeight includes the out-of-flow portal; the MutationObserver supplies the missing trigger.
Suggested fix
In setupSizeChangedNotifications, add a (debounced) MutationObserver on body alongside the existing ResizeObserver, and measure with Math.max(body.scrollHeight, documentElement.scrollHeight) (compatible with the direction #619 proposes) so out-of-flow portals are both detected and measured.
Related: #502 (height-management umbrella), #619 (measurement strategy), #567 (auto-resize side effects).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at setupSizeChangedNotifications and compare its ResizeObserver triggers and measurement with the body MutationObserver workaround described here and direction #619. Use the wire-contract harness that drives AppBridge to verify that opening a portal-based popover emits ui/notifications/size-changed with a height including the out-of-flow content.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- api, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 56/100