Skip to content

fix(mcp-apps): keep the sidebar visible in fullscreen, and give every app a way into it - #7481

Merged
iskhakov merged 8 commits into
mainfrom
task-envs/hxfhlw
Aug 27, 2026
Merged

fix(mcp-apps): keep the sidebar visible in fullscreen, and give every app a way into it#7481
iskhakov merged 8 commits into
mainfrom
task-envs/hxfhlw

Conversation

@archestra-task-envs

Copy link
Copy Markdown
Contributor

No description provided.

Adds an `open_in_fullscreen` column to `apps` and the matching optional
field on `PATCH /api/apps/:appId`, so an app whose UI is the whole point
can declare that it should fill its host surface on first render instead
of sitting inline next to the conversation.

The column seeds the first render only — the live display mode stays a
per-view toggle owned by the host chrome and the app's own
`ui/request-display-mode` — so an unrelated edit never resets it.
`McpAppCard` sized its fullscreen overlay to `window.innerWidth` /
`window.innerHeight` at `(0, 0)` and painted it at `z-[100]`, so an app
filling the screen covered the navigation as well — and the only way back
to it was to leave fullscreen, since the sidebar's own collapse control
sat underneath the overlay too.

It now measures the app shell's `<main>` region instead (`MAIN_CONTENT_ID`,
moved into `lib/app-shell-region` so the shell and the card share one id)
and re-measures through a `ResizeObserver` on that element, which is what
reports the sidebar's collapse/expand animation — no window resize fires
for it. Chrome-less routes (`/a/…`, the browser preview, the recording
renderer) render no shell, so the viewport stays the fallback there.

Two z-index consequences fall out of the overlay no longer being
viewport-wide: the card drops to `z-40`, above the page's own content
(a `z-20` sticky divider is the tallest) but below the `z-50` portal
layer, so a dialog opened from the app's chrome now renders over it
rather than under it; and the sidebar's collapse pill moves to `z-50`,
because it straddles the region's left edge and would otherwise be
clipped in half by the app.
…n open-in-fullscreen default

## The gap

Fullscreen was only ever reachable from inside the guest. `mcp-app-chrome`
exported an exit button and nothing else, so the only way *in* was the
app calling `ui/request-display-mode` over the ext-apps SDK. Apps shipped
by an MCP server commonly do that; apps authored here generally do not —
so the same display mode was available for one kind of app and not the
other, with no host affordance to close the gap.

## The host owns the surface

`McpAppFullscreenExitButton` becomes `McpAppFullscreenButton`, which
renders Maximize2/"Enter fullscreen" or Minimize2/"Exit fullscreen" from
an `isFullscreen` prop. `McpAppEntryContent` renders it unconditionally
on both surfaces it chromes: the side panel's top bar (previously
exit-only) and the chat-inline hover overlay (previously mounted only
while fullscreen). Toggling from the host still flows back to the guest —
the runtime already pushes `displayMode` into the bridge's host context.

## Opening fullscreen by default

An app whose UI is the point — a dashboard opened to be looked at, not
talked to — should not need the chat dismissed by hand on every open.
`apps.open_in_fullscreen` (previous commit) is surfaced in App settings as
an "Opens in" select, Inline or Fullscreen, alongside the existing status
and modification controls.

`McpAppEntryContent` seeds `displayMode` from it once, and only on a
surface that is actually showing the app, so leaving fullscreen — or the
app asking for inline over the SDK — is never undone underneath the
viewer. The standalone run page (`/a/…`) is left alone: it is chrome-less
and full-page already.

The e2e case drives the whole path in a browser: an app saved with the
default open filling the page, the sidebar still painted beside it, and
the exit staying exited.
`handleShowInPanel` said the panel "is the app's frame — never fullscreen
there", which stopped being true once the panel's top bar gained the way
in. The reset it describes is still right, for a different reason: the
panel instance owns its own display mode from that point, so leaving the
inline one fullscreen would strand an invisible overlay behind the panel.
Eight one-off Playwright scripts I used to drive the local stack while
verifying this change (and to seed the demo recording) were swept into the
previous commit. They are scratch, not tests — the real coverage is the
Playwright case in `tests/apps.spec.ts`.
Resolves the migration-number collision: main landed
`0438_sudden_azazel` (chatops channel instructions) on the same index this
branch had taken. Took main's `_journal.json` and `0438_snapshot.json`,
dropped the branch's `0438_chilly_bulldozer.sql`, and regenerated it
against main's snapshot as `0439_chilly_bulldozer.sql`.

The regenerated DDL is byte-identical to the original — the migration is a
single `ADD COLUMN` with no data-migration tail — so only its index and
snapshot changed. `drizzle-kit check` and `check:migrations` both pass, and
`pnpm codegen` reports no drift against the auto-merged API client.
@iskhakov
iskhakov enabled auto-merge August 26, 2026 15:50
@iskhakov
iskhakov disabled auto-merge August 27, 2026 14:18
Same migration-number collision as the previous merge, one index up: main
landed `0439_harsh_matthew_murdock` on the index this branch had taken.
Took main's `_journal.json` and `0439_snapshot.json`, dropped the branch's
`0439_chilly_bulldozer.sql`, and regenerated it against main's snapshot as
`0440_chilly_bulldozer.sql`.

The regenerated DDL is byte-identical again (a single `ADD COLUMN`, no data
tail), so only its index and snapshot moved. None of the 22 commits main
picked up touch a file this branch changes. `drizzle-kit check` and
`check:migrations` pass, `pnpm codegen` reports no drift, and the full
`turbo check:commit` is green across all four workspaces.
@iskhakov
iskhakov enabled auto-merge August 27, 2026 14:26
@iskhakov
iskhakov added this pull request to the merge queue Aug 27, 2026
@archestra-task-envs archestra-task-envs Bot added the run-e2e add label to run e2e tests on demand on a PR (remove and re-add the label to retrigger) label Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Playwright test results

passed  129 passed
skipped  7 skipped

Details

stats  136 tests across 46 suites
duration  4 minutes, 30 seconds
commit  a470a6d

Skipped tests

identity-providers › identity-providers.ee.spec.ts › Identity Provider SAML E2E Flow with Keycloak › should configure SAML provider, login via SSO, update, and delete
api-k8s › mcp-enterprise-managed.ee.spec.ts › Enterprise-managed MCP credentials › exchanges an ID-JAG at a remote MCP server before gateway tool execution
api-k8s › oauth-self-hosted.spec.ts › OAuth for Self-Hosted MCP Servers › OAuth callback fails with invalid state
chromium › mcp-install.spec.ts › MCP Install › Local server with bogus image shows error, logs, and can be fixed
chromium › chat.spec.ts › Chat-UI-cerebras › can send a message and receive a response from Cerebras
chromium › chat.spec.ts › Chat-UI-cohere › can send a message and receive a response from Cohere
chromium › chat.spec.ts › Chat active run reconnect › continues a streaming assistant turn after page reload

Merged via the queue into main with commit 478f7e4 Aug 27, 2026
66 checks passed
@iskhakov
iskhakov deleted the task-envs/hxfhlw branch August 27, 2026 14:48
@archestra-task-envs

Copy link
Copy Markdown
Contributor Author

E2E now covers this branch. I added the run-e2e label because the suite is label-gated on PRs, and the Playwright case this PR adds had until then only ever run locally.

All five legs pass (Lite chromium 1of2 / 2of2, Lite api, K8s, Quickstart) — 129 passed, 7 skipped, none failing. The new case ran in Lite chromium-1of2:

[9/37] [chromium] › tests/apps.spec.ts:137:5 › an app whose default is fullscreen opens filling the page, with the sidebar still there

The branch is also merged up to current main — main had taken the migration index twice while this was open, so open_in_fullscreen was regenerated against main's snapshot as 0440_chilly_bulldozer. The DDL is byte-identical each time (a single ADD COLUMN, no data tail); only the index and snapshot moved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-e2e add label to run e2e tests on demand on a PR (remove and re-add the label to retrigger)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant