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:
| 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?#
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");
}