<!-- AI agents: this is the Markdown version of an AppNorth documentation page. Index: https://www.appnorth.app/llms.txt -->

# Private feedback

Use `createFeedback` for a private problem report, suggestion, or comment. This is separate from public community ideas and the support chat conversation. Your app supplies the form and explicit consent for any diagnostics or capture.

## Submit a report {#submit}

Supply a stable HTTP(S) URL representing the screen, a positive viewport size, and the user's message. `pageURL` is report context, not a custom-scheme deep link. Keep credentials and personal data out of that URL.

```swift title="PrivateReport.swift"
import Foundation
import AppNorth

func submitReport(
    client: AppNorthClient,
    message: String,
    screenURL: URL,
    viewport: AppNorthViewport,
    retryKey: String
) async throws -> AppNorthFeedbackReceipt {
    let report = AppNorthCreateFeedback(
        type: .problem,
        message: message,
        pageURL: screenURL,
        pageTitle: "Settings",
        locale: Locale.current.identifier,
        viewport: viewport
    )
    return try await client.createFeedback(report, idempotencyKey: retryKey)
}
```

Use the current screen's dimensions for `viewport`. Messages must contain 3–5,000 characters after trimming. The receipt contains `id`, `correlationId`, `screenshotStored`, and `deduplicated`; check `screenshotStored` instead of assuming the attachment was retained. Reuse the key and unchanged payload only for retries of the same report.

## Attach context deliberately {#context}

`AppNorthDeviceContext` accepts app name/version/build, model/type, and OS/version. It flattens whitespace and limits each string to 120 Swift characters; it does not remove arbitrary secrets. The host chooses and sanitizes the values.

Diagnostics use `AppNorthFeedbackDiagnostic` with a category, optional level, message, timestamp, and JSON metadata. The server accepts at most 100 diagnostics and 1,000 characters per diagnostic message. Never forward raw authorization headers, cookies, passwords, tokens, or signing keys.

## Capture only after consent {#capture}

Implement the capture and redaction protocols in your app, then call the helper. The SDK contains no automatic screenshot collector or consent UI.

```swift title="ConsentedCapture.swift"
import AppNorth

func reportScreenshot(
    consent: Bool,
    capture: any AppNorthScreenshotCapturing,
    redactor: any AppNorthScreenshotRedacting
) async -> String? {
    await captureAppNorthScreenshot(
        consent: consent,
        adapter: capture,
        redactor: redactor
    )
}
```

The helper returns `nil` if consent, either adapter, validation, or redaction fails. It also rejects unchanged redactor output. Valid output is a PNG, JPEG, or WebP base64 data URL of at most 2,800,000 UTF-8 bytes. A changed image is not proof that all sensitive content is hidden: preview the redaction in your own tests before attaching the result to `AppNorthCreateFeedback`.

## Read replies as the author {#replies}

Set a verified identity **before creating** reports whose replies the user must retrieve. An optional contact email must match the signed email; anonymous reports are not reassigned after login.

```swift title="ReadReportReplies.swift"
import AppNorth

func reportReplies(client: AppNorthClient, reportID: String) async throws -> [AppNorthFeedbackReply] {
    try await client.feedbackThread(reportID).replies
}
```

Retain the returned report ID using your account-scoped storage. `feedbackThread(_:)` requires signed identity and returns the report plus public staff replies. It excludes internal notes, contact details, diagnostics, and screenshots. Another user's or project's report returns not found. The SDK has no public report-list method.

## Verify {#verify}

1. Submit a test report and locate its receipt ID in the dashboard.
2. Reply publicly as staff and fetch the thread with the original identity.
3. Try another account and confirm it cannot read the report.
4. Test capture without consent, a throwing redactor, and unchanged redactor output; all should omit the screenshot.
