kasy-cli 1.37.1 → 1.38.0

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 (49) hide show
  1. package/lib/scaffold/CHANGELOG.json +9 -0
  2. package/lib/scaffold/backends/api/patch/lib/core/data/api/user_api.dart +18 -0
  3. package/lib/scaffold/backends/patch-base-hashes.json +2 -2
  4. package/lib/scaffold/backends/supabase/patch/lib/core/data/api/user_api.dart +8 -0
  5. package/package.json +1 -1
  6. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +23 -23
  7. package/templates/firebase/AGENTS.md +7 -1
  8. package/templates/firebase/DESIGN_SYSTEM.md +13 -0
  9. package/templates/firebase/lib/components/kasy_app_bar.dart +57 -16
  10. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +283 -66
  11. package/templates/firebase/lib/components/kasy_date_picker.dart +61 -46
  12. package/templates/firebase/lib/components/kasy_sidebar.dart +394 -25
  13. package/templates/firebase/lib/components/kasy_web_header.dart +11 -3
  14. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +2 -213
  15. package/templates/firebase/lib/core/bottom_menu/sidebar_focus.dart +224 -0
  16. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +12 -4
  17. package/templates/firebase/lib/core/chrome/web_header_scope.dart +20 -0
  18. package/templates/firebase/lib/core/data/api/user_api.dart +4 -0
  19. package/templates/firebase/lib/core/data/repositories/user_repository.dart +5 -0
  20. package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +525 -65
  21. package/templates/firebase/lib/core/dev_inspector/dev_inspector_info.dart +47 -0
  22. package/templates/firebase/lib/core/icons/kasy_icons.dart +16 -1
  23. package/templates/firebase/lib/core/states/user_state_notifier.dart +41 -0
  24. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -0
  25. package/templates/firebase/lib/core/web_device_preview/png_clipboard.dart +14 -0
  26. package/templates/firebase/lib/core/web_device_preview/png_clipboard_io.dart +9 -0
  27. package/templates/firebase/lib/core/web_device_preview/png_clipboard_web.dart +36 -0
  28. package/templates/firebase/lib/core/web_device_preview/png_export_result.dart +2 -0
  29. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +498 -466
  30. package/templates/firebase/lib/core/widgets/responsive_layout.dart +8 -0
  31. package/templates/firebase/lib/features/home/home_components_page.dart +16 -6
  32. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +7 -0
  33. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +23 -13
  34. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -0
  35. package/templates/firebase/lib/features/notifications/ui/widgets/web_notifications_bell.dart +262 -0
  36. package/templates/firebase/lib/features/settings/settings_page.dart +51 -38
  37. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +549 -376
  38. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +14 -4
  39. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +266 -141
  40. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +23 -30
  41. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +30 -49
  42. package/templates/firebase/lib/i18n/en.i18n.json +23 -9
  43. package/templates/firebase/lib/i18n/es.i18n.json +23 -9
  44. package/templates/firebase/lib/i18n/pt.i18n.json +23 -9
  45. package/templates/firebase/lib/router.dart +43 -25
  46. package/templates/firebase/pubspec.yaml +1 -1
  47. package/templates/firebase/test/admin_shell_chrome_test.dart +104 -0
  48. package/templates/firebase/test/features/authentication/data/api/user_api_fake.dart +3 -0
  49. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +0 -53
@@ -1,4 +1,13 @@
1
1
  {
2
+ "1.38.0": {
3
+ "modules": {
4
+ "components": {
5
+ "pt": "Sidebar agora suporta submenus expansíveis (grupos com sub-itens, como o menu suspenso de Configurações na Home): clicar no grupo abre/fecha os filhos, o item ativo do submenu fica em negrito forte (sem pílula) e o submenu fecha sozinho ao navegar para outra tela.",
6
+ "en": "Sidebar now supports expandable submenus (groups with sub-items, like the Settings dropdown on Home): tapping the group opens/closes its children, the active sub-item shows in strong bold (no pill) and the submenu closes on its own when you navigate to another screen.",
7
+ "es": "La sidebar ahora admite submenús expansibles (grupos con subítems, como el menú desplegable de Ajustes en la Home): tocar el grupo abre/cierra sus hijos, el ítem activo del submenú se muestra en negrita fuerte (sin píldora) y el submenú se cierra solo al navegar a otra pantalla."
8
+ }
9
+ }
10
+ },
2
11
  "1.37.0": {
3
12
  "modules": {
4
13
  "components": {
@@ -44,6 +44,24 @@ class UserApi {
44
44
  }
45
45
  }
46
46
 
47
+ /// Watches the user's role so an admin promotion/demotion is picked up at
48
+ /// runtime (the shared user state refreshes when it changes). A REST backend
49
+ /// has no realtime channel, so this polls: it emits the current role at once
50
+ /// (mirroring the immediate snapshot of the Firebase/Supabase streams, which
51
+ /// the state notifier skips) and then re-checks periodically. The notifier
52
+ /// cancels the subscription on logout/dispose, which ends this generator.
53
+ Stream<String?> watchRole(String id) async* {
54
+ while (true) {
55
+ try {
56
+ final user = await get(id);
57
+ yield user?.role;
58
+ } catch (_) {
59
+ // Ignore transient network errors and keep polling.
60
+ }
61
+ await Future<void>.delayed(const Duration(seconds: 30));
62
+ }
63
+ }
64
+
47
65
  Future<void> update(UserEntity user) async {
48
66
  try {
49
67
  final data = user.toJson()..removeWhere((_, v) => v == null);
@@ -2,7 +2,7 @@
2
2
  "api/android/app/src/main/AndroidManifest.xml": "0579c57068ee50d43837ef9c7bee884b6232e92975301c8305ce389282f68291",
3
3
  "api/ios/Runner/AppDelegate.swift": "9f1027a03ad1fef2dab45464fbd7e98364b6a9cb6d2a6abe770ee9c45fa19122",
4
4
  "api/lib/core/data/api/storage_api.dart": "d77fecc6923ea9544b6ec20571dcab5c8f3d38be81ec618b0cf4f1b08be7bbae",
5
- "api/lib/core/data/api/user_api.dart": "06f491ebf4548b87431e2d6983148f4082e120ec7d6a928108cba954d16d8c56",
5
+ "api/lib/core/data/api/user_api.dart": "0e7d984dceada025b7e998c217c39da9b781de430fd0f4d2267aaa6439391d5f",
6
6
  "api/lib/core/data/entities/json_converters.dart": "92a5efd595a03a862fa03e77ab3ca3d556523d0c9e0daca6e296e35d24b760a8",
7
7
  "api/lib/core/data/entities/user_entity.dart": "5c1e56e4be3ba2ed95cdcd276112a02b526f2a3fa7ee5bba08a3ec2b51153688",
8
8
  "api/lib/environments.dart": "c41f0843a4d627b717579eec5607b7ff5c16369bdd4b2a82e081058a20ffb7a5",
@@ -34,7 +34,7 @@
34
34
  "supabase/android/app/src/main/AndroidManifest.xml": "0579c57068ee50d43837ef9c7bee884b6232e92975301c8305ce389282f68291",
35
35
  "supabase/ios/Runner/AppDelegate.swift": "9f1027a03ad1fef2dab45464fbd7e98364b6a9cb6d2a6abe770ee9c45fa19122",
36
36
  "supabase/lib/core/data/api/storage_api.dart": "d77fecc6923ea9544b6ec20571dcab5c8f3d38be81ec618b0cf4f1b08be7bbae",
37
- "supabase/lib/core/data/api/user_api.dart": "06f491ebf4548b87431e2d6983148f4082e120ec7d6a928108cba954d16d8c56",
37
+ "supabase/lib/core/data/api/user_api.dart": "0e7d984dceada025b7e998c217c39da9b781de430fd0f4d2267aaa6439391d5f",
38
38
  "supabase/lib/core/data/entities/json_converters.dart": "92a5efd595a03a862fa03e77ab3ca3d556523d0c9e0daca6e296e35d24b760a8",
39
39
  "supabase/lib/core/data/entities/user_entity.dart": "5c1e56e4be3ba2ed95cdcd276112a02b526f2a3fa7ee5bba08a3ec2b51153688",
40
40
  "supabase/lib/environments.dart": "c41f0843a4d627b717579eec5607b7ff5c16369bdd4b2a82e081058a20ffb7a5",
@@ -40,6 +40,14 @@ class UserApi {
40
40
  }
41
41
  }
42
42
 
43
+ Stream<String?> watchRole(String id) {
44
+ return _client
45
+ .from('users')
46
+ .stream(primaryKey: ['id'])
47
+ .eq('id', id)
48
+ .map((rows) => rows.isEmpty ? null : rows.first['role'] as String?);
49
+ }
50
+
43
51
  Future<void> update(UserEntity user) async {
44
52
  try {
45
53
  final data = user.toJson()..removeWhere((_, v) => v == null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.37.1",
3
+ "version": "1.38.0",
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"
@@ -1,12 +1,10 @@
1
1
  stripe_success.html,1781195415353,677b2fb430839866f32a33a751aae85b4c4819123be820f3f5b2f689b8eed00e
2
2
  manifest.json,1779810782774,1b929ac7267ff738d05646106a0fac3591082b6f365051928a62518af30b3fd5
3
3
  local_notifications.js,1772398909034,0b44b177c4b8a705ad1b0e3d7532537aad7b230e9472f93c820742b7548797b8
4
- index.html,1781388256310,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
5
- flutter_bootstrap.js,1781388256056,25f49550b20f234b84194d9e067725abdc33590343251086f6c7d147325dfb41
6
4
  flutter.js,1777393608000,b42e549a02f5c1428b0ad85dbdc663d400a6d2db10cb8aefcc0fddd592fd6ceb
7
5
  firebase-messaging-sw.js,1772484605561,dba3c939f7c240e62f63d06408d5f70e45f1168d00df98726e6a7a20cbd3b0df
8
6
  favicon.png,1779810782545,c967afd3c3d4dd165ef2e5cf3f203c3982dfff6a95881859ed163d9c38d75512
9
- .last_build_id,1781388166126,fb2a0536078d7023bcc9d41eb14cc00d74532f9465fac7aa9b88dae42e4a249c
7
+ .last_build_id,1781393235997,3217b88fca927fa7fc5a6d9c24dee562336c5b14ad2c6e8b9b7b1bacff2e4a42
10
8
  splash/img/light-4x.png,1780546027751,abf0e55345e532a4645fe14b01c2948b07c78df50cc6da23648837dfb6271c74
11
9
  splash/img/light-3x.png,1780546027699,5f02c94032cb7053807ce89603b109f14ea2740eaa3145dc76c924cbe1d534a9
12
10
  splash/img/light-2x.png,1780546027647,3c073b9da154ac8be530f8beefa3579246f223acd819e856a2db000fdabcb67e
@@ -36,6 +34,15 @@ canvaskit/chromium/canvaskit.js.symbols,1777393830000,60722421e56edacbeb674cc3ef
36
34
  canvaskit/chromium/canvaskit.js,1777393830000,10ea3badcf26e29aba851699c03e09bc35fea65cdc440a035bac263d6d02d665
37
35
  assets/.env,1781370223435,777e85eb4de226724b28db8664404d0b73ae37f63fd66c955ac90c89cd491e7e
38
36
  assets/packages/mixpanel_flutter/assets/mixpanel.js,1779806094332,1915455df317dd1a9f42c6f3ea5bbe3ee357614f89d994f157a2e821f37956c3
37
+ assets/packages/lucide_icons_flutter/assets/lucide.ttf,1779809590216,3813e3eb113e8be4c36c2082397dc3397d0ea7cd3a3b43eaecd61100b0256513
38
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w600.ttf,1779809590200,095ae41c43f72ec2997fa59fd91a0c83a36b345915e705f292f414cae6240dab
39
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w500.ttf,1779809590211,59d94414e1a3dd1732ff0b3ea50389c3e8b3915fe5a9b6163ba256a825e1e05b
40
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w400.ttf,1779809590202,9342632d6521314eb2ab7e902a8a6fe6c3d62a110f682bcc123e244e12c4a695
41
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w300.ttf,1779809590205,3bd1b359793f74027dada0e359d7d9dc712afd721db5ef30b095592e2e03e865
42
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w200.ttf,1779809590198,8026f9e7a751a47ace3ec60fdb7c6843a1d8051699fb28eb0a9fb1bd53904a66
43
+ assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w100.ttf,1779809590208,1d81fb594eb42c3e8e43a36b89120a0a8a4c2dd858dec1a0c94de0e5b2208388
44
+ assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1779806141382,010d647b6cab4e1a9b4398957353f43589903ce8dc760d05d894944b664e29bd
45
+ assets/fonts/MaterialIcons-Regular.otf,1662569016000,3a631b0761788d7cf2a71e8732a5b31288d6e7aa996c671c70d53a251bbd3848
39
46
  assets/assets/images/splash_logo_light_android12.png,1779867497377,da2caee4f16230230dbb04d84913f0a9df2eae6bcb8f9d1ce83ed556cee6d094
40
47
  assets/assets/images/splash_logo_light.png,1779867497286,643a1f45d6ef9458c4c0eed9f4889cc2adbdf1db616913215be66673a05a0066
41
48
  assets/assets/images/splash_logo_dark_android12.png,1779867497464,b237d9f9035d419525a10b32889c30d44c706d62990b30bdc6bef911fe9352d6
@@ -53,23 +60,16 @@ assets/assets/icons/google_play_games.png,1772398653083,533c1d87d7be8690ab173ef4
53
60
  assets/assets/icons/google.png,1772398653083,f423e7e7be1e06008d45617d07f095f04da7fdcab9a56523f9e0633828e464e0
54
61
  assets/assets/icons/facebook.png,1772398653083,79ac67e449c1db63319d43329ca91f682b4e0bc6a0883b0dfb2a849e13ae6eb6
55
62
  assets/assets/icons/apple.png,1772398653083,2a737d8801ca81452b2978bb2e1ac72136acf1a4c338f2de2f77b65e9f6de1fa
56
- version.json,1781388383201,55f3aab1f3d71610bf791902e59e92cd77db8963f8290193f573c4172fa6fd32
57
- flutter_service_worker.js,1781388386040,baeeaf9f4b8e6f40d3b0549429ceb70ca01f120db6a7ef2f60d5809233dc2205
58
- assets/AssetManifest.bin,1781388383326,78bccb08a36307a400711a1d7e6868bd5f89a24e63439fb5b6747660323e4475
59
- assets/AssetManifest.bin.json,1781388383326,50a11b51c3fc4cbed1b5ec3a4f466c6a6b75c481d08d4782c8191f99bdbdba9c
60
- assets/FontManifest.json,1781388383326,4d84ab517c27984d36f9a3c8be6f2a72788c0c3985c1d5874297fef0a53407ca
61
- assets/shaders/ink_sparkle.frag,1781388383423,1c8e222328206d1e06754f76fb53947aad38d62180aafad5298a3c6f510b173d
62
- assets/shaders/stretch_effect.frag,1781388383423,1a7d4ac2be40cf0a459dfb390ef08bcd740f37913ffdee8de3c2ea836a18410e
63
- assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1781388385468,12bc6bf55aad4657f62747b6c1c9b5c120a594ed3540db21729b6db3c847340d
64
- assets/fonts/MaterialIcons-Regular.otf,1781388385471,89da9939a222284d9e14e9757b4dbdb75965434cb84b0f2969495153f3a018c9
65
- main.dart.js_3.part.js,1781388360492,6f52d11c8e140736cd046ce6b541ad8d8bf20e6ef709f39d8ef4eb3dddbece8b
66
- main.dart.js_1.part.js,1781388360481,42136799a40b5acb14b5acf0a8cef39bbef13b4dc99ce808a7bc8a1f8cdd8b09
67
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w300.ttf,1781388385457,aebf5e4f6b1e6de1e61a866f813aa5b08efb4cdd108cc2831d40aad71398c88f
68
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w400.ttf,1781388385463,edcd01ee13f6ec2285fd18863c0ce0efb0876b290fb427ad5c052f753b3210c8
69
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w600.ttf,1781388385468,008f1e974c9ff4e8d40d16221226e0d34d2298a7feb2bdea8c134a9333929c9c
70
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w100.ttf,1781388385446,c16d95412fe2600bda1f53c2b8dc4bd82496f60f7c5a6ecfa180b0f866128712
71
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w500.ttf,1781388385464,3f68f958d0fa13077968a6767339c38ede8705bcadd0382078db6ec4bac96fa0
72
- assets/packages/lucide_icons_flutter/assets/lucide.ttf,1781388385448,9b000e47cddf9ada4a6b85fc9577fef7ed2ee261c9279e9a44e4753da28f83d3
73
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w200.ttf,1781388385451,1f798186cfc3e2fcd54b15aa7b8695bc7c29b781c5058029a8d80ecdfa615904
74
- assets/NOTICES,1781388383326,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
75
- main.dart.js,1781388361400,b625f1b346dc2f1475ee99ebb1d698654e74183d737c91e1bbac020869096a84
63
+ version.json,1781400658272,495754f8591b89616bdad18717ffd8aa9275a8605889167170e6bc98ced40489
64
+ flutter_service_worker.js,1781400658740,baeeaf9f4b8e6f40d3b0549429ceb70ca01f120db6a7ef2f60d5809233dc2205
65
+ assets/FontManifest.json,1781400658381,4d84ab517c27984d36f9a3c8be6f2a72788c0c3985c1d5874297fef0a53407ca
66
+ index.html,1781400619641,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
67
+ assets/AssetManifest.bin,1781400658381,78bccb08a36307a400711a1d7e6868bd5f89a24e63439fb5b6747660323e4475
68
+ assets/AssetManifest.bin.json,1781400658381,50a11b51c3fc4cbed1b5ec3a4f466c6a6b75c481d08d4782c8191f99bdbdba9c
69
+ assets/shaders/stretch_effect.frag,1781400658449,1a7d4ac2be40cf0a459dfb390ef08bcd740f37913ffdee8de3c2ea836a18410e
70
+ assets/shaders/ink_sparkle.frag,1781400658449,1c8e222328206d1e06754f76fb53947aad38d62180aafad5298a3c6f510b173d
71
+ flutter_bootstrap.js,1781400619636,371abfca6d227535d1d7bf43ada40ba5256e04ae0aed40e29ccae33f964fa7f7
72
+ main.dart.js_3.part.js,1781400646444,6f52d11c8e140736cd046ce6b541ad8d8bf20e6ef709f39d8ef4eb3dddbece8b
73
+ main.dart.js_1.part.js,1781400646439,42136799a40b5acb14b5acf0a8cef39bbef13b4dc99ce808a7bc8a1f8cdd8b09
74
+ assets/NOTICES,1781400658382,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
75
+ main.dart.js,1781400646862,c0154549b8162d55ff32593e679d1f3461e6f4ff744d9730cd87953b0244d8c9
@@ -67,6 +67,11 @@ Typography collapses `large`+`xlarge` into one "desktop" tier (same sizes); the
67
67
  renders ~10% large, so **desktop only** scales the whole UI to `0.95`. Tablet
68
68
  and mobile web render at natural size, like native. This is NOT the typography
69
69
  scale — don't conflate the two.
70
+ - **Screen width follows content type** (two tiers): read/form screens (Settings,
71
+ Notifications) are contained + centred at `kKasyContentMaxWidth` (600) — set
72
+ `KasyOverlayScaffold(maxContentWidth: kKasyContentMaxWidth)` or use `KasyScreen`;
73
+ feed/dashboard screens (Home) go full width + the 16 gutter. Mirror
74
+ Settings/Notifications, or Home. Full rule: **`DESIGN_SYSTEM.md`** → Internal-screen contract.
70
75
 
71
76
  To re-tune sizes, edit `KasyTypeScale` in `lib/core/theme/type_scale.dart` —
72
77
  nothing else. The live ramp (tabs per breakpoint) is under **Design System →
@@ -99,7 +104,8 @@ lib/
99
104
 
100
105
  1. Read `DESIGN_SYSTEM.md` (tokens, typography roles, components).
101
106
  2. Reuse a kit component before building one. Wrap new pages in `KasyScreen`
102
- (or mirror an existing page's scaffold).
107
+ (or mirror an existing page's scaffold). Pick the width tier — contained
108
+ (mirror Settings/Notifications) vs full-width (mirror Home); see `DESIGN_SYSTEM.md`.
103
109
  3. Use semantic typography roles (`pageTitle`, `sectionTitle`, `rowTitle`, …)
104
110
  and tokens for **every** size, colour and spacing.
105
111
  4. Strings via `context.t.*` (add keys to the i18n JSON, never inline text).
@@ -222,6 +222,19 @@ New internal screens follow the sidebar / Home ruler:
222
222
  - Haptics only on native (`if (!kIsWeb)`); "hide chrome on scroll" only on the
223
223
  phone breakpoint (`width < 768`).
224
224
 
225
+ **Content width — two tiers (width follows content type):**
226
+ - *Read / form screens* (Settings, Notifications, Reminder) → **contained + centred**
227
+ at `kKasyContentMaxWidth` (600, in `core/widgets/responsive_layout.dart`). With the
228
+ overlay pattern, set `KasyOverlayScaffold(maxContentWidth: kKasyContentMaxWidth)`
229
+ (centres the column on desktop, keeps the 16 gutter on mobile); with `KasyScreen`
230
+ it is the default. Long lines and lone controls read badly stretched, so cap them.
231
+ The Settings desktop master/detail centres the **nav + detail as one unit**.
232
+ - *Browse / feed / dashboard screens* (Home) → **full width + the 16 gutter, on
233
+ purpose** — width helps a grid/feed. Do **not** contain these.
234
+ - Every tier shares the same family (16 gutter, `KasyCard`, sidebar + web header) —
235
+ only the max width changes. To mirror: copy **Settings / Notifications** for a
236
+ contained screen, **Home** for a full-width one.
237
+
225
238
  ---
226
239
 
227
240
  ## Keeping it consistent — the guard-rail (optional)
@@ -27,6 +27,7 @@ import 'package:flutter/foundation.dart' show kIsWeb;
27
27
  import 'package:flutter/material.dart';
28
28
  import 'package:flutter/services.dart' show SystemUiOverlayStyle;
29
29
  import 'package:kasy_kit/core/chrome/chrome_visibility.dart';
30
+ import 'package:kasy_kit/core/chrome/web_header_scope.dart';
30
31
  import 'package:kasy_kit/core/theme/theme.dart';
31
32
  import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
32
33
  import 'package:kasy_kit/core/widgets/responsive_layout.dart';
@@ -56,8 +57,11 @@ enum KasyAppBarScrollTreatment { overlay, intrinsicScroll }
56
57
 
57
58
  /// Vertical inset for scroll/viewport when using [KasyAppBarScrollTreatment.overlay].
58
59
  double kasyAppBarBodyTopOverlap(BuildContext context) {
59
- // On desktop the app bar hides (the web header owns the top chrome).
60
- if (MediaQuery.sizeOf(context).width >= DeviceType.large.breakpoint) {
60
+ // On desktop the app bar hides only inside the web-header scope (the shell), so
61
+ // no overlap there. Outside it (a full-screen pushed route) the bar is visible,
62
+ // so reserve its height like on phone/tablet.
63
+ if (MediaQuery.sizeOf(context).width >= DeviceType.large.breakpoint &&
64
+ KasyWebHeaderScope.of(context)) {
61
65
  return 0;
62
66
  }
63
67
  return MediaQuery.paddingOf(context).top +
@@ -74,23 +78,47 @@ List<Widget> kasyOverlayPaddedSlivers(
74
78
  BuildContext context, {
75
79
  required List<Widget> slivers,
76
80
  EdgeInsetsGeometry? contentPadding,
81
+ double? maxContentWidth,
77
82
  }) {
78
- final EdgeInsetsGeometry pad =
79
- contentPadding ??
80
- EdgeInsets.fromLTRB(
81
- KasySpacing.pageHorizontalGutter,
82
- KasySpacing.belowChromeContentGap,
83
- KasySpacing.pageHorizontalGutter,
84
- MediaQuery.paddingOf(context).bottom + KasySpacing.pageVerticalGutter,
85
- );
83
+ const double topPad = KasySpacing.belowChromeContentGap;
84
+ final double bottomPad =
85
+ MediaQuery.paddingOf(context).bottom + KasySpacing.pageVerticalGutter;
86
+ const double gutter = KasySpacing.pageHorizontalGutter;
87
+
88
+ final Widget body = SliverMainAxisGroup(slivers: slivers);
89
+
90
+ // An explicit [contentPadding] takes full control; otherwise use the page
91
+ // gutters, optionally centering content within [maxContentWidth] on wide
92
+ // viewports (so lists never stretch edge-to-edge on desktop). Centering is
93
+ // computed from the real content width (SliverLayoutBuilder) so it stays
94
+ // correct inside the desktop shell where the sidebar already took space.
95
+ final Widget paddedGroup;
96
+ if (contentPadding != null) {
97
+ paddedGroup = SliverPadding(padding: contentPadding, sliver: body);
98
+ } else if (maxContentWidth == null) {
99
+ paddedGroup = SliverPadding(
100
+ padding: EdgeInsets.fromLTRB(gutter, topPad, gutter, bottomPad),
101
+ sliver: body,
102
+ );
103
+ } else {
104
+ paddedGroup = SliverLayoutBuilder(
105
+ builder: (context, constraints) {
106
+ final double available = constraints.crossAxisExtent;
107
+ final double horizontal = available - 2 * gutter > maxContentWidth
108
+ ? (available - maxContentWidth) / 2
109
+ : gutter;
110
+ return SliverPadding(
111
+ padding: EdgeInsets.fromLTRB(horizontal, topPad, horizontal, bottomPad),
112
+ sliver: body,
113
+ );
114
+ },
115
+ );
116
+ }
86
117
  return <Widget>[
87
118
  SliverToBoxAdapter(
88
119
  child: SizedBox(height: kasyAppBarBodyTopOverlap(context)),
89
120
  ),
90
- SliverPadding(
91
- padding: pad,
92
- sliver: SliverMainAxisGroup(slivers: slivers),
93
- ),
121
+ paddedGroup,
94
122
  ];
95
123
  }
96
124
 
@@ -285,8 +313,12 @@ class KasyAppBar extends StatelessWidget {
285
313
 
286
314
  @override
287
315
  Widget build(BuildContext context) {
288
- // On desktop the web header owns the top chrome, so the page app bar hides.
289
- if (MediaQuery.sizeOf(context).width >= DeviceType.large.breakpoint) {
316
+ // On desktop the web header owns the top chrome, so the page app bar hides
317
+ // but ONLY when there actually is a web header above (i.e. inside the shell's
318
+ // KasyWebHeaderScope). A full-screen route pushed over the shell has no web
319
+ // header, so the bar stays visible and its back button is never lost.
320
+ if (MediaQuery.sizeOf(context).width >= DeviceType.large.breakpoint &&
321
+ KasyWebHeaderScope.of(context)) {
290
322
  return const SizedBox.shrink();
291
323
  }
292
324
  final Color orbFg = context.colors.onSurface;
@@ -461,6 +493,13 @@ class KasyOverlayScaffold extends StatelessWidget {
461
493
  final Widget? trailing;
462
494
  final List<Widget> slivers;
463
495
  final EdgeInsetsGeometry? contentPadding;
496
+
497
+ /// When set, content is centered within this max width on wide viewports
498
+ /// (desktop) instead of stretching edge-to-edge. Use [kKasyContentMaxWidth]
499
+ /// for the standard single-column internal page. Ignored when
500
+ /// [contentPadding] is provided (that takes full control of the insets).
501
+ final double? maxContentWidth;
502
+
464
503
  final ScrollController? scrollController;
465
504
  final ScrollPhysics? physics;
466
505
  final Color? backgroundColor;
@@ -480,6 +519,7 @@ class KasyOverlayScaffold extends StatelessWidget {
480
519
  this.trailing,
481
520
  required this.slivers,
482
521
  this.contentPadding,
522
+ this.maxContentWidth,
483
523
  this.scrollController,
484
524
  this.physics,
485
525
  this.backgroundColor,
@@ -504,6 +544,7 @@ class KasyOverlayScaffold extends StatelessWidget {
504
544
  slivers: kasyOverlayPaddedSlivers(
505
545
  context,
506
546
  contentPadding: contentPadding,
547
+ maxContentWidth: maxContentWidth,
507
548
  slivers: slivers,
508
549
  ),
509
550
  );