docs: standardize UI terminology to use "view" consistently (#325)

* docs: standardize UI terminology to use "view" consistently

Replaces "widget" and "Guest UI" terminology with "view" across
the codebase as decided in the community survey.

This change updates:
- Specification document
- SDK source code and JSDoc comments
- Example servers and documentation
- Plugin documentation and skills

Closes #71

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Liad Yosef 2026-01-23 04:36:52 +02:00 • committed by GitHub
commit 4fc71656a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 381 additions and 393 deletions

View file

@ -72,7 +72,7 @@ function createServer() {
inputSchema: { userId: z.string() },
annotations: { readOnlyHint: true },
_meta: {
"openai/outputTemplate": "ui://widget/cart.html",
"openai/outputTemplate": "ui://view/cart.html",
"openai/toolInvocation/invoking": "Loading cart...",
"openai/toolInvocation/invoked": "Cart ready",
"openai/widgetAccessible": true,
@ -89,13 +89,13 @@ function createServer() {
// Register UI resource
server.registerResource(
"Cart Widget",
"ui://widget/cart.html",
"Cart View",
"ui://view/cart.html",
{ mimeType: "text/html+skybridge" },
async () => ({
contents: [
{
uri: "ui://widget/cart.html",
uri: "ui://view/cart.html",
mimeType: "text/html+skybridge",
text: getCartHtml(),
_meta: {
@ -137,7 +137,7 @@ function createServer() {
description: "Display the user's shopping cart",
inputSchema: { userId: z.string() },
annotations: { readOnlyHint: true },
_meta: { ui: { resourceUri: "ui://widget/cart.html" } },
_meta: { ui: { resourceUri: "ui://view/cart.html" } },
},
async (args) => {
const cart = await getCart(args.userId);
@ -151,13 +151,13 @@ function createServer() {
// Register UI resource
registerAppResource(
server,
"Cart Widget",
"ui://widget/cart.html",
"Cart View",
"ui://view/cart.html",
{ description: "Shopping cart UI" },
async () => ({
contents: [
{
uri: "ui://widget/cart.html",
uri: "ui://view/cart.html",
mimeType: RESOURCE_MIME_TYPE,
text: getCartHtml(),
_meta: {

View file

@ -82,11 +82,11 @@ When you need to send more data than fits in a message, use {@link app!App.updat
_See [`examples/transcript-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server) for a full implementation of this pattern._
## Persisting widget state
## Persisting view state
To persist widget state across conversation reloads (e.g., current page in a PDF viewer, camera position in a map), use [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) with a stable identifier provided by the server.
To persist view state across conversation reloads (e.g., current page in a PDF viewer, camera position in a map), use [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) with a stable identifier provided by the server.
**Server-side**: Tool handler generates a unique `widgetUUID` and returns it in `CallToolResult._meta.widgetUUID`:
**Server-side**: Tool handler generates a unique `viewUUID` and returns it in `CallToolResult._meta.viewUUID`:
{@includeCode ./patterns.tsx#persistDataServer}
@ -96,9 +96,9 @@ To persist widget state across conversation reloads (e.g., current page in a PDF
_See [`examples/map-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) for a full implementation of this pattern._
## Pausing computation-heavy widgets when out of view
## Pausing computation-heavy views when out of view
Widgets with animations, WebGL rendering, or polling can consume significant CPU/GPU even when scrolled out of view. Use [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to pause expensive operations when the widget isn't visible:
Views with animations, WebGL rendering, or polling can consume significant CPU/GPU even when scrolled out of view. Use [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to pause expensive operations when the view isn't visible:
{@includeCode ./patterns.tsx#visibilityBasedPause}

View file

@ -206,21 +206,17 @@ function hostStylingReact() {
}
/**
* Example: Persisting widget state (server-side)
* Example: Persisting view state (server-side)
*/
function persistWidgetStateServer(
url: string,
title: string,
pageCount: number,
) {
function persistViewStateServer(url: string, title: string, pageCount: number) {
function toolCallback(): CallToolResult {
//#region persistDataServer
// In your tool callback, include widgetUUID in the result metadata.
// In your tool callback, include viewUUID in the result metadata.
return {
content: [{ type: "text", text: `Displaying PDF viewer for "${title}"` }],
structuredContent: { url, title, pageCount, initialPage: 1 },
_meta: {
widgetUUID: randomUUID(),
viewUUID: randomUUID(),
},
};
//#endregion persistDataServer
@ -228,39 +224,39 @@ function persistWidgetStateServer(
}
/**
* Example: Persisting widget state (client-side)
* Example: Persisting view state (client-side)
*/
function persistWidgetState(app: App) {
function persistViewState(app: App) {
//#region persistData
// Store the widgetUUID received from the server
let widgetUUID: string | undefined;
// Store the viewUUID received from the server
let viewUUID: string | undefined;
// Helper to save state to localStorage
function saveState<T>(state: T): void {
if (!widgetUUID) return;
if (!viewUUID) return;
try {
localStorage.setItem(widgetUUID, JSON.stringify(state));
localStorage.setItem(viewUUID, JSON.stringify(state));
} catch (err) {
console.error("Failed to save widget state:", err);
console.error("Failed to save view state:", err);
}
}
// Helper to load state from localStorage
function loadState<T>(): T | null {
if (!widgetUUID) return null;
if (!viewUUID) return null;
try {
const saved = localStorage.getItem(widgetUUID);
const saved = localStorage.getItem(viewUUID);
return saved ? (JSON.parse(saved) as T) : null;
} catch (err) {
console.error("Failed to load widget state:", err);
console.error("Failed to load view state:", err);
return null;
}
}
// Receive widgetUUID from the tool result
// Receive viewUUID from the tool result
app.ontoolresult = (result) => {
widgetUUID = result._meta?.widgetUUID
? String(result._meta.widgetUUID)
viewUUID = result._meta?.viewUUID
? String(result._meta.viewUUID)
: undefined;
// Restore any previously saved state
@ -270,13 +266,13 @@ function persistWidgetState(app: App) {
}
};
// Call saveState() whenever your widget state changes
// Call saveState() whenever your view state changes
// e.g., saveState({ currentPage: 5 });
//#endregion persistData
}
/**
* Example: Pausing computation-heavy widgets when out of view
* Example: Pausing computation-heavy views when out of view
*/
function visibilityBasedPause(
app: App,
@ -284,7 +280,7 @@ function visibilityBasedPause(
animation: { play: () => void; pause: () => void },
) {
//#region visibilityBasedPause
// Use IntersectionObserver to pause when widget scrolls out of view
// Use IntersectionObserver to pause when view scrolls out of view
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
@ -296,7 +292,7 @@ function visibilityBasedPause(
});
observer.observe(container);
// Clean up when the host tears down the widget
// Clean up when the host tears down the view
app.onteardown = async () => {
observer.disconnect();
animation.pause();
@ -310,6 +306,6 @@ void chunkedDataServer;
void chunkedDataClient;
void hostStylingVanillaJs;
void hostStylingReact;
void persistWidgetStateServer;
void persistWidgetState;
void persistViewStateServer;
void persistViewState;
void visibilityBasedPause;