Skip to content

feat(web): apply host styles via skybridge({ hostStyles }) opt-in - #1012

Open
qchuchu wants to merge 1 commit into
mainfrom
qchuchu/ext-apps-host-styles
Open

feat(web): apply host styles via skybridge({ hostStyles }) opt-in#1012
qchuchu wants to merge 1 commit into
mainfrom
qchuchu/ext-apps-host-styles

Conversation

@qchuchu

@qchuchu qchuchu commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What

On the MCP Apps runtime, hosts (Claude, etc.) can hand a view a set of design tokens — theme, CSS variables, and fonts — via hostContext.styles. Skybridge received them but dropped them on the floor. This wires them onto the view, behind an opt-in flag.

Enable it on the Vite plugin:

skybridge({ hostStyles: true })

With it on, Skybridge applies (and keeps in sync on theme toggle):

  • CSS variables on :root — the standardized set (--color-*, --font-*, --border-radius-*, --shadow-*), referenced with var(…)
  • color-scheme on the document, so the host's light-dark() values resolve and native controls match
  • Host fonts, injected so var(--font-sans) renders in the host typeface

Reuses the ext-apps helpers (applyDocumentTheme / applyHostStyleVariables / applyHostFonts) — no reimplementation.

Why opt-in / default off

Turning it on lets the host set variables on :root and flip color-scheme, which restyles the view. Since the variables are applied inline on the document root, an app's own :root { --color-… } gets overridden. Default off means no existing app is silently restyled; enabling it is a deliberate one-liner.

No-op under the ChatGPT Apps SDK runtime (it sends no tokens).

How it's wired

skybridge({ hostStyles }) → generated mountView(component, { hostStyles })McpAppBridge.getInstance({ applyHostStyles }) → gated apply inside the bridge's updateContext (covers the initial connect snapshot and live host-context-changed updates).

Changes

  • plugin: SkybridgePluginOptions.hostStyles, forwarded into the generated entry
  • mountView: MountViewOptions.hostStyles, seeds the bridge before render
  • McpAppBridge: applyStylesEnabled gate; idempotent apply
  • tests: applies when enabled / untouched by default even when host sends styles / untouched when enabled but host sends nothing
  • docs: "Match the Host Styles" section in build/view, useLayout theme tip
  • skills: host-styles note in the shared ui-guidelines reference

Tracking

Addresses the styles (80 CSS variables) context-property gap tracked in #14.

🤖 Generated with Claude Code

On the MCP Apps runtime, wire the ext-apps applyDocumentTheme /
applyHostStyleVariables / applyHostFonts helpers into McpAppBridge so the
host's theme, CSS variables and fonts flow onto the view. Gated behind a new
opt-in flag threaded plugin -> mountView -> bridge; off by default so existing
apps are never silently restyled.

- plugin: SkybridgePluginOptions.hostStyles, forwarded into the generated
  mountView(...) call
- mountView: MountViewOptions.hostStyles, seeds the bridge before render
- McpAppBridge: applyStylesEnabled gate on the (idempotent) apply in
  updateContext
- docs: "Match the Host Styles" section in build/view + useLayout theme tip
- skills: host-styles note in the shared ui-guidelines reference

Addresses the `styles` context-property gap in #14.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlify Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
skybridge-staging 🟢 Ready View Preview Jul 29, 2026, 9:15 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment on lines +62 to +65
const { McpAppBridge } = await import("./bridges/mcp-app/bridge.js");
McpAppBridge.getInstance({
applyHostStyles: options?.hostStyles ?? false,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Host-style opt-in is ignored

When a view initializes a Skybridge store at module scope, createStore() constructs the bridge with host styling disabled before this dynamic import runs. The later getInstance({ applyHostStyles: true }) call cannot update the existing singleton, so the host theme, CSS variables, and fonts are never applied despite the explicit opt-in.

Knowledge Base Used: Core Web Bridges

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/web/mount-view.ts
Line: 62-65

Comment:
**Host-style opt-in is ignored**

When a view initializes a Skybridge store at module scope, `createStore()` constructs the bridge with host styling disabled before this dynamic import runs. The later `getInstance({ applyHostStyles: true })` call cannot update the existing singleton, so the host theme, CSS variables, and fonts are never applied despite the explicit opt-in.

**Knowledge Base Used:** [Core Web Bridges](https://app.greptile.com/skybridge/-/custom-context/knowledge-base/alpic-ai/skybridge/-/docs/core-web-bridges.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +279 to +280
if (context.styles?.css?.fonts) {
applyHostFonts(context.styles.css.fonts);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Live font updates stay stale

When the host sends different font CSS in a later host-context-changed event, applyHostFonts retains the existing host-font style element instead of replacing its contents. The original @font-face definitions remain active, so the view does not stay synchronized with the host's fonts.

Knowledge Base Used: Core Web Bridges

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/web/bridges/mcp-app/bridge.ts
Line: 279-280

Comment:
**Live font updates stay stale**

When the host sends different font CSS in a later `host-context-changed` event, `applyHostFonts` retains the existing host-font style element instead of replacing its contents. The original `@font-face` definitions remain active, so the view does not stay synchronized with the host's fonts.

**Knowledge Base Used:** [Core Web Bridges](https://app.greptile.com/skybridge/-/custom-context/knowledge-base/alpic-ai/skybridge/-/docs/core-web-bridges.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an opt-in path for applying MCP host styling to generated views.

  • Forwards hostStyles from the Vite plugin through mountView to McpAppBridge.
  • Applies host theme, CSS variables, and fonts during initial and live context updates.
  • Adds bridge tests and documents the option in view, layout, and builder guidance.

Confidence Score: 3/5

The PR should not merge until host-style opt-in survives early bridge initialization and live font changes actually replace prior host fonts.

Module-scope store creation can permanently initialize the bridge with styling disabled, while subsequent host font updates leave the first injected font rules in place.

Files Needing Attention: packages/core/src/web/mount-view.ts, packages/core/src/web/bridges/mcp-app/bridge.ts

Prompt To Fix All With AI
### Issue 1
packages/core/src/web/mount-view.ts:62-65
**Host-style opt-in is ignored**

When a view initializes a Skybridge store at module scope, `createStore()` constructs the bridge with host styling disabled before this dynamic import runs. The later `getInstance({ applyHostStyles: true })` call cannot update the existing singleton, so the host theme, CSS variables, and fonts are never applied despite the explicit opt-in.

### Issue 2
packages/core/src/web/bridges/mcp-app/bridge.ts:279-280
**Live font updates stay stale**

When the host sends different font CSS in a later `host-context-changed` event, `applyHostFonts` retains the existing host-font style element instead of replacing its contents. The original `@font-face` definitions remain active, so the view does not stay synchronized with the host's fonts.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(web): apply host styles via skybrid..." | Re-trigger Greptile

@mintlify

mintlify Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
skybridge-staging 🟡 Building Jul 29, 2026, 9:14 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@harijoe

harijoe commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Maybe templates should have this option enabled by default? And we would enable it by default definitively in v2?

@qchuchu

qchuchu commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Well I don't know how it's going right now - maybe in the future host will require to have a minimum of styling that matches there website but for now, I don't see that much.

But I thought it was a good idea to make some marketing (and we could actually add in the devtools the current "styles" that are applied on each host that we know, so that users can check how their app would render if they use the variables).

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.

2 participants