P1: the books keep how they are kept

Settings, conventions (posting templates, approval rules, policies,
close steps, calendar, mappings, notes), decisions, open items and
entry approvals are journal events (journal format 2). Books that keep
their settings never read wanderbooks.toml or config.json; the server
and the viewer take the vault key from --vault-key-file instead.
books.import_settings moves a settings file in once, exactly as the
server reads it. Approval rules, when enforced, gate every posting
path. Replay refuses what it can't read as newer, never as corrupt.

Lean: a record changes no figure. Mutation on the whole diff: every
survivor killed by a test but two equivalent (docs/testing.md). The
Company page says a late open item is overdue in words, not colour
alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-17 19:27:48 -06:00
1 parent e60ecc5317
commit c587593d78
71 files changed
+12249 -461

No files matched your search

+29 -9
View File
@@ -68,7 +68,9 @@ pub fn load_once(source: &Source) -> Result<Books, String> {
})
})?;
let integrity = Integrity::check(&snap, &mut HashSet::new());
Books::build(&snap, settings(source)?, today(source), None, integrity)
let settings = settings(source, &snap)?;
let today = today(source, &settings);
Books::build(&snap, settings, today, None, integrity)
}
/// What the viewer says when the books can't be read. Books from a newer
@@ -88,13 +90,25 @@ pub fn problem(e: &StoreError, context: impl FnOnce() -> String) -> String {
}
}
fn today(source: &Source) -> NaiveDate {
/// Today: as fixed for tests, else in the company's time zone, else the
/// computer's.
fn today(source: &Source, settings: &BooksSettings) -> NaiveDate {
source
.today
.unwrap_or_else(|| chrono::Local::now().date_naive())
.unwrap_or_else(|| ledger_core::settings::today_in(settings.timezone.as_deref()))
}
fn settings(source: &Source) -> Result<BooksSettings, String> {
/// The settings: the books' own when they keep them (the settings file is
/// then never read, as the server never reads it), else the settings
/// file's.
fn settings(source: &Source, snap: &Snapshot) -> Result<BooksSettings, String> {
let books = snap.ledger().settings();
if books.is_journaled() {
return Ok(ledger_config::codec::books_settings(
books,
snap.ledger().base_currency().code(),
));
}
match &source.books_toml {
Some(p) if p.exists() => ledger_config::load(p),
_ => Ok(BooksSettings::default()),
@@ -128,9 +142,10 @@ fn run(source: Source, ctx: egui::Context, tx: Sender<Update>) {
let mut build = |snap: &Snapshot, prev: Option<u64>| -> (Update, ledger_store::DiskCheck) {
let integrity = Integrity::check(snap, &mut verified);
let disk = integrity.disk.clone();
let update = match settings(&source)
.and_then(|s| Books::build(snap, s, today(&source), prev, integrity))
{
let update = match settings(&source, snap).and_then(|s| {
let today = today(&source, &s);
Books::build(snap, s, today, prev, integrity)
}) {
Ok(b) => Update::Books(Arc::new(b)),
Err(e) => Update::Error(e),
};
@@ -181,7 +196,12 @@ fn run(source: Source, ctx: egui::Context, tx: Sender<Update>) {
}
}
let mut toml_mtime = mtime(&source.books_toml);
let mut day = today(&source);
let day_now = |snap: &Snapshot| {
settings(&source, snap)
.map(|s| today(&source, &s))
.unwrap_or_else(|_| today(&source, &BooksSettings::default()))
};
let mut day = day_now(&snap);
loop {
match ev_rx.recv_timeout(Duration::from_secs(3)) {
Ok(()) => {
@@ -205,7 +225,7 @@ fn run(source: Source, ctx: egui::Context, tx: Sender<Update>) {
}
};
let toml_now = mtime(&source.books_toml);
let new_day = today(&source);
let new_day = day_now(&snap);
// The writer journals first and updates its database right after:
// a "behind" database usually catches up a moment later, so look
// again rather than leave a stale warning up.