feat: books.show — the agent puts things on the viewer's screen

books.show asks the viewer to open an entry, an account register, a
report (dates, month/quarter/year columns, prior-period or prior-year
comparison), a document, a search, or a page. The request lives outside
the books (runtime dir, keyed by data dir, written atomically); the
viewer watches it beside the journal and applies books before
navigating. 66 tools.

Site: the dashboard is the hero again; 'Here's what using it looks like'
shows the session beside the viewer, ending in books.show; the cash page
replaces the repeated overview lower down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-12 19:03:19 -06:00
1 parent 8637c1b05f
commit 475d08b28e
17 files changed
+626 -22

No files matched your search

+32 -7
View File
@@ -30,6 +30,8 @@ pub struct Source {
pub enum Update {
Books(Arc<Books>),
Error(String),
/// The agent asked the viewer to show something (`books.show`).
Show(ledger_store::viewer::ViewerTarget),
}
pub struct Loader {
@@ -46,13 +48,10 @@ impl Loader {
Loader { rx }
}
/// The newest update, if any arrived since the last call.
pub fn poll(&self) -> Option<Update> {
let mut last = None;
while let Ok(u) = self.rx.try_recv() {
last = Some(u);
}
last
/// Every update since the last call, in order (books before the
/// navigation that refers to them).
pub fn poll(&self) -> Vec<Update> {
self.rx.try_iter().collect()
}
/// Block until the first update (tests, headless).
@@ -121,6 +120,26 @@ fn run(source: Source, ctx: egui::Context, tx: Sender<Update>) {
let mut last_disk = Some(disk);
send(first);
// Requests to show something: apply new ones, and on start one made in
// the last two minutes (the agent said "open the viewer"); never an
// old one.
let started = chrono::Utc::now();
let mut last_show = 0u64;
let mut check_show = |send: &dyn Fn(Update)| {
if let Some(cmd) = ledger_store::viewer::read(&source.data_dir)
&& cmd.seq != last_show
{
let first_look = last_show == 0;
last_show = cmd.seq;
let recent = chrono::DateTime::parse_from_rfc3339(&cmd.at)
.is_ok_and(|t| started.signed_duration_since(t) < chrono::Duration::minutes(2));
if !first_look || recent {
send(Update::Show(cmd.target));
}
}
};
check_show(&send);
let (ev_tx, ev_rx) = channel();
let mut watcher = notify::recommended_watcher(move |res: notify::Result<notify::Event>| {
if res.is_ok() {
@@ -130,6 +149,11 @@ fn run(source: Source, ctx: egui::Context, tx: Sender<Update>) {
.ok();
if let Some(w) = watcher.as_mut() {
let _ = w.watch(&source.data_dir, RecursiveMode::NonRecursive);
let cmd = ledger_store::viewer::command_path(&source.data_dir);
if let Some(dir) = cmd.parent() {
let _ = std::fs::create_dir_all(dir);
let _ = w.watch(dir, RecursiveMode::NonRecursive);
}
if let Some(t) = &source.books_toml
&& let Some(dir) = t.parent()
{
@@ -174,5 +198,6 @@ fn run(source: Source, ctx: egui::Context, tx: Sender<Update>) {
last_disk = Some(disk);
send(update);
}
check_show(&send);
}
}