kasy-cli 1.36.1 → 1.37.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.
@@ -1,4 +1,18 @@
1
1
  {
2
+ "1.37.0": {
3
+ "modules": {
4
+ "components": {
5
+ "pt": "Sidebar e abas mais responsivas: a sidebar ganhou um modo de colapso mais consistente (vira rail fino conforme a largura) e teve a busca não usada removida pra ficar mais enxuta; as abas (KasyTabs) agora se medem e se adaptam melhor em telas estreitas, sem cortar nem espremer.",
6
+ "en": "More responsive sidebar and tabs: the sidebar got a more consistent collapse mode (turns into a thin rail by width) and had its unused search removed for a leaner component; tabs (KasyTabs) now measure and adapt better on narrow screens, without clipping or squeezing.",
7
+ "es": "Sidebar y pestañas más responsivas: la sidebar tiene un modo de colapso más consistente (se vuelve un rail fino según el ancho) y se le quitó la búsqueda sin uso para un componente más limpio; las pestañas (KasyTabs) ahora se miden y adaptan mejor en pantallas estrechas, sin recortar ni apretar."
8
+ },
9
+ "web": {
10
+ "pt": "Web mais redonda: login social na web agora trata o fluxo por redirecionamento (redirect), útil quando o popup é bloqueado pelo navegador; e o Firebase Hosting foi ajustado pra publicar o build/web sem descartar arquivos ocultos necessários, deixando o deploy do site mais previsível.",
11
+ "en": "Smoother web: social login on web now handles the redirect flow, useful when the browser blocks the popup; and Firebase Hosting was tuned to publish build/web without dropping needed hidden files, making the site deploy more predictable.",
12
+ "es": "Web más pulida: el inicio de sesión social en web ahora maneja el flujo por redirección (redirect), útil cuando el navegador bloquea el popup; y Firebase Hosting se ajustó para publicar build/web sin descartar archivos ocultos necesarios, dejando el deploy del sitio más predecible."
13
+ }
14
+ }
15
+ },
2
16
  "1.36.1": {
3
17
  "modules": {
4
18
  "onboarding": {
@@ -53,7 +53,7 @@ dependencies:
53
53
  json_annotation: ^4.9.0
54
54
  local_auth: ^3.0.1
55
55
  logger: ^2.6.2
56
- lucide_icons_flutter: ^3.1.10
56
+ lucide_icons_flutter: ^3.1.14
57
57
  mixpanel_flutter: ^2.5.0
58
58
  open_filex: ^4.7.0
59
59
  package_info_plus: ^8.3.0
@@ -11,7 +11,7 @@
11
11
  "api/lib/features/ai_chat/api/ai_chat_message_entity.dart": "5aca4fdb5b1c38df04c9d377c6e6eeb6930ef85b2a32689fe6403e7cc7645373",
12
12
  "api/lib/features/ai_chat/providers/ai_chat_notifier.dart": "f16ea3f3266e63a5591ceff9bc1900845f0b96e9bbbc5463ec6ab894f878eae3",
13
13
  "api/lib/features/authentication/api/authentication_api_interface.dart": "ef9219237babd73e4b3ac148a24ebbbdf39ab98166d21f578c359074b528e5da",
14
- "api/lib/features/authentication/api/authentication_api.dart": "8b460722bb3bee7eee015f34aaa39c1c97cb264df1024b2855060dac232c620a",
14
+ "api/lib/features/authentication/api/authentication_api.dart": "4ee563f9fd81b9738b71928edc146846e6226fb3b8a6974cf1dc3f3cd8813d4d",
15
15
  "api/lib/features/authentication/repositories/authentication_repository.dart": "8e98ca0d39df3aac1dab159e6bdfe8d2bbb8e43931459984eb4ecfb676698ca2",
16
16
  "api/lib/features/feedbacks/api/entities/feature_request_entity.dart": "e72c683c7321bf1f3c19fc1c6d54c6964bd99970e87c9ff31d8918efa93f0090",
17
17
  "api/lib/features/feedbacks/api/entities/feature_vote_entity.dart": "a13ccfc493d8f5277beffc8e28f77ec322ab0c0a56358c3669353206b7c6756b",
@@ -42,7 +42,7 @@
42
42
  "supabase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart": "1c4e4223e802d7fe8e93d9e7a09fcc6be81a41792d722e8b7e697837cde4737f",
43
43
  "supabase/lib/features/ai_chat/api/ai_chat_message_entity.dart": "5aca4fdb5b1c38df04c9d377c6e6eeb6930ef85b2a32689fe6403e7cc7645373",
44
44
  "supabase/lib/features/ai_chat/providers/ai_chat_notifier.dart": "f16ea3f3266e63a5591ceff9bc1900845f0b96e9bbbc5463ec6ab894f878eae3",
45
- "supabase/lib/features/authentication/api/authentication_api.dart": "8b460722bb3bee7eee015f34aaa39c1c97cb264df1024b2855060dac232c620a",
45
+ "supabase/lib/features/authentication/api/authentication_api.dart": "4ee563f9fd81b9738b71928edc146846e6226fb3b8a6974cf1dc3f3cd8813d4d",
46
46
  "supabase/lib/features/authentication/repositories/authentication_repository.dart": "8e98ca0d39df3aac1dab159e6bdfe8d2bbb8e43931459984eb4ecfb676698ca2",
47
47
  "supabase/lib/features/feedbacks/api/entities/feature_request_entity.dart": "e72c683c7321bf1f3c19fc1c6d54c6964bd99970e87c9ff31d8918efa93f0090",
48
48
  "supabase/lib/features/feedbacks/api/entities/feature_vote_entity.dart": "a13ccfc493d8f5277beffc8e28f77ec322ab0c0a56358c3669353206b7c6756b",
@@ -67,6 +67,9 @@ class SupabaseAuthenticationApi implements AuthenticationApi {
67
67
  /// — the caller stays in its loading state until the browser leaves.
68
68
  /// See [isMobileWebBrowser].
69
69
  Future<Credentials> _googleSignInWithRedirectWeb() async {
70
+ // Flag the login so startup lands on Home, not the stale tab URL the
71
+ // redirect returns to (linking is a no-op on Supabase, so only login here).
72
+ markWebRedirectLogin();
70
73
  await fb_auth.FirebaseAuth.instance.signInWithRedirect(
71
74
  fb_auth.GoogleAuthProvider(),
72
75
  );
@@ -60,7 +60,7 @@ dependencies:
60
60
  json_annotation: ^4.9.0
61
61
  local_auth: ^3.0.1
62
62
  logger: ^2.6.2
63
- lucide_icons_flutter: ^3.1.10
63
+ lucide_icons_flutter: ^3.1.14
64
64
  mixpanel_flutter: ^2.5.0
65
65
  open_filex: ^4.7.0
66
66
  package_info_plus: ^8.3.0
@@ -975,6 +975,8 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
975
975
  lines.push(`import 'package:${pkg}/firebase_options_dev.dart' as firebase_dev;`);
976
976
  }
977
977
  lines.push(`import 'package:${pkg}/i18n/translations.g.dart';`);
978
+ lines.push(`import 'package:${pkg}/features/authentication/api/auth_web_support.dart'`);
979
+ lines.push(` if (dart.library.js_interop) 'package:${pkg}/features/authentication/api/auth_web_support_web.dart';`);
978
980
  lines.push(`import 'package:${pkg}/features/authentication/api/authentication_api.dart';`);
979
981
  lines.push(`import 'package:${pkg}/features/notifications/api/local_notifier.dart';`);
980
982
  if (withFirebase) {
@@ -1063,6 +1065,12 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
1063
1065
  lines.push('');
1064
1066
  }
1065
1067
 
1068
+ lines.push(` // Web: if we just returned from a social-login redirect, reset the address`);
1069
+ lines.push(` // bar to '/' BEFORE the router reads the initial URL, so the app boots on Home`);
1070
+ lines.push(` // instead of the stale tab URL (e.g. /settings) the redirect returned to.`);
1071
+ lines.push(` resetUrlAfterRedirectLogin();`);
1072
+ lines.push('');
1073
+
1066
1074
  if (withSentry) {
1067
1075
  lines.push(` // initialize sentry for error reporting in production only`);
1068
1076
  lines.push(` // run the app with Sentry for production environment`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.36.1",
3
+ "version": "1.37.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,9 +1,12 @@
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
4
6
  flutter.js,1777393608000,b42e549a02f5c1428b0ad85dbdc663d400a6d2db10cb8aefcc0fddd592fd6ceb
5
7
  firebase-messaging-sw.js,1772484605561,dba3c939f7c240e62f63d06408d5f70e45f1168d00df98726e6a7a20cbd3b0df
6
8
  favicon.png,1779810782545,c967afd3c3d4dd165ef2e5cf3f203c3982dfff6a95881859ed163d9c38d75512
9
+ .last_build_id,1781388166126,fb2a0536078d7023bcc9d41eb14cc00d74532f9465fac7aa9b88dae42e4a249c
7
10
  splash/img/light-4x.png,1780546027751,abf0e55345e532a4645fe14b01c2948b07c78df50cc6da23648837dfb6271c74
8
11
  splash/img/light-3x.png,1780546027699,5f02c94032cb7053807ce89603b109f14ea2740eaa3145dc76c924cbe1d534a9
9
12
  splash/img/light-2x.png,1780546027647,3c073b9da154ac8be530f8beefa3579246f223acd819e856a2db000fdabcb67e
@@ -31,16 +34,8 @@ canvaskit/canvaskit.js,1777393746000,df5aa63d90b0491908ad19d64fd18f29db1dc416548
31
34
  canvaskit/chromium/canvaskit.wasm,1777393830000,44d29356b9e2c903751a8aa583716275e165d1fb56eea46963c95c7c43f7ca34
32
35
  canvaskit/chromium/canvaskit.js.symbols,1777393830000,60722421e56edacbeb674cc3ef0f6152969067e74b51a960c80cd36e75b75a54
33
36
  canvaskit/chromium/canvaskit.js,1777393830000,10ea3badcf26e29aba851699c03e09bc35fea65cdc440a035bac263d6d02d665
37
+ assets/.env,1781370223435,777e85eb4de226724b28db8664404d0b73ae37f63fd66c955ac90c89cd491e7e
34
38
  assets/packages/mixpanel_flutter/assets/mixpanel.js,1779806094332,1915455df317dd1a9f42c6f3ea5bbe3ee357614f89d994f157a2e821f37956c3
35
- assets/packages/lucide_icons_flutter/assets/lucide.ttf,1779806149220,bfca5b519d438d7194550cbc60a4e7c10f53a36ac96d48c7e17503a45b6b7b0b
36
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w600.ttf,1779806149210,cf2218879264e413687602c192e43b86000404ff85e2bca933d9a67020299ed4
37
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w500.ttf,1779806149217,587ce31612aa88e6c019210a18625531557eda111f5f8e838a298277efc9e308
38
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w400.ttf,1779806149212,644f7ccdf1b74bdb898210c4171327a25d7476690c7ade2d3082dd473bc24fe1
39
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w300.ttf,1779806149214,5dd834d2d84e5a1524e027fa21d87c6e8aaaf6421f201be2ec61e57c0b7bce72
40
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w200.ttf,1779806149208,d929819511975bf5030c0f359e8c16d21ad14b20ac24d5f2ff238462114b1d9b
41
- assets/packages/lucide_icons_flutter/assets/build_font/LucideVariable-w100.ttf,1779806149216,1e9e6ea39a38b874c40242d02533adac9dd3d09c8119dae55458e65c6c3c16e6
42
- assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1779806141382,010d647b6cab4e1a9b4398957353f43589903ce8dc760d05d894944b664e29bd
43
- assets/fonts/MaterialIcons-Regular.otf,1662569016000,3a631b0761788d7cf2a71e8732a5b31288d6e7aa996c671c70d53a251bbd3848
44
39
  assets/assets/images/splash_logo_light_android12.png,1779867497377,da2caee4f16230230dbb04d84913f0a9df2eae6bcb8f9d1ce83ed556cee6d094
45
40
  assets/assets/images/splash_logo_light.png,1779867497286,643a1f45d6ef9458c4c0eed9f4889cc2adbdf1db616913215be66673a05a0066
46
41
  assets/assets/images/splash_logo_dark_android12.png,1779867497464,b237d9f9035d419525a10b32889c30d44c706d62990b30bdc6bef911fe9352d6
@@ -58,16 +53,23 @@ assets/assets/icons/google_play_games.png,1772398653083,533c1d87d7be8690ab173ef4
58
53
  assets/assets/icons/google.png,1772398653083,f423e7e7be1e06008d45617d07f095f04da7fdcab9a56523f9e0633828e464e0
59
54
  assets/assets/icons/facebook.png,1772398653083,79ac67e449c1db63319d43329ca91f682b4e0bc6a0883b0dfb2a849e13ae6eb6
60
55
  assets/assets/icons/apple.png,1772398653083,2a737d8801ca81452b2978bb2e1ac72136acf1a4c338f2de2f77b65e9f6de1fa
61
- version.json,1781384419657,1c93a56a819f36903677817066dbda0bae5785563a39d3a0a486246cf67107ac
62
- flutter_service_worker.js,1781384420789,baeeaf9f4b8e6f40d3b0549429ceb70ca01f120db6a7ef2f60d5809233dc2205
63
- assets/FontManifest.json,1781384419796,4d84ab517c27984d36f9a3c8be6f2a72788c0c3985c1d5874297fef0a53407ca
64
- assets/AssetManifest.bin,1781384419795,78bccb08a36307a400711a1d7e6868bd5f89a24e63439fb5b6747660323e4475
65
- assets/AssetManifest.bin.json,1781384419795,50a11b51c3fc4cbed1b5ec3a4f466c6a6b75c481d08d4782c8191f99bdbdba9c
66
- assets/shaders/stretch_effect.frag,1781384419911,1a7d4ac2be40cf0a459dfb390ef08bcd740f37913ffdee8de3c2ea836a18410e
67
- assets/shaders/ink_sparkle.frag,1781384419911,1c8e222328206d1e06754f76fb53947aad38d62180aafad5298a3c6f510b173d
68
- index.html,1781384363218,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
69
- flutter_bootstrap.js,1781384363207,c9fd7e9f06eccf37ccbe2a39e88047779c1a67c67fe18febf6971479b7d6b64c
70
- main.dart.js_1.part.js,1781384401644,7be2ce26bfb76e0142df09977ff2bbb058e5bee8c33d1ebf6c49aff2158d94d0
71
- main.dart.js_3.part.js,1781384401652,6f1d83d415ac91cc4ca02b7ccefeae40d609713c7609b636502b68501dd44a38
72
- assets/NOTICES,1781384419796,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
73
- main.dart.js,1781384402163,46f09db6091efb87263aad9c85327da8097b61c8c528564b1f7359829aef790b
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "hosting": {
3
3
  "public": "build/web",
4
- "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
4
+ "ignore": ["firebase.json", "**/node_modules/**"],
5
5
  "rewrites": [
6
6
  { "source": "**", "destination": "/index.html" }
7
7
  ],
@@ -192,6 +192,22 @@ const _NavItem _kHelpItem = _NavItem(
192
192
  /// Controls which edge receives the content-facing hairline + shadow nudge.
193
193
  enum KasySidebarSide { left, right }
194
194
 
195
+ /// How the rail picks its initial width — the standard SaaS sidebar presets.
196
+ enum KasySidebarCollapseMode {
197
+ /// Adapts to the viewport: wide on desktop, auto-collapses to a thin icon
198
+ /// rail on tablet and mobile. The toggle overrides it on any breakpoint. This
199
+ /// is the default and what a typical app navigation uses.
200
+ responsive,
201
+
202
+ /// Starts wide on every breakpoint (the "pinned" sidebar); the user collapses
203
+ /// it to the thin rail via the toggle. Stays wide on mobile too.
204
+ expanded,
205
+
206
+ /// Starts as the thin icon rail (with hover tooltips); the user expands it via
207
+ /// the toggle.
208
+ collapsed,
209
+ }
210
+
195
211
  /// A SaaS-style sidebar modelled on the HeroUI Figma kit: brand logo + panel
196
212
  /// toggle, a workspace selector, a segmented control, a navigable list with an
197
213
  /// active pill, and a pinned ⌘K search row. Collapses to an icon rail (with
@@ -203,9 +219,8 @@ class KasySidebar extends StatefulWidget {
203
219
  super.key,
204
220
  this.onSettingsTap,
205
221
  this.onLogout,
206
- this.initiallyCollapsed = false,
222
+ this.collapseMode = KasySidebarCollapseMode.responsive,
207
223
  this.isDrawer = false,
208
- this.showSearch = false,
209
224
  this.side = KasySidebarSide.left,
210
225
  this.routes,
211
226
  this.onTapItem,
@@ -250,19 +265,15 @@ class KasySidebar extends StatefulWidget {
250
265
  /// (confirm dialog + sign-out). When null, the Logout row does nothing real.
251
266
  final VoidCallback? onLogout;
252
267
 
253
- /// Whether the sidebar starts in the narrow (icon-only) mode.
254
- final bool initiallyCollapsed;
268
+ /// How the rail picks its initial width (responsive / pinned-expanded /
269
+ /// collapsed). See [KasySidebarCollapseMode].
270
+ final KasySidebarCollapseMode collapseMode;
255
271
 
256
272
  /// Present the rail as a slide-in drawer (the mobile pattern): it always opens
257
273
  /// wide and hides the collapse toggle, regardless of viewport width. Use when
258
274
  /// opening the sidebar from an app-bar menu button on a phone.
259
275
  final bool isDrawer;
260
276
 
261
- /// Whether to pin a ⌘K search row above the footer in connected mode. Opt-in
262
- /// (off by default) so the live app navigation stays lean; flip it on when the
263
- /// sidebar should double as a command/search entry point.
264
- final bool showSearch;
265
-
266
277
  /// The screen edge this sidebar is anchored to.
267
278
  final KasySidebarSide side;
268
279
 
@@ -302,14 +313,6 @@ class _KasySidebarState extends State<KasySidebar> {
302
313
  /// Viewport width below which the sidebar auto-collapses (tablet breakpoint).
303
314
  static const double _kBreakpoint = 1024.0;
304
315
 
305
- /// Below this (mobile), the rail hides its collapse toggle: on a phone the
306
- /// thin-vs-wide choice is a fixed pre-configuration (set via [isDrawer] for a
307
- /// wide drawer, or the collapsed default for a thin rail), not a live toggle.
308
- /// Width does NOT force wide here — a collapsed config stays thin on mobile.
309
- static const double _kMobileBreakpoint = 768.0;
310
-
311
- bool _isMobile(BuildContext context) =>
312
- MediaQuery.sizeOf(context).width < _kMobileBreakpoint;
313
316
 
314
317
  /// True when wired to Bart's navigation (real, tappable screens).
315
318
  bool get _connected =>
@@ -321,7 +324,11 @@ class _KasySidebarState extends State<KasySidebar> {
321
324
  @override
322
325
  void initState() {
323
326
  super.initState();
324
- _collapsePreference = widget.initiallyCollapsed ? true : null;
327
+ _collapsePreference = switch (widget.collapseMode) {
328
+ KasySidebarCollapseMode.responsive => null,
329
+ KasySidebarCollapseMode.expanded => false,
330
+ KasySidebarCollapseMode.collapsed => true,
331
+ };
325
332
  // Connected mode follows Bart's currentItem (empty highlight here); the
326
333
  // showcase defaults to the active layer from the Figma reference.
327
334
  _activeItemId = _connected ? '' : 'object2';
@@ -577,15 +584,6 @@ class _KasySidebarState extends State<KasySidebar> {
577
584
  child: Column(
578
585
  crossAxisAlignment: CrossAxisAlignment.start,
579
586
  children: [
580
- if (widget.showSearch)
581
- _buildItemRow(
582
- c,
583
- icon: KasyIcons.search,
584
- label: 'Search',
585
- isActive: false,
586
- onTap: () {},
587
- trailing: [_buildKbd(c)],
588
- ),
589
587
  _buildNavItem(context, _kHelpItem, c),
590
588
  _buildItemRow(
591
589
  c,
@@ -619,9 +617,10 @@ class _KasySidebarState extends State<KasySidebar> {
619
617
  MediaQuery.sizeOf(context).width >= _kBreakpoint
620
618
  ? _kTopBandHeight
621
619
  : kasyAppBarBodyTopOverlap(context);
622
- // No collapse toggle on a drawer (always wide) or on mobile (the thin/wide
623
- // choice is a fixed pre-configuration there, not a live toggle).
624
- final bool showToggle = !widget.isDrawer && !_isMobile(context);
620
+ // The collapse toggle is available on every breakpoint so any config can be
621
+ // switched thin↔wide except a drawer, which is a dismissible overlay you
622
+ // close whole rather than collapse in place.
623
+ final bool showToggle = !widget.isDrawer;
625
624
  final bool anchoredLeft = widget.side == KasySidebarSide.left;
626
625
  // Brand wordmark — same artwork as the splash screen.
627
626
  final Widget logo = Image.asset(
@@ -125,6 +125,14 @@ class _KasyTabsState extends State<KasyTabs> {
125
125
  // Whether we have a valid measurement yet.
126
126
  bool _measured = false;
127
127
 
128
+ // Last layout width seen. The pill/underline geometry is measured from the
129
+ // laid-out tab boxes, so it must be recomputed whenever the bar's own width
130
+ // changes — e.g. when a collapsing sidebar animates the tabs open, the first
131
+ // measure happens at the narrow start width and would otherwise stay stuck in
132
+ // the corner until the next selection. Re-measuring per resize lets the
133
+ // indicator track the animation smoothly.
134
+ double? _lastLayoutWidth;
135
+
128
136
  @override
129
137
  void initState() {
130
138
  super.initState();
@@ -241,9 +249,23 @@ class _KasyTabsState extends State<KasyTabs> {
241
249
  Widget build(BuildContext context) {
242
250
  if (widget.items.isEmpty) return const SizedBox.shrink();
243
251
 
244
- return widget.variant == KasyTabsVariant.primary
245
- ? _buildPrimary(context)
246
- : _buildSecondary(context);
252
+ return LayoutBuilder(
253
+ builder: (context, constraints) {
254
+ final double width = constraints.maxWidth;
255
+ // Re-measure on every width change (e.g. a sidebar animating open) so
256
+ // the indicator follows instead of sticking at its first-frame spot.
257
+ if (_lastLayoutWidth == null ||
258
+ (_lastLayoutWidth! - width).abs() > 0.5) {
259
+ _lastLayoutWidth = width;
260
+ WidgetsBinding.instance.addPostFrameCallback((_) {
261
+ if (mounted) _measure();
262
+ });
263
+ }
264
+ return widget.variant == KasyTabsVariant.primary
265
+ ? _buildPrimary(context)
266
+ : _buildSecondary(context);
267
+ },
268
+ );
247
269
  }
248
270
 
249
271
  // ── Primary (pill indicator) ───────────────────────────────────────────────
@@ -10,3 +10,16 @@
10
10
  /// Non-web stub: always false. The real implementation lives in
11
11
  /// `auth_web_support_web.dart` and is selected via conditional import on web.
12
12
  bool isMobileWebBrowser() => false;
13
+
14
+ /// Marks that a full-page redirect *sign-in* (not provider linking) is starting.
15
+ /// Persisted across the redirect's page reload so [resetUrlAfterRedirectLogin]
16
+ /// can tell, at startup, that we returned from a login (and must land on Home)
17
+ /// rather than from a link (which stays on the originating screen, e.g. Settings).
18
+ /// Non-web stub: no-op.
19
+ void markWebRedirectLogin() {}
20
+
21
+ /// If we just returned from a redirect sign-in (see [markWebRedirectLogin]),
22
+ /// clear the marker and reset the address bar to `/` so the app boots on Home
23
+ /// instead of whatever stale tab URL the redirect returned to. MUST run before
24
+ /// the router reads the initial URL. Non-web stub: no-op.
25
+ void resetUrlAfterRedirectLogin() {}
@@ -23,3 +23,20 @@ bool isMobileWebBrowser() {
23
23
  // iPadOS 13+ masquerades as desktop Safari but still exposes touch points.
24
24
  return ua.contains('macintosh') && web.window.navigator.maxTouchPoints > 1;
25
25
  }
26
+
27
+ /// localStorage key flagging that a redirect *sign-in* is in flight. localStorage
28
+ /// (not in-memory) because the redirect reloads the whole page.
29
+ const String _redirectLoginKey = 'kasy_redirect_login';
30
+
31
+ void markWebRedirectLogin() {
32
+ web.window.localStorage.setItem(_redirectLoginKey, '1');
33
+ }
34
+
35
+ void resetUrlAfterRedirectLogin() {
36
+ if (web.window.localStorage.getItem(_redirectLoginKey) == null) return;
37
+ web.window.localStorage.removeItem(_redirectLoginKey);
38
+ // Absolute path: land on Home, dropping the stale tab path (e.g. /settings)
39
+ // the redirect returned to. replaceState (not pushState) so no bogus history
40
+ // entry is left behind.
41
+ web.window.history.replaceState(null, '', '/');
42
+ }
@@ -209,6 +209,9 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
209
209
  /// we sign in. The linked/signed-in user is materialised at startup by
210
210
  /// [getRedirectResult].
211
211
  Future<Credentials> _signInWithRedirectWeb(AuthProvider provider) async {
212
+ // Flag the login so startup lands on Home, not the stale tab URL the
213
+ // redirect returns to (linking does NOT flag — it stays put, e.g. Settings).
214
+ markWebRedirectLogin();
212
215
  final current = _auth.currentUser;
213
216
  if (current != null && current.isAnonymous) {
214
217
  await current.linkWithRedirect(provider);
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
5
5
  import 'package:flutter_riverpod/flutter_riverpod.dart';
6
6
  import 'package:go_router/go_router.dart';
7
7
  import 'package:kasy_kit/components/components.dart';
8
+ import 'package:kasy_kit/core/bottom_menu/web_url.dart';
8
9
  import 'package:kasy_kit/core/config/features.dart';
9
10
  import 'package:kasy_kit/core/data/models/user.dart';
10
11
  import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
@@ -41,6 +42,14 @@ class SigninPage extends ConsumerWidget {
41
42
  body: SizedBox.shrink(),
42
43
  );
43
44
  }
45
+ // Web: the sign-in screen is reached via the router redirect (e.g. after a
46
+ // logout), where Bart may have left a stale tab path in the address bar
47
+ // (it writes tab URLs directly via pushState). Pin the URL to '/signin' so
48
+ // it matches the screen actually shown. No-op on native.
49
+ if (kIsWeb) {
50
+ WidgetsBinding.instance
51
+ .addPostFrameCallback((_) => syncBrowserUrl('/signin'));
52
+ }
44
53
  final state = ref.watch(signinStateProvider);
45
54
  final isSending = state is SigninStateSending;
46
55
  final showBack = shouldShowAuthBackButton(context);
@@ -238,9 +247,9 @@ class _SignupPrompt extends StatelessWidget {
238
247
  }
239
248
  }
240
249
 
241
- /// "Continue as guest" — creates the anonymous account on demand and lets the
242
- /// router redirect carry the user into the app. Shown only on native in
243
- /// anonymous mode (see the build condition in [SigninPage]).
250
+ /// "Continue as guest" — creates the anonymous account on demand and navigates
251
+ /// into the app. Shown only on native in anonymous mode (see the build
252
+ /// condition in [SigninPage]).
244
253
  class _GuestContinueButton extends ConsumerStatefulWidget {
245
254
  const _GuestContinueButton();
246
255
 
@@ -256,8 +265,13 @@ class _GuestContinueButtonState extends ConsumerState<_GuestContinueButton> {
256
265
  setState(() => _loading = true);
257
266
  try {
258
267
  await ref.read(userStateNotifierProvider.notifier).continueAsGuest();
259
- // Success: the router redirect navigates to home reactively — nothing
260
- // else to do here. The widget is disposed as we leave the page.
268
+ if (!mounted) return;
269
+ // Navigate explicitly: the user is now an anonymous guest (has an id but
270
+ // is NOT an AuthenticatedUserData), and the router redirect intentionally
271
+ // only bounces *authenticated* users off the auth routes — so a guest can
272
+ // still open sign-in to upgrade. That means it won't carry us home on its
273
+ // own here; we do it ourselves.
274
+ context.go('/');
261
275
  } catch (_) {
262
276
  if (!mounted) return;
263
277
  setState(() => _loading = false);
@@ -559,16 +559,16 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
559
559
  builder: _buildSidebarAppNav,
560
560
  ),
561
561
  ComponentPreviewVariant(
562
- label: 'With search',
563
- builder: _buildSidebarWithSearch,
562
+ label: 'Mobile drawer',
563
+ builder: _buildSidebarMobileDrawer,
564
564
  ),
565
565
  ComponentPreviewVariant(
566
566
  label: 'Collapsed rail',
567
567
  builder: _buildSidebarCollapsed,
568
568
  ),
569
569
  ComponentPreviewVariant(
570
- label: 'Mobile drawer',
571
- builder: _buildSidebarMobileDrawer,
570
+ label: 'Pinned (always wide)',
571
+ builder: _buildSidebarPinned,
572
572
  ),
573
573
  ComponentPreviewVariant(
574
574
  label: 'Workspace showcase',
@@ -629,56 +629,70 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
629
629
  // Sidebar — interactive preview
630
630
  // ─────────────────────────────────────────────────────────────────────────────
631
631
 
632
- // The live Home configuration (real pages, highlight-only nav). The default
633
- // preview what actually ships in the app.
634
- Widget _buildSidebarAppNav(BuildContext context) => const _SidebarPreview();
632
+ // The live Home configuration (real pages, highlight-only nav). The default
633
+ // adapts to the breakpoint, which is what actually ships in the app.
634
+ Widget _buildSidebarAppNav(BuildContext context) => const _SidebarPreview(
635
+ caption: 'The default app navigation, wired to the live Home pages. '
636
+ 'Adapts to the breakpoint: wide on desktop, a thin icon rail on '
637
+ 'tablet and mobile — the toggle expands it on any screen.',
638
+ );
635
639
 
636
- // Same Home config plus the opt-in ⌘K search row pinned above the footer.
637
- Widget _buildSidebarWithSearch(BuildContext context) =>
638
- const _SidebarPreview(showSearch: true);
640
+ // The mobile pattern: always opens wide as a slide-in drawer, no collapse
641
+ // toggle (the sheet you open from an app-bar menu button on a phone).
642
+ Widget _buildSidebarMobileDrawer(BuildContext context) => const _SidebarPreview(
643
+ isDrawer: true,
644
+ caption: 'The phone pattern: always opens wide as a slide-in drawer, with '
645
+ 'the collapse toggle hidden. This is what an app-bar menu button opens.',
646
+ );
639
647
 
640
- // Home config starting as the narrow icon rail (tooltips on hover).
641
- Widget _buildSidebarCollapsed(BuildContext context) =>
642
- const _SidebarPreview(initiallyCollapsed: true);
648
+ // Starts as the narrow icon rail (tooltips on hover), at any breakpoint.
649
+ Widget _buildSidebarCollapsed(BuildContext context) => const _SidebarPreview(
650
+ collapseMode: KasySidebarCollapseMode.collapsed,
651
+ caption: 'Starts collapsed to the thin icon rail (labels show as hover '
652
+ 'tooltips). Expand it with the toggle on any breakpoint.',
653
+ );
643
654
 
644
- // The mobile pattern: always wide, collapse toggle hidden (isDrawer).
645
- Widget _buildSidebarMobileDrawer(BuildContext context) =>
646
- const _SidebarPreview(isDrawer: true);
655
+ // Pinned wide on every breakpoint; the user collapses it themselves.
656
+ Widget _buildSidebarPinned(BuildContext context) => const _SidebarPreview(
657
+ collapseMode: KasySidebarCollapseMode.expanded,
658
+ caption: 'Pinned wide on every breakpoint instead of auto-collapsing; the '
659
+ 'user narrows it with the toggle when they want the room.',
660
+ );
647
661
 
648
- // The rich SaaS showcase (workspace selector, KasyTabs segment, layers list).
649
- Widget _buildSidebarShowcase(BuildContext context) =>
650
- const _SidebarPreview(showcase: true);
662
+ // The rich SaaS showcase (workspace selector, KasyTabs segment, ⌘K search).
663
+ Widget _buildSidebarShowcase(BuildContext context) => const _SidebarPreview(
664
+ showcase: true,
665
+ caption: 'The rich SaaS layout: workspace switcher, a KasyTabs segment '
666
+ '(Layers / Assets) and a pinned ⌘K search row.',
667
+ );
651
668
 
652
669
  /// Launches a [KasySidebar] as a full-height drawer (left + right buttons) so
653
- /// the full-bleed chrome can be inspected without crushing the preview card.
670
+ /// the full-bleed chrome can be inspected without crushing the preview card. A
671
+ /// short [caption] (Kasy typography) explains what each configuration is.
654
672
  /// Connected variants mirror the live Home; [showcase] swaps to the HeroUI
655
673
  /// workspace demo.
656
674
  class _SidebarPreview extends StatelessWidget {
657
675
  const _SidebarPreview({
676
+ required this.caption,
658
677
  this.showcase = false,
659
- this.initiallyCollapsed = false,
678
+ this.collapseMode = KasySidebarCollapseMode.responsive,
660
679
  this.isDrawer = false,
661
- this.showSearch = false,
662
680
  });
663
681
 
682
+ final String caption;
664
683
  final bool showcase;
665
- final bool initiallyCollapsed;
684
+ final KasySidebarCollapseMode collapseMode;
666
685
  final bool isDrawer;
667
- final bool showSearch;
668
686
 
669
687
  Widget _sidebar({required bool fromEnd}) {
670
688
  final KasySidebarSide side =
671
689
  fromEnd ? KasySidebarSide.right : KasySidebarSide.left;
672
690
  if (showcase) {
673
- return KasySidebar(
674
- initiallyCollapsed: initiallyCollapsed,
675
- side: side,
676
- );
691
+ return KasySidebar(collapseMode: collapseMode, side: side);
677
692
  }
678
693
  return _DemoSidebar(
679
694
  isDrawer: isDrawer,
680
- initiallyCollapsed: initiallyCollapsed,
681
- showSearch: showSearch,
695
+ collapseMode: collapseMode,
682
696
  side: side,
683
697
  );
684
698
  }
@@ -696,6 +710,17 @@ class _SidebarPreview extends StatelessWidget {
696
710
  mainAxisSize: MainAxisSize.min,
697
711
  crossAxisAlignment: CrossAxisAlignment.stretch,
698
712
  children: [
713
+ Padding(
714
+ padding: const EdgeInsets.symmetric(horizontal: KasySpacing.md),
715
+ child: Text(
716
+ caption,
717
+ textAlign: TextAlign.center,
718
+ style: context.textTheme.bodySmall?.copyWith(
719
+ color: context.colors.muted,
720
+ ),
721
+ ),
722
+ ),
723
+ const SizedBox(height: KasySpacing.md),
699
724
  Padding(
700
725
  padding: const EdgeInsets.symmetric(horizontal: KasySpacing.md),
701
726
  child: _previewIntrinsicLaunch(
@@ -728,14 +753,12 @@ class _SidebarPreview extends StatelessWidget {
728
753
  class _DemoSidebar extends StatefulWidget {
729
754
  const _DemoSidebar({
730
755
  this.isDrawer = false,
731
- this.initiallyCollapsed = false,
732
- this.showSearch = false,
756
+ this.collapseMode = KasySidebarCollapseMode.responsive,
733
757
  this.side = KasySidebarSide.left,
734
758
  });
735
759
 
736
760
  final bool isDrawer;
737
- final bool initiallyCollapsed;
738
- final bool showSearch;
761
+ final KasySidebarCollapseMode collapseMode;
739
762
  final KasySidebarSide side;
740
763
 
741
764
  @override
@@ -783,8 +806,7 @@ class _DemoSidebarState extends State<_DemoSidebar> {
783
806
  currentItem: _current,
784
807
  onTapItem: (i) => _current.value = i, // highlight only
785
808
  isDrawer: widget.isDrawer,
786
- initiallyCollapsed: widget.initiallyCollapsed,
787
- showSearch: widget.showSearch,
809
+ collapseMode: widget.collapseMode,
788
810
  side: widget.side,
789
811
  );
790
812
  }
@@ -155,7 +155,6 @@ class SettingsPage extends ConsumerWidget {
155
155
  isAdmin: user.isAdmin,
156
156
  isPhone: isPhone,
157
157
  ),
158
- const SizedBox(height: KasySpacing.xl),
159
158
  ],
160
159
  );
161
160
  }
@@ -24,6 +24,8 @@ import 'package:kasy_kit/core/web_device_preview/web_device_preview.dart';
24
24
  import 'package:kasy_kit/core/web_viewport_scale.dart';
25
25
  import 'package:kasy_kit/core/widgets/focus_visibility.dart';
26
26
  import 'package:kasy_kit/environments.dart';
27
+ import 'package:kasy_kit/features/authentication/api/auth_web_support.dart'
28
+ if (dart.library.js_interop) 'package:kasy_kit/features/authentication/api/auth_web_support_web.dart';
27
29
  import 'package:kasy_kit/features/authentication/api/authentication_api.dart';
28
30
  import 'package:kasy_kit/features/notifications/api/local_notifier.dart';
29
31
  import 'package:kasy_kit/features/notifications/repositories/background_notification_handler.dart';
@@ -88,6 +90,11 @@ void main() async {
88
90
  // MUST be registered at the top-level BEFORE runApp().
89
91
  FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);
90
92
 
93
+ // Web: if we just returned from a social-login redirect, reset the address bar
94
+ // to '/' BEFORE the router reads the initial URL, so the app boots on Home
95
+ // instead of the stale tab URL (e.g. /settings) the redirect returned to.
96
+ resetUrlAfterRedirectLogin();
97
+
91
98
  // initialize sentry for error reporting in production only
92
99
  if (env is DevEnvironment) {
93
100
  run(sharedPrefs);
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
16
16
  # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17
17
  # In Windows, build-name is used as the major, minor, and patch parts
18
18
  # of the product and file versions while build-number is used as the build suffix.
19
- version: 1.0.0+53
19
+ version: 1.0.0+57
20
20
 
21
21
  environment:
22
22
  sdk: ^3.11.0
@@ -73,7 +73,7 @@ dependencies:
73
73
  json_annotation: ^4.9.0
74
74
  local_auth: ^3.0.1
75
75
  logger: ^2.6.2
76
- lucide_icons_flutter: ^3.1.10
76
+ lucide_icons_flutter: ^3.1.14
77
77
  mixpanel_flutter: ^2.5.0
78
78
  open_filex: ^4.7.0
79
79
  package_info_plus: ^8.3.0