A page directory can carry sibling versions next to its default layout. Same canonical URL. Each version is a fully server-rendered HTML document, built at arc blocklet build / prerender time.
This page is the operations contract, verified 2026-08-21 against Web Device after arc#4548 (token delta) and arc#4557 (Split + Select reason). It is not a design essay; for motivation see the feature article.
In-page review is a different capability that happens to share the HUD. Do not treat this page as a rewrite of In-page review mode. Review writes feedback next to content (.review/). Variants choose among finished renders (.experiment/selection.json plus the variants/ tree).
When to use it
Use page variants when:
- an agent (or a person) can produce more than one finished version of the same route in one pass;
- you need to look at those versions on the rendered page, including side by side, before visitors see any of them;
- you later want a cookie-assigned traffic split on that same tree.
Do not use them as:
- a second review overlay, a
?experiment=1mode, or a client-side theme switcher; - per-article / per-doc alternatives (collection item URLs are not variant pages);
- slot-level or sentence-level experiments (not this wave);
- a metrics or significance product (out of scope).
Declare the versions
Only pages/<name>/ pages grow variants. The control layout must exist.
pages/index/layout.aup # default / control (required)
pages/index/variants/aurora/layout.aup # kind 2: full layout tree
pages/index/variants/quiet/tokens.json # kind 1: values only, inherit default layout
pages/index/variants/_config # optional: split= / seed=| Kind | What differs | What to put in variants/<name>/ | What is inherited |
|---|---|---|---|
| 1 Values | tokens / concrete CSS variables | tokens.json only | default layout tree |
| 2 Structure | hero, block order, widgets | full layout.aup or layout.json | nothing from the control layout |
A declared directory with neither a layout nor a valid tokens.json is a build failure. Kind 2 ignores a tokens.json sitting next to a layout in this wave; do not mix both in one name and expect the tokens to apply.
_config uses one key=value per line. A malformed line is a build failure, not a skip:
split=100,0,0
seed=homepage-choose-2026split weights are parallel to [default, ...directory names in listing order] and must sum to 100. Omitting _config (or omitting split) distributes evenly across every declared version.
To keep every visitor on default while the extra versions exist for authoring, set the first weight to 100 and the others to 0. Changing seed reshuffles every visitor.
Agent recipe: one pass, several versions
This is the shape an agent should produce. Do not invent a second API.
- Read the existing
pages/<name>/layout.aup(and i18n / sources it already binds). - Decide which alternatives are values and which are structure. Values →
tokens.jsononly. Structure → a complete layout file. Do not copy the whole layout to change--accent. - Write each alternative under
pages/<name>/variants/<name>/in the same turn as the control, so a human can open review once. - Add
_config. If visitors must not enter the experiment yet:split=100,0,0(pad zeros to the number of non-default names). - Run
arc dsl lint/validate/blocklet checkon the site. A badsplitor an empty variant directory fails here, not at request time. - Open the rendered page with
?review=1. Use Split to compare, Select to record the winner.
Directory names must be ordinary (aurora, b, quiet). ., .., and names containing / are rejected.
Collection routes such as /en/articles/<slug>/ do not grow a switcher. Put the experiment on pages/index/ (or another pages/<name>/ route), not next to a markdown item.
Compare and select on the review HUD
Injection is still only ?review=1 (value exactly 1). There is no ?experiment=1. Inner iframes use ?embed=1 so they do not grow a nested HUD.
On a page that listed at least two names including default:
| Control | What it does |
|---|---|
| Version buttons | location.assign the same path with review=1 and variant=<name> |
| Split | review=1&split=1. Builds at most four iframes, declaration order |
| Pane title / chrome | Leaves split: review=1&variant=<that name>, no split |
| Select | POST to <page>/.experiment/.actions/select with { selected, reason } |
reason is the current Page note text from the same HUD. Empty or omitted still succeeds and omits the key. Max stored length 8192.
?variant= wins over the visitor cookie, does not write a cookie, and forces noindex. Unknown names fall back to default rather than 404.

This site's /en/?review=1 · 2026-08-21. Capture is a local Web Device handler after arc#4557, not a published CLI tag.

?review=1&split=1 on the same route. Inner frames have no HUD. Each pane layouts at 1280px and scales to the cell, so desktop-only visuals (WebGL heroes) still run.
Anonymous cms-write on Select is still 401 with a reason. Layout source is never rewritten by Select; the record is .experiment/selection.json next to the page.
Visitor assignment, crawlers, SEO
Production HTML is static per version. The assigned visitor receives that version's pre-rendered document. No experiment-switching JavaScript ships in the output.
| Actor | What they get |
|---|---|
| First-time browser visitor | hashed into a bucket from (seed, visitorId); arc_variant_id cookie is minted |
| Same visitor later | the same bucket, until seed changes |
| Recognized crawler UA | always default, never a cookie |
?variant= (review/QA) | that name, noindex, no cookie write |
Canonical, sitemap, and hreflang stay on the one URL. Serving a crawler a non-default version would be cloaking.
Changing variants/ or _config requires a rebuild. There is no runtime reconfiguration, no geo/device targeting, and no significance dashboard.
Limits
| Topic | Current contract |
|---|---|
| Enable flag | only ?review=1 injects the HUD; ?split=1 alone does nothing |
| Second overlay | none; no data-arc-experiment script |
| Kind 3 (one slot / one sentence) | not implemented; use whole-page split |
| Kind 2 + tokens in the same directory | tokens ignored this wave |
| Split capacity | at most four iframes, first names in declaration order |
| Split pane viewport | each iframe layouts at 1280×800 CSS px and scales to the cell (so in-frame matchMedia(max-width: 760px) is false) |
| Collection items | not variant pages |
| Metrics | out of scope |
| Published snapshot | .experiment/ is authoring-time; do not teach it as a public widget |
Check
arc --version
arc dsl lint blocklets/arcblock
arc dsl validate blocklets/arcblock
arc blocklet check blocklets/arcblockThen open the printed local URL:
http://<host>:<port>/en/?review=1
http://<host>:<port>/en/?review=1&split=1
http://<host>:<port>/en/?review=1&variant=auroraConfirm: version bar present on the homepage; Split shows distinct finished heroes; inner iframes have no HUD; a page without variants/ still has the review HUD and no version bar.