A failure says what happened and precisely why (the system and its answer: "The mail service says this file was deleted"), who or what caused it where that is known, and the next step as a working action. "Couldn't show this picture" leaves the agent guessing at the cause and at what to tell the person. When the cause is unknown, the message says what was tried and what came back.
Vague: "couldn't", "could not", "something went wrong", "unable to", "failed to load", "an error occurred", and a text that is only a bare "Error" or "Upload failed", unless the cause follows the phrase in the same sentence ("Couldn't send: you're offline. Try again when you're back online.", "… because …"; not "Couldn't send: try again"). A negation or an action before the word ("No errors", "Retry failed") passes. The source rules kit (ouro-source-rules) keeps the identical list for an app's UI copy; change both together. A text that is precise and still holds a phrase ("The card service says your card could not be charged. Use another card.") goes on an allow-list, each entry with its reason.
npx shadcn@latest add ourostack/ouro-ui/ax-precise-copy#v1.3.10It installs lib/ouro/ax/precise-copy.ts. Its tests are ax-precise-copy-tests. Every item runs on Node 22.18 or later as it is copied: the files import each other as ./<file>.ts. The Ouro AX guide shows how they fit together.
Exports
BARE_FAILURE
A bare failure: the whole text is "error" or "failed" (or "failure"), at most two words before it, or "Error:" with nothing after.
const BARE_FAILURE: RegExpCAUSE_MARKER
What marks a failure's cause after the phrase, in the same sentence: a colon, a dash or "because".
const CAUSE_MARKER: RegExpcheckFailureCopyAllow
An allow-list read from JSON, checked: a list of { match, reason }, each with a non-empty match and a reason. Throws naming each entry that is not one, with the fix.
function checkFailureCopyAllow(value: unknown): FailureCopyAllow[]Example:
checkFailureCopyAllow([{ match: "could not be charged", reason: "names the card service and its answer" }]);FAILURE_KEYS
The fields of an answer that speak to the agent or the person: a refusal's or an outcome's error, human (its reason and resume), next steps and fixes, and in an Ouro AX answer (one with an ax field: an outcome, a checks report) its summary and each finding's detail.
const FAILURE_KEYS: ReadonlySet<string>FailureCopyAllow
A failure says what happened and precisely why (the system and its answer: "The mail service says this file was deleted"), who or what caused it where that is known, and the next step as a working action. "Couldn't show this picture" leaves the agent guessing at the cause and at what to tell the person. When the cause is unknown, the message says what was tried and what came back.
Vague: "couldn't", "could not", "something went wrong", "unable to", "failed to load", "an error occurred", and a text that is only a bare "Error" or "Upload failed", unless the cause follows the phrase in the same sentence ("Couldn't send: you're offline. Try again when you're back online.", "… because …"; not "Couldn't send: try again"). A negation or an action before the word ("No errors", "Retry failed") passes. The source rules kit (ouro-source-rules) keeps the identical list for an app's UI copy; change both together. A text that is precise and still holds a phrase ("The card service says your card could not be charged. Use another card.") goes on an allow-list, each entry with its reason.
interface FailureCopyAllow {
match: string;
reason: string;
}LEAD_FAILURE
A failure that leads the text ("Error: …", "Upload failed - …"), held to the same test as a phrase; the group is the marker.
const LEAD_FAILURE: RegExpNOT_A_CAUSE
First words that make the words after a marker a next step ("try again") or a vague word ("error") in place of a cause.
const NOT_A_CAUSE: readonly string[]NOT_BARE_LEADS
Words before "error" or "failed" that make the text other than a failure: a negation ("No errors") or an action ("Retry failed").
const NOT_BARE_LEADS: readonly string[]statesCause
Whether the text after a failure phrase states the cause in the same sentence: after a colon, a dash or "because", a substitution (${…}) or at least two words whose first is not in NOT_A_CAUSE, with no vague phrase in it.
function statesCause(rest: string): booleanExample:
statesCause(" send: you're offline. Try again later."); // true
statesCause(" send: try again."); // falseVAGUE_FAILURE_PHRASES
A phrase's name, then its pattern.
const VAGUE_FAILURE_PHRASES: readonly (readonly [string, RegExp])[]VagueFailureFinding
interface VagueFailureFinding {
phrase: string;
where: string;
text: string;
allowed: FailureCopyAllow | null;
}vagueFailureIn
The vague failure phrase in a message, by its name, or null when there is none or an allow-list entry lets it through. A text that is only "Error" or "Upload failed" is "bare error" or "bare failed".
function vagueFailureIn(text: string, allow?: readonly FailureCopyAllow[]): string | nullExample:
vagueFailureIn("Couldn't show this picture"); // "couldn't"
vagueFailureIn("The photo service says this picture was deleted. Ask its owner to share it again."); // nullvagueFailuresIn
Every vague failure message in a value: a message, a help --json, an outcome, a refusal or a checks report. A finding an allow-list entry lets through carries the entry. With messagesOnly, only what is under FAILURE_KEYS, and an Ouro AX answer's summary and detail; a code is never read.
function vagueFailuresIn(value: unknown, options?: VagueFailuresOptions): VagueFailureFinding[]Example:
vagueFailuresIn({ ax: "outcome/1", state: "failed", summary: "Not sent", error: { code: "send_failed", message: "Something went wrong" } }, { messagesOnly: true });
// [{ phrase: "something went wrong", where: "error.message", text: "Something went wrong", allowed: null }]VagueFailuresOptions
interface VagueFailuresOptions {
messagesOnly?: boolean;
allow?: readonly FailureCopyAllow[];
}