Skip to content

Fixed stale sidebar member counts after member mutations#28191

Merged
jonatansberg merged 8 commits into
mainfrom
codex/sidebar-member-count-invalidation
Jun 4, 2026
Merged

Fixed stale sidebar member counts after member mutations#28191
jonatansberg merged 8 commits into
mainfrom
codex/sidebar-member-count-invalidation

Conversation

@jonatansberg

@jonatansberg jonatansberg commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Fixed stale sidebar member counts by keeping member-changing flows on the existing MembersResponseType invalidation path.
  • Added a dedicated framework useMemberCount() hook that owns the lightweight /members/?limit=1 sidebar count query.
  • Added a narrow cache reconciliation path in useBrowseMembersInfinite() so a fresh unfiltered members-list total updates the existing sidebar count cache when both queries share a query client.
  • Kept filtered and searched member-list totals isolated from the sidebar count because those totals intentionally differ.

Root Cause

Some member-changing paths updated local screen state or used raw upload calls without going through the shared query invalidation mechanics. That left the sidebar's lightweight member-count query stale while the main Members screen could show a newer total.

There was also no shared owner for the sidebar count query shape, so sidebar code knew that member count was implemented as a limit=1 members browse request.

Implementation Notes

  • Member add/import/delete paths now invalidate MembersResponseType queries through the existing admin-x-framework mechanics.
  • The sidebar consumes useMemberCount() from @tryghost/admin-x-framework/api/members, keeping the limit=1 query details inside the framework API.
  • useBrowseMembersInfinite() updates only the existing sidebar count cache entry, and only when the list query is successful, unfiltered, unsearched, non-placeholder/non-previous data, and at least as fresh as the sidebar cache.
  • The self-correction does not create the sidebar query if it is absent and does not estimate count deltas locally.

Validation

  • pnpm --filter @tryghost/admin-x-framework exec eslint src/api/members.ts test/unit/api/members.test.tsx
  • pnpm exec vitest run test/unit/api/members.test.tsx --coverage=false
  • pnpm exec tsc --noEmit
  • pnpm --filter @tryghost/admin-x-framework build
  • pnpm --filter @tryghost/posts exec eslint src/views/members/members.tsx
  • pnpm exec vitest run test/unit/views/members --coverage=false
  • pnpm --filter @tryghost/admin exec eslint src/layout/app-sidebar/nav-content.tsx
  • Earlier targeted import/member-action/Ember bridge tests from the initial invalidation pass.

ref https://linear.app/tryghost/issue/BER-3510/investigate-and-fix-stale-member-count-in-sidebar

ref https://linear.app/tryghost/issue/BER-3510/investigate-and-fix-stale-member-count-in-sidebar

Member count changes can flow through several admin paths, so imports and Ember member changes now use the existing query invalidation path instead of leaving the sidebar count stale.
@jonatansberg jonatansberg changed the title [codex] Fixed stale sidebar member counts after member mutations Fixed stale sidebar member counts after member mutations May 27, 2026
@jonatansberg jonatansberg marked this pull request as ready for review May 27, 2026 13:29
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a typed useImportMembers mutation, import types/type-guards, and a FormData builder; centralizes membersPath and member-count query helpers; introduces an internal useBrowseMembersInfiniteQuery and wraps it with useBrowseMembersInfinite to sync a sidebar member-count cache entry. Refactors ImportMembersModal to call useImportMembers.mutateAsync and handle typed responses/errors. Updates tests to cover API request shape, cache invalidation, sidebar count sync/no-sync cases, Ember bridge invalidation, modal integration, and expanded error handling.

Suggested reviewers

  • weylandswart
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing stale sidebar member counts after mutations by implementing cache invalidation and reconciliation.
Description check ✅ Passed The description comprehensively explains the problem, root cause, implementation approach, and validation steps, all directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 codex/sidebar-member-count-invalidation

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.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4b34b7481

ℹ️ 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/tryghost/issue/BER-3510/

The members list can receive a fresher unfiltered total than the lightweight sidebar count query, so the shared members hook now reconciles the existing sidebar cache without page-level wiring.
ref https://linear.app/tryghost/issue/BER-3510/

The sidebar should not need to know that member count is implemented with a lightweight members browse query, so the framework members API now owns the count-specific hook and cache key details.
ref https://linear.app/tryghost/issue/BER-3510/

The behavior coverage is still focused on invalidation, cache reconciliation, and import response handling, but repeated setup has been moved into small helpers so the PR carries less low-signal test diff.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1544451f57

ℹ️ 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 apps/admin-x-framework/src/api/members.ts
ref https://linear.app/tryghost/issue/BER-3510/

Member CSV uploads are non-idempotent, so transient network failures must not replay the same import request.
ref https://linear.app/tryghost/issue/BER-3510/

The sidebar count reconciliation guard for placeholder members data is intentional, so the members API tests cover it without expanding the scoped background import behavior.
@jonatansberg jonatansberg force-pushed the codex/sidebar-member-count-invalidation branch from 7ee85dc to 54903c8 Compare June 2, 2026 08:20

@rob-ghost rob-ghost left a comment

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.

If you've tested and it works I'm happy, I just had a couple of code structure opinions but nothing blocking.

Comment thread apps/admin-x-framework/src/api/members.ts Outdated
Comment thread apps/admin-x-framework/src/api/members.ts Outdated
Comment thread apps/admin-x-framework/src/api/members.ts Outdated
Comment thread apps/admin-x-framework/src/api/members.ts Outdated
Comment thread apps/admin-x-framework/src/api/members.ts Outdated
ref https://linear.app/tryghost/issue/BER-3510/

Applies PR review feedback and a follow-up hardening pass; no behaviour change
for the documented server contract.

- dropped the `stats?: undefined` discriminator and moved import_label
  nullability onto the response types so the import union reads honestly
- discriminate the upload response on meta.stats, keeping the guard defensive
  (returns false on a malformed payload rather than throwing)
- replaced the confusing "unexpected response" throw in the import modal with an
  exhaustive complete-vs-accepted branch that trusts the documented 2xx contract
- extracted the cache sync into a named useSyncMemberCountFromList hook with
  named guard conditions, patching only the `total` the sidebar reads and
  stamping it with the list's fetch time so a newer count fetch still wins
- the import modal test now keeps the real type-guards via importActual instead
  of duplicating their logic
ref https://linear.app/tryghost/issue/BER-3510/

The previous comments ascribed intent to the cache ("the sidebar may not be
mounted", "didn't ask for"); reworded to plainly state we only update the count
query when it already exists.
@jonatansberg jonatansberg enabled auto-merge (squash) June 4, 2026 08:51
@jonatansberg jonatansberg merged commit 5edf3dd into main Jun 4, 2026
49 checks passed
@jonatansberg jonatansberg deleted the codex/sidebar-member-count-invalidation branch June 4, 2026 09:03
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.

2 participants