Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 73 additions & 5 deletions specification/draft/apps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -456,12 +456,16 @@ interface HostContext {
displayMode?: "inline" | "fullscreen" | "pip";
/** Display modes the host supports */
availableDisplayModes?: string[];
/** Current and maximum dimensions available to the UI */
/** Current and maximum dimensions available to the UI. */
viewport?: {
width: number;
height: number;
maxHeight?: number;
/** Viewport width (if fixed). Only pass width or maxWidth, not both. */
width?: number;
Copy link
Collaborator Author

@martinalong martinalong Dec 18, 2025

Choose a reason for hiding this comment

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

@ochafik I realized that no matter what, we have to make this change (making these fields optional), which is is a minor breaking change

I don't think there's really a way around it if we want to adopt this new shape. Wdyt should be the plan here? I think we may as well just update it to the type i originally proposed and announce the next version will be a breaking change

/** Viewport height (if fixed). Only pass height or maxHeight, not both. */
height?: number;
/** Maximum available viewport width in pixels (if constrained). */
maxWidth?: number;
/** Maximum available viewport height in pixels (if constrained). */
maxHeight?: number;
};
/** User's language/region preference (BCP 47, e.g., "en-US") */
locale?: string;
Expand Down Expand Up @@ -508,12 +512,76 @@ Example:
}
},
"displayMode": "inline",
"viewport": { "width": 400, "height": 300 }
"viewport": { "width": 400, "maxHeight": 600 }
}
}
}
```

### Viewport and Sizing

The `viewport` field in `HostContext` communicates sizing constraints between host and app. Each dimension (height and width) operates independently and can be either **fixed** or **flexible**.

#### Viewport Modes

| Mode | Viewport Field | Meaning |
|------|---------------|---------|
| Fixed | `height` or `width` | Host controls the size. App should fill the available space. |
| Flexible | `maxHeight` or `maxWidth` | App controls the size, up to the specified maximum. |
| Unbounded | Field omitted | App controls the size with no limit. |

These modes can be combined independently. For example, a host might specify a fixed width but flexible height, allowing the app to grow vertically based on content.

#### App Behavior

Apps should check the viewport configuration and apply appropriate CSS:

```typescript
// In the app's initialization
const viewport = hostContext.viewport;

if (viewport) {
// Handle height
if ("height" in viewport) {
// Fixed height: fill the container
document.documentElement.style.height = "100vh";
} else if (viewport.maxHeight) {
// Flexible with max: let content determine size, up to max
document.documentElement.style.maxHeight = `${viewport.maxHeight}px`;
}
// If neither, height is unbounded

// Handle width
if ("width" in viewport) {
// Fixed width: fill the container
document.documentElement.style.width = "100vw";
} else if (viewport.maxWidth) {
// Flexible with max: let content determine size, up to max
document.documentElement.style.maxWidth = `${viewport.maxWidth}px`;
}
// If neither, width is unbounded
}
```

#### Host Behavior

When using flexible dimensions (no fixed `height` or `width`), hosts MUST listen for `ui/notifications/size-changed` notifications from the app and update the iframe dimensions accordingly:

```typescript
// Host listens for size changes from the app
bridge.onsizechange = ({ width, height }) => {
// Update iframe to match app's content size
if (width != null) {
iframe.style.width = `${width}px`;
}
if (height != null) {
iframe.style.height = `${height}px`;
}
};
```

Apps using the SDK automatically send size-changed notifications via ResizeObserver when `autoResize` is enabled (the default). The notifications are debounced and only sent when dimensions actually change.

### Theming

Hosts can optionally pass CSS custom properties via `HostContext.styles.variables` for visual cohesion with the host environment.
Expand Down
12 changes: 8 additions & 4 deletions src/app-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe("App <-> AppBridge integration", () => {
const testHostContext = {
theme: "dark" as const,
locale: "en-US",
viewport: { width: 800, height: 600 },
viewport: { width: 800, height: 600, maxHeight: 600 },
};
const newBridge = new AppBridge(
createMockClient() as Client,
Expand Down Expand Up @@ -337,7 +337,7 @@ describe("App <-> AppBridge integration", () => {
const initialContext = {
theme: "light" as const,
locale: "en-US",
viewport: { width: 800, height: 600 },
viewport: { width: 800, height: 600, maxHeight: 600 },
};
const newBridge = new AppBridge(
createMockClient() as Client,
Expand All @@ -356,7 +356,7 @@ describe("App <-> AppBridge integration", () => {

// Send another partial update: only viewport changes
newBridge.sendHostContextChange({
viewport: { width: 1024, height: 768 },
viewport: { width: 1024, height: 768, maxHeight: 768 },
});
await flush();

Expand All @@ -367,7 +367,11 @@ describe("App <-> AppBridge integration", () => {
const context = newApp.getHostContext();
expect(context?.theme).toBe("dark");
expect(context?.locale).toBe("en-US");
expect(context?.viewport).toEqual({ width: 1024, height: 768 });
expect(context?.viewport).toEqual({
width: 1024,
height: 768,
maxHeight: 768,
});

await newAppTransport.close();
await newBridgeTransport.close();
Expand Down
2 changes: 1 addition & 1 deletion src/app-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,7 @@ export class AppBridge extends Protocol<
* ```typescript
* bridge.setHostContext({
* theme: "dark",
* viewport: { width: 800, height: 600 }
* viewport: { width: 800, maxHeight: 600 }
* });
* ```
*
Expand Down
147 changes: 114 additions & 33 deletions src/generated/schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading