use crate::lifecycle_service::LifecycleAction;
use crate::lifecycle_store::LedgerEntry;
use crate::lifecycle_summary;
pub fn state_label(entry: &LedgerEntry) -> &'static str {
match entry.record.state {
crate::domain::MemoryLifecycleState::Draft => "draft",
crate::domain::MemoryLifecycleState::Candidate => "candidate",
crate::domain::MemoryLifecycleState::Accepted => "accepted",
crate::domain::MemoryLifecycleState::Canonical => "canonical",
crate::domain::MemoryLifecycleState::Archived => "archived",
}
}
pub fn action_label(entry: &LedgerEntry) -> &'static str {
match entry.action {
crate::domain::MemoryLedgerAction::RecordManual => "record_manual",
crate::domain::MemoryLedgerAction::ProposeAi => "propose_ai",
crate::domain::MemoryLedgerAction::SubmitProposal => "submit_proposal",
crate::domain::MemoryLedgerAction::Accept => "accept",
crate::domain::MemoryLedgerAction::PromoteToCanonical => "promote_to_canonical",
crate::domain::MemoryLedgerAction::Archive => "archive",
}
}
pub fn metadata_lines(entry: &LedgerEntry) -> String {
let mut lines = String::new();
if let Some(actor) = entry.metadata.actor.as_deref() {
lines.push_str(&format!("- actor: {}\n", actor));
}
if let Some(reason) = entry.metadata.reason.as_deref() {
lines.push_str(&format!("- reason: {}\n", reason));
}
if !entry.metadata.evidence_refs.is_empty() {
lines.push_str(&format!(
"- evidence_refs: {}\n",
entry.metadata.evidence_refs.join(", ")
));
}
lines
}
pub fn action_button_label(action: LifecycleAction) -> &'static str {
match action {
LifecycleAction::Accept => "Accept",
LifecycleAction::PromoteToCanonical => "Promote",
LifecycleAction::Archive => "Archive",
}
}
pub fn render_list_item(entry: &LedgerEntry, include_record_id: bool) -> String {
if include_record_id {
format!(
"- `{}` [{}] {} ({})",
entry.record_id,
state_label(entry),
entry.record.title,
entry.record.memory_type
)
} else {
format!(
"- [{}] {} ({})",
state_label(entry),
entry.record.title,
entry.record.memory_type
)
}
}
pub fn render_action_result(action: LifecycleAction, entry: &LedgerEntry) -> String {
lifecycle_summary::render_action_text(action, entry)
}
pub fn render_create_result(kind: &str, entry: &LedgerEntry) -> String {
lifecycle_summary::render_create_text(kind, entry)
}
pub fn render_gui_list_label(entry: &LedgerEntry) -> String {
format!(
"[{}] {} ({})",
state_label(entry),
entry.record.title,
entry.record.memory_type
)
}
pub fn render_detail(
entry: &LedgerEntry,
quote_record_id: bool,
include_summary_section: bool,
) -> String {
let record_id = if quote_record_id {
format!("`{}`", entry.record_id)
} else {
entry.record_id.clone()
};
let mut lines = vec![
"# Memory record".to_string(),
String::new(),
format!("- record_id: {}", record_id),
format!("- state: {}", state_label(entry)),
format!("- action: {}", action_label(entry)),
format!("- title: {}", entry.record.title),
format!("- memory_type: {}", entry.record.memory_type),
format!("- scope: {:?}", entry.record.scope),
format!("- source_kind: {:?}", entry.source_kind),
format!("- scope_key: {}", entry.scope_key),
];
if let Some(project_id) = entry.record.project_id.as_deref() {
lines.push(format!("- project_id: {}", project_id));
}
if let Some(user_id) = entry.record.user_id.as_deref() {
lines.push(format!("- user_id: {}", user_id));
}
if let Some(sensitivity) = entry.record.sensitivity.as_deref() {
lines.push(format!("- sensitivity: {}", sensitivity));
}
if !entry.record.entities.is_empty() {
lines.push(format!("- entities: {}", entry.record.entities.join(", ")));
}
if !entry.record.tags.is_empty() {
lines.push(format!("- tags: {}", entry.record.tags.join(", ")));
}
if !entry.record.triggers.is_empty() {
lines.push(format!("- triggers: {}", entry.record.triggers.join(", ")));
}
if !entry.record.related_files.is_empty() {
lines.push(format!(
"- related_files: {}",
entry.record.related_files.join(", ")
));
}
if !entry.record.related_records.is_empty() {
lines.push(format!(
"- related_records: {}",
entry.record.related_records.join(", ")
));
}
if !entry.record.applies_to.is_empty() {
lines.push(format!(
"- applies_to: {}",
entry.record.applies_to.join(", ")
));
}
if let Some(ref supersedes) = entry.record.supersedes {
lines.push(format!("- supersedes: {}", supersedes));
}
if let Some(ref valid_until) = entry.record.valid_until {
lines.push(format!("- valid_until: {}", valid_until));
}
if include_summary_section {
lines.push(String::new());
lines.push("## Summary".to_string());
lines.push(String::new());
lines.push(entry.record.summary.clone());
} else {
lines.push(format!("- summary: {}", entry.record.summary));
}
let metadata = metadata_lines(entry);
if !metadata.is_empty() {
let insert_at = lines
.len()
.saturating_sub(if include_summary_section { 3 } else { 1 });
let metadata_lines: Vec<String> = metadata
.trim_end()
.lines()
.map(ToString::to_string)
.collect();
lines.splice(insert_at..insert_at, metadata_lines);
}
lines.join("\n")
}
pub fn render_history(record_id: &str, entries: &[LedgerEntry], quote_record_id: bool) -> String {
let record_id = if quote_record_id {
format!("`{record_id}`")
} else {
record_id.to_string()
};
if entries.is_empty() {
return format!("# Memory history\n\n- record_id: {record_id}\n- none");
}
let mut output = format!(
"# Memory history\n\n- record_id: {}\n- events: {}\n\n",
record_id,
entries.len()
);
for (index, entry) in entries.iter().enumerate() {
output.push_str(&format!(
"## {}. {}\n- recorded_at: {}\n- action: {}\n- state: {}\n- title: {}\n{}\n",
index + 1,
entry.record_id,
entry.recorded_at,
action_label(entry),
state_label(entry),
entry.record.title,
metadata_lines(entry)
));
}
output.trim_end().to_string()
}
pub fn render_list(
title: &str,
entries: &[LedgerEntry],
include_record_id: bool,
markdown_heading: bool,
) -> String {
if entries.is_empty() {
return if markdown_heading {
format!("# {title}\n\n- none")
} else {
format!("{title}\n\n- none")
};
}
let mut output = String::new();
if markdown_heading {
output.push_str(&format!("# {title}\n\n"));
} else {
output.push_str(title);
output.push_str("\n\n");
}
for entry in entries {
output.push_str(&render_list_item(entry, include_record_id));
output.push('\n');
}
output
}