kasy-cli 1.20.0 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +11 -3
  2. package/lib/commands/new.js +78 -37
  3. package/lib/commands/run.js +7 -0
  4. package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +1 -1
  5. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  6. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +1 -1
  7. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +4 -5
  8. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +1 -1
  9. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +1 -1
  10. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +1 -1
  11. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +317 -0
  12. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +40 -1
  13. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +2 -0
  14. package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
  15. package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
  16. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -2
  17. package/lib/scaffold/backends/api/pubspec.yaml.tpl +2 -0
  18. package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +11 -8
  19. package/lib/scaffold/backends/supabase/deploy.js +56 -3
  20. package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +5 -11
  21. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +2 -2
  22. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +31 -1
  23. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
  24. package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
  25. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +2 -0
  26. package/lib/scaffold/catalog.js +2 -2
  27. package/lib/scaffold/generate.js +19 -3
  28. package/lib/scaffold/shared/generator-utils.js +265 -55
  29. package/lib/scaffold/shared/post-build.js +11 -0
  30. package/lib/utils/i18n/messages-en.js +5 -1
  31. package/lib/utils/i18n/messages-es.js +5 -1
  32. package/lib/utils/i18n/messages-pt.js +5 -1
  33. package/package.json +1 -1
  34. package/templates/firebase/lib/components/kasy_sidebar_pro.dart +3 -1
  35. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +38 -128
  36. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +6 -125
  37. package/templates/firebase/lib/core/states/user_state_notifier.dart +8 -10
  38. package/templates/firebase/lib/features/home/home_components_page.dart +8 -14
  39. package/templates/firebase/lib/features/home/home_page.dart +7 -8
  40. package/templates/firebase/lib/router.dart +60 -0
  41. package/templates/firebase/test/core/bottom_menu/detail_route_menu_test.dart +57 -0
  42. package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +0 -211
  43. package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +0 -185
  44. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  45. package/lib/scaffold/backends/api/patch/lib/main.dart +0 -275
  46. package/lib/scaffold/backends/api/patch/lib/router.dart +0 -133
  47. package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +0 -211
  48. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
  49. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +0 -174
  50. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  51. package/lib/scaffold/backends/supabase/patch/lib/main.dart +0 -307
  52. package/lib/scaffold/backends/supabase/patch/lib/router.dart +0 -133
@@ -524,7 +524,7 @@ module.exports = {
524
524
  'new.firebase.module.sentry': '🚨 Crash Reports (Sentry)',
525
525
  'new.firebase.module.analytics': '📊 Analytics (Mixpanel)',
526
526
  'new.firebase.module.facebook': '👤 Facebook (Login + Ads)',
527
- 'new.firebase.module.web': '🌐 Web Support (PWA, somente Firebase)',
527
+ 'new.firebase.module.web': '🌐 Web Support (PWA)',
528
528
  'new.firebase.module.widget': '📱 Home Widget (iOS/Android)',
529
529
  'new.firebase.module.llm_chat': '🤖 AI Chat (OpenAI/Gemini)',
530
530
  'new.firebase.module.local_notifications': '🔔 Local Reminders (sem servidor)',
@@ -692,6 +692,10 @@ module.exports = {
692
692
  'new.google.refreshConfigs': 'Atualizando google-services.json e GoogleService-Info.plist com Client IDs do Google…',
693
693
  'new.google.manualHint': 'Login com Google: ative manualmente no Console (provedor Google):',
694
694
  'new.google.manualHint.noEmail': 'Login com Google: não consegui detectar um e-mail de suporte (gcloud sem conta). Ative manualmente no Console:',
695
+ 'new.google.supabaseManual': 'Login com Google: client criado, mas o secret ainda não estava disponível. Ative depois no painel do Supabase (Authentication > Providers > Google).',
696
+ 'new.fcm.ok': 'gerada automaticamente',
697
+ 'new.fcm.failSupabase': 'não gerada (permissão do GCP ainda propagando); defina FIREBASE_SERVICE_ACCOUNT_JSON nos secrets do Supabase',
698
+ 'new.fcm.failApi': 'não gerada (permissão do GCP ainda propagando); rode o comando de novo em alguns minutos',
695
699
  'new.sha1.registering': 'Registrando SHA-1 para Google Sign-In (Android)…',
696
700
  'new.sha1.failed': 'SHA-1 não adicionado automaticamente: {error}',
697
701
  'new.sha1.manual': 'Adicione manualmente para o Google Sign-In funcionar no Android:',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.20.0",
3
+ "version": "1.20.1",
4
4
  "description": "CLI for scaffolding production-ready Flutter SaaS apps with Firebase, Supabase, or API REST backends.",
5
5
  "bin": {
6
6
  "kasy": "./bin/kasy.js"
@@ -309,7 +309,9 @@ class _KasySidebarProState extends State<KasySidebarPro> {
309
309
  // Use a Column that fills the sidebar height.
310
310
  // Top section scrolls if content overflows; bottom items (Help + Logout)
311
311
  // stay pinned to the bottom via Spacer in the outer Column.
312
- return Positioned.fill(
312
+ // SizedBox.expand (not Positioned.fill) because the parent here is a
313
+ // DecoratedBox, not a Stack — Positioned only works as a direct Stack child.
314
+ return SizedBox.expand(
313
315
  child: Column(
314
316
  crossAxisAlignment: CrossAxisAlignment.start,
315
317
  children: [
@@ -1,5 +1,4 @@
1
1
  import 'package:bart/bart.dart' as bart;
2
- import 'package:bart/bart/bart_bottombar_actions.dart';
3
2
  import 'package:flutter/foundation.dart';
4
3
  import 'package:flutter/material.dart';
5
4
  import 'package:flutter/services.dart';
@@ -11,79 +10,18 @@ import 'package:kasy_kit/core/widgets/responsive_layout.dart';
11
10
 
12
11
  /// Bottom navigation host powered by Bart (https://pub.dev/packages/bart).
13
12
  ///
14
- /// Bart's [bart.BartScaffold] owns a [ValueNotifier<bool>] for bottom-bar
15
- /// visibility, created in its constructor. Two issues fall out of that:
16
- ///
17
- /// 1. [ResponsiveLayout] swaps between three [bart.BartScaffold]s (small /
18
- /// medium / large). Any rebuild (theme toggle, locale switch,
19
- /// device-preview resize) instantiates a fresh notifier with the initial
20
- /// value, dropping whatever visibility we had set.
21
- /// 2. Bart's [bart.BartScaffold.onRouteChanged] only fires when the active
22
- /// *tab index* changes — pushing an inner route inside the same tab
23
- /// (e.g. `home → home/components`) is silent. We can't drive visibility
24
- /// from that callback alone.
25
- ///
26
- /// We solve both by treating inner-route presence as the source of truth.
27
- /// [BottomBarVisibilityScope] exposes a counter; the [HomeInnerRoute] wrapper
28
- /// increments while mounted and decrements on dispose. This widget reacts to
29
- /// the counter (and to lifecycle resume + every rebuild) and re-asserts the
30
- /// matching [BottomBarIntent] on Bart.
31
- class BottomMenu extends StatefulWidget {
13
+ /// [ResponsiveLayout] swaps between three [bart.BartScaffold]s (small / medium /
14
+ /// large) based on width. The bottom bar shows on every tab; detail screens
15
+ /// (Features, Components, …) are pushed on the root navigator from the pages
16
+ /// themselves, so they cover this menu and there's nothing to toggle here.
17
+ class BottomMenu extends StatelessWidget {
32
18
  final String? initialRoute;
33
19
 
34
20
  const BottomMenu({super.key, this.initialRoute});
35
21
 
36
- @override
37
- State<BottomMenu> createState() => _BottomMenuState();
38
- }
39
-
40
- class _BottomMenuState extends State<BottomMenu>
41
- with WidgetsBindingObserver, BartNotifier {
42
- final ValueNotifier<int> _innerRouteDepth = ValueNotifier<int>(0);
43
-
44
- @override
45
- void initState() {
46
- super.initState();
47
- WidgetsBinding.instance.addObserver(this);
48
- _innerRouteDepth.addListener(_scheduleSync);
49
- }
50
-
51
- @override
52
- void dispose() {
53
- _innerRouteDepth.removeListener(_scheduleSync);
54
- _innerRouteDepth.dispose();
55
- WidgetsBinding.instance.removeObserver(this);
56
- super.dispose();
57
- }
58
-
59
- @override
60
- void didChangeAppLifecycleState(AppLifecycleState state) {
61
- super.didChangeAppLifecycleState(state);
62
- if (state == AppLifecycleState.resumed) {
63
- _scheduleSync();
64
- }
65
- }
66
-
67
- void _scheduleSync() {
68
- WidgetsBinding.instance.addPostFrameCallback((_) {
69
- if (!mounted) return;
70
- if (_innerRouteDepth.value > 0) {
71
- hideBottomBar(context);
72
- } else {
73
- showBottomBar(context);
74
- }
75
- });
76
- }
77
-
78
22
  @override
79
23
  Widget build(BuildContext context) {
80
- // Re-assert visibility after every rebuild: the BartScaffold underneath
81
- // may have been freshly instantiated (see class doc).
82
- _scheduleSync();
83
-
84
- final String? resolvedInitialRoute = _resolveInitialRoute(
85
- widget.initialRoute,
86
- );
24
+ final String? resolvedInitialRoute = _resolveInitialRoute(initialRoute);
87
25
  final bool showBottomBarOnStart = _shouldShowBottomBarOnStart(
88
26
  resolvedInitialRoute,
89
27
  );
@@ -91,44 +29,39 @@ class _BottomMenuState extends State<BottomMenu>
91
29
  backgroundColor: context.colors.background,
92
30
  );
93
31
 
94
- return BottomBarVisibilityScope(
95
- innerRouteDepth: _innerRouteDepth,
96
- child: AnnotatedRegion<SystemUiOverlayStyle>(
97
- value: switch (Theme.brightnessOf(context)) {
98
- Brightness.dark => SystemUiOverlayStyle.light,
99
- Brightness.light => SystemUiOverlayStyle.dark,
100
- },
101
- child: ResponsiveLayout(
102
- small: bart.BartScaffold(
103
- routesBuilder: subRoutes,
104
- bottomBar: kasyPaddedSurfaceBottomBar(),
105
- initialRoute: resolvedInitialRoute,
106
- showBottomBarOnStart: showBottomBarOnStart,
107
- scaffoldOptions: scaffoldOptions,
108
- ),
109
- // medium (768–1024 px): KasySidebarPro auto-collapses at this width
110
- medium: bart.BartScaffold(
111
- routesBuilder: subRoutes,
112
- bottomBar: kasyPaddedSurfaceBottomBar(),
113
- initialRoute: resolvedInitialRoute,
114
- showBottomBarOnStart: showBottomBarOnStart,
115
- scaffoldOptions: scaffoldOptions,
116
- sideBarOptions: bart.CustomSideBarOptions(
117
- sideBarBuilder: (routes, onTap, current) =>
118
- const KasySidebarPro(),
119
- ),
32
+ return AnnotatedRegion<SystemUiOverlayStyle>(
33
+ value: switch (Theme.brightnessOf(context)) {
34
+ Brightness.dark => SystemUiOverlayStyle.light,
35
+ Brightness.light => SystemUiOverlayStyle.dark,
36
+ },
37
+ child: ResponsiveLayout(
38
+ small: bart.BartScaffold(
39
+ routesBuilder: subRoutes,
40
+ bottomBar: kasyPaddedSurfaceBottomBar(),
41
+ initialRoute: resolvedInitialRoute,
42
+ showBottomBarOnStart: showBottomBarOnStart,
43
+ scaffoldOptions: scaffoldOptions,
44
+ ),
45
+ // medium (768–1024 px): KasySidebarPro auto-collapses at this width
46
+ medium: bart.BartScaffold(
47
+ routesBuilder: subRoutes,
48
+ bottomBar: kasyPaddedSurfaceBottomBar(),
49
+ initialRoute: resolvedInitialRoute,
50
+ showBottomBarOnStart: showBottomBarOnStart,
51
+ scaffoldOptions: scaffoldOptions,
52
+ sideBarOptions: bart.CustomSideBarOptions(
53
+ sideBarBuilder: (routes, onTap, current) => const KasySidebarPro(),
120
54
  ),
121
- // large (1024 px+): full expanded sidebar
122
- large: bart.BartScaffold(
123
- routesBuilder: subRoutes,
124
- bottomBar: kasyPaddedSurfaceBottomBar(),
125
- initialRoute: resolvedInitialRoute,
126
- showBottomBarOnStart: showBottomBarOnStart,
127
- scaffoldOptions: scaffoldOptions,
128
- sideBarOptions: bart.CustomSideBarOptions(
129
- sideBarBuilder: (routes, onTap, current) =>
130
- const KasySidebarPro(),
131
- ),
55
+ ),
56
+ // large (1024 px+): full expanded sidebar
57
+ large: bart.BartScaffold(
58
+ routesBuilder: subRoutes,
59
+ bottomBar: kasyPaddedSurfaceBottomBar(),
60
+ initialRoute: resolvedInitialRoute,
61
+ showBottomBarOnStart: showBottomBarOnStart,
62
+ scaffoldOptions: scaffoldOptions,
63
+ sideBarOptions: bart.CustomSideBarOptions(
64
+ sideBarBuilder: (routes, onTap, current) => const KasySidebarPro(),
132
65
  ),
133
66
  ),
134
67
  ),
@@ -176,26 +109,3 @@ class _BottomMenuState extends State<BottomMenu>
176
109
  return segments.length < 2;
177
110
  }
178
111
  }
179
-
180
- /// Shared counter that inner home routes increment while mounted. The host
181
- /// [BottomMenu] observes it and toggles Bart's bottom-bar accordingly.
182
- class BottomBarVisibilityScope extends InheritedWidget {
183
- final ValueNotifier<int> innerRouteDepth;
184
-
185
- const BottomBarVisibilityScope({
186
- super.key,
187
- required this.innerRouteDepth,
188
- required super.child,
189
- });
190
-
191
- static ValueNotifier<int>? maybeOf(BuildContext context) {
192
- final scope = context
193
- .getInheritedWidgetOfExactType<BottomBarVisibilityScope>();
194
- return scope?.innerRouteDepth;
195
- }
196
-
197
- @override
198
- bool updateShouldNotify(covariant BottomBarVisibilityScope oldWidget) {
199
- return innerRouteDepth != oldWidget.innerRouteDepth;
200
- }
201
- }
@@ -1,22 +1,20 @@
1
1
  import 'package:bart/bart.dart';
2
2
  import 'package:flutter/material.dart';
3
3
  import 'package:kasy_kit/components/kasy_app_bar.dart';
4
- import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
5
- import 'package:kasy_kit/core/bottom_menu/bottom_menu.dart';
6
4
  import 'package:kasy_kit/core/bottom_menu/notification_bottom_item.dart';
7
5
  import 'package:kasy_kit/core/navigation/kasy_navigation_config.dart';
8
6
  import 'package:kasy_kit/core/navigation/kasy_route_transition.dart';
9
7
  import 'package:kasy_kit/core/rating/widgets/rate_banner.dart';
10
8
  import 'package:kasy_kit/core/theme/theme.dart';
11
- import 'package:kasy_kit/features/home/design_system_page.dart';
12
- import 'package:kasy_kit/features/home/home_components_page.dart';
13
- import 'package:kasy_kit/features/home/home_components_preview_page.dart';
14
- import 'package:kasy_kit/features/home/home_features_page.dart';
15
9
  import 'package:kasy_kit/features/home/home_page.dart';
16
10
  import 'package:kasy_kit/features/notifications/ui/notifications_page.dart';
17
11
  import 'package:kasy_kit/features/settings/settings_page.dart';
18
12
  import 'package:kasy_kit/i18n/translations.g.dart';
19
13
 
14
+ /// Bart tab routes (bottom bar). Detail screens reachable from a tab — Features,
15
+ /// Components, Design System, component previews — are NOT inner routes here:
16
+ /// they're pushed on the ROOT navigator (see home_page.dart / home_components_page.dart)
17
+ /// so they open full-screen without the bottom bar and return to the tab intact.
20
18
  List<BartMenuRoute> subRoutes() {
21
19
  return [
22
20
  BartMenuRoute.bottomBar(
@@ -51,101 +49,9 @@ List<BartMenuRoute> subRoutes() {
51
49
  transitionDuration: bottomBarTransitionDuration,
52
50
  transitionsBuilder: bottomBarTransition,
53
51
  ),
54
- BartMenuRoute.innerRoute(
55
- path: bartInnerPath('home', 'features'),
56
- showBottomBar: false,
57
- pageBuilder: (_, _, _) =>
58
- const _HomeInnerRoute(child: HomeFeaturesPage()),
59
- transitionDuration: bottomBarTransitionDuration,
60
- transitionsBuilder: bottomBarTransition,
61
- ),
62
- BartMenuRoute.innerRoute(
63
- path: bartInnerPath('home', 'components'),
64
- showBottomBar: false,
65
- pageBuilder: (_, _, _) =>
66
- const _HomeInnerRoute(child: HomeComponentsPage()),
67
- transitionDuration: bottomBarTransitionDuration,
68
- transitionsBuilder: bottomBarTransition,
69
- ),
70
- BartMenuRoute.innerRoute(
71
- path: bartInnerPath('home', 'design-system'),
72
- showBottomBar: false,
73
- pageBuilder: (_, _, _) =>
74
- const _HomeInnerRoute(child: DesignSystemPage()),
75
- transitionDuration: bottomBarTransitionDuration,
76
- transitionsBuilder: bottomBarTransition,
77
- ),
78
- BartMenuRoute.innerRoute(
79
- path: bartInnerPath('home', 'components-preview'),
80
- showBottomBar: false,
81
- pageBuilder: (_, _, settings) {
82
- final extra = settings?.arguments;
83
- if (extra is! HomeComponentPreviewRouteExtra) {
84
- return const PageFake();
85
- }
86
- return _HomeInnerRoute(
87
- child: HomeComponentsPreviewPage(
88
- title: extra.title,
89
- variants: extra.variants,
90
- ),
91
- );
92
- },
93
- transitionDuration: bottomBarTransitionDuration,
94
- transitionsBuilder: bottomBarTransition,
95
- ),
96
- BartMenuRoute.innerRoute(
97
- path: bartInnerPath('home', 'search'),
98
- showBottomBar: false,
99
- pageBuilder: (_, _, _) => const _HomeInnerRoute(child: PageFake()),
100
- transitionDuration: bottomBarTransitionDuration,
101
- transitionsBuilder: bottomBarTransition,
102
- ),
103
- BartMenuRoute.innerRoute(
104
- path: bartInnerPath('home', 'search/subscribe'),
105
- showBottomBar: false,
106
- pageBuilder: (_, _, _) => const _HomeInnerRoute(child: PageFake()),
107
- transitionDuration: bottomBarTransitionDuration,
108
- transitionsBuilder: bottomBarTransition,
109
- ),
110
52
  ];
111
53
  }
112
54
 
113
- /// Wrapper for inner home routes. While mounted, it bumps a shared counter
114
- /// exposed by [BottomBarVisibilityScope] so [BottomMenu] knows to hide the
115
- /// bottom bar — covering cases Bart's tab-only `onRouteChanged` misses (e.g.
116
- /// pushing inside the same tab) and surviving scaffold rebuilds.
117
- class _HomeInnerRoute extends StatefulWidget {
118
- final Widget child;
119
-
120
- const _HomeInnerRoute({required this.child});
121
-
122
- @override
123
- State<_HomeInnerRoute> createState() => _HomeInnerRouteState();
124
- }
125
-
126
- class _HomeInnerRouteState extends State<_HomeInnerRoute> {
127
- ValueNotifier<int>? _depth;
128
-
129
- @override
130
- void didChangeDependencies() {
131
- super.didChangeDependencies();
132
- final next = BottomBarVisibilityScope.maybeOf(context);
133
- if (identical(_depth, next)) return;
134
- _depth?.value -= 1;
135
- _depth = next;
136
- _depth?.value += 1;
137
- }
138
-
139
- @override
140
- void dispose() {
141
- _depth?.value -= 1;
142
- super.dispose();
143
- }
144
-
145
- @override
146
- Widget build(BuildContext context) => widget.child;
147
- }
148
-
149
55
  /// Placeholder page for the wishlist tab.
150
56
  /// Replace this with your own implementation.
151
57
  class WishlistPage extends StatelessWidget {
@@ -159,8 +65,8 @@ class WishlistPage extends StatelessWidget {
159
65
  crossAxisAlignment: CrossAxisAlignment.stretch,
160
66
  children: [
161
67
  KasyAppBar(
162
- title: context.t.navigation.wishlist,
163
- style: KasyAppBarStyle.rootTab,
68
+ title: context.t.navigation.wishlist,
69
+ style: KasyAppBarStyle.rootTab,
164
70
  ),
165
71
  Expanded(
166
72
  child: Padding(
@@ -224,31 +130,6 @@ class WishlistPage extends StatelessWidget {
224
130
  }
225
131
  }
226
132
 
227
- /// This is a fake page to show how to use Bart
228
- /// You can replace it with your own pages
229
- class PageFake extends StatelessWidget {
230
- final Color? color;
231
-
232
- const PageFake({this.color, super.key});
233
-
234
- @override
235
- Widget build(BuildContext context) {
236
- return SafeArea(
237
- // ignore: use_colored_box
238
- child: Container(
239
- color: color ?? context.colors.background,
240
- child: Column(
241
- children: [
242
- const RateBanner(forceInDebug: true),
243
- const SizedBox(height: 100),
244
- Opacity(opacity: 0.5, child: Text(context.t.bottom_router.fake_page_text)),
245
- ],
246
- ),
247
- ),
248
- );
249
- }
250
- }
251
-
252
133
  Widget bottomBarTransition(
253
134
  BuildContext context,
254
135
  Animation<double> animation,
@@ -267,19 +267,17 @@ class UserStateNotifier extends _$UserStateNotifier implements OnStartService {
267
267
  }
268
268
  if (user == null) {
269
269
  // User document not found after login. Possible causes:
270
- // - Firebase: onUserRegistration Cloud Function not deployed or failed
271
- // - Supabase: handle_new_user trigger not run yet (race) or failed
272
- // - User was deleted but session was still cached
273
- // Logout to clear invalid session, then create new anonymous user if applicable.
270
+ // - Cloud Functions not deployed yet (run `kasy deploy` to fix)
271
+ // - onUserRegistration Cloud Function failed
272
+ // - Supabase handle_new_user trigger not run yet (race) or failed
273
+ // Fall back to keeping the user authenticated with their auth ID so
274
+ // the app works locally before `kasy deploy` is run.
274
275
  _logger.e(
275
276
  '⚠️ User profile not found in database after sign-in.\n'
276
- ' Logging out and creating new anonymous user.',
277
+ ' If this is a new project, run: kasy deploy\n'
278
+ ' to deploy the backend and create the user document.',
277
279
  );
278
- await _authenticationRepository.logout();
279
- state = state.copyWith(user: const User.anonymous());
280
- if (mode == AuthenticationMode.anonymous) {
281
- await _loadAnonymousState();
282
- }
280
+ state = state.copyWith(user: User.anonymous(id: credentials.id));
283
281
  return;
284
282
  }
285
283
  // If the Firestore document is anonymous (no email) but Firebase Auth
@@ -1,11 +1,10 @@
1
1
  import 'package:flutter/material.dart';
2
+ import 'package:go_router/go_router.dart';
2
3
  import 'package:kasy_kit/components/kasy_app_bar.dart';
3
- import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
4
4
  import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
5
5
  import 'package:kasy_kit/core/theme/theme.dart';
6
6
  import 'package:kasy_kit/core/widgets/kasy_hover.dart';
7
7
  import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
8
- import 'package:kasy_kit/features/home/home_components_preview_page.dart';
9
8
  import 'package:kasy_kit/features/home/home_components_preview_registry.dart';
10
9
 
11
10
  /// UI-kit catalog, reachable from the home dashboard.
@@ -66,22 +65,17 @@ class HomeComponentsPage extends StatelessWidget {
66
65
  onTap: () {
67
66
  KasyHaptics.selection(context);
68
67
  if (row.name == 'Design System') {
69
- Navigator.of(context).pushNamed(
70
- bartInnerPath('home', 'design-system'),
71
- );
68
+ context.push('/design-system');
72
69
  return;
73
70
  }
74
- final ComponentPreviewDefinition? definition =
75
- getComponentPreviewDefinition(row.name);
76
- if (definition == null) {
71
+ // Skip rows without a preview yet (registry returns
72
+ // null); otherwise open /components/:name, which the
73
+ // router resolves back to this component's preview.
74
+ if (getComponentPreviewDefinition(row.name) == null) {
77
75
  return;
78
76
  }
79
- Navigator.of(context).pushNamed(
80
- bartInnerPath('home', 'components-preview'),
81
- arguments: HomeComponentPreviewRouteExtra(
82
- title: definition.title,
83
- variants: definition.variants,
84
- ),
77
+ context.push(
78
+ '/components/${Uri.encodeComponent(row.name)}',
85
79
  );
86
80
  },
87
81
  child: Padding(
@@ -1,7 +1,7 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+ import 'package:go_router/go_router.dart';
3
4
  import 'package:kasy_kit/components/kasy_app_bar.dart';
4
- import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
5
5
  import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
6
6
  import 'package:kasy_kit/core/states/components/maybe_ask_rating.dart';
7
7
  import 'package:kasy_kit/core/states/components/maybe_show_update_bottom_sheet.dart';
@@ -13,7 +13,7 @@ import 'package:kasy_kit/features/notifications/shared/att_permission.dart';
13
13
  import 'package:kasy_kit/features/subscription/shared/maybeshow_premium.dart';
14
14
  import 'package:kasy_kit/i18n/translations.g.dart';
15
15
 
16
- /// Home hub with dashboard cards; features live under [HomeFeaturesPage].
16
+ /// Home hub with dashboard cards linking to the Features and Components screens.
17
17
  class HomePage extends ConsumerWidget {
18
18
  const HomePage({super.key});
19
19
 
@@ -59,9 +59,10 @@ class HomePage extends ConsumerWidget {
59
59
  gradient: _featuresGradient(context, isDark),
60
60
  onOpen: () {
61
61
  KasyHaptics.medium(context);
62
- Navigator.of(context).pushNamed(
63
- bartInnerPath('home', 'features'),
64
- );
62
+ // Top-level go_router route: opens full-screen on the
63
+ // root navigator (above the BottomMenu, no bottom bar)
64
+ // and pops back to home with the menu intact.
65
+ context.push('/features');
65
66
  },
66
67
  );
67
68
  final componentsCard = _DashboardGradientCard(
@@ -74,9 +75,7 @@ class HomePage extends ConsumerWidget {
74
75
  gradient: _componentsGradient(context, isDark),
75
76
  onOpen: () {
76
77
  KasyHaptics.medium(context);
77
- Navigator.of(context).pushNamed(
78
- bartInnerPath('home', 'components'),
79
- );
78
+ context.push('/components');
80
79
  },
81
80
  );
82
81
  if (isWide) {
@@ -15,6 +15,11 @@ import 'package:kasy_kit/features/authentication/ui/recover_password_page.dart';
15
15
  import 'package:kasy_kit/features/authentication/ui/signin_page.dart';
16
16
  import 'package:kasy_kit/features/authentication/ui/signup_page.dart';
17
17
  import 'package:kasy_kit/features/feedbacks/ui/feedback_page.dart';
18
+ import 'package:kasy_kit/features/home/design_system_page.dart';
19
+ import 'package:kasy_kit/features/home/home_components_page.dart';
20
+ import 'package:kasy_kit/features/home/home_components_preview_page.dart';
21
+ import 'package:kasy_kit/features/home/home_components_preview_registry.dart';
22
+ import 'package:kasy_kit/features/home/home_features_page.dart';
18
23
  import 'package:kasy_kit/features/llm_chat/llm_chat_page.dart';
19
24
  import 'package:kasy_kit/features/local_reminder/ui/reminder_page.dart';
20
25
  import 'package:kasy_kit/features/onboarding/ui/onboarding_page.dart';
@@ -73,6 +78,61 @@ GoRouter generateRouter({
73
78
  ),
74
79
  ),
75
80
  ),
81
+ // Home showcase detail screens. These are TOP-LEVEL routes (siblings of
82
+ // '/', not children of the BottomMenu shell), so go_router renders them on
83
+ // the root navigator: full-screen, above the bottom bar, URL-addressable.
84
+ // Returning pops back to the tab with its menu intact.
85
+ GoRoute(
86
+ name: 'features',
87
+ path: '/features',
88
+ pageBuilder: (context, state) => kasyTransitionPage(
89
+ key: state.pageKey,
90
+ child: const HomeFeaturesPage(),
91
+ ),
92
+ ),
93
+ GoRoute(
94
+ name: 'design_system',
95
+ path: '/design-system',
96
+ pageBuilder: (context, state) => kasyTransitionPage(
97
+ key: state.pageKey,
98
+ child: const DesignSystemPage(),
99
+ ),
100
+ ),
101
+ GoRoute(
102
+ name: 'components',
103
+ path: '/components',
104
+ pageBuilder: (context, state) => kasyTransitionPage(
105
+ key: state.pageKey,
106
+ child: const HomeComponentsPage(),
107
+ ),
108
+ routes: [
109
+ // /components/:name — a single component's preview, looked up from the
110
+ // registry so the URL alone restores the screen on web reload.
111
+ GoRoute(
112
+ name: 'component_preview',
113
+ path: ':name',
114
+ pageBuilder: (context, state) {
115
+ final ComponentPreviewDefinition? definition =
116
+ getComponentPreviewDefinition(
117
+ state.pathParameters['name'] ?? '',
118
+ );
119
+ if (definition == null) {
120
+ return kasyTransitionPage(
121
+ key: state.pageKey,
122
+ child: const PageNotFound(),
123
+ );
124
+ }
125
+ return kasyTransitionPage(
126
+ key: state.pageKey,
127
+ child: HomeComponentsPreviewPage(
128
+ title: definition.title,
129
+ variants: definition.variants,
130
+ ),
131
+ );
132
+ },
133
+ ),
134
+ ],
135
+ ),
76
136
  GoRoute(
77
137
  name: 'onboarding',
78
138
  path: '/onboarding',
@@ -0,0 +1,57 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:flutter_test/flutter_test.dart';
3
+ import 'package:go_router/go_router.dart';
4
+
5
+ /// Regression test for the Features/Components navigation.
6
+ ///
7
+ /// These detail screens are TOP-LEVEL go_router routes (siblings of the home
8
+ /// shell, not children of it), so pushing them renders full-screen on the root
9
+ /// navigator: the bottom bar is gone while the detail is open, and it returns
10
+ /// intact when the user pops back to the tab.
11
+ void main() {
12
+ testWidgets('a top-level route opens full-screen over the bottom bar, '
13
+ 'and the bar returns on pop', (tester) async {
14
+ final router = GoRouter(
15
+ routes: [
16
+ // Home shell — owns the bottom bar (like BottomMenu).
17
+ GoRoute(
18
+ path: '/',
19
+ builder: (context, state) => Scaffold(
20
+ bottomNavigationBar: const Text('MENU'),
21
+ body: Center(
22
+ child: ElevatedButton(
23
+ onPressed: () => context.push('/features'),
24
+ child: const Text('open'),
25
+ ),
26
+ ),
27
+ ),
28
+ ),
29
+ // Detail screen — sibling of '/', so it renders on the root navigator.
30
+ GoRoute(
31
+ path: '/features',
32
+ builder: (context, state) => const Scaffold(
33
+ body: Center(child: Text('DETAIL')),
34
+ ),
35
+ ),
36
+ ],
37
+ );
38
+
39
+ await tester.pumpWidget(MaterialApp.router(routerConfig: router));
40
+
41
+ // Home tab: bottom bar visible, no detail.
42
+ expect(find.text('MENU'), findsOneWidget);
43
+ expect(find.text('DETAIL'), findsNothing);
44
+
45
+ // Open Features.
46
+ await tester.tap(find.text('open'));
47
+ await tester.pumpAndSettle();
48
+ expect(find.text('DETAIL'), findsOneWidget);
49
+ expect(find.text('MENU'), findsNothing); // covered — no bottom bar here
50
+
51
+ // Back: the bottom bar is intact.
52
+ Navigator.of(tester.element(find.text('DETAIL'))).maybePop();
53
+ await tester.pumpAndSettle();
54
+ expect(find.text('DETAIL'), findsNothing);
55
+ expect(find.text('MENU'), findsOneWidget);
56
+ });
57
+ }