P2 slice 6: the viewer

`wanderbooks gui [BOOK]` finds the data root, reads settings from the
journal, the vault of the book, presence through ops/, Save a copy /
Open on every document, Save the W-9, the Export menu, and colour
schemes. One builder for the CPA package and the 1099 worksheet
(ledger-package), shared by the server and the viewer.

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-20 11:14:21 -06:00
1 parent b26c099a68
commit 5bc0633cdd
77 files changed
+5833 -2427

No files matched your search

+29 -11
View File
@@ -185,6 +185,24 @@ fn inline(bytes: &[u8]) -> (String, String) {
)
}
/// The bytes of a file a tool handed over (a `HandedFile`): inline, as on
/// books served from a --data-dir, matching the size and SHA-256 it
/// states.
fn handed(file: &Value) -> Vec<u8> {
use base64::Engine;
assert!(file.get("download").is_none(), "{file}");
let bytes = base64::engine::general_purpose::STANDARD
.decode(file["content_base64"].as_str().expect("content_base64"))
.unwrap();
assert_eq!(file["size_bytes"], json!(bytes.len()), "{file}");
assert_eq!(
file["sha256"],
json!(ledger_store::journal::sha256_hex(&bytes)),
"{file}"
);
bytes
}
/// Every way the file at `path` (named `name`) could enter the books is
/// refused with `why`, and nothing is stored: the refusals, tool by tool.
async fn refused_everywhere(
@@ -281,28 +299,28 @@ async fn a_tin_bearing_file_is_refused_from_every_session_and_after_a_restart()
// A bank statement filed in the books: the package carries it, and it
// is no TIN file (its routing numbers are not TINs).
accepted_everywhere(&one, "statement.csv", BANK_CSV).await;
// What the server writes with TINs, from the first session.
// What the server hands over with TINs, from the first session, saved
// outside the books as the human would save it.
let copy = b.out.join("w9-copy.pdf");
ok(
&one,
"books.get_w9",
json!({"party": "Acme", "save_to": copy.to_str().unwrap()}),
)
.await;
let got = ok(&one, "books.get_w9", json!({"party": "Acme"})).await;
std::fs::write(&copy, handed(&got["file"])).unwrap();
let csv = b.out.join("1099.csv");
ok(
let got = ok(
&one,
"books.vendor_1099",
json!({"year": 2026, "include_tins": true, "save_csv_to": csv.to_str().unwrap()}),
json!({"year": 2026, "include_tins": true, "csv": true}),
)
.await;
std::fs::write(&csv, handed(&got["csv"])).unwrap();
let zip = b.out.join("cpa.zip");
ok(
let got = ok(
&one,
"books.export_cpa_package",
json!({"cash_accounts": ["1000"], "save_to": zip.to_str().unwrap(), "as_of": "2026-12-31"}),
json!({"cash_accounts": ["1000"], "as_of": "2026-12-31"}),
)
.await;
assert_eq!(got["tins_included"], true, "{got}");
std::fs::write(&zip, handed(&got["file"])).unwrap();
let k1 = b.out.join("k1-members.csv");
{
let mut archive = zip::ZipArchive::new(std::fs::File::open(&zip).unwrap()).unwrap();