Skip to content

fix(#389): guard startup so a failure names the step instead of a blank page - #405

Open
Matobi98 wants to merge 1 commit into
MostroP2P:mainfrom
Matobi98:fix/389-startup-guard
Open

fix(#389): guard startup so a failure names the step instead of a blank page#405
Matobi98 wants to merge 1 commit into
MostroP2P:mainfrom
Matobi98:fix/389-startup-guard

Conversation

@Matobi98

@Matobi98 Matobi98 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #389. Carries the unticked half of #227: #370 fixed the specific cause (an unparseable locale), this makes startup survivable regardless of the cause.

The problem

main.dart is Future<void> main() => bootstrapAndRun(); with no guard, and the stretch of bootstrapAndRun before runApp runs seven steps. Anything that throws there means runApp never runs: Flutter paints nothing, and the page is not broken — it is absent, with no message anywhere. #227 was exactly this, and finding a one-line cause took a full stack-trace hunt.

Everything after runApp already degrades — 12 catch blocks. This stretch was the outlier.

The fix

Each of the seven steps was classified by what the app can still do without it.

Step Without it Result
Firebase.initializeApp no push notifications continues
RustLib.init no protocol, no keys, no relays, no chat failure surface
SharedPreferences no language, no walkthrough state, no NWC wallet failure surface
setLoggingEnabled default log verbosity continues
onBondSlashed no in-app notice for a slash continues
nostr_api.initialize opens offline continues
relay status log one diagnostic line missing continues

Five of seven are optional and now log and continue through one _optional helper, so a run's degradations share a [startup] prefix and read in order — which matters when one failure causes the next.

The two that are not optional reach a last-resort catch that calls runApp with StartupFailureApp. That is the change in one sentence: runApp now always runs — with the app, or with a screen saying which step failed.

SharedPreferences is a deliberate call. It could degrade to defaults, but then the app opens looking freshly installed: walkthrough again, wrong language, wallet gone. That lies about data loss in an app holding money. A screen saying "it failed while reading your settings" is better than a convincing impostor.

Firebase keeps two arms rather than using the helper: UnsupportedError is the placeholder config, an expected state, not a failure. Collapsing both into one message would make every run log a "failed" nobody reads by the time it means something. The broad catch below it is what satisfies the second acceptance criterion — a call into a third-party JS SDK can throw FirebaseException, a network error, or anything the SDK likes, and all of those escaped before.

The failure surface

lib/core/startup_failure.dart. One screen: "Mostro could not start" and "It failed while <step>."

No localization, no app theme, no Rust, no SharedPreferences. Any of those can be what failed, and a rescue surface that needs what broke is a second blank page. Colors are hard-coded for the same reason.

No retry button: RustLib.init throws when called twice, so retrying after a failure past that point would fail differently and confuse the report. Worth adding later as a real reload.

The step name is the whole point. "Mostro won't open" is unactionable; "it failed loading the engine" is where to look — for the person reporting it and for whoever reads the report. Naming the step is also what the issue asks for: "a minimal error scaffold that names the failing step beats a blank page".

Platform-independent, as the issue asks: no kIsWeb anywhere in the change. Web is where it bites hardest — the in-app log viewer lives inside the app that did not start — but the guard is not web-specific.

Test plan

  • flutter analyze — clean

  • flutter test — 332 passed, including 3 new

  • cargo test / clippy / cargo check --target wasm32-unknown-unknown — clean (pre-commit hook)

  • Manual, against the local regtest stack in Chrome, breaking each step in turn:

    Broken Observed
    nothing order book, unchanged
    RustLib.init "It failed while loading the engine."
    SharedPreferences "It failed while reading your settings."
    all five optional at once app opens, five [startup] lines in order

    The two failure screens saying different things is the assertion that matters — a screen that named the same step regardless would pass a careless look and be worthless.

The three new tests cover the failure screen: that it renders the step it was given, that a different step renders differently (so it cannot be ignoring the value), and that it pumps with no ProviderScope, no localization and no theme. Mutation-checked: replacing the step line with a generic message fails two of the three.

bootstrapAndRun itself is not unit-tested — reaching it needs Rust, preferences and relays, i.e. the whole app assembled to watch it not assemble. That seam is covered by the manual runs above and nothing pretends otherwise.

One thing noticed while testing

Probably already known, flagging it only to confirm: with relay init broken, the app opens but the order book spins forever rather than saying it is offline. The startup guard did its job — the app opened, and Settings is reachable to switch node or edit relays — but that surface has no "disconnected" state of its own. Out of scope here; happy to open an issue if there isn't one.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 871aa22f-31f1-4071-af5a-8b8e7f0ee9ac


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@grunch

grunch commented Sep 9, 2026

Copy link
Copy Markdown
Member

This branch conflicts with main since #408 (d49f3c1) merged; both edit lib/core/app_bootstrap.dart.

What to do on rebase:

  • feat: Linux accessibility contract and Web persistence for Mortsom #408 replaced the if (!kIsWeb) { … initDb(path: p.join(dataDir, 'mostro.db')) … } block with an unconditional initDb call whose argument comes from databaseLocation(isWeb: kIsWeb, dataDir: …) in lib/core/storage/db_location.dart: on the web the store is now opened under a fixed IndexedDB name, off the web under the data directory as before. It also dropped the then-unused package:path/path.dart import. Keep that shape when you fold the store initialisation into your guarded startup steps: it should be one named step like the others, still non-fatal (the app can browse without persistence), and it must run on the web too, because every other web feature that persists anything depends on it now.
  • test/core/storage/db_location_test.dart covers the location helper; a startup-guard test that names the failing step would be the natural companion.
  • Re-run flutter analyze && flutter test after resolving. CI pins Flutter 3.38.2, so avoid matchers newer than that (isSemantics is one; containsSemantics works on both).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web: startup has no top-level guard — any failure before runApp is a silent blank page

2 participants