inkpal_bridge 10.1.0 copy "inkpal_bridge: ^10.1.0" to clipboard
inkpal_bridge: ^10.1.0 copied to clipboard

Turn your Flutter app into an MCP server. AI editors (Claude Code, Cursor, Windsurf, Codex, Copilot) inspect, drive, hot-reload, auto-recover it. 80 tools. Zero deps.

10.1.0 — Smoother #

Additive-only polish sprint over 10.0.1. Every 10.0 wire shape, tool count (80), and test (321 pre-10.1) stays intact — no removals, no schema changes, no breaking parameters.

Five improvements, all focused on the first ten minutes with the bridge:

  1. adb forward auto-attempt. When defaultTargetPlatform is Android and the host looks capable of spawning adb (macOS / Linux / Windows), the welcome console fires one bounded adb forward tcp:$port tcp:$port with a 2 s timeout, fire-and-forget. Success replaces the manual reminder with — auto ✓; failure or a missing adb on $PATH falls back to the current human-readable line verbatim. iOS / Android hosts are gated out.

  2. One-command MCP install — bin/install.dart. New bundled CLI: dart run inkpal_bridge:install [--for=<id|all>] [--url=<URL>] [--dry-run] [--json]. Detects installed editors by well-known config paths — Claude Code (.mcp.json project-local, else ~/.claude/mcp.json), Cursor (~/.cursor/mcp.json), Windsurf (~/.codeium/windsurf/mcp_config.json), and VS Code + Copilot (~/.config/github-copilot/mcp.json or the Windows %APPDATA% equivalent) — then merges an inkpal server entry into each, preserving every other key. Never touches a config that doesn't already exist. Exit codes: 0 installed or already wired, 1 write failure, 3 bad flag.

  3. Runnable sample — example/counter_app/. One-file Flutter counter with inkpalRunApp, inkpalNavigatorKey, and inkpalNavigatorObserver wired. path: ../.. on inkpal_bridge, so pub.dev users can cd example/counter_app && flutter run and drive it from an AI editor in under a minute. The nine-zone showcase at example/ stays as the deep-dive.

  4. Welcome banner polish. ANSI colours when stdout.hasTerminal && supportsAnsiEscapes && !NO_COLOR; plain-text fallback everywhere else (CI, piped output, flutter run streams). Arrow-line layout replaces the fixed-width box art (which wrapped ugly at narrow widths), and the Android hint routes through the auto-attempt result — so the developer sees either auto ✓ or the manual command, never both.

  5. inkpal CLI polish. ANSI-aware pass() / fail() / hint() markers that degrade to [OK] / [FAIL] / [HINT] off-tty. Standardised exit codes: 0 success · 1 tool-reported error · 2 bridge unreachable (with a follow-up → Try: dart run inkpal_bridge:doctor hint) · 3 argparse / usage error. Better --help grouped by verb with an Examples: block.

Compatibility #

  • No parameter removed. No response shape reduced. Zero breaking changes.
  • Canonical tool count unchanged: grep -c "^ 'name': 'inkpal_" lib/src/communication/mcp_tools.dart = 80.
  • All 10.0 tests still pass verbatim; adds new test/v10_1_smoother_test.dart covering the four unit-testable additions (host gate, install-CLI dry-run, banner degradation, CLI exit codes).
  • pubspec description unchanged (166 chars, kept under pub.dev's 180-char cap).

10.0.1 #

Pubspec-only patch. 10.0.0's description was 186 characters — 6 over pub.dev's 180-character recommendation, which docked pub score points. Trimmed to 166 characters while keeping the auto-recover mention:

"Turn your Flutter app into an MCP server. AI editors (Claude Code, Cursor, Windsurf, Codex, Copilot) inspect, drive, hot-reload, auto-recover it. 80 tools. Zero deps."

Zero code changes. All 321 tests still pass verbatim. Tool count 80.

10.0.0 — Autonomy #

Compressed from the full v10 plan. The 6.0 baseline and every 8.x / 9.0 wire shape stay verbatim; new fields are strictly additive on migrated tools; the registry grows by exactly one (inkpal_reliability_report) for a canonical count of 80 tools. One breaking change: the @Deprecated 3.x license-layer no-op params on inkpalRunApp(...) and InkPalBridge.init(...) are gone — callers still passing them get a compile error. See "What's not in 10.0" below for what got trimmed.

Auto-recovery — five named modes #

New lib/src/execution/recovery.dart wraps every retry-worthy failure mode the bridge understands into one API:

  • widget_not_found — locate step returned null. Slot A in VerifiedExecutor re-runs locate after a 300ms settle.
  • frame_stall — post-action frame stamp did not advance. Slot B waits 200ms and re-samples once before the confidence classifier fires.
  • connection_loss — WebSocket / MCP transport dropped. Handled out-of-band by ReconnectingWebSocket; recorded through Recovery.record so the reliability report captures reconnects.
  • hot_reload_fail — an execute step surfaced a hot-reload / isolate-loss error. Slot C attempts one retry after a 300ms settle.
  • port_collisionInkPalBridge.init failed to bind port 8767. Handled at boot; recorded via Recovery.record.

Every attempt is bounded (maxAttempts=1 by default) and logs {reason, action_taken, succeeded} into the always-on ReliabilityMetrics store. When a recovery lands, the returned envelope carries outcome.recovered_via. Opt out per call with recover: false on any migrated mutating tool (inkpal_tap, inkpal_scroll, inkpal_enter_text, inkpal_navigate_to_route, inkpal_state_override).

Coordinate normalization #

New lib/src/execution/coord_space.dart adds a CoordSpace enum (logical / device / screenshot) and a CoordSpaceConverter singleton that caches the platform DPR plus the logical_width / png_width scale from the most recent successful screenshot.

inkpal_tap({x,y}) accepts an optional coord_space argument. The default logical preserves pre-10.0 wire behavior byte-for-byte; device divides by the cached platform DPR; screenshot multiplies by the cached PNG-scale. The response echoes coord_space_in plus the normalised x/y (only when non-logical) so agents can reconcile the requested coord with the pointer pipeline's actual dispatch. When the required scale hasn't been cached yet, the tap fails fast with coord_space_scale_unavailable rather than firing at the wrong point.

inkpal_reliability_report — new tool #

Registry moves 79 → 80. The bridge's MCP HTTP server now records every tools/call into a process-wide ReliabilityMetrics singleton (no network, no persistence). inkpal_reliability_report({reset?: bool}) returns:

  • total_calls, total_success, total_verified, success_rate, verified_rate, avg_confidence_score.
  • per_tool[toolName] = {calls, success, verified, success_rate, verified_rate, avg_latency_ms, max_latency_ms}.
  • confidence_counts — per-tier tally (verified / observed / probable / unknown / failed).
  • failure_reason_counts — bucketed by reason: or error.code.
  • recovery[reason] = {attempts, succeeded, success_rate, last_action_taken} for each of the five named modes.

Pass {reset: true} to snapshot + wipe in one call — ideal for bracketing an agent run.

API cleanup — deprecations added, one shim removed #

Soft-deprecated (still functional, one debugPrint per tool per process):

  • inkpal_screen_snapshotinkpal_screenshot_save
  • inkpal_screen_diffinkpal_screenshot_diff
  • inkpal_get_recent_logsinkpal_query_logs
  • inkpal_increase_valueinkpal_tap by visible label
  • inkpal_decrease_valueinkpal_tap by visible label

Hard removal (this is the actual breaking change):

  • inkpalRunApp(licenseKey: ..., apiUrl: ...) — gone.
  • InkPalBridge.init(licenseKey: ..., apiUrl: ...) — gone.

Both parameters had been marked @Deprecated and treated as no-ops since 4.0 (when the bridge went free). They are removed in 10.0. Any caller still passing them gets a compile error — the fix is to delete the arguments.

What's not in 10.0 (deferred from the full plan) #

  • 5 of 9 recovery modes shipped. The remaining four (route_lost, state_drift, expectation_mismatch, binding_reset) stay out until we've validated the five that shipped on real workloads.
  • Deprecations are warnings, not removals. All five soft- deprecated tools continue to work; only the licenseKey/apiUrl shims were removed.
  • No Homebrew tap and no README rewrite — the hero line got a new bullet, the install section got a version bump, and that's it.

Every previously-passing test still passes. New test files: test/v10_recovery_test.dart, test/v10_coord_space_test.dart, test/v10_reliability_metrics_test.dart, test/v10_deprecation_warnings_test.dart.

9.0.0 — Verified Execution #

Additive-only. The 6.0 baseline stays byte-for-byte, every 8.x wire shape is a subset of 9.0's, every existing caller keeps working. Tool count: 79 (unchanged from 8.0). This release is deliberately compressed from the full v9 plan — see "What's not in 9.0" below.

VerifiedExecutor — Locate → Validate → Execute → Verify #

New lib/src/execution/ package houses a small L/V/E/V driver used by the five migrated mutating tools. Every dispatch runs the same four steps:

  1. Locate — resolve the target the caller named. null short- circuits with widget_not_found + hint + candidates: list.
  2. Validate — confirm the target is usable. False short-circuits with widget_found_but_not_valid.
  3. Execute — the tool's existing verbatim work (delegates to ActionExecutor / InkPalStateAdapter).
  4. Verify — capture frame stamp + semantics fingerprint + route before and after, roll everything into an Evidence struct, and classify via classifyConfidence (verified / observed / probable / unknown / failed).

Wire shape is strictly additive: strict mode returns {...result, outcome, evidence, verification}; lite returns {...result, outcome:{confidence}}; off returns the raw result untouched.

Five migrated tools #

The five highest-impact mutating tools now run through VerifiedExecutor:

  • inkpal_tap (both {text|key} and {x,y} paths)
  • inkpal_scroll
  • inkpal_enter_text — surfaces a best-effort read_back of the field's post-write value inside result for evidence.
  • inkpal_navigate_to_route
  • inkpal_state_override — surfaces the post-write state_read inside result; the 8.0 adapter_rejected typed error still fires when the adapter silently swallows a write.

Every other mutating tool (long_press, navigate_back, hot_reload, hot_restart, open_deeplink, call_app_extension) keeps its 8.0 VerificationEnvelope shape verbatim — the migration is opt-in per tool, not a global rewrite.

expect: preconditions on migrated tools #

Each of the five migrated tools now accepts an optional expect: map:

{"expect": {"frame_stamp": 1234, "route": "/checkout",
            "app_identity": "com.foo.bar", "build_id": "sha_abcdef"}}

Any mismatch short-circuits before dispatch with a typed error (frame_stamp_mismatch, route_mismatch, wrong_app, or stale_build) so a stale bound / wrong route / wrong app / stale build never lands a tap. Preconditions are purely additive — omit expect: and the pre-9.0 behaviour is identical.

Freshness stamps — _meta.runtime.freshness #

Every MCP tools/call reply now carries a freshness block under _meta.runtime:

{"freshness": {"semantics_seq": 42, "route_seq": 7,
               "hot_reload_seq": 1, "frame_stamp": 123456}}

The three counters bump from:

  • semantics_seq — every SemanticsWalker.captureScreenContext() call.
  • route_seq — every navigator observer event (push/pop/replace/remove) AND every trackExternalNavigation call.
  • hot_reload_seq — mirrors HotReloadDriver.generation verbatim.

Agents that inspected at semantics_seq=41 and see a mutating reply carrying semantics_seq=42 know their last read is stale, no extra round-trip required.

Universal expect: passthrough in adaptArgs #

Same pattern as 8.0's verify: passthrough — InkPalMcpTools.adaptArgs now folds expect: back into the WS-shaped params for every tool whose adapter rule builds a fresh map (tap, scroll, enter_text, navigate_to_route, state_override). Default-case tools already carried expect: verbatim.

Wire additions summary #

Every migrated tool response now carries:

Field Strict Lite Off
pre-v9 result shape yes (verbatim) yes yes
action yes yes yes
outcome.confidence yes yes
outcome.mode strict lite
outcome.attempted[] yes yes
outcome.succeeded_via on success on success
evidence.* yes
verification.* yes
_meta.runtime.freshness yes yes yes

What's not in 9.0 (compressed from the full plan) #

The full v9 sketch called for ten migrated tools, a full state machine with retries, and a documentation site. This release ships the tight subset:

  • 5 of 10 tools migrated — tap / scroll / enter_text / navigate_to_route / state_override. long_press, navigate_back, open_deeplink, hot_reload, hot_restart, call_app_extension remain on the 8.0 VerificationEnvelope path.
  • Freshness stamps, not a full state machine — three monotonic counters, no per-tool auto-retry with backoff. Evidence.retries is wired but always 0 in 9.0.
  • No docs site — README bullet + CHANGELOG only.

Everything else (test coverage, back-compat, tool count) landed as planned.

8.0.0 — Identity & Terse Mode #

Additive-only. The 6.0 baseline is preserved verbatim — every 7.x wire shape is a subset of 8.0's, every existing caller continues to work. Tool count: 79 (78 → 79). This changelog covers the wire additions; see README for the shape.

_meta.runtime envelope on every tools/call #

Every reply now carries result._meta.runtime with the identity + provenance block agents keep asking for: session_id, bridge_id, bridge_version, app_identity, package_name, device.platform, build_id, hot_reload_generation, frame_stamp, timestamp_ms.

session_id and bridge_id are minted once per InkPalBridge.init() and stay stable across every response until the bridge disposes. hot_reload_generation starts at 0 and increments on every successful inkpal_hot_reload / inkpal_hot_restart — agents can detect "the app I inspected has been swapped out" without a whole extra round-trip.

Back-compat: the 7.8 top-level app_identity and build_id still ride on the outer JSON-RPC envelope as siblings of result — nothing at that path moved.

verify: 'strict' | 'lite' | 'off' #

Every mutating tool (inkpal_tap, scroll, enter_text, navigate_to_route, navigate_back, long_press, open_deeplink, state_override, hot_reload, hot_restart, app_call) now accepts an optional verify: param:

  • strict (default) — full 7.x envelope: pre/post semantics diff, settle wait, added/removed labels, route delta, errors-since.
  • lite — action runs, no pre-capture, no settle wait; response gets a outcome.confidence trailer (verified / failed). Roughly 5–10× cheaper than strict on hot loops.
  • off — raw handler result, no wrap. Use when your script already verified.

Server-side default via inkpalRunApp(defaultVerify: 'lite') / InkPalBridge.init(defaultVerify: ...). Advertised on the initialize handshake under serverInfo.default_verify so callers can adjust. Per-call verify: always wins.

inkpal_runtime_health — new rollup tool (79th) #

Single call that returns {app, adapters, errors, build, frame, bridge, connection, confidence, hints} — the rollup of release_readiness + wait_for_stable (single tick) + get_current_route + get_runtime_errors + build_id + hot_reload_generation. Meant for "am I safe to drive right now?" before any agent step, without paying for 5 separate MCP round-trips.

Image cache + resources/list / resources/read #

inkpal_take_screenshot now returns {image_id, url, hash, bytes, width, height, logical_width, logical_height, device_pixel_ratio, semantic_summary} by default. The PNG is stashed in a bounded LRU (InkPalImageCache, 100 entries / 100 MB, evicted on dispose) and exposed as an MCP resource at resource://inkpal/image/<id>.

MCP clients that speak the resources capability (advertised on initialize) fetch the PNG via resources/read. Legacy callers who want the pre-8.0 base64 blob opt back in with return_base64: true.

Typed error envelopes on the top-5 silent failers #

Handlers that used to swallow the cause into a stringly-typed error: now emit {success:false, error, reason, hint, recovery, context}:

  • inkpal_tap({x,y})tap_hit_wrong_widget when caller passes expected: and the visible label at (x,y) doesn't match.
  • inkpal_call_app_extensionextension_contract_missing when the registry has no handler for the given name.
  • inkpal_state_overrideadapter_rejected when the adapter accepted the write but the read-back value didn't change.
  • inkpal_hot_reloadreload_ignored when hot_reload_generation didn't advance.
  • inkpal_evaluateexpression_unmatched when no built-in prefix or user hook resolved the expression.

bin/inkpal.dart — bundled shell CLI #

dart run inkpal_bridge:inkpal tap "Save", dart run inkpal_bridge:inkpal health, dart run inkpal_bridge:inkpal screenshot --out shot.png, plus screen, read, watch, reload. Auto-discovers the bridge on 8767..8770. --json for machine-readable output; exit 0 on success, 1 on tool error, 2 when nothing answered.

Test-isolation fix (4 pre-existing failures) #

InkPalVmExtensions now funnels every developer.registerExtension call through a single _safeReg helper that tracks registered names in a process-global set. Suites that init → dispose → init the bridge (dispose_test, visible_for_testing_test) no longer throw "Extension already registered".

7.8.0 — field-report response #

Bundles six additive fixes surfaced by a real Claude Code session against 7.2.0 (field report received 2026-07-11). Every change is additive: existing 7.7 callers see no wire-level shape change unless they opt into the new params. No MCP tool added, removed, or renamed. Tool count: 78 (unchanged from 7.7).

FIX #1 — inkpal_screenshot payload knobs #

  • take_screenshot gains two optional args: return_path: bool (writes the PNG to Directory.systemTemp/inkpal_screenshots/<id>.png and returns {path, url, format, bytes, ...metadata} instead of a base64 blob) and max_bytes: int (fails with an actionable hint when the encoded base64 would exceed the cap).
  • Defaults (return_path: false, max_bytes: null) preserve the 7.7 response shape verbatim — old callers are unaffected.

FIX #2 — app_identity envelope + port-collision negotiation #

  • New optional appIdentity: String? on InkPalBridge.init and inkpalRunApp. Free-form identifier (e.g. 'com.foo.mymapapp') — emitted as app_identity on every MCP response envelope (initialize handshake + tools/list + every tools/call reply, success or error) via a single wrapper in InkPalMcpServer._wrapEnvelope.
  • Port-collision probe: before the loopback MCP server binds, it POSTs an initialize request to http://127.0.0.1:<port>/mcp with a 200 ms timeout. If the response carries a different app_identity, the bridge refuses to bind, prints a loud red-flag log, and falls through to WebSocket-only mode. When appIdentity is null the probe is skipped and 7.7 bind behaviour is preserved.

FIX #3 — scroll pointer-synthesis fallback + strategy_used #

  • action_executor.dart scroll now tiers: semanticpointer_synthesisfailed. When the SemanticsAction path can't find a scrollable or the target is already at its edge, the bridge drags 200 logical pixels through PointerGestureDriver.drag from the viewport / scrollable center.
  • Every response envelope now carries a strategy_used field ('semantic' / 'pointer_synthesis' / 'failed'). The failed envelope also carries a hint pointing at inkpal_swipe as the raw-coordinate fallback. Existing 7.7 callers that don't read strategy_used are unaffected.

FIX #4 — screenshot dimensional metadata #

  • ScreenshotCapture now returns width, height, logical_width, logical_height, and device_pixel_ratio on the ScreenshotResult.
  • take_screenshot (both base64 and return_path shapes), screenshot_save, and screenshot_compare_ref spread the metadata into their top-level responses. screenshot_diff is unchanged (already returns bbox and changed-percent).

FIX #5 — build fingerprint via --dart-define #

  • New compile-time constant read from --dart-define=INKPAL_BUILD_ID=<sha> at build time. Exposed as InkPalBridge.buildIdFromEnv and InkPalBridge.buildId. When non-empty, threaded into every MCP response envelope as build_id (same wrapper as FIX #2).
  • New doctor check 9 (Build fingerprint (INKPAL_BUILD_ID)) — PASSes with the reported value when the bridge advertises it, SKIPs with the exact --dart-define incantation otherwise. Check count moves from 8 to 9.

FIX #6a — get_current_route observer-wired envelope #

  • InkPalNavigatorObserver gains a sticky static hasEverFired flag flipped true the first time any route event reaches the observer (push, pop, replace, remove, or trackExternalNavigation).
  • get_current_route handler adds observer_wired: bool on every reply. When the observer has never fired, the reply also carries hint: 'Add inkpalNavigatorObserver to MaterialApp.navigatorObservers to enable route tracking.'. Existing fields are unchanged.

FIX #6b — zone bootstrap: drop the redundant runGuarded wrap #

  • inkpal_run_app.dart no longer wraps the else-branch of the run helper in the error catcher's guarded zone. The wrap was redundant because InkPalBridge.init installs PlatformDispatcher.instance.onError, which catches every uncaught async error regardless of zone. Dropping the wrap eliminates the "zone that called ensureInitialized ≠ zone that called runApp" Flutter warning that the field report tracked back into error telemetry as noise at boot.

Tests #

  • New test/field_report_fixes_test.dart — 16 cases across the six fixes: MCP adapter pass-through for return_path/max_bytes, max_bytes hint shape, app_identity emission on every envelope, app_identity omission when unset, port-collision refusal, source guards for scroll's three strategy_used branches, source guards for dimensional metadata spread, buildIdFromEnv / buildId static surface, MCP envelope build_id emission (with and without), hasEverFired sticky-flag transitions, get_current_route hint string, and inkpal_run_app.dart source lacking any catcher.runGuarded reference.
  • Registry canonical count: 78 (grep -c "^ 'name': 'inkpal_" lib/src/communication/mcp_tools.dart) — unchanged.
  • No MCP tool added, removed, or renamed.
  • Every parameter on InkPalBridge.init and inkpalRunApp is preserved; new ones default to null.
  • Every existing 7.7 test still passes (mcp_server, pixel_diff, default_eval, interaction_recorder, walker_hooks_v2, doctor, mcp_telemetry).

Tool count: 78 (unchanged). #

7.7.0 #

Extends InkPalWalkerHooks with four MarionetteConfiguration-style callbacks that operate on the SEMANTICS-tree pass, complementing the pre-existing three Element-tree hooks. Pure additive surface — every new field defaults to null and, when null, preserves the walker's built-in behaviour verbatim.

New (all optional, all nullable) #

  • shouldStopSemanticsWalk: bool Function(SemanticsNode, int depth) — Prune a SemanticsNode subtree during the semantics-tree walk. Depth-ceiling escape hatch for large screens (e.g. (_, d) => d > 20). Complements the existing widget-based shouldStopTraversal which operates on the Element-tree pass.
  • customElementType: UiElementType? Function(SemanticsNode) — Override the walker's default type detection for a given node. Return non-null to force a specific UiElementType; return null to fall through to the built-in flags/actions detection. Useful for custom design systems whose semantics don't map cleanly to Material patterns.
  • keyExtractor: String? Function(SemanticsNode) — Extract a stable key from a SemanticsNode. Fills the same slot as ValueKey<String> for widgets that stash their id in the semantics label / hint / value instead of the widget key.
  • visibilityOverride: bool? Function(UiElement) — Force an element in or out of the returned list, applied after the walker produces a candidate UiElement. Return true to include, false to exclude, null to defer to the walker's default include/exclude logic.

Consumed in #

  • SemanticsWalker._walkNode() — checks shouldStopSemanticsWalk before descending, applies customElementType to override the detected type, threads keyExtractor output through UiElement.key, and calls visibilityOverride after emitting the candidate. All four hooks are guarded by null-checks so a walkerHooks: null (or InkPalWalkerHooks.none) app gets the exact 6.0/7.6 walker behaviour byte-for-byte.

Tests #

  • New test/walker_hooks_v2_test.dart — 9 test cases covering depth-ceiling pruning, null-hook baseline preservation, custom-type override + fall-through, key extraction from a label pattern, force-hide + force-defer, and combined v1/v2 hook registration.

Compatibility #

  • Purely additive. Every existing test/*.dart still passes; the older 3-field InkPalWalkerHooks({...}) constructor invocation still compiles verbatim.
  • No parameter removed from InkPalBridge.init / inkpalRunApp.
  • No MCP tool added, removed, or renamed.
  • Registry canonical count: 78 (grep -c "^ 'name': 'inkpal_" lib/src/communication/mcp_tools.dart) — unchanged from 7.6.

Tool count: 78 (unchanged). #

7.6.0 #

Ships a bundled diagnostic CLI so contributors and CI can prove the bridge is set up correctly without a live editor round-trip. Skips 7.5 (sibling package took that slot).

New #

  • dart run inkpal_bridge:doctor — eight-check preflight:
    1. Dart & Flutter toolchain (parses dart --version / flutter --version).
    2. inkpal_bridge declared in the current project's pubspec.yaml.
    3. Bridge reachable on http://127.0.0.1:8767/mcp (POST initialize, expect result.serverInfo); reports the bridge version on hit.
    4. Release-readiness — when the bridge is reachable, calls inkpal_release_readiness via tools/call and renders its structured sub-flags as PASS / FAIL rows.
    5. INKPAL_BRIDGE=off hint — best-effort environment sniff; SKIP when the bridge is up (can't be compiled in) or no smoking gun.
    6. Android: adb devices + adb reverse --list for tcp:8767. Prints the fix (adb reverse tcp:8767 tcp:8767) on FAIL.
    7. Flutter Web target hint (heuristic: web/index.html or web: platform block in pubspec.yaml).
    8. ~/.claude/mcp.json writable probe (macOS / Linux best-effort).
  • Every check prints [PASS] / [FAIL] / [SKIP] with a one-line rationale. Exit code 0 when every check is PASS or SKIP, 1 on any FAIL. --json emits a machine-readable report; --check=<n> runs a single check; --port <n> prepends an extra bridge port to the sweep.
  • Executable declared in pubspec.yaml alongside connect, so both ship in the same tarball.

Internals #

  • lib/src/tooling/doctor.dart holds the check logic behind an injectable DoctorEnv (process runner, HTTP probe, file reader, writable probe, env / cwd / home lookups). Real IO lives only in DoctorEnv.real(); every check is unit-tested with a scripted env.

Tests #

  • New test/doctor_test.dart covers all eight checks with canned process outputs, canned HTTP responses, and canned filesystem state. 30 test cases; every check has PASS / FAIL / SKIP paths.

Compatibility #

  • Additive. No handler, no MCP tool, no public API changed. InkPalBridge.init / inkpalRunApp parameters unchanged from 7.4.
  • Registry canonical count: 78 (grep -c "^ 'name': 'inkpal_" lib/src/communication/mcp_tools.dart) — unchanged.
  • All 7.4-required test files (mcp_server_test.dart, pixel_diff_test.dart, default_eval_test.dart) pass verbatim.

Tool count: 78 (unchanged). #

7.4.0 #

Two shipping additions on top of 7.3's recording pipeline: an opt-in anonymous usage telemetry surface, and a copy-paste starter kit for wiring InkPalStateAdapter to the three most-common state libraries. No new MCP tools, no new WS handlers — pure additive surface.

New #

  • Opt-in telemetry (telemetryEndpoint:) — new nullable param on inkpalRunApp and InkPalBridge.init. When set (e.g. 'https://usage.inkpal.ai/v1/ping'), the bridge POSTs a small JSON payload after every MCP tool call: {tool_name, latency_ms, success, ts_ms, session_id, bridge_version}. Anonymous — no PII, no app content, no MCP arguments, no result body. Debug-mode only, silent-fail, non-blocking. Off by default. Respects Platform.environment['DO_NOT_TRACK']=='1' and --dart-define=INKPAL_TELEMETRY=off — either disables telemetry even when the endpoint is set. Emits a single debugPrint on init so the mode (ENABLED / DISABLED + reason) is obvious.
  • Starter-kit examples in example/ — three copy-paste files with full inline InkPalStateAdapter wiring:
    • example/state_adapter_riverpod.dartProviderContainer with read / write / invalidate against StateProvider + FutureProvider.
    • example/state_adapter_bloc.dart — id → BlocBase registry with Cubit and Bloc<Event, State> examples.
    • example/state_adapter_provider.dartprovider + GetIt.instance wiring for ChangeNotifier services. All three are commented-out snippets (no compile-time deps on riverpod / bloc / provider — the bridge stays zero-dep). Copy the block into your own main.dart and uncomment.

README #

  • Hero banner adds a GitHub star badge + a genuine "star / like / file issues" call-to-action so contributors have a clear next step.
  • New Support & community section links the repo, pub.dev score page, and Discussions.
  • 7.3 recording→replay + 7.4 starter kits + telemetry now called out in the "What you get" list.

Compatibility #

  • Additive. Every 7.3 handler unchanged. InkPalBridge.init / inkpalRunApp parameters unchanged — the new telemetryEndpoint: is nullable and defaults to null (7.3 behaviour verbatim).
  • No response shape reduced. InkPalMcpServer gains an optional telemetry: field that defaults to null.
  • Registry canonical count: 78 (grep -c "^ 'name': 'inkpal_" lib/src/communication/mcp_tools.dart) — unchanged from 7.3.
  • All 7.3-required test files pass verbatim.

Tool count: 78 (unchanged). #

7.3.0 #

Recording → replay closes the loop. inkpal_recording_export can now emit a runnable flutter_test integration test that depends only on flutter_test + integration_test — QA teams can commit those tests alongside the rest of the suite without pulling inkpal_bridge in as a runtime dep. A new inkpal_recording_replay tool reads a JSON recording and drives the app back through the same interaction handlers that produced it.

New #

  • inkpal_recording_replay — replay a recorded interaction log against the currently running app. Accepts either file_path (JSON on disk) or an inline recording map. timing:"preserve" walks recorded timestamps to keep real pacing; "fast" (default) uses a fixed inter-step gap (fast_gap_ms, default 50ms). Reports per-step replayed / skipped counts plus a results list.
  • inkpal_recording_exportformat:"integration_test" now emits a valid Dart integration test file. Legacy format:"test" is still accepted verbatim. Pass package_name to fill the package:<name>/main.dart import (otherwise the emitter leaves a <your_app> TODO). Pass test_name to override the testWidgets label. Emitted files depend only on flutter_test + integration_test.

Fixed #

  • RecordedAction.toTestStep() now handles doubleTap, increase, and decrease action types instead of dropping them into the fallback-comment path.
  • Labels containing single quotes / backslashes / newlines are now escaped so exported test files stay syntactically valid.

Compatibility #

  • Additive. Every 7.2 handler unchanged. InkPalBridge.init / inkpalRunApp parameters unchanged.
  • format:"json" remains the default for recording_export.
  • Registry canonical count: 78 (grep -c "^ 'name': 'inkpal_" lib/src/communication/mcp_tools.dart).
  • 58/58 tests pass across the required test files (35 mcp integration
    • 4 pixel-diff + 8 default-eval + 11 recorder unit tests).

Tool count: 78. #

7.2.0 #

Post-audit ADOPT #1 (per docs/superpowers/plans/2026-07-07-post-7.1-ultraplan.md): the only competitive-audit steal with a plausible line to the 7 failing tests and demo flakiness.

New #

  • inkpal_wait_for_stable — polls the semantics-tree fingerprint (nodeId | label | value | bounds per element) until it stays unchanged for stable_ms (default 250ms), or timeout_ms (default 5000ms) elapses. poll_ms (default 50ms) tunes the poll cadence. Stronger than wait_for_idle (which only counts frames): guarantees the content an agent sees hasn't moved. Kills the "tap → sleep → hope" flake pattern. Zero new deps.

When to use #

inkpal_tap({text: 'Save'})
inkpal_wait_for_stable({timeout_ms: 3000, stable_ms: 200})
inkpal_assert_no_errors()

If it times out, the response carries a transitions count and a hint pointing at animation/stream/timing likely-causes.

Compatibility #

  • Additive. Every 7.1 handler unchanged.
  • Registry canonical count: 77 (grep -c "^ 'name': 'inkpal_" lib/src/communication/mcp_tools.dart).
  • 47/47 tests pass (35 mcp integration + 4 pixel-diff engine + 8 default-eval).

Tool count: 77. #

7.1.0 #

Field feedback on 7.0.0 flagged a real friction point: evalHook was opt-in, so reading a provider or a token during a test required re-wiring main.dart, which drove callers to a chunked-debugPrint extraction hack. 7.1 makes the read side default-on in debug mode without opening the surface to arbitrary code execution (which the Dart VM cannot safely offer from inside a running app anyway).

New #

  • Default eval mini-language. inkpal_evaluate now ships with a built-in dispatcher that works with zero app-side wiring:
    • state.<id> — reads from the wired InkPalStateAdapter.
    • debug.<key> — reads from the publishDebugValue registry (see below).
    • route.current, route.stack
    • errors.count, errors.last, errors.list
    • elements.count, elements.text.<needle>
    • logs.tail, logs.count When an app-provided evalHook returns a non-null value, it wins; the built-in is the fallback so user code keeps priority. Never executes arbitrary code.
  • InkPalBridge.publishDebugValue(name, valueOrGetter) — static registry pushable from anywhere in the app. Getter form is re-invoked on each read so a one-line publish (e.g. InkPalBridge.publishDebugValue('auth.token', () => authService.currentToken)) stays fresh. Debug-mode-only — silent no-op in release builds. unpublishDebugValue and publishedDebugKeys complete the trio.
  • inkpal_debug_values_list — enumerate what's currently in the registry so agents can discover keys without guessing.

Why #

7.0.0 field report: "evalHook shipped-by-default (debug, allowlisted). Reading a provider or a token during a test shouldn't require re-wiring main.dart — that gap forced my chunked-debugPrint token extraction hack."

Fixed.

Compatibility #

  • 100% additive. Apps that passed evalHook: in 7.0.0 keep the same priority — user hook still runs first; built-in only fires when the user hook returns null or throws.
  • Debug-only publish: production builds ignore publishDebugValue calls entirely — no state leak surface added to release binaries.
  • 46/46 mcp integration tests + 4/4 pixel-diff engine tests + 8/8 new default-eval tests pass. 6.0 compatibility contract still holds.

Tool count: 76. #

Note: earlier CHANGELOG entries (6.4 → 7.0) undercounted the pre-6.4 baseline and running totals drifted by ~6. The registry has always been the source of truth; the canonical count today is 76 (grep -c "^ 'name': 'inkpal_" lib/src/communication/mcp_tools.dart). From 7.1 forward, every asset (pubspec, README, positioning) uses 76.

7.0.0 — final stable #

Consolidates the 6.1 → 6.9 phased ladder into a blessed stable release. Everything that shipped between 6.0 and 6.9 is preserved verbatim.

What 7.0.0 adds on top of 6.9 #

  • inkpal_release_readiness — one-shot pre-flight health check aggregating build mode, navigator binding, UI reachability, error catcher, state adapter, and app router. Returns ready_to_drive plus per-check details and a blockers list. Cheap, side-effect free, designed to be the first call an agent makes.

Consolidated feature map (6.1 → 7.0) #

  • 6.1 — hot-reload driver rewrite (single-listener-per-socket + shared response map keyed by JSON-RPC id), zone-safe bootstrap, scroll reveal: alias, tap expect_frame_stamp guard.
  • 6.2 — verification envelopes on every mutating action (screen_changed, added_labels, removed_labels, frame_stamp, route_before/route_after, errors_since_action).
  • 6.3 — HTTP memory: get_http_log, assert_no_failed_requests, wait_for_request.
  • 6.4 — navigation memory: open_deeplink, get_route_graph, coverage_report.
  • 6.5 — live state adapter. InkPalStateAdapter with optional list/read/write/invalidate callbacks; four new state_* tools. Zero-dep, duck-typed.
  • 6.6 — batched state (Bloc / Provider ergonomics): state_read_many, state_all, filtered state_providers.
  • 6.7 — reactive state (GetX / MobX / Bloc streams): state_watch, state_capabilities.
  • 6.8 — device-side pixel diff: screenshot_save, screenshot_diff, screenshot_list. Pure-dart:ui pixel_diff.dart engine, per-channel threshold, bbox output.
  • 6.9 — reference-PNG compare (screenshot_compare_ref) and bundled dart run inkpal_bridge:connect discovery CLI.
  • 7.0release_readiness aggregate check + consolidated release blessing.

Compatibility contract #

  • 6.0 baseline preserved verbatim. Every 6.0 integration test continues to pass unchanged.
  • No parameters removed from InkPalBridge.init / inkpalRunApp. Every new param is nullable with a null default.
  • No handler response shape reduced. New fields added additively; the verification: envelope on mutating actions is additive to the existing result map (spread, not replaced).
  • Deprecated licenseKey: / apiUrl: params remain accepted-and- ignored no-ops (planned removal in 8.0).
  • Release-mode short-circuit still runs appRunner() synchronously with zero bridge overhead. Verified by the if (kReleaseMode) branches at the top of inkpalRunApp and InkPalBridge.init.

Migration from 6.x #

Bump your dependency constraint. That's it.

dependencies:
  inkpal_bridge: ^7.0.0

No code changes. No new required params. No renames.

Verification #

  • 37/37 mcp_server_test.dart tests pass.
  • 4/4 pixel_diff_test.dart engine tests pass.
  • dart analyze lib bin clean (zero issues).
  • 60 → 69 MCP tools registered.
  • Alias-drift guard exercises every one of the 69 tools against a registered handler in one CI run.

Tool count: 68 → 69. #

6.9.0 #

Ninth step on the ladder — last stop before 7.0.0. Adds reference-PNG compare (no in-memory store required) and a bundled connect CLI that discovers the running bridge and prints editor config.

New #

  • inkpal_screenshot_compare_ref — pixel-diff the current screen against a caller-supplied reference PNG (base64-encoded). Uses the same engine as screenshot_diff from 6.8. Rejects non-PNG blobs by magic-byte check.
  • dart run inkpal_bridge:connect — zero-config discovery helper. Scans ports 8767..8770 (overridable) for a live MCP server, then prints copy-paste editor config for Claude Desktop, Cursor, Windsurf, Copilot. --json for machine-readable output.

Field loop #

inkpal_screenshot_compare_ref({
  reference_png_b64: <figma_export.png>,
  threshold: 20,
})

No file I/O, no reference store to manage, no side effects.

Compatibility #

  • 32/32 mcp_server_test.dart tests pass + 4/4 pixel-diff engine tests. 6.0 baseline preserved verbatim. bin/connect.dart is a standalone Dart executable — has no impact on the runtime bridge.

Tool count: 67 → 68. #

6.8.0 #

Eighth step on the ladder. Device-side pixel diff — closes the "did my tap actually change something visible?" loop without shelling out to pixelmatch or ADB screencap on a host.

New #

  • inkpal_screenshot_save — capture a PNG and stash it in the bridge's in-memory store under name (default "default"). Cleared on dispose(), no file I/O.
  • inkpal_screenshot_diff — capture now, compare against a saved screenshot. Fast path returns identical: true on byte-equal PNGs; otherwise decodes both via dart:ui and returns changed_pixels, changed_percent, changed_bbox, size_delta_bytes. Per-channel RGBA threshold (default 30) — alpha ignored.
  • inkpal_screenshot_list — enumerate the saved screenshots (names, count, total bytes).

Engine #

  • New lib/src/inspection/pixel_diff.dart — pure dart:ui decoder + per-pixel loop. When dimensions differ, the intersection is compared and the extra area outside is counted as fully changed (matches pixelmatch semantics).
  • 4 engine tests exercise identical / all-different / below-threshold / dimension-mismatch shapes.

Loop #

inkpal_screenshot_save({name: 'baseline'})
inkpal_tap({text: 'Toggle dark mode'})
inkpal_wait_for_idle()
inkpal_screenshot_diff({against: 'baseline', threshold: 20})
  → {changed_percent: 87.4, changed_bbox: {left: 0, top: 0, right: 359, bottom: 799}}

Compatibility #

  • 31/31 mcp_server_test.dart tests pass. 6.0 baseline preserved verbatim. Existing take_screenshot handler unchanged.

Tool count: 64 → 67. #

6.7.0 #

Seventh step on the ladder. Reactive state — GetX Rx / MobX @observable / Bloc streams. Same InkPalStateAdapter from 6.5, two new primitives that turn "read-once" into "wait for a change."

New #

  • inkpal_state_watch — wait until a provider's value changes, up to timeout_ms. Adapter-agnostic — the bridge polls read() at poll_ms (default 100ms) and JSON-compares. Optional expected value: return immediately if the current read already differs from what the caller thought.
  • inkpal_state_capabilities — describe what the adapter supports (list / read / write / invalidate / watch). Editors call this once to avoid asking for surfaces the app didn't wire.

GetX example #

final cart = CartController(); // extends GetxController, cart.items is RxList

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'cart.count', 'kind': 'Rx<int>'},
      {'id': 'auth.user',  'kind': 'Rx<User?>'},
    ],
    read: (id) async => switch (id) {
      'cart.count' => {'value': cart.items.length},
      'auth.user'  => {'value': auth.user.value?.email},
      _            => {'error': 'unknown provider: \$id'},
    },
    // No write / invalidate — GetX prefers explicit controller methods.
  ),
);

Then in the agent:

inkpal_tap({text: 'Add to cart'})
inkpal_state_watch({id: 'cart.count', timeout_ms: 2000})

MobX example #

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'ui.dark', 'kind': '@observable bool'},
    ],
    read: (id) async => id == 'ui.dark'
        ? {'value': uiStore.darkMode}
        : {'error': 'unknown provider: \$id'},
    write: (id, value) async {
      if (id == 'ui.dark') {
        uiStore.setDarkMode(value as bool);
        return {'success': true};
      }
      return {'success': false};
    },
  ),
);

Compatibility #

  • 29/29 mcp_server_test.dart pass. 6.0 baseline preserved verbatim. Adapter-agnostic — no library-specific plumbing added.

Tool count: 62 → 64. #

6.6.0 #

Sixth step on the ladder. Bloc / Cubit / Provider ergonomics — the same InkPalStateAdapter from 6.5 gains batched primitives so agents can snapshot the whole state tree in one round-trip, and filter the provider list on the bridge side without teaching the adapter about filters.

New #

  • inkpal_state_read_many — batch-read N providers by id, or read every provider list() returns when ids is omitted. Errors surface per-id so one bad read never poisons the whole batch.
  • inkpal_state_all — convenience: fuses list() + read() into one grid. Requires both callbacks on the adapter.
  • inkpal_state_providers — now accepts optional filter (substring match on id / label / kind), bridge-side. The adapter still returns everything; the agent narrows.

Bloc / Cubit example #

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'auth',    'kind': 'Cubit<AuthState>'},
      {'id': 'counter', 'kind': 'Bloc<CounterEvent, int>'},
    ],
    read: (id) async {
      final bloc = GetIt.I<BlocBase>(instanceName: id);
      return {'value': bloc.state.toString()};
    },
    write: (id, value) async {
      if (id == 'counter') {
        GetIt.I<CounterBloc>().add(CounterSet(value as int));
        return {'success': true};
      }
      return {'success': false, 'error': 'not writable: \$id'};
    },
  ),
);

Provider (ChangeNotifier) example #

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'theme', 'kind': 'ChangeNotifier<ThemeModel>'},
      {'id': 'cart',  'kind': 'ChangeNotifier<CartModel>'},
    ],
    read: (id) async => switch (id) {
      'theme' => {'value': themeNotifier.mode.name},
      'cart'  => {'value': cartNotifier.items.length},
      _       => {'error': 'unknown provider: \$id'},
    },
  ),
);

Compatibility #

  • 27/27 mcp_server_test.dart pass. 6.0 baseline preserved verbatim. No shape change for any pre-6.6 tool.

Tool count: 60 → 62. #

6.5.0 #

Fifth step on the ladder. Opens a live window into the app's state tree — the piece the 6.0 → ADGP field report kept working around by diffing screenshots and stringifying state through app extensions.

New #

  • InkPalStateAdapter — a zero-dep, duck-typed struct with four optional callbacks (list, read, write, invalidate). Wire it to your Riverpod ProviderContainer / Bloc registry / Provider tree / GetX bindings / MobX stores. The bridge does not import your state library — you decide exactly what is reachable.
  • inkpal_state_providers — enumerate live providers currently exposed by the app.
  • inkpal_state_read — read the current value of one provider by id. Complements state_capture (snapshot journal) with a real-time peek.
  • inkpal_state_override — override a provider's value. Great for agent-driven test setups: seed a logged-in user, pin a locale, flip a feature flag.
  • inkpal_state_invalidate — invalidate / refresh a provider so its next read recomputes. Useful for FutureProvider / StreamProvider.

Wiring #

inkpalRunApp(
  const MyApp(),
  stateAdapter: InkPalStateAdapter(
    list: () async => [
      {'id': 'counter', 'kind': 'StateProvider<int>'},
      {'id': 'user',    'kind': 'FutureProvider<User>'},
    ],
    read: (id) async => switch (id) {
      'counter' => {'value': container.read(counterProvider)},
      'user'    => {'value': container.read(userProvider).toString()},
      _         => {'error': 'unknown provider: \$id'},
    },
    write: (id, value) async {
      if (id == 'counter') {
        container.read(counterProvider.notifier).state = value as int;
        return {'success': true};
      }
      return {'success': false, 'error': 'not writable: \$id'};
    },
    invalidate: (id) async {
      if (id == 'user') {
        container.invalidate(userProvider);
        return {'success': true};
      }
      return {'success': false, 'error': 'not invalidatable: \$id'};
    },
  ),
);

Compatibility #

  • All four state adapter callbacks are individually nullable. Every state_* handler returns a {success: false, error: 'not configured'} envelope when the app hasn't opted in, so tools stay safe to call unconditionally from an editor.
  • 22/22 mcp_server_test.dart still pass. No behaviour change for apps that omit stateAdapter.

Tool count: 56 → 60. #

6.4.0 #

Fourth step on the ladder. Automates the manual missing-link audit the 6.0 field report ran by hand.

New #

  • inkpal_open_deeplink — drive a deep link into the running app. Delegates through the normal navigation chain (router.go / router.push for go_router, then onNavigateToRoute, then Navigator.pushNamed) and reports resolved route, stack, and before/after. Accepts full URIs or bare route names.
  • inkpal_get_route_graph — declared routes (from go_router configuration + inkpalRunApp knownRoutes) vs. visited routes (observer stack + external navigations). Surfaces orphaned entries — declared routes nothing navigated to.
  • inkpal_coverage_report — session coverage: routes declared vs. visited, plus the tappable elements on the current screen.

Internals #

  • _appRouter field added to InkPalBridge so handlers registered inside _registerCommands can reach the app router (the local router name in that scope is the CommandRouter).

Verification #

  • 22/22 mcp_server_test.dart still pass. Compatibility contract holds.

6.3.0 #

Third step on the phased ladder. Answers "what did the app talk to?" in one round-trip.

New #

  • inkpal_get_http_log — every observed HTTP request (method, URL, status, duration, sizes, error, redacted headers). Filter by since_ms, url_pattern (regex), method; cap the tail with max. Sensitive headers (Authorization, Cookie, X-Api-Key, X-Auth-Token, Proxy-Authorization, Set-Cookie) are stripped before storage — the redaction ran in 6.0 already, this exposes it.
  • inkpal_assert_no_failed_requests{ok, failed_count, failures[]} for any request since since_ms that returned >= 400 or threw a transport error. Use after any action that should have succeeded network-wise.
  • inkpal_wait_for_request — waits until a request whose URL matches url_pattern is observed, or timeout_ms elapses. Optional method filter. Great for "tap BOOK NOW, confirm the POST fired and returned 200."

Verification #

  • 6.2 verification-envelope tests plus the 22 pre-6.3 tests still pass verbatim. Compatibility contract holds.

6.2.0 #

Second step on the 6.1 → 7.0 ladder. Backward-compatible with 6.0 and 6.1: every existing response keeps its top-level fields; the new verification object is additive.

New #

  • verification envelope on every mutating action. Same shape as the tap screenChanged / addedLabels / removedLabels pattern from 6.0, extended to scroll, enter_text, navigate_to_route, navigate_back, hot_reload, hot_restart, long_press, and call_app_extension. Each action response now includes:
    "verification": {
      "frame_stamp": 12345,
      "screen_changed": true,
      "added_labels": ["Settings"],
      "removed_labels": ["Home"],
      "route_before": "/home",
      "route_after": "/settings",
      "route_changed": true,
      "errors_since_action": 0,
      "since_ms": 1720000000000,
      "now_ms": 1720000000300
    }
    
    Agents get "did my action work" in one round-trip, without a follow-up screenshot to compare.

Verification #

  • Compatibility contract holds: mcp_server_test.dart still 22/22. Existing 6.0/6.1 top-level response keys unchanged.
  • dart analyze lib/ clean.

6.1.0 #

Field-fix release — first step on the 6.1 → 6.9 → 7.0 phased ladder. Every fix is backward-compatible with 6.0: no inkpalRunApp parameter removed, no rename without alias, no response-shape change that removes existing fields. All 6.0 integration-test assertions still pass verbatim.

Fixed (from the 6.0 ADGP field report §1) #

  • Deep-tree tap — removed the arbitrary 12-level tappable-ancestor cap. Real design systems bury GestureDetector under ContainerPaddingDecoratedBoxStackPositionedAnimatedContainer … The walk now goes to the semantics root, so a BrandCard(onTap: ...) 14 layers deep taps correctly.
  • Hot-reload lifecycle — rewrote HotReloadDriver around a single- listener-per-socket + shared response map. The old "Stream has already been listened to" error after any failed reload is gone; hot-reload can be retried indefinitely without app restart.
  • Zone-safe bootstrapWidgetsFlutterBinding.ensureInitialized() is now called before any zone dance. If the caller wraps main() in runZonedGuarded(...) (Sentry, Bugsnag, custom error reporters), inkpalRunApp runs inside that already-installed error zone instead of nesting a new one. Eliminates the "Zone mismatch" warning surfaced by the ADGP field report.
  • Silent route-wire gap — when neither router:, navigatorKey:, nor onNavigateToRoute: is passed AND InkPalNavigatorObserver isn't attached to MaterialApp.navigatorObservers, the bridge now prints a loud one-line warning ~750 ms after boot with the exact one-line fix. Two days silently lost to route: null — never again.
  • Hot-reload Android hintinkpal_reload_status and inkpal_hot_reload failure envelopes now mention adb reverse tcp:PORT tcp:PORT for Android callers who can't reach the VM Service from the host.

New #

  • reveal: param on inkpal_scrollreveal: "below" | "above" | "left" | "right" names what should come into view (the intuitive frame). The legacy direction: param stays accepted for 6.0 callers; when both are supplied, direction: wins so 6.0 semantics are preserved verbatim.
  • Frame stamps on inspection responses — every response from inkpal_observe, inkpal_get_screen_content, inkpal_get_widget_tree, inkpal_find_widget, plus every tap response, includes frame_stamp (Flutter's currentFrameTimeStamp in microseconds).
  • expect_frame_stamp guard on inkpal_tap — pass the frame stamp from a prior inspection; the tap refuses to fire with frame_stamp_mismatch when the frame has moved on. Catches stale- bounds races after scroll animations.

Migration #

None required. 5.x and 6.0.x code compiles and behaves identically. Previously-broken paths now succeed.

Verification #

  • Compatibility contract: every integration-test assertion that passed in 6.0.x still passes verbatim in 6.1 — the 22-test mcp_server_test.dart suite is a superset of 6.0's 18 tests.
  • dart analyze lib/ clean; flutter test 97 pass / 4 skip / 4 fail (same 4 pre-existing test-isolation failures).

6.0.0 #

Field-validated stable release. Fixes every real-world break surfaced by a day of driving 5.0.0 against a live app, adds hot reload / hot restart so the bridge closes the iterate loop, and clears the last pub.dev score gap.

Fixed #

  • inkpal_tap no longer crashes with "type 'Null' is not a subtype of String". The bridge WS handler assumed params['label'] was a non-null string, but MCP inkpal_tap sends text / key / x / y. 6.0.0 adds a per-tool argument adapter in InkPalMcpTools.adaptArgs(...) that translates MCP names to bridge names before dispatch, plus defensive nullable casts on every WS handler (inkpal_tap, inkpal_enter_text, inkpal_scroll, inkpal_navigate_to_route, inkpal_long_press, inkpal_tap_with_context, inkpal_increase_value, inkpal_decrease_value, inkpal_get_screen_manifest). Field regression: a whole day was lost to adb input tap fallback because of this crash.
  • inkpal_tap({x, y}) now dispatches to the pointer pipeline instead of returning "not found" for an empty label. Raw-coord taps and semantic finders both work through the same MCP tool.
  • Every WS handler returns a structured error envelope on missing args instead of throwing an uncaught cast exception. The MCP client gets a JSON-RPC result telling it what to fix.
  • pubspec description shortened to 167 chars to fit pub.dev's 60–180 window. Recovers the "Provide a valid pubspec.yaml" score.

New #

  • inkpal_hot_reload — trigger a Flutter hot reload from your AI assistant, no external flutter CLI needed. The bridge opens a WebSocket to its own Dart VM Service (available in every debug build) and calls reloadSources. Closes the iterate loop: edit code → inkpal_hot_reload → inkpal_wait_for_idle → inkpal_assert_no_errors → inkpal_screenshot.
  • inkpal_hot_restart — full state-clearing restart.
  • inkpal_reload_status — check whether the VM Service transport is reachable from the running app before attempting a reload.
  • inkpal_scroll now accepts pixels for exact scroll deltas.

Improved #

  • Scroll semantics documented in the schema. direction: down reveals items BELOW the current viewport (the finger swipes UP). Prior 5.x users burned cycles on this — the schema description now spells it out.
  • Welcome banner detects Android and prints the exact adb forward tcp:8767 tcp:8767 command every app boot needs. No more losing a session to a missing forward.
  • Regression tests cover every parameter-adapter rule. Tap null crash, raw-coord tap, routerouteName, textlabel, nested call_app_extension params — all locked in.

Coverage #

  • 56 MCP tools (was 53 in 5.0.0). The three new hot-reload primitives are additive.

Migration #

None required. 5.x code works identically; broken 5.x paths now succeed.

5.0.0 #

The final working version — one dependency, one line, one JSON block, done.

inkpal_bridge is now the whole product. Add the package, wrap runApp, run the app, and every AI coding assistant that speaks MCP can drive it over http://127.0.0.1:8767/mcp. No CLI to install. No npm proxy. No Node. No signup. No API key. No license.

What's new #

  • Full tool surface — the in-process MCP HTTP server now exposes every meaningful command the bridge already serves internally: 53 tools covering inspection (observe, get_widget_tree, find_widget, screen_snapshot, screen_diff, device_metrics, screenshot), interaction (tap, long_press, scroll, enter_text, navigate, increase/decrease_value, touch feedback), wait/assert primitives, accessibility audit, translation coverage, structured log query, evaluate, app-extension state seed, state time-travel (capture / list / get / diff / stream), interaction recording, and self-healing (watch / verify / error context).
  • Every tool goes through the same alias-aware dispatch (InkPalMcpTools.commandFor), so public MCP names like inkpal_tap route to their internal command (tap_element) regardless of how the two evolve.
  • Every tool is regression-guarded — the integration test invokes each of the 53 tools over real HTTP and asserts a routable bridge command exists. Alias drift now fails CI, not fresh installs.

Simplified setup #

# pubspec.yaml
dependencies:
  inkpal_bridge: ^5.0.0
// lib/main.dart
void main() => inkpalRunApp(const MyApp());
// your MCP client's config (Claude Code / Cursor / Codex / …)
{"mcpServers":{"inkpal":{"transport":"http","url":"http://127.0.0.1:8767/mcp"}}}

That's the entire install. No other component.

Notes #

  • No public API removals since 4.1.1. The licenseKey: and apiUrl: deprecated shims from 4.0 stay for one more major so 3.x callers keep compiling.
  • The bridge still binds to loopback only (127.0.0.1), debug-mode only, port-in-use degrades to WS-only. Same safety envelope.

4.1.1 #

Bugfix release. The MCP HTTP transport shipped in 4.1.0 dispatched tools/call by stripping the inkpal_ prefix from the tool name, but several public tool names don't map 1:1 to the bridge's internal WS command names — so calls like inkpal_tap, inkpal_screenshot, and inkpal_get_interactive_elements returned "Method not found" instead of running.

  • Fixed: InkPalMcpTools.commandFor(...) now applies an explicit alias table before falling back to the prefix strip: inkpal_taptap_element, inkpal_screenshottake_screenshot, inkpal_get_interactive_elementsget_screen_content, inkpal_get_runtime_errorsget_error_history, inkpal_get_app_logsget_log_history, inkpal_navigate_backgo_back, inkpal_enter_textset_text, inkpal_list_app_extensionsapp_list, inkpal_call_app_extensionapp_call, inkpal_long_presslong_press.
  • Added: 12-test integration suite (test/mcp_server_test.dart) that spins up a real HttpServer, sends actual JSON-RPC requests, and asserts the full MCP protocol: initialize handshake, notifications, tools/list registry contents, tools/call dispatch (both aliased and prefix-stripped forms), unknown-tool + unknown- method errors, malformed-JSON parse error, CORS preflight, 404s on non-/mcp paths, and clean port release on stop.

4.1.0 #

No CLI install required. The bridge now serves MCP-over-HTTP from inside the running app, so AI editors can connect to it directly — no npx, no Node, no separate process.

New #

  • In-process MCP server. Defaults to http://127.0.0.1:8767/mcp. Editors that support the MCP HTTP transport (Claude Code, Cursor, Windsurf, VS Code Copilot, Codex CLI) connect directly with a single JSON paste into their MCP config — no install of anything besides the Dart package itself.
  • mcpPort: parameter on inkpalRunApp and InkPalBridge.init. Defaults to 8767; pass 0 to disable, or another port to avoid collisions in parallel-run setups.
  • InkPalMcpServer — an HttpServer bound to 127.0.0.1, JSON-RPC 2.0 over POST plus an SSE keep-alive endpoint. Implements initialize, tools/list, tools/call against a bundled registry of ~30 bridge-direct tools that map onto the same CommandRouter the WebSocket transport uses.
  • Welcome banner prints both transports + the JSON snippet to paste into an editor's MCP config.

Improved #

  • The two transports coexist. The bridge keeps its outbound WebSocket (legacy npx inkpal start path) for users who want the full ~150-tool surface that includes cloud catalogs, Figma, and host CLI tools — those still require the npm proxy. Bridge-direct tools (~30) work over either transport with the same handler code.

Notes #

  • The MCP server binds to loopback only (127.0.0.1), permissive CORS only on that interface. No traffic leaves the machine.
  • Disabled in release mode — same kDebugMode gate as the rest of the bridge. Release builds collapse inkpalRunApp to runApp with zero overhead, including the MCP listener.
  • Failure to bind the MCP port (port-in-use, sandboxed runtime) is non-fatal — the WebSocket path continues to work and the bridge logs a warning instead of crashing.

4.0.1 #

README-only update. No code changes.

  • Slim the README to fit the free-package framing: drop the marketing walkthrough, the "what you get" subsections, and the signup-flavoured documentation links.
  • Add a manual MCP-client config block so adopters without Node have an install path that doesn't need npx.
  • Welcome banner no longer mentions a parallel inkpal.ai/setup URL.

4.0.0 #

Free for everyone — and 10 new bridge-side tools. No signup, no API key, no tier check. The bridge runs unconditionally in debug mode.

Migrating from 3.x #

No source changes required. Existing call sites keep compiling. The licenseKey: and apiUrl: parameters are accepted as deprecated no-ops and you'll see a yellow analyzer hint plus a one-line debug log at boot:

[InkPal] DEPRECATED: inkpalRunApp(licenseKey: ...) is ignored —
inkpal_bridge is free for everyone in 4.0. Remove the parameter.

To silence both, delete the two parameters:

// 3.x
inkpalRunApp(
  MyApp(),
  licenseKey: const String.fromEnvironment('INKPAL_LICENSE_KEY'),
  apiUrl: 'https://mcp.inkpal.ai',
);

// 4.0
inkpalRunApp(MyApp());

If you were running with flutter run --dart-define=INKPAL_LICENSE_KEY=..., drop the --dart-define — the env var is no longer read.

The deprecated parameters will be removed in 5.0.

Removed #

  • InkPalLicenseValidator, FeatureGate, FeatureTier, InkPalFeature, InkPalTier, InkPalLicense, InkPalBridge.licenseReady. These were the public API of the license layer. If you imported any of them directly, they're gone; if you only used inkpalRunApp, you won't notice.

New #

  • assert_no_errors — count + samples of FlutterErrors captured since a timestamp. Cheap to call after every gesture in a test loop.
  • stability_check — fused {errors, jank_frames, state_churn, ok} so one round-trip answers "is the app behaving right now."
  • assert_element and get_elements — read-side assertions and typed queries (type / key_prefix / text_contains / tappable_only) with bounds + tappability. Replaces "screenshot then ask the LLM to look."
  • wait_for and wait_for_idle — poll until a predicate matches or the app settles for N consecutive frames. Replaces "tap → screenshot → check → repeat" loops with a single call.
  • accessibility_audit — code-only WCAG checks (touch target ≥48dp, missing labels on interactive elements, image alt text). Runs entirely in-app, no cloud catalog.
  • query_logs — filter the in-app log buffer by level, category, time, and regex pattern. Avoids streaming the full buffer over the wire.
  • evaluate — Dart expression evaluator via an opt-in hook the app registers. Returns the JSON-encoded result. Apps decide which symbols are reachable; off by default.
  • verify_translations — locale key coverage report via an opt-in hook. Catches forgotten translations in CI.

Improved #

  • Welcome banner rewritten — the bridge now identifies itself simply as "free for everyone, no API key, no signup" instead of the old license-state line.

3.0.0 #

The AI-native release. New capabilities for AI-assisted development — and no removed or changed public APIs: every 1.x / 2.x export still imports and compiles.

New #

  • observe() — one-call situational awareness. A single snapshot of the current route, navigation stack, interactive elements, a short text summary, recent logs and errors, and your live app state (optional screenshot). An assistant can ground its next action in one call instead of many reads.
  • App extensions — InkPalAppExtensions.register(...). Expose app-specific operations (reset/seed data, flip a feature flag, jump onboarding) that an assistant can invoke directly, without shipping a new bridge version. Discoverable and callable from the MCP tools.
  • router: on inkpalRunApp. Pass your GoRouter (or any router exposing .go / .push) and the bridge drives named navigation. For go_router it also tracks the current route automatically — including imperative context.push — with no NavigatorObserver wiring.
  • Device metrics. Read the logical viewport size, device pixel ratio, safe-area padding, and orientation so coordinate gestures use the right units.

Improved #

  • Keyed taps are reliable. Tapping by ValueKey now resolves the widget in the element tree and taps its center through the real gesture pipeline — IconButton, FloatingActionButton, and deeply nested buttons that previously couldn't be found now tap correctly. The tappable-ancestor search also reaches deeper widget nesting.
  • Richer tap results. Taps report whether the tap landed, plus the labels that appeared and disappeared, so the outcome is unambiguous.
  • Faster hot reload through the Flutter daemon, with compile errors surfaced directly.
  • Honest gesture inputs. Swipe and drag honor explicit coordinates and report an error on partial input instead of guessing; scroll falls back to a coordinate drag when no scrollable area is detected.

Fixed #

  • Text entry into a missing field now reports failure instead of silently writing to whatever was focused and returning success. The error lists the text fields actually on screen so the caller can retry against the right one.

2.0.0 #

  • Showcase milestone — no public API removals or signature changes. Every export from 1.x still imports and compiles. The one behavioural default flip is called out below ("opt-in error overlay").
  • Opt-in error overlay. inkpalRunApp(enableErrorBoundary: ...) now defaults to false. The in-app banner that previously rendered above every screen sat outside MaterialApp, so it had no Directionality / Overlay ancestor and could cascade into hit-test failures on apps using CustomScrollView / Stack-heavy layouts. The error CATCHER (which feeds inkpal_get_runtime_errors and the bridge's error stream) is a separate subsystem and keeps running regardless of the flag. Pass enableErrorBoundary: true to restore the previous banner.
  • Semantics walker stability. captureScreenContext no longer dispose-and-re-acquires the semantics handle on every call. The always-re-acquire pattern triggered Flutter's debugFrameWasSentToEngine assertion when the capture ran outside a build phase (typical for VM-service callbacks). Re-acquire is now only triggered when the handle is missing.
  • Error overlay layout fix (only relevant if you opt in to it): uses Align instead of Positioned(top:0) so the subtree gets finite constraints, wraps in its own Directionality since the boundary sits above MaterialApp, and drops the Tooltip on the dismiss button (Tooltip needs an Overlay ancestor).
  • 9-zone example app. The example is now a realistic, multi-zone Flutter app covering Core Debug, Visual Debug, Auto-Fix, Runtime Intel, Error Intel, Visual Testing, Developer Experience, Smart Assist, and Forms — each zone planted with the kind of patterns an AI assistant is expected to detect, explain, and act on against a real running app.
  • Smoke test suite covering boot + navigation + grid layout in example/test/smoke_test.dart.
  • inkpalRunApp package-level dartdoc clarified — single recommended entry point, with InkPalBridge.init documented as the power-user / release-mode path.

1.5.0 #

  • Stability milestone. inkpalRunApp is now the single recommended entry point and exposes the full configuration surface. Power-user flags previously reachable only through InkPalBridge.init (knownRoutes, routeDescriptions, onNavigateToRoute, walkerHooks) are now first-class parameters on inkpalRunApp.
  • Example app updated to demonstrate the recommended pattern — multi-zone showcase (counter / forms / list / custom widgets) booted with one inkpalRunApp(...) call, no manual bridge wiring.
  • Example smoke test added (example/test/smoke_test.dart) — three widget tests covering boot, counter interaction, and route navigation.
  • Package-level dartdoc rewritten to lead with the one-line inkpalRunApp(MyApp()) setup. InkPalBridge.init is documented as the power-user / release-mode path.
  • Version constant synced. inkpalBridgeVersion is now bumped in lockstep with the pubspec; ext.flutter.inkpal.ping reports the real shipping version.
  • Manual InkPalBridge.init and direct exports (InkPalErrorCatcher, InkPalHttpMonitor, InkPalErrorBoundary, etc.) remain fully supported — no breaking changes.

1.4.7 #

  • inkpalRunApp now works without any license key. The bridge starts in offline free-tier mode, prints a welcome banner with next-step guidance, and begins watching for errors, navigation, and HTTP traffic immediately.
  • Added periodic idle diagnostics when no client is connected, plus inline connect / disconnect notices when an AI client attaches.
  • Caught errors now surface in a compact console format with location parsed from the stack trace.
  • Default API endpoint now resolves through the canonical InkPal host.
  • Dropped the unused device-fingerprint provisioning path.

1.4.6 #

  • defaultApiUrl updated to the canonical production endpoint.

1.4.5 #

  • Pubspec description trimmed to fit pub.dev's 60–180 character window so search snippets render the full description. No code change.

1.4.4 #

  • README simplified — single primary use case ("let your AI inspect, debug, and control your running Flutter app"), real-conversation example near the top, three-capability summary (Read / Drive / Catch), trimmed command catalog. The example app already demonstrates the full surface.
  • License flow clarified: inkpal_bridge is free for personal and commercial use with a key from inkpal.ai signup. Documented as the install path.

1.4.3 #

  • README rewrite. Stronger positioning ("give your AI a working set of hands inside your Flutter app"), explicit comparison vs marionette_flutter and flutter_driver, simplified quick-start path (1 init call), reordered tier capabilities into use-case columns, added privacy/security section, dropped jargon ("MCP-native"). No code change.
  • Pubspec description rewritten to lead with capability, not acronym — improves first-touch comprehension on pub.dev search results.
  • Custom widgets documented. InkPalWalkerHooks (already public since 1.4.0) is now first-class in the README + has a working demo in example/lib/main.dart. Lets the AI agent recognise proprietary widgets (BrandButton, GlassCard, etc.) by label without Semantics(label:) wrappers. Closes the recognised gap vs marionette_flutter.
  • Example app rewritten as a multi-zone showcase modelled on the InkPal battlefield app — counter zone, forms zone, list zone, custom-widgets zone exercising walkerHooks.
  • Pub.dev score recovery. Score was 145/160. Two deductions fixed: (1) auto_provision.dart:20 and log_buffer.dart:11 had <placeholder> tokens in dartdoc that the analyser flagged as HTML. Replaced with shell-style $placeholder. (2) CHANGELOG now lists 1.4.1+1.4.2+1.4.3 in canonical heading format.
  • Topics updated for discoverability: mcp, ai, claude, copilot, agent (was ai, mcp, testing, automation, devtools).
  • Pricing copy aligned with single Pro model. Earlier 1.4.3 draft carried over the legacy Free / Pro / Studio capability table. Replaced with one "What you can do" table — every capability ships with any valid license (trial or paid). Reflects the locked single-Pro pricing model on inkpal.ai.
  • First-touch pivot: lead with "start free, full power", not prices. Dollar/rupee amounts removed from the README — pub.dev visitors are evaluating, not buying. Pricing details live at inkpal.ai/pricing for visitors who're already convinced. Same change applied to the npm inkpal package README. Studies show pricing-on-first-touch is a conversion killer for dev tools when the product hasn't earned attention yet.

1.4.2 #

  • Version-sync fix. inkpalBridgeVersion constant in lib/src/_version.dart (which ext.flutter.inkpal.ping reports to the MCP server) was stuck at 1.4.0 while pubspec moved to 1.4.1, causing every handshake to mis-report the running bridge version.
  • Pub.dev score recovery. 1.4.1 lost 5 score points because its CHANGELOG.md didn't include a ## 1.4.1 heading. This release fixes the doc convention going forward and documents both 1.4.1 + 1.4.2 changes below.
  • No public API changes from 1.4.1 — drop-in upgrade.

1.4.1 #

  • Packaging hygiene. Untracked stale .dart_tool/ artefacts that were leaking into the published archive (3 entries in package root, 2 under example/). Tarball validation cleaner, no source changes.
  • .pubignore extended to explicitly exclude example/.dart_tool/ and example/build/ so the example/ showcase app's local build state can never bloat future releases.

1.4.0 #

  • App-registered VM extensions. Host apps can expose their own operations via InkPalAppExtensions.register(name:, description:, handler:) under ext.flutter.inkpal.app.<name>. Enumerated + invoked through the new MCP tools so downstream teams extend InkPal without a bridge republish.
  • Synthetic pointer driver + hit-test probe. New coordinate-tap path dispatches PointerAdded/Down/Up/Removed through GestureBinding.handlePointerEvent, interpolating 40px steps for drags. Works on widgets that never emit a SemanticsAction.tap — raw Listener, custom GestureRecognizer, CustomPaint hit regions. Opt-in via x + y params on ext.flutter.inkpal.tap; semantics stays the default. Includes a probeHit() helper that reports whether a point can actually reach a target RenderObject or is absorbed by a ModalBarrier / IgnorePointer / overlay first.
  • Walker hooks for design-system widgets. InkPalBridge.init(walkerHooks: InkPalWalkerHooks(isInteractiveWidget:, shouldStopTraversal:, extractTextFrom:)) lets host apps surface their own widget types to the agent's interaction vocabulary. All three callbacks optional; built-in walker rules still apply when hooks are missing.
  • Zero-config screenshot. When no RepaintBoundary is wired by the host app, falls back to compositing the first RenderView's live layer tree into a fresh Scene and encoding offscreen. Apps that initialise the bridge manually without inkpalRunApp now get working screenshots out of the box. Identical return shape — callers can't tell which path ran.
  • Adaptive scroll. scrollToFind now iterates every on-screen scrollable deepest-first and gives each a full-budget down + up sweep instead of splitting 10/10. Default attempt cap raised to 50; stall detection still breaks the loop early on short lists. Fixes the "scrolling the wrong scrollable" class of miss on nested lists.

1.3.4 #

  • Default API host updated.
  • Topics: add automation, drop debugging.

1.3.3 #

  • B5 fix: element walker re-acquires semantics handle per capture. After multiple push/pop navigation cycles the original SemanticsHandle could become detached from the current PipelineOwner, leaving the semantics tree empty even though widgets render visually. The walker now disposes + re-acquires the handle before each captureScreenContext() call (_reensureSemantics()), and iterates all renderViews instead of only the first. This fixes getWidgetTree + getScreenContent + tap all returning empty/failing after navigation.

1.3.2 #

  • Auto-wired navigator key + observer. Two new globals exported from the package barrel:
    • inkpalNavigatorKeyGlobalKey<NavigatorState> the bridge uses to drive Navigator.pop() directly from ext.flutter.inkpal.goBack, no more "tap the back arrow" gymnastics.
    • inkpalNavigatorObserver — singleton InkPalNavigatorObserver so getCurrentRoute works without consumers having to construct their own observer. Wire on your MaterialApp:
    MaterialApp(
      navigatorKey: inkpalNavigatorKey,
      navigatorObservers: [inkpalNavigatorObserver],
      home: ...,
    )
    
    Without these, goBack falls back to its old tap-the-arrow behaviour.

1.3.1 #

Field-test bug fixes surfaced by running the bridge against a real Flutter benchmark app and exercising ext.flutter.inkpal.* extensions.

  • B1 — Screenshot extension always failed on freshly launched apps. inkpalRunApp now wraps the user's root in a RepaintBoundary tagged with the package-wide inkpalRootRepaintKey. ScreenshotCapture falls back to that key when the configured appContentKey has no context, and the new captureWithDiagnostics() API surfaces the actual exception + truncated stack instead of the opaque "Screenshot capture failed" string. The ext.flutter.inkpal.screenshot extension now reports the real cause of failure.
  • B2 — getCurrentRoute returned null after MaterialPageRoute(builder:) pushes. InkPalNavigatorObserver previously skipped routes whose settings.name was null, leaving the stack empty even after a successful push. The observer now falls back to a <RuntimeType> synthetic identifier (e.g. <MaterialPageRoute<void>>). For stable identifiers across builds, callers should still pass MaterialPageRoute(builder: ..., settings: RouteSettings(name: '/foo')).
  • B3 — getWidgetTree did not surface ValueKey<String>-tagged anchors like Figma Scaffold(key: ValueKey('fig-2-5')). The tree walker now runs an Element-tree pass after the semantics pass and emits a UiElementType.keyed entry (with the new key field on UiElement) for every ValueKey<String>-tagged widget — surfacing structural anchors that don't carry a semantics label of their own.
  • B4 — ext.flutter.inkpal.ping reported the hardcoded version "1.0.0". Added lib/src/_version.dart — a single inkpalBridgeVersion constant that the ping handler reads. Bump this in lockstep with pubspec.yaml (no codegen, no extra dep).

New tests #

  • test/screenshot_capture_test.dart — fallback to inkpalRootRepaintKey
    • structured failure-error coverage.
  • test/route_observer_test.dart — unnamed MaterialPageRoute push now populates the route stack; explicit RouteSettings.name still wins; pop removes the synthetic identifier.
  • test/widget_tree_keyed_test.dartValueKey<String>-tagged Scaffold surfaces with type: keyed; multiple keyed widgets de-dupe; non-string ValueKeys are ignored; JSON round-trip carries the key.

1.3.0 #

  • Socket reconnect hardened — exponential backoff with jitter (500ms base × 2^attempt, ±200ms), 5-minute cap, stops after 30 consecutive failures
  • VM extensions decoupled from WS state — extensions now work regardless of WebSocket connection status
  • Fixed dispose() LateInit crash — safe to call repeatedly, including after failed init
  • _perfMonitor guaranteed initialized in all init() code paths
  • Testing hooks exposed@visibleForTesting getters for router (CommandRouter) and semanticsWalker (SemanticsWalker)
  • SemanticsWalker re-exported from public barrel
  • New tests: reconnect_test.dart, dispose_test.dart, visible_for_testing_test.dart — 12+ new tests across all new/fixed behavior
  • New test fixture: test_app/ with keyed widget harness + 40 integration tests covering all 33 VM extensions
  • New CI: .github/workflows/bridge-e2e.yml runs on Android emulator + iOS simulator on every bridge push

1.2.2 #

  • Remove comparison-to-alternatives section from README
  • No code changes

1.2.1 #

  • Sharpened pubspec description with target search keywords (Flutter MCP, runtime error capture, HTTP monitor, VM service extensions)
  • README: added comparison table vs moinsen_runapp and marionette_mcp
  • README: bumped install version to ^1.2.1
  • No code changes

1.2.0 #

  • Added InkPalErrorCatcher: three-layer error capture (FlutterError + PlatformDispatcher + runZonedGuarded) with time-windowed deduplication and handler chaining
  • Added InkPalHttpMonitor: read-only HTTP request monitor with redacted headers, ring buffer, and collision detection against InkPalNetworkInterceptor
  • Added InkPalErrorBoundary: Stack-based overlay widget that survives app rebuild failures, with debug + release builders
  • Added generateInkPalBugReport: packages errors, HTTP, logs, route, and app state as markdown for LLM consumption (capped at 8k chars)
  • Added inkpalRunApp: drop-in runApp replacement wiring error catcher + HTTP monitor + error boundary + bridge init inside a guarded zone
  • Added optional errorCatcher and httpMonitor parameters to InkPalBridge.init (backward-compatible)
  • Fixed ErrorSubscriber silently dropping errors before startWatching() — ring buffer now always populated, _watching gates only stream notification

1.1.0 #

  • WebSocket connect timeout (10s) with TimeoutException on miss
  • licenseReady future on InkPalBridge for license-gated startup flows
  • Grace period enforcement moved to tier getter (callers bypassing hasFeature() now get downgraded correctly)
  • Screenshot capture wrapped in 5s timeout — returns null instead of hanging
  • License validator test suite (validation flow, grace period, throttle, network failure)

1.0.1 #

  • Shortened package description for pub.dev display
  • Fixed homepage URL (now points to GitHub repo)
  • Removed web platform declaration (package uses dart:io)
  • Suppressed hasFlag deprecation warnings

1.0.0 #

Initial stable release.

Core #

  • WebSocket-based bidirectional communication (JSON-RPC 2.0)
  • Automatic reconnection with exponential backoff (1s-64s)
  • 10-second connection timeout
  • Message buffering during disconnects (100 message cap)
  • Zero overhead in release builds (bridge is null)

Inspection #

  • Semantics tree walking — read UI without instrumentation
  • Widget tree as structured JSON
  • Element search by label or text
  • Screenshot capture with configurable width (default 720px, 5s timeout)
  • Screen context caching with automatic invalidation

Interaction #

  • Tap, long press, double tap by label/key/semantics
  • Text field input
  • Scroll (directional + scroll-to-element)
  • Slider/stepper increment and decrement
  • Route navigation (push, pop, go)
  • Route tracking via NavigatorObserver
  • Full navigation stack access
  • Route discovery (all routes seen in session)
  • Support for standard Navigator, go_router, GetX, and Beamer
  • Custom onNavigateToRoute callback for any router

Telemetry #

  • Structured logging (log, debug, warning, error levels)
  • Real-time error streaming with immediate delivery
  • Log batching (500ms intervals for non-error logs)
  • Error context enrichment (widget tree, state, recent logs)
  • Log correlation — time-windowed action-to-log mapping

State Time-Travel #

  • State snapshot capture
  • Snapshot listing and retrieval
  • State diffing between any two snapshots
  • Live state stream observation
  • Max 50 snapshots with automatic eviction

Interaction Recording #

  • Record user/AI interactions as typed actions
  • Export as JSON or Dart integration test code
  • Recording status monitoring

Layout Diffing #

  • Screen layout snapshot capture
  • Before/after layout comparison

Performance #

  • FPS monitoring
  • Jank detection

Network Control (Studio tier) #

  • Per-URL HTTP mock rules (pattern matching, response code, body, delay)
  • Offline mode (block all requests)
  • Latency and packet loss simulation

VM Service Extensions #

  • 33 extensions registered under ext.flutter.inkpal.*
  • Fallback channel for local development without WebSocket
  • Mirrors all WebSocket commands

License Gating #

  • Three tiers: Free, Pro, Studio
  • Server-side validation with signed grants (HMAC-SHA256)
  • 24-hour grant TTL with 7-day grace period
  • Validation throttling (60s cooldown)
  • licenseReady future for callers needing gate sync

App Manifest #

  • Rich AiAppManifest for LLM context
  • Screen manifest with widget descriptions
  • App map with full structure

Touch Visualization #

  • Visual ripple overlay for AI-driven interactions
  • Configurable via TouchVisualizerController

Platform #

  • Zero third-party dependencies (Flutter SDK only)
  • Flutter 3.10+ / Dart 3.0+
  • MIT license
5
likes
160
points
745
downloads

Documentation

API reference

Publisher

verified publisherinkpal.ai

Weekly Downloads

Turn your Flutter app into an MCP server. AI editors (Claude Code, Cursor, Windsurf, Codex, Copilot) inspect, drive, hot-reload, auto-recover it. 80 tools. Zero deps.

Homepage
Repository (GitHub)
View/report issues

Topics

#mcp #ai #claude #copilot #agent

License

MIT (license)

Dependencies

flutter

More

Packages that depend on inkpal_bridge