- use ariaCurrentWhenActive on routerLinkActive so the active tab exposes
aria-current="page"
- restyle editor-nav-link with the same --primary-color/--bg-primary/
--border-color/--text-primary/--space-*/--transition-fast tokens used
across shared/ui/*
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- checkboxes -> app-toggle, wrap in SectionCard
- toggleUserExperience/toggleProductFeature typed without 'as any' (optional
chaining on the already-typed UserExperienceConfig/ProductPageConfig union)
- removed dead, broken toggleRecentViewed method (unused, not wired to the
template; toggleUserExperience('recentlyViewed', ...) is the live path)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- navigation-section: add LocaleTabs; label input now reads/writes the
active locale's translation via a new editableLabel() helper instead of
always the default locale. facade.updateNavLinkLabel() gained an optional
locale param (defaults to current default locale, so existing callers
are unaffected) and correctly promotes a plain-string label into a
per-locale map when writing a non-default locale.
- languages-section: wrap in SectionCard, add LocaleTabs for consistency.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- header-section: raw checkboxes -> app-toggle, wrap in SectionCard
- footer-section: replace unvalidated pipe-delimited textareas for payment
icons/social links with KeyValueEditor + MediaPickerComponent, add missing
footer logo picker, validate social link URLs (http/https), wrap in SectionCard
- i18n: add footer logo / key-value-editor labels and URL validation message
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 29, scoped to what wasn't already covered by concurrent work in
this session.
- dead code check: grepped console.log/console.debug/console.warn/
debugger/TODO/FIXME across features/admin/** - none found. tsc
--noUnusedLocals --noUnusedParameters over features/admin/** - clean,
no dangling imports/params.
- verified tsc --noEmit, ng build, arch:check:boundaries, and
arch:check:cycles all pass against the current working tree
- added CHANGELOG.md and RELEASE-NOTES.md at repo root summarizing
Sprints 20-28
Translation validation is deliberately not duplicated here - the ~178
missing adminXxx.* i18n keys are already logged in docs/KNOWN-ISSUES.md
and being addressed there. No lint script exists in package.json, so a
lint pass isn't applicable.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Completes the rest of Sprint 28 on top of the earlier admin-scoped
a11y/skeleton pass (576f260):
- SeoService.resetToDefaults() now reads real bootstrap.seo.default /
branding instead of hardcoded placeholder text + a broken
/og-image.jpg reference; auto-reapplies via an effect() whenever
bootstrap (re)loads, same pattern as UiRuntimeFacade.
- New public/sitemap.xml (static baseline, documented per-tenant-dynamic
limitation) + public/robots.txt Sitemap directive and admin/editor
Disallow rules.
- Global prefers-reduced-motion override in styles.scss covering every
existing hover-transform/fade-in/shimmer animation in one place.
- New adminProducts/adminUsers/adminMonitoring/adminAnalytics
empty-state i18n keys (en/ru/hy) for this sprint's skeleton/empty-state
consistency fixes.
- docs/KNOWN-ISSUES.md: logged a newly-found, much larger pre-existing
gap (~178 missing adminXxx.* i18n keys across the whole admin
backoffice) - deferred to Sprint 29's translation validation, not
fixed here.
- docs/BACKEND.md: new item 17 (sitemap generation gap).
- docs/ADMIN.md, docs/SPRINT-PLAN.md: rewritten Sprint 28 sections to
describe the full, combined scope (both commits) instead of the
earlier admin-only framing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 28, scoped to admin/* (user decision — full marketplace audit
declined in favor of a bounded pass over the 8 admin features from
Sprints 20-27).
- a11y: aria-label added to every bare <select> not already inside a
<label> across categories/products/orders/transactions/users/monitoring
- loading states: app-skeleton rows/cards added to list pages that
previously rendered blank during the initial fetch (categories, orders,
transactions, users, monitoring's event feed, analytics summary cards)
- admin-dashboard-card's custom shimmer CSS replaced with the shared
SkeletonComponent (same visual result, one less duplicated animation)
- bundle-size budget warning (~198kB over) confirmed pre-existing —
present at Sprint 20's first build before any admin/* code existed,
and new admin pages are all lazy-loaded — documented as out of scope
for this pass rather than chased
docs/ADMIN.md + docs/SPRINT-PLAN.md updated with the scope decision and
what was explicitly not done (Lighthouse, animations, SEO/sitemap,
storefront/editor a11y).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Quick Actions rendered raw i18n keys instead of translated labels for the
Users/Monitoring/Analytics actions. Also reverts useMockData back to its
pre-session value (false) after manual local verification.
Adds docs/KNOWN-ISSUES.md to track bugs found during manual QA, deferred
for a batch fix after the sprint.
Sprint 27.
New features/admin/analytics/ module + net-new /:lang/backoffice/analytics
route + Dashboard Quick Action.
- revenue/orders/avg-order-value/sales-over-time/top-products computed by
composing AdminOrdersLocalGateway (Sprint 23's seeded mock orders) - real
aggregation over mock data, not a separate fabricated dataset
- products/categories counts from AdminProductsLocalGateway/
AdminCategoriesLocalGateway
- visitors/funnels/heatmaps render pending-backend badges (no analytics
pipeline exists anywhere in this system) rather than fabricated numbers,
same convention as the Sprint 19 dashboard's pre-Sprint-23 Orders/Revenue
cards
- plain div-bar chart (no charting library), 7/30/90-day range toggle,
CSV export
docs/ADMIN.md + docs/BACKEND.md (new item 16) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 26.
New features/admin/monitoring/ module + net-new /:lang/backoffice/monitoring
route + Dashboard Quick Action.
- Health section reuses AdminDashboardFacade.healthChecks directly (real
data, unchanged since Sprint 19) instead of duplicating the logic
- unified AdminMonitoringEvent feed covering audit/security/login/
failed-login/api/error/warning, category filter + search, 40 seeded
synthetic entries (no logging backend exists anywhere in this system)
- mock queue depth/status cards, mock webhook delivery log
- intentionally kept separate from Sprint 24's per-transaction audit and
Sprint 25's per-user audit - different scopes, no consolidation attempted
Also fixed a real type error: AdminDashboardQuickActionId's union was
missing 'users' and 'monitoring' (added when wiring those Quick Actions),
caught by ng build's template type-checking even though plain tsc --noEmit
passed - a reminder that ng build is the authoritative check here.
docs/ADMIN.md + docs/BACKEND.md (new item 15) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 25.
New features/admin/users/ module, net-new /:lang/backoffice/users route +
Dashboard Quick Action.
- users: name, Telegram username, scope (marketplace vs office admin),
role (inline change), status (active/invited/suspended), last login
- 4 built-in roles (owner/admin/editor/viewer) with flat permission lists
- invitations: email + role + scope form, pending list + revoke (no email
actually sends - local record only)
- passwordless login confirmed already real (AdminAuthService Telegram QR,
docs/BACKEND.md item 1) - linked, not reimplemented
- per-user mock session list (device/IP/last-active, revoke) - flagged as
mock since the real AdminAuthService only ever tracks the current
browser's session
- per-user audit log dialog (role/status changes), same pattern as
Sprint 24's per-transaction audit, intentionally separate from the
system-wide log planned for Sprint 26
docs/ADMIN.md + docs/BACKEND.md (new item 14) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 24.
New features/admin/transactions/ module. AdminTransactionsLocalGateway
derives one synthetic transaction per Sprint 23's seeded mock order rather
than a separate dataset, keeping order numbers/totals consistent across
the two mock feature areas.
- list: search, status filter, type filter (payment/refund/qr_payment),
pagination, CSV export
- retry failed transactions (appends an audit entry)
- fraud flag toggle
- per-transaction audit log (creation/retry/fraud-flag-change), viewed via
dialog - intentionally separate from the system-wide audit/security log
planned for Sprint 26 (Monitoring)
- wired into /:lang/backoffice/transactions, replacing the coming-soon
placeholder
docs/ADMIN.md + docs/BACKEND.md (new item 13) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 23.
New features/admin/orders/ module, same container/facade/service split as
admin/products and admin/categories.
- AdminOrder model + AdminOrdersLocalGateway seeding 24 deterministic
synthetic orders (no real order data source exists anywhere in this
repo - explicitly a placeholder, not a mock of production volume)
- list: search, status filter, pagination, CSV export (client-side Blob
download)
- detail: customer/payment/shipping, itemized total, status timeline,
change-status dropdown, refund request + cancel (window.confirm-gated),
separate customer-facing vs internal notes, print invoice via
window.print() with @media print hiding non-invoice chrome
- wired into /:lang/backoffice/orders(/:id), replacing the coming-soon
placeholder
docs/ADMIN.md + docs/BACKEND.md updated; dashboard's Orders/Revenue cards
(Sprint 19) remain intentionally un-wired to this mock and still render
pending-backend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 22.
- MediaAsset gains folder (flat) and MediaListParams gains folder/tag
filters; MediaRepository.listFolders() derives the folder list from
existing records
- upload validation: 10MB size cap, mime allow-list (jpeg/png/webp/gif/
svg+xml/pdf), real error messages surfaced through MediaLibraryFacade
instead of a generic swallowed string
- SVG uploads are sanitized (script tags and on*= attributes stripped)
before storage
- raster images (excl. gif) are downscaled to a 2000px max dimension and
re-encoded via canvas before storage - compression, not a crop UI
- tag editing (window.prompt, comma-separated) via
MediaLibraryFacade.updateTags()
- MediaPickerComponent wired into Project Editor branding (logo, compact
logo, favicon) alongside its existing category/product usage - confirmed
no image fields exist on Static Pages or as a dedicated hero field to
wire
docs/ADMIN.md updated with the new Sprint 22 section including the storage
abstraction note (MediaRepository was already the abstraction).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 21.
- archived (soft archive/restore, distinct from visible) with an
include-archived list filter
- barcode field alongside sku
- variants: lightweight name|price|quantity list, same textarea-parse
convention as specifications/attributes
- relatedProductIds: checkbox picker in the editor
- gallery images now added/removed via the shared MediaPickerComponent
instead of a raw URL textarea
- read-only discounted-price preview in the editor
- infinite-scroll toggle on the list (loadMore() appends a page instead
of replacing it; pagination UI swaps for a Load more button)
- category dropdown now sourced from AdminCategoriesGateway (Sprint 20)
instead of AdminProductsLocalGateway's own BackofficeDataService seed
docs/ADMIN.md + docs/BACKEND.md updated with the new field list and the
known trade-off that related-products search is scoped to the currently
loaded page, not the full catalog.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 20. Adds features/admin/categories/ (model, gateway interface +
local gateway, facade, list/editor pages), mirroring the admin/products
container/facade/service split.
- indented hierarchy view + native HTML5 drag-and-drop reorder
- visibility toggle, item counter, empty state, include-deleted filter
- editor: slug uniqueness validation, translations, SEO fields, breadcrumb
preview, image via existing MediaPickerComponent
- soft delete/restore, blocked when a category has children or items
- draft/publish status + localStorage draft recovery (mirrors Project
Editor autosave) + CanDeactivate unsaved-changes guard
- wired into app.routes.ts (replaces the categories coming-soon placeholder)
- docs/ADMIN.md + docs/BACKEND.md updated with the new gap detail
Not yet done: admin/products' category dropdown still reads from its own
AdminProductsGateway.loadCategories() rather than this gateway (Sprint 21).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FormFieldComponent's label had [for]=fieldId but nothing ever gave the
projected control a matching id, and aria-describedby was set on a wrapper
div instead of the actual input - so screen readers announced neither the
label association nor the hint/error text.
Added FormFieldContext, an injectable abstract class FormFieldComponent
provides via its own providers array (visible to content-projected children,
same mechanism Angular Material's mat-form-field/matInput relationship
relies on). InputComponent optionally injects it and self-applies id and
aria-describedby when nested inside app-form-field.
This retroactively fixes every form built across Sprint 3-5 (Static Pages
editor, Media Manager, Products List/Form, Project Editor sections) with
no template changes needed anywhere else.
Verified in browser: input id matches label's for attribute, and
aria-describedby correctly points to the rendered hint/error text.
Completes Sprint 5 (UI/UX Polish): Admin Products List, Admin Product Form,
Project Editor sections (9 of 11), and this accessibility fix.
Applied app-input/app-form-field/app-button to 9 of 11 Project Editor
sections: general, branding, theme, footer, homepage, widgets, languages,
navigation, preview. header and features sections were left unchanged -
they contain only checkboxes and selects, and no Checkbox/Select primitive
exists yet.
Theme section's 8 color pickers stay native <input type=color> (app-input's
type union doesn't include 'color') but are now wrapped in app-form-field
for consistent label/hint treatment. Added app-form-field.full grid-column
rule to section.shared.scss (shared by all 11 sections) alongside the
existing label.full rule, since the custom element doesn't match that
selector.
Production build green, arch:check passes.
Replace single-line text/number inputs with app-form-field/app-input across
name, slug, sku, brand, priority, pricing, quantity, availability, per-locale
translation name/shortDescription, SEO metaTitle/keywords, and badges fields.
Save button now app-button. Textareas, selects, and checkboxes stay native -
no Textarea/Select/Checkbox primitive exists yet. Added app-form-field.full
grid-column rule alongside the existing label.full one (custom element,
different selector).
Production build green, arch:check passes.
Replace raw button/table/input elements with app-button, app-table, app-badge,
app-input, app-pagination. Removed now-redundant button/table/th/td CSS from
the stylesheet (would have double-styled the primitives' projected content
under Angular's emulated encapsulation). Native selects and checkboxes kept
as-is - no Select/Checkbox primitive exists yet.
Production build green, arch:check passes. In-browser verification hit a
dev-server routing hiccup unrelated to these changes (a temporary unguarded
preview route 404'd despite compiling correctly); relied on the identical,
already-verified primitive usage pattern from the Static Pages editor and
Media Manager instead.
Standalone dialog (app-dialog, size lg) reusing MediaLibraryFacade so it
shares the exact same asset list/search/pagination/upload state as the
Media Manager page. Emits (selected) with the chosen MediaAsset and
(closed) to dismiss - selecting a tile emits both.
Completes Sprint 4 (Media Manager): ADR-0002 contract, MediaRepository +
mock IndexedDB adapter, Media Manager UI, reusable Media Picker.
Not yet wired into Product Editor or Static Pages editor - that's the
next natural step when those features gain image fields, not part of
this sprint's scope.
MediaLibraryPageComponent replaces the coming-soon placeholder at
/backoffice/media. Built entirely on Sprint 6 Design System primitives
(app-card, app-button, app-input, app-empty-state, app-dialog, app-pagination,
app-skeleton) and Sprint 4 Task 2's MediaRepository/MockMediaRepository.
- Grid view with per-tile filename/size and delete action
- Hidden native file input triggered by an app-button, uploads via
MediaLibraryFacade -> MediaRepository.upload()
- Delete requires confirmation through app-dialog (destructive action)
- Search + pagination wired to MockMediaRepository's list() params
- Loading state shows app-skeleton tiles; empty state shows app-empty-state
- New mediaLibrary.* translation namespace across en/ru/hy
Verified in browser (via a temporary unguarded route, reverted before
commit - /backoffice/media itself requires Telegram QR admin auth not
available in this session): empty state renders correctly with translated
copy, search input and upload button present, no console errors.
Implements ADR-0002. MediaRepository is an abstract-class DI token (matching
the Ed25519VerificationService pattern in app.config.ts) bound to
MockMediaRepository, an IndexedDB-backed implementation storing blobs
directly with lazily-created/revoked object URLs. Swapping to a real
HttpMediaRepository later is a one-line provider change.
No UI yet (Sprint 4 Task 3).
Documents the MediaAsset model, future GET/POST/DELETE/PATCH /media contract,
and the MediaRepository interface (Mock IndexedDB-backed now, Http later via
DI swap) that Sprint 4 will implement against. Media assets never enter the
Bootstrap model, consistent with ADR-0001.
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.
StaticPagesEditorComponent.persist() hand-rolled the same title/html/route
mapping already implemented in ContentPageService.toBootstrapRecord(), with
subtly different behavior (always wrote empty-string html entries per locale
instead of omitting them, no title fallback). Added ContentManagementFacade.serializePages()
as a thin passthrough and switched the component to use the single canonical
implementation.
Standalone, OnPush. Computes truncated page range with ellipsis, composes
existing app-button (ghost/sm) for prev/next controls rather than duplicating
button styling. aria-current on active page, nav aria-label. Colors/radius/
spacing via existing tenant CSS vars with fallbacks.
Completes Sprint 6 Design System primitives: Button, Input, Card, Badge,
Dialog, FormField, EmptyState, Skeleton, Table, Pagination.
Standalone, OnPush, ViewEncapsulation.None scoped under .app-table/.app-table-wrapper
so projected thead/tbody markup receives consistent styling. Scroll container for
responsive overflow. Colors/radius/spacing via existing tenant CSS vars with fallbacks.
Standalone, OnPush. Label/hint/error slots, required marker, aria-describedby
wiring, role=alert on error. Colors via existing tenant CSS vars with fallbacks.
Standalone, OnPush. Backdrop click and Escape close, focus trap with
return-focus on close, role=dialog/aria-modal. Sizes sm/md/lg. Colors/
radius/shadow/spacing via existing tenant CSS vars with fallbacks.
Standalone, OnPush, content-projection container. Padding none/sm/md/lg,
bordered and interactive variants. Colors/radius/shadow/spacing consumed
via existing tenant CSS vars with fallbacks.
ControlValueAccessor-based, standalone, OnPush. Sizes sm/md/lg, states default/error/success.
Colors/radius/spacing/transitions consumed via existing tenant CSS vars (--border-color,
--primary-color, --error-color, --success-color, etc) with fallbacks - compatible with
bootstrap JSON theming and Project Editor live preview.
Sprint 2 high-priority cleanup: layout preference read/write called raw
localStorage from a component, violating the no-raw-localStorage rule.
Now uses the shared core/storage/LocalStorageService (same as
cart/language/location services).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 2 high-priority cleanup: cart/language/location services called
localStorage directly, bypassing the try/catch safety and core/<domain>
pattern used elsewhere (e.g. ProjectEditorDraftStorageService). New
core/storage/LocalStorageService centralizes get/set/remove and JSON
helpers with private-mode/quota error handling, reused across all three.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 2 high-priority cleanup: backoffice-dashboard.component.ts and
builder-sandbox.component.ts were not routed anywhere, superseded by
features/backoffice and features/project-editor. Their sole facades
(BackofficeFacade, BuilderConfigFacade) had no other consumers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fabricates a local admin session and activates it directly, skipping the
Telegram QR flow, for local testing without a reachable session backend.
Guarded by environment.production at runtime - no-ops in production
builds even if this code ships.
Wires up the remaining editor sections (theme, header, footer, homepage,
widgets, features, languages, navigation) with the description text and
dropdown UX started for General/Branding:
- Every enum-backed field is now a <select> with a description per option,
not free text: theme.mode, the new site-wide layout.type (previously had
no editor at all - added to the Theme section, and added to that
section's reset-scope), homepage section layout.strategy, and
catalog.navigationMode (also previously unedited, added to Features).
- Every other field (colors, header toggles, footer contact/company fields,
widget props, feature flags, language add, nav link label/url/visible)
gets a one-line plain-language description under its label via the new
*Desc i18n keys (en/ru/hy) prepared earlier.
- Genuinely open text (widget layout variant strings, JSON props) stays
free text, description-only, per the existing widgetLayoutDesc/widgetJsonDesc
wording - not force-fit into a dropdown.
Verified: tsc --noEmit and ng build both clean.