Summary
OutstandingsScreen discards every structured field of a TallyCommandError except its message. The
error code, retry semantics, remediation text and tally_state_may_have_changed are all dropped at
the catch boundary, and the load button stays enabled — so an operator can re-run a read that is
guaranteed to fail identically.
Pre-existing, not a regression. Both the catch block and operatorMessage date to 7bd6123
(#106). It is filed now because #177 introduced the first error that carries genuinely useful
remediation and retry semantics, which is what makes the loss matter.
The mechanism
src/OutstandingsScreen.tsx, the load path:
} catch (cause) {
if (requestVersion.current !== version) return;
setLoadedResult(null);
setError(operatorMessage(cause));
}
and operatorMessage keeps only the message:
function operatorMessage(cause: unknown) {
if (cause && typeof cause === "object" && "message" in cause && typeof cause.message === "string") {
return cause.message;
}
return typeof cause === "string" ? cause : "The local Tally read did not complete.";
}
The load button has no notion of a non-retryable outcome:
<button type="button" onClick={load} disabled={loading || !requestedAsOf}>
What is lost, concretely
The single-company forex path returns a fully populated envelope from tally_runtime_command_error
in src-tauri/src/commands.rs:
| field |
value |
reaches the operator? |
| message |
"Tally reported a foreign-currency closing balance for ledger FX USD Debtor 02; Bridge left the company unread rather than guessing a base-currency amount." |
yes |
| code |
company_foreign_currency_ledger_balance |
no |
| retry |
after_change |
no |
| remediation |
"Inspect the named ledger in Tally and use a base-currency company for the native outstandings read." |
no |
tally_state_may_have_changed |
true |
no |
So the operator is told what happened but not what to do about it, and the UI invites them to try
again when the code has explicitly said not to until something changes.
Impact
No wrong number reaches anyone — the read fails closed regardless, and the informative half of
the message does display. This is a remediation-and-retry quality defect, not a correctness one.
It applies to every TallyCommandError, not only the forex case; the forex diagnostic is simply the
first one where the discarded fields carry real value.
Note the asymmetry with the bulk sweep: fetch_tally_outstandings_all_companies returns a typed
partial per company, so AllClientsScreen retains the reason code. The single-company path returns a
rejected command instead, and that is where the structure is lost.
Acceptance criteria
- Preserve the forex condition (and other non-retryable outcomes) as an in-band result, or
interpret the error envelope explicitly in the single-company screen — either is acceptable
- Surface the remediation text alongside the message
- Disable or visibly re-scope the load button when
retry is after_change, so a guaranteed-to-fail
re-read is not invited
- Do not weaken the fail-closed behaviour; the company must still be left unread
- No
_ arm when matching the error enum
Reproduction
BRIDGE CORPUS FOREX on the single-company outstandings screen. Its FX USD Debtor 02 ledger
returns a composite CLOSINGBALANCE; captured bytes are committed as
ledgers_forex_composite_live.utf16le.xml with provenance.
Summary
OutstandingsScreendiscards every structured field of aTallyCommandErrorexcept its message. Theerror code,
retrysemantics, remediation text andtally_state_may_have_changedare all dropped atthe catch boundary, and the load button stays enabled — so an operator can re-run a read that is
guaranteed to fail identically.
Pre-existing, not a regression. Both the catch block and
operatorMessagedate to7bd6123(#106). It is filed now because #177 introduced the first error that carries genuinely useful
remediation and retry semantics, which is what makes the loss matter.
The mechanism
src/OutstandingsScreen.tsx, the load path:and
operatorMessagekeeps only the message:The load button has no notion of a non-retryable outcome:
What is lost, concretely
The single-company forex path returns a fully populated envelope from
tally_runtime_command_errorin
src-tauri/src/commands.rs:FX USD Debtor 02; Bridge left the company unread rather than guessing a base-currency amount."company_foreign_currency_ledger_balanceafter_changetally_state_may_have_changedtrueSo the operator is told what happened but not what to do about it, and the UI invites them to try
again when the code has explicitly said not to until something changes.
Impact
No wrong number reaches anyone — the read fails closed regardless, and the informative half of
the message does display. This is a remediation-and-retry quality defect, not a correctness one.
It applies to every
TallyCommandError, not only the forex case; the forex diagnostic is simply thefirst one where the discarded fields carry real value.
Note the asymmetry with the bulk sweep:
fetch_tally_outstandings_all_companiesreturns a typedpartial per company, so
AllClientsScreenretains the reason code. The single-company path returns arejected command instead, and that is where the structure is lost.
Acceptance criteria
interpret the error envelope explicitly in the single-company screen — either is acceptable
retryisafter_change, so a guaranteed-to-failre-read is not invited
_arm when matching the error enumReproduction
BRIDGE CORPUS FOREXon the single-company outstandings screen. ItsFX USD Debtor 02ledgerreturns a composite
CLOSINGBALANCE; captured bytes are committed asledgers_forex_composite_live.utf16le.xmlwith provenance.