Documentation
Trigger Your First Incident
Pick One Failure Kind
Use one stable failureKind for the failure class you want to reproduce. It should be a short code such as checkout_confirmation_mismatch, not a sentence or prose note. Revclust uses it as the incident title and grouping key.
Choose The Subject
Every app-owned invariant failure has one RevclustSubject(kind, value). The subject identifies the specific app-selected entity or flow involved in the failure. Good examples are order_ref/ord_ref_7d82b1, subscription_ref/sub_ref_42ac, book_ref/book_ref_7fb575a2, or flow/app_startup. Do not use transient UI text, raw customer identifiers, or random per-render values.
Add Expected And Observed Values
expected says what the app required. observed says what the app actually saw. Keep both as small non-empty objects with safe values.
Do not write an incident story for Revclust. New app-owned captures do not use
reason, signature, reproHint, stepLabel, or relevantIds. If a value
does not identify the failure class or subject, or state an expected or
observed fact, leave it out.
import "package:revclust_flutter/revclust_flutter.dart";
RevclustInvariantFailure checkoutConfirmationMismatchFailure() {
return RevclustInvariantFailure(
failureKind: "checkout_confirmation_mismatch",
subject: RevclustSubject(
kind: "order_ref",
value: "ord_ref_7d82b1",
),
expected: <String, Object?>{
"order_status": "confirmed",
},
observed: <String, Object?>{
"order_status": "retrying",
},
);
}
Capture Through The Normal Path
Use captureInvariantFailure(failure) at the point where the app knows the invariant failed, and keep the returned captureId for later upload events.
final RevclustCaptureOutcome outcome = await revclust.captureInvariantFailure(
checkoutConfirmationMismatchFailure(),
);
if (outcome is RevclustCaptureQueued) {
final String captureId = outcome.captureId;
}
if (outcome is RevclustCaptureBlocked) {
final RevclustStatus status = outcome.status;
final String? message = outcome.message;
}
if (outcome is RevclustCaptureBuildFailed) {
final String captureId = outcome.captureId;
final String? message = outcome.message;
}
if (outcome is RevclustCapturePersistenceFailed) {
final String captureId = outcome.captureId;
final String? message = outcome.message;
}
Wire It Where The Failure Is Decided
Do not trigger capture from a random widget callback if the real failure is decided deeper in the flow. Wire it where the app already knows the invariant is broken, then reuse the same RevclustRuntime or Revclust client you initialized at startup.
final class CheckoutService {
CheckoutService(this._revclustRuntime);
final RevclustRuntime _revclustRuntime;
Future<void> confirmOrder() async {
final Revclust revclust = _revclustRuntime.client;
final RevclustCaptureOutcome outcome =
await revclust.captureInvariantFailure(
checkoutConfirmationMismatchFailure(),
);
// Handle the immediate outcome here or pass it to your observer path.
}
}
That code can live in a repository, service, notifier, or controller. Capture the smallest useful facts where the app already knows the failure happened. Revclust preserves the surrounding timeline and state.
Handle The Immediate Result
RevclustCaptureQueued means the capture was accepted locally and queued for later upload handling. RevclustCaptureBlocked means capture was refused before a real local capture existed, so you should inspect status and message. RevclustCaptureBuildFailed means the capture started but could not be assembled correctly, and RevclustCapturePersistenceFailed means the capture built but did not make it into the local queue.
Continue to Verify and Observe.