feat(app): record a manual transaction from the Transactions screen#84
Merged
Conversation
Manual transactions could only be entered via MCP tools — the app had no way to record one, so every ad-hoc financial event meant opening Claude instead of the app. This adds an in-app record form on the Transactions screen, seated above the master-detail content and fed by that screen's existing account selector (no second account picker). The form sends the magnitude with its sign chosen solely by an Expense/Income toggle — the amount field's validator forbids a typed sign, so the toggle is the one source of direction. A live signed-amount preview shows the effect before submit. The daemon does not validate the amount at all, so the form is the sole gate on amount quality (rejecting zero, blank, and non-numeric). Status is always Reconciled; recurring and projected entries belong to the recurring/projection work. Shared formatAmount/statusLabel move into qml/txformat.js so the preview and the existing panel read one source, resolving the lift-to-shared-helper TODO. FinchClient gains a recordTransaction RPC path mirroring createAccount (re-entrancy guard, QFutureWatcher, verbatim InvalidArgument message). Closes #38 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The record form cleared a stale daemon error only when the name or amount changed. Editing the date or description, or flipping Expense/Income, left a rejected-field message visible while the user fixed the very field that caused it. Clear the error on every editable input, matching the clear-on-edit behavior the name and amount fields already had. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an in-app “manual transaction” recording workflow to the Qt Transactions screen, introducing a reusable record form component, shared transaction formatting helpers, and a new async FinchClient::recordTransaction RPC path (with tests around the form + embedding).
Changes:
- Embed a new
RecordTransactionFormabove the Transactions master/detail view, wired to the screen’s selected account and re-fetching transactions after a successful record. - Add
FinchClient::recordTransaction(...)with an in-progress guard, background RPC execution, and user-facing error propagation. - Add shared QML formatting helpers (
txformat.js) and Qt Quick Tests for the form and panel integration.
PR description checks (repo conventions):
- Overview section: Pass
- Scope cohesion: Pass
- No private tooling references in tracked files: Pass
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
CHANGELOG.md |
Notes the new Transactions-screen manual transaction recording feature. |
app/tests/tst_recordtransactionform.cpp |
Adds a Qt Quick Test entrypoint for the new form’s QML specs. |
app/tests/transactionspanel/tst_TransactionsPanel.qml |
Extends panel tests to cover embedded form wiring + post-record refetch. |
app/tests/recordtransactionform/tst_RecordTransactionForm.qml |
Adds direct form behavior tests (validation, sign handling, submit lifecycle). |
app/tests/MockFinchClient.qml |
Extends the mock client with record-transaction lifecycle and signals. |
app/src/finchclient.h |
Introduces record-transaction API surface, signals, watcher, and in-progress state. |
app/src/finchclient.cpp |
Implements async RecordTransaction RPC call + result handling and error mapping. |
app/qml/txformat.js |
Centralizes transaction status/amount formatting used by both panel and form. |
app/qml/TransactionsPanel.qml |
Imports shared formatting and embeds the new record form tied to selected account. |
app/qml/RecordTransactionForm.qml |
New self-contained transaction recording form component with validation + preview. |
app/CMakeLists.txt |
Adds QML resources and new test target/module for the record form. |
| // validate amount at all, so this form is the sole gate on amount quality. | ||
| readonly property bool amountValid: !isNaN(magnitude) && magnitude > 0 | ||
| // Math.round avoids float drift (12.34 * 100 -> 1233.9999...); the toggle applies the sign. | ||
| readonly property int signedCents: amountValid ? Math.round(magnitude * 100) * (expense ? -1 : 1) : 0 |
Owner
Author
There was a problem hiding this comment.
Fixed in 15cf462 — signedCents is now a JS number rather than a 32-bit int, so amounts past ~$21.5M marshal to the qlonglong exactly; added a ~$30M spec to guard it.
The form's signedCents was a QML int, which is 32-bit, while the client's amount and the proto field are 64-bit. An amount above ~$21.5M would overflow and record a truncated value — well within reach for a personal finance tool (a home purchase, a large brokerage transfer). Type it as a JS number so it marshals to the qlonglong amount exactly, and add a spec recording ~$30M that overflows a 32-bit int. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Manual transactions could previously only be entered through the MCP tools, so every ad-hoc financial event meant opening Claude instead of the app. This adds an in-app form on the Transactions screen to record a manual transaction against an account — enter a name, amount, and Expense/Income, with the date defaulting to today and a live signed-amount preview showing the effect before submit.
The form is seated above the master-detail content and fed by the screen's existing account selector, so there is no second account picker — the selected account is injected into a standalone, independently-testable component. Status is always Reconciled; recurring and projected entries belong to the recurring/projection work.
How it works
The Expense/Income toggle is the sole source of the amount's sign: the magnitude field's validator forbids a typed sign, so a user can't enter a value that fights the toggle. The daemon does not validate the amount at all, so the form's
canSubmitis the sole gate on amount quality — it rejects zero, blank, and non-numeric magnitudes, with the preview, the predicate, and the submit button all reading one shared derived-state chain so they can't disagree at a boundary.formatAmount/statusLabelmove into a sharedqml/txformat.jsso the new preview and the existing transactions panel read one source.FinchClientgains arecordTransactionpath mirroring the existingcreateAccountpath (re-entrancy guard, off-thread call, verbatim daemon validation message). The form, the client path, and the panel embedding are each covered by Qt Quick Test specs driving the real components against a mock client.Closes #38