kasy-cli 1.32.0 → 1.35.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 (169) hide show
  1. package/README.md +1 -1
  2. package/bin/kasy.js +66 -2
  3. package/docs/cli-reference.md +7 -7
  4. package/lib/commands/apple-web.js +222 -0
  5. package/lib/commands/configure.js +3 -91
  6. package/lib/commands/doctor.js +20 -0
  7. package/lib/commands/facebook.js +189 -0
  8. package/lib/commands/new.js +61 -11
  9. package/lib/commands/release-version.js +234 -0
  10. package/lib/commands/update.js +27 -0
  11. package/lib/scaffold/CHANGELOG.json +27 -0
  12. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
  13. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
  14. package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
  15. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
  16. package/lib/scaffold/backends/firebase/setup-from-scratch.js +199 -21
  17. package/lib/scaffold/backends/patch-base-hashes.json +66 -0
  18. package/lib/scaffold/backends/supabase/deploy.js +92 -0
  19. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
  20. package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
  21. package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
  22. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +92 -3
  23. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
  24. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
  25. package/lib/scaffold/generate.js +53 -4
  26. package/lib/scaffold/shared/generator-utils.js +18 -6
  27. package/lib/utils/apple-web.js +147 -0
  28. package/lib/utils/facebook.js +162 -0
  29. package/lib/utils/i18n/messages-en.js +85 -0
  30. package/lib/utils/i18n/messages-es.js +85 -0
  31. package/lib/utils/i18n/messages-pt.js +85 -0
  32. package/package.json +5 -2
  33. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
  34. package/templates/firebase/AGENTS.md +170 -0
  35. package/templates/firebase/CLAUDE.md +16 -0
  36. package/templates/firebase/DESIGN_SYSTEM.md +269 -0
  37. package/templates/firebase/docs/auth-setup.en.md +4 -2
  38. package/templates/firebase/docs/auth-setup.es.md +4 -2
  39. package/templates/firebase/docs/auth-setup.pt.md +4 -2
  40. package/templates/firebase/firebase.json +56 -1
  41. package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
  42. package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
  43. package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
  44. package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
  45. package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
  46. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
  47. package/templates/firebase/lib/components/components.dart +1 -0
  48. package/templates/firebase/lib/components/kasy_alert.dart +0 -1
  49. package/templates/firebase/lib/components/kasy_app_bar.dart +35 -17
  50. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
  51. package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
  52. package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
  53. package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
  54. package/templates/firebase/lib/components/kasy_screen.dart +114 -0
  55. package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
  56. package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
  57. package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
  58. package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
  59. package/templates/firebase/lib/components/kasy_toast.dart +108 -73
  60. package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
  61. package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
  62. package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
  63. package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
  64. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
  65. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
  66. package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
  67. package/templates/firebase/lib/core/chrome/chrome_visibility.dart +22 -0
  68. package/templates/firebase/lib/core/config/features.dart +5 -0
  69. package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
  70. package/templates/firebase/lib/core/guards/guard.dart +16 -2
  71. package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
  72. package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
  73. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +48 -124
  74. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
  75. package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
  76. package/templates/firebase/lib/core/states/logout_action.dart +5 -1
  77. package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
  78. package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
  79. package/templates/firebase/lib/core/theme/texts.dart +90 -57
  80. package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
  81. package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
  82. package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
  83. package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
  84. package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
  85. package/templates/firebase/lib/core/web_screen_width.dart +15 -0
  86. package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
  87. package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
  88. package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
  89. package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
  90. package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +29 -126
  91. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +11 -8
  92. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +21 -0
  93. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
  94. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
  95. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
  96. package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
  97. package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
  98. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +266 -0
  99. package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
  100. package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
  101. package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
  102. package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
  103. package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
  104. package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
  105. package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
  106. package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
  107. package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
  108. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +80 -15
  109. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +20 -14
  110. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
  111. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
  112. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
  113. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -2
  114. package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
  115. package/templates/firebase/lib/features/home/home_components_page.dart +8 -1
  116. package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
  117. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +186 -56
  118. package/templates/firebase/lib/features/home/home_page.dart +4 -0
  119. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +169 -208
  120. package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
  121. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +2 -2
  122. package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +21 -8
  123. package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +2 -2
  124. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -4
  125. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +84 -128
  126. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
  127. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
  128. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
  129. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +3 -4
  130. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +2 -4
  131. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +2 -1
  132. package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
  133. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +58 -21
  134. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
  135. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
  136. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
  137. package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
  138. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
  139. package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
  140. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
  141. package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
  142. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
  143. package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
  144. package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
  145. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
  146. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
  147. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
  148. package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
  149. package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
  150. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
  151. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
  152. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
  153. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
  154. package/templates/firebase/lib/i18n/en.i18n.json +54 -7
  155. package/templates/firebase/lib/i18n/es.i18n.json +54 -7
  156. package/templates/firebase/lib/i18n/pt.i18n.json +54 -7
  157. package/templates/firebase/lib/main.dart +11 -2
  158. package/templates/firebase/lib/router.dart +94 -13
  159. package/templates/firebase/pubspec.yaml +1 -1
  160. package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
  161. package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
  162. package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
  163. package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
  164. package/templates/firebase/tool/design_check.dart +152 -0
  165. package/templates/firebase/web/index.html +162 -14
  166. package/templates/firebase/assets/images/review.png +0 -0
  167. package/templates/firebase/assets/images/update.png +0 -0
  168. package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
  169. package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +0 -32
@@ -0,0 +1,170 @@
1
+ # AGENTS.md — working in this codebase
2
+
3
+ This project was generated with the **Kasy** kit. This file orients any AI coding
4
+ assistant (Claude Code, Cursor, GitHub Copilot, …). Read it before generating
5
+ code. Claude Code users: `CLAUDE.md` points here.
6
+
7
+ ## What this is
8
+
9
+ A production-ready Flutter app (web, iOS, Android) built on the **Kasy design
10
+ system** and a **feature-first** architecture. It was generated with **one**
11
+ backend — Firebase, Supabase, or a REST API. Only the **data layer** differs
12
+ between backends; the UI, state, i18n and design system are identical. So almost
13
+ everything below is backend-agnostic.
14
+
15
+ ## Golden rules (every time)
16
+
17
+ 1. **Use the design system, never raw values.** No hardcoded `fontSize`,
18
+ `Color(0x…)`, `Colors.*`, or literal padding / radius / icon sizes. Use
19
+ `context.textTheme.*` / `KasyTextTheme.*`, `context.colors.*`,
20
+ `KasySpacing.*`, `KasyRadius.*`, `KasyIconSize.*`. Full reference:
21
+ **`DESIGN_SYSTEM.md`**.
22
+ 2. **Use kit components, not raw Material.** `KasyButton` (not
23
+ ElevatedButton / TextButton), `showKasyConfirmDialog` (not AlertDialog),
24
+ `showKasyToast` (not SnackBar), plus `KasyCard`, `KasyTextField`,
25
+ `KasyScreen`, etc.
26
+ 3. **The Home screen is the visual reference** — clean, minimal, lots of
27
+ surface, one accent colour used sparingly. New screens should mirror it.
28
+ 4. **English everywhere** in code, comments and identifiers. **No hardcoded
29
+ user-facing strings** — use slang i18n (`context.t.…`).
30
+ 5. **Package imports only**: `import 'package:kasy_kit/…';`, never relative.
31
+ 6. **Zero analyzer issues.** Run `flutter analyze` and fix everything before
32
+ considering a change done.
33
+
34
+ ## Design system, components & responsiveness
35
+
36
+ This kit is an **organized, opinionated** design system — not a blank canvas.
37
+ Before building anything, browse the live **Design System** screen
38
+ (`lib/features/home/design_system_page.dart`) and the **Components** gallery:
39
+ colours, typography, spacing and dozens of ready widgets (`KasyButton`,
40
+ `KasyCard`, `KasyTextField`, `KasyScreen`, `showKasyToast`,
41
+ `showKasyConfirmDialog`, …). Reuse one before writing a new widget. Full token
42
+ reference: **`DESIGN_SYSTEM.md`**.
43
+
44
+ **Responsiveness is deliberate, not improvised.** There are exactly three
45
+ breakpoints and the type sizes are pinned per breakpoint, so the UI never
46
+ oscillates or guesses:
47
+
48
+ | Breakpoint | Width | Notes |
49
+ | ---------- | ---------- | ------------------------------------------------ |
50
+ | Mobile | `< 768` | native iOS/Android + phone-width web |
51
+ | Tablet | `768–1024` | |
52
+ | Desktop | `>= 1024` | authored baseline |
53
+
54
+ - **Typography** (`KasyTypeScale`): each role has an **explicit size per
55
+ breakpoint** — headings are largest on desktop and step down on smaller
56
+ screens; body/labels stay constant. It is applied globally by
57
+ `ResponsiveTextTheme` — **screens just use `context.textTheme.*` /
58
+ `context.kasyTextTheme.*` and the right size comes out**. Don't hardcode
59
+ `fontSize`; don't invent your own breakpoints.
60
+ - **Native mobile** is straightforward: it's the Mobile breakpoint and never
61
+ scales the viewport.
62
+ - **Web render scale** (`WebViewportScale`) is a *separate* concern: Flutter web
63
+ renders ~10% large, so **desktop only** scales the whole UI to `0.95`. Tablet
64
+ and mobile web render at natural size, like native. This is NOT the typography
65
+ scale — don't conflate the two.
66
+
67
+ To re-tune sizes, edit `KasyTypeScale` in `lib/core/theme/type_scale.dart` —
68
+ nothing else. The live ramp (tabs per breakpoint) is under **Design System →
69
+ Typography**.
70
+
71
+ ## Architecture (feature-first, three layers)
72
+
73
+ ```
74
+ lib/
75
+ ├── components/ # Kasy design-system widgets (KasyButton, KasyCard, …)
76
+ ├── core/ # cross-cutting: theme/, data/, states/, security/, widgets/, i18n
77
+ └── features/
78
+ └── <feature>/
79
+ ├── api/ # data source (Firebase / Supabase / REST) → returns entities
80
+ ├── repositories/ # entities → domain models (business logic)
81
+ ├── providers/ # Riverpod notifiers → immutable page state
82
+ └── ui/ # pages, components (use provider + domain), widgets (dumb)
83
+ ```
84
+
85
+ - **Data flow:** `api → repository → provider (notifier) → ui`. Only repositories
86
+ use api classes. The api returns entities; repositories return domain models.
87
+ - **State:** a Riverpod notifier exposes an immutable (freezed) state; the view
88
+ `ref.watch`es it and triggers actions. Use `.when` / `.map` for
89
+ data / loading / error.
90
+ - **Backend note:** the `api` layer is the only backend-specific code. A new
91
+ data-backed feature follows the same `api → repo → provider` chain the
92
+ existing features use.
93
+
94
+ ## Building a screen or component — checklist
95
+
96
+ 1. Read `DESIGN_SYSTEM.md` (tokens, typography roles, components).
97
+ 2. Reuse a kit component before building one. Wrap new pages in `KasyScreen`
98
+ (or mirror an existing page's scaffold).
99
+ 3. Use semantic typography roles (`pageTitle`, `sectionTitle`, `rowTitle`, …)
100
+ and tokens for **every** size, colour and spacing.
101
+ 4. Strings via `context.t.*` (add keys to the i18n JSON, never inline text).
102
+ 5. `flutter analyze` clean. Optional: `dart run tool/design_check.dart` flags
103
+ any raw values that slipped into `lib/features`.
104
+
105
+ ## Platform notes (native-only features)
106
+
107
+ Some features only work on a real iOS / Android build and are gated with
108
+ `kIsWeb`. They never run on web; where the user explicitly triggers them, a
109
+ "native app only" toast is shown instead of failing silently.
110
+
111
+ - **In-app review / store rating** — sends the user to the App Store / Play
112
+ Store. It is never shown on web. **iOS requires the numeric App Store ID**:
113
+ run `kasy configure` and fill `App Store ID (numeric)`. Android works
114
+ automatically from the package name. Until the ID is set, iOS store review
115
+ throws at runtime.
116
+ - **Push / FCM token, notification permission** — native APIs; on web the admin
117
+ actions surface the "native app only" toast.
118
+ - **App update prompts** — two distinct flows, both native-only and driven by
119
+ Firebase Remote Config (works on every backend): **"update available"** (shown
120
+ *before*, when the installed version is below `app_latest_version` / blocking
121
+ below `app_min_version`, sends the user to the store) and **"what's new"**
122
+ (shown *after* updating). See `lib/core/app_update/` and the README.
123
+
124
+ ## Optional design guard-rail
125
+
126
+ `dart run tool/design_check.dart` scans `lib/features` for raw values
127
+ (hardcoded fonts / colours / Material widgets). It is **opt-in and
128
+ non-blocking** — relax it, wire it into CI, or delete it freely. Per-line
129
+ escape hatch: `// design-check: ignore`.
130
+
131
+ ## Updating the kit (merging new versions with your changes)
132
+
133
+ The project is a scaffold: the kit code was copied into it. Improvements ship via
134
+ the CLI, which **overwrites** files (it does not auto-merge). The safety net is git.
135
+
136
+ Recommended flow when the user wants newer kit code:
137
+
138
+ 1. **Commit first** so the working tree is clean (the CLI also warns about this).
139
+ 2. Run the relevant command:
140
+ - `kasy update` — shows what's new for the features in use
141
+ - `kasy update core` / `kasy update components` — refreshes core files / UI components
142
+ - `kasy update <feature>` — e.g. `revenuecat`, `ai_chat`, `auth` (re-applies that feature)
143
+ 3. The command overwrites the files and prints which ones changed.
144
+ 4. **Merge:** if the user had customized any overwritten file, reconcile their version
145
+ (in the previous git commit) with the new kit version. As the in-project agent,
146
+ this is your job: read `git diff` (or `git show HEAD:<file>` for the user's prior
147
+ version) and produce a result that keeps BOTH the user's customizations and the new
148
+ kit improvements. Prefer additive merges; never silently drop the user's logic.
149
+ When unsure which side wins for a hunk, ask the user.
150
+
151
+ This is why committing before `kasy update` matters: it makes the user's prior version
152
+ recoverable so the merge is a normal 3-way reconcile, not a guess.
153
+
154
+ ## Debugging when something doesn't work
155
+
156
+ Diagnose with real signals, not guesses:
157
+
158
+ - **App logs first.** Key steps (auth, service init) are logged via `Logger` — read
159
+ the `kasy run` terminal / browser console.
160
+ - **Then the backend logs.** Supabase: Dashboard → Logs (Auth / Edge Functions /
161
+ Postgres). Firebase: Console → Functions logs / Authentication.
162
+ - **Auth / token errors:** decode the JWT (base64url-decode the middle segment) and
163
+ inspect `sub` / `aud` / `iss`. A `missing sub claim` usually means the wrong
164
+ session/token was sent (e.g. the publishable `anon` key), not a bad provider token.
165
+ - **`kasy doctor`** checks common setup gaps (Google SHA-1, authorized domains,
166
+ provider config).
167
+ - **Web vs native differ:** web runs in `authRequired` mode (no anonymous user);
168
+ native is anonymous-first. Account-linking only applies when a real session exists.
169
+
170
+ Add a temporary `print(...)` to inspect a runtime value, then remove it.
@@ -0,0 +1,16 @@
1
+ # CLAUDE.md
2
+
3
+ This project shares one AI-guidance file across tools. **Read `AGENTS.md`** in
4
+ this directory first — it defines the architecture, the Kasy design system, and
5
+ the conventions to follow before generating any code.
6
+
7
+ Quick reminders:
8
+
9
+ - Use design-system tokens, never raw values — see `DESIGN_SYSTEM.md`
10
+ (`context.textTheme.*`, `context.colors.*`, `KasySpacing.*`, `KasyRadius.*`,
11
+ `KasyIconSize.*`).
12
+ - Use Kasy components (`KasyButton`, `KasyCard`, `showKasyToast`,
13
+ `showKasyConfirmDialog`, …), not raw Material.
14
+ - The Home screen is the visual reference: clean, minimal, one accent colour.
15
+ - English in code; user strings via `context.t.*` (slang i18n).
16
+ - Run `flutter analyze` and keep it clean.
@@ -0,0 +1,269 @@
1
+ # Kasy Design System
2
+
3
+ **Version 1.0.0** · last updated 2026-06-11
4
+
5
+ The single source of truth for how the app looks. Everything here lives in code
6
+ under `lib/core/theme/` and `lib/components/`, so changing a token in one place
7
+ updates the whole app (web, Android, iOS) at once. This document just writes the
8
+ rules down so anyone, including projects generated by the Kasy CLI, inherits the
9
+ same standard without guessing.
10
+
11
+ Design principle: **clean, modern, minimal**. Lots of surface, one accent colour
12
+ used sparingly, no loud weights. The Home screen (sidebar + header + elevated
13
+ `KasyCard`) is the reference for "what good looks like"; new screens should
14
+ mirror it.
15
+
16
+ ---
17
+
18
+ ## The golden rule
19
+
20
+ Use **tokens and roles**, never bespoke values.
21
+
22
+ | Instead of… | Use… |
23
+ | ----------------------------------- | -------------------------------------------- |
24
+ | `fontSize: 16, fontWeight: w600` | `context.textTheme.*` / `KasyTextTheme.*` |
25
+ | `Icon(size: 20)` | `Icon(size: KasyIconSize.lg)` |
26
+ | `EdgeInsets.all(16)` | `EdgeInsets.all(KasySpacing.md)` |
27
+ | `BorderRadius.circular(16)` | `BorderRadius.circular(KasyRadius.lg)` |
28
+ | `Color(0xFF…)` / `Colors.*` | `context.colors.*` |
29
+ | `ElevatedButton`, `TextButton` | `KasyButton` |
30
+ | `AlertDialog`, `SnackBar` | `showKasyConfirmDialog`, `showKasyToast` |
31
+
32
+ A bespoke number is only acceptable when a component is being *calibrated* (the
33
+ sidebar, the date-picker grid, badges) and there is no token that fits. When in
34
+ doubt, add or reuse a role rather than hardcoding.
35
+
36
+ ---
37
+
38
+ ## Typography — `KasyTextTheme` (`lib/core/theme/texts.dart`)
39
+
40
+ ### HeroUI scale (the raw roles, Inter)
41
+
42
+ | Role | Size / weight / line-height |
43
+ | --------------- | --------------------------- |
44
+ | `heading1` | 36 / w800 / 40 |
45
+ | `heading2` | 24 / w700 / 32 |
46
+ | `heading3` | 20 / w600 / 28 |
47
+ | `heading4` | 16 / w600 / 24 |
48
+ | `bodyBase` | 16 / w400 |
49
+ | `bodyBaseMedium`| 16 / w500 |
50
+ | `bodySm` | 14 / w400 |
51
+ | `bodySmMedium` | 14 / w500 |
52
+ | `bodyXs` | 12 / w400 |
53
+ | `bodyXsMedium` | 12 / w500 |
54
+
55
+ ### Semantic app roles (prefer these on screens)
56
+
57
+ | Role | Maps to | Use for |
58
+ | -------------- | ------------------------------ | ----------------------------------------- |
59
+ | `pageTitle` | `heading2` (24/w700) | Screen / page titles, onboarding titles |
60
+ | `sectionTitle` | `heading4` (16/w600) | Detail / section headings |
61
+ | `sectionLabel` | 12 / w600 / letter-spacing 0.5 | Uppercase group eyebrows (lists, settings)|
62
+ | `rowTitle` | `bodySmMedium` (14/w500) | List / settings row titles |
63
+ | `rowValue` | `bodySm` (14/w400) | List / settings row values |
64
+ | `cardTitle` | `bodySmMedium` (14/w500) | Card titles |
65
+ | `cardSubtitle` | `bodyXs` (12/w400) | Card secondary text |
66
+ | `caption` | `bodyXs` (12/w400) | Captions, helper text |
67
+
68
+ Access the semantic roles via `context.kasyTextTheme.*` (colourless — apply
69
+ colour with `.copyWith`). Access Material slots via `context.textTheme.*`
70
+ (colour already applied). The raw scale roles are also exposed as
71
+ `KasyTextTheme.*` statics, but those are **fixed** (the desktop baseline, for
72
+ specs/docs) — use the `context.*` accessors in live UI so text scales.
73
+
74
+ Material → role mapping: `headlineMedium = heading2`, `headlineSmall =
75
+ titleLarge = heading3`, `titleMedium = heading4`.
76
+
77
+ The chrome/app-bar title is **w700** (not w900). Black weights read as heavy and
78
+ fight the rest of the UI.
79
+
80
+ ### Responsive type — `KasyTypeScale` (`lib/core/theme/type_scale.dart`)
81
+
82
+ Not a blind multiplier: **every role declares an explicit size per breakpoint**
83
+ (the way Figma variables / modes do). The professional pattern — **headings are
84
+ largest on desktop and step down on smaller viewports** (a 36px title would feel
85
+ huge and wrap badly on a phone), while **body and labels stay constant** for
86
+ stable reading everywhere. Desktop is the authored reference.
87
+
88
+ | Role (example) | Mobile `< 768` | Tablet `768–1024` | Desktop `>= 1024` |
89
+ | -------------- | -------------- | ----------------- | ----------------- |
90
+ | `heading1` | 28 | 32 | 36 |
91
+ | `heading2` | 22 | 23 | 24 |
92
+ | `heading3` | 18 | 19 | 20 |
93
+ | `bodyBase` | 16 | 16 | 16 |
94
+ | `bodySm` | 14 | 14 | 14 |
95
+
96
+ `ResponsiveTextTheme` (innermost wrapper in `main.dart`) reads the viewport
97
+ width, picks the breakpoint and republishes a `Theme` whose Material `textTheme`
98
+ and `KasyTextTheme` extension use that breakpoint's sizes. Every
99
+ `context.textTheme.*` and `context.kasyTextTheme.*` inherits it — **no screen
100
+ does anything**; desktop is a pass-through (the authored baseline). Component
101
+ themes (buttons, inputs) keep their calibrated sizes on purpose. To re-tune the
102
+ whole app, edit the numbers in `KasyTypeScale` — nothing else. The live ramp is
103
+ visible under **Design System → Typography** (tabs per breakpoint).
104
+
105
+ **Web render scale (`WebViewportScale`, `lib/core/web_viewport_scale.dart`).** A
106
+ separate concern from type sizes: Flutter web renders ~10% larger than an
107
+ equivalent HTML app, so on **desktop only** (`>= 1024`) the whole UI is scaled to
108
+ `0.95` — a true viewport scale, so layout, scrolling and hit-testing stay
109
+ correct. Tablet and mobile web render at natural size, exactly like the native
110
+ build, which never scales.
111
+
112
+ ---
113
+
114
+ ## Icon sizes — `KasyIconSize` (`lib/core/theme/icon_sizes.dart`)
115
+
116
+ | Token | px | Token alias | = |
117
+ | ----------- | -- | ------------ | -- |
118
+ | `xxs` | 12 | `rowLeading` | `lg` (20) |
119
+ | `xs` | 14 | `rowTrailing`| `sm` (16) |
120
+ | `sm` | 16 | `chrome` | `lg` (20) |
121
+ | `md` | 18 | `inline` | `md` (18) |
122
+ | `lg` | 20 | | |
123
+ | `xl` | 24 | | |
124
+ | `xxl` | 28 | | |
125
+ | `display` | 36 | | |
126
+ | `hero` | 72 | | |
127
+
128
+ Never pass a numeric `size:` to `Icon`; use a token so re-scaling is one edit.
129
+
130
+ ---
131
+
132
+ ## Spacing — `KasySpacing` (`lib/core/theme/spacing.dart`)
133
+
134
+ `xs 4 · sm 8 · smd 12 · md 16 · lg 24 · xl 32 · xxl 48 · xxxl 64`
135
+
136
+ `pageHorizontalGutter = md (16)` · `pageVerticalGutter = sm (8)` ·
137
+ `belowChromeContentGap = 20`.
138
+
139
+ ## Radius — `KasyRadius` (`lib/core/theme/radius.dart`)
140
+
141
+ Semantic: `xs 4 · sm 8 · md 12 · lg 16 · xl 24 · full 999`.
142
+ `KasyCard` defaults to `lg` (16).
143
+
144
+ ---
145
+
146
+ ## Colours — `KasyColors`, via `context.colors.*`
147
+
148
+ Always pull colours from `context.colors.*` so light/dark resolve automatically.
149
+ Key tokens: `primary`, `onPrimary`, `surface`, `onSurface`, `onBackground`,
150
+ `muted`, `fieldLabel`, `outline`, `error` / `onError`, `success`, `warning`,
151
+ `surfacePrimarySoft`, `surfaceNeutralSoft`, `surfaceErrorSoft`.
152
+
153
+ Brand: avocado green `#D2F51E` (accent), deep green `#011820` (dark ground).
154
+ Disabled state: blend the colour toward the surface (opaque), never raw opacity.
155
+
156
+ ---
157
+
158
+ ## Components
159
+
160
+ ### `KasyButton`
161
+ Sizes (height): `small 40` · `medium 45` (default) · `large 54`. Radius
162
+ `KasyRadius.md`. Variants: primary, secondary, tertiary, destructive,
163
+ destructiveSoft, inverse, link, soft, neutral, outline, ghost.
164
+
165
+ ### `KasyTextField`
166
+ Canonical single-line height: **`singleLineHeight = 45`** (drives the field's
167
+ vertical padding so the box itself is exactly that tall on every platform — web
168
+ density is normalised to `standard`). Matches the medium button.
169
+
170
+ Variants:
171
+ - **primary** — surface fill + hairline border + soft shadow (floats; good on a
172
+ bare background).
173
+ - **secondary** — elevated fill (contrasts on a card) + border, no shadow.
174
+ - **flat** — primary's surface fill + a soft border, **no shadow**. For fields
175
+ sitting on a same-coloured card. Border comes from
176
+ `KasyShadows.inputFieldFlatBorder` (derived from `onSurface`, so it reads in
177
+ both light and dark; subtle by design).
178
+ - **embedded** — transparent, no border, no shadow.
179
+
180
+ Auth screens (sign in / sign up / recover) use **flat** on every breakpoint.
181
+ A custom `contentPadding` opts a field out of the 45 lock (e.g. the compact
182
+ header search). Use `forceFocusBorder` to show the "active" focused border from
183
+ state without taking real focus (composite triggers like the date picker).
184
+
185
+ ### `KasyTextArea`
186
+ Multi-line sibling of `KasyTextField`; same variants (primary / secondary / flat
187
+ / embedded). Height grows with content (not locked to 45).
188
+
189
+ ### `KasyDatePicker`
190
+ Reuses `KasyTextField` as a read-only trigger, so it inherits the field look and
191
+ the 45 height. The "open" border is painted from state (`forceFocusBorder`), so
192
+ a mouse click never doubles the keyboard focus ring and the border never drops
193
+ while the calendar is open.
194
+
195
+ ### `KasyCard`
196
+ Elevated surface panel, radius `KasyRadius.lg` (16). The base for screen content.
197
+
198
+ ### `KasyScreen`
199
+ Opt-in screen scaffold. A new page wrapped in it gets the internal-screen
200
+ contract for free: page background, a centred content column capped at
201
+ `maxContentWidth` (~600), the page gutter, scroll handling and an optional
202
+ `KasyCard`. It is a thin convenience over `Scaffold` (same `appBar` /
203
+ `floatingActionButton` / `bottomNavigationBar` slots), so it never blocks a
204
+ screen that needs bespoke chrome.
205
+
206
+ ```dart
207
+ KasyScreen(
208
+ appBar: KasyAppBar.root(title: 'Settings'),
209
+ card: true,
210
+ child: Column(children: [...]),
211
+ )
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Internal-screen contract
217
+
218
+ New internal screens follow the sidebar / Home ruler:
219
+ - Body text 14, icons 20 (`KasyIconSize.lg`), titles ~16-17 (`sectionTitle`).
220
+ - `KasyCard` radius 16, content width contained (~600), no oversized boxes.
221
+ - Use `KasyButton`, `showKasyConfirmDialog`, `showKasyToast` — not raw Material.
222
+ - Haptics only on native (`if (!kIsWeb)`); "hide chrome on scroll" only on the
223
+ phone breakpoint (`width < 768`).
224
+
225
+ ---
226
+
227
+ ## Keeping it consistent — the guard-rail (optional)
228
+
229
+ `tool/design_check.dart` is a plain Dart script that flags a **feature screen**
230
+ reaching around the design system (raw `fontSize`, raw `Color`, raw Material
231
+ widgets, literal icon sizes). The design system's own code (`lib/components`,
232
+ `lib/core`, `lib/main.dart`) is out of scope — that is where the primitives are
233
+ built.
234
+
235
+ ```bash
236
+ dart run tool/design_check.dart
237
+ ```
238
+
239
+ It is **opt-in and non-blocking by default**: nothing runs it automatically (not
240
+ wired into `flutter analyze`, builds or git hooks). Run it locally or add it to
241
+ CI to turn the convention into a gate. It has no extra dependency — read it, tune
242
+ the allowlists at the top, or delete it. For a deliberate one-off, put
243
+ `// design-check: ignore` on the line.
244
+
245
+ This is intentionally not a cage: a project built with the kit can relax or drop
246
+ it freely, and rebranding is still a token change (colours / type) in one place.
247
+
248
+ ---
249
+
250
+ ## Changelog
251
+
252
+ ### 1.1.0 — 2026-06-11
253
+ - `KasyScreen` scaffold added (opt-in internal-screen contract).
254
+ - Design guard-rail added (`tool/design_check.dart`), opt-in / non-blocking.
255
+ - Feature layer brought to a green baseline against the guard-rail (deliberate
256
+ exceptions marked with `// design-check: ignore`).
257
+
258
+
259
+ ### 1.0.0 — 2026-06-11
260
+ - Icon sizes centralised into `KasyIconSize`; product migrated off numeric sizes.
261
+ - Semantic typography roles added (`pageTitle`, `sectionTitle`, `sectionLabel`,
262
+ `rowTitle`, …).
263
+ - `KasyTextField`: `flat` variant + `forceFocusBorder`; single-line height
264
+ locked to **45** via derived padding (platform-independent); flat border role
265
+ `inputFieldFlatBorder` (visible in both themes, subtle).
266
+ - `KasyButton` medium height aligned to **45**.
267
+ - `KasyTextArea` gained the variant set.
268
+ - App-bar title weight `w900 → w700`; onboarding titles `28/26 → 24` (page-title
269
+ scale); notifications group eyebrow → `sectionLabel`.
@@ -73,9 +73,9 @@ Requires an [Apple Developer](https://developer.apple.com) account (paid).
73
73
  >
74
74
  > **Android**: the Apple button is hidden by design (it needs the paid Services ID web flow and adds little on Android for a SaaS). Leave it hidden.
75
75
  >
76
- > **Web (Firebase)**: works after Steps 1-4 above (`withAppleWebSignin` already ships `true`). The Services ID Return URL (`firebaseapp.com/__/auth/handler`) covers the popup flow.
76
+ > **Web (Firebase)**: after Steps 1-3, run `kasy apple-web` — it writes the Services ID + Team ID + Key ID + `.p8` into the Firebase Apple provider and turns on `withAppleWebSignin` (which ships `false`). The Services ID Return URL (`firebaseapp.com/__/auth/handler`) covers the popup flow. Firebase re-signs the secret itself (never expires).
77
77
  >
78
- > **Web (Supabase)**: the CLI ships `withAppleWebSignin = false` (native iOS works; web needs more). To enable it: in Supabase Authentication Providers Apple, add the **client secret** (a JWT signed with your `.p8` key + Services ID), then set `withAppleWebSignin = true` in `lib/core/config/features.dart`.
78
+ > **Web (Supabase)**: Apple on the web for Supabase is **not wired in the app yet** (roadmap) the button stays hidden on web. On Supabase, Apple works on **native iOS only** (already set up by `kasy new`). See `ROADMAP.md`.
79
79
 
80
80
  ---
81
81
 
@@ -83,6 +83,8 @@ Requires an [Apple Developer](https://developer.apple.com) account (paid).
83
83
 
84
84
  Requires a [Meta for Developers](https://developers.facebook.com) account.
85
85
 
86
+ > **Shortcut:** the `kasy facebook` command automates writing the credentials (Info.plist, strings.xml, and the Firebase/Supabase provider) and opens the Meta dashboard. The steps below are what you do on Meta (manual, no API).
87
+
86
88
  ### Step 1 — Create an app on Meta
87
89
 
88
90
  1. Open [Meta for Developers → My Apps](https://developers.facebook.com/apps)
@@ -73,9 +73,9 @@ Requiere cuenta de [Apple Developer](https://developer.apple.com) (de pago).
73
73
  >
74
74
  > **Android**: el botón Apple queda oculto por defecto (necesita el flujo del Services ID de pago y aporta poco en Android para un SaaS). Déjalo oculto.
75
75
  >
76
- > **Web (Firebase)**: funciona tras los Pasos 1 a 4 anteriores (`withAppleWebSignin` ya viene `true`). La Return URL del Services ID (`firebaseapp.com/__/auth/handler`) cubre el flujo de popup.
76
+ > **Web (Firebase)**: tras los Pasos 1 a 3, ejecuta `kasy apple-web` — escribe el Services ID + Team ID + Key ID + `.p8` en el proveedor Apple de Firebase y activa `withAppleWebSignin` (que viene `false`). La Return URL del Services ID (`firebaseapp.com/__/auth/handler`) cubre el flujo de popup. Firebase vuelve a firmar el secret solo (no expira).
77
77
  >
78
- > **Web (Supabase)**: la CLI genera `withAppleWebSignin = false` (iOS nativo funciona; la web necesita más). Para habilitar: en Supabase Authentication Providers Apple, agrega el **client secret** (un JWT firmado con la clave `.p8` + Services ID) y luego define `withAppleWebSignin = true` en `lib/core/config/features.dart`.
78
+ > **Web (Supabase)**: Apple en la web para Supabase **aún no viene activado en la app** (roadmap) el botón queda oculto en la web. En Supabase, Apple funciona solo en **iOS nativo** (ya configurado por `kasy new`). Consulta `ROADMAP.md`.
79
79
 
80
80
  ---
81
81
 
@@ -83,6 +83,8 @@ Requiere cuenta de [Apple Developer](https://developer.apple.com) (de pago).
83
83
 
84
84
  Requiere cuenta en [Meta for Developers](https://developers.facebook.com).
85
85
 
86
+ > **Atajo:** el comando `kasy facebook` automatiza escribir las credenciales (Info.plist, strings.xml y el proveedor en Firebase/Supabase) y abre el panel de Meta. Los pasos de abajo son lo que haces en Meta (manual, sin API).
87
+
86
88
  ### Paso 1 — Crear una app en Meta
87
89
 
88
90
  1. Abre [Meta for Developers → My Apps](https://developers.facebook.com/apps)
@@ -73,9 +73,9 @@ Requer conta [Apple Developer](https://developer.apple.com) (paga).
73
73
  >
74
74
  > **Android**: o botão Apple fica escondido por padrão (exige o fluxo do Services ID pago e agrega pouco no Android para um SaaS). Deixe escondido.
75
75
  >
76
- > **Web (Firebase)**: funciona depois dos Passos 1 a 4 acima (`withAppleWebSignin` vem `true`). A Return URL do Services ID (`firebaseapp.com/__/auth/handler`) cobre o fluxo de popup.
76
+ > **Web (Firebase)**: depois dos Passos 1 a 3, rode `kasy apple-web` — ele grava o Services ID + Team ID + Key ID + `.p8` no provedor Apple do Firebase e liga `withAppleWebSignin` (que nasce `false`). A Return URL do Services ID (`firebaseapp.com/__/auth/handler`) cobre o fluxo de popup. O Firebase re-assina o secret sozinho (não expira).
77
77
  >
78
- > **Web (Supabase)**: a CLI gera `withAppleWebSignin = false` (iOS nativo funciona; web precisa de mais). Para habilitar: em Supabase Authentication Providers Apple, adicione o **client secret** (um JWT assinado com a chave `.p8` + Services ID) e depois defina `withAppleWebSignin = true` em `lib/core/config/features.dart`.
78
+ > **Web (Supabase)**: Apple na web no Supabase **não vem ligado no app ainda** (roadmap) o botão fica escondido na web. No Supabase, o Apple funciona no **iOS nativo** ( configurado pelo `kasy new`). Veja `ROADMAP.md`.
79
79
 
80
80
  ---
81
81
 
@@ -83,6 +83,8 @@ Requer conta [Apple Developer](https://developer.apple.com) (paga).
83
83
 
84
84
  Requer conta no [Meta for Developers](https://developers.facebook.com).
85
85
 
86
+ > **Atalho:** o comando `kasy facebook` automatiza a parte de gravar as credenciais (Info.plist, strings.xml e o provedor no Firebase/Supabase) e abre o painel da Meta. Os passos abaixo são o que você faz na Meta (manual, sem API).
87
+
86
88
  ### Passo 1 — Criar app no Meta
87
89
 
88
90
  1. Abra [Meta for Developers → My Apps](https://developers.facebook.com/apps)
@@ -1 +1,56 @@
1
- {"firestore":{"rules":"firestore.rules","indexes":"firestore.indexes.json"},"storage":{"rules":"storage.rules"},"functions":[{"source":"functions","codebase":"default","ignore":["node_modules",".git","firebase-debug.log","firebase-debug.*.log"],"predeploy":["npm --prefix \"$RESOURCE_DIR\" run build"]}],"flutter":{"platforms":{"android":{"default":{"projectId":"fir-kit-8e56b","appId":"1:613770689965:android:f800aacb0091708b0f7524","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"fir-kit-8e56b","appId":"1:613770689965:ios:7cc1f4579cf759fd0f7524","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"fir-kit-8e56b","configurations":{"android":"1:613770689965:android:f800aacb0091708b0f7524","ios":"1:613770689965:ios:7cc1f4579cf759fd0f7524","web":"1:613770689965:web:e9347bac3b3eae5d0f7524"}}}}}}
1
+ {
2
+ "hosting": {
3
+ "public": "build/web",
4
+ "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
5
+ "rewrites": [
6
+ { "source": "**", "destination": "/index.html" }
7
+ ],
8
+ "headers": [
9
+ {
10
+ "source": "**/@(index.html|flutter_bootstrap.js|flutter_service_worker.js|main.dart.js|main.dart.wasm|main.dart.mjs|manifest.json|version.json)",
11
+ "headers": [
12
+ { "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
13
+ ]
14
+ }
15
+ ]
16
+ },
17
+ "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" },
18
+ "storage": { "rules": "storage.rules" },
19
+ "functions": [
20
+ {
21
+ "source": "functions",
22
+ "codebase": "default",
23
+ "ignore": ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"],
24
+ "predeploy": ["npm --prefix \"$RESOURCE_DIR\" run build"]
25
+ }
26
+ ],
27
+ "flutter": {
28
+ "platforms": {
29
+ "android": {
30
+ "default": {
31
+ "projectId": "fir-kit-8e56b",
32
+ "appId": "1:613770689965:android:f800aacb0091708b0f7524",
33
+ "fileOutput": "android/app/google-services.json"
34
+ }
35
+ },
36
+ "ios": {
37
+ "default": {
38
+ "projectId": "fir-kit-8e56b",
39
+ "appId": "1:613770689965:ios:7cc1f4579cf759fd0f7524",
40
+ "uploadDebugSymbols": false,
41
+ "fileOutput": "ios/Runner/GoogleService-Info.plist"
42
+ }
43
+ },
44
+ "dart": {
45
+ "lib/firebase_options.dart": {
46
+ "projectId": "fir-kit-8e56b",
47
+ "configurations": {
48
+ "android": "1:613770689965:android:f800aacb0091708b0f7524",
49
+ "ios": "1:613770689965:ios:7cc1f4579cf759fd0f7524",
50
+ "web": "1:613770689965:web:e9347bac3b3eae5d0f7524"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
@@ -5,7 +5,7 @@ import {notificationsApi} from "../notifications/notifications_api";
5
5
  import {NotificationPayload, SystemNotificationParams} from "../notifications/models/notification";
6
6
  import {NotificationTypes} from "../core/data/entities/notification_entity";
7
7
  import {Timestamp} from "firebase-admin/firestore";
8
- import {usersRepository, userDevicesRepository} from "../core/data/repositories/repositories";
8
+ import {usersRepository} from "../core/data/repositories/repositories";
9
9
  import * as functions from "firebase-functions/v1";
10
10
  import {onDocumentCreated} from "firebase-functions/v2/firestore";
11
11
  import {UserDeviceEntityData} from "../core/data/entities/user_device_entity";
@@ -52,9 +52,13 @@ export const onUserRegistration = functions
52
52
  });
53
53
 
54
54
  /**
55
- * Triggered when the user registers their first device.
56
- * Sends a localized welcome notification (saved to DB only, no push).
57
- * Fires on device creation so the device locale is available.
55
+ * Triggered when a device is registered.
56
+ * Sends a localized welcome notification ONCE per account (saved to DB only, no
57
+ * push). Fires on device creation so the device locale is available.
58
+ *
59
+ * The "once per account" guard is [UserRepository.claimWelcome], not a device
60
+ * count: a device re-registered after a logout/login cycle looks like a fresh
61
+ * first device, so counting devices re-sent the welcome on every re-login.
58
62
  */
59
63
  export const onFirstDeviceRegistered = onDocumentCreated(
60
64
  "users/{userId}/devices/{deviceId}",
@@ -62,8 +66,8 @@ export const onFirstDeviceRegistered = onDocumentCreated(
62
66
  const logger = new Logger("onFirstDeviceRegistered");
63
67
  const userId = event.params.userId;
64
68
  try {
65
- const allDevices = await userDevicesRepository.getDevices([userId]);
66
- if (allDevices.length > 1) return;
69
+ const shouldWelcome = await usersRepository.claimWelcome(userId);
70
+ if (!shouldWelcome) return;
67
71
 
68
72
  const deviceData = event.data?.data() as UserDeviceEntityData | undefined;
69
73
  // Flutter serializes as "extraData" (camelCase); Firestore schema uses "extra_data".
@@ -13,6 +13,7 @@ export interface SubscriptionEntityData {
13
13
  creation_date: Timestamp;
14
14
  last_activity: Timestamp;
15
15
  expiration_date?: Timestamp;
16
+ trial_end?: Timestamp;
16
17
  status: SubscriptionStatus;
17
18
  store: Stores,
18
19
  product_id: string;
@@ -30,6 +31,7 @@ export class SubscriptionEntity {
30
31
  creation_date,
31
32
  last_activity,
32
33
  expiration_date,
34
+ trial_end,
33
35
  status,
34
36
  store,
35
37
  product_id,
@@ -41,6 +43,7 @@ export class SubscriptionEntity {
41
43
  this.creation_date = creation_date;
42
44
  this.last_activity = last_activity;
43
45
  this.expiration_date = expiration_date;
46
+ this.trial_end = trial_end;
44
47
  this.status = status;
45
48
  this.store = store;
46
49
  this.product_id = product_id;
@@ -66,6 +69,7 @@ export class SubscriptionEntity {
66
69
  creation_date: subscription.creationDate,
67
70
  last_activity: subscription.lastUpdate,
68
71
  expiration_date: subscription.expirationDate,
72
+ trial_end: subscription.trialEnd,
69
73
  status: subscription.status,
70
74
  store: subscription.store,
71
75
  product_id: subscription.productId,
@@ -78,6 +82,7 @@ export class SubscriptionEntity {
78
82
  creationDate: this.creation_date,
79
83
  lastUpdate: this.last_activity,
80
84
  expirationDate: this.expiration_date,
85
+ trialEnd: this.trial_end,
81
86
  status: this.status,
82
87
  store: this.store,
83
88
  productId: this.product_id,