A page says nothing until it has something to say
The Suspense fallback on a question is the whole served page until the question resolves - no header, no wordmark - so the word "loading…" was the first and only thing a visitor or a crawler read about the site. True, and not about them. It holds its space and stays quiet now; `aria-busy` says what is happening to a reader that would otherwise find an empty document. Same for the two resource slots inside a page, which keep a line's worth of room. Also gone: the `gitea_org_repos` resource kind, which no site ever named. `gitea_starred` and `gitea_releases` stay. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f18d1a5181
commit
9891c462c6
@@ -12,6 +12,8 @@ content repos pin the tag they run (see the end of this file).
|
|||||||
- A state can grant a group: `grants: <group>` beside its `event`, so an approval that means membership does not also need somebody to remember to invite them afterwards. The lint requires the forms that fill such a bucket to collect a required name and email, because by the time a desk presses approve there is nobody left to ask.
|
- A state can grant a group: `grants: <group>` beside its `event`, so an approval that means membership does not also need somebody to remember to invite them afterwards. The lint requires the forms that fill such a bucket to collect a required name and email, because by the time a desk presses approve there is nobody left to ask.
|
||||||
- A state can be moved by a clock: `after: { days, to }`, swept hourly, measured from the record's last move (`src/deadlines.rs`). A deadline may only do what a person could have done - `after.to` is held to the same declared edge as a desk button - and the move fires the same mail and publishes the same event, stamped `deadline`.
|
- A state can be moved by a clock: `after: { days, to }`, swept hourly, measured from the record's last move (`src/deadlines.rs`). A deadline may only do what a person could have done - `after.to` is held to the same declared edge as a desk button - and the move fires the same mail and publishes the same event, stamped `deadline`.
|
||||||
- Documentation moved out of the README into `docs/`: architecture, content reference, state machines, access, operations.
|
- Documentation moved out of the README into `docs/`: architecture, content reference, state machines, access, operations.
|
||||||
|
- A page says nothing until it has something to say. The Suspense fallback on a question was the whole served page - no header, no wordmark - so the first and only thing a visitor or a crawler read was the word "loading…", which is true and is not about them. It now holds the space and stays quiet, with `aria-busy` for a reader that would otherwise find an empty document.
|
||||||
|
- Gone: the `gitea_org_repos` resource kind, which no site ever named. `gitea_starred` and `gitea_releases` stay.
|
||||||
|
|
||||||
## 0.4.0 (2026-09-24)
|
## 0.4.0 (2026-09-24)
|
||||||
|
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ reads is never a client-supplied parameter.
|
|||||||
- { from: complete, to: on_hearing, label: Send it out }
|
- { from: complete, to: on_hearing, label: Send it out }
|
||||||
```
|
```
|
||||||
|
|
||||||
`source.kind` is one of `kv`, `gitea_starred`, `gitea_org_repos`,
|
`source.kind` is one of `kv`, `gitea_starred`,
|
||||||
`gitea_releases`, `url`.
|
`gitea_releases`, `url`.
|
||||||
|
|
||||||
A spec with neither `public: true` nor `requires_group` is
|
A spec with neither `public: true` nor `requires_group` is
|
||||||
|
|||||||
+11
-7
@@ -244,12 +244,16 @@ fn QuestionPage() -> impl IntoView {
|
|||||||
let site = expect_context::<Resource<Result<SiteConfig, ServerFnError>>>();
|
let site = expect_context::<Resource<Result<SiteConfig, ServerFnError>>>();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
// Nothing is written here on purpose. This fallback is the
|
||||||
|
// whole served page until the question resolves - no header,
|
||||||
|
// no wordmark - so any word in it is the first and only thing
|
||||||
|
// a visitor, or a crawler, reads about the site. It used to
|
||||||
|
// say "loading…", which is true and is not about them. The
|
||||||
|
// space is held so the page does not jump when the words
|
||||||
|
// arrive, and `aria-busy` says what is happening to anyone
|
||||||
|
// whose reader would otherwise find an empty document.
|
||||||
<Suspense fallback=|| {
|
<Suspense fallback=|| {
|
||||||
view! {
|
view! { <main class="loading" aria-busy="true"></main> }
|
||||||
<main class="loading">
|
|
||||||
<p>"loading…"</p>
|
|
||||||
</main>
|
|
||||||
}
|
|
||||||
}>
|
}>
|
||||||
{move || {
|
{move || {
|
||||||
// Both read unconditionally (not nested inside the match
|
// Both read unconditionally (not nested inside the match
|
||||||
@@ -1620,7 +1624,7 @@ fn ResourceFeature(
|
|||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="resource">
|
<div class="resource">
|
||||||
<Suspense fallback=|| view! { <p class="resource-loading">"loading…"</p> }>
|
<Suspense fallback=|| view! { <p class="resource-loading" aria-busy="true"></p> }>
|
||||||
{move || {
|
{move || {
|
||||||
let feature_name = feature_name.clone();
|
let feature_name = feature_name.clone();
|
||||||
let transitions = transitions.clone();
|
let transitions = transitions.clone();
|
||||||
@@ -1877,7 +1881,7 @@ fn SelectField(
|
|||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="select-field">
|
<div class="select-field">
|
||||||
<Suspense fallback=|| view! { <p class="resource-loading">"loading…"</p> }>
|
<Suspense fallback=|| view! { <p class="resource-loading" aria-busy="true"></p> }>
|
||||||
{move || {
|
{move || {
|
||||||
let id_field = id_field.clone();
|
let id_field = id_field.clone();
|
||||||
data.get()
|
data.get()
|
||||||
|
|||||||
@@ -446,7 +446,6 @@ pub struct ResourceSpec {
|
|||||||
pub enum ResourceSource {
|
pub enum ResourceSource {
|
||||||
Kv { bucket: String },
|
Kv { bucket: String },
|
||||||
GiteaStarred { username: String },
|
GiteaStarred { username: String },
|
||||||
GiteaOrgRepos { org: String },
|
|
||||||
/// A repo's releases - fetched with `GITEA_API_TOKEN`, so it works
|
/// A repo's releases - fetched with `GITEA_API_TOKEN`, so it works
|
||||||
/// for private repos too (shape the link with jq: a private
|
/// for private repos too (shape the link with jq: a private
|
||||||
/// release's html_url 404s for anonymous visitors, so map it to
|
/// release's html_url 404s for anonymous visitors, so map it to
|
||||||
@@ -466,7 +465,6 @@ impl ResourceSpec {
|
|||||||
match &self.source {
|
match &self.source {
|
||||||
ResourceSource::Kv { bucket } => Some(bucket),
|
ResourceSource::Kv { bucket } => Some(bucket),
|
||||||
ResourceSource::GiteaStarred { .. }
|
ResourceSource::GiteaStarred { .. }
|
||||||
| ResourceSource::GiteaOrgRepos { .. }
|
|
||||||
| ResourceSource::GiteaReleases { .. }
|
| ResourceSource::GiteaReleases { .. }
|
||||||
| ResourceSource::Url { .. } => None,
|
| ResourceSource::Url { .. } => None,
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -13,7 +13,7 @@ use leptos::prelude::*;
|
|||||||
/// `public: true` nor `requires_group` set is unreachable, not "open"
|
/// `public: true` nor `requires_group` set is unreachable, not "open"
|
||||||
/// by omission. `params` are the requesting form's current field
|
/// by omission. `params` are the requesting form's current field
|
||||||
/// values (if any) - forwarded as query parameters to a live external
|
/// values (if any) - forwarded as query parameters to a live external
|
||||||
/// fetch (`GiteaStarred`/`GiteaOrgRepos`/`Url`), the mechanism behind
|
/// fetch (`GiteaStarred`/`GiteaReleases`/`Url`), the mechanism behind
|
||||||
/// "a resource parameterized by other form fields"; a `Kv` resource
|
/// "a resource parameterized by other form fields"; a `Kv` resource
|
||||||
/// ignores them entirely, same as today.
|
/// ignores them entirely, same as today.
|
||||||
#[server(endpoint = "get_resource")]
|
#[server(endpoint = "get_resource")]
|
||||||
@@ -295,9 +295,6 @@ async fn fetch_resource_value(
|
|||||||
ResourceSource::GiteaStarred { username } => {
|
ResourceSource::GiteaStarred { username } => {
|
||||||
fetch_gitea_json(state, &format!("/api/v1/users/{username}/starred"), params).await?
|
fetch_gitea_json(state, &format!("/api/v1/users/{username}/starred"), params).await?
|
||||||
}
|
}
|
||||||
ResourceSource::GiteaOrgRepos { org } => {
|
|
||||||
fetch_gitea_json(state, &format!("/api/v1/orgs/{org}/repos"), params).await?
|
|
||||||
}
|
|
||||||
ResourceSource::GiteaReleases { owner, repo } => {
|
ResourceSource::GiteaReleases { owner, repo } => {
|
||||||
fetch_gitea_json(state, &format!("/api/v1/repos/{owner}/{repo}/releases"), params)
|
fetch_gitea_json(state, &format!("/api/v1/repos/{owner}/{repo}/releases"), params)
|
||||||
.await?
|
.await?
|
||||||
|
|||||||
@@ -1227,3 +1227,10 @@ textarea:focus {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: var(--ink-dim);
|
color: var(--ink-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A loading slot holds its line's worth of space and says nothing.
|
||||||
|
The words that belong here have not arrived yet, and "loading…" is
|
||||||
|
not one of them. */
|
||||||
|
.resource-loading {
|
||||||
|
min-height: 1.4em;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user