Skip to content

Fix navigation loop issue in Billing#16018

Merged
bodhish merged 8 commits into
developfrom
fix/navigation-loop
Mar 12, 2026
Merged

Fix navigation loop issue in Billing#16018
bodhish merged 8 commits into
developfrom
fix/navigation-loop

Conversation

@NikhilA8606
Copy link
Copy Markdown
Member

@NikhilA8606 NikhilA8606 commented Mar 5, 2026

Proposed Changes

Fix #16017

Screen.Recording.2026-03-05.at.11.58.10.AM.mov

Tagging: @ohcnetwork/care-fe-code-reviewers

Merge Checklist

  • Add specs that demonstrate the bug or test the new feature.
  • Update product documentation.
  • Ensure that UI text is placed in I18n files.
  • Prepare a screenshot or demo video for the changelog entry and attach it to the issue.
  • Request peer reviews.
  • Complete QA on mobile devices.
  • Complete QA on desktop devices.
  • Add or update Playwright tests for related changes

Summary by CodeRabbit

  • Bug Fixes

    • Improved browser history handling so back/forward navigation behaves more predictably when viewing or creating invoices.
  • Refactor

    • Simplified the back-control on invoice view to a cleaner link-style button.
    • Removed the back-control from invoice creation and made navigation to a newly created invoice replace the history entry to avoid extra back steps.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 5, 2026

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

Simplified BackButton usage in the invoice view, removed BackButton from invoice creation and switched to replace navigation for new invoices, and refactored HistoryAPIProvider to handle push/replace/pop actions and adjust the internal history array accordingly. Raviger dependency version bumped.

Changes

Cohort / File(s) Summary
Invoice view
src/pages/Facility/billing/invoice/InvoiceShow.tsx
Removed explicit to prop from BackButton, leaving a simplified <BackButton variant="link" className="px-0 justify-start"> usage; navigation destination is now implicit.
Invoice creation
src/pages/Facility/billing/account/CreateInvoice.tsx
Removed BackButton UI from the header and changed navigation to the newly created invoice to use navigate(invoiceUrl, { replace: true }) (history replace).
History provider
src/Providers/HistoryAPIProvider.tsx
Refactored location-change handling to derive newPath from location.fullPath + location.search, use location.initiatedBy as action, and update internal history for replace, pop, and push actions (including dropping current entry when revisiting the previous path).
Dependencies
package.json
Bumped raviger dependency from ^5.0.0-2 to ^5.3.0.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective—fixing a navigation loop issue in the Billing section—and directly aligns with the primary change across the codebase modifications.
Description check ✅ Passed The description references the linked issue (#16017), includes a demonstration video, and indicates peer reviews have been requested. However, it lacks explicit details of the specific changes made and the approach taken to fix the navigation loop.
Linked Issues check ✅ Passed The code changes directly address issue #16017 by refactoring HistoryAPIProvider to properly handle navigation history (push, pop, replace actions), simplifying BackButton usage, and updating raviger to v5.3.0, which collectively prevent the navigation loop.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the navigation loop: HistoryAPIProvider refactoring, BackButton simplification, raviger version update, and CreateInvoice.tsx navigation adjustment are all directly related to resolving issue #16017.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/navigation-loop

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

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Mar 5, 2026

Deploying care-preview with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9305fc7
Status: ✅  Deploy successful!
Preview URL: https://4b18343a.care-preview-a7w.pages.dev
Branch Preview URL: https://fix-navigation-loop.care-preview-a7w.pages.dev

View logs

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a “Back” navigation loop on the Billing Account detail page by making the back destination explicit instead of relying on history.

Changes:

  • Set BackButton destination on AccountShow to always navigate to the accounts list route.

return (
<div className="space-y-3">
<BackButton size="xs">
<BackButton size="xs" to={`/facility/${facilityId}/billing/account`}>
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

Hard-coding the BackButton to route drops any query params/filters that were used to reach the accounts list (e.g. /billing/account?patient_filter=... is used elsewhere). This can regress UX by returning to the unfiltered list instead of the prior view; consider deriving the back target from the last non-account-detail entry in app history, or preserving the list search params when available.

Suggested change
<BackButton size="xs" to={`/facility/${facilityId}/billing/account`}>
<BackButton size="xs">

Copilot uses AI. Check for mistakes.
return (
<div className="space-y-3">
<BackButton size="xs">
<BackButton size="xs" to={`/facility/${facilityId}/billing/account`}>
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

This change fixes a navigation-loop scenario but isn’t covered by the existing Playwright billing specs. Please add an e2e test that reproduces the loop (e.g., switch account tabs then click Back) and asserts Back returns to the accounts list (and, if applicable, preserves list query params).

Copilot generated this review using guidance from repository custom instructions.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 5, 2026

🎭 Playwright Test Results

Status: ❌ Failed
Test Shards: 3

Metric Count
Total Tests 271
✅ Passed 270
❌ Failed 1
⏭️ Skipped 0

📊 Detailed results are available in the playwright-final-report artifact.

Run: #7306

Copy link
Copy Markdown
Contributor

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/Facility/billing/invoice/InvoiceShow.tsx`:
- Line 397: Add a Playwright E2E spec (filename matching **/*.spec.ts) that
exercises the cross-page "from=invoice" navigation created in InvoiceShow (the
link using
`/facility/${facilityId}/billing/account/${invoice.account.id}?from=invoice`):
start on an invoice page, click the link to AccountShow, assert the current URL
contains `from=invoice` and the account id (use page.url().toContain or
expect(page).toHaveURL with toContain), then from AccountShow trigger the "back
to accounts list" action and assert the resulting URL contains the accounts list
path (use `.toContain()` for the query-string tolerant checks). Ensure the spec
uses Playwright's test API and covers the invoice → account → accounts list flow
end-to-end.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b5bf0e84-1b89-4576-898f-0457dd5e3dd4

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2328e and 53afbcb.

📒 Files selected for processing (2)
  • src/pages/Facility/billing/account/AccountShow.tsx
  • src/pages/Facility/billing/invoice/InvoiceShow.tsx

variant="link"
className="px-0 justify-start"
to={`/facility/${facilityId}/billing/account/${invoice.account.id}`}
to={`/facility/${facilityId}/billing/account/${invoice.account.id}?from=invoice`}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a Playwright regression test for this cross-page from=invoice navigation contract.

Line 397 introduces a query-param handshake with AccountShow, but this loop-fix path is still untested in the PR. Please add an E2E covering: invoice back → account page, then account back → accounts list (prefer URL .toContain() checks for query-string-tolerant assertions).

I can draft the Playwright spec for this flow if you want.
Based on learnings: **/*.spec.ts should use Playwright tests, and for dynamic query params URL assertions should prefer .toContain() over strict .toHaveURL().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Facility/billing/invoice/InvoiceShow.tsx` at line 397, Add a
Playwright E2E spec (filename matching **/*.spec.ts) that exercises the
cross-page "from=invoice" navigation created in InvoiceShow (the link using
`/facility/${facilityId}/billing/account/${invoice.account.id}?from=invoice`):
start on an invoice page, click the link to AccountShow, assert the current URL
contains `from=invoice` and the account id (use page.url().toContain or
expect(page).toHaveURL with toContain), then from AccountShow trigger the "back
to accounts list" action and assert the resulting URL contains the accounts list
path (use `.toContain()` for the query-string tolerant checks). Ensure the spec
uses Playwright's test API and covers the invoice → account → accounts list flow
end-to-end.

Copilot AI review requested due to automatic review settings March 6, 2026 07:42
@NikhilA8606 NikhilA8606 marked this pull request as ready for review March 6, 2026 07:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

Comment on lines 419 to 421
<div className="h-auto w-px bg-gray-300" aria-hidden="true" />
{account?.patient && (
<>
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

After removing the BackButton, the vertical divider (<div className="h-auto w-px ..." />) becomes the first element in this header row, which will render an orphan separator at the left edge. Either remove the divider when there’s no leading content or replace the BackButton with an alternative control so the layout remains consistent.

Suggested change
<div className="h-auto w-px bg-gray-300" aria-hidden="true" />
{account?.patient && (
<>
{account?.patient && (
<>
<div className="h-auto w-px bg-gray-300" aria-hidden="true" />

Copilot uses AI. Check for mistakes.
} else {
onSuccess?.();
navigate(invoiceUrl);
navigate(invoiceUrl, { replace: true });
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

There are existing Playwright tests covering billing navigation/back-button behavior, but this PR changes history semantics (navigate(..., { replace: true }) and invoice/account navigation) without adding a regression test for the loop described in #16017. Please add a Playwright test that opens an invoice from an account, uses the in-page Back button, and asserts the browser back button does not return to the invoice (i.e., no navigation loop).

Copilot generated this review using guidance from repository custom instructions.
Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Comment on lines +11 to +17

useEffect(() => {
const original = window.history.replaceState.bind(window.history); //store the original browser replaceState function
window.history.replaceState = (...args) => {
isReplaceRef.current = true;
return original(...args); //call the original browser replaceState function
};
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

HistoryAPIProvider monkey-patches window.history.replaceState but never restores it on unmount. In React StrictMode/dev, effects can mount/unmount and remount, and without cleanup you can end up wrapping an already-wrapped function (risking recursion/stack overflow) and leaking the patched global outside the provider’s lifetime. Store the original in a ref, guard against double-patching, and restore replaceState in the effect cleanup.

Suggested change
useEffect(() => {
const original = window.history.replaceState.bind(window.history); //store the original browser replaceState function
window.history.replaceState = (...args) => {
isReplaceRef.current = true;
return original(...args); //call the original browser replaceState function
};
const originalReplaceStateRef = useRef<History["replaceState"] | null>(null);
useEffect(() => {
if (typeof window === "undefined" || !window.history) {
return;
}
// Guard against double-patching in case this effect re-runs
if (originalReplaceStateRef.current) {
return;
}
const originalReplaceState = window.history.replaceState;
originalReplaceStateRef.current = originalReplaceState;
window.history.replaceState = function patchedReplaceState(
...args
): void {
isReplaceRef.current = true;
return originalReplaceState.apply(this, args);
};
return () => {
if (
originalReplaceStateRef.current &&
typeof window !== "undefined" &&
window.history
) {
window.history.replaceState = originalReplaceStateRef.current;
originalReplaceStateRef.current = null;
}
};

Copilot uses AI. Check for mistakes.
Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Comment on lines +22 to +33
const newPath = newLocation.fullPath + newLocation.search;
const isReplace = isReplaceRef.current;
isReplaceRef.current = false;

setHistory((history) => {
if (history.length && newPath === history[0])
// Ignore push if navigate to same path (for some weird unknown reasons?)
if (history.length && newPath === history[0]) {
return history;
}

if (isReplace) {
return [newPath, ...history.slice(1)];
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The isReplaceRef flag is only cleared inside useLocationChange. If window.history.replaceState is called without triggering a route change (or a route change is prevented), the flag will stay true and the next navigation may be incorrectly treated as a replace, corrupting the app’s history stack. Consider resetting the flag immediately after the replace call (e.g., in a microtask) or tracking replace vs push via the router API instead of a global flag.

Copilot uses AI. Check for mistakes.
to={`/facility/${facilityId}/billing/account/${invoice.account.id}`}
onClick={() => {
navigate(
`/facility/${facilityId}/billing/account/${invoice.account.id}`,
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The new Back button uses navigate(...) without { replace: true }, which will push the account page onto the history stack and can still reproduce the “Back -> invoice -> Back -> invoice” loop (account -> invoice -> account (push) -> back goes to invoice). Use navigate(target, { replace: true }) or a goBack()/window.history.back()-style navigation that doesn’t add a new entry.

Suggested change
`/facility/${facilityId}/billing/account/${invoice.account.id}`,
`/facility/${facilityId}/billing/account/${invoice.account.id}`,
{ replace: true },

Copilot uses AI. Check for mistakes.
Comment on lines +393 to +404
<Button
variant="link"
className="px-0 justify-start"
to={`/facility/${facilityId}/billing/account/${invoice.account.id}`}
onClick={() => {
navigate(
`/facility/${facilityId}/billing/account/${invoice.account.id}`,
);
}}
>
<ChevronLeft />
<span>{t("back")}</span>
</BackButton>
</Button>
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

This change switches the Back control from a link (BackButton renders a Link) to a plain button with onClick navigation. That removes link semantics (open-in-new-tab, copy link address) and may be a regression in accessibility/UX consistency. Consider keeping a link-based control and addressing the loop via { replace: true }/history handling instead.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (1)
src/pages/Facility/billing/invoice/InvoiceShow.tsx (1)

393-404: ⚠️ Potential issue | 🟠 Major

Add the missing Playwright regression for invoice → account → accounts back flow.

This navigation contract changed here, but there is still no E2E assertion guarding the loop scenario end-to-end.

I can draft the Playwright spec for this flow if you want.
Based on learnings: **/*.spec.ts should use Playwright tests, and URL assertions should prefer .toContain() when query params may vary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Facility/billing/invoice/InvoiceShow.tsx` around lines 393 - 404,
The PR changed the navigation contract in InvoiceShow.tsx (the Button onClick
that calls
navigate(`/facility/${facilityId}/billing/account/${invoice.account.id}`)), but
there's no Playwright E2E test guarding the invoice → account → accounts back
flow; add a new Playwright spec (e.g., invoices-account-back.spec.ts under
**/*.spec.ts) that: visits an invoice page, clicks the back Button, asserts the
URL contains the expected path segments (use expect(page.url()).toContain(...)
rather than strict equality to allow query params), navigates through account
and then back to accounts to verify the full loop, and include clear selectors
for the Button/ChevronLeft used in InvoiceShow.tsx to exercise the exact flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/Facility/billing/invoice/InvoiceShow.tsx`:
- Around line 397-400: The Back action currently calls
navigate(`/facility/${facilityId}/billing/account/${invoice.account.id}`) which
pushes a new history entry; change the call in the Back handler to use replace
navigation so it replaces the current history entry (i.e., call navigate with
the same target and the options object to set replace: true). Locate the
navigate invocation in InvoiceShow (the Back handler referencing facilityId and
invoice.account.id) and update it to pass the replace flag.

In `@src/Providers/HistoryAPIProvider.tsx`:
- Around line 12-18: The effect in HistoryAPIProvider patches
window.history.replaceState (stored as original) but never restores it, so on
unmount/remount wrappers stack and isReplaceRef behavior breaks; update the
useEffect to return a cleanup that restores window.history.replaceState back to
the saved original (the bound original variable) and also ensure any references
to isReplaceRef remain valid; target the useEffect block that defines original
and assigns window.history.replaceState to revert it on cleanup.

---

Duplicate comments:
In `@src/pages/Facility/billing/invoice/InvoiceShow.tsx`:
- Around line 393-404: The PR changed the navigation contract in InvoiceShow.tsx
(the Button onClick that calls
navigate(`/facility/${facilityId}/billing/account/${invoice.account.id}`)), but
there's no Playwright E2E test guarding the invoice → account → accounts back
flow; add a new Playwright spec (e.g., invoices-account-back.spec.ts under
**/*.spec.ts) that: visits an invoice page, clicks the back Button, asserts the
URL contains the expected path segments (use expect(page.url()).toContain(...)
rather than strict equality to allow query params), navigates through account
and then back to accounts to verify the full loop, and include clear selectors
for the Button/ChevronLeft used in InvoiceShow.tsx to exercise the exact flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7de3fae6-9968-4d75-bad9-6a4f2731a1c7

📥 Commits

Reviewing files that changed from the base of the PR and between 53afbcb and 17ac6a0.

📒 Files selected for processing (3)
  • src/Providers/HistoryAPIProvider.tsx
  • src/pages/Facility/billing/account/CreateInvoice.tsx
  • src/pages/Facility/billing/invoice/InvoiceShow.tsx

Comment thread src/pages/Facility/billing/invoice/InvoiceShow.tsx Outdated
Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Copy link
Copy Markdown
Contributor

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Providers/HistoryAPIProvider.tsx (1)

22-45: ⚠️ Potential issue | 🟠 Major

Add regression tests for the new replace/back state machine.

Lines 22-45 change the global navigation-history rules, but this PR still has no specs covering the reported loop. Please add tests for initial load, push, replace, and the “navigate back to history[1]” path, plus the invoice flow that triggered this fix.

Based on learnings: Test provider state management and updates with comprehensive unit tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Providers/HistoryAPIProvider.tsx` around lines 22 - 45, Add unit tests
covering the new replace/back state machine around useLocationChange: write
specs that exercise initial load (onInitial:true), a push navigation that
prepends a newPath to history, a replace navigation using the isReplaceRef path
that replaces history[0] via setHistory, and the “navigate back to history[1]”
case where history.slice(1) is returned; also add an end-to-end style test
reproducing the invoice flow that previously looped (simulate sequence: initial
-> push invoice list -> push invoice detail -> replace/update detail -> navigate
back to list) to assert no infinite loop and correct history array mutations.
Target the component/provider that holds isReplaceRef and setHistory
(HistoryAPIProvider/useLocationChange) and assert state transitions after each
navigation step.
♻️ Duplicate comments (1)
src/Providers/HistoryAPIProvider.tsx (1)

14-20: ⚠️ Potential issue | 🟠 Major

Restore window.history.replaceState in cleanup.

Lines 14-20 patch a global browser API but never restore it. If this provider remounts, wrappers stack and stale closures can corrupt isReplaceRef handling.

🔧 Proposed fix
   useEffect(() => {
     const original = window.history.replaceState.bind(window.history);
     window.history.replaceState = (...args) => {
       isReplaceRef.current = true;
       return original(...args);
     };
+    return () => {
+      window.history.replaceState = original;
+    };
   }, []);

As per coding guidelines: Implement proper cleanup and memory management in provider components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Providers/HistoryAPIProvider.tsx` around lines 14 - 20, The useEffect
that wraps window.history.replaceState (captured in the local original variable)
must restore the original function in its cleanup to avoid stacking wrappers and
stale closures; update the effect in HistoryAPIProvider to return a cleanup
function that sets window.history.replaceState back to original (guarding if
original is defined) and optionally clear or reset isReplaceRef.current to a
safe default so remounts behave correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/Providers/HistoryAPIProvider.tsx`:
- Around line 22-45: Add unit tests covering the new replace/back state machine
around useLocationChange: write specs that exercise initial load
(onInitial:true), a push navigation that prepends a newPath to history, a
replace navigation using the isReplaceRef path that replaces history[0] via
setHistory, and the “navigate back to history[1]” case where history.slice(1) is
returned; also add an end-to-end style test reproducing the invoice flow that
previously looped (simulate sequence: initial -> push invoice list -> push
invoice detail -> replace/update detail -> navigate back to list) to assert no
infinite loop and correct history array mutations. Target the component/provider
that holds isReplaceRef and setHistory (HistoryAPIProvider/useLocationChange)
and assert state transitions after each navigation step.

---

Duplicate comments:
In `@src/Providers/HistoryAPIProvider.tsx`:
- Around line 14-20: The useEffect that wraps window.history.replaceState
(captured in the local original variable) must restore the original function in
its cleanup to avoid stacking wrappers and stale closures; update the effect in
HistoryAPIProvider to return a cleanup function that sets
window.history.replaceState back to original (guarding if original is defined)
and optionally clear or reset isReplaceRef.current to a safe default so remounts
behave correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bffa869d-257a-45dd-a921-5ea14bed3014

📥 Commits

Reviewing files that changed from the base of the PR and between 17ac6a0 and be82dd8.

📒 Files selected for processing (1)
  • src/Providers/HistoryAPIProvider.tsx

@gigincg
Copy link
Copy Markdown
Member

gigincg commented Mar 11, 2026

@NikhilA8606

You should be able to implement this better with the new release of raviger(5.3.0) with kyeotic/raviger#162

Copilot AI review requested due to automatic review settings March 11, 2026 10:33
@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 11, 2026

Deploy Preview for care-ohc ready!

Name Link
🔨 Latest commit 9305fc7
🔍 Latest deploy log https://app.netlify.com/projects/care-ohc/deploys/69b292d4adb4a90008e4d70a
😎 Deploy Preview https://deploy-preview-16018.preview.ohc.network
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
className="px-0 justify-start"
to={`/facility/${facilityId}/billing/account/${invoice.account.id}`}
>
<BackButton variant="link" className="px-0 justify-start">
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

BackButton no longer provides to or fallbackUrl. If the user lands on this page directly (refresh/new tab/deep link), useAppHistory().history[1] will be undefined and BackButton returns null, leaving no in-app way to navigate back. Consider passing a fallbackUrl (e.g., the account page or invoices list) so the back control is always available.

Suggested change
<BackButton variant="link" className="px-0 justify-start">
<BackButton
variant="link"
className="px-0 justify-start"
fallbackUrl={`/facility/${facilityId}/billing/invoices`}
>

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 119: The package.json currently pins "raviger" to a non-existent version
^5.3.0 which breaks installs; update the dependency entry for the raviger
package (the "raviger" key in package.json) to a valid published version such as
"5.2.1" or replace it with a git dependency pointing to the repo/commit that
contains the initiatedBy change (e.g., a git+https URL or commit hash) so npm
install succeeds; ensure package-lock/yarn.lock is regenerated after the change.

In `@src/Providers/HistoryAPIProvider.tsx`:
- Around line 31-37: The branch in HistoryAPIProvider.tsx that checks if (action
=== "push") is redundant because it returns the same value as the default case;
remove the explicit push branch and leave the single return [newPath,
...history] path inside the navigation handler (referencing the action variable
and the newPath and history arrays) so behavior remains identical but code is
simplified.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5309443d-6935-4e0d-802a-308a3bb758a3

📥 Commits

Reviewing files that changed from the base of the PR and between be82dd8 and d82bd24.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • package.json
  • src/Providers/HistoryAPIProvider.tsx
  • src/pages/Facility/billing/invoice/InvoiceShow.tsx

Comment thread package.json
"pigeon-maps": "^0.22.1",
"qrcode.react": "^4.1.0",
"raviger": "^5.0.0-2",
"raviger": "^5.3.0",
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 11, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

raviger 5.3.0 useLocationChange initiatedBy

💡 Result:

In raviger, useLocationChange only passes a single argument to your callback: a RavigerLocation object. There is no initiatedBy parameter/property in the hook API. [1]

If you’re trying to tell what caused the navigation (e.g. <Link> / navigate() vs browser back/forward), raviger’s navigate() dispatches a custom popstate event that includes __tag: 'raviger:navigation', which you can use to distinguish raviger-initiated navigations from other popstate sources. [2]

Example:

window.addEventListener('popstate', (e: any) => {
  const initiatedByRaviger = e?.__tag === 'raviger:navigation'
  // initiatedByRaviger ? "navigate/Link" : "back/forward (or other)"
})

Sources: [1] [2]


🏁 Script executed:

fd -t f "HistoryAPIProvider" --exec cat {} \;

Repository: ohcnetwork/care_fe

Length of output: 1549


🏁 Script executed:

rg "initiatedBy" -t ts -t tsx -B 2 -A 2

Repository: ohcnetwork/care_fe

Length of output: 89


🏁 Script executed:

cat package.json | grep -A 5 -B 5 "raviger"

Repository: ohcnetwork/care_fe

Length of output: 395


🌐 Web query:

raviger 5.3.0 npm package

💡 Result:

raviger does not appear to have a 5.3.0 release on npm. The latest published line shown by package mirrors/metadata is 5.2.x (e.g., jsDelivr shows 5.2.1 and UNPKG shows package metadata for 5.2.0). [1] [2]

To install the latest available:

npm i raviger

To install a specific available version (example):

npm i raviger@5.2.1

[1]

Sources: [1] [2]


🌐 Web query:

raviger PR 162 initiatedBy

💡 Result:

In kyeotic/raviger PR #162 (“add initatedBy”), merged March 10, 2026, raviger added an initiatedBy field to the RavigerLocation object produced by useLocationChange so you can tell how navigation happened (programmatic vs back/forward). [1]

initiatedBy?: NavigationInitiator where NavigationInitiator = 'push' | 'replace' | 'pop':

  • 'push': navigate() (uses history.pushState)
  • 'replace': navigate(..., { replace: true }) (uses history.replaceState)
  • 'pop': browser back/forward (popstate)
  • undefined: fired on mount or basePath change (not triggered by navigation) [2]

🏁 Script executed:

rg "raviger" -t json package.json -A 1 -B 1

Repository: ohcnetwork/care_fe

Length of output: 139


🏁 Script executed:

rg "@types/raviger" -t json

Repository: ohcnetwork/care_fe

Length of output: 44


Raviger 5.3.0 does not exist on NPM yet; package.json specifies a non-existent version.

PR #162 that adds the initiatedBy property was merged March 10, 2026, but the 5.3.0 release has not been published to NPM. The latest available version is 5.2.1. Specifying "raviger": "^5.3.0" in package.json will cause npm install to fail. Either downgrade to an available version (e.g., 5.2.1) and implement an alternative approach for tracking navigation origin, use a git dependency pointing to the latest commit, or wait for the 5.3.0 release to be published.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 119, The package.json currently pins "raviger" to a
non-existent version ^5.3.0 which breaks installs; update the dependency entry
for the raviger package (the "raviger" key in package.json) to a valid published
version such as "5.2.1" or replace it with a git dependency pointing to the
repo/commit that contains the initiatedBy change (e.g., a git+https URL or
commit hash) so npm install succeeds; ensure package-lock/yarn.lock is
regenerated after the change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Are you sure?
Image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@NikhilA8606, you're right to question this! The Comment image shows the raviger GitHub releases page confirming that v5.3.0 has indeed been tagged as the latest release (13 hours ago).

However, there's an important distinction: a GitHub release tag does not automatically mean the package is published to npm. The npm registry currently lists 5.1.0 as the latest published version of raviger, and the main branch package.json in the raviger repo still shows "version": "5.2.1".

This means:

  • ✅ The GitHub release for 5.3.0 exists (as shown in your screenshot).
  • ⚠️ The npm package for 5.3.0 may not have been published yet (npm still shows 5.1.0 as latest).

If 5.3.0 hasn't been pushed to npm, npm install will still fail with "raviger": "^5.3.0" since ^5.3.0 requires at least that exact version to be available on the registry.

I'd recommend verifying by running:

npm view raviger versions --json | tail -5

or checking https://www.npmjs.com/package/raviger?activeTab=versions directly. If 5.3.0 is confirmed on npm, then the package.json entry is perfectly valid and my original concern no longer applies. I apologize for the confusion caused by my earlier analysis!


🧠 Learnings used
Learnt from: CR
Repo: ohcnetwork/care_fe PR: 0
File: .github/instructions/pages.instructions.md:0-0
Timestamp: 2025-11-25T13:52:19.758Z
Learning: Applies to src/pages/**/*.{ts,tsx} : Use `raviger` router for navigation (existing dependency)

Learnt from: CR
Repo: ohcnetwork/care_fe PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T13:49:43.065Z
Learning: Applies to **/*.{ts,tsx} : Use raviger for routing

Learnt from: CR
Repo: ohcnetwork/care_fe PR: 0
File: .github/instructions/config-files.instructions.md:0-0
Timestamp: 2025-11-25T13:51:07.680Z
Learning: Applies to package.json : Maintain `package.json` as the primary dependency and script configuration

Learnt from: CR
Repo: ohcnetwork/care_fe PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-03-09T11:25:31.348Z
Learning: Applies to src/pages/**/*.{ts,tsx} : Use raviger for application routing instead of other routing libraries

Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Copy link
Copy Markdown
Contributor

@Jacobjeevan Jacobjeevan left a comment

Choose a reason for hiding this comment

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

minor things

Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Comment thread src/pages/Facility/billing/account/CreateInvoice.tsx
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/Providers/HistoryAPIProvider.tsx:34

  • This PR changes browser-history semantics for billing flows (HistoryAPIProvider pop/replace handling + invoice creation navigation using replace). There are Playwright specs for BackButton/history behavior, but there isn’t a regression test covering the reported billing loop (#16017). Adding a Playwright test that reproduces the loop (open invoice from account, click in-app back, then ensure browser back doesn’t return to the invoice) would help prevent regressions.
  useLocationChange(
    (location) => {
      const newPath = location.fullPath + location.search;
      const action = location.initiatedBy;

      setHistory((history) => {
        // instead of pushing, we are popping the history when the same page is navigated to
        if (
          (history.length > 1 && newPath === history[1]) ||
          action === "pop"
        ) {
          return history.slice(1);
        }

        // replace navigation
        if (action === "replace") {
          return [newPath, ...history.slice(1)];
        }

        // normal navigation or push navigation
        return [newPath, ...history];
      });
    },
    { onInitial: true },
  );

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Comment thread src/Providers/HistoryAPIProvider.tsx Outdated
Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com>
Copilot AI review requested due to automatic review settings March 12, 2026 10:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +15 to 29
setHistory((history) => {
// Pop current path if navigate back to previous path
if (
(history.length > 1 && newPath === history[1]) ||
action === "pop"
) {
return history.slice(1);
}

if (action === "replace") {
return [newPath, ...history.slice(1)];
}

// Otherwise just push the current path
return [newPath, ...history];
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

action === "pop" currently always drops the current entry (history.slice(1)), even when the user navigates forward (also a POP). This can desync the app history (e.g., forward navigation can clear the stack) and cause BackButton to disappear or point to the wrong page. Consider handling POP by comparing newPath to history[1] (back) and otherwise treating it like a push (forward), or reconciling by finding newPath within the existing stack before slicing.

Suggested change
setHistory((history) => {
// Pop current path if navigate back to previous path
if (
(history.length > 1 && newPath === history[1]) ||
action === "pop"
) {
return history.slice(1);
}
if (action === "replace") {
return [newPath, ...history.slice(1)];
}
// Otherwise just push the current path
return [newPath, ...history];
setHistory((currentHistory) => {
const isBackToPrevious =
currentHistory.length > 1 && newPath === currentHistory[1];
if (action === "pop") {
// Handle browser history POP (back or forward)
if (isBackToPrevious) {
// Back: drop the current entry, reveal previous
return currentHistory.slice(1);
}
// Forward or arbitrary POP: try to reconcile with existing stack
const existingIndex = currentHistory.indexOf(newPath);
if (existingIndex > -1) {
// Move the existing entry to the front, preserving its forward history
return currentHistory.slice(existingIndex);
}
// POP to a path not in our stack: treat as a push
return [newPath, ...currentHistory];
}
if (action === "replace") {
if (currentHistory.length === 0) {
return [newPath];
}
return [newPath, ...currentHistory.slice(1)];
}
// Non-POP navigation that effectively goes back to the previous path
if (isBackToPrevious) {
return currentHistory.slice(1);
}
// Otherwise just push the current path
return [newPath, ...currentHistory];

Copilot uses AI. Check for mistakes.
Comment on lines 263 to 267
onSuccess?.();
} else {
onSuccess?.();
navigate(invoiceUrl);
navigate(invoiceUrl, { replace: true });
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

This change adjusts navigation history semantics (using navigate(..., { replace: true })) to avoid the invoice/account back-loop. Please add/extend a Playwright spec that reproduces #16017 (invoice -> back -> account -> back does not return to the same invoice) and also covers browser back/forward behavior after a replaced navigation, since the HistoryAPIProvider logic is sensitive to POP/REPLACE actions.

Copilot generated this review using guidance from repository custom instructions.
@bodhish bodhish merged commit 36d8808 into develop Mar 12, 2026
27 of 29 checks passed
@bodhish bodhish deleted the fix/navigation-loop branch March 12, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Navigation Loop in Billing

7 participants