# UI surfaces (app.Ui)

> `app.Ui` nominates the app's own components for the "system surfaces" the platform would otherwise render a bare fallback for — the connection-loss overlay, the not-found (404) page, the forbidden (403) page, and the error page — and tunes the automatic busy indicator. A singleton; the `Ui` block groups these so more can be added without a new setting each time.

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

## Summary        {#summary}
`app.Ui` is where you hand the platform your **own** components for the *system surfaces* it would otherwise draw a
bare fallback for — the "something happened, here's the app's branded page" moments. It is a **singleton** — one UI
configuration per app — and it groups these overrides under one `Ui` block, so an app opts into each surface it wants to
own and inherits the platform default for the rest.

```osy syntax
app.Ui = new AppUi {
  ConnectionSurface = OfflineOverlay,   // shown when the server drops mid-session
  NotFoundSurface   = NotFoundPage,     // shown (HTTP 404) when no route matches the address
  ForbiddenSurface  = ForbiddenPage,    // shown when a signed-in user is refused a page (403)
  ErrorSurface      = ErrorPage,        // shown when a page fails to load unexpectedly
};
```

## Signature      {#signature}
```osy
app.Ui = new AppUi {
  ConnectionSurface = OfflineOverlay,   // the connection-loss overlay
  NotFoundSurface   = NotFoundPage,     // the not-found (404) page
  ForbiddenSurface  = ForbiddenPage,    // the forbidden (403) page
  ErrorSurface      = ErrorPage,        // the error page
};
```

`app.Ui` is a single value, not a list. Each member names a component **by name** — the same name you gave it in its
`component X { … }` declaration.

## Description    {#description}
An `AppUi` has these members (each optional — omit one, or omit `app.Ui` entirely, to keep the platform default):

- **`ConnectionSurface`** — the component the platform mounts, as a fixed overlay over the current page, when the
  browser loses its link to the server. It reads the [Connection](https://osysharp.com/reference/ui/connection/) ambient (`Connection.State`, `Connection.Attempts`)
  and calls `Connection.Retry()` / `Connection.Reload()`. Because it has to render **with the server gone**, it must be
  self-contained — built from the [component](https://osysharp.com/reference/ui/component/) built-in elements, with no child components to fetch and no data to
  load. See [Connection](https://osysharp.com/reference/ui/connection/) for the full surface and its constraints.
- **`NotFoundSurface`** — the page the platform serves, **with HTTP status 404**, when a visitor hits an address the app
  doesn't route. The server sends your app's shell with this component as the page (so the 404 keeps the correct status
  for crawlers and monitoring, but the *body* is your branded page instead of plain text) and the client renders it.
  Unlike the connection surface, a 404 means the server **answered**, so this page may use anything the app has. Mark it
  `[AllowAnonymous]` so a bad URL renders it for anyone rather than bouncing a signed-out visitor to login.
- **`ForbiddenSurface`** — the page shown when a **signed-in** user is refused a page they aren't permitted to see (a
  403). The platform mounts it in place of the built-in access-denied surface.
- **`ErrorSurface`** — the page shown when a page fails to load for an **unexpected** reason, in place of the built-in
  "couldn't load" surface.

Three more members tune the **busy indicator** — the automatic spinner the platform shows while an action is in flight
(see [Pending](https://osysharp.com/reference/ui/pending/) for the full surface, including the `Pending` ambient):

- **`PendingIndicator`** — your own component for the global busy affordance, in place of the built-in top progress bar.
- **`PendingDelayMs`** — how long an action must run before the indicator appears (so an instant action never flashes
  one); `0` uses the platform default.
- **`PendingMinShowMs`** — once shown, the minimum time the indicator stays up, so it can't blink off; `0` uses the
  default.

The forbidden and error surfaces render at *boot-failure* time — a refusal or a failure that can happen **before the app
has a working session, or while the server is unreliable**. So the platform **inlines their trees into the page** up
front, and the client renders them with no further request. That is what makes them work when nothing else does — and it
puts the same constraint on them as the connection surface: they must be **self-contained**, built from the
[component](https://osysharp.com/reference/ui/component/) built-in elements (with `[Composable]` children bundled), carrying no data. Offer a way out with plain
`Link`s (sign in as a different account, go home). Mark each `[AllowAnonymous]`.

Each nominated component must exist, and none is a routed page — the platform serves or mounts each by name for its
occasion, so none needs a `[Page("…")]`.

`app.Ui` is a **singleton**: an app declares it once. Removing a member reverts that surface to the platform default.

> Sign-in is deliberately **not** part of this block — it is a real *flow*, declared via
> `app.AuthBootstrap { LoginPage = … }`, not a passive surface.

## Examples       {#examples}
Declare the surfaces, then nominate them:

```osy test app=ui-connection-and-notfound-surfaces
[AllowAnonymous]
component OfflineOverlay() {
  action Retry() { Connection.Retry(); }
  render {
    if (Connection.State == ConnState.Lost) {
      Row(align: Align.Center, justify: Justify.Center, position: Position.Fixed, inset: 0, bg: "#1a1a1a") {
        Text("Can't reach the server");
      }
    }
  }
}

[AllowAnonymous]
component NotFoundPage() {
  render {
    Row(align: Align.Center, justify: Justify.Center, minH: "100vh", bg: Colors.Surface) {
      Stack(gap: 3, align: Align.Center) {
        Text("Page not found");
        Link(href: "/") { Text("Go home"); }
      }
    }
  }
}

app.Ui = new AppUi {
  ConnectionSurface = OfflineOverlay,
  NotFoundSurface   = NotFoundPage,
};
```

## See also       {#see-also}
- [Connection](https://osysharp.com/reference/ui/connection/) — the `Connection` ambient the connection surface reads, and its offline-render constraint.
- [Pending](https://osysharp.com/reference/ui/pending/) — the automatic busy indicator the `Pending*` members tune, and the `Pending` ambient.
- [component](https://osysharp.com/reference/ui/component/) — components, `render` blocks, and the built-in elements a surface is built from.
