https://github.com/fluttersdk/wind_diagnostics_contracts
Pure abstract contracts for reading Wind UI widget state at runtime without a compile-time dep on fluttersdk_wind. Zero deps. plugin_platform_interface pattern applied to UI framework + debug tooling decoupling (MCP, AI agents, DevTools).
https://github.com/fluttersdk/wind_diagnostics_contracts
ai-agents contracts dart debug-tooling diagnostics e2e-testing flutter flutter-package flutter-testing llm mcp mcp-server platform-interface tailwind-flutter wind wind-ui
Last synced: about 1 month ago
JSON representation
Pure abstract contracts for reading Wind UI widget state at runtime without a compile-time dep on fluttersdk_wind. Zero deps. plugin_platform_interface pattern applied to UI framework + debug tooling decoupling (MCP, AI agents, DevTools).
- Host: GitHub
- URL: https://github.com/fluttersdk/wind_diagnostics_contracts
- Owner: fluttersdk
- License: mit
- Created: 2026-05-21T12:14:07.000Z (2 months ago)
- Default Branch: develop
- Last Pushed: 2026-05-21T12:41:16.000Z (2 months ago)
- Last Synced: 2026-05-21T19:36:12.633Z (2 months ago)
- Topics: ai-agents, contracts, dart, debug-tooling, diagnostics, e2e-testing, flutter, flutter-package, flutter-testing, llm, mcp, mcp-server, platform-interface, tailwind-flutter, wind, wind-ui
- Language: Dart
- Homepage: https://pub.dev/packages/fluttersdk_wind_diagnostics_contracts
- Size: 17.6 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
Wind Diagnostics Contracts
Wind UI widget state contracts for Flutter debug-tooling and AI agents (MCP).
Zero-dep abstract resolver interface plus process-global registry. The plugin_platform_interface pattern, applied to UI framework and debug tooling decoupling. ~80 LoC, frozen v1 contract.
Documentation ·
pub.dev ·
Issues
---
## Use cases
This package is the integration seam for any tool that needs runtime Wind UI state without pulling Wind into its compile graph:
- **LLM-agent E2E testing for Flutter**: MCP servers and AI assistants (Claude Code, Cursor, Copilot) that drive a Flutter app need structured widget state (className, breakpoint, brightness, platform, states, colors) per `Element`, not screenshots. This package is the typed contract those tools read.
- **DevTools extensions**: build a custom Wind inspector tab in Dart DevTools without a hard `fluttersdk_wind` dependency that drags the rendering surface into the extension bundle.
- **E2E drivers (`fluttersdk_dusk`, `patrol_mcp`, `marionette_mcp`, custom)**: emit a `wind:` block in snapshot YAML / JSON without importing Wind. Tests stop breaking when a className is renamed because the contract surfaces semantic state, not selectors.
- **Tailwind-on-Flutter authors**: any styling library that follows Wind's className convention can implement this contract to expose its state to the same debug-tooling ecosystem.
---
## Why this package exists
[Wind UI](https://github.com/fluttersdk/wind) ([pub.dev](https://pub.dev/packages/fluttersdk_wind)) exposes runtime widget state (`className`, `breakpoint`, `brightness`, `platform`, `states`, `bgColor`, `textColor`) that debug-tooling packages such as [`fluttersdk_dusk`](https://github.com/fluttersdk/dusk) ([pub.dev](https://pub.dev/packages/fluttersdk_dusk)) embed in their snapshot YAML so LLM agents can reason about the rendered tree.
Shipping that handoff through `fluttersdk_wind`'s own surface would force every debug-tool to compile-time depend on Wind, dragging the full rendering surface and bumping debug-tool builds on every Wind release. Shipping it the other way (Wind depending on each debug tool) is even worse.
**This package breaks the loop.** Both sides depend on the abstract contract here; neither side imports the other.
```
fluttersdk_wind_diagnostics_contracts
|
+---------------+---------------+
| |
fluttersdk_wind fluttersdk_dusk
(registers a resolver) (reads the resolver
at snapshot time)
```
The pattern mirrors Flutter's `*_platform_interface` convention. [`plugin_platform_interface`](https://pub.dev/packages/plugin_platform_interface) (the canonical precedent) sits at 4.97M downloads on pub.dev for exactly this reason.
---
## Install
```bash
flutter pub add fluttersdk_wind_diagnostics_contracts
```
Most consumers never add this dep by hand. `fluttersdk_wind` declares it as a direct production dependency, so any app that already depends on Wind picks it up transitively. Add it explicitly when you are authoring a debug-tooling package that reads Wind state without depending on Wind itself.
---
## Usage
### Registering a resolver (in `fluttersdk_wind`)
Wind installs its concrete resolver at app boot, gated by `kDebugMode` so release builds tree-shake the entire registration site:
```dart
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:fluttersdk_wind/fluttersdk_wind.dart';
void main() {
if (kDebugMode) {
Wind.installDebugResolver();
}
runApp(const MyApp());
}
```
`Wind.installDebugResolver()` is a one-liner inside `fluttersdk_wind` that does:
```dart
import 'package:fluttersdk_wind_diagnostics_contracts/fluttersdk_wind_diagnostics_contracts.dart';
WindDebugRegistry.register(const WindDebugResolverImpl());
```
### Reading the resolver (in a debug-tooling package)
The consumer looks up the currently-installed resolver and calls `resolve(element)` per `Element` it wants to inspect. The resolver returns `const {}` for non-Wind widgets, so the walk is safe for any element:
```dart
import 'package:fluttersdk_wind_diagnostics_contracts/fluttersdk_wind_diagnostics_contracts.dart';
void emitWindBlock(StringBuffer buffer, Element element) {
final WindDebugResolver? resolver = WindDebugRegistry.current;
if (resolver == null) return; // wind not in this app, or release build.
final Map data = resolver.resolve(element);
if (data.isEmpty) return; // not a Wind widget.
buffer.writeln('wind:');
data.forEach((key, value) {
buffer.writeln(' $key: $value');
});
}
```
The returned map's key set is documented as the v1 frozen contract (see [CHANGELOG.md](CHANGELOG.md) for the full key list).
### Test seams
`WindDebugRegistry` exposes two `@visibleForTesting` helpers so debug-tool tests can register fake resolvers without going through Wind:
```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:fluttersdk_wind_diagnostics_contracts/fluttersdk_wind_diagnostics_contracts.dart';
class _FakeResolver implements WindDebugResolver {
@override
Map resolve(Element element) {
return const {
'className': 'flex p-4',
'breakpoint': 'lg',
'brightness': 'light',
'platform': 'web',
'states': ['hover'],
};
}
}
void main() {
setUp(() => WindDebugRegistry.resetForTesting());
testWidgets('observe emits wind block from registry', (tester) async {
WindDebugRegistry.registerForTesting(_FakeResolver());
// ... rest of the test ...
});
}
```
---
## Versioning
This package follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). The `WindDebugResolver.resolve` return-map key set is the load-bearing v1 contract: additive changes (new keys in the returned map) are non-breaking; renaming or removing existing keys requires a major bump.
---
## License
MIT. See [LICENSE](LICENSE).