# Artboards (/docs/guides/artboards)

Artboards are off until you turn them on under **Account → Preferences** on [www.obelism.studio](https://www.obelism.studio/account/preferences). Then **Artboards** (in the sidebar) shows the pages on the environment's board: your site, rendered at the environment's **preview URL** (Settings) in a frame per page, with the page's path appended (with `{locale}` in the preview URL, like `https://example.com/{locale}`, the locale chosen at the top goes there and the path is the one in that locale). A [product page](/docs/guides/shopify#product-pages) opens at the environment's product path instead (`/products/<handle>` by default).

Each page type is a column, with the home page's first. A column with more than six pages shows five of them and a sixth frame with a page switcher over it. **Edit on page** on an entry's page opens just that page (`/artboards?entry=<id>`), when artboards are on: the breadcrumb above it goes back to **Everything** or to every page of its type, and **Show entire board** fits the whole board.

## Choosing pages [#choosing-pages]

The board starts small: the home page and the first page of each type. **Pages** in the toolbar lists every page of the environment by type, with the ones on the board ticked. Tick another to add it, untick one to take it off; **Show all pages** fills the board, **Remove all** empties it, and **Back to the starter board** returns to the first setup. The pages save as you change them, and everyone in the Space sees the same board.

A page that is not on the board can still be opened: **Edit on page** and `/artboards?entry=<id>` show it for that visit.

## Making a site editable [#making-a-site-editable]

1. **Include the bridge** on every page. With the [React SDK](/docs/sdk/react) that is `<ContentBridge />` in the root layout; with a bundler, `getContentBridge()` from `@obelism/content/bridge`; without one, a script tag:

   ```html
   <script src="https://content.obelism.studio/bridge.js"></script>
   ```

   It does nothing outside the artboards. Inside it, it only talks to the CMS origin it was loaded from. The script tag puts it on `window.ObelismBridge`. Options go on the tag: `data-prompt="false"` leaves the prompt box of [editing on your site](/docs/guides/site-editing) out, and `data-url` names the CMS when the file comes from an npm CDN.

2. **Mark blocks** with their uid, which every block in a delivery response has:

   ```jsx
   <section {...editable(block)}>…</section>
   // or, without the SDK:
   <section data-cms-uid={block._uid}>…</section>
   ```

3. **Show drafts in preview.** Every frame's URL carries `cms_preview=1`, `cms_entry=<id>`, `cms_environment=<name>` and `cms_token`: a token made for the person editing, each time the artboards open. Read the page with `cms_token` as the bearer token, on your server, naming your space and environment (`space` and `environment` on `ContentClient`, the `Obelism-Space` and `Obelism-Environment` headers otherwise), and the delivery API answers drafts. Without them, a `cms_token` someone made for their own space would put that space's drafts on your site. It only reads, acts as that person (with their role at the time) and expires after 8 hours. A missing, made-up or expired token gets a `401`: show the published page then. [Previews and draft mode](/docs/guides/preview) has the code, and the rules that keep drafts off your public pages.

   <Callout type="warn">
   	Decide on drafts by `cms_token`, never by `cms_preview` alone with a
   	preview token of your own: anyone can add `cms_preview=1` to a URL.
   </Callout>

4. **Re-render on live updates** (optional, recommended): `useLiveEntry(entry)` in React, or

   ```js
   ObelismBridge.on('update', ({ entry }) => render(entry))
   ```

   `entry` has exactly the shape of a delivery API entry for a preview token, built from the unsaved draft. Without an `update` listener the page reloads after every save instead.

5. **Allow framing** by the CMS: if your site sends `Content-Security-Policy: frame-ancestors`, include `https://content.obelism.studio`, and drop `X-Frame-Options: DENY` for preview requests. The preview URL has to be reachable by the editor's browser too: a private host — Vercel deployment protection, Cloudflare Access, a private Oxygen environment — shows its login page in the frame instead of your site ([Shopify Oxygen](/docs/getting-started/oxygen#2-the-preview-environment-must-be-public)).

## Products [#products]

With a [Shopify store](/docs/guides/shopify) connected to the space, the artboards have a **Products** and a **Collections** column after the others. **Products** holds the [product pages](/docs/guides/shopify#product-pages) of every product page type and ends with **Any product**: your site's product route (the environment's product path, `/products/<handle>` by default) for a product of the store, the store's first to begin with. Select it and **Choose a product** to see another. **Collections** does the same for [collection pages](/docs/guides/shopify#collection-pages) and **Any collection**, at the collection path (`/collections/<handle>` by default).

For a product (and a collection the same way):

- A product with a page: **Show its page** selects that page's frame, where you edit it like any other page. Choosing a product that has one goes straight there.
- A product without one: **Add a product page** makes a draft page of the product page type for it, with the product filled in and its handle as slug, and opens it. The space needs a product page type for this (Settings → Shopify).

Products without a page are shown as visitors see them; there is nothing to edit on them in Content.

## Moving around [#moving-around]

The board fills the page beside the dashboard's navigation. At the top left are the environment, the page breadcrumb, **Pages** (which pages the board shows), a page type filter (**All page types**, or one), **Go to page** (any page of the board), **New page**, and **Save all** and **Publish all** while pages have changes. At the top right are the board's controls, and a pointer button whose tip sums them up:

- **Scroll** or **drag** to move the board, **⌘ or Ctrl and scroll** (or pinch on a trackpad) to zoom, or **−** and **+**. **Fit** shows the whole board. On the artboards, pinching zooms the board, not the browser.
- **Desktop** and **Mobile** render every frame 1440 or 390 pixels wide, so your site's own breakpoints apply. With more than one locale, the locale select shows every page in that one.
- Only the frames near what you're looking at load your site, at most eight at a time, each marked **Rendering** until it has loaded. The last dozen you've seen keep theirs when you move on; the others show an outline of a page until you move to them. A frame outside the visible area stops drawing until you bring it back (in Chromium, its animation frames stop too), so a board of open frames doesn't keep painting in the background; its document, scripts and unsaved changes stay as they were.

## Editing a page [#editing-a-page]

Click a page to select it. Its whole form opens in a panel over the right of the board, the same fields and blocks as on the entry page, with **Save draft** and **Publish** at its end and whether the draft has changes beside them. Clicks inside the panel now reach your site: clicking a block on the page, or a row in the form, opens that block's row and scrolls it into view, and the row is outlined. Hold **Space** to move the board over the selected page, and press **Esc** or the panel's close button to clear the selection.

**Shift**-click (or **⌘**/**Ctrl**-click) adds pages to the selection or takes them out, and **⌘A** (**Ctrl A**) selects every page of the last clicked page's column. The prompt bar shows what it will change: the selected pages as chips, which you can take out one by one or with **Clear selection**. A store column's last frame (**Any product**, **Any collection**) has no page of its own, so the bar asks you to select a page while it's selected.

- Edits show on the page as you type, before they are saved.
- The prompt bar acts on the selected page, or the selected block (a chip of its own): type what to change and it changes it [by prompt](/docs/guides/prompt-editing). The change shows on the page at once, unsaved, and **Suggested by Obelism** above the bar says what changed: **Keep it**, **Try another** (undo it and ask again) or **Undo**, with the first text it changed before and after. Until you decide, the changed pages carry a **Preview** chip.
- With several pages selected, each gets the same prompt, one after another; the panel lists them as they land.
- **New page** asks for a page type, a path and what the page should say, and the model writes it as a draft of that type (with no page selected, the board's first gives an example). The page goes on the board and opens selected (save or undo other changes first).
- Every page keeps its unsaved changes while you edit others; a page with changes is marked **Unsaved**. The panel's **Save draft** saves the selected page and **Publish** saves and publishes it; **Save all** and **Publish all** do the same for every changed page, each with its own check that nobody saved it in the meantime. A page that can't be saved or published is named. Closing the tab with unsaved changes asks first.
- Where publishing is reviewed and you may not publish, **Publish** and **Publish all** ask for review instead.

## Adding blocks [#adding-blocks]

The page's form lists its blocks as rows, with **Add block** under them. A row's chevron opens its fields; **Add block** offers the types the blocks field allows (with more than one blocks field, each has its own list). A full field, or one that allows no type, says so.

**Shift**- or **⌘**/**Ctrl**-click rows to pick several: a bar above the list moves them up or down together, or copies them, and dragging any picked row's grip moves the group.

## Reusing a block on another page [#reusing-a-block-on-another-page]

Open a block's row and choose **Copy block** in its ⋯ menu — or pick several rows and use **Copy** in the bar above them. Select another page: every blocks field shows **Paste block** beside **Add block**, so paste it into the field it belongs in. The copies go at the end of that list with new ids throughout, so the same blocks can be pasted more than once, and the first opens. A block type the field doesn't allow, or a field without room for all of them, is refused.

The copy stays in the browser tab until you copy another block or close it. References and files in it are the same Space's, so they keep working on the new page.

## Variants side by side [#variants-side-by-side]

A page with an [A/B test or flag block](/docs/guides/ab-tests) lists its tests and flags under **Variants** in the panel. **Side by side** adds a column after the others with a frame per option of that test or flag, each showing the page as it is with that option's content, while you edit the page in its own frame. **Hide** takes the column away again.

Each variant frame gets the page's unsaved content with every block of that test or flag holding only its option's blocks, so whatever option your site picks for the visitor, it can only render that one. This needs a site that re-renders on live updates (`useLiveEntry`, or `ObelismBridge.on('update', …)`); a site that only reloads after saving shows its own pick in every frame.

## Changing the whole site [#changing-the-whole-site]

With no page selected, the prompt bar takes a request about the **whole site**, several pages at once: "make every hero shorter", "end each journal post with a call to action". It works in two steps:

1. A plan: the model reads an outline of the pages on the board (their names, paths, types and the start of each block) and answers which of them the request is about, and what to ask about each, at most 12.
2. Every step then runs as that page's own [prompt](/docs/guides/prompt-editing), one after another, with the same checks as a prompt on one page.

The panel lists the steps as they land; click one to go to its page. The changes are unsaved, marked on their frames: look them over, **Undo** them all from the suggestion above the bar, and **Save all** or **Publish all** when they're right. A request reaches the pages on the board, so in a column with a switcher only the page it shows.

## Whole pages [#whole-pages]

A frame is as tall as its page. The bridge tells the artboards the page's height, and the artboards tell the bridge the screen height the frame stands for (900 pixels on desktop, 844 on mobile). The bridge then rewrites `vh`, `svh`, `lvh` and `dvh` sizes in your stylesheets as pixels of that height, so a `100vh` hero stays one screen high instead of growing with the frame. Stylesheets from another origin can't be read and keep their units. This needs `@obelism/content` 0.20 or later (or Content's own `/bridge.js`); with an older bridge, frames are one screen high and scroll inside.

## Click rates [#click-rates]

With **Show click rates on the page** in the panel, the selected page shows how often each of its blocks is clicked, as a label on each block. The switch shows once the page has click rates. The switch is remembered in your browser. The numbers come from [Obelism numbers](https://numbers.obelism.studio), the events service every Obelism product shares:

1. Load numbers' script on every page of the site (its Setup page has the tag for your Organization and environment).
2. Tag a block's clickable element with its uid, in production too:

   ```tsx
   <a href={linkHref(block.link) ?? undefined} data-numbers-id={block._uid}>
   	{block.label}
   </a>
   ```

   The script sends a `click` event with that uid for every click on (or inside) the element.

A block's click rate is the share of the visitors who viewed a page it was clicked on who clicked it, over the last 30 days, in the environment you're editing. Blocks nobody clicked show nothing.

## When a frame shows published content [#when-a-frame-shows-published-content]

A frame that shows the published page, or no page, instead of the draft almost always comes from the `401` fallback in step 3 of [making a site editable](#making-a-site-editable). Log the response's body before falling back (`error.body` on the SDK's `ContentApiError`): it says which of these it is.

- **The entry was never published.** The fallback has nothing to show, so the frame shows the page as if it had no entry: on a product route, the product without its blocks.
- **`cms_token` doesn't reach the client**, and the error is `Invalid or revoked token`. Check what your server actually reads, especially after a rewrite or a redirect. A Next.js rewrite that captures the query with `has` and a bare `key` names the value by its letters only (`cms_token` is `:cmstoken` in the destination). Name it with a group instead:

  ```ts title="next.config.ts"
  {
  	source: '/products/:handle',
  	has: [{ type: 'query', key: 'cms_token', value: '(?<cmsToken>.+)' }],
  	destination: '/preview/products/:handle?cms_token=:cmsToken',
  }
  ```

- **The space or environment doesn't match** (`token_place_mismatch`). The token is for the environment you're editing, and the site names its own (`CONTENT_SPACE_ID`, `CONTENT_ENVIRONMENT`). Point each environment's preview URL at a deployment configured for that environment. With a [connected deployment](/docs/guides/api-tokens#without-a-stored-token), that is the host environment whose rule leads there, such as a preview deployment for staging.

## Bridge reference [#bridge-reference]

`window.ObelismBridge`, or what `getContentBridge()` returns:

- `isEditing`: true inside the artboards.
- `entryId`: the entry being edited.
- `on(event, listener)`: `update` (`{ entry }`), `saved`, `published`, and `change` for the prompt state.
- `prompt(text)`, `undo()`, `createPage(path, prompt)`: when [editing on your site](/docs/guides/site-editing); see [prompt editing](/docs/guides/prompt-editing).

The bridge and the artboards talk over `postMessage`. The site sends `{ source: 'obelism-content-bridge', type }` with `ready` (with the page's `height`), `size` (`{ height }`, whenever it changes) and `select`; the artboards send `{ source: 'obelism-content-editor', type }` with `connect`, `viewport` (`{ height }`, the screen height the frame stands for), `update`, `select`, `saved`, `published` or `stats`. `stats` carries the blocks' click rates and the switch, `{ stats: { [uid]: { clicks, clickers, viewers, rate } }, visible }`, which the bridge shows on the page next to each `data-cms-uid` while `visible` is true (`@obelism/content` 0.7 and up; see the SDK's README, "Click rates"). Both sides check the other's origin.

Editors can also work on the site itself, outside the dashboard: see [editing on your site](/docs/guides/site-editing).
