MCP App stays blank after side-panel opening until resize: possible lost spring completion visibility update
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of the Codex App are you using (From “About Codex” dialog)?
26.901.51231 (build 8109), verified from the installed application's version metadata.
What platform is your computer?
macOS, Apple Silicon. uname -mprs: Darwin 25.6.0 arm64 arm.
What issue are you seeing?
An MCP App renders inline, but intermittently becomes entirely blank when it requests fullscreen in the right side panel. The entire app disappears, including its header. Dragging the conversation/panel splitter restores the content without reloading the app. Sending a chat message also restored it in one observation.
The request itself succeeds quickly. In a failing trace, requestDisplayMode returned after 23 ms, but the app document remained hidden and host-reported container dimensions remained 0 x 0 through the 5580 ms sample.
What steps can reproduce the bug?
- Open an inline MCP App in Codex Desktop with the right panel closed.
- From a button click, call
app.requestDisplayMode({ mode: 'fullscreen' }). - If the panel is blank, wait at least six seconds without resizing or sending a message.
- Drag the conversation/panel splitter slightly: the app content appears.
- As a control, first open a file in the right panel and let that panel fully open, then expand the same MCP App. In the confirmed control, it became visible after 25 ms and stayed visible through the 5002 ms sample, with unchanged app code and Codex version.
The failure is intermittent; a single successful expansion is not sufficient to rule it out.
Reproduced with @modelcontextprotocol/ext-apps 1.7.5, @modelcontextprotocol/sdk 1.30.0, and new App(appInfo, {}, { autoResize: true }). The page uses a 780 px inline body height and 100vh in fullscreen. Its click handler awaits the returned mode and updates its own display-mode attribute. No data loading is triggered by expansion.
A stripped control page containing only a header, text, the expand/collapse button, and diagnostic listeners also failed in native Codex. Workflow rendering, canvas libraries, business polling, and UI locking were removed. The SDK, App identity, height policy, autoResize, and diagnostics remained shared, so this control does not independently exclude those factors.
What is the expected behavior?
The app should become visible when the right panel finishes opening, without a manual resize or unrelated chat update.
Additional information
Native observations
Times are relative to the expand click. These are projections of app-internal diagnostic traces, not measurements of the outer native DOM.
| Run | Request response | Observed visibility | Host dimensions |
|---|---|---|---|
| Full app, failing | 23 ms | Still hidden at 5580 ms | 0 x 0 |
| Stripped control, failing | 20 ms | Still hidden at 5966 ms | 0 x 0 |
| Full app, right panel already open, user-confirmed | 26 ms | Visible at 25 ms; remained visible through 5002 ms | Restored to 785 x 1038 at 29 ms |
In the failing runs, the app's own body still had display: block, visibility: visible, and opacity: 1, with nonzero internal geometry. Its operation overlay was hidden and the workbench was not inert. No post-click resize or return-to-visible event arrived during the failing observation window.
Candidate cause from read-only inspection of the installed bundle
The relevant code in app-primary-6cd7b8b3f5e3.js and app-initial-cadb12d4a15e.js suggests a lost final visibility update:
- Right-panel opening uses a spring with duration 0.5 seconds and bounce 0.1.
- Panel width is derived by clamping raw progress to [0, 1].
- The shell publishes
rightPanelLayoutTickwhen that derived width changes. - The fullscreen frame's layout callback hides the frame unless raw progress is exactly 1.
- The raw spring overshoots 1 before settling. Width has already reached its clamped final value, so settling to exactly 1 produces no final width-change/layout notification.
- Without another layout event, the frame can remain hidden. A later resize reruns the visibility check and restores it.
Samples from the extracted installed spring, for a target width of 1117 px:
| Time | Raw progress | Clamped width |
|---|---|---|
| 375 ms | 1.000613257460101 | 1117 |
| 499 ms | 1.0009485905572695 | 1117 |
| 500 ms | 1 | 1117 |
This mechanism reproduces in an isolated harness that retains width-derived layout notifications. It is consistent with the native observations, but live outer-container animation progress was not directly captured in native Codex.
Candidate patch
Subscribe raw animation progress to the existing requestAnimationFrame-coalesced layout callback, and unsubscribe in both cleanup branches. In the inspected bundle, ce2 is raw panel progress and o11 schedules the existing layout callback.
The diff below targets a formatted extraction of the installed bundle, for maintainer source mapping. app-primary.pretty.js is not a file in this public repository. This is not a patch to the CLI or app-server, and no installed application files were modified.
--- a/app-primary.pretty.js
+++ b/app-primary.pretty.js
@@ -28862,14 +28862,15 @@
a11 = null, i11();
});
};
+ const stopVisibilitySync = ce2.on(`change`, o11);
if (i11(), typeof ResizeObserver > `u`) return () => {
- n11.disconnect(), a11 != null && window.cancelAnimationFrame(a11);
+ stopVisibilitySync(), n11.disconnect(), a11 != null && window.cancelAnimationFrame(a11);
};
let s11 = new ResizeObserver(i11);
s11.observe(e11), s11.observe(wt2);
let c11 = XE(e11, Oe2, o11, { ancestorResize: false, ancestorScroll: false, elementResize: false }), l11 = pe2.on(`change`, o11);
return () => {
- s11.disconnect(), c11(), n11.disconnect(), l11(), a11 != null && window.cancelAnimationFrame(a11);
+ stopVisibilitySync(), s11.disconnect(), c11(), n11.disconnect(), l11(), a11 != null && window.cancelAnimationFrame(a11);
};
}, [Oe2, Tt2, xt2, _e2, Ne2, ce2, pe2, Ae2, bt2, wt2]), (0, xW.useLayoutEffect)(() => (ve2.current = false, () => {
ve2.current = true, M10.get(fW, u10)?.hostedInThreadScrollLayout !== true && ($U(M10, u10), cW(M10, u10, { isFullScreen: false }));
Validation and remaining work
- 10 isolated callback tests passed, including the actual extracted spring and width-notification chain at 60, 90, and 120 Hz, a zero-bounce control, and subscription cleanup/coalescing checks.
- A local browser fixture with real ResizeObserver and requestAnimationFrame reproduced the original frame staying hidden at progress 1; the candidate restored visibility. Both paths received the same 44 width-derived layout ticks.
- The patch dry-run passed against the unchanged formatted extraction.
- A patched native Codex build has not been run. The fix remains a candidate pending maintainer validation, including closed-panel and already-open-panel expansion, rapid collapse/reopen, and cleanup on unmount.
Related reports: #35786 and #44156 discuss MCP App disappearance at particular panel widths and transformed/covered containers. This report adds an opening-transition case with persistent document.visibilityState === 'hidden', host dimensions 0 x 0, and an independently reproduced spring/notification mechanism. Whether these reports share a cause has not been established.
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 by mapping the inspected app-primary-6cd7b8b3f5e3.js and app-initial-cadb12d4a15e.js logic to public source, then trace raw panel progress (ce2) and the existing layout callback (o11). Re-run the isolated callback checks and validate a patched native Codex build for closed-panel expansion, already-open-panel expansion, rapid collapse/reopen, and unmount cleanup. Done means the app becomes visible without resize or chat activity and existing cleanup remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- desktop, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100