Migrating from fluttersdk_dusk
How to move an app that runs fluttersdk_dusk onto tapir and dowel, what tapir:install and tapir:doctor detect and print for you, and which command and tool names changed.
On this page
Quick Reference#
Nothing is migrated automatically. The package, the repository, the barrels, the classes, the extensions, the tools and the environment variables all changed name, and the substrate under them is dowel instead of fluttersdk_artisan:
| Was | Is |
|---|---|
fluttersdk_dusk, fluttersdk_artisan |
tapir, dowel |
Dusk* classes, DuskArtisanProvider |
Tapir* classes, TapirDowelProvider |
ext.dusk.* VM Service extensions |
ext.tapir.* |
dusk_* MCP tools |
tapir_* |
dusk:* CLI commands, artisan:dusk:* substrate commands |
tapir:*, dowel:tapir:* |
DUSK_* environment variables (DUSK_FORCE_FRAMES) |
TAPIR_* (TAPIR_FORCE_FRAMES) |
The perf zone key #fluttersdk_interaction |
#tapir_interaction |
bin/fsa, .artisan/ |
bin/dowel, .dowel/ |
The fluttersdk server in .mcp.json |
The dowel server |
mcp__fluttersdk__dusk_* in a Claude Code allow list |
mcp__dowel__tapir_* |
Migration Steps#
Run them in this order. tapir:install can run at any point and prints what is still left, so a half-finished move is reported rather than failing inside a compiler error.
-
Swap the packages. Replace the old packages and rename what your code imports:
bash flutter pub remove fluttersdk_dusk fluttersdk_artisan flutter pub add tapir dowelEvery
package:fluttersdk_dusk/...import becomespackage:tapir/tapir.dart, orpackage:tapir/cli.dartin the code that runs on the plain Dart VM. EveryDusk*identifier becomesTapir*, andDuskArtisanProviderbecomesTapirDowelProvider. -
Move the state directories. Follow the dowel migration: move
.artisan/to.dowel/and~/.artisan/to~/.dowel/, then delete.dowel/plugins.jsonand thefluttersdk_*records under.dowel/installed/. -
Delete the generated files. Remove
bin/dispatcher.dart,bin/fsa,lib/app/_plugins.g.dartandlib/app/commands/_index.g.dart. They import the old package and no longer compile, and an existingbin/dispatcher.dartis kept by the installer, sobin/stops analysing while it stays. -
Port your own commands. In every file under
lib/app/commands/, importpackage:dowel/dowel.dartinstead of the old package and rename eachArtisan*class toDowel*. -
Delete the old MCP entry. Remove the
fluttersdkserver from.mcp.jsonand from every client you registered by hand (claude mcp remove fluttersdk, the VS Code or Claude Desktop settings). It runs./bin/fsa mcp:serve, which step 3 deleted, andmcp:installaddsdowelnext to it instead of replacing it. Renamedusk_*totapir_*in any saved allow list, prompt or script. -
Scaffold again and register.
bash dart run dowel install dart run dowel commands:refresh dart run tapir tapir:install dart run dowel plugin:install <package> dart run tapir mcp:installdowel installwrites the dispatcher, the barrels andbin/dowel,commands:refreshregisters your commands again, andtapir:installpatcheslib/main.dartand registers the plugin. Step 2 deleted.dowel/plugins.json, so the barrel now registers tapir alone: rundart run dowel plugin:install <package>once for every other plugin the app uses, or its commands are gone.mcp:installwrites thedowelentry; reconnect your MCP client afterwards, because a stdio server is not reloaded on its own. -
Check the result.
bash dart run tapir start --device=chrome dart run tapir tapir:doctorThe
fluttersdk_dusk leftoversrow of the doctor readsnone foundwhen the project is clean.
A Magic app has one more step: see Magic Apps.
What tapir Detects#
tapir:install and tapir:doctor both look in the project root for what an install of fluttersdk_dusk left behind. The installer matters here because an old bin/dispatcher.dart also satisfies its own idempotency marker, so without the notice dowel install would be skipped in silence.
| It reports | When |
|---|---|
bin/dispatcher.dart (imports fluttersdk_artisan): delete it |
The file exists and mentions fluttersdk_artisan. |
bin/fsa: delete it |
The file exists. |
lib/app/commands/_index.g.dart: delete it |
The file imports package:fluttersdk_artisan/ or package:fluttersdk_dusk/, or names a class the old package exported (ArtisanCommand, ArtisanContext and the rest dowel renamed) or runArtisan. The index dowel install and commands:refresh write is not reported. |
<path> (uses Artisan* classes): port it to the Dowel* classes |
A .dart file under lib/app/commands/ (other than the index) mentions an Artisan class. One line per file. |
.mcp.json: delete the "fluttersdk" server entry |
The file has a "fluttersdk" key. |
tapir:install patches lib/main.dart first, then prints one warning that lists every finding, and goes on with its second phase. It deletes nothing and still exits 0; it exits 1 only when lib/main.dart is missing:
⚠ Leftovers from fluttersdk_dusk found:
- bin/dispatcher.dart (imports fluttersdk_artisan): delete it
- bin/fsa: delete it
- lib/app/commands/_index.g.dart: delete it
- lib/app/commands/deploy_command.dart (uses Artisan* classes): port it to the Dowel* classes
- .mcp.json: delete the "fluttersdk" server entry
Then run `dart run dowel install`, `dart run dowel commands:refresh` and `dart run tapir tapir:install`.
Each line appears only when its file is there. With bin/dispatcher.dart still in place the installer does not scaffold bin/dowel, because its marker for that step is the dispatcher file.
tapir:doctor prints the same list as the last row, as a warning that never changes its exit code. A clean project prints:
✓ fluttersdk_dusk leftovers: none found
and a project with leftovers prints fluttersdk_dusk leftovers: found, the same lines and the closing Then run ... sentence. See tapir:doctor for the other rows.
Renamed Commands and Tools#
Every command and tool takes the new prefix. Most keep their verb; these do not:
| Was | Is |
|---|---|
dusk:wait |
tapir:wait_for |
dusk:modal |
tapir:dismiss_modals |
dusk:resize, artisan:dusk:resize |
tapir:resize_viewport, dowel:tapir:resize_viewport |
dusk:device, artisan:dusk:device |
tapir:device_profile, dowel:tapir:device_profile |
dusk:dblclick, dusk_dblclick, ext.dusk.dblclick |
tapir:double_click, tapir_double_click, ext.tapir.double_click |
ext.dusk.find_by_label |
Removed. tapir:find and tapir_find cover it. |
Every other dusk:<verb> is tapir:<verb>, every other dusk_<verb> is tapir_<verb> and every other ext.dusk.<verb> is ext.tapir.<verb>. No alias is kept for any of them, so rename them in scripts, allow lists, tapir:run steps and CI configuration. See Commands and the MCP tool reference for the full lists.
Magic Apps#
magic_devtools has to be a release built on tapir, one that ships package:magic_devtools/tapir.dart. A release that ships MagicDuskIntegration has no such library.
- Remove
import 'package:magic_devtools/dusk.dart';and theMagicDuskIntegration.install()call fromlib/main.dart. The old installer added them, andtapir:installdoes not remove them. - Upgrade
magic_devtoolsto a release built on tapir and runflutter pub get. - Run
dart run tapir tapir:install. It addsimport 'package:magic_devtools/tapir.dart';and, inside a!kReleaseModeblock afterMagic.init(,MagicTapirIntegration.install(). It adds nothing when a call to it is already inlib/main.dart, so check there is one and not two.
The installer reads .dart_tool/package_config.json to see which magic_devtools resolved. When it has no tapir.dart it skips the Magic lines and prints:
⚠ Skipped MagicTapirIntegration: the installed magic_devtools has no tapir integration yet (no lib/tapir.dart). Upgrade magic_devtools to a release built on tapir, then rerun `dart run tapir tapir:install` (it is idempotent).
When pub get has not run, the parenthesis reads (no lib/tapir.dart resolved; run flutter pub get if it has not run). The doctor says the same in its Magic row: Magic detected, but the installed magic_devtools has no tapir integration yet; upgrade it to a release built on tapir, then rerun tapir:install, or Magic detected, but no magic_devtools resolved; add a release built on tapir to dev_dependencies, run flutter pub get, then rerun tapir:install.
Gotchas#
| Mistake | Fix |
|---|---|
Running tapir:install before deleting bin/dispatcher.dart. |
It patches lib/main.dart, prints the leftovers and skips dowel install. Delete the leftovers it names, then run the three commands it prints. |
Adding the dowel entry with mcp:install while the fluttersdk entry is still in .mcp.json. |
Both servers then run side by side. Delete the old entry first. |
Keeping mcp__fluttersdk__dusk_* in an allow list. |
The server key is dowel and the tools are tapir_*: use mcp__dowel__tapir_*. |
Reading none found in the doctor as proof the move is complete. |
The row looks for files only. It does not see Dusk* identifiers or a MagicDuskIntegration call left in your Dart code; the analyzer does. |
| An SDK older than the floor. | The package needs the Dart and Flutter versions in Installation; an app on an older SDK stays on the last fluttersdk_dusk release. |
Related Documentation#
- Installation: requirements and the wiring
tapir:installwrites. - tapir:install: the two phases and the leftover notice.
- tapir:doctor: every row of the health check.
- Magic integration: what
MagicTapirIntegrationadds to a snapshot. - MCP setup: the
.mcp.jsonentry and the reconnect after it changes. - dowel migration: the substrate half of the move.