Content dashboard with real published/draft/SEO-health/completion metrics and a recommended-next-action; static pages editor rebuilt as visual page cards (icon/status/SEO badge/last edited) with legal pages surfaced first; full-page editor grouped into Content/SEO/Sharing/Advanced tabs, raw HTML moved behind an Advanced disclosure; hero image now supports alt text and caption; content-health-widget is a reusable checklist+completion component.
Extends the fieldError() wiring pattern (already used in theme/general/branding)
to the remaining sections that have matching ProjectValidator fieldKeys:
- languages: localization.supportedLocales (no-languages, missing-translations)
- homepage: pages (empty-homepage, missing-widget, duplicate-routes)
- widgets: pages (invalid-widget-config)
- navigation: navigation.header (duplicate-nav-links)
- static-pages: staticPages (duplicate-slugs, invalid-css)
Header/footer/features sections have no matching validator issues today,
so nothing to wire there yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Milestone 5 of the Static Pages Module sprint.
- New StaticPagePreviewComponent: client-side, sanitized HTML preview at
desktop/tablet(768px)/mobile(375px) widths, entirely without navigation or
publish. Uses the same DomSanitizer.sanitize(SecurityContext.HTML, ...)
pattern as the real storefront renderer (StaticPageComponent), so what
authors preview here matches what will actually render live.
- Wired into static-pages-editor as a per-page collapsible "Preview" toggle,
showing the default-locale (or first available) translation's html/title.
- i18n: staticPages.previewDesktop/Tablet/Mobile/Toggle in interface + en/ru/hy.
Gate: tsc --noEmit, npm test (57/57), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Milestone 4 of the Static Pages Module sprint.
- heroImage/thumbnail now wire through the existing MediaPickerComponent
(same media-field-row + "choose image" pattern as branding-section), not
plain URL text alone. gallery stays a lightweight CSV field ("future
ready" per the brief - no dedicated multi-upload UI this sprint).
- ProjectEditorFacade: add originalStaticPages, a narrow computed exposing
the originally loaded/published staticPages snapshot (mirrors the facade's
existing pattern of small single-purpose computeds).
- StaticPagesEditorComponent: isModified(page) diffs a page against its
normalized original snapshot, reusing ContentManagementFacade.pages() for
normalization rather than reimplementing it. Renders as an amber
"unsaved changes" badge per page.
Draft/published status UI, publish/unpublish actions, and the plain-text
media fields landed already in M2; this milestone completes M4's remaining
scope (visual media picker + modified indicator) without duplicating that
work.
Gate: tsc --noEmit, npm test (57/57), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Milestone 2 of the Static Pages Module sprint.
- StaticPagesEditorComponent: duplicate page, confirm-before-delete/bulk-
delete (matches the resetDraft confirm pattern), route/enabled/customTemplate/
media(hero/thumbnail/gallery) fields wired into the card, per-page publish/
unpublish action, status + duplicate-route/invalid-html/invalid-seo badges.
- Search (id/slug/route/title across all locales), filter by status
(draft/published) and by locale (hides pages missing a translation for the
selected locale) - all local computed() filters, no new service.
- Bulk selection (per-row + select-all-visible checkboxes) with bulk delete/
enable/disable/publish/unpublish, one updateBootstrap() call each.
- Correctness note: introduced `allPages` (unfiltered) vs `pages` (filtered
view) computeds. Every mutation (create/duplicate/delete/move/bulk) reads
from allPages(), never the filtered pages() - reading from the filtered
view would have silently deleted whatever an active search/filter hid on
the next persist(). Documented inline on persist() as a guardrail for
future edits.
- Fixed a template compile error found by the build gate: Angular templates
don't support inline arrow functions in binding expressions
((ngModelChange)="...map(v => v.trim())..." failed to parse) - moved the
gallery CSV-parsing into a component method (updateGallery).
- SEO robots field added to the page card (validated against a known-token
set from M1).
- i18n: staticPages.* extended (search/filter/bulk/route/enabled/status/
media/robots/disabled labels) across the interface + en/ru/hy.
Gate: tsc --noEmit, npm test (57/57), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace global duplicate-slug/empty-title banners with per-page indicators:
a danger badge next to the offending page's heading, plus inline error text
on the specific pageId/slug app-form-field. Made ContentPageService.normalizeSlug
public (was private) so the component can match validation results to a given
page's normalized slug without duplicating the normalization logic.
Verified in browser: setting a duplicate slug live shows both the header
badge and the inline field error immediately.
Completes Sprint 3 (Static Page Generator): DRY cleanup, Design System
adoption, SEO field coverage, per-page validation UX.
ContentPage.seo (title/description/keywords/canonical/ogTitle/ogDescription/
ogImage) was already modeled and serialized to bootstrap JSON but had no
editable UI. Added updateSeo() to the component and a translated SEO fieldset
using app-form-field/app-input. Added seoSection/seoTitle/seoDescription/
seoKeywords/seoCanonical/seoOgTitle/seoOgDescription/seoOgImage translation
keys to en/ru/hy and the Translations type.
Verified in browser at /edit/static-pages: SEO section renders with
translated labels for all three mock pages.
Replace raw button/input elements with app-button, app-input, app-card,
app-form-field, app-badge, app-empty-state across the static pages CMS editor.
Checkboxes left native (no checkbox primitive built yet).
Also fix pre-existing bug in ContentPageService.normalizePages: iterating
Object.values(config) lost the record key, so legacy-shaped bootstrap entries
without explicit id/slug fields (e.g. mock bootstrap.json's about-us,
privacy-policy, terms-of-service) crashed normalizeSlug(undefined). Now
falls back to the record key for id/slug/title.
Verified in browser at /edit/static-pages: renders correctly, primitives
styled per tenant CSS vars, no console errors after fix.