Skip to content

feat(mcp-apps): fullscreen title bar for the App frame, header contrast, reachable consent#418

Merged
philmerrell merged 1 commit into
developfrom
feature/mcp-app-fullscreen-header
Jun 2, 2026
Merged

feat(mcp-apps): fullscreen title bar for the App frame, header contrast, reachable consent#418
philmerrell merged 1 commit into
developfrom
feature/mcp-app-fullscreen-header

Conversation

@philmerrell
Copy link
Copy Markdown
Contributor

What & why

Three fixes to the MCP App frame's fullscreen experience, grouped into one commit because they're interleaved in the same component markup (mcp-app-frame.component.ts).

1. Fullscreen title bar (was: floating Exit chip overlapping the App)

Previously, fullscreen promoted the bare iframe to a fixed inset-0 z-[9999] overlay and floated the "Exit fullscreen" button as a z-[10000] chip in the top-right — which landed on top of the App's own top-corner chrome (e.g. Excalidraw's "Open" button).

Now the container is the fixed full-viewport flex column, with the connected header promoted to a title bar that hosts the Exit control; the iframe absolute-fills its flex-1 host below the bar. No overlap, and a more discoverable labeled Exit button. Toggling display mode stays pure CSS on existing DOM, so the running App never reloads.

2. Header dark-mode contrast / low-vision

The header background is translucent (dark:bg-gray-800/60), so it composites over whatever's behind it. The inline card's container had bg-white with no dark override, so in dark mode the header composited over white into a muddy mid-grey (~#797f87) and the tool name (text-gray-400 on that) dropped to roughly 1.5:1 — the grey-on-grey you'd see on create_view.

  • Inline card now carries dark:bg-gray-900, so the header composites over the same surface as fullscreen (whose container was already dark:bg-gray-900). The fullscreen header's background is unchanged.
  • Tool name text-gray-500 dark:text-gray-400text-gray-600 dark:text-gray-300 (≈7.4:1 / ≈11:1).
  • Separator dot, details-toggle glyph (was sub-3:1 in light), and the running-state shimmer (incl. the reduced-motion fallback) bumped to clear WCAG AA.

3. "Open in Excalidraw" hung on "exporting…" (unreachable consent in fullscreen)

The App's export fires ui/open-link; the host holds the JSON-RPC response open until the user answers its inline consent strip ("This app wants to open excalidraw.com — Allow"). That strip rendered as a sibling above the card — outside the fullscreen overlay — so the z-[9999] overlay painted over it. In fullscreen the user could never grant consent, the bridge never responded, and Excalidraw sat on "exporting…" forever.

The consent strip now renders inside the frame, below the title bar and above the iframe, so it's within the overlay and reachable in both modes. (The bridge/consent/proxy logic is unchanged — this was purely a stacking/placement bug.)

Reviewer notes

  • Behavior change for inline: the App-initiated consent strip now appears at the top of the card (below the header) instead of floating above the whole card. It reads as more clearly anchored to the App; flag if you'd prefer to keep the above-the-card placement for inline only.
  • No changes to the bridge, consent service, or sandbox proxy — only the frame template/styles + two doc comments.

Testing

  • ng build clean.
  • ng test — 36 MCP-apps specs pass (mcp-app-bridge 31, mcp-app-consent.service 5).
  • Not exercised live: the header/consent only render when a deployed mcp-sandbox stack serves an App that emits a ui_resource. Manual check against a live App:
    1. Fullscreen an App → title bar spans the top, Exit button in it, no overlap with the App's chrome; Exit + Escape return to inline with state preserved.
    2. Dark mode → header is a clean dark bar, create_view clearly legible.
    3. Fullscreen → click "Open in Excalidraw" → confirm Excalidraw's modal → an "Allow" strip appears under the title bar → click it → a new excalidraw.com tab opens and the button clears. Confirm the same works inline.

🤖 Generated with Claude Code

…st, reachable consent

In fullscreen the App frame now becomes a fixed flex-column overlay with the
connected header promoted to a title bar that hosts the Exit control, instead
of a floating chip overlaying the App. Three fixes to the frame's fullscreen
surface ship together since they touch the same markup:

- Fullscreen header: the container (not the bare iframe) is the fixed
  full-viewport overlay; the iframe absolute-fills its flex-1 host below the
  title bar. The Exit button moves into the title bar so it no longer overlaps
  the App's own top-corner chrome (e.g. Excalidraw's toolbar). Toggling mode
  stays pure CSS, so the running App never reloads.

- Header contrast: the inline card now carries dark:bg-gray-900 so the header's
  translucent fill composites over the same surface as fullscreen — an inline
  card left bg-white turned the header a muddy mid-grey and dropped the tool
  name to ~1.5:1. The tool name, separator, details glyph, and running shimmer
  are bumped to clear WCAG AA for low vision.

- App-initiated consent (ui/open-link) now renders inside the frame, below the
  title bar, instead of as a sibling above the card. In fullscreen the
  out-of-overlay prompt was painted over by the z-[9999] overlay and was
  unreachable, so "Open in Excalidraw" hung on "exporting…" — the host holds
  the JSON-RPC response open awaiting a consent the user could not grant.

Build clean; 36 MCP-apps specs pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@philmerrell philmerrell merged commit 6c9404a into develop Jun 2, 2026
10 checks passed
@philmerrell philmerrell deleted the feature/mcp-app-fullscreen-header branch June 2, 2026 00:08
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