_open_directive_exists hardcoded '^YYYY-MM-DD open ' (dash-only, 2-digit,
single-space), but Beancount's DATE token (parser/lexer.l) is
(17|18|19|20)[0-9]{2}[-/][0-9]+[-/][0-9]+ and inter-token whitespace is any
[ \t\r] run. So a validly-formatted existing Open written as '2024/3/5 open X'
or '2020-01-01 open X' escaped detection → duplicate Open appended →
bean-check rejects the file. Anchor on Beancount's actual date pattern and
[ \t]+ separators. Adds parametrized coverage for slash/single-digit/multi-
space/tab variants.
Found in a coherence pass over the Beancount source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When add_account reported the Open already existed, the endpoint raised
409 before the DB-mirror step — so an account present in the ledger but
missing from libra's DB (a prior sync failure with no cross-DB atomicity,
or an out-of-band open) was stranded: invisible to permissions with no
recovery path. Now 409 only when the account is already in the DB too;
otherwise sync it and return success. Adds a recovery test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
get_or_create_user_account opened per-user receivable/payable accounts
constrained to EUR/SATS/USD, so a posting in any other currency tripped
'Invalid currency CAD/GBP/JPY for account Assets:Receivable:User-…' at
bean-check — the exact errors the optional-currencies work set out to fix,
which had only reached the admin chart-account path. Open user accounts
unconstrained (currencies=None) so they hold arbitrary fiat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The existence check matched 'open <name>' anywhere in the chart source,
so a prior account's description metadata or a comment mentioning the
name produced a false 409, while a real directive with an inline comment
and no space ('open X;legacy') was missed → a duplicate Open was appended
and bean-check then rejected the file, breaking every later /api/source
write. Extract the check into a pure _open_directive_exists() anchored to
'^YYYY-MM-DD open <name>' with an account-boundary negative-lookahead, and
unit-test both failure directions plus prefix/child non-matches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test harness was never updated to the post-server-deploy#4 split ledger
layout, so libra's per-user account opens (routed to accounts/users.beancount
by fava_client._infer_target_file) 500'd as a 'non-source file' and fell back
to DB-only — breaking the balance test and contributing to settlement errors.
Make the harness ledger a faithful split (root includes accounts/chart.beancount
+ accounts/users.beancount; title stays in root so the slug still matches).
Also raise lnbits_rate_limit_no for the session: the full suite fires >200
req/min and the default limiter 429'd fixture setup intermittently (10-11
errors). The limiter is built once at app creation, so setting it in the
session settings fixture (before the app fixture) disables it suite-wide.
Net: full suite goes from 1 failed / ~10 errors to fully green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The endpoint only checked the root prefix, so a direct API call (bypassing
the UI) could write a malformed Open directive into the ledger source.
Add _validate_account_name mirroring Beancount's core/account.py grammar
(root [\p{Lu}][\p{L}\p{Nd}-]*, sub [\p{Lu}\p{Nd}][\p{L}\p{Nd}-]*, >=1
sub-account) — verified to match beancount.core.account.is_valid across
20 cases incl. Unicode, digit-start subs, hyphens. Align the client
segment regex to the same rule (was ASCII-only, rejected valid names).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Free-typing the full hierarchical name let admins fat-finger the parent
(wrong/invalid root). Replace the single name field with a required
Account Type select (the 5 valid roots, mirroring _VALID_ACCOUNT_PREFIXES)
plus a sub-account input, a live 'Will create: ...' preview, and
per-segment validation (each part must be a capitalized Beancount
account component). The root prefix is now structurally guaranteed valid.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add_account no-ops if the Open directive is already present but returned
a normal-looking dict, so the admin endpoint reported success ('created
(sync pending)') for a duplicate. Return an already_existed flag and
raise 409 from the endpoint. Also anchor the existence check on the Open
directive with a trailing-boundary match so a prefix (Expenses:Gas)
doesn't match a longer sibling (Expenses:GasStation). The flag is
additive, so the idempotent user-account path keeps no-opping silently.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The UI omits currencies so the Open directive is written unconstrained,
but the model defaulted currencies to ["EUR","SATS","USD"], so Pydantic
refilled them and the endpoint passed the constraint through — every
admin-created account got a currency-constrained Open (which would
reject postings in other currencies, the same CAD/GBP/JPY bean-check
class we hit on user accounts). Default to None so omission reaches
add_account and the directive is unconstrained; an explicit list still
works for API callers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface the existing POST /api/v1/admin/accounts endpoint in the UI: a
super-user-only 'Add Account' button on the Chart of Accounts card opens
a dialog for the hierarchical account name + optional description, posts
with the wallet admin key (require_super_user), then reloads accounts.
Client-side prefix validation mirrors the server's _VALID_ACCOUNT_PREFIXES.
No currency input — an Open directive does not require currency constraints.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
add_account wrote free-text metadata values straight into the ledger
source via /api/source with no escaping — an unescaped quote or newline
in an admin-supplied description would corrupt the Beancount file (or
forge extra metadata lines). Escape backslash/quote/newline per the
tokenizer's cunescape rules (verified round-trip through beancount's
parser). Also make the currency constraint list optional so an Open
directive can be written unconstrained (currencies are an optional
part of the directive, not required).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The suite targets the lnbits dev worktree (needs lnbits.core.signers)
and trips on three non-obvious environment requirements, each of which
cost a failed run today: LNBITS_EXTENSIONS_PATH is the parent of an
extensions/ dir, the data folder must be a fresh temp dir per run, and
lnbits dev mandates LNBITS_KEY_MASTER at boot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approving a pending entry created with a reference (e.g. invoice
"42-144") 404'd with "Pending entry unknown not found": the list
endpoints recovered the entry id by parsing links for a libra- prefix,
but reference-bearing entries displace that link with the fused
"{reference}-{entry_id}" form, so the id surfaced as the literal
"unknown" and the approve call round-tripped it.
Make the entry-id transaction metadata the single canonical identity:
- _extract_entry_id() resolves metadata-first (libra- link parsing kept
only for pre-dfdcc44 ledger history); used by /entries/user,
/entries/pending, approve, and reject.
- Creation endpoints no longer fuse the reference with the entry id —
the user reference becomes its own sanitized link and round-trips
verbatim in API responses. Typed exp-/rcv-/inc- links stay as the
settlement-tracking handles.
- format_revenue_entry now writes entry-id metadata like its siblings
and sanitizes its reference link (was appended raw); generic
POST /entries sanitizes its reference link too.
- User-journal reference extraction skips all system link prefixes
(typed links used to leak into the reference field).
Contract documented in CLAUDE.md (Data Integrity → Entry Identity &
Links), pinned by tests/test_entry_identity_api.py and formatter
contract tests in test_unit.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the caller omits settled_entry_links (the default), the endpoint
auto-detects open entries across both directions for the user and writes
a single transaction that:
- Zeros every per-user account that has an open balance, not just the
net (the libra-#33 bug — previously the 2-leg form left both Payable
and Receivable carrying non-zero balances after a complete cash
settlement, while only netting the cash side).
- Routes any cash above the net obligation to Liabilities:Credit:User-X
(libra-#41), so over-payment lands on a real liability account
instead of silently drifting.
- Attaches every reconciled source entry's link
(exp-..., rcv-...) so a reader scanning the settlement transaction
can trace what it cleared.
Cash less than the net obligation, with no explicit links, returns 400
with a structured diff (cash_paid, net_obligation, receivable_total,
payable_total). The operator either pays the exact net or passes
settled_entry_links to settle a specific subset; partial settlement
without a coherent target is not silently absorbed.
The legacy explicit-links code path is unchanged — callers that pass
settled_entry_links keep the 2-leg shape with no auto-detection. None
of the callers in libra or aiolabs/webapp currently use that field, but
the contract is preserved for the partial-settle-of-specific-entries
flow.
format_fiat_net_settlement_entry is the new helper for the 2/3/4-leg
shape; it enforces the cash-balance constraint inline so callers can't
accidentally produce an unbalanced transaction.
tests/test_settlement_api.py (6 tests) locks in:
- Nancy's #33 scenario: receivable 100 + payable 50 + cash 50
zeros both per-user accounts, links both source entries
- Overpay: cash 70 against net 50 → credit balance 20
- Pure receivable overpay → credit appears
- Underpay without explicit links → 400 with diff
- No open receivables → 400 with hint pointing at /payables/pay
- Explicit settled_entry_links uses legacy 2-leg path
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends get_user_balance_bql and get_all_user_balances_bql to fold
Liabilities:Credit:User-X into the same query as Payable and Receivable.
Credit is the overpay-absorbing liability that libra owes the user going
forward — it carries the same sign as Payable, so the existing fiat
aggregation subtracts it from net obligation without further changes.
Adds UserBalance.account_balances to surface the BQL per-account
breakdown so libra extension UI and webapp can render Payable /
Receivable / Credit as distinct line items. The legacy `accounts` field
stays empty for back-compat with anything reading the older shape.
Prepares for libra-#33 / libra-#41: settlement netting (#14 task) will
write the overflow leg to credit; this changeset makes sure that, the
moment credit exists, the displayed net everywhere already reflects it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
113 passing tests + 3 skipped + 8 xfailed across 10 files, covering user
expense and income flow, admin receivable/revenue, settings + auth gates,
void/reject, manual payment requests, balance display, Lightning auth
paths, reconciliation API, and pure-function units. Runs against a real
Fava subprocess and full LNbits app via asgi_lifespan; the harness
captures the auth-flow / settings / env-var disciplines surfaced during
build-out (see tests/README.md and tests/conftest.py docstring).
Eight xfailed/skipped tests carry full implementations gated behind issues
#38, #39, #40 — they flip back on automatically when those land.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The admin transactions table assumed voided entries used flag='x', but
the libra reject convention keeps the '!' flag and appends a 'voided'
tag. Without this, the dashboard rendered voided rows as orange 'Pending'
once they started reaching it. Detect via tag and give the voided icon
precedence over the flag-based branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /api/v1/entries/user view was silently dropping any transaction tagged
'voided', so users couldn't see entries that had been rejected against
their accounts. Per the libra reject convention, voided entries keep the
'!' flag and carry a 'voided' tag for audit; clients can use the tag to
style them distinctly. Pending-approval listing still filters voided.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original "find last Open directive, insert after its metadata" logic
was a clever optimisation for the monolithic ledger where opens, txns,
and assertions all lived in one file -- you wanted new opens grouped with
existing opens, not appended after a long transaction tail.
Post-split, each include file has one mutation profile:
- accounts/chart.beancount: only Open directives
- accounts/users.beancount: only Open directives
- transactions.beancount: only Transactions
There is no longer a content shape that benefits from mid-file insertion;
the existing heuristic also had a pre-existing bug where it only matched
'open ' OR '{current_year}-' as line prefixes, so 1970-* seed opens were
invisible and the search "stuck" to the first current-year line in the
file (which on aio-demo ended up being the wrong place).
Drop the search; always append. Simpler, chronological, append-only
friendly.
Refs: aiolabs/libra#28
Three small fixes shaken out by live testing on aio-demo:
1. fava_client.add_account: when the target file has no Open directives
yet (e.g. the empty accounts/users.beancount seed), append at end of
file instead of inserting at index 0. Keeps the seed header comments
at the top where they belong.
2. account_sync.sync_single_account_from_beancount: read the full user_id
from Beancount metadata when present, fall back to the name-derived
8-char prefix otherwise. crud.get_or_create_user_account writes the
full 32-char user_id into Beancount metadata when creating per-user
accounts; the sync function was only looking at the account name and
returning the prefix, so the post-sync `WHERE user_id=:user_id` query
in crud.py missed the row and fell through the UNIQUE-constraint
recovery path. Three lines of warning noise per user-account creation.
3. tasks.wait_for_account_sync: await `wait_for_fava_client()` (new
helper backed by an asyncio.Event in fava_client.py) before the first
sync iteration. Previously the sync task started in libra_start()
raced the fire-and-forget `_init_fava()` coroutine and reliably
crashed the first run with "Fava client not initialized".
Refs: aiolabs/libra#28
Fava's /api/source endpoint rejects relative paths with HTTP 500
(NonSourceFileError: "Trying to read a non-source file at '...'"). The
include-aware `_infer_target_file` helper returns relative paths
(e.g. "accounts/users.beancount"), so add a `_resolve_target_file`
hook that prepends the ledger root directory.
The dirname is derived from a one-time GET /api/options and cached on
the FavaClient instance (which is a module-level singleton), guarded by
an asyncio.Lock so concurrent first-callers don't double-fetch.
Absolute paths pass through unchanged, so the admin endpoint that
explicitly passes target_file="accounts/chart.beancount" works the same
as one that passes "/var/lib/fava/accounts/chart.beancount".
Verified against aio-demo's live fava: relative paths now produce
HTTP 200 reads on options.beancount, accounts/chart.beancount,
accounts/users.beancount, and transactions.beancount.
Refs: aiolabs/libra#28
Companion to the fava ledger split (aiolabs/server-deploy#4). Super-user
endpoint that adds a new Open directive to accounts/chart.beancount via
fava_client.add_account (explicit target_file), then mirrors the account
into Libra's DB via sync_single_account_from_beancount so permissions can
be granted on it.
Validates the account name against the five Beancount top-level prefixes
(Assets:/Liabilities:/Equity:/Income:/Expenses:) and returns 400 on a bad
prefix.
Per-user accounts (matching :User-xxxxxxxx) keep their existing code path
via crud.get_or_create_user_account, which inherits the inferred target_file
(accounts/users.beancount) from the add_account default.
Backend only -- the LNbits admin UI on top is tracked separately as
aiolabs/libra#30.
Refs: aiolabs/libra#29
The Fava-backed ledger is being split into purpose-specific files (see
aiolabs/server-deploy#4): accounts/chart.beancount for static + admin-managed
opens, accounts/users.beancount for libra-appended per-user opens.
Add a `target_file` parameter to `add_account` that defaults to inference
from the account name (`:User-[0-9a-f]{8}$` -> users.beancount, otherwise
chart.beancount). Drop the now-redundant `GET /api/options` call that was
only used to discover the root file path. Callers that need explicit
control (e.g. the upcoming admin chart-edit endpoint) can pass
`target_file=` directly.
The retry loop, write lock, and insertion-point search are unchanged --
each included file is a self-contained source the existing logic operates
on cleanly.
Refs: aiolabs/libra#28
The previous journal_entries/entry_lines local mirror was removed during
the Fava migration but the docs still described it as a local cache.
Replace with explicit statement that Fava is canonical and the remaining
SQLite tables hold orthogonal operational state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Twenty-six endpoints documented "(admin only)" were using
require_admin_key, which only checks the caller owns a wallet with
its admin key — not LNbits-instance admin. Any logged-in user could
fabricate receivables against any other user_id, grant themselves
MANAGE permission on any account, create + self-assign privileged
roles, etc.
Swaps Depends(require_admin_key) -> Depends(require_super_user) on:
receivable/revenue creation, equity-eligibility grant/revoke/list,
permission grant/list/revoke/bulk/bulk-grant, account-sync admin,
role + role-permission + user-role CRUD, cross-user contributions
and unsettled-entries reports.
Also deletes the unsafe duplicate /api/v1/pay-user — both function
defs shared the name api_pay_user, the second shadowed the first at
module scope but FastAPI registered both routes. /api/v1/payables/pay
already provides the super-user-gated equivalent.
Two pre-existing orphan wallet.wallet.user references inside
api_settle_receivable and api_approve_manual_payment_request (both
already used the auth parameter) would have raised NameError at
runtime; fixed in passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a user has entries in multiple currencies that go in opposite
directions — e.g. an income entry in EUR (user owes the org) and an
expense entry in CAD (org owes user) — the previous row collapsed
both into a single "Owes you" / "You owe" label driven by the net
sats balance. The fiat amounts were displayed via Math.abs(), hiding
the per-currency signs the backend already returns, so the row was
actively misleading: it showed €200 and CA$300 under one direction
when in reality they point in opposite directions.
Render up to two grouped lines instead — "Owes you €200.00" and
"You owe CA$300.00" — using new owesYouFiat / youOweFiat helpers
that filter the signed fiat_balances dict by sign. Net sats stays
as a small caption with an explicit "(receivable)"/"(payable)"
qualifier, since sats can be netted but distinct fiat currencies
can't without a spot rate. Falls back to the old single-line render
when there are no fiat balances (sats-only entries).
New get_user_lifetime_totals_bql() runs tag-filtered BQL queries
(Payable + expense-entry, Receivable + income-entry) to compute
per-user lifetime totals separately from the net balance. Plumbed
through /api/v1/balance and /api/v1/balance/{user_id}; existing
clients keep working (fields default to zero / empty dict).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With both kinds of entry sharing the Pending Approvals list, the row
alone didn't tell the reviewer which direction the accounting goes.
Adds a small green INCOME / red EXPENSE badge as a caption line above
the description (so it doesn't compete with description wrapping),
driven by an isIncomeEntry(entry) helper that reads the Beancount tag
set the API already returns. Also drops the now-redundant orange
pending-icon avatar — the card title already says these are pending,
and the badge does the heavier lifting.
Both expense and income entries land in the same pending list (the
backend's /entries/pending endpoint already returns all pending
transactions regardless of type, and approve/reject is type-agnostic),
so the expense-specific title was misleading once income approval
shipped in #13.
closeViewRoleDialog already clears rolePermissionsForView and
roleUsersForView; closeRoleDialog (used by both Edit and Create flows)
did not. With editRole now populating those arrays, leftover state
would otherwise survive a close → open-Create round trip. The Create
template branch doesn't read the arrays today (v-if guarded on
editingRole), so this is defensive — keeps the two close handlers
symmetrical and avoids future regressions if the Create branch ever
starts referencing them.
The dialog reads from rolePermissionsForView / roleUsersForView, but
editRole(role) only ever populated the form fields and showed the
dialog — those arrays were left at whatever state the rest of the page
had set them to. Result: opening Edit Role for a role with existing
permissions showed "No permissions assigned to this role yet", and the
list only "appeared" because adding a permission triggered a refresh.
Mirror viewRole's pattern: clear both arrays, GET /admin/roles/{id},
populate from the response, then show the dialog after $nextTick.
Closes#14
When a user submits income, the money is physically in *their* pocket,
not the entity's cash drawer. The original income endpoint posted DR
on a configurable payment-method asset account (Cash/Bank/Lightning),
which implicitly assumed the entity already had the funds.
Mirror the expense flow instead: DR Assets:Receivable:User-{id[:8]}
(via get_or_create_user_account), CR the revenue account. The user
now owes the entity until they hand the cash over via the existing
/settle-receivable workflow. With this, the per-user Outstanding
Balances card correctly nets expenses (entity owes user, -liability)
against income receipts (user owes entity, +receivable).
Drops payment_method_account from IncomeEntry — no longer needed.
Adds the new permission type to the grant/bulk-grant dialog dropdown
(static/js/permissions.js) so admins can grant 'Submit Income' on
revenue accounts the same way they grant 'Submit Expense' on expense
accounts. Without this, the backend's SUBMIT_INCOME check on the new
income endpoint is ungranted-able from the UI and users see a 403.
Uses 'teal' + the 'payments' icon to distinguish income-grant badges
from green-and-add_circle expense-grant badges in the role/account
permission lists. Also updates a stale comment in migrations.py
listing the valid permission_type values.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the existing expense submission flow so non-admin users can log
income on behalf of the organization for super-user review. New endpoint
POST /api/v1/entries/income takes invoice-key auth, creates a Beancount
transaction with the pending '!' flag, and reuses the existing
/entries/{id}/approve and /reject endpoints (which match by libra-{id}
link regardless of entry type).
Adds PermissionType.SUBMIT_INCOME granted on revenue accounts (parallel
to SUBMIT_EXPENSE on expense accounts) rather than overloading
SUBMIT_EXPENSE — the two operations target distinct account types and
should be grantable independently. Enforces AccountType.REVENUE on the
income account and AccountType.ASSET on the payment-method account;
fiat currency is required (matches the expense flow's effective
requirement). Income entries get a 'income-entry' tag and an
^inc-{entry_id} link for tracking, and surface in the existing
/entries/pending list for super-user approval.
UI work lives in the standalone webapp, out of scope here.
Closes#9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Recent Transactions card was showing Beancount-generated opening-balance
entries from ledger summarization (flag 'S'). Adds a _SYNTHETIC_FLAGS set
mirroring Fava's _EXCL_FLAGS (S/T/C/P/U/R/M) and skips matching entries in
the two user-facing endpoints that previously only filtered by transaction
type. Other journal callers already filter by flag '!' so are unaffected.
Closes#3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces entity-sense references to "the Libra" with "the
organization"/"the collective" where Libra was being used as a
stand-in for the original "Castle" entity, and drops the redundant
"(like cooperatives)" parenthetical in DOCUMENTATION.md. Also swaps
the 🏰 emoji in the import helper for ⚖️.
Closes#12
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the existing POST /api/v1/admin/accounts/sync endpoint into the
Castle index toolbar (sync icon between permissions and settings).
Surfaces sync stats (added/reactivated/deactivated/virtual_parents/errors)
via a Quasar notification and refreshes the accounts list on success.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
castle_start() was using CastleSettings() defaults (slug=castle-ledger)
instead of reading the saved settings from the database. This caused all
Fava queries to 404 on instances where the ledger slug differs from the
default (e.g. demo-ledger).
Now loads settings from extension_settings table at startup, falling
back to defaults only if no saved settings exist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous BQL query (SELECT DISTINCT account) only returned accounts
with postings, missing all accounts that were opened but had no
transactions yet. On a fresh ledger this returned 0 accounts, causing
the account sync to deactivate everything.
Now uses Fava's balance_sheet and income_statement API endpoints which
return the full account tree including zero-balance accounts. Falls back
to BQL if the tree endpoints fail.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The BQL queries in get_user_balance_bql() and get_all_user_balances_bql()
used GROUP BY account without currency, causing sum(number) to add EUR
face values from expense entries (EUR @@ SATS notation) with SATS face
values from payment entries (plain SATS). This inflated displayed fiat
amounts by orders of magnitude for users with settlement payments.
Fix: add currency to GROUP BY so EUR and SATS rows are separate, use
sum(weight) for net SATS (correct across all entry formats), and scale
fiat proportionally for partial settlements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix default amount showing fiat instead of sats when lightning payment selected
- Fix invoice response field name (bolt11 instead of payment_request)
- Fix NameError in payables/pay endpoint (wallet -> auth.user_id)
- Add get_user_wallet_settings_by_prefix() for truncated 8-char user IDs
- Update user-wallet endpoint to handle truncated IDs from Beancount accounts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Fava /context endpoint returns structured entry data, not raw source
text with slice/sha256sum as expected. Updated both endpoints to:
1. Get entry metadata (filename, lineno) from the parsed entry
2. Read the full source file via GET /source
3. Modify the specific line at the entry's line number
4. Write back via PUT /source with sha256sum for concurrency control
- Approve: Changes flag from '!' to '*' at the entry line
- Reject: Adds #voided tag to the entry line
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Fava URL, ledger slug, and timeout settings to super admin Settings dialog
- Reinitialize Fava client when settings are updated via services.py
- Add settingsLoaded flag to prevent race conditions where wrong toolbar
buttons appeared before isSuperUser was determined
- Remove premature Vue mount() call from permissions.js that caused
"Cannot read properties of undefined (reading 'user')" error
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>