Files
ARCADIA-Studio/crates/oa-desktop/src/semantic_review/row.rs
T

931 lines
37 KiB
Rust

//! Independent authored row; session owns planning and canonical references.
use super::*;
pub struct ReviewRow {
pub(super) base_identity: Id,
pub(super) base_fingerprint: String,
pub anchor: Id,
pub trace: Option<Id>,
pub direction: TransitionDirection,
pub facts: Vec<FacetDrift>,
pub selected: Option<FacetSelector>,
pub decisions: BTreeMap<FacetSelector, SemanticDecision>,
pub references: BTreeMap<Id, Id>,
pub operation_omission: Option<String>,
pub resets: BTreeSet<FacetSelector>,
pub staged: BTreeSet<FacetSelector>,
pub presence: BTreeMap<FacetSelector, bool>,
pub row_drafts: BTreeMap<FacetSelector, keyed_rows::RowDraft>,
pub order_drafts: BTreeMap<OrderedCollection, Vec<Id>>,
pub attachments: attachments::Editor,
pub artifacts: artifacts::Editor,
pub annotation_oa: keyed_rows::AddressDraft,
pub annotation_sa: keyed_rows::AddressDraft,
pub member_family: keyed::Family,
pub member_oa: Option<Choice>,
pub member_sa: Option<Choice>,
pub literal: String,
pub literal_oa: bool,
pub literal_sa: bool,
pub draft: Option<Draft>,
// Each closed constructor has an independent draft, including nested kinds.
// Scalar and sibling drafts survive decision and constructor changes.
pub group_drafts: BTreeMap<FacetSelector, Draft>,
pub plan: Option<SemanticTransitionPlan>,
pub proposed: Option<Project>,
pub error: Option<String>,
pub tab: Tab,
pub show_unchanged: bool,
pub reference_oa: Option<Choice>,
pub reference_sa: Option<Choice>,
pub reference_search: String,
pub(super) accepted_impacts: Vec<SemanticImpact>,
}
impl ReviewRow {
pub fn new(project: &Project, selected: Id) -> Result<Self, String> {
let explicit = project.traces.get(&selected);
let anchor = explicit.map_or(selected, |trace| trace.oa);
let layer = project
.layer_of(anchor)
.ok_or("Select an OA or SA object, relationship or trace to review.")?;
let choices: Vec<_> = project
.traces
.values()
.filter(|t| t.oa == anchor || t.sa == anchor)
.collect();
if choices.is_empty() {
return Err("This object has no trace. Create a counterpart or link existing counterparts first.".into());
}
let trace = explicit
.map(|t| t.id)
.or_else(|| (choices.len() == 1).then(|| choices[0].id));
let mut state = Self {
base_identity: project.id,
base_fingerprint: project_fingerprint(project),
anchor,
trace,
direction: if layer == Layer::Oa {
TransitionDirection::OaToSa
} else {
TransitionDirection::SaToOa
},
facts: vec![],
selected: None,
decisions: BTreeMap::new(),
references: BTreeMap::new(),
operation_omission: None,
resets: BTreeSet::new(),
staged: BTreeSet::new(),
presence: BTreeMap::new(),
row_drafts: BTreeMap::new(),
order_drafts: BTreeMap::new(),
attachments: Default::default(),
artifacts: Default::default(),
annotation_oa: Default::default(),
annotation_sa: Default::default(),
member_family: keyed::Family::Literal,
member_oa: None,
member_sa: None,
literal: String::new(),
literal_oa: true,
literal_sa: true,
draft: None,
group_drafts: BTreeMap::new(),
plan: None,
proposed: None,
error: None,
tab: Tab::Facts,
show_unchanged: false,
reference_oa: None,
reference_sa: None,
reference_search: String::new(),
accepted_impacts: vec![],
};
state.load_trace(project);
Ok(state)
}
pub fn target(&self, project: &Project) -> Option<Id> {
let trace = project.traces.get(&self.trace?)?;
Some(if self.direction == TransitionDirection::OaToSa {
trace.sa
} else {
trace.oa
})
}
pub(super) fn load_trace(&mut self, project: &Project) {
self.facts.clear();
self.decisions.clear();
self.resets.clear();
self.staged.clear();
self.presence.clear();
self.row_drafts.clear();
self.order_drafts.clear();
self.attachments = Default::default();
self.artifacts = Default::default();
self.annotation_oa = Default::default();
self.annotation_sa = Default::default();
self.member_oa = None;
self.member_sa = None;
self.literal.clear();
self.literal_oa = true;
self.literal_sa = true;
self.selected = None;
self.accepted_impacts.clear();
self.references.clear();
self.operation_omission = None;
self.draft = self
.target(project)
.and_then(|id| Draft::from_project(project, id));
self.group_drafts.clear();
if let Some(trace) = self.trace.and_then(|id| project.traces.get(&id)) {
for facet in constructors::selectors() {
if constructors::available(project, trace, &facet)
&& let Some(mut draft) = self
.target(project)
.and_then(|id| Draft::from_project(project, id))
{
draft.semantic_groups = true;
self.group_drafts.insert(facet, draft);
}
}
}
if let Some(trace) = self.trace.and_then(|id| project.traces.get(&id)) {
self.references.insert(trace.oa, trace.sa);
}
self.member_family = self
.member_families(project)
.first()
.copied()
.unwrap_or(keyed::Family::Literal);
self.refresh(project);
}
pub fn update(&mut self, project: &Project, event: RowEvent) {
let mut replan = true;
let result = match event {
// Browser messages belong to the app overlay and must never replan.
RowEvent::ReferenceOpen(_) | RowEvent::ReferenceEvent(_) => return,
RowEvent::ReferenceSet(intent) => {
let path = intent.context.path.clone();
self.edit(project, &path, |draft| {
draft.reference_set(project, &intent)
})
}
RowEvent::OperationOmission(enabled) => {
self.operation_omission =
enabled.then(|| self.operation_omission.clone().unwrap_or_default());
Ok(())
}
RowEvent::OperationRationale(raw) => {
self.operation_omission = Some(raw);
Ok(())
}
#[cfg(test)]
RowEvent::Trace(choice) => {
if !project
.traces
.get(&choice.id)
.is_some_and(|t| t.oa == self.anchor || t.sa == self.anchor)
{
Err("Choose one of this object's existing traces.".into())
} else {
self.trace = Some(choice.id);
self.load_trace(project);
return;
}
}
#[cfg(test)]
RowEvent::Direction(direction) => {
self.direction = direction;
self.load_trace(project);
return;
}
RowEvent::Select(facet) => {
self.selected = Some(facet);
self.tab = Tab::Facts;
replan = false;
Ok(())
}
RowEvent::Decide(facet, decision) => {
if let Some(decision) = decision {
if !self.editable(project, &facet)
&& !(decision == SemanticDecision::KeepTarget
&& self.can_acknowledge_constructor(project, &facet))
{
Err("This fact needs the structured review workflow; its history remains unchanged.".into())
} else {
if artifacts::supported(&facet)
&& decision == SemanticDecision::Candidate
&& let Err(error) = self.artifacts.ensure(
project,
&facet,
self.direction.target_layer(),
)
{
self.error = Some(error);
return;
}
if attachments::supported(&facet)
&& decision == SemanticDecision::Candidate
&& let Err(error) = self.attachments.ensure(
project,
&facet,
self.direction.target_layer(),
)
{
self.error = Some(error);
return;
}
if let FacetSelector::Order(which) = facet
&& decision == SemanticDecision::Candidate
{
if let Err(error) = self.initialize_order(project, which) {
self.error = Some(error);
return;
}
} else if keyed_rows::supported(&facet) {
self.initialize_row(project, &facet);
} else if keyed::supported(&facet) {
let target_presence = self
.effective_trace(project)
.and_then(|t| capture_facet(project, t.oa, t.sa, &facet).ok())
.and_then(|f| scalar::value(&f, self.direction.target_layer()))
.and_then(|v| v.as_bool())
.unwrap_or(false);
self.presence
.entry(facet.clone())
.or_insert(target_presence);
}
self.decisions.insert(facet, decision);
Ok(())
}
} else {
self.decisions.remove(&facet);
Ok(())
}
}
RowEvent::Input(path, value) => self.edit(project, &path, |d| {
d.input(&path, value);
Ok(())
}),
RowEvent::Text(path, action) => self.edit(project, &path, |d| {
d.edit_text(&path, action);
Ok(())
}),
RowEvent::Shape(path, value) => {
self.edit(project, &path, |d| d.shape_scoped(&path, value))
}
RowEvent::Variant(path, value) => {
self.edit(project, &path, |d| d.variant_scoped(&path, value))
}
RowEvent::Array(path, index, direction) => {
self.edit(project, &path, |d| d.array_scoped(&path, index, direction))
}
RowEvent::Append(path, value) => {
self.edit(project, &path, |d| d.append_scoped(&path, value))
}
RowEvent::ValuePath(path, key) => {
self.edit(project, &path, |d| d.value_path_scoped(&path, key))
}
RowEvent::ValueBrowse(path, scope, page) => {
replan = false;
self.edit(project, &path, |draft| {
draft
.value_browsers
.insert(path.clone(), crate::forms::ValueBrowser { scope, page });
Ok(())
})
}
RowEvent::ReferenceBrowse(path, query, page) => {
replan = false;
self.edit(project, &path, |draft| {
let page = if draft
.reference_browsers
.get(&path)
.is_some_and(|b| b.query == query)
{
page
} else {
0
};
draft
.reference_browsers
.insert(path.clone(), crate::forms::ReferenceBrowser { query, page });
Ok(())
})
}
RowEvent::Tab(tab) => {
self.tab = tab;
replan = false;
Ok(())
}
RowEvent::ShowUnchanged(show) => {
self.show_unchanged = show;
replan = false;
Ok(())
}
RowEvent::ReferenceOa(choice) => {
self.reference_oa = Some(choice);
replan = false;
Ok(())
}
RowEvent::ReferenceSa(choice) => {
self.reference_sa = Some(choice);
replan = false;
Ok(())
}
RowEvent::ReferenceSearch(value) => {
self.reference_search = value;
replan = false;
Ok(())
}
RowEvent::AddReference => match (&self.reference_oa, &self.reference_sa) {
(Some(oa), Some(sa)) => {
self.references.insert(oa.id, sa.id);
Ok(())
}
_ => Err("Choose both an Operational and a System reference.".into()),
},
RowEvent::RestoreReviewedReferences => {
Err("Restore reviewed pairs through the complete review session.".into())
}
RowEvent::RemoveReference(id) => {
self.references.remove(&id);
Ok(())
}
RowEvent::AcceptImpact(index, accepted) => {
if let Some(impact) = self
.plan
.as_ref()
.and_then(|p| p.impacts.get(index))
.cloned()
{
self.accepted_impacts.retain(|i| i.object != impact.object);
if accepted {
self.accepted_impacts.push(impact);
}
}
self.refresh(project);
return;
}
RowEvent::Preview => Ok(()),
RowEvent::OrderMove(which, index, delta) => self.move_order(which, index, delta),
RowEvent::OrderReconcile(which) => self.reconcile_order(project, which),
RowEvent::Reset(facet, reset) => self.reset_history(project, facet, reset),
RowEvent::Presence(facet, present) => {
if keyed::supported(&facet)
&& self.decisions.get(&facet) == Some(&SemanticDecision::Candidate)
{
self.presence.insert(facet, present);
Ok(())
} else {
Err("Choose Set final value for this member before editing presence.".into())
}
}
RowEvent::MemberFamily(family) => {
self.member_family = family;
replan = false;
Ok(())
}
RowEvent::MemberOa(choice) => {
self.member_oa = choice;
replan = false;
Ok(())
}
RowEvent::MemberSa(choice) => {
self.member_sa = choice;
replan = false;
Ok(())
}
RowEvent::Literal(value) => {
self.literal = value;
replan = false;
Ok(())
}
RowEvent::LiteralSide(layer, present) => {
if layer == Layer::Oa {
self.literal_oa = present;
} else {
self.literal_sa = present;
}
replan = false;
Ok(())
}
RowEvent::EditPair(facet) => {
replan = false;
if artifacts::supported(&facet) {
self.artifacts.select_pair(project, &facet).map(|()| {
self.tab = Tab::Artifacts;
})
} else if attachments::supported(&facet) {
self.attachments.select_pair(project, &facet).map(|()| {
self.tab = Tab::Attachments;
})
} else {
self.edit_pair(project, facet)
}
}
RowEvent::Artifact(event) => {
replan = !matches!(
event,
artifacts::Event::Pick(_, _) | artifacts::Event::Search(_)
);
let edited = match &event {
artifacts::Event::Input(facet, _) | artifacts::Event::Description(facet, _) => {
Some(facet)
}
_ => None,
};
if edited.is_some_and(|facet| {
self.decisions.get(facet) != Some(&SemanticDecision::Candidate)
}) {
Err("Choose Set final value before editing this exact artifact facet.".into())
} else {
match self.effective_trace(project) {
Some(trace) => self
.artifacts
.update(project, &trace, self.direction, event)
.map(|facets| {
if let Some(facets) = facets {
let pair = self.artifacts.pair();
if let Some((oa, sa)) = pair.oa.zip(pair.sa) {
self.references.insert(oa, sa);
}
self.selected = facets.first().cloned();
self.staged.extend(facets);
self.tab = Tab::Facts;
}
}),
None => Err("Choose a trace first.".into()),
}
}
}
RowEvent::Attachment(event) => {
replan = matches!(event, attachments::Event::Stage);
match self.effective_trace(project) {
Some(trace) => self
.attachments
.update(project, &trace, event)
.map(|facet| {
if let Some(facet) = facet {
if let Some((oa, sa)) = attachments::explicit_pair(&facet) {
self.references.insert(oa, sa);
}
self.staged.insert(facet.clone());
self.selected = Some(facet);
self.tab = Tab::Facts;
}
}),
None => Err("Choose a trace first.".into()),
}
}
RowEvent::StageMember => self.stage_member(project),
RowEvent::DropStaged(facet) => {
if self.staged.remove(&facet) {
self.decisions.remove(&facet);
}
Ok(())
}
RowEvent::AnnotationAddress(layer, key) => {
self.annotation_address_mut(layer).selected = key;
replan = false;
Ok(())
}
RowEvent::AnnotationBrowse(layer, scope, page) => {
let address = self.annotation_address_mut(layer);
address.scope = scope;
address.page = page;
replan = false;
Ok(())
}
#[cfg(test)]
RowEvent::RowExpression(facet, expression) => self.edit_row(&facet, |draft| {
draft.editor = text_editor::Content::with_text(&expression);
draft.expression = expression;
}),
RowEvent::RowText(facet, action) => self.edit_row(&facet, |draft| {
draft.editor.perform(action);
draft.expression = draft.editor.text();
}),
RowEvent::RowLocal(facet, local) => self.edit_row(&facet, |draft| {
draft.local = Some(local);
}),
};
if let Err(error) = result {
self.error = Some(error);
self.plan = None;
self.proposed = None;
return;
}
if replan {
self.accepted_impacts.clear();
self.refresh(project);
}
}
pub(super) fn reference_draft(
&self,
project: &Project,
facet: &FacetSelector,
path: &str,
) -> Option<&Draft> {
if self.selected.as_ref() != Some(facet)
|| self.decisions.get(facet) != Some(&SemanticDecision::Candidate)
|| matches!(path, "/id" | "/alias" | "/body" | "/layer")
|| !self
.candidate_roots(project, facet)?
.iter()
.any(|root| path == root || path.starts_with(&format!("{root}/")))
{
return None;
}
self.candidate_draft(facet)
}
fn edit(
&mut self,
project: &Project,
path: &str,
action: impl FnOnce(&mut Draft) -> Result<(), String>,
) -> Result<(), String> {
let facet = self.selected.as_ref().ok_or("Choose a property first.")?;
if self.decisions.get(facet) != Some(&SemanticDecision::Candidate) {
return Err("Choose Set final value before editing this property.".into());
}
let roots = self
.candidate_roots(project, facet)
.ok_or("Selected property is unavailable.")?;
if !roots
.iter()
.any(|root| path == root || path.starts_with(&format!("{root}/")))
{
return Err("Only the selected review property can change in this draft.".into());
}
let draft = if attachments::supported(facet) {
self.attachments.drafts.get_mut(facet)
} else if scalar::grouped(facet) {
self.group_drafts.get_mut(facet)
} else {
self.draft.as_mut()
}
.ok_or("Choose an existing target.")?;
// The scoped view sends only the selected property or closed group roots.
// Protected identity and top-level body/perspective controls are never exposed.
if path == "/id" || path == "/alias" || path == "/body" || path == "/layer" {
return Err("Semantic candidates retain identity, alias and perspective.".into());
}
action(draft)
}
pub(super) fn candidate_roots(
&self,
project: &Project,
facet: &FacetSelector,
) -> Option<Vec<String>> {
if attachments::supported(facet) {
attachments::path(project, facet, self.direction.target_layer()).map(|path| vec![path])
} else {
self.target(project)
.and_then(|id| constructors::roots(project, id, facet))
}
}
pub fn editable(&self, project: &Project, facet: &FacetSelector) -> bool {
if artifacts::supported(facet) {
return self.effective_trace(project).is_some_and(|trace| {
self.artifacts
.editable(project, &trace, facet, self.direction.target_layer())
});
}
if attachments::supported(facet) {
return attachments::path(project, facet, self.direction.target_layer()).is_some()
&& self.effective_trace(project).is_some_and(|trace| {
capture_facet(project, trace.oa, trace.sa, facet).is_ok()
});
}
if keyed::supported(facet) || matches!(facet, FacetSelector::Order(_)) {
return self
.effective_trace(project)
.is_some_and(|trace| capture_facet(project, trace.oa, trace.sa, facet).is_ok());
}
self.target(project)
.and_then(|id| scalar::binding(project, id, facet))
.is_some()
&& (!scalar::grouped(facet)
|| (self.group_drafts.contains_key(facet)
&& self
.trace
.and_then(|id| project.traces.get(&id))
.is_some_and(|trace| constructors::available(project, trace, facet))))
}
pub(super) fn candidate_draft(&self, facet: &FacetSelector) -> Option<&Draft> {
if attachments::supported(facet) {
self.attachments.drafts.get(facet)
} else if scalar::grouped(facet) {
self.group_drafts.get(facet)
} else {
self.draft.as_ref()
}
}
pub fn can_acknowledge_constructor(&self, project: &Project, facet: &FacetSelector) -> bool {
if !scalar::grouped(facet) {
return false;
}
let Some(trace) = self.effective_trace(project) else {
return false;
};
if trace
.baseline
.as_ref()
.and_then(|b| b.semantic.as_ref())
.is_some_and(|b| b.facets.iter().any(|f| f.selector() == *facet))
{
return false;
}
capture_facet(project, trace.oa, trace.sa, facet)
.ok()
.is_some_and(|current| {
scalar::value(&current, Layer::Oa).is_some()
&& scalar::observation(&current, Layer::Oa)
== scalar::observation(&current, Layer::Sa)
})
}
/// Compose selected values for both the candidate and dependent form
/// controls. Incomplete inputs retain their stored shape only for controls;
/// request construction always requires every selected input to parse.
pub(super) fn combined_value(
&self,
project: &Project,
form_context: bool,
) -> Result<Value, String> {
self.combine(project, form_context, false)
}
pub(super) fn combine(
&self,
project: &Project,
form_context: bool,
skip_orders: bool,
) -> Result<Value, String> {
let trace = self.effective_trace(project).ok_or("Choose a trace.")?;
let (source, target) = if self.direction == TransitionDirection::OaToSa {
(trace.oa, trace.sa)
} else {
(trace.sa, trace.oa)
};
let mut final_value = Draft::from_project(project, target)
.ok_or("Target has no editable record.")?
.value;
let mut map = BTreeMap::new();
for (oa, sa) in &self.references {
let (from, to) = if self.direction == TransitionDirection::OaToSa {
(*oa, *sa)
} else {
(*sa, *oa)
};
if map.insert(from, to).is_some_and(|old| old != to) {
return Err("Reverse reference correspondence is ambiguous.".into());
}
}
map.insert(source, target);
let original = final_value.clone();
let mut group_candidates = vec![];
// Outer groups precede nested groups and leaves. The core rejects actual
// overlapping replacement scopes; this order also supports first-equal
// discriminator acknowledgment together with explicit descendant values.
let mut decisions: Vec<_> = self.decisions.iter().collect();
decisions.sort_by_key(|(facet, _)| {
let group = match facet {
FacetSelector::Occurrence(OccurrenceFacet::Constructor) => 0,
facet if scalar::grouped(facet) => 1,
FacetSelector::Order(_) => 3,
_ => 2,
};
(
group,
keyed_rows::target_selector(facet, self.direction.target_layer()),
)
});
for (facet, decision) in decisions {
if attachments::supported(facet)
|| artifacts::supported(facet)
|| *decision == SemanticDecision::KeepTarget
{
continue;
}
if let FacetSelector::Order(which) = facet {
if !skip_orders {
// Invalid order drafts remain visible while dependent controls use the
// surviving membership; request construction still rejects them.
if let Err(error) =
self.project_order(project, &trace, &mut final_value, *which, *decision)
&& !form_context
{
return Err(error);
}
}
continue;
}
if matches!(facet, FacetSelector::Member { collection, .. } if ordered::ordered(*collection).is_some())
{
self.project_ordered_member(
project,
&trace,
&mut final_value,
facet,
*decision,
form_context,
)?;
continue;
}
if keyed::supported(facet) {
self.keyed_value(project, &trace, &mut final_value, facet, *decision)?;
continue;
}
let roots = constructors::roots(project, target, facet)
.ok_or("Selected property is not applicable to the target.")?;
let supplied = match decision {
SemanticDecision::KeepTarget => continue,
SemanticDecision::Candidate => {
let draft = self
.candidate_draft(facet)
.ok_or("Target draft is unavailable.")?;
match draft.materialize_scopes(&roots) {
Ok(value) => value,
Err(_) if form_context => draft.value.clone(),
Err(error) => return Err(error),
}
}
SemanticDecision::TakeSource if scalar::grouped(facet) => {
if self.can_acknowledge_constructor(project, facet) {
original.clone()
} else {
constructors::take_value(project, &trace, self.direction, facet, &map)?
}
}
SemanticDecision::TakeSource => {
let fact = capture_facet(project, trace.oa, trace.sa, facet)?;
let value = scalar::source_value(&fact, self.direction.source_layer(), &map)?;
let mut supplied = original.clone();
crate::forms::set_shape_value(&mut supplied, &roots[0], value)?;
supplied
}
};
for path in roots {
let value = supplied
.pointer(&path)
.cloned()
.or_else(|| {
(matches!(facet, FacetSelector::Occurrence(OccurrenceFacet::Operation)))
.then_some(Value::Null)
})
.ok_or("Candidate property is no longer available.")?;
if scalar::grouped(facet) && *decision == SemanticDecision::Candidate {
group_candidates.push((path.clone(), value.clone()));
}
crate::forms::set_shape_value(&mut final_value, &path, value)?;
}
}
if !form_context {
for (path, authored) in group_candidates {
if !original
.pointer(&path)
.zip(final_value.pointer(&path))
.is_some_and(|(original, final_value)| {
constructors::authored_agrees(original, &authored, final_value)
})
{
return Err("The constructor candidate and descendant decision propose different final values. Keep the kind or make their final values agree; both drafts are retained.".into());
}
}
}
Ok(final_value)
}
pub fn refresh(&mut self, project: &Project) {
self.plan = None;
self.proposed = None;
self.error = None;
let context = self.combined_value(project, true).ok();
if let Some(draft) = &mut self.draft {
draft.set_context(context.clone());
}
for draft in self.group_drafts.values_mut() {
draft.set_context(context.clone());
}
let Some(trace) = self.effective_trace(project) else {
self.error = Some(
"Several traces are available. Choose the correspondence and review history above."
.into(),
);
return;
};
match semantic_facets(project, &trace, &self.references) {
Ok(mut facts) => {
// Explicit addresses survive catalog normalization, pairing changes and resets.
for selector in self.staged.iter().chain(self.decisions.keys()) {
if !facts.iter().any(|f| &f.selector == selector) {
match capture_facet(project, trace.oa, trace.sa, selector) {
Ok(current) => {
let previous = trace
.baseline
.as_ref()
.and_then(|b| b.semantic.as_ref())
.and_then(|b| {
b.facets.iter().find(|f| f.selector() == *selector)
});
facts.push(FacetDrift {
selector: selector.clone(),
drift: facet_drift(&current, previous),
current,
});
}
Err(error) => {
self.error = Some(error);
return;
}
}
}
}
facts.sort_by(|a, b| a.selector.cmp(&b.selector));
self.facts = facts;
}
Err(error) => {
self.error = Some(error);
return;
}
}
if self
.selected
.as_ref()
.is_none_or(|s| !self.facts.iter().any(|f| &f.selector == s))
{
self.selected = self
.facts
.iter()
.find(|f| f.drift != Drift::Unchanged)
.or(self.facts.first())
.map(|f| f.selector.clone());
}
}
pub fn all_references(&self, project: &Project, layer: Layer) -> Vec<Choice> {
let historical: BTreeSet<_> = self
.facts
.iter()
.flat_map(|f| scalar::references(&f.current))
.chain(
self.trace
.and_then(|id| project.traces.get(&id))
.and_then(|t| t.baseline.as_ref())
.and_then(|b| b.semantic.as_ref())
.into_iter()
.flat_map(|b| b.facets.iter().flat_map(scalar::references)),
)
.collect();
let mut ids: BTreeSet<_> = project
.elements
.keys()
.chain(project.relations.keys())
.chain(project.artifact_links.keys())
.filter(|id| project.layer_of(**id) == Some(layer))
.copied()
.collect();
ids.extend(historical.into_iter().filter(|id| !project.contains(*id)));
let search = self.reference_search.to_lowercase();
let mut choices: Vec<_> = ids
.into_iter()
.map(|id| self.review_choice(project, id))
.filter(|c| search.is_empty() || c.label.to_lowercase().contains(&search))
.collect();
choices.sort_by(|a, b| a.label.cmp(&b.label));
choices
}
}
pub fn form_event(message: Message) -> Message {
let event = match message {
Message::Field(path, value) => RowEvent::Input(path, value),
Message::FieldText(path, action) => RowEvent::Text(path, action),
Message::FieldShape(path, value) => RowEvent::Shape(path, value),
Message::FieldVariant(path, value) => RowEvent::Variant(path, value),
Message::FieldArray(path, index, direction) => RowEvent::Array(path, index, direction),
Message::FieldAppend(path, value) => RowEvent::Append(path, value),
Message::FieldValuePath(path, key) => RowEvent::ValuePath(path, key),
Message::FieldValueBrowse(path, scope, page) => RowEvent::ValueBrowse(path, scope, page),
Message::FieldReferenceBrowse(path, query, page) => {
RowEvent::ReferenceBrowse(path, query, page)
}
Message::FieldReferenceOpen(request) => RowEvent::ReferenceOpen(request),
Message::FieldReferenceEvent(event) => RowEvent::ReferenceEvent(event),
_ => RowEvent::Preview,
};
Message::Workflow(crate::workflow::Event::Semantic(Event::Editor(event)))
}