Requirement gains an optional `resource` (reuses ResourceSpec/ ResourceSource/jq wholesale - a resource is a resource whether it's displayed read-only or offered as choices to pick from) and `id_field` (which field in each item is its stable id, defaults to _id then id). `type: select` + `multiple` (already-existing field, previously file -only) picks single vs multi. New get_requirement_options server fn shares its auth/fetch/jq logic with get_resource via two extracted helpers rather than duplicating it. Submitted value is the selected id (single) or a JSON array of ids (multi) - a new select_field_map (RwSignal<Vec<String>>, alongside the existing field_map/file_refs maps, since a multi-select's value is a set, not a string) threaded through the same nested <For> structure the other requirement kinds already use. Content validation extended: a `type: select` requirement declaring no resource now fails at load time instead of rendering a dead field. Caught a real bug in my own first version of that check while testing it - it was nested inside a feature-level resource guard, so it never ran unless the *feature* also happened to have its own resource. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
43fa496778
commit
755b796ad1
+25
-1
@@ -209,13 +209,29 @@ pub struct Requirement {
|
||||
pub kind: String,
|
||||
#[serde(default)]
|
||||
pub optional: bool,
|
||||
/// `type: file` only - accept multiple files.
|
||||
/// `type: file` - accept multiple files. `type: select` - pick more
|
||||
/// than one option (checkbox-style toggle) instead of exactly one
|
||||
/// (radio-style); the submitted value is a JSON array of ids
|
||||
/// instead of a single id string.
|
||||
#[serde(default)]
|
||||
pub multiple: bool,
|
||||
/// `type: file` only - HTML `accept` hint (UX only, not a security
|
||||
/// boundary - the upload handler re-checks content-type itself).
|
||||
#[serde(default)]
|
||||
pub accept: Option<String>,
|
||||
/// `type: select` only - where the selectable options come from.
|
||||
/// Reuses the exact same `ResourceSpec`/`ResourceSource`/`jq`
|
||||
/// mechanism a `Feature.resource` uses (`resource::get_requirement_options`)
|
||||
/// - a resource is a resource regardless of whether it's displayed
|
||||
/// read-only or offered as choices to pick from.
|
||||
#[serde(default)]
|
||||
pub resource: Option<ResourceSpec>,
|
||||
/// `type: select` only - which field in each resource item is that
|
||||
/// option's stable identifier, submitted as the requirement's value
|
||||
/// (or one entry of it, if `multiple`). Defaults to trying `_id`
|
||||
/// then `id` if unset.
|
||||
#[serde(default)]
|
||||
pub id_field: Option<String>,
|
||||
}
|
||||
|
||||
fn default_requirement_type() -> String {
|
||||
@@ -372,6 +388,14 @@ pub fn validate_questions(
|
||||
}
|
||||
}
|
||||
for feature in &alternative.features {
|
||||
for requirement in &feature.requirements {
|
||||
if requirement.kind == "select" && requirement.resource.is_none() {
|
||||
anyhow::bail!(
|
||||
"question {:?} alternative {:?} feature {:?}: requirement {:?} is type: select but declares no resource to select from",
|
||||
question.id, alternative.name, feature.name, requirement.name
|
||||
);
|
||||
}
|
||||
}
|
||||
let Some(resource) = &feature.resource else {
|
||||
continue;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user