brevwick 0.4.1 copy "brevwick: ^0.4.1" to clipboard
brevwick: ^0.4.1 copied to clipboard

Brevwick SDK for Flutter — send QA feedback from your app in one widget wrap.

Changelog #

All notable changes to the brevwick Flutter SDK are documented here. The format follows Keep a Changelog; the project follows SemVer (pre-1.0: minor for any public-API addition, patch for bug fixes / internal refactors).

0.4.1 — 2026-05-02 #

Fixed #

  • Screenshot pre-capture (race-free frame). The "Attach screenshot" flow now captures the underlying app frame BEFORE the bottom-sheet is pushed (in BrevwickOverlay._openComposer). Previously the capture ran from inside the sheet, so the captured frame included the modal barrier scrim and the sheet UI itself. The captured screenshot is now exactly the page state the user wanted to report on; subsequent "Attach screenshot" taps within the same sheet are no-ops (the captured frame is the moment the FAB was tapped).
  • Attachment-cap race. Two concurrent attach actions (e.g. tapping "Attach screenshot" while a previous "Attach file" is still resolving) could both pass the pre-flight cap-check and double-append past the 5-attachment cap. Both attach buttons now disable while any attach action is in flight (_attachInFlight); the cap is also re-checked inside _tryAddAttachment as defence-in-depth.
  • file_picker unbounded memory. defaultBrevwickPickFiles now uses allowMultiple: false. Multi-select with withData: true materialises every selected file's bytes before the overlay can apply its caps, so a user picking 100 × 10 MB files would allocate 1 GB before any gate ran. Bounding to one file per tap keeps the per-tap memory footprint at the documented 10 MB cap; multi-attach is N taps instead of one batch.
  • Single-place cap-check. _tryAddAttachment now owns all three validations (count cap, size cap, MIME allow-list). The duplicate cap-check inside _attachFile's loop body is gone; the helper's comment block matches the code.
  • AI toggle accessibility. The Format with AI row was a Row with a Text label and a separate Switch, which exposed two semantic nodes — TalkBack and VoiceOver read the switch as unlabeled and the label was not tappable. Now rendered as SwitchListTile.adaptive: merged semantics, tappable label, iOS / macOS get a Cupertino switch automatically.

Tests #

  • New: Pre-capture happens BEFORE the sheet opens confirms the screenshot hook fires once on FAB tap, BEFORE any sheet-side interaction.
  • New: Race-safe: two concurrent attach actions never exceed the cap pins the disabled-during-in-flight contract.
  • New: Attach screenshot at-cap surfaces the cap SnackBar covers the early-return path when the screenshot future would otherwise be awaited unnecessarily.
  • New: Screenshot can only be attached once per sheet pins the pre-capture latch semantics.
  • New: AI toggle: tapping the label (not the switch) toggles the value pins the merged-semantics behaviour of the SwitchListTile.
  • Hardened defaultBrevwickPickFiles test-seam restoration: FilePicker.platform is now restored in every group's tearDown so a fake leaks no further than the test that installed it.
  • The _resolveHooks production fall-through test no longer makes a real network call: a fake _ConfigOkAdapter resolves the project-config GET and submit_seam.submitDioOverride covers the submit boundary.

0.4.0 — 2026-05-02 #

Added #

  • BrevwickOverlay — themed FAB + modal bottom-sheet composer that mirrors the React widget UX (description, expected/actual collapsible, attach screenshot/file, AI toggle, send). Wrap the app once after Brevwick.install(...) and the FAB stacks over child. The FAB itself is wrapped in BrevwickSkip so it never appears in captured screenshots.
  • BrevwickOverlay.positionAlignment controlling where the FAB sits inside the overlay's Stack. Defaults to Alignment.bottomRight. Replaces the placeholder OverlayPosition enum from the original SDD contract — Alignment is the existing Flutter primitive for this and removes the need for a one-off enum.
  • BrevwickOverlay.hiddenbool flag that unmounts the FAB without unmounting child. Toggle from ancestor state to suppress the FAB during tutorials, onboarding, or full-screen takeovers.
  • BrevwickOverlay.debugHooks@visibleForTesting static seam for widget tests. Production callers leave it null; the overlay then binds against the live Brevwick.instance plus FilePicker.platform on first use.
  • BrevwickOverlayHooks + BrevwickPickedFile — test-seam types in lib/src/overlay_testing.dart. Deliberately not exported from the public barrel (lib/brevwick.dart); tests reach them via direct import 'package:brevwick/src/overlay_testing.dart'. Mirrors the established pattern from submit_test_seam.dart.
  • file_picker: ^10.0.0 runtime dependency for the "Attach file" button. Supports Android, iOS, macOS, Linux, Windows, and web. Adds the third runtime dep on the SDK after dio and screenshot (plus the crypto / device_info_plus / package_info_plus contextual deps); justified by the cross-platform native picker UX and bounded by the existing 10 MB / 5-attachment cap. Recorded in SDD § 12 dependency-discipline block.

Changed #

  • BrevwickOverlay is a StatelessWidget (the original SDD draft declared StatefulWidget; the shipped widget holds no mutable state on the outer wrapper). The composer sheet itself remains StatefulWidget because it owns text controllers + attachment list
    • submit lifecycle.

AI toggle behaviour (intentional JS divergence) #

  • The Flutter overlay defaults the "Format with AI" switch to off and omits use_ai from the submitted FeedbackInput until the submitter explicitly toggles it. The React widget defaults the switch to on and always attaches use_ai. This is a deliberate UX divergence — issue #8 acceptance criteria call for opt-in semantics on Flutter. Server-side default applies when use_ai is omitted, making the wire-level behaviour semantically equivalent for tenants whose admin-default is "on". Documented inline on BrevwickOverlay and _AiToggleRow dartdocs.

0.3.0 — 2026-05-02 #

Added #

  • Brevwick.send(input) — static submit(input) entry point that preserves the never-throws contract even before install() has run (returns SubmitFailed(ingestRejected, 'Brevwick.install(...) must be called first') pre-install). Use this when the call site cannot be certain Brevwick.instance is live.
  • Brevwick.instance.submit(input) — full submit pipeline implementation (was UnimplementedError in 0.2.0). Validates attachments, presigns each, PUTs bytes to the returned URL, then POSTs /v1/ingest/issues with retries on the final POST only (matches JS SDK byte-for-byte). Returns the sealed SubmitResult union; never throws.
  • Brevwick.clearRings() — drains every ring buffer. Called by the submit pipeline after a SubmitOk so a successful report does not leak context into the next submission.
  • Brevwick.currentRoutePath() — live top-of-stack route path captured by the route observer.
  • kSubmitTotalBudget (default 30 s) and kSubmitBackoffs ([250 ms, 1 s]) public constants documenting the submit-pipeline contract.
  • crypto: ^3.0.6 runtime dependency for SHA-256 attachment digests (sent in the presign body so the server signs R2 PUTs with a matching x-amz-checksum-sha256, and on each attachments[*].sha256 for ingest cross-checking).
  • characters: ^1.4.0 runtime dependency for grapheme-aware title truncation — the previous String.substring(0, 120) would have split surrogate pairs (e.g. emoji landing exactly across the 120-codepoint boundary), emitting malformed UTF-16 / UTF-8 on the wire.
  • ProjectConfig value class (ai_enabled, ai_submitter_choice_allowed) — drives whether the overlay renders the "Format with AI" toggle. Both flags must be true for the toggle to be shown.
  • Brevwick.projectConfigValueListenable<ProjectConfig?> exposing the cached tenant config. Seeded null and stays null until ensureProjectConfig() resolves; after first resolution the notifier always holds a non-null ProjectConfig (failure modes write ProjectConfig(false, false) rather than reverting to null).
  • Brevwick.ensureProjectConfig() — best-effort, idempotent fetch against GET {endpoint}/v1/ingest/config. Concurrent calls share the in-flight Future so the network request is performed exactly once per install(). Never throws — non-2xx, malformed JSON, empty body (204), and any thrown exception all resolve to the ProjectConfig(false, false) fallback plus a warn-level entry in the console ring (when the console ring is installed).

Changed #

  • Wire shape: user now lives INSIDE user_context alongside the merged userContext() extras. This matches the canonical SDD § 12 line 1562 and the JS SDK's composePayload (submit.ts:489-495,514). The previous top-level user placement was a divergence from both sources of truth.
  • Wire field name: the network ring's snapshot ships under network_errors per SDD § 7 line 642 + § 12 line 1569. The JS SDK is tracked for alignment in a follow-up cross-repo PR (it currently emits network_calls); server ingest reads network_errors.
  • Presign retry removed — presignOne is now single-shot per attachment, matching the JS source. Retrying a transient 5xx three times burned 3× the presign quota for one blip; only the final /v1/ingest/issues POST retries (1 + 2 backoffs = 3 total attempts).
  • Server-rejection redaction is now redact-then-cap, not cap-then-redact. A bearer token at offset > 256 in a 5 MB server-echo body could previously slip past the cap unredacted; the new ordering collapses secrets first so the cap can never sever a token mid-pattern.
  • SubmitErrorCode.ingestRejected is now used for non-retryable Dio failures (badCertificate, unknown) — they were never retried, so labelling them ingestRetryExhausted lied about the failure shape.
  • BrevwickUser.id runs through redact() at the boundary as defence-in-depth; the SDD documents id as opaque, but consumers in practice stuff in email-shaped strings, JWTs, etc.
  • _raceWithCancel rewritten on top of Completer so the losing-side whenCancel.then continuation cannot throw _CancelledByBudget into the unhandled-error zone after the task resolved (previously crashed flutter_test and any caller installing a zone error handler).

Security #

  • Every payload context field (title, description, expected, actual, route_path, device_context, user_context, BrevwickUser.traits) runs through redact() before transmission; the test suite pins each path with a redaction test.
  • userContext() callback exceptions have their message run through redact() before landing in the console-ring warn entry. A consumer exception could otherwise carry the very PII the boundary scrubber is meant to remove (e.g. StateError("user 'jane@acme.com' missing flag")).
  • Project-config fetch failures route through the console ring's record(), which runs redact() over the message at the boundary. A Bearer pk_… substring accidentally surfaced through a transport error's toString() collapses to Bearer [redacted] before reaching the captured warn entry — pinned by a redaction test.

Internal #

  • New SubmitContext interface decouples Submitter from the live Brevwick singleton so the submit pipeline is no longer in a circular import with the client. Tests can construct Submitter with a hand-rolled SubmitContext fake.
  • submitDioOverride test seam moved to lib/src/submit_test_seam.dart (NOT re-exported from the public barrel) — pub.dev consumers cannot reach the override without an explicit package:brevwick/src/... import that signals "I am reaching into internals".
  • lib/brevwick.dart barrel now uses show Brevwick to narrow the exported surface — internal helpers (Submitter, the submit-test seam, etc.) stay invisible to consumers.

Notes #

  • Project-config failure-mode divergence from JS. JS getConfig() returns Promise<ProjectConfig | null>; the Flutter SDK exposes a ValueListenable<ProjectConfig?> that, after first resolution, holds ProjectConfig(false, false) rather than reverting to null. Both surfaces encode the same "no AI toggle, no submitter choice" outcome. The Flutter shape lets consumer widgets render the AI toggle unconditionally without a null-state arm. Documented in brevwick-ops SDD § 12.

0.2.0 — 2026-05-01 #

Added #

  • BrevwickScreenshotScope widget — wraps the app once, owns the SDK's ScreenshotController, and exposes an opaque BrevwickScreenshotScopeHandle to descendants via BrevwickScreenshotScope.maybeOf(context). Closes #7.
  • BrevwickSkip widget — wraps any subtree that must NOT appear in the captured PNG (passwords, the SDK FAB itself, tenant-flagged sensitive UI). Mirrors the JS SDK [data-brevwick-skip] semantics. Outside a scope it still wraps in a permanently-visible Visibility(maintainSize: true) so layout never reflows when a scope is added or removed.
  • BrevwickScreenshotScopeHandle — opaque capture handle with a reference-counted capture(pixelRatio:, onCaptureWarn:, timeout:) method. Concurrent captures cannot strand the UI hidden; nested captures only restore visibility when the outermost finalises. Default 5 s timeout — a wedged render tree resolves to null plus a warn console-ring entry rather than hanging the SDK's submit pipeline indefinitely.
  • BrevwickRouteObserver — a NavigatorObserver that captures {path, timestamp} records onto the route ring (cap 20, FIFO) and exposes the live top-of-stack path via ValueListenable<String?> routePath. Wire into MaterialApp.navigatorObservers or GoRouter.observers. Path is run through the placeholder pass (:token/:auth/:key) and the global redact() sweep so resolved emails / JWTs / bearer tokens / blobs collapse to coarse tokens before reaching the ring.
  • Brevwick.routeObserver() and Brevwick.goRouterObserver() — lazy-construct (and reuse) the bound BrevwickRouteObserver. The observer is disposed on uninstall() so dangling navigator callbacks become no-ops.
  • Brevwick.snapshotRoute() — FIFO defensive snapshot of the route ring.
  • Console ring (lib/src/rings/console.dart, internal) — chains FlutterError.onError + PlatformDispatcher.instance.onError preserving previously-installed handlers by reference, dedups on (message, firstFrame) within 500 ms via a Map<key, entry> (mirroring JS canonical, including the opportunistic prune past size 32), trims stacks to top 20 frames, and redacts message + stack at the boundary.
  • Brevwick.recordConsoleEvent({level, message, stack?}) — manual seam for non-error logs and screenshot-ring warnings; honours the ring's dedup + redact + trim. No-op when config.rings.console = false.
  • Brevwick.snapshotConsole() — FIFO defensive snapshot of the console ring (entries are live references the dedup path mutates).
  • Brevwick.runGuarded(body) — static runZonedGuarded wrapper that routes uncaught zone errors into the console ring. Safe to call before install() (escapes are silently dropped, matching the JS contract).

Changed #

  • Brevwick.captureScreenshot() now takes a BuildContext and an optional pixelRatio (default 2.0); the previous signature was a Phase-3 stub. Returns Uint8List?null when the SDK is disabled, when no BrevwickScreenshotScope is mounted (a warn console-ring entry is recorded so triagers see why the submit payload had no attachment), or when the underlying capture throws (the throw is caught + forwarded to the console ring as a warn entry; the never-throws contract holds for the public surface).

Security #

  • Route paths run through redact() before landing in the ring, so a consumer who pushes /users/jane@acme.com/edit writes /users/[email]/edit — and the routePath listenable, which submit reads to populate top-level route_path, reflects the same redacted form.
  • Console messages and trimmed stacks run through redact() at the ring boundary so bearer tokens / JWTs / emails surfaced through FlutterError or PlatformDispatcher exceptions never leave the device raw.
  • The internal "no scope" warn message emitted by captureScreenshot contains no PII — just the diagnostic string.

0.1.0 — 2026-05-01 #

Added #

  • Brevwick singleton facade with install / uninstall / instance lifecycle — the one entry point pub.dev consumers touch.
  • BrevwickConfig validated configuration contract (mirrors the JS SDK byte-for-byte: projectKey, endpoint, environment, enabled, buildSha, release, user, userContext, rings, fingerprintOptOut).
  • BrevwickUser opaque user identity carrier.
  • FeedbackInput / FeedbackAttachment submission contracts.
  • SubmitResult sealed union (SubmitOk / SubmitFailed) with SubmitErrorCode enum.
  • DeviceContext / SdkInfo / Viewport device-context value objects. SdkInfo.version is now sourced from package_info_plus so the wire payload tracks pubspec.yaml automatically.
  • DeviceContextCollector with cached plugin reads + BuildContext viewport capture.
  • BrevwickDioInterceptor Dio network ring with header allow-list, query-param redaction, body caps (2 kB request / 4 kB response), loop-avoidance matching JS makeLoopGuard, and per-request redaction.
  • Brevwick.recordNetworkEvent manual seam for non-Dio HTTP clients; every entry runs through redactValue at the boundary.
  • Brevwick.snapshotNetwork for FIFO-ordered defensive ring snapshots.
  • redact / redactValue public redaction helpers.

Changed #

  • N/A (initial public release).

Security #

  • Network ring strips userInfo (https://user:pass@host/...) from every captured URL unconditionally — accidental credentials embedded in a URL never reach the wire payload.
  • DioException.message flows through redact() before reaching the ring so PII leaked into Dio error messages cannot escape the device.
0
likes
120
points
0
downloads

Documentation

API reference

Publisher

verified publishertatlacas.com

Weekly Downloads

Brevwick SDK for Flutter — send QA feedback from your app in one widget wrap.

Homepage
Repository (GitHub)
View/report issues

Topics

#feedback #bug-reports #qa #observability

License

MIT (license)

Dependencies

characters, crypto, device_info_plus, dio, file_picker, flutter, package_info_plus, screenshot

More

Packages that depend on brevwick