From 2e14c702c0afa0ef1597fa3fb50c98430386bf28 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 15 Oct 2026 12:35:39 -0600 Subject: [PATCH] viewer: journal text for asset.acquisition_linked; a test that every event type the store writes has plain-language journal text 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 Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX --- crates/ledger-gui/src/pages/journal.rs | 12 ++++++++ crates/ledger-gui/tests/assets.rs | 39 ++++++++++++++++++++++++++ crates/ledger-store/src/journal.rs | 13 +++------ 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/crates/ledger-gui/src/pages/journal.rs b/crates/ledger-gui/src/pages/journal.rs index 814ec7e..e7a9fcc 100644 --- a/crates/ledger-gui/src/pages/journal.rs +++ b/crates/ledger-gui/src/pages/journal.rs @@ -267,6 +267,18 @@ pub fn describe(b: &Books, line: &JournalLine) -> Option { }; 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!( diff --git a/crates/ledger-gui/tests/assets.rs b/crates/ledger-gui/tests/assets.rs index 0d4d207..b061e8a 100644 --- a/crates/ledger-gui/tests/assets.rs +++ b/crates/ledger-gui/tests/assets.rs @@ -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"); + } +} diff --git a/crates/ledger-store/src/journal.rs b/crates/ledger-store/src/journal.rs index bb51f93..09dc247 100644 --- a/crates/ledger-store/src/journal.rs +++ b/crates/ledger-store/src/journal.rs @@ -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.