modelcontextprotocol / modelcontextprotocol/ext-apps

autoResize misses absolutely-positioned portal growth (popover opens → no size-changed)

Open
#686 0 comments 0 reactions 0 assignees View on GitHub

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):

  1. Trigger gap: the ResizeObserver watches only document.documentElement and document.body border-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.
  2. Measurement gap: even when a measure does run, it reports documentElement.getBoundingClientRect().height under a transient height: 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 proposed body.scrollHeight measure 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.