fix(static-pages): stop createPage/duplicatePage from generating colliding slugs/routes
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

createPage() derived its slug from array length (custom-page-${length+1}):
create, delete, create again reliably reproduces a duplicate slug against a
surviving page. duplicatePage() had the same issue with a fixed '-copy'
suffix - duplicating the same page twice collides with the first duplicate.
Both trip the duplicate-slug/route validator on a page the user never
directly touched.

Added uniqueValue() (append -2, -3, ... until free) and used it for both.
Verified live via window.ng.getComponent(): reproduced the exact collision
scenario pre-fix, confirmed no duplicates post-fix (custom-page-6-2,
about-us-copy/about-us-copy-2).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-17 18:49:36 +04:00
parent eee6695d7f
commit 2e683cc872

View File

@@ -156,8 +156,28 @@ export class StaticPagesEditorComponent {
this.mediaPickerOpen = false;
}
/**
* Appends `-2`, `-3`, ... to `candidate` until it's not in `existing`.
* createPage()/duplicatePage() both used to derive slugs/routes from array
* length or a fixed "-copy" suffix, which reproducibly collides with an
* existing page (create, delete, create again; or duplicate the same page
* twice) - the duplicate then trips the duplicate-slug/route validator
* on a page the user never touched.
*/
private uniqueValue(candidate: string, existing: Set<string>): string {
if (!existing.has(candidate)) {
return candidate;
}
let suffix = 2;
while (existing.has(`${candidate}-${suffix}`)) {
suffix++;
}
return `${candidate}-${suffix}`;
}
createPage(): void {
const slug = `custom-page-${this.allPages().length + 1}`;
const existingSlugs = new Set(this.allPages().map(page => page.slug));
const slug = this.uniqueValue(`custom-page-${this.allPages().length + 1}`, existingSlugs);
const page: ContentPage = {
id: `page-${Date.now()}`,
slug,
@@ -189,11 +209,13 @@ export class StaticPagesEditorComponent {
if (!source) {
return;
}
const existingSlugs = new Set(this.allPages().map(page => page.slug));
const existingRoutes = new Set(this.allPages().map(page => page.route));
const clone: ContentPage = {
...source,
id: `page-${Date.now()}`,
slug: `${source.slug}-copy`,
route: `${source.route}-copy`,
slug: this.uniqueValue(`${source.slug}-copy`, existingSlugs),
route: this.uniqueValue(`${source.route}-copy`, existingRoutes),
order: this.allPages().length + 1,
status: 'draft',
translations: Object.fromEntries(Object.entries(source.translations).map(([locale, t]) => [locale, { ...t }])),