Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Every registration routes through `registerExtensionIdempotent` (from `fluttersd
These cannot change without a coordinated bump across `magic` + `wind` + `dusk`:

1. `DuskSnapshotEnricher` typedef shape: `String? Function(Element, RefRegistry)`
2. `DuskPlugin.install()`, the `DuskPlugin.enrichers` live-append list (magic appends to it via `MagicDuskIntegration`), and `DuskPlugin.registerNavigateAdapter()` signatures
2. `DuskPlugin.install()`, the `DuskPlugin.enrichers` live-append list (`magic_devtools` appends to it via `MagicDuskIntegration`), and `DuskPlugin.registerNavigateAdapter()` signatures
3. `RefRegistry` public method signatures (`register`, `lookup`, `registerQuery`, `lookupQuery`, `disposeAll`, `resetForTesting`)
4. The 6 alpha-1 MCP tool names (`dusk_snap`, `dusk_tap`, `dusk_screenshot`, `dusk_hover`, `dusk_drag`, `dusk_type`) and their `ext.dusk.*` extension method names
5. `DuskActionabilityException` `reason` substring vocabulary (`not enabled`, `zero rect`, `off-viewport`, `not stable`, `obscured by`)
Expand Down
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.

---

## [Unreleased]

---

## [0.0.8] - 2026-06-17

### Changed

- **`dusk:install` now injects `import 'package:magic_devtools/dusk.dart';` and gates on the `magic_devtools` dependency** instead of the removed `package:magic/dusk_integration.dart`. The `MagicDuskIntegration` class was extracted from the `magic` core into the new `magic_devtools` package; the injected class name (`MagicDuskIntegration.install()`) is unchanged. Consumers that follow magic's install.yaml (which adds `magic_devtools` to dev_dependencies before running `dusk:install`) get the integration wired automatically; magic-only consumers without `magic_devtools` in pubspec.yaml are unaffected. Coordinated with the magic_devtools extraction.

### Fixed

- **`dusk:install` no longer injects `import 'package:magic_devtools/dusk.dart';` or `MagicDuskIntegration.install()` into a vanilla Flutter app that has `magic_devtools` in its pubspec but no `await Magic.init(` call in `lib/main.dart`.** Previously, the `magic_devtools` wiring block ran whenever the pubspec listed the dependency, regardless of whether a `Magic.init` anchor existed. This left an unused import in the consumer's file, causing `dart analyze` to fail. The gate is now `hasMagicInit && _hasMagicDevtoolsDep()`, matching the block's own intent documented in the comment above it. The existing `try/catch` around `injectAfterMagicInit` is retained as a defensive fallback.

### Documentation

- Docs, skill, and example synced to the `magic_devtools` extraction: `doc/plugins/magic-integration.md` updated to note that `MagicDuskIntegration` now ships in `magic_devtools` (add as a dev_dependency) and shows the required `import 'package:magic_devtools/dusk.dart';`. `skills/fluttersdk-dusk/references/cli-commands.md` updated to reflect the `magic_devtools` gate and `magic_devtools/dusk.dart` import. `ARCHITECTURE.md` frozen-contracts item updated from `magic` to `magic_devtools`. Version pins bumped to `^0.0.8` throughout (`pubspec.yaml`, `example/pubspec.yaml`, `doc/getting-started/installation.md`, `skills/fluttersdk-dusk/SKILL.md`).

Comment thread
anilcancakir marked this conversation as resolved.
---

## [0.0.7] - 2026-06-17

### Added
Expand Down Expand Up @@ -191,7 +211,9 @@ Initial public release of `fluttersdk_dusk`. E2E driver for Flutter apps. Snapsh

`DuskSnapshotEnricher` typedef, `DuskPlugin.install` / `DuskPlugin.enrichers` / `DuskPlugin.registerNavigateAdapter`, `RefRegistry` public methods (`register`, `lookup`, `registerQuery`, `lookupQuery`, `disposeAll`, `resetForTesting`), and every MCP tool name / `ext.dusk.*` extension name are part of the public 0.0.1 contract. Future releases keep these stable across the 0.x line; any change requires a coordinated bump with `magic` + `wind`.

[Unreleased]: https://github.com/fluttersdk/dusk/compare/0.0.6...HEAD
[Unreleased]: https://github.com/fluttersdk/dusk/compare/0.0.8...HEAD
[0.0.8]: https://github.com/fluttersdk/dusk/compare/0.0.7...0.0.8
[0.0.7]: https://github.com/fluttersdk/dusk/compare/0.0.6...0.0.7
[0.0.6]: https://github.com/fluttersdk/dusk/compare/0.0.5...0.0.6
[0.0.5]: https://github.com/fluttersdk/dusk/compare/0.0.4...0.0.5
[0.0.4]: https://github.com/fluttersdk/dusk/compare/0.0.3...0.0.4
Expand Down
17 changes: 10 additions & 7 deletions doc/commands/dusk-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

One-shot bootstrap for `fluttersdk_dusk`. Runs in two phases, both idempotent:

1. **Patch `lib/main.dart`** — inject the imports, `WidgetsFlutterBinding.ensureInitialized()`, and a `kDebugMode`-gated `DuskPlugin.install()` block before `runApp(`. Magic-stack apps additionally wire `MagicDuskIntegration.install()` after `Magic.init(`.
1. **Patch `lib/main.dart`** — inject the imports (including `import 'package:magic_devtools/dusk.dart';` when applicable), `WidgetsFlutterBinding.ensureInitialized()`, and a `kDebugMode`-gated `DuskPlugin.install()` block before `runApp(`. Magic-stack apps additionally wire `MagicDuskIntegration.install()` after `Magic.init(` when `magic_devtools:` is present in `pubspec.yaml`.
2. **Chain fastcli setup** — best-effort `dart run fluttersdk_dusk install` (scaffolds `bin/dispatcher.dart` + `./bin/fsa` AOT wrapper) + `dart run fluttersdk_dusk plugin:install fluttersdk_dusk` (registers `DuskArtisanProvider`). Both sub-process calls are skipped when their idempotency markers already exist (`bin/dispatcher.dart` for the scaffold, `.artisan/installed/fluttersdk_dusk.json` for the plugin record). Failures are swallowed with a warning; the Phase 1 patch always succeeds on its own, so the consumer can still drive dusk via `dart run fluttersdk_dusk <cmd>` even when the chain skipped.

Together, the two phases mean a fresh consumer needs only:
Expand All @@ -12,7 +12,7 @@ flutter pub add fluttersdk_dusk
dart run fluttersdk_dusk dusk:install
```

After the second command, `./bin/fsa list` surfaces all 32 `dusk:*` commands and `./bin/fsa mcp:serve` exposes the 31 MCP tools.
After the second command, `./bin/fsa list` surfaces all 34 `dusk:*` commands and `./bin/fsa mcp:serve` exposes the 33 MCP tools.

---

Expand Down Expand Up @@ -46,7 +46,7 @@ dart run fluttersdk_dusk dusk:install
| Input | Source | Purpose |
|-------|--------|---------|
| `lib/main.dart` path | `DuskInstallCommand.mainDartPathResolver()` (defaults to `lib/main.dart`) | Target file for snippet injection. Test seam: override the resolver to point at a fixture. |
| `pubspec.yaml` path | `DuskInstallCommand.pubspecPathResolver()` (defaults to `pubspec.yaml`) | Inspected for `magic:` and `fluttersdk_wind:` dependency entries. Drives the conditional wiring decisions described under [Anchor modes](#anchor-modes). |
| `pubspec.yaml` path | `DuskInstallCommand.pubspecPathResolver()` (defaults to `pubspec.yaml`) | Inspected for `magic_devtools:` and `fluttersdk_wind:` dependency entries. Drives the conditional wiring decisions described under [Anchor modes](#anchor-modes). |

Both resolvers are public static fields so tests can override per-test without leaking files into the running test process' cwd.

Expand All @@ -71,7 +71,7 @@ No structured payload is emitted. Status flows through `ArtisanOutput.info` / `s

The injector picks one of two anchor strings depending on what `lib/main.dart` already contains:

- **Magic-stack apps** (`lib/main.dart` contains `await Magic.init(`): `DuskPlugin.install()` is wired BEFORE `Magic.init(` so the driver is live during Magic boot. When `magic:` is also a pubspec dependency, `MagicDuskIntegration.install()` is also injected AFTER `Magic.init()` (the integration queries `Magic.find<X>()` for the form and nav enrichers, which only resolves once the container is ready).
- **Magic-stack apps** (`lib/main.dart` contains `await Magic.init(`): `DuskPlugin.install()` is wired BEFORE `Magic.init(` so the driver is live during Magic boot. When `magic_devtools:` is also a pubspec dependency or dev_dependency, `import 'package:magic_devtools/dusk.dart';` is added and `MagicDuskIntegration.install()` is injected AFTER `Magic.init()` (the integration queries `Magic.find<X>()` for the form and nav enrichers, which only resolves once the container is ready).
- **Vanilla apps** (no `Magic.init` anchor): `DuskPlugin.install()` is wired immediately before `runApp(`.

When the consumer's pubspec lists `fluttersdk_wind:` as a top-level dependency, `Wind.installDebugResolver()` lands inside the same `kDebugMode` block as `DuskPlugin.install()`. Wind alpha-10 no longer ships a dusk-specific integration class; dusk reads wind state through the neutral `WindDebugRegistry` bridge at snap time. The Wind enricher wiring is independent of the Magic detection: a magic-free app with `fluttersdk_wind` still gets the wind metadata block.
Expand All @@ -80,7 +80,7 @@ The full sub-step list (from the source docblock):

1. Add the two required imports (`kDebugMode` from `package:flutter/foundation.dart`; the `package:fluttersdk_dusk/dusk.dart` barrel).
2. Inject `WidgetsFlutterBinding.ensureInitialized()` (skip when already present) plus the `kDebugMode`-gated dusk block before the canonical install anchor.
3. When pubspec has `magic:` AND main.dart has `await Magic.init(`, inject `MagicDuskIntegration.install()` AFTER that call.
3. When pubspec has `magic_devtools:` (dependency or dev_dependency) AND main.dart has `await Magic.init(`, inject `import 'package:magic_devtools/dusk.dart';` and `MagicDuskIntegration.install()` AFTER that call.

---

Expand Down Expand Up @@ -127,9 +127,12 @@ The injector early-returns on every duplicate snippet. The output still prints `

### 3. Magic-stack app with wind enricher

When pubspec lists both `magic:` and `fluttersdk_wind:`, the post-install `lib/main.dart` looks like:
When pubspec lists both `magic_devtools:` and `fluttersdk_wind:`, the post-install `lib/main.dart` looks like:

```dart
import 'package:fluttersdk_dusk/dusk.dart';
import 'package:magic_devtools/dusk.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (kDebugMode) {
Expand All @@ -138,7 +141,7 @@ void main() async {
}
await Magic.init(MyApp.new);
if (kDebugMode) {
MagicDuskIntegration.install();
MagicDuskIntegration.install(); // magic_devtools wiring
}
}
```
Expand Down
10 changes: 6 additions & 4 deletions doc/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Alternatively, add it manually to `pubspec.yaml`:

```yaml
dependencies:
fluttersdk_dusk: ^0.0.2
fluttersdk_dusk: ^0.0.8
```

Then fetch dependencies:
Expand Down Expand Up @@ -94,13 +94,15 @@ either, both, or neither depending on your stack.

| Integration | Package | Enrichment |
|:------------|:--------|:-----------|
| `MagicDuskIntegration.install()` | `magic` | MagicForm field values, validation state, named route per node. |
| `MagicDuskIntegration.install()` | `magic_devtools` | MagicForm field values, validation state, named route per node. |
| `Wind.installDebugResolver()` (in `package:fluttersdk_wind/fluttersdk_wind.dart`) | `fluttersdk_wind` >= alpha-10 | Wind state surfaces through the neutral `WindDebugRegistry` bridge; dusk emits the 6 core fields (breakpoint, brightness, platform, states, bgColor, textColor) automatically without enricher registration. |

```dart
import 'package:magic_devtools/dusk.dart'; // magic_devtools only
// ...
if (kDebugMode) {
DuskPlugin.install();
MagicDuskIntegration.install(); // magic-stack only
MagicDuskIntegration.install(); // magic-stack only (from magic_devtools)
Wind.installDebugResolver(); // wind UI only (alpha-10+)
}
```
Expand All @@ -127,7 +129,7 @@ The optional `mcp:install` step in the next section writes the plugin-aware `.mc
<a name="wire-mcp-tools"></a>
## Wire MCP tools

With artisan registered, expose dusk's 31 MCP tools to your AI client by writing the `.mcp.json` entry:
With artisan registered, expose dusk's 33 MCP tools to your AI client by writing the `.mcp.json` entry:

```bash
dart run fluttersdk_dusk mcp:install
Expand Down
14 changes: 13 additions & 1 deletion doc/plugins/magic-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ of `DuskSnapshotEnricher` callbacks against `DuskPlugin.enrichers`, so every
`dusk:snap` (or `dusk_snap` MCP) output carries Magic-aware annotations
alongside the standard Semantics tree.

`MagicDuskIntegration` ships in the **`magic_devtools`** package, not in magic
core. Add it as a dev_dependency (it is debug-only; release builds tree-shake
the `kDebugMode` branch that calls `install()`):

```yaml
dev_dependencies:
magic_devtools: ^<version>
```

This document covers the five behavioural enrichers most likely to drive an
agent's reasoning loop. The full integration ships fourteen enrichers; the
nine not covered here (`magicFormEnricher`, `magicNavigationEnricher`,
Expand All @@ -27,9 +36,12 @@ require the service providers to be live). It must also run **after**
`DuskPlugin.install()`, because the `DuskPlugin.enrichers` list is the
target the integration mutates.

The canonical debug-only host integration:
The canonical debug-only host integration (`MagicDuskIntegration` comes from
the `magic_devtools` dev_dependency):

```dart
import 'package:magic_devtools/dusk.dart'; // from magic_devtools dev_dependency

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Magic.init();
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.0.7"
version: "0.0.8"
fluttersdk_wind_diagnostics_contracts:
dependency: transitive
description:
Expand Down
1 change: 0 additions & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ dependencies:

fluttersdk_dusk:
path: ..
version: 0.0.7

dev_dependencies:
flutter_test:
Expand Down
29 changes: 15 additions & 14 deletions lib/src/commands/dusk_install_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import 'package:fluttersdk_artisan/artisan.dart';
/// Two anchor modes:
/// - Magic-stack apps (`lib/main.dart` contains `await Magic.init(`):
/// wire `DuskPlugin.install()` BEFORE `Magic.init` so the driver is
/// live during Magic boot. When `magic:` is also a pubspec dep, ALSO
/// inject `MagicDuskIntegration.install()` AFTER `Magic.init` (the
/// integration queries `Magic.find<X>()` for the form / nav
/// enrichers, which only resolves once the container is ready).
/// live during Magic boot. When `magic_devtools:` is also a pubspec
/// dependency or dev_dependency, ALSO inject `MagicDuskIntegration.install()`
/// AFTER `Magic.init` (the integration queries `Magic.find<X>()` for the
/// form / nav enrichers, which only resolves once the container is ready).
/// - Vanilla apps: wire `DuskPlugin.install()` before `runApp(`.
///
/// Idempotent across re-runs: each `addImport` / `injectBeforeAnchor` /
Expand All @@ -45,7 +45,7 @@ class DuskInstallCommand extends ArtisanCommand {
static String _defaultMainDartPath() => 'lib/main.dart';

/// Hook for tests to override the resolved `pubspec.yaml` path used
/// for `magic:` dependency detection.
/// for `magic_devtools:` dependency detection.
static String Function() pubspecPathResolver = _defaultPubspecPath;

static String _defaultPubspecPath() => 'pubspec.yaml';
Expand Down Expand Up @@ -177,7 +177,7 @@ class DuskInstallCommand extends ArtisanCommand {
/// the canonical install anchor: `await Magic.init(` on
/// Magic-stack apps (so dusk is wired before Magic boot side
/// effects), otherwise `runApp(` for vanilla Flutter apps.
/// 3. When pubspec has `magic:` AND main.dart has `await Magic.init(`,
/// 3. When pubspec has `magic_devtools:` AND main.dart has `await Magic.init(`,
/// inject `MagicDuskIntegration.install()` AFTER that call (the
/// integration queries `Magic.find<X>()` for the form / nav
/// enrichers, so it must run after the container is ready).
Expand Down Expand Up @@ -233,13 +233,13 @@ class DuskInstallCommand extends ArtisanCommand {
FileHelper.writeFile(mainDartPath, source);
}

// 3. Magic-side coordinated wiring when the consumer pulls in magic.
// Detect via pubspec.yaml; skip silently when magic is not a dep
// 3. Magic-side coordinated wiring when the consumer pulls in magic_devtools.
// Detect via pubspec.yaml; skip silently when magic_devtools is not a dep
// or when main.dart has no Magic.init() anchor (vanilla app).
if (_hasMagicDep()) {
if (hasMagicInit && _hasMagicDevtoolsDep()) {
MainDartEditor.addImport(
mainDartPath,
"import 'package:magic/dusk_integration.dart';",
"import 'package:magic_devtools/dusk.dart';",
);
try {
MainDartEditor.injectAfterMagicInit(
Expand All @@ -255,11 +255,12 @@ class DuskInstallCommand extends ArtisanCommand {
}
}

/// Returns true when the consumer's pubspec.yaml lists `magic:` as a
/// top-level dependency (2-space indent under `dependencies:`).
static bool _hasMagicDep() {
/// Returns true when the consumer's pubspec.yaml lists `magic_devtools:`
/// (the package that ships MagicDuskIntegration) under `dependencies:` or
/// `dev_dependencies:` (2-space indent).
static bool _hasMagicDevtoolsDep() {
final pubspec = File(pubspecPathResolver());
if (!pubspec.existsSync()) return false;
return RegExp(r'\n magic:').hasMatch(pubspec.readAsStringSync());
return RegExp(r'\n magic_devtools:').hasMatch(pubspec.readAsStringSync());
}
}
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: fluttersdk_dusk
description: "Flutter E2E driver for LLM agents and CI. 34 CLI commands and 33 MCP tools drive a running app over VM Service extensions; no flutter_test harness needed."
version: 0.0.7
version: 0.0.8
homepage: https://fluttersdk.com/dusk
repository: https://github.com/fluttersdk/dusk
issue_tracker: https://github.com/fluttersdk/dusk/issues
Expand Down Expand Up @@ -32,7 +32,7 @@ dependencies:

executables:
# `dart run fluttersdk_dusk <cmd>` is the dusk-flavoured artisan wrapper.
# Surfaces 32 CLI commands + 31 MCP tools (28 ext.dusk.* + 3 artisan:dusk:*) via DuskArtisanProvider.
# Surfaces 34 CLI commands + 33 MCP tools (30 ext.dusk.* + 3 artisan:dusk:*) via DuskArtisanProvider.
fluttersdk_dusk: fluttersdk_dusk

dev_dependencies:
Expand Down
Loading