Skip to content

fix(core): make term-list counts opt-out for the editor picker - #2243

Merged
khoinguyenpham04 merged 3 commits into
emdash-cms:mainfrom
MA2153:fix/term-list-counts-opt-out
Jul 31, 2026
Merged

fix(core): make term-list counts opt-out for the editor picker#2243
khoinguyenpham04 merged 3 commits into
emdash-cms:mainfrom
MA2153:fix/term-list-counts-opt-out

Conversation

@MA2153

@MA2153 MA2153 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

The content editor's taxonomy sidebar (TaxonomySidebar, rendered by ContentSettingsPanel on every editor open with no staleTime) lists terms to pick from and never renders a usage count. It shares GET /_emdash/api/taxonomies/:name/terms with the Taxonomies settings page, which does render counts — and handleTermList aggregated counts unconditionally. Opening any entry therefore ran the content_taxonomies × ec_* UNION ALL aggregate once per applicable taxonomy, with one branch per collection the taxonomy declares:

SELECT taxonomy_id, SUM(count) FROM (
  SELECT ct.taxonomy_id, COUNT(*) FROM content_taxonomies AS ct
  JOIN "ec_resources" AS e ON e.id = ct.entry_id WHERE ... GROUP BY ct.taxonomy_id
  UNION ALL ... -- one branch per declared collection
) AS per_collection GROUP BY taxonomy_id

The endpoint now takes an includeCounts query param, defaulting to true. When false, the aggregate is skipped and count is omitted from each term rather than reported as a misleading 0. The editor sidebar passes includeCounts=false; the Taxonomies settings page keeps the default and still shows real counts.

#2219 made the public render path demand-driven (getTaxonomyTerms({ includeCounts })); this is the admin-side counterpart, and the param name matches.

Compatibility: the default is unchanged, so existing API consumers see identical responses. The one ripple is type-level — TermWithCount.count becomes number | undefined, so TS consumers reading it must handle undefined. The only consumer in this repo (TaxonomyManager.tsx) already did ({term.count || 0}).

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes (pnpm lint:json → 0 diagnostics, matching the pre-change baseline)
  • pnpm test passes (full packages/core suite: 5022 passed, 3 skipped, 0 failed)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable) — n/a, no new strings; the one touched call site keeps its existing msg -wrapped error. No messages.po changes included.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion — n/a, bug fix

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 5 (Claude Code)

Screenshots / test output

New test asserts on the SQL actually executed (logging Kysely, filtering for the aggregate's per_collection subquery alias) — a response-shape assertion alone can't prove the work was skipped. Written first, failing on the two opt-out cases before the fix.

 ✓ tests/unit/taxonomies/term-list-counts.test.ts (4 tests)
   ✓ counts by default, so existing callers are unaffected
   ✓ omits the aggregate and the count field when the caller opts out
   ✓ honours ?includeCounts=false on the route
   ✓ still counts on the route when the param is absent

 Test Files  1 passed (1)
      Tests  4 passed (4)

Full core suite:

 Test Files  388 passed | 1 skipped (389)
      Tests  5022 passed | 3 skipped (5025)

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 996d498

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2243

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2243

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2243

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2243

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2243

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2243

emdash

npm i https://pkg.pr.new/emdash@2243

create-emdash

npm i https://pkg.pr.new/create-emdash@2243

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2243

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2243

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2243

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2243

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2243

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2243

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2243

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2243

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2243

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2243

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2243

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2243

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2243

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2243

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2243

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2243

commit: 996d498

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

This is the right fix in the right place. The admin terms endpoint was sharing its expensive visible-count aggregate between the Taxonomies settings page (which needs counts) and the content editor’s taxonomy picker (which doesn’t). Adding an includeCounts query flag with a backward-compatible default and having the editor opt out mirrors the existing public-side getTaxonomyTerms API and directly fixes the editor-open regression.

I read the full diff and the changed files, traced the admin call sites in TaxonomySidebar and TaxonomyManager, checked the route’s authorization/CSRF wiring, query-param parsing, and OpenAPI registration. The code is clean:

  • includeCounts defaults to true, so existing callers see identical responses.
  • handleTermList skips fetchVisibleTermCounts only when opted out, and omits count from the term object rather than returning a misleading 0.
  • The editor sidebar always passes includeCounts=false.
  • The TermWithCount type and OpenAPI response schema correctly become count?: number.
  • A changeset is included, and the new tests assert the actual aggregate behavior, not just response shape.

No AGENTS.md convention violations (localization, RTL, SQL safety, API envelope shape, authorization, locale filtering, index discipline, changesets, logged-out query counts, or comment discipline) stood out.

No blocking issues — LGTM.

@github-actions github-actions Bot added review/approved Approved; no new commits since review/needs-rereview Author pushed changes since the last review and removed review/needs-review No maintainer or bot review yet review/approved Approved; no new commits since labels Jul 26, 2026

@khoinguyenpham04 khoinguyenpham04 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

just one thing before merge. the editor and TaxonomyManager need separate cache keys since one fetches counts and the other doesn’t.

Comment thread packages/admin/src/components/TaxonomySidebar.tsx
MA2153 and others added 2 commits July 31, 2026 13:56
The content editor's taxonomy sidebar lists terms to pick from and never
renders a usage count, but it shares GET /_emdash/api/taxonomies/:name/terms
with the Taxonomies settings page, which does. handleTermList aggregated
counts unconditionally, so every editor open ran one
`content_taxonomies x ec_*` UNION ALL per applicable taxonomy — a branch for
each collection the taxonomy declares.

Add an `includeCounts` query param (default true, so existing callers and the
settings page are unchanged). When false the aggregate is skipped and `count`
is omitted from each term rather than reported as a misleading 0. The editor
sidebar passes includeCounts=false.

emdash-cms#2219 made the public render path demand-driven; this is the admin-side
counterpart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khoinguyenpham04
khoinguyenpham04 force-pushed the fix/term-list-counts-opt-out branch from 9706f5b to 0e00ec9 Compare July 31, 2026 12:56
The editor's taxonomy picker and the Taxonomies settings page read the same
terms endpoint under the same react-query key, but the picker now opts out of
counts. Within the app's 1 minute staleTime, the settings page mounting after
an editor open was served the cached count-free list and rendered every term's
count as 0 until something invalidated it.

Add the count mode to both query keys. The existing prefix invalidations still
match, so creating, editing or deleting a term refreshes both consumers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added size/L and removed size/M labels Jul 31, 2026
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Jul 31, 2026

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

This is a re-review of a PR I previously approved, and the current state is still the right fix in the right place. The additional changes since the prior review — adding the includeCounts mode to the TanStack Query cache keys and adding packages/admin/tests/components/taxonomy-term-cache.test.tsx — correctly close the only remaining cache-coherence risk: without the count mode in the key, the editor sidebar's count-free list would be served to the Taxonomies settings page, which renders counts.

I re-read the full diff and the changed files, traced the admin call sites, and checked the route, handler, schema, OpenAPI registration, and tests against AGENTS.md conventions. Everything looks clean:

  • includeCounts defaults to true at the API schema, route, and handler levels, so existing callers get identical responses.
  • The editor sidebar hardcodes ?includeCounts=false; the Taxonomies settings page still relies on the server default and gets real counts.
  • TermWithCount.count and the OpenAPI response schema are correctly optional.
  • The query keys now distinguish count modes, and invalidations use broad prefixes that still invalidate both variants.
  • The new tests assert observable behavior (SQL executed, cache isolation across components), not implementation trivia.
  • No localization, RTL, SQL-safety, authorization, locale-filtering, logged-out-query-count, changeset, or comment-discipline issues stand out.

No blocking issues — LGTM.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 31, 2026

@khoinguyenpham04 khoinguyenpham04 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM! Thanks @MA2153

@khoinguyenpham04
khoinguyenpham04 merged commit 4a49262 into emdash-cms:main Jul 31, 2026
57 checks passed
@emdashbot emdashbot Bot mentioned this pull request Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/admin area/core review/needs-rereview Author pushed changes since the last review size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants