# 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 — the page's own queries read them back before anything is saved. `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 called with an **entity**
> argument 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`.
>
> A function taking only **scalars** runs standalone and commits in-band, which is why a `Create(string title)` verb
> persists with no `UnitOfWork.Commit()` of its own while `Toggle(Item i)` next to it does not. That difference appears nowhere
> in the source of either — it is the argument type that decides.

## 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; }
}

[Page("/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);
    }
  }
}
```

⚠ **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 the **page's own `live var` / `foreach`** (they read
  *through* it). Add ten items across ten clicks and all ten show up, before anything is saved.

  ⚠ **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: **the page's own queries read it back**. 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: Root)` ([Dialog.Open / Dialog.Ask / Dialog.Confirm / Dialog.Discard](https://osysharp.com/reference/ui/dialogs/)). Nothing is pending in the list's unit of work, and
  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.**
- **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; }

[Page("/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; }

[Page("/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 three binding targets and no others: a client-field scalar
(`Input(value: draft)`), a loaded entity field (`Input(value: org.Name)`), and a `Binding<T>` prop this component was
handed ([generic component](https://osysharp.com/reference/ui/generic-component/)).

⛔ **A field of a `class` value is NOT one of them, and this 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."*
**So if a page must let a person EDIT the rows of a list, those rows are an `entity`** — hold the value in a
component field and copy it into the class when you save, or make the row a real entity. Deciding that up front is
much cheaper than discovering it once the page is written; the full target list is at [[ui-component#two-way]].

```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; }
}

[Page("/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.)

## 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). 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:

- The server function **sees your pending edits** — the page's uncommitted overlay travels with the call, so the
  server reads the same in-progress data the user is looking at.
- 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()
```

## 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
