InspectorPane
NAME
App::Moneymoor::View::InspectorPane - the detail rail's lines, and the Explain dialog's, both derived from the one equation that defines an envelope's balance.
SYNOPSIS
use App::Moneymoor::View::InspectorPane;
# The rail beside the envelope table (fixed 30 columns, 26 inside the
# frame and its padding):
my @lines = inspector-lines($view, '2026-08-01', $groceries-id,
width => RAIL-TEXT-COLS, icons => $icons);
@linesΒ».<text>;
# ('Carry-in Β£0.00',
# 'Assigned Β£400.00',
# 'Activity -Β£72.50',
# 'Moved in Β£0.00',
# 'Moved out Β£0.00',
# 'ββββββββββββββββββββββββββ',
# 'Available Β£327.50')
# With a target, two or three more lines under it. The figures are
# gathered first, by the one sub in this module that knows what a
# Model::Category is:
my %target = target-figures($view, $scheme, $groceries, '2026-08-01');
inspector-lines($view, '2026-08-01', $groceries-id, :%target)Β».<text>.tail(2);
# ('Target Β£400.00', 'To fund +Β£72.50')
# A goal, which carries its schedule with it:
target-lines(target-figures($view, $scheme, $tax, '2026-12-01'))Β».<text>;
# ('Target Β£50,000.00',
# 'by April 2027 Β· 5 of 9',
# 'To fund +Β£2,777.77')
# Straight to a RichText, coloured per Β§2:
$rail.set-content(inspector-spans($view, $period, $id,
theme => $theme, icons => $icons));
# The Explain dialog is the same equation plus the derived moves:
explain-lines($view, '2026-08-01', $visa-payment-id,
names => %categories-by-name, width => 60)Β».<text>;
# (β¦ , '', 'Derived moves (2)',
# 'card coverage Groceries β Visa Β£72.50',
# 'card refund Visa β Dining Out Β£5.00')
cause-label('card-inflow-to-rta'); # 'card inflow β RTA'
DESCRIPTION
Pure line builders. Every function takes a BudgetView, a period and
a category id, and returns a list of < { text, severity } > hashes;
the *-spans wrappers turn those into Selkie::Widget::RichText
spans using App::Moneymoor::View::BudgetRow::severity-style, so the
rail, the grid and the Explain dialog cannot drift apart on colour.
Rule 1, written out
An envelope's balance is
available = carry-in + assigned + activity + moved-in - moved-out
and the rail is that sum, one term per line, with a rule and the total
underneath. moved-out is rendered negated for exactly that
reason: a column of figures that does not add up to the number at the
bottom is worse than no column at all.
moved-in and moved-out are the derivation's own credit-card
moves, not anything the user typed β which is why the rail says how
many there are and offers x to see them, rather than trying to
explain them in twenty-six columns.
The target block is not part of the equation
An envelope with a target gets two or three more lines under the
flags: what it wants, what its plan says about this period, and what
is still missing (To fund +Β£37.50) or that it has it. They sit
below the rule and the total on purpose. available is a derived
fact and the five terms above it are why it is what it is; a target is
a plan, and the derivation has never heard of one. Putting the target
inside the sum would make the column stop adding up, which is the one
thing the rail must never do.
Underfunded is not computed here
It used to be β max(0, target - available), in target-lines, and
that is only right for one of the three target kinds. The kind-aware
answer is App::Moneymoor::Service::Target's target-ask, and
there is exactly one of it in the app: the rail, the grid, f and
the assign field's bare = all ask the same function the same
question. What is still true is that the figure is never negative β
an envelope over its target is funded, not "minus Β£20 to fund".
β¦but the figures are gathered here, and rendered separately
target-figures is the seam. It takes the model and the scheme,
calls Service::Target four times and answers a flat Hash of
numbers and strings; target-lines takes that Hash and knows
nothing else. Two reasons for the split, and both of them are about
keeping this module honest:
Presentation stays testable without a budget. Every wording below β and there are now three kinds' worth β can be asserted against a hand-built
Hash, with no view, no facts and no schedule arithmetic in the way.The
Model::Categorystops at one function. Every other builder in this module still takes a view and an id, which is what lets the Explain dialog reuse them. The target block cannot: a kind is a property of the row, not of the derivation. So the model goes intotarget-figuresand comes no further.
What each kind's block says
| Kind | Line 1 | Line 2 | Line 3 | |
|---|---|---|---|---|
| refill | Target | Β£400.00 | To fund +Β£72.50 β or Target met | |
| set_aside | Target | Β£100.00 /period | To fund +Β£40.00 β or Funded this period | |
| by_period | Target | Β£50,000.00 | by April 2027 Β· 5 of 9 | To fund +Β£2,777.77 β or On track, or Target met |
set_aside says /period rather than Target met on the second
line because its question is per-period: Β£900 already saved does not
mean this period's Β£100 is done, and "met" would claim it does.
describe-target's full caption ("Set aside Β£100.00 each period") is
five columns too wide for the rail, so the rail says the same thing in
its own shorthand.
by_period's schedule line is packed to fit: the clauses are the
goal period ("by April 2027"), the position in the plan ("3 of 9") and,
when the goal repeats, how often ("every 3 periods"). They are joined
with a middle dot while they fit in $width and broken onto another
line when they do not. That is not decoration β a monthly/14
budget's period label is "14 Aug β 13 Sep 2026", which with a position
clause after it is thirty-two columns in a rail that has twenty-six.
Its last line tells three states apart: Target met when the
envelope is at or past the goal (the plan is done, whatever period
it is), On track when this period asks for nothing but the goal is
still ahead, and the amber To fund otherwise.
Why the width is a parameter
The rail is 30 cells wide, less two for the frame and two for its
padding: 26. The Explain dialog is a modal and much wider. Both want
the same lines with the money flushed right against a different edge,
so the width is an argument and RAIL-TEXT-COLS is just the default
the rail passes.
A label and figure that will not fit in the given width fall back to "label space figure" rather than being truncated β a squeezed layout that still says the number beats a tidy one that does not.
EXPORTS
inspector-lines($view, $period, $id, :$width, :%target, :$icons --List)>inspector-spans($view, $period, $id, :$theme!, :$width, :%target, :$icons --List)>equation-lines($view, $period, $id, :$width --List)>flag-lines($cm, :$width, :$icons --List)>target-figures($view, $scheme, $category, Str $period --Hash)> β the block's numbers and captions, or an emptyHashwhen there is no target. Tolerates an uncomputed view, a missing scheme and aCategorytype object.target-lines(%target, :$width --List)> β empty for an emptyHash.moves-footer($view, $period, $id --Str)> βStr(the type object) when the period has no derived moves for the category.explain-lines($view, $period, $id, :%names!, :$width, :$icons --List)>explain-spans(β¦, :$theme! --List)>cause-label(Str $cause --Str)>pad-line(Str $label, Str $value, Int $width --Str)>RAIL-COLS(30) /RAIL-TEXT-COLS(26)
SEE ALSO
App::Moneymoor::View::BudgetRow β the severity table these colours come from.
App::Moneymoor::Service::Budget β
CategoryPeriod,Moveand what acausemeans.App::Moneymoor::Service::Target β
target-ask,target-milestoneandtarget-cycle: the whole of what the target block knows.