Skip to content

Complete subscription nudge#13

Merged
saikumarbt merged 4 commits into
mainfrom
complete-subscription-nudge
Jul 16, 2026
Merged

Complete subscription nudge#13
saikumarbt merged 4 commits into
mainfrom
complete-subscription-nudge

Conversation

@saikumarbt

Copy link
Copy Markdown
Owner

No description provided.

saikumarbt and others added 4 commits July 15, 2026 17:07
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>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added prompts to confirm assumed subscription renewal dates.
    • Added visual confirmation indicators and success feedback after dates are updated.
    • Added back navigation to sign-in and sign-up screens.
    • Enhanced onboarding with smoother animations and improved brand categorization.
  • Bug Fixes

    • Renewal dates occurring today now remain scheduled for today instead of moving forward.
  • Tests

    • Added coverage for billing behavior, database migrations, and subscription date mapping.

Walkthrough

The 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.

Changes

Assumed renewal date workflow

Layer / File(s) Summary
Renewal date persistence and migration
type.d.ts, db/migrations.ts, db/subscriptionsRepo.ts, __tests__/migrations.test.ts
Subscriptions now persist dateAssumed, migrate the date_assumed column, map SQLite values to booleans, and test migration and mapping behavior.
Assumed date creation and renewal calculation
app/onboarding.tsx, lib/billing.ts, __tests__/billing.test.ts
Quick-added subscriptions mark dates as assumed, while renewal calculation keeps dates that fall exactly on today.
Renewal confirmation surfaces
app/(tabs)/index.tsx, components/SubscriptionCard.tsx, components/PulsingDot.tsx, app/subscriptions/[id].tsx, components/SubscriptionFormModal.tsx
Home, cards, detail views, and the edit modal prompt for assumed dates and clear the flag after confirmation.

Motion and gesture updates

Layer / File(s) Summary
Animation runtime setup
package.json, babel.config.js, app/_layout.tsx
Expo Babel configuration and gesture-root wrapping are added for the updated animation setup.
Reusable motion components and onboarding animation
components/motion/index.tsx, app/onboarding.tsx, components/onboarding/GuideBubble.tsx
Reanimated fade and press-scale helpers replace onboarding’s imperative animations and add animated step, option, CTA, and guide-bubble transitions.

Authentication navigation

Layer / File(s) Summary
Authentication back navigation
app/(auth)/sign-in.tsx, app/(auth)/sign-up.tsx
Sign-in and sign-up forms add accessible Back controls with route fallback behavior.

Onboarding brand categorization

Layer / File(s) Summary
Keyword-based brand categories
constants/onboardingBrands.ts
Missing preset categories are inferred from brand titles and keywords, while price fallback handling remains separate.

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
Loading

Possibly related PRs

Poem

A bunny hops through dates today,
No renewal skips away.
A pulsing dot points out the clue,
“Confirm this date—it waits for you!”
With springs and fades, the screens now glow.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No description was provided, so the PR context is too sparse to assess beyond the code diff. Add a brief description of the feature and key behavior changes so reviewers can confirm the intent.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding the subscription nudge.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch complete-subscription-nudge

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e5ecdac and a09ad29.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • __tests__/billing.test.ts
  • __tests__/migrations.test.ts
  • app/(auth)/sign-in.tsx
  • app/(auth)/sign-up.tsx
  • app/(tabs)/index.tsx
  • app/_layout.tsx
  • app/onboarding.tsx
  • app/subscriptions/[id].tsx
  • babel.config.js
  • components/PulsingDot.tsx
  • components/SubscriptionCard.tsx
  • components/SubscriptionFormModal.tsx
  • components/motion/index.tsx
  • components/onboarding/GuideBubble.tsx
  • constants/onboardingBrands.ts
  • db/migrations.ts
  • db/subscriptionsRepo.ts
  • lib/billing.ts
  • package.json
  • type.d.ts

Comment thread app/(auth)/sign-in.tsx
Comment on lines +144 to +159
<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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 the needs_client_trust view.
  • 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.

Comment thread app/(auth)/sign-in.tsx
Comment on lines +150 to +151
accessibilityRole="button"
accessibilityLabel="Back to app"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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”.

Comment on lines +17 to +28
/** 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)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +60 to +65
onPressIn={() => {
scale.value = withSpring(0.96, { damping: 15, stiffness: 220 });
}}
onPressOut={() => {
scale.value = withSpring(1, { damping: 12, stiffness: 180 });
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
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 .valuehttps://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 .valuehttps://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

Comment thread components/PulsingDot.tsx
Comment on lines +22 to +35
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 }],
}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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:


🌐 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:


🌐 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:


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 .valuehttps://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

Comment on lines +145 to +151
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;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

@saikumarbt
saikumarbt merged commit 87fe8e8 into main Jul 16, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant