Troubleshooting
An error message from fsp is explained, with its fix, in the fespalier-troubleshooting agent skill (skills/README.md). The table below covers the problems that are not a message of fsp itself.
Symptoms
| Symptom | Cause | Fix |
|---|---|---|
fsp check passes, but lib/app.g.dart is stale | fsp check checks the routes but never compares the committed file with what the tree generates | Regenerate and fail on a difference: Failing CI on a stale file |
test/widget_test.dart refers to MyApp and flutter analyze fails | flutter create wrote it for the MyApp that fsp init replaced | Delete it or rewrite it: fsp init |
| Routes do not animate on go_router 18 | go_router 18 checks for MaterialApp from package:material_ui | Add a root transition.dart: go_router 18 and Material |
| "URIs can't use string interpolation" | an unescaped $id in an import | Escape it: Importing from a $segment folder |
| "A Timer is still pending" at the end of a test | fakes that use Future.delayed under go_router's whole matched stack | pumpRouter and currentLocation |
a FlutterError that says a deferred page's code is not loaded | a widget test's pump never runs loadLibrary() | Deferred routes in tests |
the app is blank on the web with otel_zone | OtelZone.runGuarded did not run its body on the web before v0.8.1 | Bump otel_zone to v0.8.1 or later. On an older one, run the body as it is on the web: OpenTelemetry with otel_zone |
a test fails with MissingPluginException on connectivity_status | the test reaches the plugin without FakeConnectivity | Reconnects |
| every screen has two Sentry transactions | Sentry's own navigator observer runs next to fespalier's | One transaction per screen |
Deep links (or pushRoute) stop opening pages after the app replaced its router (telemetry: true) | The replaced router was not disposed: telemetry keeps its provider a platform observer, ahead of the new one | Dispose it after the swap, or keep one router: Turning it on |
fsp upgrade says this fsp is the one dart run fespalier keeps | the binary is the launcher's cached copy for the version your pubspec.yaml pins | Change the ref: of fespalier and every companion, then flutter pub get: Upgrading fsp |
fsp upgrade says it needs curl, or could not look up the latest release | the lookup uses the system curl and the network (and HTTPS_PROXY) | Install curl, set the proxy, or run the install script: Upgrading fsp |
fsp upgrade says the download does not match the release's .sha256 or the pinned checksum | the archive is not what the release published, or what its tag pins: a corrupt download, a proxy that rewrites it, or a tampered release; the message names both hashes | Nothing was changed. Retry; if it repeats, do not install it, and report it at the repository: Upgrading fsp |
fsp upgrade says the tag pins no checksums, or pins another version's | the release's release_checksums.dart is empty or for a different version (a release staged before its pin commit) | Wait for the release to be pinned, or install with the script. For a release you staged yourself, FSP_UPGRADE_ALLOW_UNPINNED=1: Upgrading fsp |
fsp upgrade says the downloaded fsp says something other than fsp X.Y.Z | the archive holds a binary of another release, or one that cannot run on this machine | Nothing was changed. Install with the script, or report it: Upgrading fsp |
fsp upgrade says it cannot write to the folder fsp is installed in | the binary sits in a folder you cannot write (/usr/local/bin); fsp upgrade never uses sudo | Install into a folder you own with FSP_INSTALL_DIR, first on PATH: Upgrading fsp |
Things to know
- Page transitions and
Material. Pages render below theirlayout.dart, so a layout'sScaffoldis not their nearestMaterialduring page transitions. WrapListTile-heavy pages inMaterial(type: MaterialType.transparency, …), asproducts/page.dartdoes. - Optional nullable parameters are query parameters. In a route file, any optional nullable parameter of a primitive type (or of an enum) becomes a query parameter, including one you meant as widget configuration (
String? title). Keep such parameters on inner widgets instead of the file's exported one (How parameters are filled). - go_router builds the whole matched stack, so
/products/abcalso loads/productsunderneath the not-found view.
Known limitations
Types are followed through the files fsp reads, not resolved. Since 0.21.0 fsp follows a typedef and an import prefix declared in the app's own files: the file itself, its relative imports and the package: imports of its own package, through exports and show/hide. So ProductView and the Product it names, or m.Product and Product, are one type. Before 0.21.0 they were compared by spelling, and a typedef counted as a different type. Since 0.22.0 it reads the package: imports of the app's path: dependencies the same way, so a typedef or an enum declared in one is followed too: the packages pubspec.yaml lists with a path: under dependencies, dev_dependencies or dependency_overrides (those of pubspec_overrides.yaml when it has that key, as pub reads it), and their own path: dependencies, found from the pubspecs alone. In a pub workspace (an app that says resolution: workspace) it applies the dependency_overrides of the workspace root too, as pub does for every member: those of the root's pubspec.yaml, or of its pubspec_overrides.yaml when that has the key. pub reads resolution: and workspace: from a pubspec_overrides.yaml too, in place of the pubspec's when the file has the key (even with no value: resolution: there makes the app resolve on its own), and since 0.24.1 so does fsp: an app whose pubspec_overrides.yaml alone says resolution: workspace is a member, and a folder above it whose pubspec_overrides.yaml alone has workspace: can be its root. A workspace: with no value, or [], lists no member: that folder is no root, in its pubspec or in its file. The Dart compiler still catches real mismatches in the generated code.
Not followed, so still compared as spelled: a typedef of a hosted, git: or SDK package (one of a path: dependency too, before 0.22.0), a package of a pub workspace that the pubspec names without a path: (shared: ^1.0.0 with resolution: workspace: write shared: {path: ../shared} to have it followed, which pub accepts in a workspace), a package that only another member of the workspace overrides (pub applies a member's dependency_overrides to the whole workspace, and fsp reads those of the app and of the root, not of a sibling member: write them in the root's pubspec.yaml), a generic typedef (typedef Json<T> = Map<String, T>;), a typedef in a part file, and a name that does not start with a capital letter. A hosted package's files are in the pub cache, which only pub get fills, so following them would make the answer depend on it (why). When two types differ and one of their names could not be followed, the four messages that compare types (a parameter against data.dart, against a section's data.dart, a form's return against its input, and an extra that clashes) end by naming those names: found no declaration of `JsonMap` there, so it compares it as spelled.
Still by spelling: a form field's kind (a typedef Email = String; field is a value field, not a text field), the framework types (Ref, BuildContext, RouteScope, ...), and an enum alias that two routes' query parameters spell differently (Cat in one, Category in another), which stays a mismatch for an action's keys. fsp never reads .dart_tool/, pubspec.lock or the pub cache, so fsp check gives the same answer before and after pub get.