-
Notifications
You must be signed in to change notification settings - Fork 59
Open
Description
Summary
Setting app.onhostcontextchanged multiple times silently overwrites previous handlers. This breaks real-time theme updates and any custom host context handling.
Root Cause
The setter calls setNotificationHandler() which replaces the previous handler:
// app.ts line 510-521
set onhostcontextchanged(callback) {
this.setNotificationHandler(
McpUiHostContextChangedNotificationSchema,
(n) => {
this._hostContext = { ...this._hostContext, ...n.params };
callback(n.params); // Only ONE callback ever runs
},
);
}Breaking Examples
Example 1: useHostStyles internal conflict
// User writes this simple code:
function MyWidget() {
const { app } = useApp({ ... });
useHostStyles(app, hostContext); // Expects theme updates to work
return <div>...</div>;
}
// BREAKS because inside useHostStyles:
useHostStyleVariables(app, ...); // Sets handler A (theme + variables)
useHostFonts(app, ...); // Sets handler B (fonts only) - OVERWRITES A!
// Result: Theme changes → only fonts handler runs → theme never updatesExample 2: User handler + SDK hook
function MyWidget() {
const { app } = useApp({ ... });
const [ctx, setCtx] = useState(null);
useEffect(() => {
if (!app) return;
// User sets their own handler
app.onhostcontextchanged = (params) => {
setCtx(prev => ({ ...prev, ...params })); // Update React state
trackAnalytics("theme_changed", params); // Custom logic
};
}, [app]);
// Later, user adds SDK hook for convenience
useHostStyles(app, ctx); // OVERWRITES user handler!
// Result: setCtx() never called, trackAnalytics() never called
}Example 3: Two SDK hooks
function MyWidget() {
const { app } = useApp({ ... });
// User wants variables but not fonts
useHostStyleVariables(app, ctx);
// User also wants to track theme changes
useEffect(() => {
app.onhostcontextchanged = (params) => {
console.log("Theme changed to:", params.theme); // OVERWRITES SDK hook!
};
}, [app]);
// Result: Variables never applied, only console.log runs
}Example 4: Multiple custom handlers (impossible today)
function MyWidget() {
const { app } = useApp({ ... });
// Component A wants to know about theme
useEffect(() => {
app.onhostcontextchanged = (params) => {
updateComponentA(params);
};
}, [app]);
// Component B also wants to know about theme
useEffect(() => {
app.onhostcontextchanged = (params) => {
updateComponentB(params); // OVERWRITES Component A!
};
}, [app]);
// Result: Only Component B gets updates
}Symptoms
- Dark/light mode toggle does not update widget in real-time
- Custom
onhostcontextchangedhandlers stop working after adding SDK hooks - No errors in console (silent failure)
- Requires page refresh to see changes
Proposed Fix
Change the setter to chain handlers instead of replacing:
private _hostContextChangedHandlers: Array<(params) => void> = [];
set onhostcontextchanged(callback) {
this._hostContextChangedHandlers.push(callback);
// Only register the notification handler once
if (this._hostContextChangedHandlers.length === 1) {
this.setNotificationHandler(
McpUiHostContextChangedNotificationSchema,
(n) => {
this._hostContext = { ...this._hostContext, ...n.params };
for (const handler of this._hostContextChangedHandlers) {
handler(n.params);
}
},
);
}
}Workaround
Until fixed, use a single combined handler instead of SDK hooks:
useEffect(() => {
if (!app) return;
const ctx = app.getHostContext();
if (ctx?.theme) applyDocumentTheme(ctx.theme);
if (ctx?.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
if (ctx?.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts);
app.onhostcontextchanged = (params) => {
if (params.theme) applyDocumentTheme(params.theme);
if (params.styles?.variables) applyHostStyleVariables(params.styles.variables);
if (params.styles?.css?.fonts) applyHostFonts(params.styles.css.fonts);
// Add any custom logic here too
};
}, [app]);ochafik
Metadata
Metadata
Assignees
Labels
No labels