vantage 0.2.0 copy "vantage: ^0.2.0" to clipboard
vantage: ^0.2.0 copied to clipboard

An in-app QA & developer-tools HUD for Flutter — device simulation, widget inspection, and an extensible plugin architecture for building QA tooling.

Changelog #

0.2.0 #

Added #

  • VantageMemoryStorage is exported. It is the default Vantage.storage and takes seed and enabled, but it was not in the barrel — so the only way to pre-seed QA state, or to force storage on in an internal release-mode QA build, was to write an entire VantageStorage from scratch.
  • RTL characterisation tests for both shipped shell styles. hudTextDirection made RTL reachable and nothing exercised it. The docked bar mirrors correctly (its Row is direction-aware); the bubble's geometry is deliberately physical, so a bubble the user dragged somewhere stays there regardless of locale. That asymmetry is now pinned rather than merely true.

Fixed after review — enabled, and the resize handle #

  • enabled was a construction-time constant that crashed when it changed. initState returned early when disabled, leaving the controller, scope and registry — all late final — unassigned, so false -> true threw LateInitializationError, true -> false threw on the scope, and a host disabled after mounting never detached its plugins (leaking every EventSource subscription). dispose compounded it by consulting the current flag. The trio is now nullable, built by _activate, released by _deactivate, driven from didUpdateWidget, with teardown keyed off the scope's existence rather than the flag. Pre-existing; found reviewing the work above.
  • The resize handle was a 24pt touch target — the paint was the hit area, half the Material minimum. The band still paints 24; the box is now 48.
  • The resize handle was drag-only. It is now a semantics slider with increase/decrease actions and a focusable node with arrow-key handling, so switch control and keyboards can resize. Drag, semantics and keyboard share one clamp/persist path.
  • VantageResizablePanel and VantageResizeEdge were exported by mistake — neither appears in a shell style's public signature. Retracted before release.
  • The panel's floor now accounts for the handle's extent; a 48pt handle overflowed a short viewport by squeezing the panel's TabBar below its intrinsic height.
  • A resize no longer loses the first ~18pt of a drag. Tap-to-focus had put a tap recognizer in the same gesture arena, so the drag had to win it; focus is taken on drag start instead.
  • Shell storage keys are versioned (vantage.shell.v1) — a durable store outlives the code that wrote it and the storage contract has no migration hook.
  • redactor and eventBufferCapacity are now applied live on rebuild; lowering the capacity drops the oldest events immediately. storage, plugins, initiallyOpen and initialSimulation are documented as mount-only.
  • Mounting a Vantage inside another Vantage now asserts in debug. Vantage.appBuilder binds to the nearest host, so the outer one reported its builder missing forever and silently never applied its simulation.

The configuration surface #

The HUD was full of decisions a host could not change. The panel opened at exactly half the screen, the bubble was a 48pt bug icon, the bar said "Vantage", the activation gesture was two fingers for 500ms, and the chrome rendered in a stock ThemeData() in en_US. None of that was configurable without reimplementing the chrome.

Added — panel geometry ([api-change], approved by the maintainer) #

  • VantagePanelSize.fraction(f), .pixels(n), .available(), with validation and value equality.

  • VantageShellData.panelSize, so a third-party VantageShellStyle receives the host's choice too, and Vantage(panelSize:) to set it. It travels through the same InheritedWidget as shellStyle, so a new value takes effect live rather than freezing in the shell route's builder closure.

    .available() is named for what it can promise: on the docked style the panel shares the screen with a 48pt bar, which is subtracted before sizing. Sizing resolves against the safe-area box via a LayoutBuilder inside the SafeArea — resolving against MediaQuery.size overshoots by exactly the safe-area padding, which shows up only on a notched device.

Added — shell appearance #

  • VantageBubbleShellStyle: bubbleSize, margin, icon, activeIcon, initialAlignment, panelAlignment (the panel can dock to the bottom) and resizable.
  • VantageDockedShellStyle: label, icon, barHeight and resizable. An empty label omits the text and its spacing.
  • Both styles now implement ==/hashCode. The parameterless const classes were getting that from const canonicalisation; adding fields would have quietly taken it away and rebuilt the whole shell on every host build.

Added — drag to resize, and state that survives #

  • resizable (default off — it adds visible chrome and a new gesture surface) puts a drag handle on the panel's free edge. The dragged height is hydrated synchronously on the first frame and written on drag end only; one write per drag frame would queue dozens of unawaited futures with no ordering between them. The bubble and docked styles remember separate heights, because they negotiate against different boxes.
  • The bubble's position is now persisted. Vantage.storage's own dartdoc has always named bubble position as its use case, and nothing had ever written it.
  • Core-owned shell state is namespaced under vantage.shell, the same discipline the storage contract asks of plugins.

Added — host options that already existed one layer down #

VantageActivationDetector, VantageEventBusImpl and VantageControllerImpl all took these; Vantage simply never passed them on, so they were unreachable defaults rather than choices.

  • activationPointers, activationHoldDuration, activationEnabled. Disabling the gesture does not disable Vantage — the bubble still opens the HUD.
  • eventBufferCapacity.
  • initiallyOpen, initialSimulation — read once at mount, and documented as such.

Added — HUD chrome environment #

  • hudTheme, hudLocale, hudTextDirection, hudLocalizationsDelegates. The HUD sits above the host's MaterialApp and so cannot inherit any of these.

    hudLocale and hudLocalizationsDelegates are a pair: the default delegates are English-only, so an unsupported locale leaves the HUD with no MaterialLocalizations and the panel's TabBar asserts.

Fixed #

  • DefaultCupertinoLocalizations is now among the HUD's delegates. Without it any Cupertino widget in a plugin panel asserted, and a panel cannot borrow the app's delegates from above the app.
  • hudTextDirection is applied inside Localizations, which publishes its own Directionality derived from the resolved locale. An override above it is silently discarded.

Compatibility #

Every default is exactly the previous hard-coded value, so a host that passes none of the new options sees no change. No existing test needed editing.

0.1.3 #

Documentation only — no code change.

  • The icon had a grey matte and border baked into it. It was rasterised with macOS qlmanage -t, which emits a Quick Look thumbnail composited onto an opaque card — every one of its 262,144 pixels was fully opaque and the corners were solid white, so pub.dev's dark theme showed a light square behind the rounded artwork. Replaced with tool/render_icon.dart, a dependency-free rasteriser that evaluates signed-distance coverage natively at each output size and verifies the corner alpha is 0 rather than assuming it.
  • New mark: two opposed brackets framing a V — an inspection frame around a vantage point.

0.1.2 #

Documentation only — no code change.

  • A proper icon, and a README that actually sells the thing: a scannable feature list instead of a wall of prose. The claims are the same ones the tests back; nothing was added to the list that the package does not do.
  • Platform badge now states all six, which 0.1.1 earned but never advertised.

0.1.1 #

Two defects found by the first integrator to compose every Vantage package at once. Both were in the host widget; no public symbol changed.

The "you forgot appBuilder" banner could accuse a correctly-wired app #

The check was a single post-frame sample that latched: if Vantage.appBuilder had not run by the end of the first frame, the banner went up and nothing ever took it down. An async LocalizationsDelegate is enough to trigger that — until every delegate resolves, Localizations renders an empty Container, so there is no Navigator and no MaterialApp.builder on the first frame. vantage_a11y's VantagePseudoLocalizationDelegate declares Future<T> load(Locale) async, which can never return a SynchronousFuture, so using vantage_a11y made core accuse the app of a misconfiguration that did not exist.

  • The check is now bound by wall clock (a two-second grace), not by a frame or loop count — an app that renders one frame and then idles produces no further frames to count.
  • The diagnostic no longer latches: appBuilder mounting at any later point retracts the banner.
  • The banner is now wrapped in IgnorePointer. It is a Material pinned across the top of the app whose Text hit-tests opaquely, so it silently swallowed every tap in that band — the integrator lost app-bar buttons to it. It has nothing to tap, and the same message still goes to debugPrint.
  • It still fires, unchanged, for an app that genuinely never adds the hook.

Vantage.shellStyle did nothing after the first build #

The HUD shell's onGenerateRoute runs once, so the route's builder closed over whatever shellStyle existed then. Passing a different style to Vantage later changed nothing on screen, which made bubble-vs-docked — a headline feature — a construction-time constant. The one-off quickActions list went stale through the identical closure and is fixed with it.

Both values now travel down the tree in an inherited widget above the HUD's Navigator instead of into the closure; the route's overlay entries are ordinary descendants, so they depend on it and rebuild when it changes. The shell route is still an OverlayRoute — never a ModalRoute, which would contribute a full-screen ModalBarrier and make the hosted app uninteractable. The tap-through and single-ModalBarrier regression tests are unchanged and still pass.

0.1.0 #

Redaction keeps the document parseable — approved api-change, 2026-08-01 #

VantageDefaultRedactor now emits a quoted mask where it replaced a quoted value, so {"password": "hunter2"} redacts to {"password": "[REDACTED]"} instead of the unparseable {"password": [REDACTED]}. Every text artifact in a bug bundle goes through this redactor, so the old behaviour handed a developer a jsonDecode failure on exactly the artifacts that had carried something sensitive. No public symbol changed — behaviour only.

  • Unquoted values are unchanged: form-encoded bodies, query strings and plain log lines have no quotes to preserve, so token=abc&page=2 still redacts to token=[REDACTED]&page=2. Pinned by a test, so the fix cannot over-reach.
  • redact() is still idempotent, and the shared corpus is unaffected — it asserts a secret's absence, never the mask's shape.
  • This closes the item the entry below recorded as "Known and unchanged".

Redaction — approved api-change, 2026-08-01 #

VantageDefaultRedactor guards every contributor's text artifact on the way into a bug bundle, and a shared corpus found it leaking 13 credential shapes. All are now covered. No public symbol changed — behaviour only.

  • Keys match separator-insensitively, so accessToken, access_token and access-token are one rule. Dart JSON is overwhelmingly camelCase, so this was the common case, not an edge case.
  • New key coverage: sessionId, cvv, cvc, pin, ssn, creditCard, cardNumber, accountNumber, routingNumber, iban, otp, passphrase, credentials, privateKey, and the usual auth headers (Authentication, X-Csrf-Token, X-Amz-Security-Token, X-Firebase-AppCheck and friends).
  • New shape coverage, for secrets with no key beside them: JWTs (self- identifying — the header segment always begins eyJ), credentials embedded in a URL (https://user:pw@host, masking the whole pair), card numbers written in groups (spaces defeat the plain 13–19 digit rule), and US SSNs.
  • Fixed: the unquoted-value branch swallowed the closing quote of an enclosing JSON string literal, so redacting a key=value shape inside a literal produced unparseable JSON — on exactly the failed-request payloads a tester files a bug about.

Known and unchanged at the time of this entrysuperseded by the entry above, same day: the mask was emitted unquoted, so masking a quoted JSON value yielded technically invalid JSON. Still best-effort, not a guarantee — the class dartdoc states what it does and does not catch, and vantageRedactionCorpus in vantage_network_core pins both.

Core 1.1 — capability batch #

  • EventSource + a host-owned merged, timestamped, ring-buffered event bus (VantageScope.eventBus, default capacity 500). Registered EventSource plugins are relayed automatically and stamped with their plugin id.
  • ExportContributor + an export pipeline with mandatory redaction of text artifacts (VantageScope.buildExport). VantageSensitivity.sensitive artifacts are dropped unless explicitly requested; text artifacts always pass through VantageRedactor; plugin ids and filenames are sanitized to one safe path segment each (no traversal); a throwing contributor is isolated and recorded in the manifest.
    • Scope is stated in the manifest rather than overclaimed: redacted: true covers text only, alongside redactionScope: 'text-artifacts-only' and unredactedBinaryArtifacts. VantageDefaultRedactor is pattern-based best-effort — a safety net over data the plugin already redacted, never the only line of defence.
    • Email masking is opt-in (VantageDefaultRedactor(maskEmails: true)) and the pipeline enables it for VantageSensitivity.sensitive artifacts only, so an ordinary log keeps the addresses a tester needs.
    • Breaking (experimental API): ExportContributor.buildExport now takes a VantageExportContext. Nothing implemented the mixin before, so no plugin is affected.
  • VantageScope.captureApp({pixelRatio}) — PNG bytes of the app subtree from a core-owned RepaintBoundary placed above the app and inside the simulation. Returns null (never throws) where rasterization is unavailable, before the first paint, or for a pixelRatio outside (0, 8].
  • The host now detaches every plugin (onDetach) when Vantage is disposed, so an EventSource's stream controller is released with its plugin.
  • VantageStorage + VantageMemoryStorage: namespaced, debug-only key-value persistence for String / bool / int / double, inert under kReleaseMode. An invalid namespace throws ArgumentError (not an assert), and views nest (a/b/). Core still depends on the Flutter SDK only; a persistent adapter is a later federated package.
  • VantageScope.appRootElement is now populated by the host (same boundary as captureApp). No signature change — the getter existed since 1.0; its doc comment now states exactly which element it is (the boundary wrapping the app, whose single child is the app's own root).
  • Contract-test kit: expectVantageEventSourceIsBroadcast, expectVantageExportArtifactsSafe, plus four new tests inside runVantagePluginContractTests (lifecycle-with-scope, broadcast events, bundle-safe artifacts, export-survives-null-capture).
  • src/api/experimental.dart is now a compatibility re-export of vantage_event.dart + vantage_export.dart.

Slice 1 #

  • Initial repository scaffold: pub workspace + melos, strict lints (very_good_analysis), CI (format · analyze · api-guard · test), the frozen-contract guard, and the Slice 1 (core shell + plugin API) design spec.
  • Core shell: Vantage + Vantage.appBuilder, controller, registry, scope, activation gesture, bubble + docked shell styles, quick actions, contract kit.
0
likes
160
points
317
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

An in-app QA & developer-tools HUD for Flutter — device simulation, widget inspection, and an extensible plugin architecture for building QA tooling.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, flutter_test

More

Packages that depend on vantage