Fix get_resource/transition_answer: scope feature lookup to its own alternative
Deploy / deploy (push) Successful in 28s

Both resolved a feature by name flattened across every alternative on
the question, not scoped to the one the caller actually meant - fine
when every question had exactly one alternative with a resource
feature, silently wrong now that /review has three (Applicants,
Inquiries, Subscribers all use the same empty feature name). Every
lookup always resolved to the first alternative's feature - Subscribers
was reading the (empty) Applicants bucket instead of its own, and
Invite/Decline/Mark-handled buttons would have had the same problem
had two of those transitions ever been clicked side by side.

Threaded a new `alternative` parameter through get_resource,
transition_answer, and their client-side callers
(ResourceFeature/ResourceValue/AnswerRow) - the fix lives entirely in
the already-generic resource-fetching machinery, no per-alternative
special-casing.
This commit is contained in:
Bendik Aagaard Lynghaug
2026-08-05 15:04:02 +02:00
parent c73981da2d
commit 6df895b1d3
3 changed files with 41 additions and 7 deletions
+17 -4
View File
@@ -8,12 +8,14 @@
use leptos::prelude::*;
/// Fetches the live data for `question_id`'s `feature_name` feature.
/// Fails closed: a resource with neither `public: true` nor
/// `requires_group` set is unreachable, not "open" by omission.
/// Fetches the live data for `question_id`'s `alternative`'s
/// `feature_name` feature. Fails closed: a resource with neither
/// `public: true` nor `requires_group` set is unreachable, not "open"
/// by omission.
#[server]
pub async fn get_resource(
question_id: String,
alternative: String,
feature_name: String,
) -> Result<serde_json::Value, ServerFnError> {
use crate::auth::{User, SESSION_USER_KEY};
@@ -26,10 +28,21 @@ pub async fn get_resource(
.get(&question_id)
.cloned()
.ok_or_else(|| ServerFnError::new("unknown question"))?;
// Scoped to the named alternative first, not flattened across all
// of them - a feature name (often just "") is only unique within
// its own alternative, not across a whole question. Flattening
// silently resolved every same-named feature to whichever
// alternative happened to be first, so "Subscribers" (and any
// other later resource-listing alternative sharing an unnamed
// feature with an earlier one on the same question) always read
// the first alternative's bucket instead of its own.
let feature = question
.alternatives
.iter()
.flat_map(|a| &a.features)
.find(|a| a.name == alternative)
.ok_or_else(|| ServerFnError::new("unknown alternative"))?
.features
.iter()
.find(|f| f.name == feature_name)
.ok_or_else(|| ServerFnError::new("unknown feature"))?;
let resource = feature