@@ -31,7 +31,7 @@ class _StartupGateAppState extends State<StartupGateApp> {
3131 _boot = _bootstrap ();
3232 }
3333
34- Future <_BootResult > _bootstrap ({ bool userRetry = false } ) async {
34+ Future <_BootResult > _bootstrap () async {
3535 final notifications = DesktopNotificationService ();
3636 await notifications.init (
3737 onDidReceiveNotificationResponse: (response) {
@@ -43,13 +43,9 @@ class _StartupGateAppState extends State<StartupGateApp> {
4343 );
4444 final settings = SettingsProvider ();
4545 await settings.load ();
46-
47- // READY GATE (strict): do not enter the app until engine is healthy.
48- final out = userRetry
49- ? await EngineBundledLauncher .ensureReadyUserRetry ()
50- : await EngineBundledLauncher .ensureReady ();
51- return _BootResult (
52- settings: settings, notifications: notifications, gate: out);
46+ // Engine startup is handled by EngineProvider (single owner) to avoid
47+ // duplicate launches on app boot.
48+ return _BootResult (settings: settings, notifications: notifications);
5349 }
5450
5551 @override
@@ -69,21 +65,10 @@ class _StartupGateAppState extends State<StartupGateApp> {
6965 }
7066
7167 final data = snap.data! ;
72- if (! data.gate.success) {
73- return MaterialApp (
74- debugShowCheckedModeBanner: false ,
75- theme: theme,
76- darkTheme: dark,
77- home: _StartupError (
78- message: data.gate.message ?? 'Engine failed to start.' ,
79- onRetry: () =>
80- setState (() => _boot = _bootstrap (userRetry: true )),
81- ),
82- );
83- }
84-
8568 return SentraCoreApp (
86- settings: data.settings, notifications: data.notifications);
69+ settings: data.settings,
70+ notifications: data.notifications,
71+ );
8772 },
8873 );
8974 }
@@ -92,11 +77,9 @@ class _StartupGateAppState extends State<StartupGateApp> {
9277class _BootResult {
9378 final SettingsProvider settings;
9479 final DesktopNotificationService notifications;
95- final EngineBootstrapOutcome gate;
9680 const _BootResult ({
9781 required this .settings,
9882 required this .notifications,
99- required this .gate,
10083 });
10184}
10285
@@ -140,51 +123,10 @@ class _StartupSplash extends StatelessWidget {
140123 }
141124}
142125
143- class _StartupError extends StatelessWidget {
144- final String message;
145- final VoidCallback onRetry;
146- const _StartupError ({required this .message, required this .onRetry});
147-
148- @override
149- Widget build (BuildContext context) {
150- return Scaffold (
151- body: Center (
152- child: Padding (
153- padding: const EdgeInsets .all (24 ),
154- child: ConstrainedBox (
155- constraints: const BoxConstraints (maxWidth: 520 ),
156- child: Column (
157- mainAxisSize: MainAxisSize .min,
158- crossAxisAlignment: CrossAxisAlignment .stretch,
159- children: [
160- Text (
161- 'Engine failed to start' ,
162- style: TextStyle (
163- color: AppTheme .textPrimaryFor (context),
164- fontSize: 18 ,
165- fontWeight: FontWeight .w800,
166- ),
167- ),
168- const SizedBox (height: 8 ),
169- Text (
170- message,
171- style: TextStyle (color: AppTheme .textMutedFor (context)),
172- ),
173- const SizedBox (height: 16 ),
174- FilledButton (
175- onPressed: onRetry,
176- child: const Text ('Retry' ),
177- ),
178- ],
179- ),
180- ),
181- ),
182- ),
183- );
184- }
185- }
126+ // Startup errors are handled inside the app (EngineProvider), so we no longer
127+ // gate app entry on engine health here.
186128
187- class SentraCoreApp extends StatelessWidget {
129+ class SentraCoreApp extends StatefulWidget {
188130 const SentraCoreApp ({
189131 super .key,
190132 required this .settings,
@@ -194,19 +136,32 @@ class SentraCoreApp extends StatelessWidget {
194136 final SettingsProvider settings;
195137 final DesktopNotificationService notifications;
196138
139+ @override
140+ State <SentraCoreApp > createState () => _SentraCoreAppState ();
141+ }
142+
143+ class _SentraCoreAppState extends State <SentraCoreApp > {
144+ @override
145+ void dispose () {
146+ // Allow the app to stop the engine on close (owned process only).
147+ // Users can still kill the process externally; we auto-restart on next launch.
148+ EngineBundledLauncher .stopOwnedEngine ();
149+ super .dispose ();
150+ }
151+
197152 @override
198153 Widget build (BuildContext context) {
199154 return MultiProvider (
200155 providers: [
201- Provider <DesktopNotificationService >.value (value: notifications),
202- ChangeNotifierProvider <SettingsProvider >.value (value: settings),
156+ Provider <DesktopNotificationService >.value (value: widget. notifications),
157+ ChangeNotifierProvider <SettingsProvider >.value (value: widget. settings),
203158 ChangeNotifierProvider <HistoryProvider >(
204159 create: (_) => HistoryProvider ()..load (),
205160 ),
206161 ChangeNotifierProvider (
207162 create: (ctx) => EngineProvider (
208- settings: settings,
209- notifications: notifications,
163+ settings: widget. settings,
164+ notifications: widget. notifications,
210165 history: Provider .of <HistoryProvider >(ctx, listen: false ),
211166 )..connect (),
212167 ),
0 commit comments