Facade / Developer docs / FramesDesign proposal

FRAMES

Your app.
Inside Facade.

A Frame is a web app embedded in Facade. It receives approved workspace context and can connect to services beyond Facade.

Contract under design. This guide proposes the iframe runtime, message names, manifest, permissions, and review workflow. These are not yet a shipped SDK or submission API.
FacadeWorkspace & permissions
FrameYour embedded web app
Your APIServices & integrations

Build the Frame with your own web stack. Facade hosts its UI in an iframe; your app owns its interface and integrations. Store distribution will require review before a Frame is listed. Local installations of extensions / Frames must be available without store submission, publisher approval, or review.

Available today

The desktop Frames rail opens a searchable store sidebar. Its bundled entries include OpenRouter and the Facade Model Picker, a built-in React interface backed by imported JSON. These are built-in React interfaces, not iframe apps.

Implementation references: desktop/src/components/Frames.tsx and desktop/src/lib/modelFrames.ts. Everything below is the proposed general Frame contract.

Receiving context

Use window.postMessage for initial context and later updates. Do not depend on shared JavaScript globals: a separately hosted Frame has a different origin.

ChannelProposed use
URL parametersPublic bootstrap values only: protocol version, instance ID, and expected parent origin. A parent-origin parameter must match the Frame's trusted deployment allowlist.
postMessagePermission-filtered context after a ready handshake, followed by replacement snapshots when context changes.
Shared variablesNo cross-origin contract. Do not read the parent DOM or assume access to Facade storage.

Start with display context: theme and locale. Workspace ID, display name, or selection require explicit grants. Omit ungranted fields. Never include local filesystem paths, raw conversations, credentials, or API keys by default. If access is revoked, send a reduced snapshot and clear previously cached values.

Message protocol Proposed v1

  1. Facade loads the installed Frame’s permitted entry point and assigns a unique instanceId.
  2. The Frame installs its listener, then sends frame.ready to the trusted parent origin.
  3. The host validates origin, source window, protocol version, instance ID, and payload shape, then replies with facade.context.
  4. Updates replace the prior context. Use a monotonically increasing revision to ignore stale snapshots.
  5. On navigation, reload, or unmount, discard the session and listeners. On a handshake timeout, show a retry state rather than sending context blindly.
{
  "protocol": "facade.frames",
  "version": 1,
  "type": "facade.context",
  "instanceId": "frame-instance-123",
  "revision": 1,
  "context": {
    "theme": "dark",
    "locale": "en-US",
    "workspace": { "id": "workspace-123", "name": "Design team" }
  }
}

Both sides must validate each message. The host checks event.source === iframe.contentWindow; the Frame checks event.source === window.parent. Both check the exact allowed origin and schema. Ignore unknown types and unsupported versions; never execute received code or treat messages as native commands.

Use a dedicated remote origin with a reviewed iframe sandbox policy. A candidate is allow-scripts allow-same-origin for a cross-origin Frame that needs its own origin; never serve untrusted Frames on the host origin. Additional sandbox capabilities require explicit host permission grants; store-distributed releases also undergo review. Without allow-same-origin, a sandboxed Frame has an opaque origin and this exact-origin protocol needs a different design.

The host must configure CSP, embedding policy, and Tauri capabilities before this runtime ships. Review does not itself enforce permissions. No Frame should receive Tauri IPC, shell, or filesystem access through this bridge.

Frame example

Illustrative client code for the proposed contract. Replace the example origin with an explicit allowlist of your supported Facade host origins. Actual production and development host origins still need to be specified.

const trustedHosts = new Set(['https://host.example.com']);
const params = new URLSearchParams(location.search);
const hostOrigin = params.get('parentOrigin');
const instanceId = params.get('instanceId');
let revision = -1;

if (!trustedHosts.has(hostOrigin) || !instanceId || parent === window) {
  throw new Error('Unsupported Frame host');
}

function receiveContext(event) {
  if (event.origin !== hostOrigin || event.source !== parent) return;
  const message = event.data;
  if (!message || message.protocol !== 'facade.frames' ||
      message.version !== 1 || message.instanceId !== instanceId ||
      message.type !== 'facade.context' ||
      !Number.isSafeInteger(message.revision) ||
      message.revision <= revision) return;
  const context = message.context;
  if (!context || !['light', 'dark'].includes(context.theme) ||
      typeof context.locale !== 'string') return;
  if (context.workspace !== undefined &&
      (!context.workspace || typeof context.workspace.id !== 'string' ||
       typeof context.workspace.name !== 'string')) return;
  revision = message.revision;
  document.documentElement.dataset.theme = context.theme;
  // Replace app state with these validated fields; clear omitted fields.
  renderContext({ theme: context.theme, locale: context.locale,
    workspace: context.workspace });
}

// Supply renderContext in your app; use textContent or escaped rendering.
window.addEventListener('message', receiveContext);
parent.postMessage({ protocol: 'facade.frames', version: 1,
  type: 'frame.ready', instanceId }, hostOrigin);
// On component teardown:
// window.removeEventListener('message', receiveContext);

This snippet is documentation, not a working Facade SDK. The embedding host must implement the matching handshake and context sender before a Frame can run in the desktop app.

Connecting to external APIs

A Frame can call its own backend or a third-party API. Context delivery and service authentication are separate: receiving a workspace ID does not authenticate a user to your service.

Do not put tokens in iframe URLs or use Facade's desktop credentials. Network-domain declarations in a manifest describe intended access; runtime enforcement still needs implementation.

Frame manifest

Proposed installation metadata for local and store distribution, distinct from today's model-picker JSON. No current installer accepts this format. Permission names and schema remain draft.

{
  "id": "com.example.project-dashboard",
  "name": "Project dashboard",
  "version": "1.0.0",
  "protocolVersion": 1,
  "entryUrl": "https://frames.example.com/dashboard",
  "contextPermissions": ["workspace.summary"],
  "networkDomains": ["https://api.example.com"],
  "publisher": "Example Studio",
  "supportUrl": "https://example.com/support",
  "privacyUrl": "https://example.com/privacy"
}

The installed entry origin must match the host's message allowlist. Any redirect to another origin must fail closed. Installation grants and enforced network policies are future host responsibilities, not properties a manifest can enforce on its own.

The Frame store & review

Facade's Frame store will have a review process. The following lifecycle is a proposal; submission tooling, review criteria, and service timelines have not been finalized.

Local installations without review

Local installation is an independent distribution path for extensions / Frames. Users must be able to install a versioned local package without a store listing, review status, publisher approval, or a review-service connection. Local packages must not be rejected solely because they are unsigned or unreviewed. Installing locally does not submit or publish a package to the store.

Format validation, sandboxing, and user-granted permissions apply to both distribution paths. These checks run locally and must not depend on store approval. Local updates follow the same path, with user consent for expanded permissions. Users must be able to remove a local Frame or restore a bundled replacement.

Today this path is implemented for model-picker JSON catalogs through Frames → Install locally. General executable Frame packages and their permission/runtime handling remain proposed.

Store distribution

  1. Submit

    Provide a versioned manifest, publisher identity, screenshots, test access, permission rationale, external domains, and privacy/support details.

  2. Validate & review

    Check HTTPS and embedding, context handling, authentication, data practices, accessibility, and behavior against the declared purpose. A reviewer can request changes.

  3. Approve & list

    Only approved submissions become store listings. Users should see publisher, version, requested context, and data disclosures before installing.

  4. Maintain

    Submit changes for review. Expanded permissions need renewed consent. Define suspension and removal paths for harmful or noncompliant Frames.

A remote URL can change after approval. Before launch, define release integrity and monitoring controls so review applies to the served app, not just its metadata. Approval is not a guarantee of third-party uptime or safety.

Before you submit

Browser references

Underlying browser behavior: MDN: postMessage, iframe sandbox, and CORS. These references describe web standards, not an implemented Facade runtime.