# The PageBuilder kit — the desk staff arrange the site at

> `Osysharp.PageBuilder` is the desk where the people who run a site arrange its [[ui-blocks|regions]]: add, move, fill in and take out blocks beside a live preview at phone and desk width, keep it as a draft, and publish. The site comes in VERSIONS — the everyday one and, say, Christmas — each with its own arrangements and look, live now or on a schedule, and a member of staff can preview the whole site as a version would look on a date before anyone else sees it.

<!-- id: ui-pages-kit · area: ui · stability: stable · html: https://osysharp.com/reference/ui/pages-kit/ -->

## Summary        {#summary}
Put `PagesDesk()` on a staff page and `SiteLook()` in the layout, grant staff the tables, and the regions the app's
pages declare are theirs to arrange. The desk is in English and Swedish, and staff write each block's words in every
language the app speaks.

```osy syntax
// app.osy
use Osysharp.Blocks;
use Osysharp.PageBuilder@1;
```

## Description    {#description}

### Putting the desk in an app {#setup}
Three things: the desk on a page only staff reach, `SiteLook()` in the layout every public page uses, and the grants.

```osy title="a site whose front page staff arrange" test app=ui-pages-kit
app Greenhouse {
  use Osysharp.Ui;
  use Osysharp.Storage;
  use Osysharp.Blocks;
  use Osysharp.PageBuilder@1;
  model "model/**/*.osy";
}

using Osysharp.Blocks;
using Osysharp.PageBuilder;

theme Greenhouse {
  Colors {
    Leaf = Modes.Of(light: "#2F6B3A", christmas: "#8C1C13");
    Pot = Modes.Of(light: "#F3EDE4", christmas: "#FBF7EF");
  }
}

[Role] public enum AppRole { Authenticator, Staff }

[Principal]
public entity Member {
  [Required, Unique, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated where Id == user.Id; }
}

[RoleGrant] public entity RoleGrant {
  [Required] Member Member;
  [Required] AppRole Role;
  security { allow read when IsAuthenticated; }
}

policy IsStaff => RoleGrant.Any(g => g.Member == user && g.Role == AppRole.Staff);

policy ArrangesPages => IsStaff;   // Osysharp.Blocks' one question: who arranges the site

/// A headline on the leaf-green ground.
[Block("Headline")]
component Headline([Label("Headline"), MaxLength(80)] string title, Color ground = Colors.Pot) {
  render { Stack(p: 8, bg: ground) { Text(title, fontSize: FontSize.Title, color: Colors.Leaf); } }
}

// Every public page sits in this layout — SiteLook() dresses it in the live version's look, and shows staff the
// banner while they preview.
[Layout]
[Composable]
component Site() {
  render { Stack(gap: 0) { SiteLook(); Outlet; } }
}

[Route("/")]
[Layout(Site)]
[AllowAnonymous]
component Front() {
  render { Region("front", allow: [Headline]) { Headline(title: "Plants for dark flats"); } }
}

[Route("/staff/pages")]
[Authorize(IsStaff)]
component StaffPages() {
  render { Box(p: 6) { PagesDesk(); } }
}
```

### What does staff do at the desk? {#desk}
The desk lists the site's versions and every region the code declares, with the page it is on and whether it is still
as the code has it, arranged, or has a draft waiting. Arranging a region opens it beside the page itself, at phone
width or desk width:

- **Add a block** from those the region allows — each drawn small in the site's own look, with its name and what it
  is for ([[ui-blocks#thumbnails]]) — **Up**/**Down** to move one, **Take out** to remove it;
- a block's fields, each with the editor its type chose ([[ui-blocks#params]]) — words in the language chosen at the
  top, so a Swedish reader gets Swedish words; a `Markdown` field is written in the Markdown editor (the one a
  newsletter is written in), and a `Color` field offers the theme's colours by name ([[ui-blocks#colours]]);
- **When it shows** — a block of its own can be dated (a sale banner from the 1st to the 21st), and is gone from the
  morning of its last date;
- **Publish** — the draft becomes what customers see while its version is live. **Throw the draft away** goes back
  to what is published; **Back to the code's own** to the default the code writes.

### The site's frame — the menu, the strip, the footer {#frame}
Regions in the LAYOUT and the app's `SiteMenu(…)` ([The site's frame — a menu, a strip and a footer staff change](https://osysharp.com/reference/ui/site-frame/)) are listed first, under **Around every page**.
A frame region is arranged like any other, previewed on the front page. The MENU opens as a list instead of blocks:
each entry's words and where it goes (**Change**, with its dates), **Hide**/**Show**, **Move in**/**Move out** (under
the entry above), **Up**/**Down**, **Take out**, and **Add an entry** — an address that is neither a page of the site
nor an `https://` one is refused by name, and an outside address is marked **Another site**. Publish, throw away and
back to the code's own work as for a region; the page beside it shows the draft menu as it changes.

### Arranging a mail {#mails}
A mail's [[ui-email#region|region]] — the words an order confirmation greets with, a photograph or a quote beside
them — is listed under **What each mail says** and arranged the same way, in the same versions of the site. Beside its
blocks the desk shows the MAIL, as the mail renderer writes it and as the region's `sample:` renders it for something
real (the latest order), with this member of staff's draft in it:

- **Phone** — the mail at a phone's width; **Wide** — in a reading pane 720 pixels wide; **Plain text** — the
  text twin a client that shows no HTML reads. Each is rendered again after every change.
- **Add a block** draws each mail block small, as mail.
- A block the mail must keep (`require:`) says **Stays** and has no **Take out**.
- Words naming a placeholder the mail does not fill are refused where they are written, naming the ones it offers.

A mail rendered while its sender previews says it is a preview (`RenderedEmail.IsPreview`), and is not sent — so a
member of staff looking at the Christmas draft can mark an order sent without mailing the draft to its customer: the
shop's outbox refuses it, and says to stop previewing first.

Nothing a customer sees changes until Publish: every edit goes to a DRAFT, and a draft is visible only in a preview.

### Versions of the site — the everyday one, Christmas {#versions}
The site starts with one version, **Everyday**, which customers see whenever no other version is live. **New version**
copies the one on screen — every arrangement, as drafts — so Christmas starts as Everyday and changes from there.

A version other than Everyday is live between two dates (**Live from**, **Live until** — the first day it is not), or
now with **Make it live now**. Two versions cannot be live over the same days: a schedule that overlaps another is
refused, naming it. A region a version never arranged shows what Everyday shows there.

Each version can **wear** one of the theme's variants — the `christmas:` values of a `Modes.Of(…)` ([[ui-theming#variants]]).
The theme declares them; the desk only offers what is there. The live version's variant is on the page the server
sends — `<html data-variant="christmas">` — so the very first paint is already the Christmas look, never the everyday
one for a moment first. (A staff member's preview is applied once the page knows who they are.)

### A version's look — the whole design {#looks}
An app that installs LOOKS ([Looks — a site's whole design, picked per version](https://osysharp.com/reference/ui/looks/)) — five shop designs, say — shows them on each version's card: **Look** and a
button per look, the one it wears marked, with the look's line under it. Choosing another look first lists what that
look has no design for — a block only the current look draws, a type the new one does not, a region it has no place for —
under **What will not come along**, with **Switch to …** and **Keep …**; nothing moves until staff choose. A switch keeps
every arrangement: the same words and photographs, drawn in the new look's design, and anything left behind comes back
with a look that draws it.

A new version copies its look with everything else, so a redesign is drafted in a copy, previewed as a whole site and
switched on a date like any other version. The regions listed are the version's look's — what each allows, its default —
and the **Add a block** list draws each block small **in that look** (its design, its theme), whatever the live site wears.

### Seeing the site as it will be — the preview {#preview}
**Preview** a version **as of** a date, with or without its drafts, and then open the site: every page of it answers as
that version would on that date — its arrangements, its dated blocks, its look — for this browser only, with a banner
saying so and **Back to the live site**. Nobody else sees it, and the link cannot be sent: the preview belongs to the
signed-in staff member AND the browser they started it in.

A preview moves what the site SHOWS, never the clock: nothing that runs on a date runs early. And it is read-only —
code that does real work asks `SitePreviewing()` and refuses, as the shop kit's checkout does:

```osy title="refusing real work while staff preview" test app=ui-pages-kit
using Osysharp.Blocks;

[AllowAnonymous]
public string Reserve(string plant) {
  if (SitePreviewing()) { throw new Exception("You are previewing the site — nothing is reserved until you go back to the live site."); }
  return $"{plant} is set aside for you.";
}
```

`SiteDate()` answers the date the site is being looked at — today, or the previewed date — for a block that shows
something by date itself. The shop's own sales go by the same rule ([Blocks and regions — pages the staff arrange](https://osysharp.com/reference/ui/blocks/)' one rule for dated content,
`ShowsAt`): a markdown can be scheduled from one date until another, a customer never sees it before it starts, and a
preview as of a date inside it shows its prices and its sale shelf.

### Doing it from code — versions and arrangements {#from-code}
Everything the desk does is a function of `Osysharp.Blocks`, for a test, an import or a scheduled job:

| function | what it does |
|---|---|
| `MainVersion()` | the everyday version |
| `DuplicateVersion(id, name)` | a copy of a version, its arrangements as drafts — the new version's id |
| `ScheduleVersion(id, from, until)` · `MakeMainVersion(id)` | put a version live between two dates · make it the everyday one |
| `SetThemeVariant(id, name)` | the variant a version wears (`""` for none) |
| `EditRegion(version, region, key)` | the draft of a region (made from what is published, or the code's default) |
| `AddBlock(draft, block, position)` · `MoveBlock` · `RemoveBlock` | arrange a draft; a block is named by its full name |
| `SaveBlockValue(placed, language, value)` · `DateBlock(placed, from, until)` | fill in a field · date a block |
| `PublishRegion(draft)` · `DiscardDraft(draft)` · `ResetRegion(version, region, key)` | publish · throw away · back to the code's default |
| `StartSitePreview(version, at, withDrafts)` · `StopSitePreview()` | preview for this browser · back to the live site |

```osy title="a Christmas version, prepared and scheduled in one call" test app=ui-pages-kit
using Osysharp.Blocks;

[Authorize(IsStaff)]
public Guid PrepareChristmas() {
  var christmas = DuplicateVersion(MainVersion().Id, "Christmas");
  SetThemeVariant(christmas, "christmas");
  var draft = EditRegion(christmas, "front", "");
  var headline = PlacedBlock.Where(b => b.Arrangement.Id == draft).OrderBy(b => b.Position).First();
  SaveBlockValue(headline.Id, "en", new BlockValue { Name = "title", Text = "Wreaths, and trees that last" });
  PublishRegion(draft);
  ScheduleVersion(christmas, new DateTime(2026, 11, 28), new DateTime(2026, 12, 27));
  return christmas;
}
```

### How the desk shows a page beside its fields — `PageFrame` {#page-frame}
The desk shows the page beside the fields in a `PageFrame("/c/christmas", title: "…", w: …, h: …)` — an atom that
frames a page of the SAME app, and nothing else: an address outside the app, a `javascript:` or a `//host` path is not
framed. It is how the desk shows the phone width and the desk width of the real page, and it is free for any staff
page of your own.

## See also   {#see-also}
- [Blocks and regions — pages the staff arrange](https://osysharp.com/reference/ui/blocks/) — writing blocks and regions.
- [[ui-theming#variants]] — the palette a version wears.
- [Looks — a site's whole design, picked per version](https://osysharp.com/reference/ui/looks/) — the design a version wears: looks, block types as contracts, look kits.
