Skip to content

Single-company outstandings screen discards the error envelope, and re-invites a guaranteed-to-fail read #178

Description

@lamemustafa

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions