Skip to content

fix: generate inline taxonomy slugs server-side - #2506

Merged
ascorbic merged 2 commits into
mainfrom
codex/taxonomy-unicode-slugs-2355
Aug 17, 2026
Merged

fix: generate inline taxonomy slugs server-side#2506
ascorbic merged 2 commits into
mainfrom
codex/taxonomy-unicode-slugs-2355

Conversation

@ascorbic

@ascorbic ascorbic commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Moves automatic taxonomy-term slug generation to the server for inline creation. When callers omit a slug, the API uses the shared Unicode slug utility from #2505 and resolves collisions against the database with locale-scoped numeric suffixes. Manually supplied slugs remain unchanged and still return a conflict when already taken.

The entry-editor taxonomy sidebar now sends only the label (plus locale), while the taxonomy manager omits the slug only while its slug field remains auto-generated.

Depends on #2505.

Closes #2355

Type of change

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

Checklist

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenAI Codex (GPT-5)

Screenshots / test output

  • pnpm --filter @emdash-cms/admin --filter emdash build
  • pnpm typecheck
  • pnpm lint
  • pnpm lint:json | jq '.diagnostics | length'0
  • pnpm --filter emdash exec vitest run tests/unit/taxonomies/term-slug-generation.test.ts tests/unit/taxonomies/taxonomy-crud.test.ts tests/unit/taxonomies/term-reorder.test.ts tests/integration/taxonomies/taxonomy-locale-terms.test.ts tests/integration/mcp/taxonomy.test.ts → 122 passed.
  • pnpm --filter @emdash-cms/admin exec vitest run tests/components/TaxonomySidebar.test.tsx tests/components/TaxonomyManager.test.tsx → 31 passed.

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks codex/taxonomy-unicode-slugs-2355. Updated automatically when the playground redeploys.

@changeset-bot

changeset-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0de54d1

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/admin 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/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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🔵 In progress
View logs
emdash-playground 31eb04c Aug 16 2026, 10:23 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🔵 In progress
View logs
emdash-demo-do 31eb04c Aug 16 2026, 10:23 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🔵 In progress
View logs
emdash-demo-cache 31eb04c Aug 16 2026, 10:23 PM

@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 PR correctly moves taxonomy-term slug generation to the server so Unicode labels and inline creation work with the shared Unicode slug utility. The approach matches the architecture: the schema makes slug optional, the handler derives it, and the admin components omit the slug only when it is auto-generated. I checked the changed handler, repository, schemas, MCP tool, admin components, tests, and changeset.

Main concern: handleTermCreate is not atomic between choosing a generated slug and inserting it. Two concurrent requests can pick the same slug; the top-level catch then returns a generic TERM_CREATE_ERROR instead of a structured conflict or a recovered suffix. That's a real robustness gap in the new default path.

Secondary: the new schema-only unit test is tautological; the dialect-aware handler tests already cover the real behavior.

Everything else looks good: Kysely is used safely, locale filtering is present, the changeset follows conventions, there are no logged-out hot-path changes, and no new user-facing untranslated strings.


Findings

  • [needs fixing] packages/core/src/api/handlers/taxonomies.ts:910-914

    generateUniqueSlug probes the database and then the handler inserts, so two concurrent requests can both choose the same generated slug. This catch block then turns the resulting unique-constraint failure into an opaque TERM_CREATE_ERROR, meaning the new default create path can fail with a generic message instead of a structured CONFLICT or a recovered suffix.

    At minimum, detect the unique-constraint violation and return a structured conflict; better, retry generation a bounded number of times so the auto-generator can recover from its own race.

    	} catch (error) {
    		if (error instanceof Error && error.message.includes("UNIQUE constraint failed")) {
    			return {
    				success: false,
    				error: {
    					code: "CONFLICT",
    					message: `Term with slug '${slug}' already exists in taxonomy '${taxonomyName}'`,
    				},
    			};
    		}
    		return {
    			success: false,
    			error: { code: "TERM_CREATE_ERROR", message: "Failed to create term" },
    		};
    	}
    
  • [suggestion] packages/core/tests/unit/taxonomies/term-slug-generation.test.ts:13-15

    This test asserts that the Zod schema accepts a label-only object, which mostly restates the schema definition rather than validating behavior. The dialect-aware handler tests below already cover the real behavior (server derives slug from label). Drop this standalone schema-only test or fold it into a handler-level assertion.

@github-actions github-actions Bot added area/admin area/core size/L review/awaiting-author Reviewed; waiting on the author to respond labels Aug 16, 2026
Copilot AI lite review requested due to automatic review settings August 16, 2026 15:51
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Aug 16, 2026
@ascorbic

Copy link
Copy Markdown
Collaborator Author

Addressed both automated-review findings in 4c5dd49.

  • Generated term creation now retries slug selection/insertion up to 16 times only when the database reports a slug-specific unique violation. If contention persists, the handler returns a structured CONFLICT instead of TERM_CREATE_ERROR; explicit-slug behavior is unchanged.
  • Replaced the schema-only assertion with a real six-request concurrent-create regression. It failed before the fix and now verifies all requests succeed with 同時 through 同時-5.

Verified with the 122-test taxonomy/MCP suite, pnpm typecheck, pnpm lint, and pnpm lint:json | jq '.diagnostics | length' (0).

@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 re-review looks good. The server-side slug generation for inline taxonomy terms correctly moves Unicode slug derivation into handleTermCreate while preserving explicit-slug conflict behavior.

I re-checked the handler, repository, schemas, MCP tool, admin components, and tests. The previous race condition is now handled by a bounded retry loop around generateUniqueSlug, plus structured CONFLICT responses for unique-constraint failures. The tautological schema-only unit test was replaced with dialect-aware handler tests covering Unicode slugs, numeric suffix collisions, concurrent recovery, locale scoping, and explicit-slug conflicts.

Kysely usage remains safe, locale filtering is present where the term will be inserted, the changeset follows conventions, no logged-out hot paths are affected, and no untranslated admin UI strings were added. No remaining blockers.

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

Copilot AI 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.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Moves inline taxonomy term slug generation from the admin client to the core server/API (and MCP tool), deriving Unicode slugs when omitted and resolving collisions with locale-scoped numeric suffixes.

Changes:

  • Make term slug optional across API/MCP schemas and admin client types; server derives slug when omitted.
  • Add server-side unique-slug generation + retry-on-collision logic for concurrent creates.
  • Add unit/integration/admin tests covering Unicode slugs, collisions, and “omit slug” client behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/core/tests/unit/taxonomies/term-slug-generation.test.ts Adds unit coverage for Unicode slug derivation, collisions, locale scoping, and concurrency.
packages/core/tests/integration/mcp/taxonomy.test.ts Verifies the MCP tool derives a Unicode slug when slug is omitted.
packages/core/src/mcp/server.ts Makes MCP taxonomy_create_term slug optional and documents omission behavior.
packages/core/src/database/repositories/taxonomy.ts Introduces generateUniqueSlug backed by DB collision checks and numeric suffixing.
packages/core/src/api/schemas/taxonomies.ts Makes REST createTerm slug optional with description metadata.
packages/core/src/api/handlers/taxonomies.ts Implements server-side slug derivation, retry loop, and unique-violation handling.
packages/admin/tests/components/TaxonomySidebar.test.tsx Ensures sidebar inline create omits slug and lets server derive it.
packages/admin/tests/components/TaxonomyManager.test.tsx Ensures manager omits slug when auto-generated, and sends slug when manually edited.
packages/admin/src/lib/api/taxonomies.ts Updates CreateTermInput to allow optional slug.
packages/admin/src/components/TaxonomySidebar.tsx Stops client-side slugification for inline creation.
packages/admin/src/components/TaxonomyManager.tsx Omits slug on create when still auto-generated.
.changeset/fix-inline-unicode-taxonomies.md Adds a changeset describing the fix and collision behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/core/src/database/repositories/taxonomy.ts
Comment thread packages/core/src/api/handlers/taxonomies.ts
Comment thread packages/core/src/api/handlers/taxonomies.ts
Comment thread packages/core/src/database/repositories/taxonomy.ts
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 16, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/registry-verification

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 0de54d1

@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch from 4c5dd49 to 262e7ca Compare August 16, 2026 16:43
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review needs-rebase labels Aug 16, 2026
@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch from 262e7ca to 6f73c3c Compare August 16, 2026 17:25
@github-actions github-actions Bot removed the review/approved Approved; no new commits since label Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch 2 times, most recently from 77cda11 to 83b2b12 Compare August 16, 2026 19:12
@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch 2 times, most recently from 79701f3 to 49dd293 Compare August 16, 2026 19:59
@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch from 49dd293 to 9102dbc Compare August 16, 2026 20:21
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Aug 16, 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 PR makes the right change: automatic slug generation for taxonomy terms now happens server-side in handleTermCreate, using the shared Unicode slugify utility, locale-scoped collision detection, and a bounded retry loop for concurrent races. Explicit slugs still get the existing per-locale conflict behavior. This is the minimal, architecturally correct fix for #2355.

I re-reviewed the changed files and the surrounding code paths:

  • API contract: createTermBody now makes slug optional, matching the handler change; the REST term-create route and the MCP taxonomy_create_term tool both pass the new shape through unchanged.
  • Slug generation: TaxonomyRepository.generateUniqueSlug uses a parameterized Kysely query scoped to (name, locale), with a safe LIKE prefix and numeric suffix logic. No SQL interpolation issues.
  • Race handling: handleTermCreate retries generated slug inserts on unique-constraint violations and surfaces any final conflict as a structured CONFLICT response. The error-message heuristic checks for both SQLite and Postgres unique-violation phrases and requires the column name slug to be present.
  • Admin callers: TaxonomySidebar no longer sends a slug; TaxonomyManager only sends a slug when autoSlug is false. Both call sites still pass the entry/admin locale so the server scopes the term correctly.
  • Tests: The new dialect-aware handler tests cover Unicode scripts, emoji fallback, numeric suffix collisions, concurrent recovery, locale scoping, and explicit-slug conflicts. Admin component tests verify the request body shape. No tautological/assert-the-implementation tests were added.
  • AGENTS.md conventions: All admin strings remain Lingui-wrapped (no new user-visible strings). No changes to logged-out routes. A correct changeset is present. Comments are minimal and describe invariants, not reviewers.

No remaining blockers. This is a clean, focused bug fix that does what it says.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 16, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Aug 16, 2026
@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch from 9102dbc to 57ba47b Compare August 16, 2026 20:33
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Aug 16, 2026
@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch 2 times, most recently from f4191c9 to a1b55e0 Compare August 16, 2026 21:05
Base automatically changed from codex/unicode-slugs-2271 to main August 16, 2026 22:23
@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch from a1b55e0 to 31eb04c Compare August 16, 2026 22:23
@ascorbic
ascorbic force-pushed the codex/taxonomy-unicode-slugs-2355 branch from 31eb04c to 0de54d1 Compare August 16, 2026 22:25
@ascorbic
ascorbic merged commit 5289385 into main Aug 17, 2026
68 of 69 checks passed
@ascorbic
ascorbic deleted the codex/taxonomy-unicode-slugs-2355 branch August 17, 2026 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admin slugifier strips all non-ASCII, so inline taxonomy-term creation 400s for CJK-only labels (slug: "")

2 participants