4 Commits
Author SHA1 Message Date
Bendik Aagaard Lynghaug 7ba9b08d76 Release 0.3.4
Test / test (push) Successful in 24s
Publish release / publish (push) Successful in 1m27s
2026-08-25 20:25:52 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 1a45839699 Alternative.disabled: announced but not yet takeable
Test / test (push) Successful in 24s
The button renders disabled (title 'Not yet') and submit_answer
refuses the alternative server-side - lysbue's disabled flag, back as
content, for advertising a path before it works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 20:25:49 +02:00
Bendik Aagaard Lynghaug be54f54804 Release 0.3.3
Test / test (push) Successful in 24s
Publish release / publish (push) Successful in 1m26s
2026-08-25 20:09:00 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 8212477377 Empty list says so before it can parse as zero answers
Test / test (push) Successful in 24s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 20:08:59 +02:00
5 changed files with 34 additions and 6 deletions
Generated
+1 -1
View File
@@ -2948,7 +2948,7 @@ dependencies = [
[[package]] [[package]]
name = "portal" name = "portal"
version = "0.3.2" version = "0.3.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "portal" name = "portal"
version = "0.3.2" version = "0.3.4"
edition = "2021" edition = "2021"
[lib] [lib]
+20 -4
View File
@@ -1177,7 +1177,17 @@ fn AlternativeCard(
children=move |e| view! { <p class="alt-encouragement">{e}</p> } children=move |e| view! { <p class="alt-encouragement">{e}</p> }
/> />
</div> </div>
{if is_gateway { {if alternative.disabled {
// Advertised, not yet takeable: the label
// stays (it says what will be possible),
// the button doesn't act.
view! {
<button type="button" class="alt-submit soon" disabled=true title="Not yet">
{button_label.clone()}
</button>
}
.into_any()
} else if is_gateway {
let href = alternative.action.clone().unwrap_or_default(); let href = alternative.action.clone().unwrap_or_default();
view! { view! {
<a class="alt-submit" href=href>{button_label.clone()}</a> <a class="alt-submit" href=href>{button_label.clone()}</a>
@@ -1300,6 +1310,12 @@ fn ResourceValue(
return view! { <p class="resource-empty">{empty}</p> }.into_any(); return view! { <p class="resource-empty">{empty}</p> }.into_any();
} }
if let serde_json::Value::Array(items) = &value { if let serde_json::Value::Array(items) = &value {
// Before the Answer parse below: an empty list deserializes as
// an (empty) Vec<Answer> too, and rendered as a blank
// answer-list instead of saying anything.
if items.is_empty() {
return view! { <p class="resource-empty">{empty}</p> }.into_any();
}
if let Ok(answers) = serde_json::from_value::<Vec<Answer>>(value.clone()) { if let Ok(answers) = serde_json::from_value::<Vec<Answer>>(value.clone()) {
let mut answers = answers; let mut answers = answers;
answers.sort_by(|a, b| { answers.sort_by(|a, b| {
@@ -1332,9 +1348,6 @@ fn ResourceValue(
} }
.into_any(); .into_any();
} }
if items.is_empty() {
return view! { <p class="resource-empty">{empty}</p> }.into_any();
}
// A list of plain objects (e.g. a jq-shaped GiteaStarred/Url // A list of plain objects (e.g. a jq-shaped GiteaStarred/Url
// resource) - render as cards rather than dumping raw JSON. // resource) - render as cards rather than dumping raw JSON.
// Generic on purpose, no content-declared "kind": `name`/`title` // Generic on purpose, no content-declared "kind": `name`/`title`
@@ -1857,6 +1870,9 @@ pub async fn submit_answer(
.iter() .iter()
.find(|a| a.name == alternative) .find(|a| a.name == alternative)
.ok_or_else(|| ServerFnError::new("unknown alternative"))?; .ok_or_else(|| ServerFnError::new("unknown alternative"))?;
if alt.disabled {
return Err(ServerFnError::new("this alternative isn't open yet"));
}
let next = alt.action.clone(); let next = alt.action.clone();
let record_as = alt.record_as.clone(); let record_as = alt.record_as.clone();
+5
View File
@@ -111,6 +111,11 @@ pub struct Alternative {
pub description: String, pub description: String,
#[serde(default)] #[serde(default)]
pub action: Option<String>, pub action: Option<String>,
/// Shown but not takeable yet - the button renders disabled and the
/// server refuses the submission. For advertising a path before it
/// works (lysbue had the same flag).
#[serde(default)]
pub disabled: bool,
#[serde(default)] #[serde(default)]
pub consequence: Vec<String>, pub consequence: Vec<String>,
#[serde(default)] #[serde(default)]
+7
View File
@@ -675,6 +675,13 @@ textarea:focus {
cursor: wait; cursor: wait;
} }
/* A content-disabled alternative (`disabled: true`): announced, not
yet takeable - reads as a promise, not as a stuck form. */
.alt-submit.soon:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.gate-card { .gate-card {
text-align: center; text-align: center;
} }