# A failing query

> When a query a region reads fails or is refused, that region shows the failure in place — the server's own sentence, the correlation id to look it up by, and a Retry — instead of rendering as empty. It is automatic: you write nothing. The rest of the page keeps working, because only the data is missing.

<!-- id: ui-query-failure · area: ui · stability: stable · html: https://osysharp.com/reference/ui/query-failure/ -->

## Summary        {#summary}
When a region reads a query that **fails** — the server refused it, the request errored, the connection dropped — that
region renders the failure **where the rows would have been**. You write nothing; any `foreach` over a query member gets
it.

It exists because the alternative is silence. A failed query has no rows, and a region with no rows renders as *empty* —
so a table the user is not allowed to see looked exactly like a table with nothing in it. That is not a small confusion:
it is the difference between "there are no expenses" and "you were refused", and the user cannot tell which they are
looking at.

The failure is **in-page**, not a whole-page error screen. The page rendered fine and its other controls still work, so
replacing all of it would misdescribe what broke: only the data is missing. A page whose other half is a form the user
was half-way through filling in must not lose that form because a list beside it failed to load.

For a page that fails to **load at all** — no data, no render — the app's error surface is the right thing; that is a
different situation with a different answer. For the *waiting* state before an answer arrives, see [Pending](https://osysharp.com/reference/ui/pending/).

## Signature      {#signature}
```osy syntax
// Automatic — no code. A `foreach` over a query whose fetch failed renders the failure in place of its rows.
```

The rendered affordance, for styling:

| Class | What it is |
|---|---|
| `.osy-query-failed` | the container (carries `role="alert"`) |
| `.osy-query-failed-message` | the server's sentence, or the transport error when there is none |
| `.osy-query-failed-correlation` | the correlation id — present only when the failure carried one |
| `.osy-query-failed-retry` | the Retry button |

## Examples       {#examples}
Nothing here opts in — the `foreach` is ordinary, and the affordance appears only if the read is refused or errors:

```osy title="a list that reports its own refusal" test app=ui-query-failure
[Principal] entity User { [Required] string Email; }

entity ExpenseLine {
  [Required] string Description;
  decimal Amount;
  security { allow read where CreatedBy == user.Id; }   // a refusal here is what the affordance reports
}

[Page("/expenses")] [Render(CSR)]
component Expenses() {
  live var lines = ExpenseLine.OrderBy(l => l.Description).ToList();
  render {
    Stack(gap: 2) {
      // No failure handling written here: if this query is refused, the failure renders in place of the rows.
      foreach (var l in lines) { Text(l.Description); }
    }
  }
}
```

## Description    {#description}
**What it says.** The message prefers the **server's own sentence** over the transport's. A refusal that says *"You do
not have access to these lines."* is worth showing; `GET /query/… failed: 403` tells the user nothing they can act on.
When the failure carries no sentence of its own — a dropped connection, a DNS failure — the transport's message is shown
instead, because something specific always beats a blank box.

**The correlation id** is the id to run `osy logs --correlation <id>` with. It appears when the failure carried one, so
whoever hit the problem can report *which* failure they hit. A failure a user cannot report is most of the way to a
failure nobody can fix.

**Retry** re-runs that query and nothing else. On success the affordance disappears and the rows render through the
ordinary path; on a second failure it stays, with whatever the server said this time. Retrying does not reload the page
or re-run the page's other queries.

**It is entirely client-side, and it is mechanism.** The platform draws a plain container with `osy-` classes — the same
arrangement as the built-in `.osy-spinner` — and an app restyles it in its own CSS alongside every other control state.

**Scope.** It covers a region reading a query member: a `foreach` over one, including an inline query. A query that
succeeds renders no affordance at all.

## Examples       {#examples}
The common case is **nothing** — this is automatic:
```osy title="only the failed region reports it — the rest keeps working" syntax
component ReportDetail(ExpenseReport report) {
  query lines = ExpenseLine.Where(l => l.Report == report);
  render {
    Text(report.Title);              // still renders if `lines` fails
    foreach (var l in lines) {       // if the query is refused, THIS region shows why, with a Retry
      Text(l.Merchant);
    }
    Button("Save", onPress: Save);   // still works
  }
}
```

Restyling it to match your app:
```css
.osy-query-failed {
  display: flex; align-items: center; gap: .75rem;
  padding: .75rem 1rem; border: 1px solid var(--danger-border); border-radius: 6px;
}
.osy-query-failed-correlation { font: 12px/1 monospace; opacity: .6; }
```

## See also       {#see-also}
- [Pending](https://osysharp.com/reference/ui/pending/) — the *waiting* state: the automatic per-control spinner, `save.Pending`, and the page-wide `Pending`
  ambient.
- [Connection](https://osysharp.com/reference/ui/connection/) — the sibling case where the server itself became unreachable, which is a whole-page condition.
- [public pages (what a signed-out visitor can see and do)](https://osysharp.com/reference/security/public-reads/) — what decides whether a query is refused in the first place.
