Skip to content

Window API

client.window lets an app describe its document and control its own window: focus, minimize, maximize, restore, and ask to be closed. It is the smallest API in the SDK and the one every app should use.

Availability

No permission is required, and no connection: the authority is intrinsic, because every call acts on the window your app is running in and nothing else. An app cannot see or control another app's window.

Interface

ts
interface AppDocumentState { dirty: boolean; busy: boolean; title?: string }

interface AppWindowState {
  visible: boolean;
  focused: boolean;
  mode: "normal" | "maximized" | "tiled-left" | "tiled-right";
  canMaximize: boolean;
}

interface AppWindowAPI {
  setDocumentState(state: AppDocumentState): Promise<void>;
  getState(signal?: AbortSignal): Promise<AppWindowState>;
  focus(signal?: AbortSignal): Promise<void>;
  minimize(signal?: AbortSignal): Promise<void>;
  maximize(signal?: AbortSignal): Promise<void>;
  restore(signal?: AbortSignal): Promise<void>;
  requestClose(signal?: AbortSignal): Promise<void>;
}

setDocumentState is how the desktop learns three things it cannot see inside your frame:

  • dirty — there is unsaved work. The desktop warns before closing the window, the workspace or the app.
  • busy — an operation is in progress. Closing, disconnecting and quitting are blocked while it is true, so set it around real work and clear it in a finally.
  • title — an optional document name, shown in the title bar beside your app's name. Non-blank, at most 120 characters.

Examples

Track edits and saves:

ts
function markEdited(): void {
  void client.window.setDocumentState({ dirty: true, busy: false, title: currentFile });
}

async function save(): Promise<void> {
  await client.window.setDocumentState({ dirty: true, busy: true, title: currentFile });
  try {
    await writeSomewhere();
    await client.window.setDocumentState({ dirty: false, busy: false, title: currentFile });
  } catch (error) {
    await client.window.setDocumentState({ dirty: true, busy: false, title: currentFile });
    throw error;
  }
}

Adapt to the window instead of assuming a size:

ts
const state = await client.window.getState();
layout.compact = state.mode === "tiled-left" || state.mode === "tiled-right";
if (!state.focused) pausePolling();

Offer a close action that respects the desktop's guards:

ts
closeButton.addEventListener("click", () => void client.window.requestClose());

requestClose() acknowledges the request; it does not promise the window closes. If the document is dirty the desktop asks the user first, and your app may be unmounted before the promise settles — do the saving before you call it.

Errors

CodeMessageMeaning
invalidProvide dirty, busy and an optional window title.Wrong shape, or a title over 120 characters or blank.
invalidThis method takes no options.Arguments passed to a method that takes none.
unavailableWindow controls are unavailable.The host chrome is not available for this instance.
abortedWindow request canceled.The supplied signal fired.

Lifecycle and permissions

Calls affect only the calling window, and there is no way to address another instance — opening your app twice gives two windows that each control themselves.

The desktop combines your busy flag with its own: a running transfer, an open system dialog or a clipboard publication also marks the window busy. Your flag can add to that but cannot clear it.

State you set is not persisted. After a reconnect or a reopen, set it again from your own state as your app initialises.

ShellCanvas documentation