- Hero widget autoplay had no pause control and ignored
prefers-reduced-motion (WCAG 2.2.2 requires a way to pause
auto-updating content lasting >5s). Added a pause/resume toggle
button and skip autoplay entirely when the OS prefers reduced motion.
- Cart's swipe-reveal delete-btn-mobile was reachable by Tab even
while invisible (opacity: 0, only the touch-swipe gesture could
reveal it) - a confusing, unusable focus stop for keyboard users.
Now tabindex=-1 + aria-hidden until swiped. Keyboard users already
had a full removal path via the always-visible header remove button;
this just stops the redundant hidden button from being a dead tab
stop.
- stars.component.scss hardcoded #cdd6d5 for the unfilled-star color
instead of the --border-color design token.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- SearchFacade.popularSearches hardcoded English titles regardless of
active locale. Converted to a getter using translate.t() for the
displayed title/text; the underlying search query stays the stable
English canonical term the backend index matches against.
- Compare table and compare page rendered product.name raw instead of
through getTranslatedField(), same pattern used everywhere else
product titles are shown (catalog, product detail).
- SearchTrendingService.loadTrending() is a genuine backend gap (no
trending-search endpoint exists) - already degrades gracefully,
documented as a gap in BACKEND-API-REFERENCE.md \u00a712.6 rather than
faked client-side.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Quick View button (search results page only) emitted
quickViewPlaceholder with zero listeners anywhere up the chain - the
button did nothing. Built a minimal QuickViewDialogComponent (image,
name, price incl. discount, short description, Add to Cart, link to
full product page) and wired the event through product-grid ->
search-results -> catalog-container, which fetches the product via
ProductFacade and opens the dialog.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
notifyMe() just called toggleWishlist() - no actual subscription
mechanism existed. Now calls a new subscribeToRestock() API method
(POST /items/{id}/notify-me, not yet built server-side - see
BACKEND-API-REFERENCE.md §12.5) and falls back to a local-only record
in localStorage on failure, so the request isn't silently dropped
while the backend catches up.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
getPaymentDescription()'s final fallback ('Покупка на Маркетплейсе')
ignored the active language. Moved to a translated key.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The bulk button read 'Archive' but called applyBulkDelete() ->
gateway.deleteReview() - a real hard delete, and reviews have no
archived status in the model at all, so 'archive' was never a real
concept here. Relabeled to 'Delete' and added an app-confirm-dialog
gate, same pattern as products/categories/orders.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bulk-delete button called facade.applyBulkDelete() with no gate.
Added app-confirm-dialog, same pattern as products/categories.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(delete) and (bulkDelete) called facade.deleteOne()/applyBulkDelete()
straight from the click event. Added app-confirm-dialog gates for
both, same pattern as categories.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bulk-delete called facade.applyBulkDelete() straight from the button
with zero confirmation. Single-delete had a confirm gate but via raw
window.confirm/alert instead of the themed dialog used elsewhere.
Migrated both to app-confirm-dialog (single + new bulk), and the
delete-blocked message to a themed app-dialog instead of window.alert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
recordOrder() and autoSubmitPurchase() read userEmail()/userPhone()
signals and submitEmail() was fully implemented (validation, error
handling), but the success screen's template never rendered the
inputs - so the form was unreachable and the fallback auto-submit
always sent blank email/phone.
- Added the email/phone form to the payment-success screen, wired to
the existing signals/handlers.
- autoSubmitPurchase() (the 5s fallback if the user doesn't submit
manually) no longer navigates home via an unconditional setTimeout(0)
fired before the submission result is known - it now waits for
submitPurchaseEmail() to settle, same as the manual path, and skips
entirely if the user already submitted (new purchaseSubmitted flag).
- It also now sends whatever the user has typed instead of
hardcoded-blank fields, and shows a toast instead of only logging to
console when no Telegram user id is available.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
isSlugTaken previously caught network/API errors and returned false,
letting the save proceed as if the slug were free. Now the error
propagates and blocks save via a distinct slugCheckError state,
surfaced in the category form with its own hint.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint G: audited every BootstrapConfig field for a real runtime consumer
(docs/DEAD-CONFIG-AUDIT.md). Wired 3 previously-dead editable fields:
footer.logoUrl, company.address.street/contacts.phone, catalog.suggestionsEnabled.
Remaining dead fields needing a business/design decision tracked in
PRODUCT_BACKLOG.md/KNOWN-ISSUES.md, not silently left.
Sprint H: 6 new spec files (test count 57 -> 83), covering ProjectEditorFacade
(undo/redo, draft persistence, publish gating), AdminAnalyticsFacade
(never-fabricate-a-number contract), and regression coverage for this
session's carousel/hero/profile-toggle fixes.
Sprint I: widget settingsSchema (declared in widget-manifest.json, never
validated) now enforced via a new lightweight schema check in
ProjectValidator, surfaced through the existing issuesByField pipeline.
Same check reused in diagnostics so editor and diagnostics can't disagree.
Verification: tsc clean, ng build clean, 83/83 tests pass, barry-cache
validate clean (2 pre-existing unrelated warnings only).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint E: homepage section editor now filters the layout-strategy picker
to each widget's widget-manifest.json supportedLayouts instead of always
showing all 5 strategies. columns field gated to widgets that read it
(hero, product-collection carousel).
Sprint F: closes client bug report (no items-per-page control, hero
carousel not manually/automatically scrollable, no 1-2 slide big-carousel
option). Product carousel item width now driven by layout.columns
(reused, was already editable but dead). Hero widget gains prev/next
arrows, touch swipe, and 1-2 panel mode via the same field.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint A: storefront header profile control (login/logout only, no menu),
wired to existing customer Telegram auth (AuthService).
Sprint B: backoffice/reports page, reuses AdminAnalyticsFacade (Sales,
Top Products, Marketplace Health cards + CSV export).
Sprint C: backoffice/settings page, admin UI density preference
(comfortable/compact), localStorage-persisted, applied to app-table
across all admin list pages.
Sprint D: admin bottom-nav Help -> mailto using existing supportEmail,
Documentation -> external link via new TenantConfig.documentationUrl.
AdminNavLink gains externalHref for non-routerLink nav entries.
Docs: docs/GLOBAL-SPRINT-PLAN.md tracks the full sprint breakdown.
docs/COMING-SOON-AUDIT.md removed, folded into docs/KNOWN-ISSUES.md.
docs/BACKEND.md updated with the new documentationUrl bootstrap field.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ProjectEditorFacade.resetField(key): field-level revert-to-original, reusing getByPath + a new immutable setByPath (write-side counterpart, scalar/object dot-paths only).
- app-form-field gains showReset/resetLabel/(resetClicked) - a reusable per-field reset affordance, wired on branding logo/title and theme primary/background color.
- Save bar: unsaved-changes count is now clickable, expanding a field-level diff list (reuses facade.changeSummary(), already built for the Preview tab) with jump-to-section links.
- BuilderPropertySearchComponent: filters EditorSchemaService.all() by translated label/hint, jumps to the owning section - no new registry, reuses the existing schema.
- navigation-section: app-empty-state (existing component) added for empty header/footer link lists.
- Reset Section and Draft/Published badge were already implemented; not touched.
- New copy added as translation keys (en/ru/hy).
- PreviewHighlightService + appHighlightSource directive: shared hover/focus bridge between editor fields and the schematic live preview.
- BuilderLivePreviewComponent: in-page schematic homepage render (header/hero/blocks/footer) reading the same bootstrap the sections mutate, highlighting the area matching the active field.
- VisualLayoutPickerComponent: card-based layout picker (ControlValueAccessor, same shape as app-select) replacing the raw layout <select> in homepage-section.
- app-form-field gains optional usedBy/usedByLabel inputs for the "Where is this used?" helper text, wired into aria-describedby.
- Wired homepage/branding/theme sections with highlight sources + usedBy hints; live preview panel shown in project-editor-page for those three sections.
- All new copy added as translation keys (en/ru/hy).
No backend, no CRUD, no API, no business logic - production-quality
UI only, built entirely from existing shared components (app-dialog,
app-empty-state, app-button, app-form-field, app-input, app-icon,
app-badge). Gated per ADR-011: reads
modules.sellerManagement.enabled from bootstrap (always false today,
no backend sets it) rather than hardcoding disabled state.
New:
- AdminSellerManagementPageComponent (features/admin/seller-management/
pages/) - renders the specified empty state (title/description/
Request Access + Learn More buttons) using existing shared/ui
primitives only, no new UI infrastructure.
- Request Access dialog: Company/Email/Message form via
app-form-field + app-input + a plain textarea (no dedicated
textarea component exists yet, styled to match app-input's own
tokens exactly). Submission is mocked (setTimeout), no API call.
On submit: closes and opens a success dialog ("Thank you...").
- Learn More dialog: 6 capability bullets (seller dashboards,
storefronts, permissions, analytics, product ownership, marketplace
administration) under a "Coming Soon" badge.
- New admin nav group "Partners" > "Seller Management" link
(admin-nav.model.ts), new route /backoffice/partners/seller-management
(app.routes.ts), using the same loadComponent/breadcrumb pattern as
every other admin route.
Translations: full en/ru/hy coverage, zero hardcoded strings - new
adminShell.nav.{partnersGroup,sellerManagement},
adminShell.pages.sellerManagement, and a new adminSellerManagement.*
namespace (emptyState/requestDialog/requestSuccessDialog/
learnMoreDialog) added to translations.ts (types) and all three
locale files.
Accessibility: inherited from app-dialog (role="dialog",
aria-modal, focus trap on Tab/Shift+Tab, Escape to close, focus
restored to trigger on close) - no new a11y code needed, reused as-is.
Responsive: existing --space-*/--font-size-* tokens throughout,
flex-wrap on button row, mobile breakpoint stacks actions full-width.
Verified live (ru locale, devBypassAdmin): nav group/link render
correctly, breadcrumb shows "Управление продавцами", empty state
copy matches spec exactly, Request Access dialog opens with all 3
fields + Cancel/Send Request, filled + submitted -> success dialog
with exact spec copy, Learn More dialog shows all 6 bullets + Coming
Soon badge, no console errors, verified again at 375px mobile
viewport. tsc --noEmit clean, ng build clean (pre-existing bundle-
budget warning only), arch:check (boundaries + cycles) clean.
Analytics, Reports, and Diagnostics were already clean (no raw
HTTP/queue-worker strings found on audit). Monitoring had three spots
speaking developer language by default:
- Background queue names ("order-notifications") -> friendly labels
("Order notifications").
- Webhook event keys ("order.created") -> friendly labels ("New order
placed").
- Activity log's "api" category showed the raw HTTP line
("GET /api/products responded 200 in 84ms") as the primary message.
Now shows a plain-language summary by default ("Product data
refreshed successfully"), with the raw string moved to a collapsed
"Technical details" <details> per event (api/error/warning rows).
- Route aria-label/alt/title strings (rating, discount, carousel arrows,
hero slides, dialog close, toast dismiss, QR code, bank payment iframe,
guest checkout fallback) through the translate pipe/service instead of
literal English.
- Drop the "Oops!"/"Упс!" apology framing from category/subcategory empty
states (en/ru/hy) - zero results is not an error.
Phase 8 (RC-01): these 4 list/dashboard pages had no error-state handling
on their primary data-load subscriptions — on a gateway error, `loading`
was either never reset (Users, Monitoring, Analytics: genuine infinite-
spinner risk, nested subscribe chain in Analytics never resolved on
failure) or there was no loading/empty/error handling at all (Reports
queue: raw table with zero skeleton or fallback).
- admin-users.facade.ts, admin-monitoring.facade.ts: add `error` signal,
error callback on the primary load subscribe so `loading` always
resolves.
- admin-analytics.facade.ts: add `error` signal; every level of the
4-deep nested gateway subscribe chain (orders -> products ->
categories -> reviews) now has an error handler that resolves loading
instead of leaving it stuck true.
- admin-moderation.facade.ts: add `reportsLoading`/`reportsError` signals
(reports list had none previously).
- Templates: reuse existing `app-skeleton`/`app-empty-state`/`app-button`
primitives for the new error branch, `common.retry` label, two new
generic `common.errorTitle`/`common.errorDescription` i18n keys added
to en/ru/hy (reused across all 4 fixes instead of one-off per-page
copy).
Verified: tsc --noEmit clean, `npm run build` green (pre-existing bundle-
budget warning only, unrelated). Live-checked Home (375px) and Backoffice
Products (1024px) — no console errors, tables/cards render without
overflow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Replaced hardcoded English audit/timeline text (order status changes,
review moderation events, user role/status changes) with proper
adminXxx.timelineEvent.*/adminUsers.audit.* i18n keys, so Recent
Activity/Timeline/Audit panels no longer mix English into ru/hy UI.
- Translated raw internal codes rendered directly to users: transaction
payment method ('card'/'qr'/'cash_on_delivery' -> adminTransactions.methodValue.*)
and user roles/permissions ('products.manage' etc -> adminUsers.roleValue.*/
adminUsers.permission.*), replacing developer-facing enum leakage with
real copy.
- Fixed wrong-noun list-footer counts: Orders/Transactions/Moderation
list pages all reused adminProducts.items ("N товаров"/"N products")
regardless of what was actually listed; each now has its own itemsCount
key ("N заказов", "N транзакций", "N отзывов").
- Fixed customer detail page's "Back" button reusing adminOrders.back
("Back to orders") instead of a customers-specific label.
- Added translation keys to en/ru/hy + translations.ts interface for all
of the above.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- KNOWN-ISSUES item 11: the WYSIWYG page content editor was buried inside
a collapsed Advanced <details>, labeled 'Raw HTML (advanced)'. Moved it
to the top of the Content tab, unwrapped, relabeled 'Page Content' /
'Содержимое страницы' / 'Эջի բովանդակություն' with a plain-language
description. Advanced tab keeps genuinely technical fields (id, slug,
route, custom template).
- Publish (save-bar) and Remove language (languages-section) had no
confirmation despite being destructive/high-impact — added
window.confirm guards using the existing builder.confirm* i18n pattern
(matches resetDraft/resetSection/dirty-guard precedent), all 3 locales.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RC-01 Phase 1 mechanical fixes (verified against current repo state,
not blindly reapplied from TODO.md):
- admin/products create/edit/duplicate now protected by an unsaved-
changes guard (adminProductDirtyGuard), mirroring the existing
categories pattern. AdminProductsFacade had zero dirty-tracking
before this - added a dirty signal, set true on updateDraft(),
cleared on load/create/successful save. Added confirmLeaveUnsaved
to the adminProducts i18n section (en/ru/hy) - categories already
had its own copy of this key, products didn't.
- barry-cache bumped ^0.1.0 -> ^0.9.3 (the pinned range no longer
resolved on the registry - ETARGET - which had been silently
blocking every npm install/uninstall all cycle).
- Removed primeng/primeicons (npm uninstall, now unblocked) - the
only consumer (items-carousel) was already deleted in RC PERF-01.
- Removed core/search/services/search-history.service.ts, a dead
1-line re-export with zero importers (verified: the real
implementation is features/search/services/search-history.service.ts,
used by search.facade.ts). Left core/search/models/* alone - those
ARE live, imported by catalog components.
Verified before touching: HeaderConfig.showProfile toggle is already
removed from the header-section editor template (TODO.md was stale on
this one) - no change needed, will correct the tracking doc separately.
tsc --noEmit clean, npm run build green (bundle unchanged, primeng
was already tree-shaken out, this just removes the dead dependency
declaration itself).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RC A11Y-01 audit pass, Backoffice (admin/*) area. Builds on 712a7b4/63c9cee's
composition pass (scope="col", skeletons, empty-states) without redoing that
work.
- Sidebar nav landmark: admin-layout's <nav aria-label> reused the
"Dashboard" nav-item translation key as its landmark label, misleadingly
announcing the whole sidebar as "Dashboard" - added a dedicated
adminShell.sidebarLabel key ("Admin sidebar navigation") in en/ru/hy.
Skip link, #admin-content main landmark, Escape handling, and mobile-drawer
focus management were already correct - verified, not touched.
- Categories tree drag-and-drop keyboard fallback (WCAG 2.1.1): the category
tree's native HTML5 DnD (draggable/dragstart/drop) reorders siblings with
no keyboard equivalent - existing arrow-key tree navigation only expands/
collapses/selects, never reorders. Added per-row move-up/move-down icon
buttons (disabled at sibling boundaries), reusing the existing `reorder`
output so the facade's reorder logic is untouched; new
adminCategories.moveUp/moveDown keys in en/ru/hy.
- Screen-reader loading announcements: skeleton-row loading states across
Products, Categories, Customers, Orders, Transactions, Users, Reviews,
Monitoring (webhooks/events), and Analytics (summary cards + top products)
were purely visual (app-skeleton is aria-hidden by design) with no
accessible "loading" text, unlike the storefront/product-details pattern -
added role="status"/aria-live="polite"/aria-busy + sr-only text using the
existing common.loading key.
- Table row headers: added scope="row" to the primary identifying cell
(product/category/customer name, order number, transaction order number,
user name, review customer, report target, top-products/low-stock product
name, webhook endpoint) on 9 tables that only had scope="col". Added
matching `tbody th[scope='row'] { font-weight/color/text-align/
vertical-align }` + last-row border resets in each component's own scss so
the semantic change doesn't alter visuals (the shared app-table stylesheet
styles all <th> as bold/muted by default).
Verified via `git show --stat` of fb1afb7/a03260e and `docs/UI-COMPOSITION-
REVIEW.md`'s Backoffice sections first, per instructions - confirmed
scope="col" coverage already complete, all admin modals already route
through the shared app-dialog (focus-trap/Escape/return-focus already
correct, nothing to fix), and the bare-<select> filters still carry
aria-label per the accepted Sprint 28 decision (not re-migrated to
app-select).
Flagged, not fixed:
- No toast/notification system exists anywhere in this codebase (product/
category save and delete call the gateway with no success/error UI at
all, not even a subscribe error handler) - there is nothing to wire
aria-live onto without adding a new UI mechanism, which is out of scope
for an a11y-only pass. A prerequisite feature-level fix, not an a11y
regression.
- Dashboard's per-card metric/status-row/timeline skeletons (dashboard-
metric, dashboard-status-row, dashboard-timeline) were left without
aria-live wiring - wrapping each of the ~10 simultaneous mini-widgets in
its own live region would fire a burst of redundant announcements; needs
a single page-level "loading dashboard" region instead, a larger change
than this surgical pass.
- Monitoring's events table and the notifications dropdown (role="menu"
with a static empty-state message, aria-haspopup="true") were left as-is -
matches the same partial-widget-pattern precedent already accepted for
locale-tabs/product-tabs in the storefront and builder passes.
- Analytics `lowStockProducts` table's missing loading-skeleton branch
(already flagged, not fixed, in the RC-Visual-02 pass) - untouched again
here for the same reason.
Verified: npx tsc --noEmit clean; npm run build green (only the pre-existing
768.57 kB vs 700 kB initial-bundle budget warning, unrelated to this pass).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RC A11Y-01 audit pass, Builder (project-editor) + content-management static
pages editor. Builds on 4ebc15f's composition pass without redoing it.
- Skip link: Builder's /edit and /edit/:section routes render outside the
storefront app-shell (isAdminRoute() branch has no skip link/landmark, only
a bare router-outlet, unlike the storefront's app.html) - added a
"skip to content" link targeting a new #builder-main-content landmark on
project-editor-page.component, reusing the existing adminShell.skipToContent
key and global .skip-link style. Nav landmark already had aria-label
(builder.appName) from the prior pass, verified correct.
- Save bar: added role="status"/aria-live="polite" to the save/publish status
block and role="status" to the draft-restored notice so save/publish state
changes and draft recovery are announced to screen readers (previously
silent DOM updates).
- Media picker (shared, used by both Builder and static-pages editor): upload
error message had no aria-live wiring - added role="alert".
- HTML editor (marketplace-html-editor): the contenteditable rich-text surface
had no accessible role/name - added role="textbox", aria-multiline="true",
aria-label.
- Drag-and-drop keyboard fallback (WCAG 2.1.1): homepage-section's block list
and footer-section's column list + per-column link list use Angular CDK
drag-drop (cdkDrag/cdkDropList), which has no built-in keyboard reordering.
Added move-up/move-down icon buttons (disabled at the first/last boundary),
matching the existing pattern already used by widgets-section and
navigation-section.
- Color picker: the swatch <input type="color"> had no accessible name (only
the paired text input was labelled via app-form-field) - added explicit
ariaLabel bindings to all 8 color-picker instances in theme-section.
- Languages: the new-locale code input relied on a placeholder ("de") as its
only accessible name - added ariaLabel + new builder.newLanguageLabel i18n
key (en/ru/hy).
- Undefined CSS var --color-primary (never defined anywhere, silently used its
hardcoded hex fallback and never responded to tenant theming - same
recurring bug class as 4ebc15f) - remapped to the real --primary-color token
in marketplace-html-editor, homepage-section, and section.shared (7 usages).
- role="alert" added to all 11 validation-error <p class="editor-error">
occurrences across footer/homepage/languages/navigation/preview/widgets
sections and the static-pages editor, so field/section validation messages
are announced.
- scope="col" added to preview-section's change-summary table headers.
Flagged, not fixed (design-system decisions, matching the storefront pass's
precedent):
- Save bar's --warning-color/--error-color/--info-color text fail WCAG AA
4.5:1 in some themes - same genuine brand semantic colors flagged (not
fixed) in fb1afb7's storefront pass; needs a deliberate token decision,
not a Builder-specific issue.
- locale-tabs (app-locale-tabs, shared) has role="tablist"/"tab" and
aria-selected but no roving-tabindex/arrow-key navigation - matches the
same partial-tablist pattern already accepted for product-tabs in the
storefront pass; all tabs remain natively Tab-focusable, so this meets
4.1.2/2.1.1 without the full ARIA authoring-practice pattern.
Verified: npx tsc --noEmit clean; npm run build green (only the pre-existing
bundle-budget warning, unrelated to this pass).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RC A11Y-01 audit pass, storefront + shared app-shell chrome only. Builds
on RC-Visual-02/RC-Premium-01/RC STORE-01 without redoing that work.
- Skip link: added first-focusable "skip to main content" link (app.html,
styles.scss .skip-link/.sr-only), targeting new #main-content landmark.
New app.skipToContent i18n key in en/ru/hy.
- Header: mobile menu items stayed keyboard-focusable and screen-reader
reachable while visually collapsed (max-height:0 with no visibility
toggle) - fixed with visibility:hidden + matched transition-delay.
Desktop search input (readonly, click-to-navigate) had no keyboard
activation - added aria-label + (keydown.enter).
- Cart payment/bank-payment modals: custom (non-app-dialog) UI had no
focus trap, no Escape handling, and never returned focus to the
triggering element - ported app-dialog's confirmed-correct
focus-trap/Escape/return-focus pattern directly onto cart.component.ts.
Added role="dialog"/aria-modal/aria-label to both panels and
role="status"|"alert"/aria-live to every payment-status screen so
screen readers announce state changes (creating/waiting/success/
error/timeout).
- Search combobox: suggestion listbox had no role="combobox" wiring on
the input and suggestion buttons weren't role="option" - added
aria-autocomplete, aria-controls, aria-activedescendant, aria-selected
so the existing arrow-key navigation is announced to screen readers.
- Product tabs: tablist/tab pattern was incomplete (no role="tablist",
no tabpanel) - added role="tablist" + ids to product-tabs.component,
role="tabpanel"/aria-labelledby to the content panel in
product-details-container.
- Review form: rating/text validation errors weren't associated with
their controls (no aria-describedby, no role="alert") - fixed; added
aria-required to the review textarea.
- delivery-selector: added aria-required to the delivery <select> when
a selection is mandatory.
- Shared app-icon component: doc comment claimed "decorative by default
(aria-hidden)" but no aria-hidden was ever applied - fixed to actually
set aria-hidden="true" when undecorated, and role="img"/aria-label
when ariaLabel is passed. Shared component, affects every icon-only
usage app-wide, no visual change.
- Color contrast: --text-light fails WCAG AA 4.5:1 for normal text in
every theme (dexar 3.39:1, lavero/novo 2.54:1 against white). The two
in-scope usages (company-details org-short/basis, review-form
upload-placeholder) switched to --text-secondary (4.55:1-7.56:1,
passes), same visual family, no layout change.
Flagged, not fixed (design-system decisions, not polish):
- --border-color fails WCAG 1.4.11 3:1 for UI-component boundaries in
every theme (dexar 1.42:1, lavero/novo 1.24:1 vs white) - pervasive
token used by hundreds of borders app-wide; needs theme-owner sign-off.
- --success-color/--warning-color/--error-color/--info-color used as
plain text-on-white in several places (product-information,
question-card, review-form, compare-page) fail 4.5:1 (2.15-3.76:1) -
genuine brand semantic colors, changing them to pass would visibly
shift the palette; needs a deliberate token decision.
- Header mobile-menu max-height/padding transition (pre-existing,
unrelated to this fix) flagged by design lint as layout-thrashing;
left as-is per the "no layout/business-logic changes" constraint.
Verified: npx tsc --noEmit clean; npm run build green (only the
pre-existing bundle-budget warning, unrelated to this pass).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- delivery-information, product-actions, product-description,
product-gallery, product-information, related-products,
variant-selector: normalize hardcoded hex colors to design tokens
(--text-primary, --text-secondary, --border-color, --bg-primary/
--bg-secondary, --primary-color/--primary-hover); stock status and
discount badge now use semantic --success-color/--warning-color/
--error-color instead of near-duplicate literal hex
- product-actions: add aria-pressed to wishlist/compare toggle
buttons; add hover/active/disabled states to action buttons
- product-gallery: add aria-current + aria-label to active thumbnail
button; add focus-visible ring and hover state on thumbnails and
toolbar buttons
- variant-selector: add aria-pressed to colour/size option buttons;
add a visible checkmark glyph on the selected colour swatch so
selection isn't color-only; add hover states
- product-tabs: add role="tab"/aria-selected to tab buttons
- star-selector: add per-star aria-label (new starsLabel i18n key
added to en/hy/ru + translations.ts interface)
- question-list: add aria-expanded to the ask-question disclosure
toggle; swap plain empty-state <p> for app-empty-state; add
hover/disabled states to pager buttons
- review-list: swap plain empty-state <p> for app-empty-state; add
hover/disabled states to pager and load-more buttons
- question-card, question-form, review-form: normalize accepted/
success/error colors to semantic tokens; add focus-visible and
hover/disabled states to inputs and submit buttons
- compare-table: add scope="col"/scope="row" to table headers; make
header row and attribute column sticky for easier comparison on
long tables
- compare-page: add hover/focus states to the remove-from-compare
chip button
Build verified green via `npm run build`.
Out of scope / skipped:
- src/app/pages/item-detail/* is dead code (not referenced by any
route or component) - left untouched
- wishlist page and product-details-container were already fully
composed with shared skeleton/empty-state/button components from
the RC-Visual-02 pass - no changes needed
- stars.component display-only rating glyphs use a light gray not an
exact token match - left as-is to avoid an unintended visual shift
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Reset-section button: raw <button> with hardcoded colors -> app-button variant="danger"
- section.shared.scss: raw button/input/select colors switched to CSS theme vars (--primary-color, --bg-primary, --bg-secondary, --error-color) instead of bare hex
- save-bar scss referenced nonexistent CSS vars (--surface, --border, --warning, --danger, --muted-foreground, --info-bg, --info) that always fell back to hardcoded hex; renamed to the real theme vars (--bg-primary, --border-color, --warning-color, --error-color, --text-secondary, --info-color) so the save bar is actually theme-aware
- footer/homepage/widgets section scss: nonexistent --danger-color var renamed to --error-color
- static-pages-editor: replaced dead `.editor-section-card` wrapper class (removed from shared stylesheet in the Sprint 30 redesign, never migrated here) with app-section-card, restoring the card chrome every sibling editor section has
- widgets-section: empty state (no widgets) rendered nothing; added app-empty-state
- homepage-section: empty state (no homepage page) rendered nothing; added app-empty-state
- navigation-section: header/footer nav move-up/move-down buttons had no accessible name (bare uarr/darr glyphs); added aria-label
- Added builder.widgetsEmptyTitle/Desc and builder.homepageEmptyTitle/Desc i18n keys (en/ru/hy)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same select-all/row-checkbox accessible-name gap in table view; grid
view already had aria-label on its row checkbox (customer name),
table view was the gap. New adminModeration.selectAllRows key added
across en/ru/hy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same gap as products/categories: table select-all and per-row select
checkboxes had no accessible name. Added aria-label (row label
includes order number for context). New adminOrders.selectAllRows/
selectRow keys across en/ru/hy.
Order detail page and order-timeline component reviewed — no
interactive checkboxes or focus-visible gaps found there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add accessible names to select-all, per-row select, and per-row
visibility-toggle checkboxes in the table view — none had them,
including the visibility toggle whose <label> wrapped only the input
with no text content (empty accessible name). Tree/grid views already
had aria-label on their row checkboxes; table view was the gap.
New adminCategories.selectAllRows/selectRow/toggleVisibility keys
added across en/ru/hy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add accessible names to the table-view select-all and per-row
checkboxes — they had none, while the grid-view equivalent already
did (product name via aria-label). New adminProducts.selectAllRows/
selectRow keys added across en/ru/hy.
Reviewed toolbar, filters, bulk actions, empty/loading states, and
grid view: all buttons already route through app-button (own
:focus-visible), empty state already uses shared EmptyStateComponent.
No orphaned bindings or corrupted glyphs found.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix real regression: the payment success checkmark and timeout clock
icons had been corrupted to literal '?' glyphs (confirmed via git
history — ✓ and ⏱ were replaced), so a customer who just paid saw a
confusing '?' instead of a success indicator. Restored both icons and
marked them aria-hidden since the adjacent heading already conveys
the status.
Add missing accessible names to icon-only buttons that had none:
quantity increase/decrease controls and the mobile delete button
(desktop remove button had a title attribute only, which is not
reliably announced by screen readers — added aria-label alongside it).
New cart.increaseQuantity/decreaseQuantity keys added across all
three locales.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add accessible label to the compare-chip remove button: it rendered
only a bare × glyph with no aria-label, announced as meaningless
symbol text by screen readers. Added ux.removeFromCompare across all
three locales.
Global :focus-visible already covers .btn/.btn-primary/.btn-ghost, so
no separate focus styling was needed here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
P0-8: re-verified the ~178-key admin i18n gap (docs/KNOWN-ISSUES.md
item 2, docs/ADMIN-UX-AUDIT.md) is largely fixed by prior commits,
but a live pass over Orders/Transactions/Users/Monitoring/Analytics
found 2 remaining leaks:
- adminOrders.status.all rendered literally in the Orders list's
primary status filter (the bulk-action status select already
excluded 'all' and was fine; the primary filter loop did not).
- adminUsers.suspend / adminUsers.reactivate rendered literally on
every row action button on the Users page.
Added the missing keys to all 3 locales. Transactions, Monitoring,
and Analytics show no raw dot-key leaks in this pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
P0-1: stock badge rendered the raw item.remainings value ('High',
etc) untranslated on every product card sitewide. Now maps to
catalog.stockHigh/Medium/Low/Out via the translate pipe; the 'out'
class check is now case-insensitive to match.
P0-2: favorite/share/quick-view action-button aria-labels rendered
literal 'catalog.favorite'/'catalog.share'/'catalog.quickView' keys
because they never existed in translations.ts/en.ts/ru.ts/hy.ts
(only catalog.compare existed, and it was likewise unused). Added
all 4 keys to the Translations interface and all 3 locales.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: homepage builder showed raw section.id like 'section-hero'/'section-categories'; hero widget exposed 'full-bleed'/'boxed' and raw px/vh as free text with no explanation; Overlay/Autoplay toggles made no sense without a slides concept; product carousel widgets rendered arrows nowhere near a working carousel.
Homepage section (sections list -> visual blocks):
- Replaced raw section.id display with a merchant-facing block catalog (icon + name + one-line explanation) for hero/categories/featured-products/product-carousel/recently-viewed/banner/partners/custom-html
- Added block catalog picker to append new blocks (was fixed at whatever the seed data had - task asked 'what if we add manually? not fixed 3')
- Added duplicate and remove per block, alongside the existing drag-to-reorder
- Verified in browser: labels render correctly, add-block and duplicate both confirmed working end-to-end
Widgets section (hero widget):
- 'Layout' free-text replaced with a select (Full width / Boxed) instead of typing 'full-bleed'/'boxed' blind
- 'Height' free-text replaced with a select (Compact/Medium/Tall/Full screen) mapped to real vh values
- New Slides editor: title/subtitle pairs an admin can add/remove: this is the actual multi-slide data the Overlay/Autoplay toggles were referring to with nothing to point at before
- HeroWidgetData contract gains slides[]/autoplay; HeroWidgetComponent now renders a real rotator (dots, click-to-jump, autoplay interval) when more than one slide exists - previously autoplay/overlay props existed but there was no slideshow behavior anywhere to control
Carousel arrows root cause and fix:
- widget-manifest.json offers 'carousel' as a layout option for product-collection/product-carousel widgets, and the admin UI let you select it, but ProductCarouselWidgetComponent always rendered a static CSS grid regardless - there was no carousel implementation to have arrows in the first place
- Now renders a real horizontally-scrollable strip with working prev/next buttons (native scrollBy, disabled at each end) when section.layout.strategy === 'carousel'; falls back to the existing grid otherwise
- Confirmed src/app/components/items-carousel (a PrimeNG p-carousel) is dead code, not wired into any route or widget - not the source of the reported bug
New builder.* i18n keys (en/ru/hy), zero duplicate-key collisions verified via scan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: footer static pages were comma-separated text; no way to add extra phones/emails for different countries.
- New FooterColumnConfig/FooterLinkConfig model (footer-config.model.ts): columns of links, each link pointing at an existing static page (resolved by key, so it survives route renames) or a custom URL
- Footer Builder UI: add/remove columns and links, per-link toggle between 'existing page' (dropdown of real static pages) and 'custom URL', drag-and-drop reordering of both columns and links via @angular/cdk/drag-drop (same primitive already used by the homepage section builder)
- Wired FooterResolverService (the service the real storefront footer actually renders through) to read footer.columns as the primary source when present - without this the builder would have saved data nobody ever displayed. Falls back to the existing legacy static-page auto-grouping when no columns are configured, so existing sites are unaffected
- CompanyContactConfig gains additionalPhones/additionalEmails (primary phone/email field unchanged) with add/remove UI for country-specific support lines
- Old comma-separated staticPageKeys input removed from the UI; field kept on the model as deprecated/read-compat only
- New builder.* i18n keys (en/ru/hy); fixed an accidental duplicate-key collision with pre-existing navigation-section addLink/removeLink keys during the rename pass
- Verified in browser: added column, added link, switched link source page->custom, added phone number - all reactive and error-free
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: variants were just free-text name+price, and the user shared the real production payload - a flat array of {color, size, price, currency, remaining} rows, colors as '0x8B4513' hex.
- New model: AdminProductVariantAttributeDef (key/label/isColor/values) + AdminProductVariant (attributes: Record<string,string>, sku, image, remaining, prices: {currency,price}[]) - this IS the production shape, grouped by combo with one row per currency instead of flattened, so admins edit one variant card instead of 4 duplicate rows
- Attribute manager: add custom attributes (Color, Size, or anything), color attributes get a native color picker + live swatch preview instead of typing hex; other attributes get plain value chips
- 'Generate variants' computes the cartesian product of attribute values (Color x Size = 4 combos for 1 color x 4 sizes) and preserves existing sku/price/stock data for combos that still exist after regeneration
- Multi-currency pricing per variant (matches prod: same combo priced in RUB/USD/EUR/AMD) with per-currency add/remove
- Color hex kept in the exact '0x8B4513' production format (toBackendColor/toCssColor conversion helpers)
- Fixed an Angular v21 control-flow parser bug hit while building this: an @if/@else block whose only content is a bare {{ interpolation }} touching the block's closing brace fails to parse (NG5002 'Unclosed block for' cascading from a completely unrelated line) - worked around by keeping interpolation in its own element
- Verified end-to-end in browser: added Color (color picker) + Size (S/M/L/XL) attributes, generated 4 variant combos, added all 4 currencies to a variant - matches the shared production JSON exactly
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: General asked admins to type locale codes comma-separated, and Branding (logos) came before Theme (colors).
- Root cause: General duplicated language management as raw text/CSV inputs while a full Languages manager section (add/remove/set-default with per-locale content seeding) already existed one tab away. Removed the duplicate inputs; General now shows a read-only chip summary (default language first, marked) with a 'Manage languages' link to the real manager. One source of truth, no comma parsing, no risk of bypassing LocaleSyncService
- Builder navigation now orders Theme before Branding - merchants pick a palette first, then upload logos that match it
- New builder keys (languagesSummaryDesc, manageLanguages) in en/ru/hy; verified in browser (chips RU-default/EN/HY, nav order theme->branding)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: unstyled B/I/U/H2/List/1,2,3 buttons were not understandable for non-technical users.
- Toolbar buttons grouped by intent (text style | headings | lists | insert | advanced) with visual separators, hover states, consistent 30px hit targets
- Icons (PrimeIcons) for list/link/image/table/divider/code/embed; B/I/U keep their conventional letters but rendered in their own style (bold/italic/underline) as every mainstream editor does; every button gets a translated tooltip and aria-label (en/ru/hy)
- Code view toggle moved to the right edge, visually de-emphasized - it is the expert path, not a primary action
- Toolbar visually attaches to the editing surface (shared border, joined radius) so it reads as one control
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: badges were a comma-separated text input with no preview.
- Replaced with a chip editor: current badges render as removable pills in the exact colors customers see on the storefront (colors sourced from getBadgeClass/item.utils.ts so admin preview and product card never drift)
- The standard set (new/sale/exclusive/hot/limited/bestseller/featured) is offered as one-click add chips; custom badges added via text field, unlimited, shown in the storefront's custom-badge grey
- Deliberately NOT per-badge arbitrary colors: badge colors are part of the storefront design contract (string[] + fixed palette); introducing per-badge color storage would change the product data shape consumed by the future backend
- New adminProducts keys (badgesHint, addBadge, removeBadge, customBadgePlaceholder) in en/ru/hy; verified in browser
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: four parallel description fields (default/ru/en/hy) read as duplicates and confused the admin.
- The default-language name/short description/rich description now stand alone; per-language fields moved into a collapsed 'Translations' disclosure with a translated-count badge (e.g. 1/3)
- The disclosure explains the fallback rule in merchant language: empty translation means customers see the default text
- Translation inputs show the default value as placeholder, making the fallback visible instead of implied
- Locale codes shown uppercase (RU/EN/HY) to read as language labels, not field suffixes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: category icon input and breadcrumbs ('Хлебные крошки') were unexplained, slug and SEO were manual.
- Title edits derive the slug and SEO title with the same never-overwrite-manual-input rule as products (verified in browser)
- SEO group gains the same 'Fill from product details' one-click action
- Icon field now explains itself (emoji/symbol shown in menus) and shows a live preview of the entered symbol
- Breadcrumb block explains in merchant language what customers see (Home › Electronics › Phones) and where it comes from; separator changed to › to match the storefront
- New adminCategories keys (navBreadcrumbHint, iconHint) in en/ru/hy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: admins did not know what SKU means, had to hand-write slugs, and left SEO empty.
- Name edits now derive the URL slug automatically (supports Cyrillic/Armenian characters); the derivation stops the moment the slug no longer matches the auto value, so a manually edited slug is never overwritten - verified in browser
- SEO title mirrors the name under the same only-while-untouched rule
- SKU field explains itself (what a stock keeping unit is, that any unique text works) and gains a Generate button producing readable codes like WIR-GAM-7K2P from the product name
- SEO tab gains 'Fill from product details': fills only empty title/description/keywords from name and short description, existing text untouched
- New adminProducts keys (slugHint, skuHint, generate, seoGenerate, seoGenerateHint) in en/ru/hy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Transactions: 'adminTransactions.flag'/'clearFlag' raw keys rendered on the fraud toggle button (missed by earlier audit because the key sits inside a ternary); added en/ru/hy strings with merchant wording ('Flag as suspicious')
- Table overflow on Customers/Transactions root-caused: app-table host is a grid item with default min-width:auto, so wide tables stretched their parent card instead of scrolling inside the wrapper; shared TableComponent now declares display:block/min-width:0/max-width:100% on its element selector (encapsulation None - :host would not match), fixing every admin table at once; verified in browser: cards contained, wrapper scrolls internally
- Builder trap root-caused: section editor pages (/:lang/edit/<section>) had zero routes to the backoffice - only a link back to the builder overview - so admins inside a section could not return to the dashboard; sidebar now has a persistent 'Back to dashboard' link (new builder.backToDashboard key, en/ru/hy); verified navigation lands on /ru/backoffice/dashboard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Audited every 'key | translate' and translateService.t() usage under features/admin against en/ru/hy; 37 static keys and 8 dynamic namespaces were missing and rendered as raw keys
- Added full adminTransactions namespace (was entirely absent), adminUsers table/invite/session keys with scopeValue/statusValue/invitationStatus maps, adminMonitoring section titles and categoryValue/queueStatus/webhookStatus/levelValue maps, adminAnalytics.topProducts, adminProducts.variants/archived
- Resolved leaf/object key conflicts: dynamic 'adminMonitoring.category.*' renamed to categoryValue.*, 'adminTransactions.type.*' to typeValue.* so the leaf labels translate correctly
- Monitoring event severity now translated (levelValue) instead of raw English enum
- Merchant wording over technical: 'Background queues', 'Webhook deliveries', 'Activity log', 'Running smoothly/Slowed down/Stopped'
- Verified zero missing keys in en/ru/hy via AST-level key extraction
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Business Dashboard: revenue/orders/customers/AOV/top products/low stock/recent activity/warnings/completion% (Overview tab)
- Marketplace Health: real completion checks (images, SEO, categories, reviews, orders, translations, homepage, backend, performance) with clickable recommendations
- Product Analytics: top selling/most reviewed/worst rated/hidden/archived counts; top-viewed honestly marked Unknown (no view tracking exists)
- Customer Analytics: new/returning customers, average spend, retention - derived from real order data
- Search & Traffic tabs: honest 'Unknown - available after backend integration' placeholders, no fabricated numbers
- Recommendations engine: actionable cards (missing images/SEO/category, empty categories, incomplete homepage, unpublished static pages) linking to the relevant admin page
- System Monitoring: added real draft/publish/sync/backend-connectivity state from AdminDashboardFacade
- CSV export extended to top products and health checks; added print action
- New adminAnalytics.* and adminMarketplaceHealth.* i18n namespaces (en/ru/hy), no raw i18n keys
- Reused existing shared UI (app-table/app-badge/app-skeleton/app-empty-state) and content-health-widget completion-meter pattern - no new backend APIs, no storage changes
New Reviews & Moderation feature (frontend only): moderation dashboard (real pending/approved/rejected/reported/spam counts, average rating, recent activity, moderation-health%, computed from the full review queue); reviews list with table/cards, density, saved column visibility, search/status/rating filters, bulk approve/reject/spam/hide/export; review detail shows customer/product/rating/text/photos/timeline/moderator-notes with a real moderation workflow (approve/reject/spam/hide/restore/pin/feature, feature disabled with an explanation unless the review is approved); reusable ReviewHealthWidget (rating/text/media/moderated/report-status/visible + completion%); reports queue lists reports against products/reviews/customers/categories with resolve/dismiss, and honestly renders 'Not available yet' rather than fabricating a value wherever a target can't be resolved (e.g. photos, customer-target reports). Backed by a new in-memory AdminModerationLocalGateway seeded from real product data - the same mock-gateway pattern already used by every other admin feature in this app (orders/products/categories), since no review/report backend exists to reuse. Replaced the 'Reviews' comingSoon nav placeholder with a working link; added full adminModeration i18n coverage.