UI
NAME
App::Moneymoor::UI - the TUI's entry point: builds the notcurses app, runs the login handshake, and hands over to the main shell.
SYNOPSIS
# In bin/moneymoor, AFTER `use MacOS::NativeLib <sqlcipher>`:
use App::Moneymoor::UI;
App::Moneymoor::UI.new.run;
DESCRIPTION
The whole of the app's lifecycle, in one small class:
load
App::Moneymoor::Configfrom~/.moneymoor/config.json;point
App::Moneymoor::Util::Moneyat the saved currency symbol and decimal mark. That module holds the display locale as module state precisely so that no other call site has to thread it, which makes this the one place it is set;resolve the palette by name and hand it to
Selkie::Appat construction, so the login screen is themed from the first frame rather than starting stock and snapping over once a budget opens;show
Screen::Loginand wait for an unlock or a create;on submit, claim the handshake and show a non-dismissable progress modal, so repeated Enter presses cannot queue a second login;
open and migrate the
DBon a worker, while a child Rakudo warmsService::WorkspaceandScreen::Maincompilation;on success, hand DB ownership to the UI thread, build the workspace and main screen there, and switch to it;
on a create, ask the new budget's owner when their period starts, over the empty budget the shell has just come up on.
The two things that can go wrong at the boundary
DB.connect answers with a Failure (see below).
Service::Workspace.new throws, and for exactly one reason: the
file's budget_meta.period_scheme holds something the engine cannot
read. Both land on the login screen's status line and neither switches
screens ā the login screen is still the current one either way, so the
user is looking at the message in the place they can act on it.
Refusing to open, rather than falling back to the calendar month, is the engine's ruling and this is only the other end of it: a budget opened under a scheme its owner never chose buckets every derived figure by the wrong windows and then refuses every write, which is a much worse afternoon than a file that will not open and says why.
The period question is asked after creation, not during it
The create-a-budget form is 24 rows tall on the nose, which is the
height of the terminal it has to fit; a scheme picker does not go in
it. So !handle-create hands :fresh to !show-main, which opens
the period picker in its first-run mode once the shell is up. The
budget behind that dialog is empty, so whatever is chosen re-buckets
nothing, and Esc means "the calendar month" ā which is what an
untouched file already says.
Nothing here knows anything about budgets, envelopes or periods. The login screen knows nothing about databases. This class is the only place the two meet, which is what keeps the DB handshake ā the one step that can fail in a way the user has to understand ā in a single readable method.
The sqlcipher ordering rule
App::Moneymoor::DB deliberately does not use MacOS::NativeLib:
it is a macOS-only distribution and depending on it inside the engine
would make every Linux consumer install it. The obligation therefore
falls on the entry point, which must load the shim before the first
use App::Moneymoor::DB ā including the transitive one through this
module. bin/moneymoor does it in a BEGIN block, guarded on the
symlink already existing (use MacOS::NativeLib shells out to
brew config per library, which costs about a second per launch).
Failure, not exceptions
DB.connect answers with a Failure and never throws, with two
distinct messages: "Not an encrypted budget file (plain SQLite
file)" and "Invalid passphrase (or corrupted budget file)". Both
go to the login screen's status line verbatim ā the difference between
"you typed the wrong passphrase" and "you opened the wrong file"
decides what the user does next, and a generic "login failed" would
have them retyping a passphrase that was correct all along.
Every handled Failure is then .so'd. A Failure that is never
defused re-throws when it is next sunk, which in a TUI means a crash
several frames later with a backtrace pointing at innocent code.
EXAMPLES
Driving it against a scratch data home
MONEYMOOR_HOME moves the config file, the budget list and the error
log together, so a throwaway run cannot touch a real budget:
MONEYMOOR_HOME=/tmp/mm-demo raku -I lib bin/moneymoor
SEE ALSO
App::Moneymoor::Config ā the data home and the four display settings.
App::Moneymoor::Util::Money ā the display locale this class installs.
App::Moneymoor::Screen::Login ā the dialog this drives.
App::Moneymoor::Screen::Main ā what it hands over to.