Transitions now declare which state they fire from (default "open", today's implicit behavior - existing content needs no edits). AnswerRow renders only the buttons legal from a row's current state; two declared transitions may share a target (open -> declined, in_dialogue -> declined), so apply_transition matches on (from, to) against the row's actual state instead of the old blanket "already decided" check. validate_questions/question_lint reject a from naming an undeclared state or a from -> to pair the bucket's graph never declared. store_answer's KV projection now starts a record at its schema's declared initial state, not a hardcoded "open" - organizations begin at "prospect", and the read model has to agree with the event log about that. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
35663a28c1
commit
40694a8f3f
+24
-7
@@ -1183,7 +1183,24 @@ fn AnswerRow(
|
||||
pending_transitions: RwSignal<std::collections::HashMap<(String, String), String>>,
|
||||
transition_batch: ServerAction<TransitionAnswers>,
|
||||
) -> impl IntoView {
|
||||
let is_open = answer.state == crate::answers::OPEN_STATE;
|
||||
// Only the transitions legal from this row's current state - two
|
||||
// declared transitions may share a target (open -> declined and
|
||||
// in_dialogue -> declined), each rendered only on rows actually in
|
||||
// its `from` state.
|
||||
let available: Vec<Transition> = transitions
|
||||
.iter()
|
||||
.filter(|t| t.from == answer.state)
|
||||
.cloned()
|
||||
.collect();
|
||||
// "Decided" (dimmed) means this row has no legal move here. A
|
||||
// read-only listing (no transitions declared at all) keeps the old
|
||||
// rule - dim anything past `open` - since "no legal move" would
|
||||
// dim every row indiscriminately.
|
||||
let decided = if transitions.is_empty() {
|
||||
answer.state != crate::answers::OPEN_STATE
|
||||
} else {
|
||||
available.is_empty()
|
||||
};
|
||||
let responses = answer
|
||||
.responses
|
||||
.as_object()
|
||||
@@ -1195,7 +1212,7 @@ fn AnswerRow(
|
||||
let key = (feature_name.clone(), item_id.clone());
|
||||
|
||||
view! {
|
||||
<article class="answer-row" class:decided=move || !is_open>
|
||||
<article class="answer-row" class:decided=move || decided>
|
||||
<div class="answer-fields">
|
||||
<For
|
||||
each={
|
||||
@@ -1220,19 +1237,19 @@ fn AnswerRow(
|
||||
<span class="answer-time">{format_ms(submitted_ms)}</span>
|
||||
</div>
|
||||
<Show when={
|
||||
let transitions = transitions.clone();
|
||||
move || is_open && !transitions.is_empty()
|
||||
let available = available.clone();
|
||||
move || !available.is_empty()
|
||||
}>
|
||||
{
|
||||
let key = key.clone();
|
||||
let transitions = transitions.clone();
|
||||
let available = available.clone();
|
||||
view! {
|
||||
<div class="answer-actions">
|
||||
<div class="select-options">
|
||||
<For
|
||||
each={
|
||||
let transitions = transitions.clone();
|
||||
move || transitions.clone()
|
||||
let available = available.clone();
|
||||
move || available.clone()
|
||||
}
|
||||
key=|t| t.to.clone()
|
||||
children=move |t: Transition| {
|
||||
|
||||
Reference in New Issue
Block a user