Skip to content

Conversation

@rvab
Copy link
Contributor

@rvab rvab commented Nov 11, 2025

Clickup

app.clickup.com

Code Coverage

Please add code coverage here

UI Preview

Please add screenshots for UI changes

Summary by CodeRabbit

  • Refactor
    • Reconciliation logic is now gated by a feature flag and evaluated asynchronously for safer, controlled rollouts.
    • Number input component state management improved so disabled/ enabled state reliably syncs with its internal control.

@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

Walkthrough

Reconciliation check in the add-edit-expense page is now gated by a LaunchDarkly feature flag and executed asynchronously; the fy-number component now uses an Angular effect (registered after fc creation in ngOnInit) to synchronize its internal form control enabled/disabled state with the disabled input.

Changes

Cohort / File(s) Summary
Feature-flagged reconciliation
src/app/fyle/add-edit-expense/add-edit-expense.page.ts
Replaces a direct synchronous reconciliation check with an async LaunchDarkly variation fetch for reconciliation_beta. Reconciliation-related disables (e.g., dateOfSpend, currencyObj) are applied only when the flag is enabled; otherwise those steps are skipped. Optional chaining used for control access.
Reactive disabled sync (fy-number)
src/app/shared/components/fy-number/fy-number.component.ts
Imports effect from @angular/core. After creating the internal form control (fc) in ngOnInit, adds an effect that watches the disabled signal and updates isDisabled and the enabled/disabled state of fc accordingly.

Sequence Diagram(s)

sequenceDiagram
  participant UI as UI / Component
  participant LD as LaunchDarkly
  participant Logic as AddEditExpense Logic

  Note over UI,Logic: New flow when saving or loading expense
  UI->>Logic: request reconciliation check (async)
  Logic->>LD: fetch variation "reconciliation_beta"
  LD-->>Logic: variation (true / false)
  alt variation == true
    Logic->>Logic: apply reconciliation handling (disable dateOfSpend, currencyObj)
  else variation == false
    Logic-->>Logic: skip reconciliation handling
  end
  Logic-->>UI: continue flow
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas to focus:
    • Correctness of async LaunchDarkly call and handling of its promise/observable lifecycle
    • Proper use of optional chaining and null-safety when accessing controls
    • Ensure the effect is created after fc exists (ngOnInit) and does not leak or reference stale state

Poem

I strike with a flag, calm and bold, 🚩
Effects awaken controls, steady as gold,
Reconcile only when the signal sings,
Numbers obey when the effect bell rings,
Rajini-approved code — swift and cold.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete. The Clickup link is a placeholder URL without actual ticket reference, and critical sections lack required information. Replace 'app.clickup.com' with the actual Clickup ticket URL, add code coverage metrics, and include UI preview screenshots if applicable.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: fy-number disable state' clearly and concisely describes the main change: fixing the disabled state handling in the fy-number component.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix_fy_number_disable_state

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e29445 and 29743a8.

📒 Files selected for processing (2)
  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts (1 hunks)
  • src/app/shared/components/fy-number/fy-number.component.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.component.{ts,html}

📄 CodeRabbit inference engine (.cursor/rules/component-i18n-key-naming.mdc)

**/*.component.{ts,html}: Top-level object in i18n translation files must be the component or feature folder name, derived from the file name by converting kebab-case to camelCase and removing the first matching prefix from: 'feature', 'ui', 'component'.
Keys inside the top-level object in i18n translation files must be free-form, not prefixed, and there must be no nested objects beyond the first level.
Use meaningful, semantic, and context-aware key names for i18n keys; avoid generic names like 'label1', 'text1', or 'message1'.
Do not translate strings that are already translation keys, tracking/analytics strings, strings with only special characters, comments, non-user-visible text, or variable bindings and expressions.
One object per component or feature in i18n translation files; avoid deep nesting and avoid prefixes like 'ui*' or 'feature*' in object names.

Files:

  • src/app/shared/components/fy-number/fy-number.component.ts
src/**/*.component.{ts,html}

📄 CodeRabbit inference engine (.cursor/rules/component-i18n-key-naming.mdc)

Translation keys for Angular components and templates located in 'src/**' must be placed in 'src/assets/i18n/{lang}.json' for every supported language.

Files:

  • src/app/shared/components/fy-number/fy-number.component.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: sumrender
Repo: fylein/fyle-mobile-app PR: 3265
File: src/app/fyle/personal-cards-matched-expenses/expense-preview/expense-preview.component.spec.ts:35-37
Timestamp: 2024-11-18T06:55:17.972Z
Learning: In `src/app/fyle/add-edit-expense/add-edit-expense.page.ts`, the `unmatchExpense` method is used for corporate card expenses, not for personal card expenses.
Learnt from: bistaastha
Repo: fylein/fyle-mobile-app PR: 3240
File: src/app/fyle/manage-corporate-cards/manage-corporate-cards.page.spec.ts:293-354
Timestamp: 2024-10-23T11:45:16.974Z
Learning: In 'src/app/fyle/manage-corporate-cards/manage-corporate-cards.page.spec.ts', the flags 'isVisaRTFEnabled$', 'isMastercardRTFEnabled$', and 'isYodleeEnabled$' do not have rapidly changing values, so tests for rapid changes in these flags are unnecessary.
📚 Learning: 2024-10-23T11:45:16.974Z
Learnt from: bistaastha
Repo: fylein/fyle-mobile-app PR: 3240
File: src/app/fyle/manage-corporate-cards/manage-corporate-cards.page.spec.ts:293-354
Timestamp: 2024-10-23T11:45:16.974Z
Learning: In 'src/app/fyle/manage-corporate-cards/manage-corporate-cards.page.spec.ts', the flags 'isVisaRTFEnabled$', 'isMastercardRTFEnabled$', and 'isYodleeEnabled$' do not have rapidly changing values, so tests for rapid changes in these flags are unnecessary.

Applied to files:

  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts
📚 Learning: 2024-11-18T06:55:17.972Z
Learnt from: sumrender
Repo: fylein/fyle-mobile-app PR: 3265
File: src/app/fyle/personal-cards-matched-expenses/expense-preview/expense-preview.component.spec.ts:35-37
Timestamp: 2024-11-18T06:55:17.972Z
Learning: In `src/app/fyle/add-edit-expense/add-edit-expense.page.ts`, the `unmatchExpense` method is used for corporate card expenses, not for personal card expenses.

Applied to files:

  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts
📚 Learning: 2024-11-04T13:10:36.868Z
Learnt from: arjunaj5
Repo: fylein/fyle-mobile-app PR: 3242
File: src/app/fyle/my-expenses/my-expenses.page.spec.ts:967-968
Timestamp: 2024-11-04T13:10:36.868Z
Learning: In `src/app/fyle/my-expenses/my-expenses.page.spec.ts`, when testing private methods like `filterDEIncompleteExpenses`, it's acceptable to use `//ts-ignore` to spy on them in unit tests.

Applied to files:

  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts
📚 Learning: 2025-06-17T05:15:26.587Z
Learnt from: Dimple16
Repo: fylein/fyle-mobile-app PR: 3714
File: src/app/core/services/mileage.service.ts:23-25
Timestamp: 2025-06-17T05:15:26.587Z
Learning: In the Fyle mobile app, the migration from OrgUserSettingsService to PlatformEmployeeSettingsService introduced a potential breaking change where mileage_settings could be null or undefined. Test cases in add-edit-mileage-2.spec.ts and add-edit-mileage-5.spec.ts explicitly test scenarios where getOrgUserMileageSettings returns null or undefined, indicating this is a real-world scenario that needs defensive handling.

Applied to files:

  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts
📚 Learning: 2024-11-13T06:25:08.957Z
Learnt from: Dimple16
Repo: fylein/fyle-mobile-app PR: 3259
File: src/app/core/services/personal-cards.service.ts:35-42
Timestamp: 2024-11-13T06:25:08.957Z
Learning: In 'PersonalCardsService', move the LaunchDarkly feature flag subscription inside the service methods instead of the constructor to prevent issues during first-time login.

Applied to files:

  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)

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 and usage tips.

@github-actions github-actions bot added the size/S Small PR label Nov 11, 2025
@github-actions
Copy link

Unit Test Coverage % values
Statements 94.99% ( 20828 / 21925 )
Branches 91.51% ( 7880 / 8611 )
Functions 94.17% ( 6131 / 6510 )
Lines 95.03% ( 20169 / 21222 )

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60923ff and 2e29445.

📒 Files selected for processing (2)
  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts (1 hunks)
  • src/app/shared/components/fy-number/fy-number.component.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.component.{ts,html}

📄 CodeRabbit inference engine (.cursor/rules/component-i18n-key-naming.mdc)

**/*.component.{ts,html}: Top-level object in i18n translation files must be the component or feature folder name, derived from the file name by converting kebab-case to camelCase and removing the first matching prefix from: 'feature', 'ui', 'component'.
Keys inside the top-level object in i18n translation files must be free-form, not prefixed, and there must be no nested objects beyond the first level.
Use meaningful, semantic, and context-aware key names for i18n keys; avoid generic names like 'label1', 'text1', or 'message1'.
Do not translate strings that are already translation keys, tracking/analytics strings, strings with only special characters, comments, non-user-visible text, or variable bindings and expressions.
One object per component or feature in i18n translation files; avoid deep nesting and avoid prefixes like 'ui*' or 'feature*' in object names.

Files:

  • src/app/shared/components/fy-number/fy-number.component.ts
src/**/*.component.{ts,html}

📄 CodeRabbit inference engine (.cursor/rules/component-i18n-key-naming.mdc)

Translation keys for Angular components and templates located in 'src/**' must be placed in 'src/assets/i18n/{lang}.json' for every supported language.

Files:

  • src/app/shared/components/fy-number/fy-number.component.ts
🧠 Learnings (3)
📚 Learning: 2024-11-18T06:55:17.972Z
Learnt from: sumrender
Repo: fylein/fyle-mobile-app PR: 3265
File: src/app/fyle/personal-cards-matched-expenses/expense-preview/expense-preview.component.spec.ts:35-37
Timestamp: 2024-11-18T06:55:17.972Z
Learning: In `src/app/fyle/add-edit-expense/add-edit-expense.page.ts`, the `unmatchExpense` method is used for corporate card expenses, not for personal card expenses.

Applied to files:

  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts
📚 Learning: 2024-10-23T11:45:16.974Z
Learnt from: bistaastha
Repo: fylein/fyle-mobile-app PR: 3240
File: src/app/fyle/manage-corporate-cards/manage-corporate-cards.page.spec.ts:293-354
Timestamp: 2024-10-23T11:45:16.974Z
Learning: In 'src/app/fyle/manage-corporate-cards/manage-corporate-cards.page.spec.ts', the flags 'isVisaRTFEnabled$', 'isMastercardRTFEnabled$', and 'isYodleeEnabled$' do not have rapidly changing values, so tests for rapid changes in these flags are unnecessary.

Applied to files:

  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts
📚 Learning: 2024-11-04T13:10:36.868Z
Learnt from: arjunaj5
Repo: fylein/fyle-mobile-app PR: 3242
File: src/app/fyle/my-expenses/my-expenses.page.spec.ts:967-968
Timestamp: 2024-11-04T13:10:36.868Z
Learning: In `src/app/fyle/my-expenses/my-expenses.page.spec.ts`, when testing private methods like `filterDEIncompleteExpenses`, it's acceptable to use `//ts-ignore` to spy on them in unit tests.

Applied to files:

  • src/app/fyle/add-edit-expense/add-edit-expense.page.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines 2871 to 2878
const isReconciliationEnaled = this.launchDarklyService.getVariation('reconciliation_beta', false);
if (isReconciliationEnaled) {
this.isReconciledExpense.set(expense.is_reconciled);
if (this.isReconciledExpense()) {
this.fg.controls.dateOfSpend.disable();
this.fg.controls.currencyObj.disable();
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Thalaivaa! This feature flag logic has a serious problem, da!

Style la irukku, but three critical issues I'm spotting:

  1. Typo in variable name: isReconciliationEnaled should be isReconciliationEnabled - even Superstar doesn't make spelling mistakes, machaan!

  2. Incorrect Observable usage: getVariation returns an Observable but you're using it synchronously. This is like trying to catch a bullet with your bare hands - only Rajini can do that! Based on the LaunchDarkly service pattern in this codebase, you need to subscribe to it.

  3. One-way street logic: Once you disable these controls, there's no logic to re-enable them if the expense becomes unreconciled or the feature flag changes. Controls can get permanently locked, boss!

Apply this diff to fix the synchronous Observable usage and typo:

-        const isReconciliationEnaled = this.launchDarklyService.getVariation('reconciliation_beta', false);
-        if (isReconciliationEnaled) {
-          this.isReconciledExpense.set(expense.is_reconciled);
-          if (this.isReconciledExpense()) {
-            this.fg.controls.dateOfSpend.disable();
-            this.fg.controls.currencyObj.disable();
-          }
-        }
+        this.launchDarklyService.getVariation('reconciliation_beta', false).subscribe((isReconciliationEnabled) => {
+          if (isReconciliationEnabled) {
+            this.isReconciledExpense.set(expense.is_reconciled);
+            if (this.isReconciledExpense()) {
+              this.fg.controls.dateOfSpend?.disable();
+              this.fg.controls.currencyObj?.disable();
+            }
+          }
+        });

Also consider: You might want to add logic to re-enable these controls when the expense is not reconciled or when the feature flag is disabled. Right now it's a one-way ticket to Disabled Town!

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/fyle/add-edit-expense/add-edit-expense.page.ts around lines 2871 to
2878, rename the mistyped variable isReconciliationEnaled to
isReconciliationEnabled, change the synchronous call to
this.launchDarklyService.getVariation(...) to subscribe to the returned
Observable (or pipe takeUntil) and handle the emitted boolean; inside the
subscription set this.isReconciledExpense accordingly and then enable or disable
fg.controls.dateOfSpend and fg.controls.currencyObj based on the current
expense.is_reconciled value (disable when reconciled true, enable when false),
and ensure you react to future changes by either subscribing to
expense/is_reconciled changes or re-evaluating on feature-flag updates; also
store/teardown the subscription properly (e.g., add to a Subscription or use
takeUntil and clean up in ngOnDestroy) so controls aren’t permanently locked or
leaked.

Comment on lines 39 to 51
constructor() {
effect(() => {
const isDisabled = this.disabled();
if (isDisabled !== undefined && this.fc) {
this.isDisabled = isDisabled;
if (isDisabled) {
this.fc.disable();
} else if (this.fc.disabled) {
this.fc.enable();
}
}
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Machaan, this effect has a timing problem that even my style can't fix!

The constructor effect you've added has a critical race condition, boss:

  1. Lifecycle timing issue: The constructor runs before ngOnInit, but this.fc is only initialized in ngOnInit (line 156/159). When the effect first executes, this.fc is undefined, so the initial disabled() value won't be applied. The logic only works for subsequent changes to disabled() - not the first time!

  2. Duplicate logic: You already have setDisabledState() method (lines 120-127) that does exactly the same thing. Now you have two mechanisms fighting for control of the disabled state - this is like having two heroes in the same movie, unnecessary confusion!

  3. Incomplete synchronization: The disabled() signal input and the setDisabledState() method are separate paths. If the parent component calls setDisabledState(), the signal won't update. If the signal updates, great, but the initial value is missed.

Recommended fix: Move this logic to ngOnInit after fc is created, or use ngAfterViewInit. Here's the style way to do it:

  constructor() {
-   effect(() => {
-     const isDisabled = this.disabled();
-     if (isDisabled !== undefined && this.fc) {
-       this.isDisabled = isDisabled;
-       if (isDisabled) {
-         this.fc.disable();
-       } else if (this.fc.disabled) {
-         this.fc.enable();
-       }
-     }
-   });
  }

  ngOnInit(): void {
    this.isIos = this.platform.is('ios');
    
    // ... existing code ...
    
    if (!this.isDistance) {
      this.fc = new UntypedFormControl(null, Validators.pattern(/^-?(?:\d*\.\d+|\d+\.?)$/));
    } else {
      this.fc = new UntypedFormControl(null, Validators.pattern(/^\d*(\.\d+)?$/));
    }
+
+   // Apply initial disabled state from signal input
+   const initialDisabledState = this.disabled();
+   if (initialDisabledState !== undefined) {
+     this.setDisabledState(initialDisabledState);
+   }
+
+   // Subscribe to changes in disabled signal
+   effect(() => {
+     const isDisabled = this.disabled();
+     if (isDisabled !== undefined) {
+       this.setDisabledState(isDisabled);
+     }
+   }, { injector: this.injector });

    this.fc.valueChanges.subscribe((value) => {
      // ... existing code ...
    });
  }

Even better approach - just update the initial value in ngOnInit and rely on the existing setDisabledState method for everything else!

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/shared/components/fy-number/fy-number.component.ts around lines 39 to
51, the constructor effect runs before fc is initialized (ngOnInit), causing the
initial disabled() value to be missed and duplicating logic already present in
setDisabledState; remove the effect from the constructor, and instead apply the
initial disabled state after fc is created in ngOnInit (or ngAfterViewInit) and
rely on the existing setDisabledState method for subsequent updates so there is
a single authoritative path to enable/disable the control; ensure the signal and
setDisabledState remain synchronized (update the signal inside setDisabledState
or vice versa) and delete the duplicate constructor logic.

@github-actions
Copy link

🔁 Code Duplication Report - Angular

Format Files analyzed Total lines Total tokens Clones found Duplicated lines Duplicated tokens
java 4 169 1239 0 0 (0.00%) 0 (0.00%)
typescript 743 56925 491303 204 3164 (5.56%) 29450 (5.99%) -0.01% 🚀
scss 191 21229 113618 111 3886 (18.31%) 20937 (18.43%)
markup 289 19457 164009 104 1408 (7.24%) 11810 (7.20%)
python 4 697 5373 3 32 (4.59%) 368 (6.85%)
css 1 38 219 0 0 (0.00%) 0 (0.00%)
swift 1 60 405 0 0 (0.00%) 0 (0.00%)
properties 2 29 73 0 0 (0.00%) 0 (0.00%)
javascript 14 930 6540 1 10 (1.08%) 108 (1.65%)
prolog 1 60 559 0 0 (0.00%) 0 (0.00%)
groovy 6 178 1051 0 0 (0.00%) 0 (0.00%)
yaml 14 618 4561 9 187 (30.26%) 1554 (34.07%)
markdown 2 274 2046 0 0 (0.00%) 0 (0.00%)
bash 1 12 83 0 0 (0.00%) 0 (0.00%)
Total: 1273 100676 791079 432 8687 (8.63%) 64227 (8.12%)

📊 No significant change in code duplication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S Small PR

Development

Successfully merging this pull request may close these issues.

3 participants