Add safeAreaInsets support to all example apps (#202)

Apply host-provided safe area insets as padding on main containers
across all examples. This ensures content doesn't render under
system UI elements like phone notches.

React apps use `useState` + `onhostcontextchanged` handler with
initial fetch via `getHostContext()` in `useEffect`. Vanilla JS
apps apply padding in the handler and invoke it after `connect()`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-01-08 12:03:13 -06:00 • committed by GitHub
commit 79670b4dcd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4,7 +4,7 @@
* Interactive cohort retention analysis heatmap showing customer retention
* over time by signup month. Hover for details, click to drill down.
*/
import type { App } from "@modelcontextprotocol/ext-apps";
import type { App, McpUiHostContext } from "@modelcontextprotocol/ext-apps";
import { useApp } from "@modelcontextprotocol/ext-apps/react";
import { StrictMode, useCallback, useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";
@ -65,19 +65,39 @@ function formatNumber(n: number): string {
// Main App Component
function CohortHeatmapApp() {
const [hostContext, setHostContext] = useState<
McpUiHostContext | undefined
>();
const { app, error } = useApp({
appInfo: IMPLEMENTATION,
capabilities: {},
onAppCreated: (app) => {
app.onhostcontextchanged = (params) => {
setHostContext((prev) => ({ ...prev, ...params }));
};
},
});
useEffect(() => {
if (app) {
setHostContext(app.getHostContext());
}
}, [app]);
if (error) return <div className={styles.error}>ERROR: {error.message}</div>;
if (!app) return <div className={styles.loading}>Connecting...</div>;
return <CohortHeatmapInner app={app} />;
return <CohortHeatmapInner app={app} hostContext={hostContext} />;
}
// Inner App with state management
function CohortHeatmapInner({ app }: { app: App }) {
function CohortHeatmapInner({
app,
hostContext,
}: {
app: App;
hostContext?: McpUiHostContext;
}) {
const [data, setData] = useState<CohortData | null>(null);
const [loading, setLoading] = useState(true);
const [selectedMetric, setSelectedMetric] = useState<MetricType>("retention");
@ -143,7 +163,15 @@ function CohortHeatmapInner({ app }: { app: App }) {
}, []);
return (
<main className={styles.container}>
<main
className={styles.container}
style={{
paddingTop: hostContext?.safeAreaInsets?.top,
paddingRight: hostContext?.safeAreaInsets?.right,
paddingBottom: hostContext?.safeAreaInsets?.bottom,
paddingLeft: hostContext?.safeAreaInsets?.left,
}}
>
<Header
selectedMetric={selectedMetric}
selectedPeriodType={selectedPeriodType}