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.
Signature#
// 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#
Nothing here opts in — the foreach is ordinary, and the affordance appears only if the read is refused or errors:
[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#
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#
The common case is nothing — this is automatic:
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:
.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#
- Pending — the waiting state: the automatic per-control spinner,
save.Pending, and the page-widePendingambient. - 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) — what decides whether a query is refused in the first place.