Surface API

The /surface entry point defines the contract between an isolated webview contribution and The AI Platform. It does not mount a framework for you.

Mount Contract

Export a mount function that accepts an HTMLElement and TapFederatedSurfaceMountContext, then returns a TapFederatedSurfaceMount cleanup handle.

import type {
  TapFederatedSurfaceMount,
  TapFederatedSurfaceMountContext,
} from '@theaiplatform/miniapp-sdk/surface';

export function mount(
  container: HTMLElement,
  context: TapFederatedSurfaceMountContext,
): TapFederatedSurfaceMount {
  container.textContent = `Release: ${context.releaseId}`;
  return {
    unmount() {
      container.replaceChildren();
    },
  };
}

The context identifies the package, release, installation, contribution, instance, and host origin. Interactive mounts may also receive userId, the host-canonical control-plane user identity used by trusted MCP {userId} storage selectors. It is not the OAuth/OIDC profile subject. Optional workspace, channel, and conversation IDs describe the immutable instance-policy scope and appear only when that scope provides them. Read context.owner for the live host-selected workspace, channel, and conversation. Subscribe to context.launches for verified context actions routed to this exact surface. Disposable activation-preflight mounts omit userId and do not receive an owner.

Make unmount idempotent. Remove event listeners, subscriptions, timers, framework roots, and rendered content, and settle or ignore work that finishes after cleanup.

File Handler Launches

context.userFileLaunchContext is an optional TapFederatedSurfaceUserFileLaunchContext delivered only with the initial mount for one host-validated file-handler launch. It contains apiVersion: 1, an open, import, or export intent, and an opaque owner-bound file handle. Use the handle through sdk.files; the context never exposes a native path.

Production desktop file associations launch only with the open intent. Start Import through a user-mediated file picker so the host can apply import and disclosure consent. Start Export from an owner-authorized surface action and commit it through the trusted projection boundary. A native file association does not grant import disclosure, artifact checkpoint, or export authority. Test Lab can supply all three intent values to test each semantic path.

Successful mount preparation accepts that launch. Later OS launches create a fresh mount instead of using an event, subscription, query parameter, or ordinary postMessage byte channel. Clean up all launch-derived work during unmount. The host also revokes the mount owner's handles when the frame leaves, even if package cleanup does not run.

Throw TapFederatedSurfaceUserFileLaunchUnsupportedError only when the package recognizes the handler contract but cannot support the selected document version. Its reason is the TapFederatedSurfaceUserFileLaunchUnsupportedReason value unsupported-version or newer-version, and its stable code is file_unsupported. This narrow failure lets the host try another eligible handler. Validation errors, provider failures, and ordinary mount failures must remain their original errors and must not trigger handler fallback.

import {
  TapFederatedSurfaceUserFileLaunchUnsupportedError,
  type TapFederatedSurfaceUserFileLaunchUnsupportedReason,
} from '@theaiplatform/miniapp-sdk/surface';

const rejectLaunchVersion = (
  reason: TapFederatedSurfaceUserFileLaunchUnsupportedReason,
): never => {
  throw new TapFederatedSurfaceUserFileLaunchUnsupportedError(reason);
};

// Call only after parsing identifies an unsupported document version.
rejectLaunchVersion('newer-version');

Package Events

TapPackageEventPublisher exposes publish and subscribe for the declared package event channel. subscribe returns a cleanup function; call it during unmount. Event access does not confer channel, project, or navigation authority.

const stop = context.events.subscribe('selection.changed', (payload) => {
  renderSelection(payload);
});

await context.events.publish('surface.ready', { version: 1 });
// Call stop() during unmount.

Surface Entropy

Use context.entropy.randomUUID() for identifiers owned by the surface instead of reading an ambient random source. Ordinary mounts use cryptographically strong browser UUIDs. Test Lab mounts derive a deterministic stream from the test profile seed and exact frame identity, so the same action sequence is repeatable while retained remounts remain distinct.

const draftId = context.entropy.randomUUID();
drafts.set(draftId, { title: 'Untitled draft' });

The stream belongs to the current mount context. Do not retain it after unmount, use it as secret material, or assume Test Lab UUIDs are globally random.

Host Authority

TapFederatedSurfaceHostAuthority exposes a boolean snapshot and a subscription. A candidate frame can start without authority, so wait for getSnapshot() to become true before performing host-backed effects.

Authority can be revoked while the surface remains mounted. Disable protected actions promptly and keep cleanup available regardless of the current snapshot.

const renderAuthority = () => {
  submitButton.disabled = !context.hostAuthority.getSnapshot();
};
renderAuthority();
const stopAuthority = context.hostAuthority.subscribe(renderAuthority);
// Call stopAuthority() during unmount.

Selected Owner

TapFederatedSurfaceOwner exposes the host-selected owner of the mounted realm. Its snapshot is null until the host projects one. A per-workspace or singleton realm can remain mounted while its selected conversation changes, so use the subscription instead of treating the immutable mount-context scope fields as current selection.

const renderOwner = () => {
  const owner = context.owner.getSnapshot();
  channelLabel.textContent = owner?.channelId ?? 'No channel selected';
};
renderOwner();
const stopOwner = context.owner.subscribe(renderOwner);
// Call stopOwner() during unmount.

Context Action Launches

TapFederatedSurfaceLaunches delivers host-verified action.command invocations to the exact package release, target, surface contribution, and logical instance named by the manifest. The host queues the launch before it opens or focuses the surface. The launch contains an opaque artifact reference, not copied message, task, issue, or pull-request content.

import { sdk } from '@theaiplatform/miniapp-sdk/sdk';

const stopLaunches = context.launches.subscribe(async (launch) => {
  if (launch.actionContributionId !== 'inspect-artifact') {
    return false;
  }

  if (!sdk.artifacts) {
    renderUnavailable('Artifact resolution is unavailable on this host.');
    return true;
  }

  const { artifact } = await sdk.artifacts.resolve({
    reference: launch.reference,
  });
  if (artifact === null) {
    renderUnavailable('This artifact is no longer available.');
  } else {
    renderArtifact(artifact);
  }
  return true;
});
// Call stopLaunches() during unmount.

Delivery is FIFO and at least once. The runtime acknowledges a launch only after one subscriber resolves to true. If no subscriber handles it because listeners return false, throw, or are absent, that launch remains pending and prevents later launches from overtaking it. The SDK sends the acknowledgement for you and binds it to the exact frame document. Package code cannot acknowledge a different request or surface.

Use launch.requestId as the idempotency key for any durable effect. A launch can be delivered again before its acknowledgement reaches the host. The actionContributionId names the manifest action, invokedAt records the host invocation time, and optional owner is the immutable workspace, channel, and conversation selection captured for that invocation. Do not substitute the live context.owner snapshot when the effect must remain bound to the original selection.

The artifact reference expires at reference.expiresAt. Finish resolution promptly, and treat a null artifact as an ordinary unavailable result. See Artifact context resolution for the reference and snapshot contract.

Package Assets

resolvePackageAssetUrl joins a canonical relative path to context.packageAssetBaseUrl and returns a URL that remains inside the descriptor-selected target directory.

import { resolvePackageAssetUrl } from '@theaiplatform/miniapp-sdk/surface';

const iconUrl = resolvePackageAssetUrl(context, 'assets/icon.svg');
image.src = iconUrl.href;

Absolute paths, traversal segments, encoded separators, credentials, query strings, fragments, control characters, and non-HTTP package bases fail closed. Keep assets in the emitted target graph so integrity and portability checks can cover them.

Public Export Contract

ExportPurposeInputs and resultLifecycle and errorsWorking example
TapPackageEventPublisherPublishes and subscribes to package-declared events.publish takes a name and record; subscribe takes a listener and returns cleanup.Events outside the descriptor reject; remove every subscription during unmount.Package events
TapFederatedSurfaceHostAuthorityExposes current host authority for the exact surface realm.getSnapshot returns a boolean and subscribe returns cleanup.Authority can begin false and change while mounted; subscribers must clean up.Host authority
TapFederatedSurfaceOwnerExposes the live host-selected owner for a retained surface realm.getSnapshot returns an owner or null; subscribe returns cleanup.Broad realms can change owners without remounting; subscribers must clean up.Selected owner
TapFederatedSurfaceOwnerSnapshotIdentifies the selected workspace, channel, and conversation.Contains nullable workspaceId, channelId, and conversationId.The host identity-fences every projection to the exact physical frame.Selected owner
TapFederatedSurfaceLaunchOwnerContextIdentifies the owner captured for one context-action invocation.Contains a workspace ID plus nullable channel and conversation IDs.It is immutable and can differ from the later live owner snapshot.Context action launches
TapFederatedSurfaceLaunchCarries one verified context-action invocation.Contains request and action IDs, invocation time, artifact reference, and optional owner.Content is never copied into the launch; use its request ID for idempotency.Context action launches
TapFederatedSurfaceLaunchesDelivers ordered context-action invocations to the exact surface realm.subscribe takes a listener returning a boolean or Promise<boolean> and returns cleanup.Delivery is at least once; only a true result triggers the exact-frame acknowledgement.Context action launches
TapFederatedSurfaceEntropySupplies host-owned UUID entropy for identifiers created by the surface.randomUUID takes no input and returns one UUID string.Scoped to the mount; Test Lab resets a deterministic frame-specific stream.Surface entropy
TapFederatedSurfaceUserFileLaunchContextCarries one initial host-validated file-handler launch.Contains API version 1, one launch intent, and an opaque owner-bound file handle.Initial mount only; later launches remount and owner teardown revokes handles.File handler launches
TapFederatedSurfaceUserFileLaunchUnsupportedReasonNames a version-specific handler fallback reason.Accepts unsupported-version or newer-version.Use only when the package recognizes the launch but cannot decode its version.File handler launches
TapFederatedSurfaceUserFileLaunchUnsupportedErrorRequests narrow fallback to another eligible file handler.Construct with one unsupported reason and optional message; code is file_unsupported.Other mount failures must remain ordinary failures and never trigger fallback.File handler launches
TapFederatedSurfaceMountContextSupplies package, release, instance, owner, file-launch, context-action, event, and authority context.The host passes one immutable context to the exported mount function.Optional scope and file-launch fields can be absent; do not retain context after unmount.Mount contract
TapFederatedSurfaceMountDefines the cleanup handle returned from mount.Contains an unmount function returning void or a promise.Cleanup must be idempotent and must settle all surface-owned resources.Mount contract
resolvePackageAssetUrlResolves an integrity-covered package-relative asset URL.Accepts mount context and a relative path; returns a same-target URL.Invalid bases, traversal, encoded separators, and URL decorations throw.Package assets