Skip to content

feat: Add canvas layout sync API for screenInstances (position, label, add/remove) #365

Description

@meiseayoung

Summary

The SDK can read project canvas layout via get_projectscreenInstances, but provides no first-class API to write/sync screen placement, labels, or canvas membership back to Stitch. Integrators building infinite-canvas UIs must call undocumented REST endpoints directly — and those PATCH operations often fail with API keys, requiring OAuth instead.

Current state

Reading canvas data (works)

get_project returns screenInstances with placement metadata:

const raw = await stitch.callTool("get_project", { name: "projects/{projectId}" });
// raw.screenInstances[]:
// { id, sourceScreen, label, x, y, width, height, hidden, type }

The README mentions project.data.screenInstances for apply_design_system, but there is no documented SDK method to update canvas state.

Writing canvas data (missing / broken for API keys)

There is no SDK method or MCP tool such as:

  • update_screen_instance — move/resize a screen on the canvas
  • sync_screen_to_canvas — add a screen to the project canvas with placement
  • update_project_canvas — batch update screenInstances
  • update_screen_label — rename the canvas label for a screen

Integrators currently call the REST API directly:

PATCH https://stitch.googleapis.com/v1/projects/{projectId}?updateMask=screenInstances
Body: { "screenInstances": [...] }
Header: X-Goog-Api-Key: {STITCH_API_KEY}

This fails for many API-key users with:

{
  "error": {
    "code": 401,
    "message": "Request is missing required authentication credential. Expected OAuth 2 access token",
    "status": "UNAUTHENTICATED"
  }
}

Concrete gaps

Operation Workaround today Problem
Sync screen position (x, y, width, height) Direct REST PATCH OAuth-only for many accounts; not in SDK
Rename screen label on canvas Direct REST PATCH screenInstances[].label Same
Add screen to canvas Push new instance to screenInstances Same; some projects use screen IDs as instance IDs, breaking apply_design_system
Normalize invalid instance IDs Manual UUID assignment + PATCH Required before design system apply; no SDK helper
Remove screen from canvas (without delete) Filter screenInstances + PATCH Same OAuth issue
Bidirectional layout sync Read via get_project, write via REST No unified SDK abstraction

Requested API

SDK methods on Project

interface ScreenInstance {
  id: string;              // canvas instance id (UUID, not screen id)
  sourceScreen: string;    // "projects/{projectId}/screens/{screenId}"
  label?: string;
  x: number;
  y: number;
  width: number;
  height: number;
  hidden?: boolean;
}

// Read (may already exist via project.data — document it)
const layout = await project.getCanvasLayout(): Promise<ScreenInstance[]>;

// Write
await project.updateCanvasLayout(instances: ScreenInstance[]): Promise<void>;

// Convenience helpers
await project.addScreenToCanvas(screenId: string, placement: { x, y, width, height, label? }): Promise<ScreenInstance>;
await project.updateScreenPlacement(screenId: string, placement: Partial<{ x, y, width, height, label }>): Promise<void>;
await project.removeScreenFromCanvas(screenId: string): Promise<void>;

All methods should work with STITCH_API_KEY (BYOK).

MCP tools

Tool Description
update_project_canvas Replace or patch screenInstances for a project
update_screen_instance Update placement/label for one canvas instance

Documentation

  • Document the screenInstances schema in the README API reference
  • Clarify the difference between screen resource ID (screens/{id}) and canvas instance ID (screenInstances/{uuid})
  • Document which operations require OAuth vs API key

Expected behavior

import { stitch } from "@google/stitch-sdk";

const project = stitch.project("1234567890");
const screen = await project.generate("A login page", "MOBILE");

// Place on canvas at specific coordinates
await project.addScreenToCanvas(screen.screenId, {
  x: 100, y: 200, width: 390, height: 844, label: "Login"
});

// User drags screen in external canvas UI → sync back to Stitch
await project.updateScreenPlacement(screen.screenId, { x: 300, y: 200 });

// Rename label visible on Stitch canvas
await project.updateScreenPlacement(screen.screenId, { label: "Sign In" });

const layout = await project.getCanvasLayout();
expect(layout.find(i => i.sourceScreen.endsWith(screen.screenId))).toMatchObject({
  x: 300, y: 200, label: "Sign In"
});

Use case

We built an infinite-canvas UI designer on top of Stitch where:

  1. Users arrange screens on a local canvas (pan/zoom/drag)
  2. Screen positions are persisted locally and should sync to Stitch so the Stitch web UI shows the same layout
  3. Renaming a screen locally should update the Stitch canvas label
  4. New screens generated via SDK should appear on the Stitch project canvas (some projects only list screens in screenInstances, not via list_screens)

Without SDK canvas sync, we maintain a fragile REST workaround that fails for API-key-only users and requires manual fixes in the Stitch web UI.

Environment

  • @google/stitch-sdk ^0.3.5
  • Auth: STITCH_API_KEY (BYOK)
  • Node.js 22

Related

This complements #364 (delete project/screen). Canvas sync covers the update path for project screenInstances; delete covers resource removal.

Happy to share integration details or help test a fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions