> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chatblocks.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# In-app editor

> Monaco-based editor for hand-authoring a block — file tree, live preview, save vs publish.

The in-app editor lives at `/blocks/[blockId]/edit` and in the **canvas block editor overlay** (`?expanded=<id>&edit=1` or block ⋯ → Edit). The overlay is the primary authoring surface on canvas; the standalone route remains for direct links and embed blocks.

If you'd rather have your coding agent drive the same workflow over MCP, see [Authoring via an agent](/authoring/via-agent). Both paths produce the same artifact.

## Layout

The editor is a three-pane layout:

* **File tree** on the left — virtual filesystem rooted at the block. `chatblocks.json` is the manifest; `src/App.tsx` is the conventional entry for code blocks. Add, rename, and delete files inline.
* **Monaco** in the center — full TypeScript + JSX language services with Tailwind class IntelliSense, multi-file open tabs, find-in-file, and the usual Monaco affordances.
* **Live preview** on the right — sandboxed iframe that re-renders as you edit. Two engines depending on `manifest.format` (see below).

A toolbar across the top has Save, Publish, the Widget panel, a hide-header toggle for the preview frame, and a kebab menu for less-frequent actions.

## Save vs Publish

Two distinct operations, two distinct buttons.

<CardGroup cols={2}>
  <Card title="Save" icon="floppy-disk">
    Persists the current file set as the block's working draft. Cheap — no build, no quota. Save freely while you iterate.
  </Card>

  <Card title="Publish" icon="rocket">
    Triggers the Vercel Sandbox build of your current files, uploads the resulting HTML bundle to Convex storage, and cuts a new `blockPublishedVersions` row. Counts against your monthly build quota.
  </Card>
</CardGroup>

Both buttons disable when there are no unsaved changes — except Publish, which stays enabled for the first publish of a never-published block so you can ship a brand-new block without first making a meaningless edit.

Canvas placements pinned to "latest" pick up the new version on the next render. Placements pinned to a specific `publishedVersionId` stay on that version until you re-pin them.

## Preview engines

The live preview branches on `manifest.format`:

<Tabs>
  <Tab title="format: code">
    Sandpack compiles your source in-browser. The conventional entry is `src/App.tsx`, declared via the manifest's `entry` field. Sandpack supports TSX, hooks, and the dependency set you declare in `package.json`.

    The Sandpack preview reflects your unsaved edits live — you don't need to Save first to see them.
  </Tab>

  <Tab title="format: embed">
    The preview renders a sandboxed iframe pointing at `manifest.embed.url`. No source code to compile — the embed format has no workspace. The platform validates the URL is HTTPS with a public hostname at publish time.
  </Tab>
</Tabs>

A common gotcha when switching formats: `code` blocks use Sandpack, which compiles your file as TSX. If you try to drop a full `<!DOCTYPE html>` HTML document into a `code` block's entry, Sandpack rejects it. Set `format` to `embed` with an external URL, or build the page in JSX.

## Hide-header toggle

The preview frame ships with a small block header (block name, owner). The toolbar has a toggle to hide it while you're working on visuals that own the full canvas. This is **client-only state** — it's not persisted to the block manifest. Each time you reload the editor the header is back.

## File constraints

Each block carries a virtual filesystem with hard caps, enforced server-side by `convex/lib/blockFiles.ts`:

| Constraint                     | Value                |
| ------------------------------ | -------------------- |
| Maximum files                  | 100                  |
| Maximum size per file          | 250 KB               |
| Maximum total size             | 1 MB                 |
| Required at root               | `chatblocks.json`    |
| Entry path (for `code` format) | declared in manifest |

Hit the cap and Save returns an error listing every offending file. The same cap applies whether you're editing in-app or pushing via MCP `blocks.setFiles` — agents see the same error.

## Dependencies and lockfiles

For `format: code` blocks, your `package.json` declares the runtime dependencies. Two valid shapes:

<Tabs>
  <Tab title="Exact-version deps">
    ```json theme={null}
    {
      "dependencies": {
        "react": "18.3.1",
        "lucide-react": "0.460.0"
      }
    }
    ```

    Every declared dependency must be pinned to an exact semver. Ranges (`^`, `~`, `*`) are rejected.
  </Tab>

  <Tab title="Lockfile">
    Include a workspace lockfile in your file set. Any of:

    * `package-lock.json`
    * `pnpm-lock.yaml`
    * `yarn.lock`
    * `bun.lock` / `bun.lockb`
    * `npm-shrinkwrap.json`

    With a lockfile present, `dependencies` can use ranges — the lockfile is the source of truth at install time.
  </Tab>
</Tabs>

The build sandbox is offline by default for security. If your block needs network access at runtime, declare `"network"` in `manifest.permissions` and list the allowed origins in `manifest.network.allowedOrigins` — see [Manifest](/authoring/manifest).

## What's next

<CardGroup cols={2}>
  <Card title="Manifest" icon="file-code" href="/authoring/manifest">
    The full `chatblocks.json` schema — formats, sizes, widget, binding, permissions.
  </Card>

  <Card title="Runtime" icon="cube" href="/authoring/runtime">
    What's available inside the iframe — render paths, sandbox model, data injection.
  </Card>
</CardGroup>
