Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

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

[Remembered] bool collapsed = false;

`[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.

preview1 example compiled by CIuistatecomponent

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#

[Remembered] bool collapsed = false;
[Remembered] string tab = "Conversation";
[Remembered] Density density = Density.Comfortable;

Description#

What is it kept per?#

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

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:

refusedwhy
a row, a list, a classbrowser 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
constit never changes
a field of a storea 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?#

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

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#

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#

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.

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

  • component — component fields, which [Remembered] is one kind of
  • RailShell — the rail whose widened state the kit remembers
  • store — state shared by every component, which is not remembered
  • Visitor — who this browser is before it is anybody

Related

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

RailShell

An app shell built around a permanently narrow icon rail, in the shape Slack, Linear and Discord converge on. Every row…

store

`store` declares a headless reactive unit — the same members a component has, minus `render` — meant to hold UI state…

Visitor

`Visitor.Id` names the browser someone is using. The platform issues it on their first visit, proves it on every…