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 is the primary reproduction anchor: the privacy-safe thing the engineer should recreate around. 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.
Shape The Oracle
expected and observed are the factual oracle. expected says what the app invariant 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, anchor the subject, or state the expected
or observed facts, leave it out.
import "package:revclust_flutter_sdk/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 seam can live in a repository, service, notifier, or controller. The important part is simple: the app declares the smallest factual oracle where it already knows the failure happened. Revclust preserves the reproduction conditions around it.
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.