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::Config from ~/.moneymoor/config.json;

  • point App::Moneymoor::Util::Money at 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::App at construction, so the login screen is themed from the first frame rather than starting stock and snapping over once a budget opens;

  • show Screen::Login and 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 DB on a worker, while a child Rakudo warms Service::Workspace and Screen::Main compilation;

  • 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 v0.4.2

YNAB-style envelope budgeting: a derivation engine

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

DBIish:ver<0.6.7>:auth<zef:raku-community-modules>Notcurses::Native:ver<0.6.5+>:auth<zef:apogee>Selkie:ver<0.16.0+>:auth<zef:apogee>JSON::Fast:ver<0.19>:auth<cpan:TIMOTIMO>MacOS::NativeLib:ver<0.0.6>:auth<zef:lizmat>

Test Dependencies

Provides

  • App::Moneymoor
  • App::Moneymoor::Config
  • App::Moneymoor::DB
  • App::Moneymoor::Gateway::Account
  • App::Moneymoor::Gateway::Assignment
  • App::Moneymoor::Gateway::Category
  • App::Moneymoor::Gateway::Payee
  • App::Moneymoor::Gateway::Transaction
  • App::Moneymoor::Handlers::Boot
  • App::Moneymoor::Model::Account
  • App::Moneymoor::Model::Assignment
  • App::Moneymoor::Model::Category
  • App::Moneymoor::Model::CategoryGroup
  • App::Moneymoor::Model::Payee
  • App::Moneymoor::Model::Split
  • App::Moneymoor::Model::Transaction
  • App::Moneymoor::Screen::Accounts
  • App::Moneymoor::Screen::Budget
  • App::Moneymoor::Screen::Login
  • App::Moneymoor::Screen::Main
  • App::Moneymoor::Screen::Main::Keybinds
  • App::Moneymoor::Screen::Main::Modals
  • App::Moneymoor::Screen::Main::Subscriptions
  • App::Moneymoor::Screen::Reports
  • App::Moneymoor::Service::Budget
  • App::Moneymoor::Service::Icons
  • App::Moneymoor::Service::Target
  • App::Moneymoor::Service::Workspace
  • App::Moneymoor::StoreHandlers
  • App::Moneymoor::Theme
  • App::Moneymoor::Theme::Catppuccin
  • App::Moneymoor::Theme::Dracula
  • App::Moneymoor::Theme::Everforest
  • App::Moneymoor::Theme::Gruvbox
  • App::Moneymoor::Theme::Kanagawa
  • App::Moneymoor::Theme::Monokai
  • App::Moneymoor::Theme::Nord
  • App::Moneymoor::Theme::OneDark
  • App::Moneymoor::Theme::RosePine
  • App::Moneymoor::Theme::Solarized
  • App::Moneymoor::Theme::TokyoNight
  • App::Moneymoor::Themes
  • App::Moneymoor::UI
  • App::Moneymoor::Util::Money
  • App::Moneymoor::Util::Period
  • App::Moneymoor::View::BudgetRow
  • App::Moneymoor::View::EmptyState
  • App::Moneymoor::View::HintBar
  • App::Moneymoor::View::InspectorPane
  • App::Moneymoor::View::ModalChrome
  • App::Moneymoor::View::RegisterRow
  • App::Moneymoor::View::ReportRow
  • App::Moneymoor::Widget::BannerBar
  • App::Moneymoor::Widget::BootProgressModal

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite — the markup and publishing tools behind this site.