Skip to content

fix: preserve source locale when duplicating content - #2303

Merged
khoinguyenpham04 merged 1 commit into
emdash-cms:mainfrom
edrpls:fix/duplicate-preserves-locale
Aug 3, 2026
Merged

fix: preserve source locale when duplicating content#2303
khoinguyenpham04 merged 1 commit into
emdash-cms:mainfrom
edrpls:fix/duplicate-preserves-locale

Conversation

@edrpls

@edrpls edrpls commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes ContentRepository.duplicate() dropping the source entry's locale. It generated the copy's unique slug scoped to the source locale, then called create() without a locale, so the fallback landed every copy in en regardless of the source row's locale.

Two observable consequences on non-English content, both hit in production on an es-canonical site (emdash 0.31.1):

  • The admin content list is scoped to the active locale, so clicking Duplicate appears to do nothing — the copy is born under en and is invisible in the list the editor is looking at. Stray -copy rows accumulate (18 on the audited production DB).
  • Because the slug-uniqueness check ran against the source locale while the insert landed in en, duplicating could crash with a UNIQUE(slug, locale) constraint violation when the generated slug already existed in en.

The fix passes locale: original.locale ?? undefined through to create(), which also makes the slug-uniqueness scoping consistent with the inserted row. Duplicates keep getting a fresh translation group (a duplicate is a new entry, not a translation) — that was already create()'s default behavior, and a new test locks it in so it can't regress.

Found during a measured database audit of a production deployment (the Macabro festival site).

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 test passes (or targeted tests for my change)
  • 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). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main. — n/a: no admin UI strings changed
  • 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 Fable 5 (Claude Code)

Screenshots / test output

Failing first (on main, before the fix):

× duplicate() preserves the source item's locale
  → expected 'en' to be 'es'
× duplicate() does not collide with the generated slug in another locale
  → UNIQUE constraint failed: ec_post.slug, ec_post.locale

After the fix:

Test Files  3 passed (3)
     Tests  141 passed (141)   (i18n.test.ts, content.test.ts, content-handlers.test.ts)

🤖 Generated with Claude Code

duplicate() built the copy's slug against the source row's locale but
inserted the row without one, so create()'s fallback landed every copy
in 'en'. On locale-scoped admin lists the copy silently vanished, and
the mis-scoped slug-uniqueness check could trip the UNIQUE(slug, locale)
constraint when the generated slug already existed in 'en'.

Pass the source locale through to create(). Duplicates keep their own
fresh translation group — a copy is a new entry, not a translation —
which create() already does by default; a test now locks that in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: de95137

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

@github-actions github-actions Bot added area/core size/M review/needs-review No maintainer or bot review yet labels Jul 31, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 31, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/registry-verification

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: de95137

@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 narrowly-scoped, correct bug fix. ContentRepository.duplicate() already generated a unique slug scoped to original.locale, but it called create() without a locale, so SQLite's default en landed every copy in the wrong locale (and could cause a (slug, locale) unique-constraint collision when the generated slug existed in en). Passing locale: original.locale ?? undefined makes the slug check and the insert consistent.

I read the diff, the full repository implementation and input types, the duplicate route/handler, and the existing duplicate tests. The change:

  • Uses the existing CreateContentInput.locale field, so no type or API surface changes are needed.
  • Correctly handles null locale values via ?? undefined (the input type doesn't accept null).
  • Keeps the fresh-translation-group behavior, which create() already enforces.
  • Adds three focused integration tests that reproduce the reported symptoms: missing copy in source locale, fresh translation group, and cross-locale slug collision.

No AGENTS.md conventions are violated: there are no new logged-out queries, no raw SQL interpolation, no UI string changes, the changeset is present and written for users, and the tests exercise observable behavior rather than asserting implementation details back at themselves. The code is clean.

@edrpls
edrpls force-pushed the fix/duplicate-preserves-locale branch from 6fae716 to de95137 Compare July 31, 2026 18:12
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-review No maintainer or bot review yet labels 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.

Thanks!

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

Labels

area/core review/approved Approved; no new commits since size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants