Complete subscription nudge#13
Conversation
Phase 0 (enablement, proven on device): - Add babel.config.js (babel-preset-expo -> react-native-worklets plugin) so Reanimated worklets actually run; add babel-preset-expo devDep. - Wrap the app root in GestureHandlerRootView. - Rule learned: never put a NativeWind className on an Animated.View (NativeWind v5 clobbers the style prop); animated views use inline styles, classes go on inner Views. Phase 1 (onboarding animation pass, zero new runtime deps): - New in-house motion helper (components/motion): FadeInUp entrance + PressableScale press feedback, over Reanimated. - Onboarding: keyed per-step Reanimated FadeIn page transitions; guide bubble + intro text spring/stagger in; goal chips cascade; primary CTAs press-scale. Kept AnimatedCounter / ProgressBar / SVG celebration. Further motion polish (Home/cards/sheets, swipe-to-cancel, gesture sheets, calendar-from-bottom) deferred to the UX-redesign phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Quick-add assumes today's date, so renewals are guessed wrong. Add a
dateAssumed flag (migration v2, additive) set on quick-add and cleared
when the user saves the edit form. Surface it as an animated, honest
completion loop:
- pulsing "Confirm date" dot on cards; Home nudge card ("N need a
renewal date -> Review"); detail banner -> edit with the date field
glowing; payment shown as optional; success celebration when the last
one is confirmed.
- Broaden onboarding category inference so fewer services fall back to
"Other" (category is auto-assigned, not nudged).
- New migration-runner + rowToSubscription mapping tests (established the
DB test harness via factory-mocked native modules).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolveNextRenewal rolled to the first occurrence strictly after today, so a charge landing on today was treated as already paid and skipped to the next cycle (e.g. Netflix started Jun 16 showed Aug 16 instead of Jul 16). Since we don't link to payment platforms we can't assume today's charge was taken, so roll forward only past dates strictly before today. Updated the test that encoded the old behavior + added a due-today case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AuthLayout hides the header and the screens had no back control, so a guest who tapped Sign in was trapped (no header, no iOS hardware back). Add a "Back" button that returns to the app (router.back, else replace /). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary by CodeRabbit
WalkthroughThe PR adds assumed renewal-date persistence and confirmation flows, preserves renewal dates due today, introduces Reanimated motion helpers and onboarding animations, adds authentication back navigation, improves onboarding brand categorization, and configures gesture handling. ChangesAssumed renewal date workflow
Motion and gesture updates
Authentication navigation
Onboarding brand categorization
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Onboarding
participant Home
participant SubscriptionDetail
participant SubscriptionFormModal
participant subscriptionsRepo
Onboarding->>subscriptionsRepo: Create subscription with dateAssumed=true
subscriptionsRepo-->>Home: Provide active subscriptions and assumedCount
Home->>SubscriptionDetail: Navigate to subscriptions
SubscriptionDetail->>SubscriptionFormModal: Open highlighted date editor
SubscriptionFormModal->>subscriptionsRepo: Submit dateAssumed=false
subscriptionsRepo-->>SubscriptionDetail: Return updated subscription
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/`(auth)/sign-in.tsx:
- Around line 150-151: Update the conditional back navigation accessibility
labels in app/(auth)/sign-in.tsx lines 150-151 and app/(auth)/sign-up.tsx lines
131-132 from “Back to app” to an accurate generic label such as “Go back” or
“Back”.
- Around line 144-159: The back-navigation control is unreachable in both
verification flows. Update the `needs_client_trust` branch in `sign-in.tsx` and
the email-verification branch in `sign-up.tsx` to render the same back control,
preserving its existing router behavior and accessibility properties; the
existing controls at app/(auth)/sign-in.tsx lines 144-159 and
app/(auth)/sign-up.tsx lines 125-140 should remain consistent.
In `@components/motion/index.tsx`:
- Around line 60-65: Update the onPressIn and onPressOut handlers to assign the
spring animations through scale.set(...) instead of directly mutating
scale.value. Preserve the existing spring targets and configuration in both
handlers.
- Around line 17-28: Update the Reanimated entrance animation used by the
exported FadeInUp component: import and use FadeInUp instead of FadeInDown in
its entering prop, while preserving the existing springify, damping, mass, and
delay configuration.
In `@components/PulsingDot.tsx`:
- Around line 22-35: Update the shared-value accesses in the PulsingDot
animation: replace the p.value assignment inside the useEffect animation setup
with p.set(...), and replace the p.value read in useAnimatedStyle with p.get().
Preserve the existing animation configuration and opacity/scale calculations.
In `@constants/onboardingBrands.ts`:
- Around line 145-151: Update inferCategory to safely handle missing or
undefined icon.keywords by defaulting to an empty array, and replace substring
matching in the CATEGORY_KEYWORDS check with case-insensitive whole-word
matching using word boundaries so short terms do not match inside unrelated
words. Preserve the existing category ordering and FALLBACK.category behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: db1c2fbe-f6cb-4937-8548-561a9d6ab5ef
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (20)
__tests__/billing.test.ts__tests__/migrations.test.tsapp/(auth)/sign-in.tsxapp/(auth)/sign-up.tsxapp/(tabs)/index.tsxapp/_layout.tsxapp/onboarding.tsxapp/subscriptions/[id].tsxbabel.config.jscomponents/PulsingDot.tsxcomponents/SubscriptionCard.tsxcomponents/SubscriptionFormModal.tsxcomponents/motion/index.tsxcomponents/onboarding/GuideBubble.tsxconstants/onboardingBrands.tsdb/migrations.tsdb/subscriptionsRepo.tslib/billing.tspackage.jsontype.d.ts
| <Pressable | ||
| className="mb-2 flex-row items-center gap-1 self-start py-2" | ||
| onPress={() => | ||
| router.canGoBack() ? router.back() : router.replace("/") | ||
| } | ||
| hitSlop={8} | ||
| accessibilityRole="button" | ||
| accessibilityLabel="Back to app" | ||
| > | ||
| <Text className="text-2xl font-sans-medium text-muted-foreground"> | ||
| ‹ | ||
| </Text> | ||
| <Text className="text-sm font-sans-semibold text-muted-foreground"> | ||
| Back | ||
| </Text> | ||
| </Pressable> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Expose back navigation from both verification screens.
The back button is placed only in each normal form branch, while both verification branches return before reaching it.
app/(auth)/sign-in.tsx#L144-L159: also render the control for theneeds_client_trustview.app/(auth)/sign-up.tsx#L125-L140: also render the control for the email-verification view.
📍 Affects 2 files
app/(auth)/sign-in.tsx#L144-L159(this comment)app/(auth)/sign-up.tsx#L125-L140
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/`(auth)/sign-in.tsx around lines 144 - 159, The back-navigation control
is unreachable in both verification flows. Update the `needs_client_trust`
branch in `sign-in.tsx` and the email-verification branch in `sign-up.tsx` to
render the same back control, preserving its existing router behavior and
accessibility properties; the existing controls at app/(auth)/sign-in.tsx lines
144-159 and app/(auth)/sign-up.tsx lines 125-140 should remain consistent.
| accessibilityRole="button" | ||
| accessibilityLabel="Back to app" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use an accurate accessibility label for conditional back navigation.
Because the handler may return to another authentication screen, "Back to app" does not describe the actual action.
app/(auth)/sign-in.tsx#L150-L151: change the label to"Go back"or"Back".app/(auth)/sign-up.tsx#L131-L132: change the label to"Go back"or"Back".
📍 Affects 2 files
app/(auth)/sign-in.tsx#L150-L151(this comment)app/(auth)/sign-up.tsx#L131-L132
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/`(auth)/sign-in.tsx around lines 150 - 151, Update the conditional back
navigation accessibility labels in app/(auth)/sign-in.tsx lines 150-151 and
app/(auth)/sign-up.tsx lines 131-132 from “Back to app” to an accurate generic
label such as “Go back” or “Back”.
| /** Springy fade + slide-up entrance. Plays on mount (re-key to replay). */ | ||
| export const FadeInUp = ({ | ||
| children, | ||
| delay = 0, | ||
| style, | ||
| }: { | ||
| children: ReactNode; | ||
| delay?: number; | ||
| style?: StyleProp<ViewStyle>; | ||
| }) => ( | ||
| <Animated.View | ||
| entering={FadeInDown.springify().damping(16).mass(0.6).delay(delay)} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fix the Reanimated entrance direction to match the component name.
The component is named FadeInUp and documented as a "slide-up entrance", but it uses FadeInDown from Reanimated (which typically fades in while sliding down from a higher vertical offset). To achieve a slide-up effect, use FadeInUp.
🐛 Proposed fix
Update the import at the top of the file to include FadeInUp instead of FadeInDown, and update the entering prop:
- entering={FadeInDown.springify().damping(16).mass(0.6).delay(delay)}
+ entering={FadeInUp.springify().damping(16).mass(0.6).delay(delay)}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/motion/index.tsx` around lines 17 - 28, Update the Reanimated
entrance animation used by the exported FadeInUp component: import and use
FadeInUp instead of FadeInDown in its entering prop, while preserving the
existing springify, damping, mass, and delay configuration.
| onPressIn={() => { | ||
| scale.value = withSpring(0.96, { damping: 15, stiffness: 220 }); | ||
| }} | ||
| onPressOut={() => { | ||
| scale.value = withSpring(1, { damping: 12, stiffness: 180 }); | ||
| }} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Prefer .set() over .value mutation for React Compiler compatibility.
Directly mutating .value on a shared value returned by a hook can be flagged as an invalid mutation by the React Compiler. Reanimated added .get() and .set() methods specifically to ensure compatibility. Using .set() here will future-proof the component against compiler errors.
♻️ Proposed refactor
onPressIn={() => {
- scale.value = withSpring(0.96, { damping: 15, stiffness: 220 });
+ scale.set(withSpring(0.96, { damping: 15, stiffness: 220 }));
}}
onPressOut={() => {
- scale.value = withSpring(1, { damping: 12, stiffness: 180 });
+ scale.set(withSpring(1, { damping: 12, stiffness: 180 }));
}}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onPressIn={() => { | |
| scale.value = withSpring(0.96, { damping: 15, stiffness: 220 }); | |
| }} | |
| onPressOut={() => { | |
| scale.value = withSpring(1, { damping: 12, stiffness: 180 }); | |
| }} | |
| onPressIn={() => { | |
| scale.set(withSpring(0.96, { damping: 15, stiffness: 220 })); | |
| }} | |
| onPressOut={() => { | |
| scale.set(withSpring(1, { damping: 12, stiffness: 180 })); | |
| }} |
🧰 Tools
🪛 React Doctor (0.7.6)
[warning] 61-61: This component misses React Compiler's automatic memoization & re-renders more than it should: This value cannot be modified. Rewrite the flagged code so the compiler can optimize it.
Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed.
If this is a Reanimated shared value, prefer its React Compiler-compatible .get() / .set() accessors over .value — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support
(immutability)
[warning] 64-64: This component misses React Compiler's automatic memoization & re-renders more than it should: This value cannot be modified. Rewrite the flagged code so the compiler can optimize it.
Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed.
If this is a Reanimated shared value, prefer its React Compiler-compatible .get() / .set() accessors over .value — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support
(immutability)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/motion/index.tsx` around lines 60 - 65, Update the onPressIn and
onPressOut handlers to assign the spring animations through scale.set(...)
instead of directly mutating scale.value. Preserve the existing spring targets
and configuration in both handlers.
Source: Linters/SAST tools
| const p = useSharedValue(0); | ||
|
|
||
| useEffect(() => { | ||
| p.value = withRepeat( | ||
| withTiming(1, { duration: 900, easing: Easing.inOut(Easing.quad) }), | ||
| -1, | ||
| true, | ||
| ); | ||
| }, [p]); | ||
|
|
||
| const style = useAnimatedStyle(() => ({ | ||
| opacity: 0.5 + p.value * 0.5, | ||
| transform: [{ scale: 0.85 + p.value * 0.35 }], | ||
| })); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect declared React Compiler and Babel configuration without executing it.
fd -a '^(package\.json|babel\.config\.(js|cjs|mjs))$' \
-x sh -c 'echo "### $1"; sed -n "1,240p" "$1"' sh {}
rg -n -C3 'babel-plugin-react-compiler|reactCompiler|compiler' \
--glob 'package.json' --glob 'babel.config.*' .Repository: saikumarbt/react-native-recurly
Length of output: 3054
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "### components/PulsingDot.tsx"
sed -n '1,200p' components/PulsingDot.tsx
echo
echo "### React Compiler-related references"
rg -n -C2 -i 'react compiler|reactcompiler|babel-plugin-react-compiler|compiler' \
--glob 'package.json' \
--glob 'babel.config.*' \
--glob 'metro.config.*' \
--glob 'app.json' \
--glob 'app.config.*' \
--glob '*.ts' \
--glob '*.tsx' \
.Repository: saikumarbt/react-native-recurly
Length of output: 1480
🌐 Web query:
Reanimated React Compiler shared value .value .get .set React Compiler Expo reactCompiler true
💡 Result:
When using the React Compiler (enabled via experiments.reactCompiler: true in Expo), you should avoid accessing or modifying the .value property of Reanimated shared values directly [1][2]. The React Compiler’s static analysis may incorrectly interpret direct mutations to the .value property as violations of the Rules of React, potentially leading to compilation errors or unexpected behavior [3][4][5]. To resolve this, React Native Reanimated provides a compiler-safe API using .get() and .set() methods [1][3][5]. Guidelines for using Shared Values with the React Compiler: 1. Use.get and.set: Instead of sv.value or sv.value = ..., use sv.get() to read the value and sv.set(newValue) to update it [1][2]. The .set() method also accepts a functional update, such as sv.set((prev) => prev + 1) [1][5]. 2. Avoid Render-Time Access: Regardless of the compiler, you must not read or modify shared values directly during a component's render [1]. All reads and writes should occur within event callbacks, useEffect, or useAnimatedStyle [1]. 3. Expo Configuration: Ensure your Expo project is configured correctly in app.json [6]: { "expo": { "experiments": { "reactCompiler": true } } } 4. Compatibility: Ensure you are using a modern version of React Native Reanimated that supports this API, as it was specifically introduced to ensure compatibility with the React Compiler's static analysis requirements [3][5]. Using this secondary API allows the React Compiler to safely handle shared values without flagging them as illegal mutations [3][5].
Citations:
- 1: https://docs.swmansion.com/react-native-reanimated/docs/3.x/core/useSharedValue/
- 2: https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/
- 3: feat: Add react-compiler safe API to mutables software-mansion/react-native-reanimated#6312
- 4: Reanimated, useSharedValue, and third-party defined types in the Compiler reactwg/react-compiler#14
- 5: https://github.com/software-mansion/react-native-reanimated/blob/8fd9302d/packages/react-native-reanimated/src/mutables.ts
- 6: https://docs.expo.dev/guides/react-compiler
🌐 Web query:
site:docs.swmansion.com react-native-reanimated React Compiler shared value get set .value
💡 Result:
In React Native Reanimated, shared values are typically accessed and modified via their.value property [1][2][3]. However, when working with the React Compiler, you should use the alternative get and set methods to ensure compliance with compiler standards [1]. Directly accessing or modifying the.value property is considered a side effect, which can violate the Rules of React when it occurs during the render phase [1]. The get and set methods provide a way to interact with shared values that aligns with modern React standards and the requirements of the React Compiler [1]. Key points regarding shared value usage: 1. Interaction Methods: While.value is the standard approach in many examples [2][3], use sv.get to read and sv.set(newValue) to update when compatibility with the React Compiler is required [1]. 2. Rules of React: Avoid reading from or modifying a shared value during a component's render phase [1]. Perform these operations only within event callbacks (e.g., onPress) or hooks like useAnimatedStyle and useEffect that execute outside of the render cycle [1]. 3. Complexity Management: For large objects or arrays, prefer the.modify method to update data efficiently rather than replacing the entire object [1]. By utilizing the get and set methods, you ensure that your Reanimated logic remains compatible with the optimizations and static analysis performed by the React Compiler [1].
Citations:
- 1: https://docs.swmansion.com/react-native-reanimated/docs/3.x/core/useSharedValue/
- 2: https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/your-first-animation/
- 3: https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary/
🌐 Web query:
"react-native-reanimated" "React Compiler" "get()" "set()" ".value"
💡 Result:
When using the React Compiler with React Native Reanimated, you should use the get and set methods on your shared values instead of directly accessing or modifying the.value property [1][2]. The React Compiler relies on immutability to optimize component rendering and often flags direct mutations of objects returned from hooks as violations of its rules [3]. Because assigning to.value is a property mutation that the Compiler's static analysis may not always correctly interpret as a setter invocation, Reanimated introduced the get and set API as a compiler-safe alternative [4][5]. Key details regarding this API: - Compatibility: The get and set methods are designed to be fully compliant with React Compiler standards [2]. - Usage: Use sv.get to read the current value and sv.set(newValue) to update it [2]. The set method also supports functional updates, allowing you to pass a callback that receives the current value, such as sv.set((prev) => prev + 1) [2][5]. - Best Practices: Even with these methods, you should avoid reading or modifying shared values directly during the component render phase, as this constitutes a side effect and violates the Rules of React [2]. All reads and writes should occur within appropriate hooks or callbacks (e.g., useAnimatedStyle, useEffect, or event handlers) [2]. While this API resolves Eslint warnings and Compiler errors regarding property mutation, be aware that the React Compiler is still evolving; in rare cases, you may encounter internal compiler "Invariant" errors related to shared value types, which are generally considered isolated compiler issues rather than problems with your Reanimated implementation [6].
Citations:
- 1: https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/
- 2: https://docs.swmansion.com/react-native-reanimated/docs/3.x/core/useSharedValue/
- 3: [Compiler Bug]: react native reanimated shared value mutation react/react#29640
- 4: https://github.com/software-mansion/react-native-reanimated/blob/5fbbbe0423ac899227539e5c10431cd931cdd021/packages/react-native-reanimated/src/mutables.ts
- 5: https://github.com/software-mansion/react-native-reanimated/blob/8fd9302d/packages/react-native-reanimated/src/mutables.ts
- 6: Compatibility with React compiler software-mansion/react-native-reanimated#7949
Switch these shared-value accesses to .get()/.set() — app.json enables reactCompiler, so p.value reads/writes in components/PulsingDot.tsx:22-35 can trip the compiler; replace them with p.set(...) and p.get().
🧰 Tools
🪛 React Doctor (0.7.6)
[warning] 25-25: This component misses React Compiler's automatic memoization & re-renders more than it should: This value cannot be modified. Rewrite the flagged code so the compiler can optimize it.
Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed.
If this is a Reanimated shared value, prefer its React Compiler-compatible .get() / .set() accessors over .value — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support
(immutability)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/PulsingDot.tsx` around lines 22 - 35, Update the shared-value
accesses in the PulsingDot animation: replace the p.value assignment inside the
useEffect animation setup with p.set(...), and replace the p.value read in
useAnimatedStyle with p.get(). Preserve the existing animation configuration and
opacity/scale calculations.
Source: Linters/SAST tools
| const inferCategory = (icon: { title: string; keywords: string[] }): string => { | ||
| const haystack = [icon.title, ...icon.keywords].join(" ").toLowerCase(); | ||
| for (const [category, needles] of CATEGORY_KEYWORDS) { | ||
| if (needles.some((n) => haystack.includes(n))) return category; | ||
| } | ||
| return FALLBACK.category; | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use word boundaries to prevent false-positive category matches.
Using .includes() for short keywords like "git", "llm", and "vpn" will match unrelated substrings (e.g., "git" matches "digital" and "logitech", while "llm" matches "Allmusic"). Using a regular expression with word boundaries (\b) ensures we only match whole words. Additionally, defaulting keywords to an empty array prevents crashes if the field is ever missing or undefined.
🐛 Proposed fix
-const inferCategory = (icon: { title: string; keywords: string[] }): string => {
- const haystack = [icon.title, ...icon.keywords].join(" ").toLowerCase();
+const inferCategory = (icon: { title: string; keywords?: string[] }): string => {
+ const haystack = [icon.title, ...(icon.keywords || [])].join(" ").toLowerCase();
for (const [category, needles] of CATEGORY_KEYWORDS) {
- if (needles.some((n) => haystack.includes(n))) return category;
+ if (needles.some((n) => new RegExp(`\\b${n}\\b`).test(haystack))) return category;
}
return FALLBACK.category;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const inferCategory = (icon: { title: string; keywords: string[] }): string => { | |
| const haystack = [icon.title, ...icon.keywords].join(" ").toLowerCase(); | |
| for (const [category, needles] of CATEGORY_KEYWORDS) { | |
| if (needles.some((n) => haystack.includes(n))) return category; | |
| } | |
| return FALLBACK.category; | |
| }; | |
| const inferCategory = (icon: { title: string; keywords?: string[] }): string => { | |
| const haystack = [icon.title, ...(icon.keywords || [])].join(" ").toLowerCase(); | |
| for (const [category, needles] of CATEGORY_KEYWORDS) { | |
| if (needles.some((n) => new RegExp(`\\b${n}\\b`).test(haystack))) return category; | |
| } | |
| return FALLBACK.category; | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@constants/onboardingBrands.ts` around lines 145 - 151, Update inferCategory
to safely handle missing or undefined icon.keywords by defaulting to an empty
array, and replace substring matching in the CATEGORY_KEYWORDS check with
case-insensitive whole-word matching using word boundaries so short terms do not
match inside unrelated words. Preserve the existing category ordering and
FALLBACK.category behavior.
No description provided.