# [Remembered] — a component field the reader's browser keeps

> `[Remembered]` on a component field keeps the reader's last value in their browser — per app, signed-in user, component and field — and the next instance of the component starts from it. A fresh browser has nothing kept, so it shows the initializer; the server always renders the initializer. Only a plain preference may be remembered.

<!-- id: ui-remembered · area: ui · stability: preview · html: https://osysharp.com/reference/ui/remembered/ -->

## Summary        {#summary}
A component field is the component's state, and it starts from its initializer every time the component is created —
so a rail the reader collapsed springs open on the next page load. `[Remembered]` keeps that field's value in the
reader's browser instead: every change is stored, and the next instance of the component starts from what was stored.
A browser that has never stored one — a new device, a cleared site, a private window — shows the initializer, exactly
as before.

## Signature      {#signature}
```osy syntax
[Remembered] bool collapsed = false;
[Remembered] string tab = "Conversation";
[Remembered] Density density = Density.Comfortable;
```

## Description    {#description}

### What is it kept per?   {#key}
One value per **app, signed-in user, component and field**. The component is its TYPE: every instance of
`SidebarShell` shares one `collapsed`, which is what a rail on every page needs — the rail collapsed on one page is the
rail met on the next. Two people who share a browser each keep their own; a signed-out reader keeps one of their own.

The kit's shells use it: `SidebarShell`'s collapsed rail and `RailShell`'s widened rail are both remembered.

### Which fields may be remembered?   {#which}
A plain field of type `bool`, `int`, `double`, `string` or an enum, with its type written out. Everything else is
refused, with the rule in the message:

| refused | why |
|---|---|
| a row, a list, a class | browser storage is the READER's to edit, so a remembered value may only be a preference the page shows — never a fact the app decides with |
| `live var x = …` | computed from other state, so nothing in it is the reader's choice |
| `var x = …` | a stored value is checked against the DECLARED type, and `var` declares none |
| `const` | it never changes |
| a field of a `store` | a store is shared by every component, so there is no one component to keep it for |

A setting that should FOLLOW a person to every browser (their pinned pages, their language) is a row the app keeps —
an entity with an owner, covered by security like any other. `[Remembered]` is for what belongs to this browser.

### What does a fresh browser, or a hand-edited value, show?   {#fallback}
The initializer. A stored value is read back only when it still fits the declared type — a bool is a bool, an int an
integer, an enum one of its members — so a value someone edited by hand, or one left by an older version of the app,
is ignored rather than reaching the page. Storage that is missing or refuses writes (a private window, blocked site
data, a full quota) changes nothing but the memory: the page works as it does on a fresh browser.

### What does the server render?   {#server}
The initializer, always — the server never sees browser storage, and nothing remembered is ever sent anywhere. The
client applies the stored value before its first render, so a client render never paints the initializer at all, and a
page hydrating a server paint corrects the affected nodes in place during hydration rather than rebuilding them.

## Examples       {#examples}
```osy title="a panel whose open state and density survive a reload" test app=ui-remembered
using Osysharp.Ui;

enum Density { Comfortable, Compact }

[Route("/")]
[AllowAnonymous]
component Inbox() {
  [Remembered] bool filtersOpen = true;
  [Remembered] Density density = Density.Comfortable;
  action Dense() { density = density == Density.Compact ? Density.Comfortable : Density.Compact; }
  render {
    Stack(gap: density == Density.Compact ? 1 : 3, p: 6) {
      Row(gap: 2) {
        Switch("Filters", value: filtersOpen);   // a boolean is a Switch — it binds the field and says its state
        Button(density == Density.Compact ? "Comfortable" : "Compact", onPress: Dense);
      }
      if (filtersOpen) { Text("Status · Priority · Assignee"); }
      Text("Refund charged twice");
      Text("Cannot sign in after reset");
    }
  }
}
```

### Testing it   {#testing}
A test is one browser: `Ui.Visit` boots a fresh page in the SAME browser, carrying what it stored, and
`Ui.NewDevice()` is a different browser with nothing kept.

```osy syntax
[Test]
void Hidden_filters_stay_hidden_after_a_reload() {
  Ui.Visit("/");
  Ui.Click("Filters");                // the switch: off
  Ui.Visit("/");                      // same browser — the choice was kept
  Assert.Hidden("Status · Priority · Assignee");
  Ui.NewDevice();
  Ui.Visit("/");                      // a new browser — the initializer
  Assert.Visible("Status · Priority · Assignee");
}
```

## See also   {#see-also}
- [component](https://osysharp.com/reference/ui/component/) — component fields, which `[Remembered]` is one kind of
- [RailShell](https://osysharp.com/reference/ui/shell-rail/) — the rail whose widened state the kit remembers
- [store](https://osysharp.com/reference/ui/store/) — state shared by every component, which is not remembered
- [Visitor](https://osysharp.com/reference/ui/visitor/) — who this browser is before it is anybody
