Skip to content

Removed Ember members implementation#27599

Open
jonatansberg wants to merge 9 commits intomainfrom
jonatan-ber-3580-remove-ember-members-implementation
Open

Removed Ember members implementation#27599
jonatansberg wants to merge 9 commits intomainfrom
jonatan-ber-3580-remove-ember-members-implementation

Conversation

@jonatansberg
Copy link
Copy Markdown
Member

@jonatansberg jonatansberg commented Apr 28, 2026

Summary

Removes the old Ember members list, filters, import/export UI, and their tests. /members and /members/import are now handled by React.

The Ember member detail/new/activity screens stay in place. Member detail invalidates the React members cache through the existing Ember bridge when member data changes.

Reviewer note

GitHub makes this look larger than it is because members.css had a large block removed near the top, so the default diff shows retained CSS as newly added.

  • GitHub/default diff: +444/-1586
  • Histogram diff: +14/-1156

This PR is mostly deletion, with only a small amount of real new code.

Verification

  • git diff --check
  • pnpm --filter @tryghost/admin test:unit
  • pnpm --filter ghost-admin lint
  • pnpm exec ember exam --split 1 --parallel 1 --filter "Unit: Controller: member"
  • Reference scans for removed Ember members code/assets

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 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

This PR removes a large portion of members-related admin UI and logic: many Ember/Glimmer components, templates, modals, the filter-builder and its CSS, import/export CSV tooling and validator service, bulk-member modals, member list templates/controllers/routes, Mirage CSV upload mocks, numerous filter/export tests, and related devDependencies. It adds a StateBridge action/event for member commenting changes, maps the Ember comment model to CommentsResponseType for React Query sync, updates the MemberController to use stateBridge invalidation helpers, and adds unit tests for state-bridge and controller invalidation.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonatan-ber-3580-remove-ember-members-implementation

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 60.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.77%. Comparing base (0c4d441) to head (2b32def).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
ghost/admin/app/controllers/member.js 60.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #27599      +/-   ##
==========================================
+ Coverage   73.17%   73.77%   +0.60%     
==========================================
  Files        1561     1511      -50     
  Lines      127051   125649    -1402     
  Branches    15383    15125     -258     
==========================================
- Hits        92970    92701     -269     
+ Misses      33105    32007    -1098     
+ Partials      976      941      -35     
Flag Coverage Δ
admin-tests 53.09% <60.00%> (+3.21%) ⬆️
e2e-tests 73.77% <60.00%> (+0.60%) ⬆️

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.

@jonatansberg jonatansberg marked this pull request as ready for review April 28, 2026 08:55
@jonatansberg jonatansberg requested a review from 9larsons as a code owner April 28, 2026 08:55
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 451a8e9127

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ghost/admin/app/controllers/member.js Outdated
window.adminXQueryClient.invalidateQueries({queryKey: ['CommentsResponseType']});
window.adminXQueryClient.invalidateQueries({queryKey: ['MembersResponseType']});
}
this.invalidateMembersCache();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate comments cache when enabling member commenting

Calling invalidateMembersCache() here only emits an Ember data change for model member, which the React bridge maps to MembersResponseType invalidation only (apps/admin/src/ember-bridge/ember-bridge.tsx). Before this change, enabling commenting explicitly invalidated both MembersResponseType and CommentsResponseType, so after toggling commenting on from member detail, cached Comments views can remain stale until a hard refresh.

Useful? React with 👍 / 👎.

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

Caution

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

⚠️ Outside diff range comments (3)
ghost/admin/app/controllers/member.js (1)

187-193: ⚠️ Potential issue | 🟠 Major

Invalidate the React members cache here too.

confirmDisableCommenting() still only refetches the Ember member, so React can remain stale after this mutation. The new helper is already the shared path for the other member mutations, so this callback should use it as well.

Suggested fix
 afterDisable: () => {
     this.fetchMemberTask.perform(this.member.id);
+    this.invalidateMembersCache();
 }
🤖 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 187 - 193,
confirmDisableCommenting currently only refetches the Ember member via
this.fetchMemberTask.perform(this.member.id) so the React members list can
remain stale; update the afterDisable callback in confirmDisableCommenting to
also invoke the shared member cache invalidation helper used by the other member
mutations (the same helper other mutations call) so the React cache is
invalidated as well—keep the existing fetchMemberTask.perform call if needed for
Ember state, but ensure the shared invalidate-members-cache helper is called
from afterDisable.
ghost/admin/mirage/routes-test.js (1)

88-110: ⚠️ Potential issue | 🔴 Critical

This unconditional block will break 13+ legitimate limit=all callers across the codebase.

The guard at lines 88-90 throws before the endpoint-specific allowlist, blocking all limit=all requests unconditionally. However, many production code paths still actively use limit=all:

  • app/routes/posts.js, app/controllers/lexical-editor.js, app/utils/publish-options.js
  • app/services/members-utils.js, app/services/members-count-cache.js, app/services/limit.js
  • app/components/gh-members-recipient-select.js, app/components/posts/debug.js, app/components/koenig-lexical-editor.js, app/components/gh-psm-authors-input.js, app/components/gh-members-segment-select.js, app/components/gh-post-settings-menu/visibility-segment-select.js

This change will break existing features (newsletters, members, tiers, snippets, posts, users). Either remove the unconditional block or add specific allowlist rules for these endpoints before landing.

e2e/helpers/pages/admin/members/members-list-page.ts (1)

144-149: ⚠️ Potential issue | 🟠 Major

Add null check for download path before reading file.

download.path() can return null, and the as string cast masks this from TypeScript. Explicitly handle the null case before passing to readFileSync().

Proposed fix
    const download = await this.exportMembersData();
    const suggestedFilename = download.suggestedFilename();
    const downloadPath = await download.path();
-   const downloadContent = readFileSync(downloadPath as string, 'utf-8');
+   if (!downloadPath) {
+       throw new Error('Download path is unavailable in this test environment');
+   }
+   const downloadContent = readFileSync(downloadPath, 'utf-8');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers/pages/admin/members/members-list-page.ts` around lines 144 - 149,
In exportMembers(), handle the case where download.path() can return null before
calling readFileSync: call const downloadPath = await download.path(); check if
downloadPath is null (or undefined) and throw or return a clear error/empty
result, then only call readFileSync(downloadPath, 'utf-8'); update exportMembers
to reference exportMembersData(), suggestedFilename() and readFileSync so the
null path branch prevents passing a coerced string to readFileSync.
🤖 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/styles/layouts/members.css`:
- Around line 91-93: The rule is using the deprecated CSS property
grid-column-gap in the selector .gh-member-settings .gh-main-section.columns-3;
replace that property with the modern column-gap property (keep the same value
48px) so the stylesheet uses current CSS syntax and satisfies linters expecting
column-gap instead of grid-column-gap.

---

Outside diff comments:
In `@e2e/helpers/pages/admin/members/members-list-page.ts`:
- Around line 144-149: In exportMembers(), handle the case where download.path()
can return null before calling readFileSync: call const downloadPath = await
download.path(); check if downloadPath is null (or undefined) and throw or
return a clear error/empty result, then only call readFileSync(downloadPath,
'utf-8'); update exportMembers to reference exportMembersData(),
suggestedFilename() and readFileSync so the null path branch prevents passing a
coerced string to readFileSync.

In `@ghost/admin/app/controllers/member.js`:
- Around line 187-193: confirmDisableCommenting currently only refetches the
Ember member via this.fetchMemberTask.perform(this.member.id) so the React
members list can remain stale; update the afterDisable callback in
confirmDisableCommenting to also invoke the shared member cache invalidation
helper used by the other member mutations (the same helper other mutations call)
so the React cache is invalidated as well—keep the existing
fetchMemberTask.perform call if needed for Ember state, but ensure the shared
invalidate-members-cache helper is called from afterDisable.
🪄 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: 65af68c3-f6de-4e3c-bdc9-2930a5c217e5

📥 Commits

Reviewing files that changed from the base of the PR and between eb51a1d and 451a8e9.

⛔ Files ignored due to path filters (10)
  • ghost/admin/public/assets/icons/email-member.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/import-in-progress.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/members-all.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/members-outline.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/members-paid.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/members-post.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/members-segment.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/img/marketing/members-1.jpg is excluded by !**/*.jpg
  • ghost/admin/public/assets/img/marketing/members-2.jpg is excluded by !**/*.jpg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (99)
  • apps/admin/src/ember-bridge/ember-bridge.test.tsx
  • apps/admin/src/ember-bridge/ember-bridge.tsx
  • e2e/helpers/pages/admin/members/members-list-page.ts
  • e2e/helpers/pages/admin/members/members-page.ts
  • ghost/admin/app/components/gh-member-single-label-input.hbs
  • ghost/admin/app/components/gh-member-single-label-input.js
  • ghost/admin/app/components/gh-members-import-mapping-input.hbs
  • ghost/admin/app/components/gh-members-import-mapping-input.js
  • ghost/admin/app/components/gh-members-import-table.hbs
  • ghost/admin/app/components/gh-members-import-table.js
  • ghost/admin/app/components/gh-members-no-members.hbs
  • ghost/admin/app/components/gh-members-no-members.js
  • ghost/admin/app/components/members/filter-value.hbs
  • ghost/admin/app/components/members/filter-value.js
  • ghost/admin/app/components/members/filter.hbs
  • ghost/admin/app/components/members/filter.js
  • ghost/admin/app/components/members/filters/audience-feedback.js
  • ghost/admin/app/components/members/filters/columns/date-column.js
  • ghost/admin/app/components/members/filters/created-at.js
  • ghost/admin/app/components/members/filters/email-clicked.js
  • ghost/admin/app/components/members/filters/email-count.js
  • ghost/admin/app/components/members/filters/email-open-rate.js
  • ghost/admin/app/components/members/filters/email-opened-count.js
  • ghost/admin/app/components/members/filters/email-opened.js
  • ghost/admin/app/components/members/filters/email-sent.js
  • ghost/admin/app/components/members/filters/email.js
  • ghost/admin/app/components/members/filters/index.js
  • ghost/admin/app/components/members/filters/label.js
  • ghost/admin/app/components/members/filters/last-seen.js
  • ghost/admin/app/components/members/filters/name.js
  • ghost/admin/app/components/members/filters/next-billing-date.js
  • ghost/admin/app/components/members/filters/offers.js
  • ghost/admin/app/components/members/filters/plan-interval.js
  • ghost/admin/app/components/members/filters/relation-options/contains.js
  • ghost/admin/app/components/members/filters/relation-options/date.js
  • ghost/admin/app/components/members/filters/relation-options/index.js
  • ghost/admin/app/components/members/filters/relation-options/match.js
  • ghost/admin/app/components/members/filters/relation-options/number.js
  • ghost/admin/app/components/members/filters/signup-attribution.js
  • ghost/admin/app/components/members/filters/status.js
  • ghost/admin/app/components/members/filters/subscribed.js
  • ghost/admin/app/components/members/filters/subscription-attribution.js
  • ghost/admin/app/components/members/filters/subscription-start-date.js
  • ghost/admin/app/components/members/filters/subscription-status.js
  • ghost/admin/app/components/members/filters/tier.js
  • ghost/admin/app/components/members/list-item-column.hbs
  • ghost/admin/app/components/members/list-item-column.js
  • ghost/admin/app/components/members/list-item-loading.hbs
  • ghost/admin/app/components/members/list-item.hbs
  • ghost/admin/app/components/members/list-item.js
  • ghost/admin/app/components/members/modals/bulk-add-label.hbs
  • ghost/admin/app/components/members/modals/bulk-add-label.js
  • ghost/admin/app/components/members/modals/bulk-delete.hbs
  • ghost/admin/app/components/members/modals/bulk-delete.js
  • ghost/admin/app/components/members/modals/bulk-remove-label.hbs
  • ghost/admin/app/components/members/modals/bulk-remove-label.js
  • ghost/admin/app/components/members/modals/bulk-unsubscribe.hbs
  • ghost/admin/app/components/members/modals/bulk-unsubscribe.js
  • ghost/admin/app/components/modal-import-members.hbs
  • ghost/admin/app/components/modal-import-members.js
  • ghost/admin/app/components/modal-import-members/csv-file-mapping.hbs
  • ghost/admin/app/components/modal-import-members/csv-file-mapping.js
  • ghost/admin/app/components/modal-import-members/csv-file-select.hbs
  • ghost/admin/app/components/modal-import-members/csv-file-select.js
  • ghost/admin/app/components/modal-unsubscribe-members.hbs
  • ghost/admin/app/components/modal-unsubscribe-members.js
  • ghost/admin/app/components/offers/segment-select.hbs
  • ghost/admin/app/components/offers/segment-select.js
  • ghost/admin/app/components/tiers/segment-select.hbs
  • ghost/admin/app/components/tiers/segment-select.js
  • ghost/admin/app/controllers/member.js
  • ghost/admin/app/controllers/members.js
  • ghost/admin/app/controllers/members/import.js
  • ghost/admin/app/errors/member-import-error.js
  • ghost/admin/app/helpers/reset-query-params.js
  • ghost/admin/app/routes/member.js
  • ghost/admin/app/routes/members.js
  • ghost/admin/app/routes/members/import.js
  • ghost/admin/app/services/member-import-validator.js
  • ghost/admin/app/services/state-bridge.js
  • ghost/admin/app/styles/app-dark.css
  • ghost/admin/app/styles/app.css
  • ghost/admin/app/styles/components/filter-builder.css
  • ghost/admin/app/styles/layouts/dashboard.css
  • ghost/admin/app/styles/layouts/members.css
  • ghost/admin/app/templates/members.hbs
  • ghost/admin/app/templates/members/import.hbs
  • ghost/admin/mirage/config/members.js
  • ghost/admin/mirage/routes-test.js
  • ghost/admin/package.json
  • ghost/admin/tests/acceptance/members-test.js
  • ghost/admin/tests/acceptance/members/filter-test.js
  • ghost/admin/tests/acceptance/members/import-test.js
  • ghost/admin/tests/integration/components/gh-members-import-table-test.js
  • ghost/admin/tests/integration/components/modal-import-members-test.js
  • ghost/admin/tests/integration/services/member-import-validator-test.js
  • ghost/admin/tests/unit/components/members/filters/offers-test.js
  • ghost/admin/tests/unit/controllers/member-test.js
  • ghost/admin/tests/unit/services/state-bridge-test.js
💤 Files with no reviewable changes (91)
  • ghost/admin/app/routes/member.js
  • apps/admin/src/ember-bridge/ember-bridge.tsx
  • apps/admin/src/ember-bridge/ember-bridge.test.tsx
  • ghost/admin/app/components/members/filters/relation-options/date.js
  • ghost/admin/app/templates/members/import.hbs
  • ghost/admin/app/components/members/filters/relation-options/match.js
  • ghost/admin/app/components/members/filters/relation-options/contains.js
  • ghost/admin/app/components/members/filters/tier.js
  • ghost/admin/app/components/members/filters/created-at.js
  • ghost/admin/app/components/members/list-item-column.hbs
  • ghost/admin/app/components/gh-members-no-members.hbs
  • ghost/admin/app/styles/app.css
  • ghost/admin/app/routes/members/import.js
  • ghost/admin/app/components/members/filters/email-clicked.js
  • ghost/admin/app/components/members/filters/subscription-attribution.js
  • ghost/admin/app/components/members/filters/relation-options/number.js
  • ghost/admin/app/components/members/filters/label.js
  • ghost/admin/app/components/members/list-item.hbs
  • ghost/admin/app/components/members/filters/subscription-start-date.js
  • ghost/admin/app/components/members/modals/bulk-remove-label.hbs
  • ghost/admin/app/components/members/modals/bulk-delete.hbs
  • ghost/admin/app/components/members/list-item-loading.hbs
  • ghost/admin/app/components/members/filter-value.js
  • ghost/admin/app/components/members/filters/offers.js
  • ghost/admin/app/helpers/reset-query-params.js
  • ghost/admin/app/components/members/filters/last-seen.js
  • ghost/admin/app/components/members/filters/signup-attribution.js
  • ghost/admin/app/components/members/filters/email-opened.js
  • ghost/admin/app/components/members/filter.hbs
  • ghost/admin/mirage/config/members.js
  • ghost/admin/app/components/gh-members-no-members.js
  • ghost/admin/app/components/gh-members-import-mapping-input.hbs
  • ghost/admin/app/components/members/filters/email-opened-count.js
  • ghost/admin/app/components/members/filters/email-count.js
  • ghost/admin/app/components/members/filters/subscription-status.js
  • ghost/admin/app/components/modal-unsubscribe-members.hbs
  • ghost/admin/tests/unit/components/members/filters/offers-test.js
  • ghost/admin/app/components/members/modals/bulk-unsubscribe.hbs
  • ghost/admin/app/components/modal-import-members/csv-file-select.js
  • ghost/admin/app/components/members/filters/status.js
  • ghost/admin/app/components/gh-members-import-mapping-input.js
  • ghost/admin/app/styles/components/filter-builder.css
  • ghost/admin/tests/integration/components/gh-members-import-table-test.js
  • ghost/admin/app/components/gh-member-single-label-input.hbs
  • ghost/admin/app/components/members/filters/relation-options/index.js
  • ghost/admin/tests/integration/components/modal-import-members-test.js
  • ghost/admin/app/components/modal-unsubscribe-members.js
  • ghost/admin/app/styles/layouts/dashboard.css
  • ghost/admin/tests/integration/services/member-import-validator-test.js
  • ghost/admin/app/components/members/filters/plan-interval.js
  • ghost/admin/app/controllers/members.js
  • ghost/admin/app/components/members/filters/next-billing-date.js
  • ghost/admin/app/components/modal-import-members/csv-file-mapping.hbs
  • ghost/admin/app/components/members/list-item.js
  • ghost/admin/app/components/modal-import-members/csv-file-select.hbs
  • ghost/admin/app/components/offers/segment-select.js
  • ghost/admin/app/components/members/filter-value.hbs
  • ghost/admin/tests/acceptance/members-test.js
  • ghost/admin/app/routes/members.js
  • ghost/admin/app/components/members/filters/email-open-rate.js
  • ghost/admin/app/components/members/modals/bulk-add-label.hbs
  • ghost/admin/app/components/offers/segment-select.hbs
  • ghost/admin/app/components/members/filters/audience-feedback.js
  • ghost/admin/app/components/members/modals/bulk-remove-label.js
  • ghost/admin/app/components/modal-import-members.hbs
  • ghost/admin/app/services/member-import-validator.js
  • ghost/admin/tests/acceptance/members/import-test.js
  • ghost/admin/app/components/members/filter.js
  • ghost/admin/app/styles/app-dark.css
  • ghost/admin/app/components/modal-import-members.js
  • ghost/admin/app/components/members/filters/email-sent.js
  • ghost/admin/tests/acceptance/members/filter-test.js
  • ghost/admin/app/components/members/modals/bulk-delete.js
  • ghost/admin/app/components/members/modals/bulk-unsubscribe.js
  • ghost/admin/app/components/members/filters/email.js
  • ghost/admin/app/components/members/list-item-column.js
  • ghost/admin/app/components/members/modals/bulk-add-label.js
  • ghost/admin/app/templates/members.hbs
  • ghost/admin/app/errors/member-import-error.js
  • ghost/admin/app/components/tiers/segment-select.js
  • ghost/admin/app/components/gh-member-single-label-input.js
  • ghost/admin/app/controllers/members/import.js
  • ghost/admin/app/components/gh-members-import-table.js
  • ghost/admin/app/components/members/filters/subscribed.js
  • ghost/admin/app/components/tiers/segment-select.hbs
  • ghost/admin/app/components/members/filters/index.js
  • ghost/admin/app/components/members/filters/name.js
  • ghost/admin/package.json
  • ghost/admin/app/components/members/filters/columns/date-column.js
  • ghost/admin/app/components/gh-members-import-table.hbs
  • ghost/admin/app/components/modal-import-members/csv-file-mapping.js

Comment on lines +91 to 93
.gh-member-settings .gh-main-section.columns-3 {
grid-column-gap: 48px;
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify there are no deprecated `grid-column-gap` declarations left in CSS files.
# Expected result after fix: no matches.
rg -nP --type=css '\bgrid-column-gap\s*:'

Repository: TryGhost/Ghost

Length of output: 718


Replace deprecated grid gap property.

Line 92 uses deprecated grid-column-gap; use column-gap instead to satisfy current CSS/lint expectations.

Suggested fix
 .gh-member-settings .gh-main-section.columns-3 {
-    grid-column-gap: 48px;
+    column-gap: 48px;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.gh-member-settings .gh-main-section.columns-3 {
grid-column-gap: 48px;
}
.gh-member-settings .gh-main-section.columns-3 {
column-gap: 48px;
}
🧰 Tools
🪛 Stylelint (17.9.0)

[error] 92-92: Expected "grid-column-gap" to be "column-gap" (property-no-deprecated)

(property-no-deprecated)

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

In `@ghost/admin/app/styles/layouts/members.css` around lines 91 - 93, The rule is
using the deprecated CSS property grid-column-gap in the selector
.gh-member-settings .gh-main-section.columns-3; replace that property with the
modern column-gap property (keep the same value 48px) so the stylesheet uses
current CSS syntax and satisfies linters expecting column-gap instead of
grid-column-gap.

ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

React owns the members list and import routes now, so the old Ember route, controller, component, skipped test, and admin-only dependency surface can be deleted.
ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

Removing the Ember list surfaced orphaned helpers, Mirage upload mocks, legacy CSS, and public assets that are no longer referenced by the retained member detail or activity screens.
ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

The retained Ember member detail screen can no longer depend on the removed members controller, so it now invalidates the React members cache directly after member mutations.
ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

The retained Ember member detail screen should use the existing Ember bridge for React cache invalidation instead of reaching into the AdminX query client directly.
ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

Comments do not have an Ember model counterpart, so the retained member detail screen only emits member bridge updates after member mutations.
ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

The controller test now stubs the injected bridge dependency at the controller boundary instead of spying on the real service instance, which is not stable in the isolated Ember test owner.
@jonatansberg jonatansberg force-pushed the jonatan-ber-3580-remove-ember-members-implementation branch from 451a8e9 to fb3f204 Compare May 4, 2026 08:33
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

https://github.com/TryGhost/Ghost/blob/fb3f20433d644d58a7cf0f37b7d1fe727c64e71a/ghost/admin/public/assets/img/marketing/members-1.jpg#L1
P2 Badge Restore deleted members help-card image asset

Removing this image breaks the React members empty-state help cards, which still hardcode url(${assetRoot}img/marketing/members-1.jpg) and members-2.jpg in apps/posts/src/views/members/components/members-help-cards.tsx; after this commit those URLs resolve to missing files and the cards render with broken backgrounds in production. Please keep these assets (or update the component to use new asset paths) so the members onboarding UI does not regress.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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 `@e2e/helpers/pages/admin/members/members-page.ts`:
- Around line 5-8: Make the locator visibility consistent by changing the
declarations of newMemberButton and memberListItems to public readonly so they
match loadMoreButton and membersListScrollRoot; update the class member
declarations for newMemberButton and memberListItems (the Locator fields) to use
public readonly so tests can access them for assertions without TS visibility
errors.
🪄 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: 52c4a02e-0230-4e9e-a86a-e0156dcf7a24

📥 Commits

Reviewing files that changed from the base of the PR and between 451a8e9 and fb3f204.

⛔ Files ignored due to path filters (4)
  • ghost/admin/public/assets/icons/email-member.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/import-in-progress.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/members-all.svg is excluded by !**/*.svg
  • ghost/admin/public/assets/icons/members-outline.svg is excluded by !**/*.svg
📒 Files selected for processing (90)
  • apps/admin/src/ember-bridge/ember-bridge.test.tsx
  • apps/admin/src/ember-bridge/ember-bridge.tsx
  • e2e/helpers/pages/admin/members/members-list-page.ts
  • e2e/helpers/pages/admin/members/members-page.ts
  • ghost/admin/app/components/gh-member-single-label-input.hbs
  • ghost/admin/app/components/gh-member-single-label-input.js
  • ghost/admin/app/components/gh-members-import-mapping-input.hbs
  • ghost/admin/app/components/gh-members-import-mapping-input.js
  • ghost/admin/app/components/gh-members-import-table.hbs
  • ghost/admin/app/components/gh-members-import-table.js
  • ghost/admin/app/components/gh-members-no-members.hbs
  • ghost/admin/app/components/gh-members-no-members.js
  • ghost/admin/app/components/members/filter-value.hbs
  • ghost/admin/app/components/members/filter-value.js
  • ghost/admin/app/components/members/filter.hbs
  • ghost/admin/app/components/members/filter.js
  • ghost/admin/app/components/members/filters/audience-feedback.js
  • ghost/admin/app/components/members/filters/columns/date-column.js
  • ghost/admin/app/components/members/filters/created-at.js
  • ghost/admin/app/components/members/filters/email-clicked.js
  • ghost/admin/app/components/members/filters/email-count.js
  • ghost/admin/app/components/members/filters/email-open-rate.js
  • ghost/admin/app/components/members/filters/email-opened-count.js
  • ghost/admin/app/components/members/filters/email-opened.js
  • ghost/admin/app/components/members/filters/email-sent.js
  • ghost/admin/app/components/members/filters/email.js
  • ghost/admin/app/components/members/filters/index.js
  • ghost/admin/app/components/members/filters/label.js
  • ghost/admin/app/components/members/filters/last-seen.js
  • ghost/admin/app/components/members/filters/name.js
  • ghost/admin/app/components/members/filters/next-billing-date.js
  • ghost/admin/app/components/members/filters/offers.js
  • ghost/admin/app/components/members/filters/plan-interval.js
  • ghost/admin/app/components/members/filters/relation-options/contains.js
  • ghost/admin/app/components/members/filters/relation-options/date.js
  • ghost/admin/app/components/members/filters/relation-options/index.js
  • ghost/admin/app/components/members/filters/relation-options/match.js
  • ghost/admin/app/components/members/filters/relation-options/number.js
  • ghost/admin/app/components/members/filters/signup-attribution.js
  • ghost/admin/app/components/members/filters/status.js
  • ghost/admin/app/components/members/filters/subscribed.js
  • ghost/admin/app/components/members/filters/subscription-attribution.js
  • ghost/admin/app/components/members/filters/subscription-start-date.js
  • ghost/admin/app/components/members/filters/subscription-status.js
  • ghost/admin/app/components/members/filters/tier.js
  • ghost/admin/app/components/members/list-item-column.hbs
  • ghost/admin/app/components/members/list-item-column.js
  • ghost/admin/app/components/members/list-item-loading.hbs
  • ghost/admin/app/components/members/list-item.hbs
  • ghost/admin/app/components/members/list-item.js
  • ghost/admin/app/components/members/modals/bulk-add-label.hbs
  • ghost/admin/app/components/members/modals/bulk-add-label.js
  • ghost/admin/app/components/members/modals/bulk-delete.hbs
  • ghost/admin/app/components/members/modals/bulk-delete.js
  • ghost/admin/app/components/members/modals/bulk-remove-label.hbs
  • ghost/admin/app/components/members/modals/bulk-remove-label.js
  • ghost/admin/app/components/members/modals/bulk-unsubscribe.hbs
  • ghost/admin/app/components/members/modals/bulk-unsubscribe.js
  • ghost/admin/app/components/modal-import-members.hbs
  • ghost/admin/app/components/modal-import-members.js
  • ghost/admin/app/components/modal-import-members/csv-file-mapping.hbs
  • ghost/admin/app/components/modal-import-members/csv-file-mapping.js
  • ghost/admin/app/components/modal-import-members/csv-file-select.hbs
  • ghost/admin/app/components/modal-import-members/csv-file-select.js
  • ghost/admin/app/components/modal-unsubscribe-members.hbs
  • ghost/admin/app/components/modal-unsubscribe-members.js
  • ghost/admin/app/components/offers/segment-select.hbs
  • ghost/admin/app/components/offers/segment-select.js
  • ghost/admin/app/components/tiers/segment-select.hbs
  • ghost/admin/app/components/tiers/segment-select.js
  • ghost/admin/app/controllers/member.js
  • ghost/admin/app/controllers/members.js
  • ghost/admin/app/controllers/members/import.js
  • ghost/admin/app/errors/member-import-error.js
  • ghost/admin/app/helpers/reset-query-params.js
  • ghost/admin/app/routes/member.js
  • ghost/admin/app/routes/members.js
  • ghost/admin/app/routes/members/import.js
  • ghost/admin/app/services/member-import-validator.js
  • ghost/admin/app/services/state-bridge.js
  • ghost/admin/app/styles/app-dark.css
  • ghost/admin/app/styles/app.css
  • ghost/admin/app/styles/components/filter-builder.css
  • ghost/admin/app/styles/layouts/dashboard.css
  • ghost/admin/app/styles/layouts/members.css
  • ghost/admin/app/templates/members.hbs
  • ghost/admin/app/templates/members/import.hbs
  • ghost/admin/mirage/config/members.js
  • ghost/admin/mirage/routes-test.js
  • ghost/admin/package.json
💤 Files with no reviewable changes (84)
  • ghost/admin/app/routes/member.js
  • ghost/admin/app/components/modal-import-members/csv-file-select.js
  • ghost/admin/app/components/members/filters/offers.js
  • ghost/admin/app/components/members/filters/plan-interval.js
  • ghost/admin/app/components/members/filters/email-open-rate.js
  • ghost/admin/app/components/members/filters/subscription-status.js
  • ghost/admin/app/components/members/filters/relation-options/date.js
  • ghost/admin/app/components/modal-unsubscribe-members.hbs
  • ghost/admin/app/components/members/filters/email-opened.js
  • ghost/admin/app/components/members/filters/relation-options/number.js
  • ghost/admin/app/components/members/filters/relation-options/match.js
  • ghost/admin/app/components/modal-unsubscribe-members.js
  • ghost/admin/app/components/tiers/segment-select.hbs
  • ghost/admin/app/components/members/filters/next-billing-date.js
  • ghost/admin/app/components/members/list-item.hbs
  • ghost/admin/app/components/members/filters/status.js
  • ghost/admin/app/components/members/filters/tier.js
  • ghost/admin/app/components/members/filters/email-count.js
  • ghost/admin/app/errors/member-import-error.js
  • ghost/admin/app/components/members/list-item-column.hbs
  • ghost/admin/app/templates/members/import.hbs
  • ghost/admin/app/components/members/list-item-loading.hbs
  • ghost/admin/app/components/members/filters/created-at.js
  • ghost/admin/app/components/members/filters/relation-options/index.js
  • apps/admin/src/ember-bridge/ember-bridge.tsx
  • ghost/admin/app/components/members/filter.hbs
  • ghost/admin/app/components/gh-members-import-mapping-input.js
  • ghost/admin/package.json
  • ghost/admin/app/styles/components/filter-builder.css
  • ghost/admin/app/components/members/filters/last-seen.js
  • ghost/admin/app/routes/members/import.js
  • ghost/admin/app/components/members/list-item.js
  • ghost/admin/app/components/gh-member-single-label-input.hbs
  • ghost/admin/app/components/members/modals/bulk-add-label.hbs
  • ghost/admin/app/components/gh-members-import-mapping-input.hbs
  • ghost/admin/app/components/members/filters/email.js
  • ghost/admin/app/components/members/filters/email-sent.js
  • ghost/admin/app/components/modal-import-members/csv-file-select.hbs
  • ghost/admin/app/components/members/filters/name.js
  • ghost/admin/app/controllers/members/import.js
  • ghost/admin/app/components/members/filters/columns/date-column.js
  • ghost/admin/app/components/members/modals/bulk-delete.js
  • ghost/admin/app/components/members/filter-value.hbs
  • ghost/admin/app/components/members/filters/subscription-start-date.js
  • ghost/admin/app/components/gh-members-no-members.hbs
  • apps/admin/src/ember-bridge/ember-bridge.test.tsx
  • ghost/admin/app/components/members/modals/bulk-remove-label.hbs
  • ghost/admin/app/components/members/filter-value.js
  • ghost/admin/app/templates/members.hbs
  • ghost/admin/app/components/members/modals/bulk-remove-label.js
  • ghost/admin/app/components/modal-import-members/csv-file-mapping.js
  • ghost/admin/app/components/members/filters/signup-attribution.js
  • ghost/admin/app/styles/app-dark.css
  • ghost/admin/app/components/members/filters/subscription-attribution.js
  • ghost/admin/app/components/members/modals/bulk-unsubscribe.js
  • ghost/admin/app/controllers/members.js
  • ghost/admin/app/components/members/filters/subscribed.js
  • ghost/admin/app/components/members/filters/email-clicked.js
  • ghost/admin/app/components/members/modals/bulk-add-label.js
  • ghost/admin/app/components/gh-members-import-table.hbs
  • ghost/admin/app/components/modal-import-members/csv-file-mapping.hbs
  • ghost/admin/app/components/members/filters/email-opened-count.js
  • ghost/admin/app/components/members/filters/index.js
  • ghost/admin/app/components/members/list-item-column.js
  • ghost/admin/app/components/members/filter.js
  • ghost/admin/mirage/config/members.js
  • ghost/admin/app/components/members/modals/bulk-unsubscribe.hbs
  • ghost/admin/app/components/members/modals/bulk-delete.hbs
  • ghost/admin/app/components/gh-members-no-members.js
  • ghost/admin/app/components/gh-members-import-table.js
  • ghost/admin/app/components/members/filters/audience-feedback.js
  • ghost/admin/app/components/offers/segment-select.hbs
  • ghost/admin/app/styles/layouts/dashboard.css
  • ghost/admin/app/components/modal-import-members.hbs
  • ghost/admin/app/components/offers/segment-select.js
  • ghost/admin/app/helpers/reset-query-params.js
  • ghost/admin/app/routes/members.js
  • ghost/admin/app/styles/app.css
  • ghost/admin/app/components/gh-member-single-label-input.js
  • ghost/admin/app/components/tiers/segment-select.js
  • ghost/admin/app/components/members/filters/relation-options/contains.js
  • ghost/admin/app/components/members/filters/label.js
  • ghost/admin/app/components/modal-import-members.js
  • ghost/admin/app/services/member-import-validator.js
✅ Files skipped from review due to trivial changes (2)
  • ghost/admin/app/services/state-bridge.js
  • e2e/helpers/pages/admin/members/members-list-page.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/admin/app/controllers/member.js

Comment on lines 5 to 8
readonly newMemberButton: Locator;
public readonly loadMoreButton: Locator;
public readonly membersListScrollRoot: Locator;
readonly memberListItems: Locator;
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 | ⚡ Quick win

Inconsistent public readonly on locators used externally.

newMemberButton (line 5) and memberListItems (line 8) are declared as just readonly, while loadMoreButton and membersListScrollRoot (lines 6–7) are public readonly. Per the coding guidelines, locators used with assertions must be public readonly. The inconsistency means any test directly asserting against newMemberButton or memberListItems would get a TypeScript private visibility error.

♻️ Proposed fix
-    readonly newMemberButton: Locator;
+    public readonly newMemberButton: Locator;
     public readonly loadMoreButton: Locator;
     public readonly membersListScrollRoot: Locator;
-    readonly memberListItems: Locator;
+    public readonly memberListItems: Locator;

As per coding guidelines: "Page Objects should expose locators as public readonly when used with assertions."

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

In `@e2e/helpers/pages/admin/members/members-page.ts` around lines 5 - 8, Make the
locator visibility consistent by changing the declarations of newMemberButton
and memberListItems to public readonly so they match loadMoreButton and
membersListScrollRoot; update the class member declarations for newMemberButton
and memberListItems (the Locator fields) to use public readonly so tests can
access them for assertions without TS visibility errors.

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/ember-bridge/ember-bridge.test.tsx (1)

140-168: 💤 Low value

Consider adding unmount() for consistency with other tests.

Other similar tests in this file (e.g., lines 114, 233) call unmount() after assertions. While the test will still pass without it, adding the unmount call would maintain consistency with the test suite patterns.

♻️ Suggested fix
         await waitFor(() => {
             const queries = queryClient.getQueryCache().getAll();
             const changedQueries = queries.filter(q => ['MembersResponseType', 'CommentsResponseType'].includes(q.queryKey[0] as string));
             const unchangedQueries = queries.filter(q => !['MembersResponseType', 'CommentsResponseType'].includes(q.queryKey[0] as string));

             expect(changedQueries.every(q => q.state.isInvalidated)).toBe(true);
             expect(unchangedQueries.every(q => !q.state.isInvalidated)).toBe(true);
         });
+
+        unmount();
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin/src/ember-bridge/ember-bridge.test.tsx` around lines 140 - 168,
Add a call to the rendered hook's unmount() for consistency: after the final
assertions in the "invalidates member and comment queries when member commenting
changes" test (where you call renderHook(() => useEmberDataSync(), { wrapper }))
capture the return value (which provides unmount) and invoke unmount() at the
end of the test to mirror other tests that call unmount() and ensure cleanup of
the hook instance created by renderHook.
🤖 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/ember-bridge/ember-bridge.test.tsx`:
- Around line 140-168: Add a call to the rendered hook's unmount() for
consistency: after the final assertions in the "invalidates member and comment
queries when member commenting changes" test (where you call renderHook(() =>
useEmberDataSync(), { wrapper })) capture the return value (which provides
unmount) and invoke unmount() at the end of the test to mirror other tests that
call unmount() and ensure cleanup of the hook instance created by renderHook.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7eb5c3dd-eaa2-43d7-9c1b-67e7acbd4a8d

📥 Commits

Reviewing files that changed from the base of the PR and between fb3f204 and 251d1ab.

📒 Files selected for processing (7)
  • apps/admin/src/ember-bridge/ember-bridge.test.tsx
  • apps/admin/src/ember-bridge/ember-bridge.tsx
  • ghost/admin/app/components/members/modals/disable-commenting.js
  • ghost/admin/app/controllers/member.js
  • ghost/admin/app/services/state-bridge.js
  • ghost/admin/tests/unit/controllers/member-test.js
  • ghost/admin/tests/unit/services/state-bridge-test.js
💤 Files with no reviewable changes (1)
  • ghost/admin/app/components/members/modals/disable-commenting.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/admin/tests/unit/controllers/member-test.js

ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

Member commenting changes need to refresh both React member and comment data, but Ember UI should not call the AdminX query client or know React query data type details directly.
@jonatansberg jonatansberg force-pushed the jonatan-ber-3580-remove-ember-members-implementation branch from 251d1ab to 84f4b8f Compare May 4, 2026 08:53
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

https://github.com/TryGhost/Ghost/blob/84f4b8fb5c4ff2f0796adf7a05bb282f58507b5a/ghost/admin/public/assets/img/marketing/members-1.jpg#L1
P2 Badge Keep marketing images required by React members cards

Deleting these marketing JPEGs breaks the React members empty-state help cards, which still hardcode assetRoot + "img/marketing/members-1.jpg" and members-2.jpg in apps/posts/src/views/members/components/members-help-cards.tsx. After this commit, opening the members empty state shows broken image panels for users because no replacement assets are provided.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

The retained Ember member detail and activity screens no longer reference these styles after the list, import, and older member UI paths were removed.
ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation

The extra cleanup was outside the bridge follow-up scope, so this restores the PR to the bridge-focused changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant