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:
parent
9e42ee05a3
commit
4fc71656a3
3 changed files with 381 additions and 393 deletions
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue