Skip to content

Fix visibility and refresh#15

Merged
saikumarbt merged 14 commits into
mainfrom
fix-visibility-and-refresh
Jul 20, 2026
Merged

Fix visibility and refresh#15
saikumarbt merged 14 commits into
mainfrom
fix-visibility-and-refresh

Conversation

@saikumarbt

Copy link
Copy Markdown
Owner

No description provided.

saikumarbt and others added 7 commits July 16, 2026 15:21
- "Delete this one" used solid bg-destructive + text-white, but solid
  bg-destructive doesn't render under NativeWind v5-preview (only the /10
  opacity variant does), so the white text was invisible. Use an explicit
  inline red background + white text.
- Home nudge counts (review / duplicates / confirm-date) and the
  Subscriptions list didn't reflect actions taken on the pushed detail
  screen until relaunch. Expose refresh() from the context and re-pull on
  screen focus (useFocusEffect) so counts update at runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A same-name sub can be legitimate (paying for a partner's or child's
subscription), so the detail banner now offers a real "Keep it" beside
"Delete this one":
- "Keep it" sets a persisted duplicate_acknowledged flag (migration v4),
  which excludes the sub from duplicate detection — the banner, card
  chip, and Home nudge all stop flagging it.
- Copy advises renaming (e.g. "Netflix for Sally") so identical rows
  don't confuse.
- Relabel the delete confirmation's cancel from "Keep it" to "Cancel" so
  it isn't mistaken for the new acknowledge action.
Confirming a renewal advanced confirmedThrough but not the stored
renewalDate, so the detail's "Next renewal" stayed on the just-confirmed
(today's) date. Also advance renewalDate to the following cycle.
Per the agreed journey: the add-time duplicate confirm now makes
"Rename it" the primary action and "Add it anyway" secondary. Choosing
"Add it anyway" creates the sub with duplicateAcknowledged = true, so a
decision the user made isn't re-flagged as a possible duplicate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- SubscriptionCard: soft, unique per-brand panel tint (colour-coding for
  scan/recall) plus a coloured left edge on cards needing attention
  (duplicate > confirm-date > renewed?), so warnings are findable in the list.
- Detail hero now uses the same cardTint as the list card for consistency.
- Home "Your subscriptions" card: overlapping brand-icon stack previews
  content and invites the tap; PressableScale for tactile feedback.
- lib/brand: add tintColor() + cardTint() helpers.
- Startup: hide the splash as soon as fonts load instead of waiting on Clerk
  auth (guest-first) — removes the visible menu delay on launch/reload.
- Preload tab-bar icon assets (expo-asset) and gate the splash-hide on them
  alongside fonts, so the whole bottom bar paints at once. Fixes the "pop-in"
  where only the active Home pill showed first and the other white glyphs
  appeared a frame later.
- Rewrite the duplicate-warning copy (detail banner + add modal) to drop the
  presumptuous "a partner's or child's / for Sally" phrasing. Now frames the
  legitimate case as "a different plan or account" with a neutral rename
  example ("Netflix – work") — relevant to every user, not just families.
Celebrate NOT spending — the emotional payoff competitors skip. When a
subscription is cancelled (from the detail action or the renewal check-in's
"I cancelled"), a mint confetti burst + success haptic reveals what the user
just saved, counting up the yearly figure.

- components/Confetti.tsx: in-house falling-confetti burst on Reanimated
  (plain Animated.Views, no new animation library — per the animation-stack
  decision).
- components/CancelCelebration.tsx: overlay with the count-up saving and a
  self-contained, inline-styled share card captured via react-native-view-shot
  and shared through expo-sharing (RN Share text fallback). This is the plan's
  day-one viral loop — every cancellation becomes a shareable stat.
- Detail screen: cancelAndCelebrate() snapshots the monthly saving before the
  status flips, wired into both cancel paths.
- Adds expo-sharing + react-native-view-shot.
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds persistent duplicate acknowledgement, cancellation celebrations with sharing and confetti, focus-triggered subscription refreshes, subscription list filtering, expanded onboarding brands, derived card styling, startup icon preloading, and removal of per-subscription currency fields.

Changes

Subscription experience

Layer / File(s) Summary
Duplicate acknowledgement persistence
db/migrations.ts, db/subscriptionsRepo.ts, type.d.ts, lib/duplicates.ts, __tests__/migrations.test.ts
Adds the duplicate_acknowledged schema field, maps and persists duplicateAcknowledged, excludes acknowledged subscriptions from duplicate detection, and updates migration tests.
Duplicate actions and subscription detail flow
components/SubscriptionFormModal.tsx, app/subscriptions/[id].tsx
Records “Add it anyway” acknowledgement, adds keep/delete duplicate actions, and advances renewal dates when renewal is confirmed.
Cancellation celebration and sharing
components/CancelCelebration.tsx, components/Confetti.tsx, app/subscriptions/[id].tsx, package.json
Shows animated savings, confetti, haptic feedback, and PNG sharing after cancellation, with native-sharing fallback behavior.
Focus refresh and subscription presentation
context/SubscriptionsContext.tsx, app/(tabs)/index.tsx, components/SubscriptionCard.tsx, lib/brand.ts
Exposes refresh through context, refreshes the Home screen on focus, displays subscription icons, and applies derived card tints and warning borders.
Subscription list filtering and sorting
app/(tabs)/subscriptions.tsx
Adds category filters, cost and renewal sorting, focus refresh, filter chips, and filter-aware empty-state text.
Onboarding brand catalog and picker
constants/onboardingBrands.ts, constants/brandIcons.ts, scripts/generate-brand-icons.mjs, app/onboarding.tsx
Expands brand presets and icons, defines category ordering, and adds searchable grouped onboarding brand selection.
Startup readiness and currency cleanup
app/_layout.tsx, app.json, app/onboarding.tsx, type.d.ts, app/(tabs)/index.tsx
Preloads tab icons, gates initial rendering on fonts and icons, and removes per-subscription currency fields from types and creation flows.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SubscriptionDetail
  participant CancelCelebration
  participant Confetti
  participant Sharing
  SubscriptionDetail->>CancelCelebration: pass cancellation savings
  CancelCelebration->>Confetti: render animated confetti
  CancelCelebration->>Sharing: capture and share celebration card
Loading

Possibly related PRs

Poem

A bunny taps “Keep it” with a grin,
While savings confetti tumbles in.
Fresh brands bloom, cards softly glow,
Focus brings the latest flow.
Share the cheer, then hop away! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No meaningful PR description was provided, so there is nothing substantive to assess. Add a brief description of the main changes and intent, such as duplicate handling, refresh behavior, and UI updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main themes of visibility and refresh changes in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-visibility-and-refresh

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 `@__tests__/migrations.test.ts`:
- Around line 53-56: Strengthen the migration test around MIGRATIONS version 4
to assert the duplicate_acknowledged column includes INTEGER, NOT NULL, and
DEFAULT 0, not just the column name. Add rowToSubscription assertions covering
duplicate_acknowledged values 0 → duplicateAcknowledged false and 1 → true,
using baseRow and an overridden copy.

In `@app/_layout.tsx`:
- Around line 98-122: Store notification deep-link routes in a pendingDeepLink
state from the addNotificationResponseReceivedListener instead of navigating
immediately. Add an effect that, once ready is true and a route is pending,
pushes it through router and clears the pending state; preserve listener cleanup
and subscriptionId validation.

In `@app/`(tabs)/index.tsx:
- Around line 384-394: Update the avatar wrapper View in the subscription
rendering block to replace the hardcoded borderColor value with the existing
semantic card-border styling mechanism, such as the NativeWind border-card
class, so the border follows dark mode and dynamic themes while preserving the
current width and radius.

In `@app/subscriptions/`[id].tsx:
- Around line 534-538: Update the CancelCelebration invocation to pass the
subscription’s currency for the monthlySaved amount instead of baseCurrency. Use
the currency field associated with subscription.price, while preserving the
existing celebration visibility, name, and savings value handling.

In `@components/Confetti.tsx`:
- Around line 49-54: Move the progress animation assignment in ConfettiPiece
into a useEffect with an appropriate dependency array so it starts only when the
component mounts. Preserve the existing piece.delay, duration, and easing
configuration, and keep the shared value declaration outside the effect.

In `@components/SubscriptionFormModal.tsx`:
- Around line 186-190: Update the subscription edit flow around
duplicateAcknowledged in SubscriptionFormModal so a subscription name change
clears the existing acknowledgement before duplicate evaluation; only preserve
or set duplicateAcknowledged when the name remains unchanged or the current
duplicate warning is explicitly acknowledged.
🪄 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: d5e82319-532d-4acb-80e0-c52eff6b87d3

📥 Commits

Reviewing files that changed from the base of the PR and between ed4bb9a and ee763de.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • __tests__/migrations.test.ts
  • app.json
  • app/(tabs)/index.tsx
  • app/(tabs)/subscriptions.tsx
  • app/_layout.tsx
  • app/subscriptions/[id].tsx
  • components/CancelCelebration.tsx
  • components/Confetti.tsx
  • components/SubscriptionCard.tsx
  • components/SubscriptionFormModal.tsx
  • context/SubscriptionsContext.tsx
  • db/migrations.ts
  • db/subscriptionsRepo.ts
  • lib/brand.ts
  • lib/duplicates.ts
  • package.json
  • type.d.ts

Comment thread __tests__/migrations.test.ts
Comment thread app/_layout.tsx
Comment thread app/(tabs)/index.tsx
Comment on lines +534 to +538
<CancelCelebration
visible={celebration !== null}
name={celebration?.name ?? ""}
monthlySaved={celebration?.monthlySaved ?? 0}
currency={baseCurrency}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use the subscription’s currency for the savings amount.

monthlySaved is derived from subscription.price, so labeling it with baseCurrency misstates savings when the stored subscription currency differs.

       monthlySaved={celebration?.monthlySaved ?? 0}
-      currency={baseCurrency}
+      currency={subscription.currency ?? baseCurrency}
📝 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
<CancelCelebration
visible={celebration !== null}
name={celebration?.name ?? ""}
monthlySaved={celebration?.monthlySaved ?? 0}
currency={baseCurrency}
<CancelCelebration
visible={celebration !== null}
name={celebration?.name ?? ""}
monthlySaved={celebration?.monthlySaved ?? 0}
currency={subscription.currency ?? baseCurrency}
🤖 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/subscriptions/`[id].tsx around lines 534 - 538, Update the
CancelCelebration invocation to pass the subscription’s currency for the
monthlySaved amount instead of baseCurrency. Use the currency field associated
with subscription.price, while preserving the existing celebration visibility,
name, and savings value handling.

Comment thread components/Confetti.tsx Outdated
Comment thread components/SubscriptionFormModal.tsx
…ustness

- _layout: buffer notification deep-links into pending state and flush once the
  navigator is mounted (ready), instead of pushing immediately — a cold start
  launched by a reminder tap could push before the Stack mounted.
- SubscriptionFormModal: clear a stale duplicateAcknowledged when an edit
  renames the sub (the ack was granted for the old name); preserve it otherwise.
- Confetti: start the fall animation in a mount effect, not during render.
- Home avatar stack: use the border-card token instead of a hardcoded hex so
  the notch separator follows the card color.
- migrations test: assert the full duplicate_acknowledged column shape
  (INTEGER NOT NULL DEFAULT 0) + rowToSubscription 0/1 mapping.

Skipped: passing subscription.currency to the cancel celebration — the app
formats all amounts with the single app-wide baseCurrency by design (no FX);
per-sub currency is never used for display and equals baseCurrency anyway.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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: 1

🤖 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 `@components/Confetti.tsx`:
- Around line 53-57: Update the effect in Confetti’s animation setup to replace
direct progress.value assignment with the Reanimated shared-value set() method,
passing the existing withDelay animation unchanged. Preserve the current delay,
duration, and easing 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: 65acf57a-aaec-4e2c-95f7-aee5189ba7e0

📥 Commits

Reviewing files that changed from the base of the PR and between ee763de and e9238eb.

📒 Files selected for processing (5)
  • __tests__/migrations.test.ts
  • app/(tabs)/index.tsx
  • app/_layout.tsx
  • components/Confetti.tsx
  • components/SubscriptionFormModal.tsx

Comment thread components/Confetti.tsx
Comment on lines +53 to +57
useEffect(() => {
progress.value = withDelay(
piece.delay,
withTiming(1, { duration, easing: Easing.out(Easing.quad) }),
);

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

Use .set() instead of mutating .value for React Compiler compatibility.

Modifying the .value property of a shared value directly can be flagged by the React Compiler as an illegal mutation. Use the .set() method provided by Reanimated for React Compiler compatibility.

♻️ Proposed refactor
   useEffect(() => {
-    progress.value = withDelay(
-      piece.delay,
-      withTiming(1, { duration, easing: Easing.out(Easing.quad) }),
-    );
+    progress.set(
+      withDelay(
+        piece.delay,
+        withTiming(1, { duration, easing: Easing.out(Easing.quad) }),
+      )
+    );
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);
📝 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
useEffect(() => {
progress.value = withDelay(
piece.delay,
withTiming(1, { duration, easing: Easing.out(Easing.quad) }),
);
useEffect(() => {
progress.set(
withDelay(
piece.delay,
withTiming(1, { duration, easing: Easing.out(Easing.quad) }),
)
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
🧰 Tools
🪛 React Doctor (0.7.6)

[warning] 54-54: 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/Confetti.tsx` around lines 53 - 57, Update the effect in
Confetti’s animation setup to replace direct progress.value assignment with the
Reanimated shared-value set() method, passing the existing withDelay animation
unchanged. Preserve the current delay, duration, and easing behavior.

Source: Linters/SAST tools

We format every amount with the single app-wide base currency (no FX), so a
per-sub currency was dead weight and a source of ambiguity. Remove it from the
Subscription/Draft/UpcomingSubscription types and every read/write:

- type.d.ts: drop `currency` from Subscription (cascades to SubscriptionDraft)
  and UpcomingSubscription.
- subscriptionsRepo: drop from SubscriptionRow, rowToSubscription, the INSERT,
  and PATCH_COLUMNS. The DB column is kept dormant (NOT NULL DEFAULT 'USD') so
  no destructive migration runs on local-first user data; it can be revived if
  per-currency ever returns.
- Remove the now-dead `currency: baseCurrency` from the add/edit form and
  onboarding bulk-add, and the unused UpcomingSubscription.currency in Home.
- migrations test fixture updated to the new row shape.
The plan's "biggest daily-use win we currently lack" — the list only had
search. Add lightweight inline controls on the Subscriptions tab:

- Sort by Renewal (soonest first), Cost (monthly-equivalent so cycles compare
  fairly), or Name; tapping the active sort flips direction (↑/↓). Paused and
  cancelled subs have no active renewal, so they sink to the bottom of the
  renewal sort — matching the card, which only shows a countdown when active.
- Multi-select category chips (derived from the data; shown only when there's
  more than one category) with a Clear affordance. Composes with search.

Chips reuse the card/pill styling and the reliable bg-accent/10 active state.
The pick step showed the whole catalog as one flat 60-tile wall. Now:

- Search box filters brands by name.
- Brands render under ordered category headings (Entertainment, Gaming,
  Music, AI Tools, ... Bills & Utilities) so users scan to what they pay for.
- Categorize every catalogued brand so the grouped view is tidy — no "Other"
  bucket (added categories for the Lobe AI tools + Trello/Asana/Netlify/etc.
  that previously inferred to Other).

Expand the catalog with 37 real logos (only brands that still ship a
simple-icons logo — Disney+/Hulu/Prime/Amazon/Canva/Adobe were dropped by
simple-icons and are intentionally skipped rather than shown as monograms):

- Health & Fitness: Peloton, Strava, Fitbit, Headspace
- Food & Delivery: HelloFresh, DoorDash, Uber Eats, Deliveroo, Instacart,
  Just Eat, Zomato, Swiggy
- Streaming: Apple TV, Plex, MUBI, Fubo
- News & Reading: New York Times, The Guardian
- Music: Pandora, iHeartRadio
- Cloud & security: LastPass, Bitwarden, Backblaze, MEGA, Surfshark, Mullvad,
  Box, Proton
- Gaming: EA, Ubisoft, NVIDIA (GeForce Now), Humble Bundle (+ moved Steam,
  PlayStation, Roblox, Epic Games into the new Gaming category)
- Shopping: Target
- Bills & Utilities: Verizon, Vodafone, O2, Spectrum

Catalog: 77 -> 114 brands. Regenerated via generate-brand-icons.mjs.

@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: 3

🤖 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/`(tabs)/subscriptions.tsx:
- Around line 154-156: Reconcile selectedCategories with the current categories
before applying the subscription filter, removing any values no longer present
while preserving valid selections. Update the state used by the filtering and
filter-row visibility logic so that when categories shrink to one or fewer,
stale selections cannot hide the list without an available Clear control; anchor
the change near the selectedCategories state/effect and the inCategory
calculation.
- Around line 78-84: The Chip Pressable currently lacks accessibility semantics
for its toggle state. Update the Pressable in the Chip component to expose a
button role and derive its selected state from the existing active value,
preserving the current press behavior and styling.
- Around line 260-263: Update the ListEmptyComponent in the subscriptions screen
to distinguish between an account with no subscriptions and an empty result
caused by active search or category filters. Show an unfiltered empty-state
message when no subscriptions exist and no filters are applied, while preserving
the existing filtered-results message when filters are active.
🪄 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: 4b9d8654-ee50-4db9-8e88-9173b1b6b6e8

📥 Commits

Reviewing files that changed from the base of the PR and between c38a68c and af80c0f.

📒 Files selected for processing (5)
  • app/(tabs)/subscriptions.tsx
  • app/onboarding.tsx
  • constants/brandIcons.ts
  • constants/onboardingBrands.ts
  • scripts/generate-brand-icons.mjs

Comment on lines +78 to +84
<Pressable
onPress={onPress}
className={clsx(
"rounded-full border px-3.5 py-1.5",
active ? "border-accent bg-accent/10" : "border-border bg-card",
)}
>

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

Expose Chip toggle state to assistive tech.

The Pressable announces its label but not that it's a button or whether it's selected, so screen-reader users can't tell active sort/filter pills from inactive ones. Add a role and selected state.

♿ Proposed fix
   <Pressable
     onPress={onPress}
+    accessibilityRole="button"
+    accessibilityState={{ selected: active }}
     className={clsx(
       "rounded-full border px-3.5 py-1.5",
       active ? "border-accent bg-accent/10" : "border-border bg-card",
     )}
   >
📝 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
<Pressable
onPress={onPress}
className={clsx(
"rounded-full border px-3.5 py-1.5",
active ? "border-accent bg-accent/10" : "border-border bg-card",
)}
>
<Pressable
onPress={onPress}
accessibilityRole="button"
accessibilityState={{ selected: active }}
className={clsx(
"rounded-full border px-3.5 py-1.5",
active ? "border-accent bg-accent/10" : "border-border bg-card",
)}
>
🤖 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/`(tabs)/subscriptions.tsx around lines 78 - 84, The Chip Pressable
currently lacks accessibility semantics for its toggle state. Update the
Pressable in the Chip component to expose a button role and derive its selected
state from the existing active value, preserving the current press behavior and
styling.

Comment on lines +154 to +156
const inCategory =
selectedCategories.length === 0 ||
selectedCategories.includes(categoryOf(subscription));

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

Stale category selection can strand the list with no way to clear it.

selectedCategories is never reconciled against the currently available categories. If subscriptions change so that a previously selected category no longer exists (e.g. its last sub is deleted) and categories.length drops to <= 1, the filter row — including the "Clear" chip — is hidden at Line 218, yet the stale selection still filters at Lines 154-156. The result is an empty list the user can't recover from without other actions.

Prune selections against available categories, e.g.:

🔧 Proposed fix
   const categories = useMemo(() => {
     const set = new Set(subscriptions.map(categoryOf));
     return Array.from(set).sort((a, b) => a.localeCompare(b));
   }, [subscriptions]);
+
+  // Drop any selected categories that no longer exist so filters can't strand the list.
+  useEffect(() => {
+    setSelectedCategories((prev) => {
+      const next = prev.filter((c) => categories.includes(c));
+      return next.length === prev.length ? prev : next;
+    });
+  }, [categories]);

Also applies to: 218-244

🤖 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/`(tabs)/subscriptions.tsx around lines 154 - 156, Reconcile
selectedCategories with the current categories before applying the subscription
filter, removing any values no longer present while preserving valid selections.
Update the state used by the filtering and filter-row visibility logic so that
when categories shrink to one or fewer, stale selections cannot hide the list
without an available Clear control; anchor the change near the
selectedCategories state/effect and the inCategory calculation.

Comment on lines 260 to 263
ListEmptyComponent={
<Text className="home-empty-state">
No subscriptions match your search.
No subscriptions match your filters.
</Text>

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

Empty-state text is misleading when there are no subscriptions or no active filters.

This message renders for every empty result, including a fresh/reset account with zero subscriptions and no search or category filter applied, where "match your filters" is inaccurate. Consider distinguishing the truly-empty case from the filtered-out case.

💬 Proposed fix
           ListEmptyComponent={
             <Text className="home-empty-state">
-              No subscriptions match your filters.
+              {subscriptions.length === 0
+                ? "No subscriptions yet."
+                : "No subscriptions match your filters."}
             </Text>
           }
📝 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
ListEmptyComponent={
<Text className="home-empty-state">
No subscriptions match your search.
No subscriptions match your filters.
</Text>
ListEmptyComponent={
<Text className="home-empty-state">
{subscriptions.length === 0
? "No subscriptions yet."
: "No subscriptions match your filters."}
</Text>
}
🤖 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/`(tabs)/subscriptions.tsx around lines 260 - 263, Update the
ListEmptyComponent in the subscriptions screen to distinguish between an account
with no subscriptions and an empty result caused by active search or category
filters. Show an unfiltered empty-state message when no subscriptions exist and
no filters are applied, while preserving the existing filtered-results message
when filters are active.

Add the brands users asked for, using real logos where any available source
ships one and lucide glyph tiles where none does.

Real logos:
- simple-icons: Google Cloud, Hostinger, Namecheap, GoDaddy, Vultr, Render,
  Railway, Supabase, Firebase, MongoDB, PlanetScale (Developer Tools).
- Lobe: AWS, Azure (simple-icons dropped both over trademark; Lobe still
  ships them).

Generic day-to-day category tiles (lucide glyphs, no brand identity) so
household bills are one tap: Rent / Housing, Utilities, Mobile / Phone,
Internet, Insurance, Gym, Transport, Streaming.

Big brands neither source has a logo for get a themed glyph stand-in for this
release (distinct tile colours keep them apart): Disney+, Hulu, Prime Video,
Peacock, Canva, Adobe, Xbox, Nintendo Switch, Amazon Prime.

lucide-static added as a build-time devDependency (same pattern as
simple-icons/lobe). Catalog: 114 -> 144; still zero "Other" bucket. Keyword
aliases kept specific to avoid greedy substring matches in the name resolver.
The "+" add-subscription sheet listed every brand in raw catalog order (looked
random), while onboarding groups them by category with search. Make them match:

- Extract groupOnboardingBrands() as the single source of truth for
  ordering/grouping; onboarding now uses it instead of inline logic.
- New BrandPickerSheet renders the same category-grouped, searchable tile grid;
  the add form's "Browse brands" opens it (single-select) instead of the flat
  PickerSheet list.
- Align the form's category chips + colors with the full onboarding taxonomy
  (Gaming, Health & Fitness, Food & Delivery, News & Reading, Shopping,
  Bills & Utilities) so categories are consistent everywhere.
Fixes a trial being tracked as if it renewed one cycle after the start date.
A free trial's first charge lands when it converts (the trial-end date), and
the user may or may not convert — so:

- billing: firstChargeDate() anchors a trial's renewal to its end date;
  trialPendingConversion() flags a trial that has reached its end; the form now
  sets renewalDate from the trial end (not start + one cycle) and leaves
  confirmedThrough unset until conversion.
- Detail: at/after trial end, a conversion check-in — "Did your trial convert
  to paid?" → Yes keeps it (starts the paid cycle from the trial end), No runs
  the cancel + savings celebration, Not yet snoozes. The renewal check-in is
  suppressed while on trial.
- Card: "Free trial" badge + "Trial ends in N days" meta; a pulsing "Convert?"
  once ended.
- Reminders: during a trial only the T-2/T-0 trial reminders fire (renewal
  reminders + generic check-in suppressed so they don't cluster on one day);
  T-0 copy nudges the keep-or-cancel decision.
- Reconciler skips trials (no auto-assumed renewals before conversion).
- Spend: free trials excluded from the monthly total (Home + Insights) until
  they convert; countsTowardSpend() + updated Insights disclosure.
- Tests: firstChargeDate / trialPendingConversion / countsTowardSpend, and
  trials suppress renewal reminders. 60 pass.
@saikumarbt
saikumarbt merged commit 2fc84d1 into main Jul 20, 2026
1 check passed
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