No description
  • Rust 99.9%
  • Just 0.1%
Find a file
Kerry Hatcher ff3983b301
docs(claude-md): point the superproject link at Forgejo, not the stale GitHub mirror
The GitHub repo is deliberately kept as a mirror, but its main is frozen at
b08f1f0 (pre-thor-deployment) with a .gitmodules that still points every
submodule back at GitHub — the same trap finding 3 caught in install.md.
An agent following this link lands on stale code, not the current tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGisjtKSjUcVGw1jX1Hf8M
2026-08-07 13:39:28 -04:00
.claude/skills/ply-engine feat: cross-platform desktop client on ply-engine 2026-08-03 15:05:19 -04:00
assets/fonts feat: cross-platform desktop client on ply-engine 2026-08-03 15:05:19 -04:00
src test(auth): pin the cleared-store revocation route 2026-08-06 14:08:20 -04:00
.gitignore Initial commit 2026-08-03 14:11:41 -04:00
Cargo.lock feat(auth): PKCE login machinery and shared token storage 2026-08-05 14:54:58 -04:00
Cargo.toml feat(auth): PKCE login machinery and shared token storage 2026-08-05 14:54:58 -04:00
CLAUDE.md docs(claude-md): point the superproject link at Forgejo, not the stale GitHub mirror 2026-08-07 13:39:28 -04:00
justfile feat: settings screen for the api address, and a just run 2026-08-05 12:48:44 -04:00
LICENSE Initial commit 2026-08-03 14:11:41 -04:00
PRODUCT.md feat: light-first civic palette, keyboard operation, delete confirmation 2026-08-03 16:46:24 -04:00
README.md feat: settings screen for the api address, and a just run 2026-08-05 12:48:44 -04:00

openbooks-desktop

Desktop client for OpenBooks, built on ply-engine — a Rust UI engine that renders the same code on Linux, macOS, Windows, Android, iOS and the web.

just run                                    # inside the superproject, starts the api first
cargo run                                   # needs openbooks-api on :38081
OPENBOOKS_API=http://box:38081 cargo run    # point it somewhere else

The Settings screen points it somewhere else for good: the address is saved to settings.json beside the snapshot and read on every launch, overriding OPENBOOKS_API.

Other platforms go through plyx (cargo install plyx): plyx web, plyx apk, plyx ios. Only the desktop build has been exercised so far.

Works offline, read-only

Every successful sync writes a JSON snapshot to the platform's app data directory (~/Library/Application Support/openbooks/snapshot.json on macOS). On launch the app shows that snapshot before it touches the network, so the window is never empty and a dead API is not a blank screen.

When the API can't be reached the app is strictly read-only: the record form and the per-row delete buttons aren't rendered at all. Nothing is queued for later. That's deliberate — a replay-on-reconnect queue is a good way to post the same payment twice, and this is a ledger. Recording comes back on its own once a sync succeeds. A failed sync never overwrites a good snapshot.

Layout

client.rs HTTP. net::get is idempotent per request id and responses are evicted a few frames after you stop reading them, so each refresh uses a generation-stamped id.
cache.rs The snapshot, via ply's Storage on a background job.
settings.rs The saved API address, same storage as the snapshot.
api.rs Wire types. Integer cents, dates as strings.
msg.rs Ply handlers must be 'static, so they queue a Msg and the main loop is the only place state changes.
widgets.rs, theme.rs Buttons, cards, tables, colour and spacing.
views/ One file per screen.

Tests

cargo test                       # money and date logic
cargo test --no-default-features # the above, plus headless render checks

money.rs and date.rs carry the fiddly logic — penny rounding, leap years.

To look at a screen without sitting at the window:

OPENBOOKS_VIEW=reports OPENBOOKS_SHOT=/tmp/reports.png cargo run

It renders ~120 frames, writes the real framebuffer and exits. Worth doing after any layout change: the headless tests below assert what was drawn, never how it looks, and both bugs found this way — a wordmark overlapped by a button, and tofu boxes where the font had no arrow glyph — were invisible to them.

views/mod.rs lays each screen out with no window (Ply::new_headless) and asserts what it drew, which is how the offline read-only rule is pinned down: a delete control must render while online and be absent offline, not merely disabled. Those tests need --no-default-features because ply's eval() syncs a live OS accessibility tree and panics without a window, so they're compiled out whenever a11y is on.

The two-legged transaction is composed in main.rs::submit — debit positive, credit negative, summing to zero. Users pick an amount, an account and a category; no accounting vocabulary reaches the UI.