Skip to content

BER-3361: Remove members-forward release gate#27221

Open
jonatansberg wants to merge 14 commits intomainfrom
ber-3361-remove-feature-flag-release
Open

BER-3361: Remove members-forward release gate#27221
jonatansberg wants to merge 14 commits intomainfrom
ber-3361-remove-feature-flag-release

Conversation

@jonatansberg
Copy link
Copy Markdown
Member

Summary

This PR removes the membersForward rollout machinery and the /members-forward route so the released React members experience owns /members and /members/import directly.

What changed

  • removed the membersForward labs flag and related feature-service/settings exposure
  • removed the members route gate and /members-forward route so React owns the released members routes directly
  • kept the retained Ember member detail and new-member flows working by carrying the current members URL through the back path
  • aligned the remaining tests with the released route ownership

Why

BER-3506 landed the route handoff foundation. This branch finishes the release by deleting the rollout gate and flag-specific behavior without mixing in the broader Ember dead-code cleanup.

Testing

  • YARN_IGNORE_ENGINES=1 yarn workspace @tryghost/admin test:unit src/members-route.test.tsx
  • YARN_IGNORE_ENGINES=1 yarn workspace @tryghost/posts exec vitest run test/unit/views/members/members-actions.test.tsx
  • cd ghost/core && YARN_IGNORE_ENGINES=1 yarn test:single test/unit/shared/labs.test.js
  • YARN_IGNORE_ENGINES=1 yarn workspace @tryghost/e2e test:types

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 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

Removed the membersForward lab flag and its gating across the codebase: deleted the feature declaration and private-feature entry, removed the /members-forward route and MembersRouteGate, updated navigation to use literal #/members/members routes and React Router Outlet for nested members routes, and threaded a backPath flow into member-detail navigation (buildMemberDetailPath and prop propagation). Updated Ember templates to use hash links. Numerous unit and e2e tests and page-objects were adjusted to remove per-test membersForward overrides and to use the consolidated members list pages.

Possibly related PRs

  • Remove members-forward route #27040: Modifies/removes members-forward routing and feature-flag gating across routes, nav, labs, and related tests — touches the same members-forward and routing code paths.

Suggested reviewers

  • 9larsons
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: removing the membersForward release gate and related machinery.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining what was removed, why it was removed, and how it aligns with prior work.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ber-3361-remove-feature-flag-release

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
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24124282464 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24127132919 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

Codecov Report

❌ Patch coverage is 44.44444% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.89%. Comparing base (049544d) to head (604331b).
⚠️ Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
ghost/admin/app/controllers/member.js 50.00% 3 Missing and 2 partials ⚠️
ghost/admin/app/routes/member.js 37.50% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #27221      +/-   ##
==========================================
- Coverage   73.42%   72.89%   -0.54%     
==========================================
  Files        1552     1553       +1     
  Lines      124677   124914     +237     
  Branches    15073    15100      +27     
==========================================
- Hits        91544    91055     -489     
- Misses      32126    32904     +778     
+ Partials     1007      955      -52     
Flag Coverage Δ
admin-tests 49.82% <44.44%> (-4.54%) ⬇️
e2e-tests 72.89% <44.44%> (-0.54%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24129099035 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24181947096 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@jonatansberg jonatansberg marked this pull request as ready for review April 9, 2026 10:18
@jonatansberg jonatansberg requested a review from 9larsons as a code owner April 9, 2026 10:18
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: 3

🧹 Nitpick comments (5)
ghost/admin/tests/acceptance/members-test.js (1)

11-11: Avoid disabling the entire acceptance suite with describe.skip.

Line 11 currently skips every members acceptance test in this file, which drops CI coverage for still-supported /members behavior and can hide regressions. Please keep the suite enabled and only skip/remove truly obsolete cases.

Proposed minimal fix
-describe.skip('Acceptance: Members Test', function () {
+describe('Acceptance: Members Test', function () {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/admin/tests/acceptance/members-test.js` at line 11, The test suite is
entirely disabled by using describe.skip for the 'Acceptance: Members Test'
suite; change describe.skip to describe to re-enable the acceptance tests in
members-test.js, and if certain individual specs are truly obsolete mark those
with it.skip or remove them instead of skipping the whole suite (locate the
top-level describe.skip('Acceptance: Members Test', ...) and update it
accordingly).
e2e/tests/admin/members/export.test.ts (1)

96-105: Extract repeated export metadata assertions into a shared helper.

Both scenarios repeat the same assertions for IDs/timestamps and filename shape. Pulling that into one helper will reduce drift as export behavior evolves.

♻️ Optional refactor
+function assertExportMetadata(
+    suggestedFilename: string,
+    contentIds: RegExpMatchArray | null,
+    contentTimestamps: RegExpMatchArray | null,
+    expectedCount: number
+) {
+    expect(contentIds).toHaveLength(expectedCount);
+    expect(contentTimestamps).toHaveLength(expectedCount);
+    expect(suggestedFilename.startsWith('members')).toBe(true);
+    expect(suggestedFilename.endsWith('.csv')).toBe(true);
+}
+
 test.describe('Ghost Admin - Members Export', () => {
@@
-        expect(contentIds).toHaveLength(MEMBERS_FIXTURE.length);
-        expect(contentTimestamps).toHaveLength(MEMBERS_FIXTURE.length);
-        expect(suggestedFilename.startsWith('members')).toBe(true);
-        expect(suggestedFilename.endsWith('.csv')).toBe(true);
+        assertExportMetadata(suggestedFilename, contentIds, contentTimestamps, MEMBERS_FIXTURE.length);
@@
-        expect(contentIds).toHaveLength(filteredMembers.length);
-        expect(contentTimestamps).toHaveLength(filteredMembers.length);
-        expect(suggestedFilename.startsWith('members')).toBe(true);
-        expect(suggestedFilename.endsWith('.csv')).toBe(true);
+        assertExportMetadata(suggestedFilename, contentIds, contentTimestamps, filteredMembers.length);

Also applies to: 119-127

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

In `@e2e/tests/admin/members/export.test.ts` around lines 96 - 105, Create a
shared test helper that centralizes the repeated export assertions: move the
checks referencing membersPage.exportMembers() results (suggestedFilename,
content), extractDownloadedContentSpecifics(content) outputs (contentIds,
contentTimestamps), and assertExportedMembers(content, MEMBERS_FIXTURE) into one
function (e.g., assertExportedMembersMetadata or verifyExportDownload) and call
it from both test sites; the helper should assert contentIds and
contentTimestamps lengths equal MEMBERS_FIXTURE.length and that
suggestedFilename startsWith 'members' and endsWith '.csv', then replace the
duplicated assertions in the tests with a single call to this new helper.
apps/admin/src/layout/app-sidebar/nav-content.tsx (2)

80-97: Extract the members base path to one constant.

'members' is now hardcoded in both the useIsActiveLink call and the nav target. Pulling that into a shared constant will make future route cleanup less error-prone.

♻️ Suggested cleanup
+const MEMBERS_ROUTE = 'members';
 const LEGACY_MEMBERS_ACTIVE_ROUTES = ['member', 'member.new', 'members-activity'];
 ...
-const isMembersRouteActive = useIsActiveLink({path: 'members', activeOnSubpath: true});
+const isMembersRouteActive = useIsActiveLink({path: MEMBERS_ROUTE, activeOnSubpath: true});
 ...
-const membersRoute = 'members';
+const membersRoute = MEMBERS_ROUTE;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin/src/layout/app-sidebar/nav-content.tsx` around lines 80 - 97,
Extract the hardcoded 'members' string into a single constant (e.g.,
MEMBERS_ROUTE) and use that constant wherever the literal is used: replace the
argument in useIsActiveLink({path: 'members', ...}) and the membersRoute
assignment (currently membersRoute = 'members'), and any other occurrences in
this file (e.g., references involving isMembersRouteActive or membersNavActive)
so all member-route logic references the new MEMBERS_ROUTE constant
consistently.

91-93: Please add regression coverage for the legacy active-route fallback.

This branch is now what keeps the Members item highlighted on Ember-only detail/new/activity screens after the /members-forward removal. A focused test here would make the follow-up cleanup much safer.

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

In `@apps/admin/src/layout/app-sidebar/nav-content.tsx` around lines 91 - 93, Add
a focused regression test for the legacy active-route fallback used by
membersNavActive: exercise the branch where isMembersRouteActive is false so the
code falls back to routing.isRouteActive(LEGACY_MEMBERS_ACTIVE_ROUTES), and
assert the Members item remains highlighted when routing.isRouteActive returns
true; also include cases where isMembersRouteActive is true but either
hasActiveMemberView or membersExpanded cause the expression to resolve false to
ensure the original Ember-only detail/new/activity screens remain covered. Mock
or stub routing.isRouteActive and render the component (or call the selector) to
assert expected boolean outcomes for membersNavActive across these permutations.
ghost/admin/app/routes/member.js (1)

57-65: Truncated comment at line 57.

The cleanup logic is correct. However, the comment "Make sure we clear" appears to be incomplete. Consider completing it to explain what's being cleared and why.

📝 Suggested comment improvement
-        // Make sure we clear
+        // Make sure we clear navigation state when leaving the member route
         if (isExiting) {
             controller.set('backPath', null);
             controller.set('directlyFromAnalytics', false);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/admin/app/routes/member.js` around lines 57 - 65, The comment "Make
sure we clear" is incomplete—update it to briefly explain what and why is being
cleared: when isExiting is true, we're resetting transient navigation state on
the controller (clearing backPath and directlyFromAnalytics) and clearing
postAnalytics if present to avoid leaking state between member route visits;
modify the comment above the conditional that references isExiting and
controller.postAnalytics (the block that sets backPath, directlyFromAnalytics,
and postAnalytics to null/false) to a concise sentence like "Clear transient
navigation and analytics state when exiting the route to avoid carrying state
into the next visit."
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ghost/admin/app/controllers/member.js`:
- Around line 54-56: The current check this.backPath?.startsWith('/members') is
too permissive and can match unintended routes like '/members-forward'; tighten
validation by matching only the exact /members route or subpaths (e.g. use a
strict test like /^\/members(?:$|\/)/ or normalize and compare path segments)
before returning this.backPath so stale or manipulated back links cannot fall
through; update the conditional around this.backPath to use that stricter
pattern (reference: this.backPath and the startsWith check).

In `@ghost/admin/tests/acceptance/members/filter-test.js`:
- Line 16: The test suite is entirely skipped via describe.skip in the
Acceptance: Members filtering suite; remove or replace describe.skip in the
members/filter-test.js so the suite runs, then run the tests and either fix
failing tests (adapting selectors, test setup, or stubs referenced inside the
describe block) or, if a specific case truly must be skipped, change only that
individual it(...) to it.skip and add a linked follow-up issue ID in the test’s
comment; ensure you reference the top-level describe('Acceptance: Members
filtering', ...) and any failing nested tests when making fixes.

In `@ghost/admin/tests/acceptance/members/import-test.js`:
- Line 11: The test suite has been disabled via describe.skip in the Members
import acceptance test (describe.skip in members/import-test.js), which silences
all coverage; either re-enable the suite by changing describe.skip back to
describe so tests run, or if the suite is truly obsolete remove the entire
describe block; if re-enabling, update any assertions and route references
inside the suite (e.g., route transition expectations and controller/route
ownership checks within the tests) to match the new route ownership and paths so
the restored tests pass against the current implementation.

---

Nitpick comments:
In `@apps/admin/src/layout/app-sidebar/nav-content.tsx`:
- Around line 80-97: Extract the hardcoded 'members' string into a single
constant (e.g., MEMBERS_ROUTE) and use that constant wherever the literal is
used: replace the argument in useIsActiveLink({path: 'members', ...}) and the
membersRoute assignment (currently membersRoute = 'members'), and any other
occurrences in this file (e.g., references involving isMembersRouteActive or
membersNavActive) so all member-route logic references the new MEMBERS_ROUTE
constant consistently.
- Around line 91-93: Add a focused regression test for the legacy active-route
fallback used by membersNavActive: exercise the branch where
isMembersRouteActive is false so the code falls back to
routing.isRouteActive(LEGACY_MEMBERS_ACTIVE_ROUTES), and assert the Members item
remains highlighted when routing.isRouteActive returns true; also include cases
where isMembersRouteActive is true but either hasActiveMemberView or
membersExpanded cause the expression to resolve false to ensure the original
Ember-only detail/new/activity screens remain covered. Mock or stub
routing.isRouteActive and render the component (or call the selector) to assert
expected boolean outcomes for membersNavActive across these permutations.

In `@e2e/tests/admin/members/export.test.ts`:
- Around line 96-105: Create a shared test helper that centralizes the repeated
export assertions: move the checks referencing membersPage.exportMembers()
results (suggestedFilename, content), extractDownloadedContentSpecifics(content)
outputs (contentIds, contentTimestamps), and assertExportedMembers(content,
MEMBERS_FIXTURE) into one function (e.g., assertExportedMembersMetadata or
verifyExportDownload) and call it from both test sites; the helper should assert
contentIds and contentTimestamps lengths equal MEMBERS_FIXTURE.length and that
suggestedFilename startsWith 'members' and endsWith '.csv', then replace the
duplicated assertions in the tests with a single call to this new helper.

In `@ghost/admin/app/routes/member.js`:
- Around line 57-65: The comment "Make sure we clear" is incomplete—update it to
briefly explain what and why is being cleared: when isExiting is true, we're
resetting transient navigation state on the controller (clearing backPath and
directlyFromAnalytics) and clearing postAnalytics if present to avoid leaking
state between member route visits; modify the comment above the conditional that
references isExiting and controller.postAnalytics (the block that sets backPath,
directlyFromAnalytics, and postAnalytics to null/false) to a concise sentence
like "Clear transient navigation and analytics state when exiting the route to
avoid carrying state into the next visit."

In `@ghost/admin/tests/acceptance/members-test.js`:
- Line 11: The test suite is entirely disabled by using describe.skip for the
'Acceptance: Members Test' suite; change describe.skip to describe to re-enable
the acceptance tests in members-test.js, and if certain individual specs are
truly obsolete mark those with it.skip or remove them instead of skipping the
whole suite (locate the top-level describe.skip('Acceptance: Members Test', ...)
and update it accordingly).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e61c4aa8-7850-4d7f-b1e9-5ea6a1d99938

📥 Commits

Reviewing files that changed from the base of the PR and between 54eaf84 and c1efdf6.

⛔ Files ignored due to path filters (1)
  • ghost/core/test/e2e-api/admin/__snapshots__/config.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (44)
  • apps/admin-x-settings/src/components/settings/advanced/labs/private-features.tsx
  • apps/admin/src/layout/app-sidebar/nav-content.tsx
  • apps/admin/src/members-route-gate.tsx
  • apps/admin/src/members-route.test.tsx
  • apps/admin/src/members-route.tsx
  • apps/admin/src/routes.tsx
  • apps/posts/src/views/members/components/members-actions.tsx
  • apps/posts/src/views/members/components/members-list-item.tsx
  • apps/posts/src/views/members/components/members-list.tsx
  • apps/posts/src/views/members/member-detail-hash.ts
  • apps/posts/src/views/members/members.tsx
  • apps/posts/test/unit/views/members/members-actions.test.tsx
  • apps/posts/test/unit/views/members/members-list.test.ts
  • e2e/helpers/pages/admin/members/member-details-page.ts
  • e2e/tests/admin/analytics/post-analytics/growth.test.ts
  • e2e/tests/admin/members-legacy/disable-commenting.test.ts
  • e2e/tests/admin/members-legacy/filter-actions.test.ts
  • e2e/tests/admin/members-legacy/impersonation.test.ts
  • e2e/tests/admin/members-legacy/import.test.ts
  • e2e/tests/admin/members-legacy/member-activity-events.test.ts
  • e2e/tests/admin/members-legacy/members.test.ts
  • e2e/tests/admin/members-legacy/stripe-webhooks.test.ts
  • e2e/tests/admin/members/bulk-actions.test.ts
  • e2e/tests/admin/members/export.test.ts
  • e2e/tests/admin/members/list.test.ts
  • e2e/tests/admin/members/multiselect-filters.test.ts
  • e2e/tests/admin/members/saved-views.test.ts
  • e2e/tests/admin/members/search-and-filter.test.ts
  • e2e/tests/admin/members/tier-filter-search.test.ts
  • e2e/tests/admin/members/virtual-window.test.ts
  • ghost/admin/app/components/dashboard/onboarding-checklist.hbs
  • ghost/admin/app/components/editor/publish-options/publish-type.hbs
  • ghost/admin/app/components/posts-list/list-item.hbs
  • ghost/admin/app/controllers/member.js
  • ghost/admin/app/router.js
  • ghost/admin/app/routes/member.js
  • ghost/admin/app/routes/members.js
  • ghost/admin/app/services/feature.js
  • ghost/admin/app/templates/member.hbs
  • ghost/admin/tests/acceptance/authentication-test.js
  • ghost/admin/tests/acceptance/members-test.js
  • ghost/admin/tests/acceptance/members/filter-test.js
  • ghost/admin/tests/acceptance/members/import-test.js
  • ghost/core/core/shared/labs.js
💤 Files with no reviewable changes (18)
  • e2e/tests/admin/members/saved-views.test.ts
  • e2e/tests/admin/members/search-and-filter.test.ts
  • apps/admin-x-settings/src/components/settings/advanced/labs/private-features.tsx
  • e2e/tests/admin/members/virtual-window.test.ts
  • apps/admin/src/members-route-gate.tsx
  • ghost/admin/app/router.js
  • e2e/tests/admin/members-legacy/impersonation.test.ts
  • ghost/admin/app/routes/members.js
  • e2e/tests/admin/members/tier-filter-search.test.ts
  • apps/admin/src/routes.tsx
  • ghost/admin/app/services/feature.js
  • e2e/tests/admin/members-legacy/disable-commenting.test.ts
  • e2e/tests/admin/members-legacy/members.test.ts
  • e2e/tests/admin/members-legacy/import.test.ts
  • e2e/tests/admin/members-legacy/filter-actions.test.ts
  • e2e/tests/admin/members/bulk-actions.test.ts
  • ghost/core/core/shared/labs.js
  • e2e/tests/admin/members-legacy/member-activity-events.test.ts

Comment on lines +54 to +56
if (this.backPath?.startsWith('/members')) {
return this.backPath;
}
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 | 🟡 Minor

Tighten backPath validation to avoid invalid route fallthrough.

Line 54 uses startsWith('/members'), which also accepts removed/non-list paths like /members-forward.... That can send delete/breadcrumb navigation to stale routes if back is manipulated or old links are used.

Suggested fix
-        if (this.backPath?.startsWith('/members')) {
+        if (this.backPath?.match(/^\/members(?:$|\?)/)) {
             return this.backPath;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/admin/app/controllers/member.js` around lines 54 - 56, The current
check this.backPath?.startsWith('/members') is too permissive and can match
unintended routes like '/members-forward'; tighten validation by matching only
the exact /members route or subpaths (e.g. use a strict test like
/^\/members(?:$|\/)/ or normalize and compare path segments) before returning
this.backPath so stale or manipulated back links cannot fall through; update the
conditional around this.backPath to use that stricter pattern (reference:
this.backPath and the startsWith check).

Comment thread ghost/admin/tests/acceptance/members/filter-test.js
Comment thread ghost/admin/tests/acceptance/members/import-test.js
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.

🧹 Nitpick comments (1)
apps/admin/src/layout/app-sidebar/nav-content.tsx (1)

80-80: Optional: centralize the "members" route literal.

Using one constant for the route path would reduce drift risk between active-state checks and link targets.

♻️ Suggested refactor
+const MEMBERS_ROUTE_PATH = 'members';
 const LEGACY_MEMBERS_ACTIVE_ROUTES = ['member', 'member.new', 'members-activity'];

-const isMembersRouteActive = useIsActiveLink({path: 'members', activeOnSubpath: true});
+const isMembersRouteActive = useIsActiveLink({path: MEMBERS_ROUTE_PATH, activeOnSubpath: true});

 ...
-    to="members"
+    to={MEMBERS_ROUTE_PATH}
 ...
-    to="members"
+    to={MEMBERS_ROUTE_PATH}

Also applies to: 183-197

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

In `@apps/admin/src/layout/app-sidebar/nav-content.tsx` at line 80, The "members"
route string is repeated (e.g., in the isMembersRouteActive useIsActiveLink call
and elsewhere around lines 183-197), which risks drift; extract that literal
into a single constant (e.g., MEMBERS_ROUTE or MEMBERS_PATH) and replace all
occurrences where the route is referenced (for active-state checks like
isMembersRouteActive and any Link/Href targets) so both use the same identifier;
update import/exports if needed so the constant is shared where used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/admin/src/layout/app-sidebar/nav-content.tsx`:
- Line 80: The "members" route string is repeated (e.g., in the
isMembersRouteActive useIsActiveLink call and elsewhere around lines 183-197),
which risks drift; extract that literal into a single constant (e.g.,
MEMBERS_ROUTE or MEMBERS_PATH) and replace all occurrences where the route is
referenced (for active-state checks like isMembersRouteActive and any Link/Href
targets) so both use the same identifier; update import/exports if needed so the
constant is shared where used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 115fe09e-99b1-4d52-8702-3a5d5594054d

📥 Commits

Reviewing files that changed from the base of the PR and between c1efdf6 and 77b9413.

📒 Files selected for processing (6)
  • apps/admin/src/layout/app-sidebar/nav-content.tsx
  • apps/posts/src/views/members/components/members-list-item.tsx
  • apps/posts/src/views/members/components/members-list.tsx
  • apps/posts/src/views/members/member-detail-hash.ts
  • apps/posts/test/unit/views/members/members-list.test.ts
  • e2e/tests/admin/members/list.test.ts
✅ Files skipped from review due to trivial changes (3)
  • apps/posts/test/unit/views/members/members-list.test.ts
  • apps/posts/src/views/members/member-detail-hash.ts
  • apps/posts/src/views/members/components/members-list.tsx

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.

🧹 Nitpick comments (1)
e2e/tests/admin/members/import.test.ts (1)

16-31: Optional: clean up temporary CSV files after test execution.

The temp file created around Line 29–Line 30 is never removed. Wrapping import steps in try/finally with unlinkSync(csvPath) would avoid local/CI temp-dir buildup over time.

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

In `@e2e/tests/admin/members/import.test.ts` around lines 16 - 31, The test
creates a temp CSV via tmpdir() and writeFileSync into csvPath but never deletes
it; wrap the import and assertions that use csvPath (the block around the import
steps in import.test.ts) in a try/finally and call unlinkSync(csvPath) in the
finally to ensure cleanup; reference csvPath, writeFileSync and tmpdir() to
locate the creation and add unlinkSync(csvPath) to the finally block so temp
files are removed even if the test fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@e2e/tests/admin/members/import.test.ts`:
- Around line 16-31: The test creates a temp CSV via tmpdir() and writeFileSync
into csvPath but never deletes it; wrap the import and assertions that use
csvPath (the block around the import steps in import.test.ts) in a try/finally
and call unlinkSync(csvPath) in the finally to ensure cleanup; reference
csvPath, writeFileSync and tmpdir() to locate the creation and add
unlinkSync(csvPath) to the finally block so temp files are removed even if the
test fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 661ddbaa-1ca4-4f55-b780-933798c60b03

📥 Commits

Reviewing files that changed from the base of the PR and between 77b9413 and 4a25fdb.

📒 Files selected for processing (3)
  • e2e/tests/admin/members-legacy/members.test.ts
  • e2e/tests/admin/members/bulk-actions.test.ts
  • e2e/tests/admin/members/import.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • e2e/tests/admin/members-legacy/members.test.ts
  • e2e/tests/admin/members/bulk-actions.test.ts

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24190425447 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@jonatansberg jonatansberg added the preview Deploy a PR preview environment label Apr 16, 2026
@Ghost-Slimer Ghost-Slimer temporarily deployed to pr-preview-27221 April 16, 2026 06:42 Destroyed
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
This releases the React-owned /members routes while keeping Ember detail flows intact and deferring dead-code cleanup to the next stacked PR.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
This aligns the import-route unit coverage with the released behavior after removing the members-forward flag.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
This drops the extra routes test file so the release branch stays focused on the shipped members routing change.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
The config and runtime changes already cover the flag removal, so this extra assertion only added noise to the branch.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
Reused the shared link active matcher for the React-owned members routes and dropped the bespoke pathname check.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
The release branch moved /members out of the Ember test lane, so the auth acceptance tests now use an Ember-owned protected route and the analytics assertion matches the released React copy.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
The old filter-actions Playwright spec still drove Ember-only members filter controls that no longer exist on the released React /members route, while equivalent bulk-label coverage already exists in the React members suite.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
These Playwright tests still asserted the old Ember members list surface even though /members is now React-owned, and the retained Ember detail flows are covered by the remaining detail-focused specs.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release

Row clicks on the React members list were bypassing the back query string even though the rendered links already carried it. This shares one detail-hash builder between link and row navigation so retained Ember detail screens can return to the filtered list state.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release

The release branch still ran Ember-list import and export coverage even though those routes are no longer part of the shipped members list. This drops the dead import spec and keeps the export coverage focused on the React-owned members route.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
Removed redundant nav wiring, simplified the member detail path helper naming, and dropped the stale members-route e2e case without changing runtime behavior.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
Migrated the dropped React import flow and retained legacy member-detail coverage, and tightened bulk-action coverage so the filtered-members behavior still has an active browser test.
ref https://linear.app/ghost/issue/BER-3361/remove-feature-flag-release
Updated the Playwright import modal helper to match the shipped React import dialog instead of the old Ember data-test selectors so the restored browser coverage exercises the real UI.
@rob-ghost rob-ghost force-pushed the ber-3361-remove-feature-flag-release branch from 428d463 to 13f4ef3 Compare April 16, 2026 08:39
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

🧹 Nitpick comments (2)
e2e/helpers/pages/admin/members/members-import-modal.ts (1)

21-24: Scope mapping row lookup to the modal to avoid cross-page row collisions.

Line 22 still queries from this.page, so rows outside the modal can be matched in noisy admin screens. Prefer keeping this scoped to this.dialog for consistency with the new locator strategy.

Suggested refactor
 getMappingRow(fieldName: string): Locator {
-    return this.page.getByRole('row').filter({
-        has: this.page.getByRole('cell', {name: fieldName, exact: true})
+    return this.dialog.getByRole('row').filter({
+        has: this.dialog.getByRole('cell', {name: fieldName, exact: true})
     });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers/pages/admin/members/members-import-modal.ts` around lines 21 -
24, The getMappingRow function currently queries rows from this.page which can
match elements outside the modal; change both role lookups to be scoped to the
modal by using this.dialog.getByRole(...) for the row and the cell (i.e.,
replace this.page.getByRole with this.dialog.getByRole inside getMappingRow) so
the locator only matches rows inside the dialog and still returns a Locator from
getMappingRow.
apps/admin/src/layout/app-sidebar/nav-content.tsx (1)

89-93: Consider naming the activation branches for readability.

This logic is correct, but the ternary is a little dense. Extracting intermediate booleans would make future maintenance safer.

♻️ Optional readability refactor
-    const membersNavActive = isMembersRouteActive
-        ? (!hasActiveMemberView || !membersExpanded)
-        : routing.isRouteActive(LEGACY_MEMBERS_ACTIVE_ROUTES);
+    const shouldHighlightMembersBase = !hasActiveMemberView || !membersExpanded;
+    const isLegacyMembersRouteActive = routing.isRouteActive(LEGACY_MEMBERS_ACTIVE_ROUTES);
+    const membersNavActive = isMembersRouteActive
+        ? shouldHighlightMembersBase
+        : isLegacyMembersRouteActive;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin/src/layout/app-sidebar/nav-content.tsx` around lines 89 - 93, The
ternary that computes membersNavActive is dense; introduce descriptive
intermediate booleans to name the branches (e.g., activeRouteHandlesMemberViews
= isMembersRouteActive and hasActiveMemberView && membersExpanded, or
legacyRouteActive = routing.isRouteActive(LEGACY_MEMBERS_ACTIVE_ROUTES)) and
then set membersNavActive using those names so the intent of the two branches is
explicit; update references to hasActiveMemberView, membersExpanded
(savedMembersExpanded), isMembersRouteActive, routing.isRouteActive and
LEGACY_MEMBERS_ACTIVE_ROUTES accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/tests/admin/members/list.test.ts`:
- Line 55: Rename the test title string in the test declaration test('preserves
filters when returning from member detail', async ({page}) => { ... }) to follow
the required lowercase "what is tested - expected outcome" format; change it to
test('returning from member detail - preserves applied filters', async ({page})
=> { ... }) so the test name matches guidelines.

---

Nitpick comments:
In `@apps/admin/src/layout/app-sidebar/nav-content.tsx`:
- Around line 89-93: The ternary that computes membersNavActive is dense;
introduce descriptive intermediate booleans to name the branches (e.g.,
activeRouteHandlesMemberViews = isMembersRouteActive and hasActiveMemberView &&
membersExpanded, or legacyRouteActive =
routing.isRouteActive(LEGACY_MEMBERS_ACTIVE_ROUTES)) and then set
membersNavActive using those names so the intent of the two branches is
explicit; update references to hasActiveMemberView, membersExpanded
(savedMembersExpanded), isMembersRouteActive, routing.isRouteActive and
LEGACY_MEMBERS_ACTIVE_ROUTES accordingly.

In `@e2e/helpers/pages/admin/members/members-import-modal.ts`:
- Around line 21-24: The getMappingRow function currently queries rows from
this.page which can match elements outside the modal; change both role lookups
to be scoped to the modal by using this.dialog.getByRole(...) for the row and
the cell (i.e., replace this.page.getByRole with this.dialog.getByRole inside
getMappingRow) so the locator only matches rows inside the dialog and still
returns a Locator from getMappingRow.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: abf46348-39c5-4bcb-a6bc-27f789d717ef

📥 Commits

Reviewing files that changed from the base of the PR and between 428d463 and 13f4ef3.

📒 Files selected for processing (44)
  • apps/admin-x-settings/src/components/settings/advanced/labs/private-features.tsx
  • apps/admin/src/layout/app-sidebar/nav-content.tsx
  • apps/admin/src/members-route-gate.tsx
  • apps/admin/src/members-route.test.tsx
  • apps/admin/src/members-route.tsx
  • apps/admin/src/routes.tsx
  • apps/posts/src/views/members/components/members-actions.tsx
  • apps/posts/src/views/members/components/members-list-item.tsx
  • apps/posts/src/views/members/components/members-list.tsx
  • apps/posts/src/views/members/member-detail-hash.ts
  • apps/posts/src/views/members/members.tsx
  • apps/posts/test/unit/views/members/members-actions.test.tsx
  • apps/posts/test/unit/views/members/members-list.test.ts
  • e2e/helpers/pages/admin/members/member-details-page.ts
  • e2e/helpers/pages/admin/members/members-import-modal.ts
  • e2e/tests/admin/analytics/post-analytics/growth.test.ts
  • e2e/tests/admin/members-legacy/disable-commenting.test.ts
  • e2e/tests/admin/members-legacy/filter-actions.test.ts
  • e2e/tests/admin/members-legacy/impersonation.test.ts
  • e2e/tests/admin/members-legacy/member-activity-events.test.ts
  • e2e/tests/admin/members-legacy/members.test.ts
  • e2e/tests/admin/members-legacy/stripe-webhooks.test.ts
  • e2e/tests/admin/members/bulk-actions.test.ts
  • e2e/tests/admin/members/export.test.ts
  • e2e/tests/admin/members/import.test.ts
  • e2e/tests/admin/members/list.test.ts
  • e2e/tests/admin/members/multiselect-filters.test.ts
  • e2e/tests/admin/members/saved-views.test.ts
  • e2e/tests/admin/members/search-and-filter.test.ts
  • e2e/tests/admin/members/tier-filter-search.test.ts
  • e2e/tests/admin/members/virtual-window.test.ts
  • ghost/admin/app/components/dashboard/onboarding-checklist.hbs
  • ghost/admin/app/components/editor/publish-options/publish-type.hbs
  • ghost/admin/app/components/posts-list/list-item.hbs
  • ghost/admin/app/controllers/member.js
  • ghost/admin/app/router.js
  • ghost/admin/app/routes/member.js
  • ghost/admin/app/routes/members.js
  • ghost/admin/app/services/feature.js
  • ghost/admin/app/templates/member.hbs
  • ghost/admin/tests/acceptance/authentication-test.js
  • ghost/admin/tests/acceptance/members-test.js
  • ghost/admin/tests/acceptance/members/filter-test.js
  • ghost/admin/tests/acceptance/members/import-test.js
💤 Files with no reviewable changes (14)
  • e2e/tests/admin/members-legacy/member-activity-events.test.ts
  • e2e/tests/admin/members/search-and-filter.test.ts
  • e2e/tests/admin/members/virtual-window.test.ts
  • e2e/tests/admin/members/tier-filter-search.test.ts
  • e2e/tests/admin/members/saved-views.test.ts
  • ghost/admin/app/services/feature.js
  • ghost/admin/app/router.js
  • ghost/admin/app/routes/members.js
  • apps/admin-x-settings/src/components/settings/advanced/labs/private-features.tsx
  • apps/admin/src/members-route-gate.tsx
  • e2e/tests/admin/members-legacy/disable-commenting.test.ts
  • e2e/tests/admin/members-legacy/impersonation.test.ts
  • apps/admin/src/routes.tsx
  • e2e/tests/admin/members-legacy/filter-actions.test.ts
✅ Files skipped from review due to trivial changes (9)
  • ghost/admin/tests/acceptance/members-test.js
  • ghost/admin/tests/acceptance/members/import-test.js
  • e2e/tests/admin/members-legacy/stripe-webhooks.test.ts
  • e2e/tests/admin/analytics/post-analytics/growth.test.ts
  • ghost/admin/tests/acceptance/members/filter-test.js
  • e2e/tests/admin/members/multiselect-filters.test.ts
  • apps/posts/src/views/members/member-detail-hash.ts
  • apps/posts/test/unit/views/members/members-actions.test.tsx
  • apps/posts/src/views/members/components/members-list-item.tsx
🚧 Files skipped from review as they are similar to previous changes (13)
  • apps/posts/src/views/members/members.tsx
  • apps/admin/src/members-route.tsx
  • ghost/admin/app/components/posts-list/list-item.hbs
  • apps/posts/test/unit/views/members/members-list.test.ts
  • e2e/tests/admin/members/bulk-actions.test.ts
  • e2e/helpers/pages/admin/members/member-details-page.ts
  • ghost/admin/app/components/editor/publish-options/publish-type.hbs
  • ghost/admin/app/templates/member.hbs
  • apps/posts/src/views/members/components/members-list.tsx
  • apps/admin/src/members-route.test.tsx
  • e2e/tests/admin/members/export.test.ts
  • e2e/tests/admin/members/import.test.ts
  • e2e/tests/admin/members-legacy/members.test.ts

await expect(page).toHaveURL(new RegExp(`/members/${member.id}`));
});

test('preserves filters when returning from member detail', async ({page}) => {
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 | 🟡 Minor

Rename test to match required title format

Please rename this test to the mandated 'what is tested - expected outcome' format (lowercase), e.g. returning from member detail - preserves applied filters.

As per coding guidelines, "Test names should follow format: 'what is tested - expected outcome' in lowercase".

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

In `@e2e/tests/admin/members/list.test.ts` at line 55, Rename the test title
string in the test declaration test('preserves filters when returning from
member detail', async ({page}) => { ... }) to follow the required lowercase
"what is tested - expected outcome" format; change it to test('returning from
member detail - preserves applied filters', async ({page}) => { ... }) so the
test name matches guidelines.

@github-actions
Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24504119691 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@rob-ghost rob-ghost force-pushed the ber-3361-remove-feature-flag-release branch from 8346bea to e9c550f Compare April 16, 2026 11:59
@rob-ghost rob-ghost added preview Deploy a PR preview environment and removed preview Deploy a PR preview environment labels Apr 16, 2026
@rob-ghost rob-ghost force-pushed the ber-3361-remove-feature-flag-release branch from e9c550f to 4ea9ad7 Compare April 16, 2026 12:11
@rob-ghost rob-ghost added preview Deploy a PR preview environment and removed preview Deploy a PR preview environment labels Apr 16, 2026
@github-actions
Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24509461754 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@github-actions
Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24513932942 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@rob-ghost rob-ghost force-pushed the ber-3361-remove-feature-flag-release branch from da066ac to 6b3b0a0 Compare April 16, 2026 14:27
@github-actions
Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24515892736 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@rob-ghost rob-ghost enabled auto-merge (squash) April 16, 2026 14:52
@rob-ghost rob-ghost disabled auto-merge April 16, 2026 14:53
ref https://linear.app/tryghost/issue/BER-3361

Adds E2E test for direct navigation to /members/import and prevents
Ember's dynamic member route from capturing /members/import URLs.
@rob-ghost rob-ghost force-pushed the ber-3361-remove-feature-flag-release branch from 6b3b0a0 to 604331b Compare April 16, 2026 16:40
@sonarqubecloud
Copy link
Copy Markdown

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

Labels

preview Deploy a PR preview environment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants