Ledgerly v1.1.0
·
157 commits
to ledgerly-app
since this release
The first release after v1.0.0. It closes the gaps that stopped Ledgerly
being usable as a daily finance app: things you could create but never
correct or remove, a scheduling engine with no way to reach it, and the
rough edges — a bare framework 404, no way to change your password — that
made a finished product feel unfinished.
No breaking changes. All four migrations add nullable or defaulted
columns and are safe to apply to an existing database.
Added
- Recurring transactions — subscriptions, rent, salary. Tick "Repeat
this" when adding an expense or income and the schedule is created with
it; manage everything from Settings → Recurring transactions (edit,
pause, resume, delete). The engine had existed since v1.0.0, but
nothing could create a rule, so no rule ever ran. - Backup restore — a Ledgerly Backup
.jsonexport can be restored
from the Import Center. Additive only: accounts are matched by (name,
type) and categories by (name, kind); only missing ones are created and
nothing existing is overwritten. Every restore is oneImportBatch,
undoable in a click, and the preview names each backup section it does
not restore. Seedocs/backup.md. - Account archive, restore and delete — an account nothing references
is deleted outright; one with history is archived instead, keeping its
transactions, balance and card details, and restorable at any time.
Archived accounts leave the pickers but stay reachable under Accounts →
Archived, with a link to their transactions. - Bills: edit and delete — including a Settled bills section for
one-off bills already paid, which previously disappeared from the app
entirely. Deleting a bill removes the reminder only; the payment it
recorded stays in your transactions. - Budgets: edit and delete — changing a limit keeps the month's
spending and re-evaluates alerts against the new figure; deleting
removes the budgeting layer and nothing else. - Profile settings — change your display name and your password from
inside the app, independently of the forgotten-password email flow.
Email is intentionally read-only for now, with the reason shown. - Error and not-found screens — a failed page keeps the navigation
and offers Try again / Go to Dashboard; an unknown URL gets a branded
page instead of the framework default. No stack traces or internal
messages are ever shown. - Transaction filtering by account —
?account=on the ledger,
matching both sides of a transfer. - Import Center in the navigation — desktop sidebar and mobile More
sheet, alongside the existing Settings entry.
Improved
- Account pickers are grouped by funding type (Cash, Bank, Wallet,
Credit card, Investment), and credit cards show their Card Vault
identity at the point of choice. - Accessibility — labels added wherever repeated controls shared one
visible label: per-budget Edit/Delete buttons, per-account rename
fields, amount inputs, and the bill name field. - Mobile — verified free of horizontal overflow at 390px across
Accounts, Bills, Budgets, Settings and the import wizard. - Wording — destructive confirmations now say what survives ("3
past transactions stay", "the payment you already recorded stays in
your transactions"), because the fear when deleting a bill or a budget
is that it takes the money with it. - Performance — profiled against a production build: 15–22 ms time to
first byte and 52–102 ms full server render across every route, on a
lean 102 kB shared JS bundle.
Fixed
- Restored account balances were silently wrong. A newly created
account was seeded with the backup's exported closing balance and
then had every transaction in that backup replayed onto it, counting
each one twice. Accounts now start at their opening position, restoring
the documented invariantbalance = openingBalance + the ledger sum. - One bad recurring rule could stop the nightly job entirely. A
template referencing a deleted account raised an error that escaped the
loop, skipping every remaining rule for every user — and the balance
reconciliation that follows it — silently, every night. Failures are
now isolated per rule and reported. - Month-end schedules drifted downward permanently, in both recurring
rules and bills: the 31st became Feb 28 and then stayed on the 28th
forever. Schedules now carry an anchor day, so 31 becomes Feb 28 and
then Mar 31 again. Existing rules and bills keep their current dates
rather than being silently rescheduled. - Undo of a restore could delete an account still in use. Its
reference check excluded rows whereimportBatchIdis NULL — under SQL
three-valued logic that means every hand-entered transaction. - Budget alerts outlived the budgets they described. Threshold
notifications link to a budget only by a key string, so nothing
cascaded: deleting a budget left its alerts in the notification centre,
and a stale alert also suppressed the correct one. Alerts are now
cleared on delete and re-evaluated when a limit changes. - A card-funded loan could silently lose its funding source.
LoanEntry.accountIdisonDelete: SetNull, so hard-deleting an
account would not have failed — it would have blanked the loan's
funding source, and with it the card billing history. - A restore preview could promise more rows than the commit delivered,
because preview and commit resolved backup ids independently. Both now
derive from one shared plan, so the two cannot disagree. - One malformed date aborted an entire restore instead of rejecting
that single row. - Credit cards restored with a debt were zeroed out, because a
positive-only amount helper was applied to balances, where zero and
negative are both meaningful. - Missed recurring occurrences recovered at one per day, so a
five-day outage took five more days to catch up. Catch-up now completes
in a single run, bounded to 60 occurrences.
Testing
- Unit: 282 tests across 23 files (up from 265), and the suite no
longer requires a database — verified by running it against an
unreachableDATABASE_URL. - Integration: 79 tests across 5 files — a new suite covering the
backup restore engine, recurring rules, accounts, bills and budgets
against real Postgres. - E2E: 25 suites, four of them new — account, bill, budget and
recurring-rule lifecycles. - CI runs again. Its triggers had been narrowed to a branch that does
not exist on this repository, leaving the workflow configured but
unreachable since 2026-07-19. It now also runs the integration suite in
a job with a Postgres service and migrations applied.
Migrations
Four, all additive and backward-compatible:
20260725120247_import_batch_created_entities— records what a restore
created, so undo can reverse it.20260725194241_recurring_rule_anchor_day— day-of-month anchor for
recurring schedules.20260725194832_recurring_rule_is_paused— pause and resume for
recurring rules.20260725231628_bill_anchor_day— day-of-month anchor for bill due
dates.
Known limitations
Deliberate deferrals, not defects:
- Backup restore covers the ledger only — accounts, categories and
transactions. Budgets, bills, lending entries, groups, settlements,
recurring rules and tags are exported for completeness but not
restored; the preview names them. - Khatabook import lands as transactions, not lending entries. The
wizard says so. Routing it into the lending module is the first
enhancement after this release. - Offline sync and shared expenses/collaboration have not had a
real-world validation pass — they need genuine multi-device and
multi-user scenarios, scheduled as Phase 3. - Email address changes are not supported in-app; doing it safely
needs a verification step that does not exist yet. - Some E2E suites need a retry against a cold dev server. Eight still
submit the sign-in form before React hydrates; the rest were fixed.