Annotated Feedback

Add Debug-only in-app feedback capture with frozen screenshots, all registered visual-tree sources, free-draw annotations, app hooks, artifacts, and live or offline delivery.

Ansight.Annotations provides opt-in in-app feedback capture for .NET Android, iOS, and Mac Catalyst apps. It is included by the Ansight and Ansight.Maui all-in-one packages, but it never starts automatically.

The feature is hard-disabled when the consuming application is built in the Release configuration, even if app code calls WithAnnotatedFeedback().

Enable the Feature

For a non-MAUI app:

using Ansight;
using Ansight.Annotations;

var options = Options.CreateBuilder()
    .WithAnsightSdk(ansight =>
    {
        ansight.WithAnnotatedFeedback();
    })
    .Build();

Runtime.InitializeAndActivate(options);

For .NET MAUI:

using Ansight.Annotations;
using Ansight.Maui;

builder.UseAnsight<App>(ansight =>
{
    ansight.WithAnnotatedFeedback();
});

If the app does not reference an all-in-one package, install the feature directly:

dotnet add package Ansight.Annotations --prerelease

Present the Built-In Editor

Trigger the native annotation overlay from app UI:

var result = await Feedback.PresentAsync();

Native Android apps can pass the foreground Activity explicitly:

var result = await Feedback.PresentAsync(this);

Capture immediately timestamps the request, freezes the current screenshot, and captures every registered visual-tree source before opening the editor. The editor supports free-draw paths, contextual feedback text, selection, movement, resizing, deletion, and undo.

Studio preserves the original free-draw paths and infers approximate rectangles, ovals, lines, or arrows for CLI consumers. Inferred arrows retain the perceived focal point at the arrow tip.

Submit App-Owned Shapes

A host-owned UI can bypass the built-in editor and submit normalized shapes:

var result = await Feedback.CaptureAsync(new AnnotationCaptureRequest
{
    Feedback = "The total overlaps the action button.",
    Shapes =
    [
        new AnnotationShape(
            AnnotationShapeKind.Rectangle,
            x: 0.62,
            y: 0.74,
            width: 0.31,
            height: 0.12)
    ]
});

Add Context and Artifacts

Hooks run after screenshot and visual-tree capture and before the versioned .ansightannotation bundle is sealed:

using System.Text.Json.Nodes;
using Ansight.Annotations;
using Ansight.Artifacts;

sealed class FeedbackContextHook : IAnnotationCaptureHook
{
    public ValueTask ContributeAsync(
        AnnotationCaptureContext context,
        CancellationToken cancellationToken)
    {
        context.AddCustomData("account", JsonValue.Create("example"));
        context.AddArtifact(new AnnotationArtifact(
            "Navigation state",
            "application/json",
            "navigation.json",
            ArtifactPayload.FromText("{\"route\":\"/checkout\"}")));

        return ValueTask.CompletedTask;
    }
}

ansight.WithAnnotatedFeedback(annotations =>
{
    annotations.AddHook(new FeedbackContextHook());
});

A failing hook is recorded in the bundle and does not prevent other hooks or delivery from running. Use WithEvidencePolicy(...) to deny individual evidence sources.

Visual Trees and Delivery

Annotation capture queries VisualTreeProviderRegistry at capture time and captures every registered source:

  • native is always present.
  • Ansight.Tools.Maui registers maui.
  • App and framework integrations can register additional providers.

Screenshot and visual-tree entries retain their exact capture times. The annotation and bundle retain the original request time regardless of how long the editor remains open.

Delivery follows the available destinations:

  • With a live Studio connection, the sealed bundle is submitted through the session.
  • With Offline Capture active, the bundle is stored under annotations/bundles and indexed in annotations/index.jsonl.
  • When no destination accepts the bundle, it is retained atomically in the local annotation outbox and the result status is Queued.

Screenshot, visual-tree provider, hook, artifact, outbox, and destination failures are isolated where possible. One missing or disallowed source does not abort the rest of the annotation.