fix(static-pages): backfill enabled/status in export, fix missing i18n key
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

Independent review pass over the Static Pages Module sprint (M1-M7),
including live browser E2E per the task's own verification checklist.

- ProjectEditorFacade.normalize() now backfills enabled/status on
  record-format static pages (defaulting missing values to enabled+published,
  same rule ContentPageService.normalizePage applies for display - mirrored
  rather than imported, to avoid a project-editor <-> content-management
  circular dependency since ContentManagementFacade already depends on this
  facade). Found live: exporting a page that predates this sprint and was
  never touched/re-saved in the current session produced JSON missing
  enabled/status entirely - the editor UI and storefront resolver both
  normalize-on-read so nothing was actually broken live, but Export/Import
  fidelity should match what the editor shows. Verified fixed live (export
  now includes "enabled":true,"status":"published" for an untouched legacy
  page) and via the full gate.
- Added the missing adminCategories.chooseImage i18n key (interface +
  en/ru/hy). Found live: the media-picker "choose image" button rendered as
  the literal string "adminCategories.chooseImage" - a pre-existing,
  repo-wide bug (5 templates reference this key; none of the locale files
  ever defined it) that I propagated into a 3rd/4th/5th... well, 2 new
  occurrences by copying the existing branding-section/footer-section
  pattern into static-pages-editor. Fixed the actual defect (missing
  translation) rather than renaming the key, which would have required
  touching 2 unrelated admin components outside this sprint's scope.

Live-verified this pass: Static Pages editor renders with all new fields;
create page works (page count 4->confirmed); device preview toggles
desktop/tablet/mobile widths correctly; navigation "Insert page link"
creates a real type:'staticPage' nav item end-to-end (confirmed in the
exported JSON); export includes all Sprint X+2 fields after the fix; no
console errors throughout.

Gate: tsc --noEmit, npm test (57/57), arch:check, build all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-17 11:27:29 +04:00
parent 82b4a7849a
commit feda0f685f
5 changed files with 48 additions and 1 deletions

View File

@@ -552,7 +552,37 @@ export class ProjectEditorFacade {
const leftOrder = Math.min(...left.sections.map(section => section.order)); const leftOrder = Math.min(...left.sections.map(section => section.order));
const rightOrder = Math.min(...right.sections.map(section => section.order)); const rightOrder = Math.min(...right.sections.map(section => section.order));
return leftOrder - rightOrder; return leftOrder - rightOrder;
}) }),
staticPages: this.normalizeStaticPagesShape(config.staticPages),
}; };
} }
/**
* Backfills `enabled`/`status` on record-format static pages at the
* bootstrap-normalization layer, mirroring (not importing - would create a
* project-editor <-> content-management circular dependency, since
* ContentManagementFacade already depends on this facade) the same default
* ContentPageService.normalizePage applies for display: a page with no
* enabled/status predates this field and was always live, so it defaults
* to enabled+published rather than silently disappearing. Without this,
* exportBootstrap() on a page nobody has touched this session would omit
* both fields (present in the editor's display via normalize-on-read, but
* literally absent from the stored/exported JSON) - the storefront
* resolver was never at risk (it normalizes on every read), but export
* fidelity should match what the editor shows.
*/
private normalizeStaticPagesShape(staticPages: BootstrapConfig['staticPages']): BootstrapConfig['staticPages'] {
if (!staticPages || Array.isArray(staticPages)) {
return staticPages;
}
const result: typeof staticPages = {};
for (const [key, page] of Object.entries(staticPages)) {
result[key] = {
...page,
enabled: page.enabled ?? true,
status: page.status ?? 'published',
};
}
return result;
}
} }

View File

@@ -921,6 +921,9 @@ export const en: Translations = {
emptyTitle: 'No files yet', emptyTitle: 'No files yet',
emptyDescription: 'Upload an image or document to get started.', emptyDescription: 'Upload an image or document to get started.',
}, },
adminCategories: {
chooseImage: 'Choose image',
},
adminProducts: { adminProducts: {
emptyTitle: 'No products found', emptyTitle: 'No products found',
emptyDescription: 'Try adjusting your filters, or create a new product.', emptyDescription: 'Try adjusting your filters, or create a new product.',

View File

@@ -916,6 +916,9 @@ export const hy: Translations = {
emptyTitle: 'Ֆայլեր դեռ չկան', emptyTitle: 'Ֆայլեր դեռ չկան',
emptyDescription: 'Վերբեռնեք պատկեր կամ փաստաթուղթ սկսելու համար։', emptyDescription: 'Վերբեռնեք պատկեր կամ փաստաթուղթ սկսելու համար։',
}, },
adminCategories: {
chooseImage: 'Ընտրել պատկեր',
},
adminProducts: { adminProducts: {
emptyTitle: 'Ապրանքներ չեն գտնվել', emptyTitle: 'Ապրանքներ չեն գտնվել',
emptyDescription: 'Փոխեք ֆիլտրերը կամ ստեղծեք նոր ապրանք։', emptyDescription: 'Փոխեք ֆիլտրերը կամ ստեղծեք նոր ապրանք։',

View File

@@ -916,6 +916,9 @@ export const ru: Translations = {
emptyTitle: 'Пока нет файлов', emptyTitle: 'Пока нет файлов',
emptyDescription: 'Загрузите изображение или документ, чтобы начать.', emptyDescription: 'Загрузите изображение или документ, чтобы начать.',
}, },
adminCategories: {
chooseImage: 'Выбрать изображение',
},
adminProducts: { adminProducts: {
emptyTitle: 'Товары не найдены', emptyTitle: 'Товары не найдены',
emptyDescription: 'Измените фильтры или создайте новый товар.', emptyDescription: 'Измените фильтры или создайте новый товар.',

View File

@@ -923,6 +923,14 @@ export interface Translations {
// state consistency fix introduces - NOT the full adminProducts/ // state consistency fix introduces - NOT the full adminProducts/
// adminUsers/adminMonitoring/adminAnalytics translation surface, which is // adminUsers/adminMonitoring/adminAnalytics translation surface, which is
// still missing entirely (see docs/KNOWN-ISSUES.md, deferred to Sprint 29). // still missing entirely (see docs/KNOWN-ISSUES.md, deferred to Sprint 29).
// Shared across branding/footer/static-pages/admin-product/admin-category media
// pickers - despite the namespace name, this is a generic "choose image" label,
// not category-specific. Renaming is a separate cleanup outside this sprint's
// scope; the key was previously referenced by 5 templates but never defined
// anywhere, so it rendered as the literal string "adminCategories.chooseImage".
adminCategories: {
chooseImage: string;
};
adminProducts: { adminProducts: {
emptyTitle: string; emptyTitle: string;
emptyDescription: string; emptyDescription: string;