Repository navigation
fix(core): keep term translations under their term's parent - #3368
Conversation
A term's parent and position belong to its translation group: updating the parent through any locale already moves every locale. Creating a translation did not follow that rule. `TaxonomyRepository.create()` with `translationOf` stored whatever parent the caller passed, so a translation created without one became a root term in its locale while the term stayed nested in the others. The REST API, MCP, seed apply and the WordPress import all reach that path without a parent. A translation created without a parent now takes the group's parent and position. One created under a different parent moves the whole group there, as an update does, so the tested case of translating a term straight under another parent keeps working and no longer splits the term across parents. An update also skipped the group write when the addressed row already had the requested parent, so a locale left under another parent could not be repaired from the locale that was right. It now writes the group whenever any row differs.
🦋 Changeset detectedLatest commit: d94e447 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
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 |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-test
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-loader
@emdash-cms/registry-moderation
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Overlapping PRsThis PR modifies files that are also changed by other open PRs: This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
There was a problem hiding this comment.
This is the right fix for a real data-integrity problem: translations and their source term should share one parent/position, and the previous code let a translation become a root term (or land anywhere the caller specified). The change centralizes the invariant in TaxonomyRepository — a translation with no parentId inherits the source’s parent and sort order, an explicit different parent moves the whole translation group, and an update that names the current parent now repairs drifted siblings. The cycle check is correctly extended to create-with-translation so the move-the-group behavior can’t introduce cycles. All four code paths called out in the description (REST, MCP, plugin API, seed, WordPress import) flow through TaxonomyRepository.create/handleTermCreate and inherit the fix; no per-caller changes were needed.
I read the diff and the changed files end-to-end, traced the callers through the REST route, MCP tool, plugin context, seed apply engine, and WordPress WXR importer, and checked the new tests. The implementation uses parameterized Kysely queries throughout, adds no new SELECT on logged-out routes (the only new read, groupHasOtherParent, runs on authenticated updates), and invalidates the taxonomy caches on writes. The regression tests exercise the create-inherit, create-move-group, update repair-drift, and translation-under-descendant rejection behaviors, including one seed-specific case.
I found no logic bugs, no SQL-injection or auth regressions, no missing locale filters, no logged-out query-count increase, and no comment or changeset issues that would mislead a reader. The prose changes in the public docs and changeset accurately describe the new behavior.
What does this PR do?
Keeps a translated term under the same parent as the term it translates. Creating a translation stored whatever parent the caller passed, so one created without a parent became a root term in its locale while the term stayed nested elsewhere. The REST API, MCP, seed apply and the WordPress import all create translations that way.
A translation created without a parent now takes its term's parent and position. One created under another parent moves the term in every locale, as a parent update already does. An update naming the parent its row already has now also moves translations left elsewhere, so existing drift can be repaired from any locale. The admin already creates translations under the source's parent, so that path is unchanged. Logged-out routes run no new query.
Part of #2525. Independent of #3360, which covers taxonomy definitions; either can merge first.
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain. (n/a: no admin UI change)AI-generated code disclosure
Screenshots / test output
Not applicable (no UI change). Four of the five new tests in
term-reorder.test.tsandapply.test.tsfailed before the change; the fifth pins the cycle check that a translation's move now relies on. Theterm-reorder.test.tsones run on SQLite and PostgreSQL.