Skip to main content

2026-08-21

Page variants

Declare several finished renders of one page URL, compare them on the existing ?review=1 HUD, and keep visitor assignment separate from the authoring choice.

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=1 mode, 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.

text
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=

KindWhat differsWhat to put in variants/<name>/What is inherited
1 Valuestokens / concrete CSS variablestokens.json onlydefault layout tree
2 Structurehero, block order, widgetsfull layout.aup or layout.jsonnothing 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:

text
split=100,0,0
seed=homepage-choose-2026

split 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.

  1. Read the existing pages/<name>/layout.aup (and i18n / sources it already binds).
  2. Decide which alternatives are values and which are structure. Values → tokens.json only. Structure → a complete layout file. Do not copy the whole layout to change --accent.
  3. Write each alternative under pages/<name>/variants/<name>/ in the same turn as the control, so a human can open review once.
  4. Add _config. If visitors must not enter the experiment yet: split=100,0,0 (pad zeros to the number of non-default names).
  5. Run arc dsl lint / validate / blocklet check on the site. A bad split or an empty variant directory fails here, not at request time.
  6. 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:

ControlWhat it does
Version buttonslocation.assign the same path with review=1 and variant=<name>
Splitreview=1&split=1. Builds at most four iframes, declaration order
Pane title / chromeLeaves split: review=1&variant=<that name>, no split
SelectPOST 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.

Homepage review HUD with the version bar

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

Homepage split grid

?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.

ActorWhat they get
First-time browser visitorhashed into a bucket from (seed, visitorId); arc_variant_id cookie is minted
Same visitor laterthe same bucket, until seed changes
Recognized crawler UAalways 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

TopicCurrent contract
Enable flagonly ?review=1 injects the HUD; ?split=1 alone does nothing
Second overlaynone; no data-arc-experiment script
Kind 3 (one slot / one sentence)not implemented; use whole-page split
Kind 2 + tokens in the same directorytokens ignored this wave
Split capacityat most four iframes, first names in declaration order
Split pane viewporteach iframe layouts at 1280×800 CSS px and scales to the cell (so in-frame matchMedia(max-width: 760px) is false)
Collection itemsnot variant pages
Metricsout of scope
Published snapshot.experiment/ is authoring-time; do not teach it as a public widget

Check

bash
arc --version
arc dsl lint blocklets/arcblock
arc dsl validate blocklets/arcblock
arc blocklet check blocklets/arcblock

Then open the printed local URL:

text
http://<host>:<port>/en/?review=1
http://<host>:<port>/en/?review=1&split=1
http://<host>:<port>/en/?review=1&variant=aurora

Confirm: 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.