# creating & saving data

> A UI `action` creates, updates and deletes data by writing `new Entity { … }`, assigning fields, and calling `.Delete()`. Edits apply instantly and stay visible while the user keeps working — a list of every row of that type reads a new row back before anything is saved, and a filtered list takes it from its own re-read once it is. `UnitOfWork.Commit()` sends the accumulated edits to the server atomically. A **form** commits once, on Save; a page that saves **per action** — ticking a to-do IS the save — commits in each verb. Both are correct; what is never correct is a page with no Save and no `UnitOfWork.Commit()`, where the write is discarded with no error.

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

## Summary        {#summary}
A component **writes data** from an `action`: create a row with `new Entity { … }`, set fields by assignment, then
call **`UnitOfWork.Commit()`** to persist. The mutations apply **optimistically** — they take effect on the client the instant
the action runs, so the UI updates with no round-trip — and `UnitOfWork.Commit()` flushes them to the server, which validates
and persists them **atomically**. If the server rejects the write, the optimistic edit rolls back.

```osy syntax
action Save() {
  new Note { Title = title };   // create — applied optimistically on the client
  UnitOfWork.Commit();          // persist to the server (atomic); on failure, rolls back
}
```

A **form** is this action plus inputs bound to state: the user types, state updates, the action reads that state
into the new row.

### Where `UnitOfWork.Commit()` goes — the one decision {#where-commit-goes}

There are two shapes, and a page is one of them:

| shape | example | where `UnitOfWork.Commit()` goes |
|---|---|---|
| **form** — nothing persists until Save | an edit screen with a Save button | once, in the Save action |
| **per action** — the act IS the save | ticking a to-do, archiving, deleting a row | in each verb |

Both are correct and the compiler does not ask you which one you meant — **you choose by asking whether the user
should be able to change their mind before anything is stored.** If yes — an edit screen, a form with several
fields, anything a Cancel makes sense on — the page holds its edits and one Save commits them. If no, commit in the
action.

The unit of work is the DEFAULT, and it is the better model whenever the choice is close: a person can add, close
and delete several things and then decide, and `UnitOfWork.Discard()` throws the pending edits away without leaving
the page. That is the shape `admin` is built on.

> ⚠ **Do not silence it by removing the `UnitOfWork.Commit()` on a page that has no Save.** A function that runs in
> the **browser** runs inside the calling page's unit of work rather than its own, so its write becomes durable only
> when that page commits — and if nothing does, it is discarded when the page goes away, with no error and no failed
> request. The optimistic overlay renders the change, so the screen looks right. `osy lint` catches this shape as
> `data-write-never-committed`, and `osy validate` and `osy test` print it as `DATA_NEVER_SAVED`, naming the write.
>
> A function that runs on the **server** — one whose body reaches something only the server has (a secret, an
> outside call), which the compiler places there for you — is its own request, and its write is
> stored when it returns. So `Create(string title)` that only builds a row loses it like `Toggle(Item i)` does, while
> the same `Create` that also reads a secret keeps it. What the function TAKES does not decide this; where it RUNS
> does — so when in doubt, commit.

## Signature      {#signature}
```osy syntax
new Entity { Field = value, … };   // create a row (optimistic); evaluates to its id
var e = new Entity { … };          // …bind the id to update or reference it
e.Field = value;                   // update a field (optimistic)
e.Delete();                        // delete a row (optimistic) — the row vanishes from the page's queries at once
UnitOfWork.Commit();               // persist all pending edits to the server, atomically
```

### Deleting a row        {#delete}

`.Delete()` on the row. It joins the page's unit of work exactly like an edit does, and lands when **Save** does —
so the delete action itself has no `UnitOfWork.Commit()` in it, the same as an "Add item" action does not:

```osy title="delete a row — it lands with the page's Save, like any other edit" test app=ui-data-mutation-delete
using Osysharp.Ui;

entity Note {
  [Required, MaxLength(200)] string Title;
  security { allow read, create, update, delete when IsAnonymous || IsAuthenticated; }
}

[Route("/notes")]
[AllowAnonymous]
[Render(CSR)]
component Notes() {
  live var notes = Note.ToList();
  string draft = "";

  action Remove(Note n) { n.Delete(); }          // the delete joins the page's unit of work
  action Add() { new Note { Title = draft }; draft = ""; }
  action Save() { UnitOfWork.Commit(); }         // …and lands here, with everything else

  render {
    Stack {
      Field("Title", value: draft);
      Button("Add", onPress: Add);
      foreach (var n in notes) {
        Row { Text(n.Title); Button("Remove", onPress: () => Remove(n)); }
      }
      Button("Save", onPress: Save);
    }
  }
}
```

### Is there anything to save, and what if they leave first?   {#is-there-anything-to-save}

A Save button that is always live tells the reader nothing, and a page they can walk away from mid-edit loses their
work silently. Both are one read — **`Navigation.IsDirty`**, which is true exactly when this page holds uncommitted
edits and updates itself as they type:

```osy title="a Save that lights up only when there is something to save" test app=ui-data-mutation-dirty
entity Note { [Required, MaxLength(200)] string Title; }

[Route("/notes/{id}")] [AllowAnonymous] [Render(CSR)]
component EditNote(string id) {
  var note = Note.Single(n => n.Id == id);
  action Save() { UnitOfWork.Commit(); }
  // ⭐ THE IDIOM. `disabled: !Navigation.IsDirty` — nothing typed, nothing to press.
  render {
    Stack {
      Field("Title", value: note.Title);
      Button("Save", onPress: Save, disabled: !Navigation.IsDirty);
    }
  }
}
```

And the same read is what a **leave guard** asks: hold the destination, prompt, and offer Save
(`Navigation.Save(path)`) · Discard (`Navigation.Close(path, true)`) · Keep editing. That is [Navigation](https://osysharp.com/reference/ui/navigation/)
`#in-actions` and `#closing`, in full; the shells that ship with the platform do it for you on their own exits —
every nav row, and in `SidebarShell` the app switcher behind the brand (`AppChrome.Apps`) as well.

⚠ **This is not `UnitOfWork.Discard()`.** Discard throws away *this* body's pending writes; the leave guard is about
a **route** the user is walking away from, and it is the router — not the page — that can reach that route's unit of
work once the page is gone.

⚠ **A per-row action that commits is a different design, and it needs no ceremony.** If a page genuinely has no
Save — a list where pressing the bin is the whole interaction — then `Remove` commits, and that is all there is to
write:

```osy title="the other design — a page with no Save, where the press IS the save" syntax
action Remove(Note n) { n.Delete(); UnitOfWork.Commit(); }
```

⛔ **What you must NOT do is drop the `UnitOfWork.Commit()` from that shape.** On a page with no Save the write then
sits in a unit of work nothing commits, and is discarded when the page goes away — with no error, no failed request
and a screen that looks exactly right, because the optimistic overlay rendered it. That is the one failure here you
cannot see for yourself, and it is why `data-write-never-committed` is a MUST rule in `osy lint`.

It is **optimistic like the others**: the row leaves the page's queries immediately, before the server has been
asked, and comes back if the commit fails. So a list re-renders without it at once and nothing has to be re-fetched.

⚠ **There is no `Delete` on the entity TYPE** — no `Note.Delete(id)`. You delete a row you are holding, which is
what a page always has: the `foreach` variable, or an entity-typed parameter passed to the action. That is the same
rule as updating, where you assign to `n.Title` rather than calling a setter on `Note`.

⚑ **Deleting several ROWS THE PAGE HOLDS is a loop**, and it is still one commit: the unit of work is what makes
them atomic, so all of them land or none does. Deleting **by predicate** — "every stale order", rows the page never
loaded — is the set-based terminal instead: `Order.Where(o => o.Status == "Stale").Delete()` runs one statement in
the database, immediately, outside the page's unit of work, and answers how many went. Same split for updates
(`.Update(o => { … })`) and per-row creates (`.Insert(s => new T { … })`). See [Delete](https://osysharp.com/reference/query/delete/), [Update](https://osysharp.com/reference/query/update/)
and [Insert](https://osysharp.com/reference/query/insert-from/) — and note the refusal that keeps the two models honest: a bulk verb will not run while
the page's unit of work holds uncommitted changes of the same type, because a statement over stored rows cannot see
them.

### Why it hangs off `UnitOfWork` {#why-the-receiver}

The save is spelled on a receiver — `UnitOfWork.Commit()`, not a bare `commit()` — because the receiver is the point.
A page's edits accumulate in one **unit of work**, and the single most common mistake is not knowing that: writing to
an entity and never committing, or committing in every action because each one looked like a separate save. Naming
the unit of work at every call site puts the thing you are committing in front of you while you write it.

There is no lowercase carve-out to remember: everything you declare and everything you call is PascalCase, and the
compiler says so if it drifts (`ACTION_NAME_NOT_PASCAL_CASE`). Parameters and component props stay camelCase
(`Guid id`, `tone`), as do the platform's own event props (`onClick`).

## Description    {#description}
A page's data edits accumulate in an **optimistic overlay** — one unit of work for the whole page (or tab). Edits
from *every* action land in that same overlay and stay there, visible, until the user decides to save:

- **`new Entity { … }`** creates a row locally and evaluates to its id. It's visible immediately — to the rest of
  the action (its fields read back through the overlay), and to every list on the page that reads **every row of
  that type**: `Item.ToList()`, ordered or with `.Include(…)`s. Add ten items across ten clicks and all ten show up,
  before anything is saved.

  A list with a filter or a window — `Item.Where(i => i.Done).ToList()`, `Item.OrderBy(…).Take(6)` — does **not**
  show it yet: the filter runs on the server and the browser cannot ask whether a new row matches it, so rather than
  guess (a draft order under "Paid orders", seven rows in a list of six) that list takes the row from **its own
  re-read** after `UnitOfWork.Commit()` reaches the server, about one round trip later. To show a parent's unsaved
  children, read them through the parent's collection — `order.Lines` — which follows the pending rows exactly
  ([Child collections (navigating a relation)](https://osysharp.com/reference/query/collections/)).

  ⚠ **One read does not see it: a new `Entity.Where(…)` issued inside an action.** That is a SERVER read — the filter
  never leaves the server — so it answers over committed data only, and a row you created a line earlier is not in it.
  The runtime refuses that line rather than handing back a wrong count. Ask the page's `live var`, which already holds
  the rows including the pending ones, or `UnitOfWork.Commit()` first and then read.
- **`e.Field = value`** records a field change on an existing (or just-created) row; reads reflect it at once.
- **`UnitOfWork.Commit()`** sends everything accumulated so far to the server, which applies your app's validation and
  security rules and persists it in one atomic step, then confirms it back as settled data.
- **`UnitOfWork.Discard()`** throws that accumulation away instead — the edits roll back and the screen returns to
  what the server last confirmed. The page stays open; only its pending changes go.

Both read the same in a server function and in a UI action, and mean the same thing: `UnitOfWork` is the accumulated
work, `Commit` persists it, `Discard` drops it.

**Where `UnitOfWork.Commit()` belongs — two places, never more.** Committing is a *user* decision, so put `UnitOfWork.Commit()` on:

1. a dedicated **Save** action (a Save button), and
2. a **discard-guard** — when the user is about to leave unsaved work (closing a tab/dialog, navigating away).

Do **not** sprinkle `UnitOfWork.Commit()` through ordinary actions. An "Add item" action just creates the row; the user's
Save is what persists the batch. This keeps drafts editable and cancellable, and matches how the server already
lets a function read its own uncommitted writes. A commit that fails (validation or permission) **reverts** the
overlay, so the UI never shows data the server rejected. A create obeys the entity's write permissions — the acting
user must be allowed to create the entity.

### Throwing changes away — `UnitOfWork.Discard()`   {#discard}

Discarding is the other half of the same decision, and until it existed the only way to abandon edits was to close
the thing holding them:

```osy syntax
action StartOver() {
  UnitOfWork.Discard();     // the page's pending edits are gone; the page itself stays open
}
```

It clears **one** unit of work — the one you are in — and stops there. That asymmetry with `Commit()` is deliberate
and it matters: a commit reaches outward, because persisting is the outermost unit of work's job and an inner scope's
edits have to get there. A discard must not, or closing an inner surface would take the surrounding page's unsaved
work with it.

Reads fall back to what the server last confirmed, so a field that was edited shows its stored value again, and a row
created only in the overlay is simply no longer there.

### Where a create-form's draft belongs   {#draft-scope}

A draft is an ordinary pending row, so it obeys the rule above: **a list of every row of its type, read in the same
unit of work, shows it**. That is the feature — it is what makes `Add` show the new item instantly — and it is also
the one way a create form goes wrong.

A component that holds `Entity draft = new Entity { };` **and** queries that same entity has enlisted a row before
anybody has typed. Two things follow, neither visible in the source: the list paints a **blank phantom row** on first
paint, and the never-filled draft rides the next genuine `Commit()`, where its unset `[Required]` fields fail the
save and name a row the user never opened. `osy lint` reports the pair as `ui-draft-field-ghosts-its-own-list`
(SHOULD) — it fires on the member-initialiser spelling and on `on mount { draft = new Entity { }; }` alike, because
both run at mount.

Two ways out, and they are not equivalent:

- **Give the draft its own unit of work** — put it on a component you open with `Dialog.Open(NewThing(),
  unitOfWork: Inherit)` (the page's Save persists it) or `unitOfWork: Root` (the dialog's own Confirm does)
  ([Dialog.Open / Dialog.Ask / Dialog.Confirm / Dialog.Discard](https://osysharp.com/reference/ui/dialogs/)). A list reads through the unit of work it was mounted in, so the draft is visible to the dialog's
  own reads and to **none of the page's lists** — filtered or not — until the dialog hands it on; Cancel drops it
  everywhere. The draft stays an entity, so every `[Required("…")]` sentence is still declared once on the model
  ([Validation](https://osysharp.com/reference/ui/validation/)). **This is the one to reach for** — an edit you can cancel belongs in a unit of work you can
  throw away.
- **Hold the fields as local scalars** (`string title = "";`) and construct the entity inside the action that saves
  it. Nothing is pending until the action runs — but the fields are no longer entity properties, so a bound control
  has no rules to configure itself from and every declared message has to be re-typed as a guard in the action. Right
  when the form does not correspond to one entity; a real cost otherwise.

⚠ The **kit `Dialog(title, onDismiss)` control is not the first option.** It is `[Composable]` markup inlined into
the page's own tree and carries no unit of work at all, so a draft inside it is pending in the page's — exactly the
shape being fixed.

## Examples       {#examples}
A list the user builds up before saving — `Add` creates a row optimistically (it appears in the list at once, via
`foreach` reading through the overlay); a separate **Save** action is the only place that commits:

```osy title="item-list" test app=ui-items
entity Item { string Title; }

[Route("/items")]
[Render(CSR)]
component ItemList() {
  string draft = "";
  var items = Item.ToList();

  action Add()  { new Item { Title = draft }; }   // optimistic — shows immediately, not yet saved
  action Save() { UnitOfWork.Commit(); }          // the user's Save button — persists the whole batch

  render {
    Stack(gap: 2) {
      foreach (var it in items) { Text(it.Title); }
      Input(value: draft, placeholder: "New item");
      Button("Add", onPress: Add);
      Button("Save", onPress: Save);
    }
  }
}
```

The simplest form — create and Save in one action — is just the same pattern with `new` and `UnitOfWork.Commit()` in a single
Save handler:

```osy title="note-form" test app=ui-notes
entity Note { string Title; }

[Route("/notes/new")]
[Render(CSR)]
component NoteForm() {
  string title = "";

  action Save() {
    new Note { Title = title };
    UnitOfWork.Commit();
  }

  render {
    Stack(gap: 2) {
      Input(value: title, placeholder: "Note title");
      Button("Save", onPress: Save);
    }
  }
}
```

## Editing a row — bind an input to its field   {#edit-binding}
An **edit form** binds an input straight to a **field of a loaded row**: `Input(value: org.Name)`. This is a **two-way
binding**, exactly like binding to a client field — but the target is an entity field, so:

- the input **pre-fills** with the row's current value, and
- typing writes the change **into the page's overlay** (not the database) — the same optimistic overlay a `new`
  accrues into. The page becomes **dirty** as the user types (its tab shows the unsaved-work marker), and the edit is
  persisted only when a **Save** action calls `UnitOfWork.Commit()` — or discarded if the user closes the tab.

The row itself comes from a **scalar server read** (`Single`/`FirstOrDefault`), typically keyed by a route parameter, so
the page loads exactly the record being edited. There are four binding targets and no others: a client-field scalar
(`Input(value: draft)`), a loaded entity field (`Input(value: org.Name)`), a `Binding<T>` prop this component was
handed ([generic component](https://osysharp.com/reference/ui/generic-component/)), and an element of a list this component owns — or a field of one
(`DecimalField(value: caps[i].Amount)`).

⛔ **A field of a BARE `class` value is not one of them, and that is a data-model decision.** A `class` is an
in-memory shape with no row behind it, so there is nothing to write through — the compiler refuses it rather than
rendering a box that quietly discards what is typed: *"cannot two-way bind to `k.Weight` — `k` is a `class` … the
edit would be read-only."* A `live var` is refused for the same reason: it is computed, so *"there is nothing to
write back into."*

⭐ **But a list this component OWNS is writable, elements and their fields alike** — `caps[i].Amount` over a
`Cap[] caps;` field writes through the component's own signal, replacing the element and then the list. That is how a
page edits rows whose number belongs to the data rather than to the source. So the choice between a `class` and an
`entity` here is about **where the rows LIVE** — a class list is page state that a Save action sends somewhere, an
entity list is rows with identity that the overlay tracks and `UnitOfWork.Commit()` persists — and no longer about
whether a person can type into them. The full target list is at [[ui-component#two-way]], and the worked list form at
[[ui-component#editing-a-list]].

```osy title="editing a loaded row" test app=ui-org-edit
[Principal] entity User { [Required] string Email; }

entity Organization {
  [Required] string Name;
  [Required] string Slug;
  security { allow create, read, update when IsAuthenticated; }
}

[Route("/org/{id}")]
[Render(CSR)]
component OrgEdit(Guid id) {
  var org = Organization.Single(o => o.Id == id);   // load the one row (route param → scalar query)

  action Save() { UnitOfWork.Commit(); }                          // persist the edits the bindings accrued

  render {
    Stack(gap: 2) {
      Input(value: org.Name);                          // pre-fills; typing dirties the page overlay
      Input(value: org.Slug);
      Button("Save", onPress: Save);
    }
  }
}
```

For the row to be editable in the overlay it must be **held in the page's own unit of work** — a scalar server read on the
page does exactly that. (An input bound to a row created on a *different* page/tab would be writing into the wrong
overlay; each retained tab has its own.)

### Who saves an edit — the page   {#the-page-saves}
The person's edit is already in the row: the bound input wrote it into the page's unit of work as they typed. So the
**page** saves it — `UnitOfWork.Commit()` in its Save action, or a `SaveBar` / `SaveActions` / `AutoSave` over the
page ([The flow kit — a node-and-edge canvas whose nodes are your own Osy#](https://osysharp.com/reference/ui/flow-kit/)). A function is for the part of the work that is **not** the person's edit — a computation, a
process, an outside call — and the page's save carries whatever it set:

```osy title="the page saves its edit; a function only computes" test app=ui-data-mutation-page-saves
entity Shelf {
  [Required, MaxLength(80)] string Label;
  [MaxLength(80)] string Slug = "";
  security { allow read, create, update when IsAnonymous || IsAuthenticated; }
}

/// A computation — it answers, and writes nothing.
string SlugFor(string label) { return label.Trim().ToLower().Replace(" ", "-"); }

[Route("/shelves/{id}")]
[AllowAnonymous]
[Render(CSR)]
component EditShelf(Guid id) {
  var shelf = Shelf.Single(s => s.Id == id);

  action Save() {
    shelf.Slug = SlugFor(shelf.Label);   // the derived field, set on the row the page holds
    UnitOfWork.Commit();                 // the page saves — the person's edit and the derived field together
  }

  render {
    Stack(gap: 2) {
      Input(value: shelf.Label);
      Button("Save", onPress: Save);
    }
  }
}
```

⚠ **Do not hand the edit back to a function to write and save.** The shape below reads as tidy and is the one to
avoid; `osy lint` names it `data-page-edit-saved-by-a-function`:

```osy title="the shape to avoid — the edit handed to a function that writes it and commits" syntax
void UpdateShelf(Shelf s, string label) { s.Label = label; UnitOfWork.Commit(); }

action Save() { UpdateShelf(shelf, shelf.Label); }   // ✗ the save is the function's, not the page's
```

That function runs on the server and writes on what **it** read. So when somebody else saved the field after this
page read the row, their value is overwritten without a word — even on a [[entity-concurrency-check|`[ConcurrencyCheck]`]]
field, where the page's own commit is checked against what the person saw and shows them the
[conflict notice](https://osysharp.com/reference/ui/conflict-notice/) (their value and the new one, Keep theirs / Apply mine). A rule such a function
enforces ("only a draft can be edited") belongs on the entity — a `rule`, or `allow update when …` in its `security`
block — where every save meets it, the page's included.

## Calling a server function   {#server-calls}
An action can call a **server function** mid-flow — to run logic that belongs on the server (a privileged read, a
cross-record calculation, a process, an outside call). Not to save the person's edit: that is the page's
([[ui-data-mutation#the-page-saves]]). You just call it like any other function; the hand-off happens under the hood (no `await`,
no ceremony). The boundary is seamless in both directions:

- A **row you hand it is the row the person edited** — its pending edits travel with the call (see below). The
  page's *other* uncommitted edits do not: an ordinary call never carries the whole page.
- Whatever the server function **creates or changes comes back**, and the rest of your action reads it immediately —
  *read-your-writes* across the boundary.

⚠ **From an `[AllowAnonymous]` page, the server function must be `[AllowAnonymous]` too.** A signed-out visitor may
only hand off to a target that says it is public, so a plain server function called from a public page is
refused — and the refusal arrives *at the call site*, which unwinds the rest of the action: the write does not
happen and **no statement after the call runs either**. Marking the function `[AllowAnonymous]` does not open its
data: every read and write inside it is still gated by the entity's own `security { }`. This compiles clean today,
so it is the one part of the hand-off the compiler cannot yet warn you about.

Crucially, this **keeps the same Save agency**: rows the server produced ride back into the page overlay **still
uncommitted** — they show up in the UI, but the user's **Save** is what persists them, exactly like a local `new`.
The one exception is deliberate: if the server function itself calls `UnitOfWork.Commit()`, its own writes persist server-side
at that point (server logic can own its transaction). So "nothing persists until Save" holds for ordinary server
calls, and a server function only bypasses it by explicitly committing — which the compiler flags with a warning so
it's never a silent surprise.

```osy syntax
action Reserve() {
  var seat = AssignSeat(row);      // server picks a seat, returns the row it created (hand-off is implicit)
  note = "You got " + seat.Label;  // read-your-writes: the returned row is visible here
}                                  // …still uncommitted — the user's Save persists it,
                                   //    unless AssignSeat itself called UnitOfWork.Commit()
```

### A row you hand a function carries its edits   {#handed-row}
Pass a row the page is editing to a server function and the function sees the values on the screen, not the row as
it was stored — as it would in C#, where the object you pass is the object the callee reads. So a function that
commits saves what the person typed:

```osy title="the edited row, handed to a function that commits" syntax
public Bottle SaveBottle(Bottle b) { UnitOfWork.Commit(); return b; }

component BottleEditor(Guid id) {
  var b = Bottle.Single(x => x.Id == id);
  action Save() { SaveBottle(b); }        // ✓ the label the person typed is what SaveBottle commits
  render { Stack() { Input(value: b.Label, label: "Label"); Button("Save", onClick: Save); } }
}
```

The edits are applied as the caller's own writes before the function runs — the same path, and the same checks, as
the page's own `UnitOfWork.Commit()`, including what the person was shown on a
[[entity-concurrency-check|`[ConcurrencyCheck]`]] field. They persist only if the function commits; if it does not,
they come back as the person's pending edits. Only the rows the call is handed travel — an argument typed as a row or a list of rows — never
another row the page has edited, and never a row named only by its id (`Approve(report.Id)` hands over a `Guid`).

An edit to a field the caller may not change itself does not travel. A field under `deny update Checked except via
MarkChecked` changes only inside `MarkChecked`, so a page's tick on it is not the row's state: the function sees the
field as stored, and decides. The compiler warns when a control two-way binds such a field, because the page can never
save that edit — see [[security-except-via#from-a-page]].

## See also       {#see-also}
- [component](https://osysharp.com/reference/ui/component/) — the component an `action` and its bound state live in
- [layout primitives](https://osysharp.com/reference/ui/layout/) — the `Stack`/`Input`/`Button` atoms a form is built from
- [routes and pages](https://osysharp.com/reference/ui/routing/) — binding the form page to a route
