A page's content can be split into screens. Each screen is a whole HTML document, stored in the page record's data.screens[]. Visitors see one of them at the page's address; the others can be opened with ?screen=. When the page wraps other pages (the root page, a parent page, a feature's template, the sign-in frame), its screens mean something else: the first is drawn above the content and the second below it.
This page covers screens in depth. Building a page is Build a website page; root and parent pages are Shared header and footer; feature and sign-in frames are Built-in pages and templates; screen variants are Show different content to different visitors.
"Tested" means checked on a local site in headless Chrome, reading which pieces rendered and in what order. "Code" means read from the code, with the file named.
What a screen is
data.screens is an array. A screen's position in it is what the site uses; nothing else orders screens.
| Field | Holds | What the site does with it |
|---|---|---|
id | a unique id; Build Studio makes s-… ids | data.activeScreenId names it; ?screen=<id> shows it |
name | the label on the screen chip in Build Studio | ?screen=<name> (any letter case) shows it; a screen variant names its source screen by name |
html | a full document (<!DOCTYPE html>…) or a body fragment | the head goes into the page, the body is drawn |
config | variants, activeVariants and animations, keyed by data-node-id | variants of every screen shown are resolved; animations only for the active screen (below) |
slug | Studio's "Screen web address name" | nothing. No address uses it (base-app build-studio/html-format.ts 147–175 reads only index, id and name) |
order | Studio's "Order (for template interleaving)" | nothing. Tested: a parent page whose first screen had order: 1 and second order: 0 still drew the first above the page |
description | free text | nothing |
attributes | what the screen chip's Style button saves (style, class) | nothing. Tested: a screen with attributes.style background: blue and a class drew with neither |
On the page record next to them:
| Field | Holds |
|---|---|
data.activeScreenId | the id of the screen visitors see. Build Studio sets it on every Save (step 3). |
data.html | Build Studio's copy of the active screen, written on every Save (websitemint build-studio-store.tsx 431–433). The site never draws it while screens exist. |
data.bodyClass, data.bodyStyle | set on the real <body> if present; otherwise the active screen's own <body> class and style are used (html-format.ts 99–117) |
Which screen a visitor sees
The page's address shows one screen. ?screen= picks another (html-format.ts pickScreensToRender 147–175, html-renderer.tsx 183–209):
| Address | Shows | Tested |
|---|---|---|
/<page> | the screen whose id is activeScreenId; if no screen has that id (or it is empty), screens[0] | yes, both |
/<page>?screen=2 | the screen at position 2, counting from 0 | yes |
/<page>?screen=s-done | the screen with that id | yes |
/<page>?screen=step%20two | the screen with that name, any letter case | yes |
/<page>?screen=all | every screen, one after another, in array order | yes |
?screen=99, ?screen=nope, an empty value | the active screen, as if there were no parameter | yes |
?screen=Intro&screen=Done | only the first value counts | yes |
- A number is a position first. Tested: on a page whose first screen had the id
"2",?screen=2showed the third screen. Don't give screens ids that are plain digits. ?screen=only touches this page's screens. Therootheader and footer and a parent's band wrap whatever it shows (tested with?screen=all: root header → parent top → both screens → parent bottom → root footer).- Every screen is public. Anyone who adds
?screen=<n>sees it. There is no hidden or draft flag on a screen. - Search engines see one page. Every
?screen=view has the same title and the same canonical link, the page's plain address (tested;app/[[...slug]]/page.tsx334–341).
What follows the active screen even when ?screen= shows another:
| Thing | Comes from | Tested |
|---|---|---|
The <body> class and style | data.bodyClass / bodyStyle, else the active screen's <body> tag | yes: ?screen=0 and ?screen=2 kept the active screen's body class |
| Animations | the active screen's config.animations, and only while the page's data.animateContent isn't false (app/[[...slug]]/page-client.tsx 66–89) | code |
What follows the screen being shown:
| Thing | Comes from | Tested |
|---|---|---|
The screen's <head> (<style>, <link>, <script>) | each screen shown; a screen not shown loads nothing (html-renderer.tsx 124–145, 202) | yes |
| Variants | each shown screen's own config.variants (html-renderer.tsx 191–197) | code (variants themselves are tested in targeted-content-variants) |
With ?screen=all, every screen's head applies to every screen. Tested: a <style> in screen 1's head (.mark { color: red }) did not touch screen 2 on its own address, but turned screen 2's .mark text red with ?screen=all. Scope each screen's CSS under an id or class on that screen's own wrapper element.
Screens beat data.html
A page with any screens is drawn from its screens. data.html is not read for the body (html-format.ts isHTMLFormat 17–22; html-renderer.tsx 183–209). Tested: a page whose data.html held other text never showed it, on any address.
- An empty active screen draws a blank page, not a 404. Tested: the active screen held nothing, a second screen and
data.htmlheld text; the address returned 200 with the root header and footer and nothing between. A page counts as "something to draw" when any screen ordata.htmlhas content, or when the site has arootor the page a parent (app/[[...slug]]/feature-template.ts10–18, 47–51), but only the active screen is drawn. - Edit the screen, not
data.html. Build Studio rewritesdata.htmlfrom the active screen on every Save.page_writewithhtmlwrites the active screen and leavesdata.htmlas it was (appenginesrc/ai/skills/appmint-pages.skill.ts67–86), so update the mirror yourself if you write through the API. - Don't empty
screensto makedata.htmlwin. The screens are the page.
Screens on a page that wraps other pages
When a page is used around other content, its screens are read by position. activeScreenId and order play no part, and ?screen= doesn't change them.
| Used as | Screen 1 | Screen 2 | One screen only | Three or more | Only data.html | Heads loaded |
|---|---|---|---|---|---|---|
root or a page's parent, around an ordinary page | above the page | below the page | all of it above, a <footer> in it included | screens 1 and 2; screen 3 onward never shows | all of it above | yes |
A feature's template (a page at /blog, a page chosen in Site Features, account-layout) | above the feature | below it | all of it above | screens 1 and 2 | all of it above | yes |
| The frame of sign-in, My Account, checkout and order pages | above | below | split at the first top-level <footer>: before it above, from it on below | screens 1 and 2 | split at <footer> | no: only the bodies, plus the frame page's Page settings → Style |
Code: html-renderer.tsx extractWrapperFragments 50–75 and 147–163 for the first two rows; components/layout/RootPage.tsx 38–61 for the third. Tested here, on parent pages: one screen (its <footer> stayed above the page), three screens (the third never showed; screen 1's <style> applied), and order reversed (array position won). The feature and sign-in rows are tested in Built-in pages and templates.
Two details from the code for the sign-in frame:
- A second screen that is empty counts as no second screen, so screen 1 is split at its
<footer>(RootPage.tsx51–53). - Which page frames them follows
lib/layout-template.ts4–22: a page withisRoot: trueor two or more screens qualifies; a one-screen page qualifies only if it has no<main>orrole="main".
A wrapper page opened at its own address is an ordinary page again: its active screen alone (tested: a three-screen parent whose active screen was the third showed only the third at its own address).
On root and parent pages, the first two positions are the header and the footer. Dragging a screen to the front, adding one before them, or deleting one changes what every wrapped page shows. Build extra screens for a wrapper only at position 3 or later, and know they will never show around other pages.
Ask first
- Which page, on which site? By its address or name.
- Which screen should visitors see? By name. If the person is changing the live one, say what visitors will see instead.
- For a new screen: what goes on it, and is it a separate step of the same page or really a separate page? A screen has no address of its own except
?screen=, and search engines don't see it. Something people should find or share is a page. - Does this page wrap other pages? If it is
root, a parent (some page hasparent: <this name>), or a feature's page, positions 1 and 2 are the header and footer of other pages. - Is anything on a screen private? Every screen is public through
?screen=.
Steps
1. Build Studio: read the page's screens
Find the page with repo_find on page ({ "data.site": "<site>", "data.name": "<name>" }), then read the whole record with repo_find_one (GET /repository/get/page/<id>; a find result is partial). For each screen note its position, id, name, whether its html has content, and which one data.activeScreenId names.
Then find out whether it wraps anything: it is named root; or repo_find on page with { "data.site": "<site>", "data.parent": "<name>" } returns pages; or the site's data.features names it as a feature's page, or it is named like a feature's address or account-layout.
Hands on: the page id, its screens in order (position, id, name), the active id, and whether it wraps other pages.
Check: open /app/build-studio/<page name>. The screen bar above the canvas, to the right of the page name, shows one chip per screen in array order; the canvas opens on the active screen. On the site, /<page>?screen=all shows every screen in that order.
2. Build Studio: add, clone, rename, reorder or remove screens
In Build Studio (websitemint build-studio/screen-navigator.tsx, selective-virtual-dom.ts):
| To | In Studio | What it does to the record (after Save) |
|---|---|---|
| Add a screen | + at the end of the screen bar | a screen named New Screen with a new s-… id and empty html (no head), added last; the canvas switches to it (selective-virtual-dom.ts 1525–1539) |
| Copy a screen | the chip's ⋮ → Clone | a copy named "html, config (variants, animations), slug and order, added last (1555–1563) |
| Rename | ⋮ → Name | name. The slug box fills from the name only while it is empty (screen-navigator.tsx 119–126). Links with ?screen=<old name> and screen variants that name the old name stop matching. |
| Reorder | drag the chip (a grip shows on hover) | the array order (reorderScreens). This is what ?screen=<n>, ?screen=all and every wrapper use. |
| Set Order | ⋮ → Order | only the order field. The site ignores it, although the box says "Lower numbers render first when combined with parent page screens" (tested). |
| Style a screen | ⋮ → Style | the screen's attributes. The site ignores it (tested). Style an element inside the screen instead. |
| Delete | ⋮ → Delete Screen, then Confirm within 3 seconds; only offered when there are two or more screens | the screen is removed; if it was open, the canvas moves to the first screen (1565–1573) |
| Hide | there is no hide | see below |
Then press Save. Nothing above is stored until then.
The screen open on the canvas when you press Save becomes the live one. Save writes activeScreenId as the screen being edited and copies it into data.html (websitemint build-studio-store.tsx 410–433). Editing screen 2 and saving puts screen 2 at the page's address for everyone. Before saving, click the chip of the screen visitors should see (step 3). On root and parent pages this doesn't matter: they use positions, not the active screen.
Hiding a screen. There is no switch. A screen that isn't active doesn't show at the page's address, but ?screen= shows it to anyone. To take a screen off the site, delete it; to keep its content, first copy the page as a template or clone the screen into another page.
Through the API (no canvas), send the whole screens array back, every screen in the order you want, plus activeScreenId and html (the active screen's document):
repo_updateonpagewithpatch: { "screens": [ … ], "activeScreenId": "<id>", "html": "<active screen's html>" }(the merge replacesscreensas a whole);- or
POST /repository/update-partial/page/<id>with dot-paths,{ "data.screens": [ … ], "data.html": "…", "version": <current> }(tested), never{ "data": { … } }, which replaces all ofdata; - or
POST /repository/update/<id>with the whole record read fromGET /repository/get/page/<id>(tested).
page_write's html only ever writes the active screen. To change another screen, use one of the above.
Stop: on a site that serves drafts, a saved change is live at once. Removing, reordering or renaming screens changes what visitors and existing ?screen= links see; on a page that wraps others, it changes every one of those pages. Say which addresses change and how, and wait for yes.
Hands on: the new list of screens in order (id, name) and the active id.
Check: repo_find_one on the page: data.screens in the order you wanted, activeScreenId the screen visitors should see, and data.html the same document as that screen.
3. Build Studio: choose which screen visitors see
In Build Studio, click that screen's chip so the canvas shows it, then press Save. Through the API, set data.activeScreenId to its id and data.html to its html, as in step 2.
Stop: this changes what everyone sees at the page's address. Say which screen will show, and wait for yes.
Hands on: the active screen's id.
Check: open /<page> in a browser. The chosen screen is there and no other.
4. Build Studio: each screen's styles and code
| Want | Put it | Why |
|---|---|---|
| CSS for one screen | a <style> or <link rel="stylesheet"> in that screen's <head>, with selectors under an id on the screen's own wrapper (#step-two .card { … }) | the head loads only while the screen is shown; with ?screen=all every head applies to every screen (tested) |
| CSS for every screen of the page | Page settings → Style → CSS (the record's top-level style.css), written with page_write css | loaded whichever screen shows |
| JavaScript | Page settings → Style → JavaScript (style.javascript), one script for the whole page. To act on one screen, check for that screen's own element first. | Tested: the page's script ran on every screen view and found the screen shown by its marker element. |
| A background or class for the whole screen | on a wrapper element inside the screen's body | the <body> class and style come from the active screen only (tested), and the chip's Style button is ignored |
| Animations | Build Studio's animation panel, with that screen open | stored in that screen's config.animations; only the active screen's play on the site (code) |
A <script> written inside a screen ran on a full page load of that screen (tested, in its head and in its body), but it doesn't run after the site moves the visitor without a reload, and Build Studio and these docs treat it as unsupported. Use Page settings JavaScript: The AppMint JS engine on a page.
Hands on: nothing new.
Check: open each screen with ?screen=<position>: its own styles apply and the other screens' don't.
5. Check every screen on the site
In a browser (not a grep of the page source, which carries the HTML of the screens in a script payload):
/<page>: the active screen only, between the site header and footer;/<page>?screen=<n>for each position, and/<page>?screen=all;- if the page wraps others, one page it wraps: screen 1 above, screen 2 below, nothing of screen 3;
- if it frames the sign-in pages,
/login.
When it's done
Tell the person: the page's screens in order, by name; which one visitors see at its address; the ?screen= address of each of the others, and that anyone can open those; and, if the page wraps other pages, which screen is drawn above them and which below. Open the page in Build Studio and on the site.
What it can't do yet
- No hidden or draft screens. Every screen is public through
?screen=. - No address per screen. A screen's
slugisn't used;?screen=is the only way in, and search engines index only the page. orderand the screen's Style button do nothing on the site, although Studio offers both.- Save makes the screen you are editing the live one.
- The body class and style, and the animations, come from the active screen only, also when
?screen=shows another. - Wrappers use only the first two screens, by position. One screen is drawn whole above the content, except around the sign-in pages, where it is split at its
<footer>. - Screens sent through Build Studio's AI are dropped at Save (code): see Build Studio's AI and the page-building AI tools.
Don'ts
- Don't edit
data.htmlon a page that has screens; write the screen and keephtmlas its mirror. - Don't empty
screensto makedata.htmlshow. - Don't save in Build Studio with the wrong screen open.
- Don't give screens plain-number ids;
?screen=2means position 2 first. - Don't put anything private on any screen.
- Don't add, move or delete the first two screens of
root, a parent page or a feature's page without a yes; they are other pages' header and footer. - Don't rely on
order,slugor the screen's Style button to do anything on the site. - Don't style one screen with unscoped selectors;
?screen=allapplies every screen's CSS to all of them.