viewer: journal text for asset.acquisition_linked; a test that every event type the store writes has plain-language journal text
check / check (push) Waiting to run

The acquisition link was added after the viewer's asset work and showed its raw event name in recent activity. The new test reads the event types from the store's replay arms, so the next new event without text fails it (checked: it fails without this fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-15 12:35:39 -06:00
1 parent ee7e205f5c
commit 2e14c702c0
3 files changed
+55 -9

No files matched your search

+12
View File
@@ -267,6 +267,18 @@ pub fn describe(b: &Books, line: &JournalLine) -> Option<Event> {
};
ev(icon::BARCODE, text, asset_id.map(Route::Asset))
}
"asset.acquisition_linked" => {
let entry = p.get("entry_id").and_then(|v| v.as_u64()).unwrap_or(0);
Some(Event {
icon: icon::LINK,
text: format!(
"Linked {} to the entry that acquired it, #{entry}",
asset(asset_id)
),
route: asset_id.map(Route::Asset),
also: Some(Route::Entry(entry)),
})
}
"asset.depreciation_posted" => with_asset(
icon::TREND_DOWN,
format!(
+39
View File
@@ -612,3 +612,42 @@ proptest! {
}
}
}
/// Every event the store can journal reads as a sentence in the journal
/// and recent activity, never as its raw type: the event types come from
/// the store's own source (its replay arms), so a new event without
/// journal text fails here. (asset.acquisition_linked showed raw,
/// 2026-10-15.)
#[test]
fn every_journal_event_has_plain_language_text() {
let src = include_str!("../../ledger-store/src/store.rs");
let mut kinds: Vec<&str> = src
.lines()
.flat_map(|l| l.split('"').skip(1).step_by(2))
.filter(|t| {
let mut parts = t.split('.');
matches!(
(parts.next(), parts.next(), parts.next()),
(Some(a), Some(b), None)
if !a.is_empty() && !b.is_empty()
&& a.chars().chain(b.chars()).all(|c| c.is_ascii_lowercase() || c == '_')
)
})
.filter(|t| src.contains(&format!("\"{t}\" =>")) || src.contains(&format!("\"{t}\" |")))
.collect();
kinds.sort_unstable();
kinds.dedup();
assert!(kinds.len() > 30, "found only {kinds:?}");
let f = fixture("every-event");
for kind in kinds {
let line = ledger_store::JournalLine {
seq: 1,
ts: "2026-10-15T00:00:00Z".into(),
line_type: kind.to_string(),
payload: serde_json::json!({}),
prev: String::new(),
};
let e = ledger_gui::pages::journal::describe_any(&f.books, &line);
assert_ne!(e.text, kind, "{kind} has no journal text");
}
}
+4 -9
View File
@@ -27,15 +27,10 @@ pub struct JournalLine {
pub seq: u64,
/// UTC timestamp, RFC 3339. Recorded, never trusted for ordering.
pub ts: String,
/// Event type: `account.added`, `entry.created`, `entry.line_added`,
/// `entry.posted`, `entry.reversed`, `entry.line_class_set`,
/// `receipt.attached`, `receipt.linked`, `receipt.unlinked`,
/// `document.filed`, `attachment.withdrawn`,
/// `asset.registered`, `asset.depreciation_posted`, `asset.disposed`,
/// `period.created`, `period.closed`, `line.cleared`, `customer.added`,
/// `vendor.added`, `invoice.created`, `bill.created`,
/// `invoice.payment_received`, `bill.paid`, `fx.rate_set`,
/// `payroll.run`, `class.added`, `store.created`.
/// Event type, e.g. `account.added`, `entry.posted`, `asset.tagged`:
/// every type the store writes has a replay arm in
/// `replay_line_into_ledger` (an unknown type is refused as corrupt),
/// and journal text in the viewer (`ledger-gui` tests check both).
#[serde(rename = "type")]
pub line_type: String,
/// The full domain facts. Everything a replay needs.