Only breadcrumb logic anywhere in the storefront was a local signal +
inline markup inside catalog-container. Extracted a generic
shared/ui/breadcrumb component (rootLabel/items/ariaLabel inputs,
rootClick/itemClick outputs) and repointed catalog-container onto it,
removing the now-dead inline SCSS block. Future breadcrumb usages
(product detail, admin) have something to reuse instead of duplicating.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Third attempt, done properly this time - first two were reverted
(one stopped cleanly on real conflicts, one botched sequencing and
deleted the old focus-trap before finishing the swap).
DialogComponent gains closeOnEscape/closeOnBackdropClick (default true,
backward-compatible with its 13 other call sites) and ariaLabel (for
dialogs with no visible title header). FOCUSABLE_SELECTOR now includes
iframe for the bank-payment panel's focus trap.
Cart wires closeOnBackdropClick=false on both dialogs (in-flight payment
shouldn't cancel on a stray click) and closeOnEscape tied to the bank
popup's open state, so Escape closes the nested bank iframe first and
falls back to the QR view - matches the original priority exactly.
Original geometry (500px QR modal/40px padding, 960x760 bank modal/
56-16-16 padding, both mobile breakpoints) preserved via :host ::ng-deep
overrides scoped per dialog instance - same pattern already used by
product-carousel-widget.component.ts.
cart.component.ts loses ~90 lines of hand-rolled ViewChild/HostListener/
focus-trap code - app-dialog owns all of it now.
Verified live in browser: dialog sizing/padding/aria-label correct at
mobile+desktop, backdrop-click confirmed inert, Escape-priority confirmed
(bank closes first, then QR), initial focus lands on close button.
83/83 tests pass, tsc/build clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@lucide/angular shipped its entire ~1500-icon set in the initial bundle
despite the app only using 85 named-imported icons - confirmed upstream
tree-shaking failure (sideEffects:false, clean named imports, single
non-splittable fesm file). Replaced icon-registry.ts/icon.component.ts
with hand-rolled inline SVG rendering of just the 85 used icons,
transcribed from lucide's own node data for pixel-identical output.
Zero call-site changes - AppIconName and app-icon's public API unchanged.
Also: karma-coverage wired (npm run test:coverage), baseline captured
in docs/SPRINT-PLAN-NEXT.md (32% statements / 18.5% branches).
Cart-modal -> app-dialog migration was attempted and reverted - real
conflicts (backdrop-close, nested-modal escape priority, iframe sizing),
documented in docs/FUTURE_FEATURES.md for a properly scoped follow-up.
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).
- 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.
Search suggestions/popular-searches (search-autocomplete.service.ts,
search.facade.ts) set icon values like 'inventory_2', 'category', 'sell',
'auto_awesome', 'trending_up' - Material Symbols ligature names rendered as
raw {{ item.icon }} text in search-bar.component.html. No Material Icons
font is loaded anywhere in this Lucide/app-icon-based app, so these
rendered as literal garbled text ("inventory_2", etc.) instead of icons.
- SearchSuggestion.icon retyped from string to AppIconName (search.model.ts)
- Suggestion icon values mapped to registered app-icon names: product->package,
category->folder, brand->tag, ai->zap, popular/trending->trendingUp (new
registry entry, LucideTrendingUp)
- search-bar.component now renders <app-icon [name]="item.icon" /> instead of
the raw ligature string, and its icon-only clear ("x") button now renders
app-icon name="x" instead of a bare literal "x" character glyph
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>
Mechanical, value-preserving: font-weight: 400/500/600/700 -> var(--font-weight-normal/medium/semibold/bold, <same value>) across src/app.
Note: cart.component.scss now sits ~771 bytes over its per-file budget
in angular.json due to longer var() strings; non-fatal build warning,
noted in docs/UI-DESIGN-REVIEW.md as a follow-up (either bump the
component style budget slightly or accept the warning).
Normalized the 18 shared/ui components (button, input, select, badge,
card, section-card, table, dialog, empty-state, form-field, pagination,
toggle, image-field, key-value-editor, locale-tabs, color-picker,
code-editor, skeleton) — these are the reusable primitives consumed
across storefront/builder/backoffice — to use the new
--font-size-*/--font-weight-*/--line-height-* tokens and the
--radius-xs/--radius-full tokens introduced in the previous commit,
replacing one-off literal values (0.875rem, 13px, 999px, 12px, etc.).
Fixes found along the way:
- code-editor.component.scss: focus border/shadow used a hardcoded
#497671 (the dexar tenant's primary color) instead of
var(--primary-color) — would not adapt to the lavero/novo tenant
themes. Now theme-aware.
- section-card.component.scss used raw 16px/18px/14px radius/padding
instead of the --radius-lg/--space-* tokens the sibling card
component already used, so cards and section-cards had slightly
different rounding/padding for no reason. Aligned to the same scale.
- toggle/badge/item-tag: raw 999px pill radius replaced with the new
--radius-full token.
Deferred: syntax-highlighting colors in code-editor (.cm-*) are
intentional and left untouched; tenant brand colors in the three
theme.scss files are intentional per-tenant palettes, left untouched.
app-select (the shared select used across every admin form) rendered
the browser's native dropdown indicator - inconsistent across
Chrome/Firefox/Safari and outside the icon system entirely. Hid it
(appearance: none) and added a consistent chevronDown via app-icon.
app-pagination used literal HTML entities (« / ») for
prev/next instead of icons. Replaced with chevronLeft/chevronRight.
Every native <details>/<summary> expander (7 call sites across admin
product form, page editor, and the builder's widget advanced-settings
panel) relied on the browser's default disclosure triangle, which
again varies per browser and shares no visual relationship with the
rest of the icon system. Added one global CSS rule (details > summary)
that hides the native marker and draws the same Lucide chevron path
used everywhere else, animated on open/close - covers all 7 without
touching each template.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
telegram-login: close X and lock icons (auth-gate icon reused across
telegram-login and cart - same 'log in required' concept, now the
same lock icon in both instead of two different hand-drawn shapes);
retry/refresh icon (QR expired/error states, was duplicated). Added
missing aria-label on the close button (had none).
catalog-empty-state, category, subcategories, item-detail: empty-state
illustrations (package/search/grid), add-to-cart icons, success/error
status icons, and thumbs up/down vote icons replaced. Rating stars
(item-detail, both product rating and per-review rating) now use one
Star icon with a color input and a .dx-star--filled CSS class for the
solid/outline toggle, instead of hand-toggling raw fill/stroke SVG
attributes - fixes the same rating-star pattern being drawn two
different ways (outline-only in the carousel earlier, fill-toggling
here).
Added color input to app-icon (was stroke-only via currentColor
before) and four more icons to the registry: refresh, thumbsUp/Down,
locate/mapPin (added earlier this session).
Also fixed the currency-dropdown chevron in language-selector that a
prior replace_all missed (same markup, different [class.rotated]
binding target).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cart: trash/X/plus/minus/lock icons replaced with app-icon. Removed
the standalone EmptyCartIconComponent entirely - it was a duplicate
80px shopping-cart glyph with no unique illustration, only ever used
in one place; now app-icon name="cart" inline.
Language/region selectors: dropdown chevrons (duplicated 3x with
identical path data across two components) unified on
chevronDown; region pin, locate (crosshair), and globe icons replaced.
New mapPin/locate icons added to the registry.
Items carousel: rating star and add-to-cart icons replaced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace every PrimeIcons pi-* usage across the builder: overview page
(back link, next-step arrow, section cards, readiness checklist,
quick links), sidebar nav (group/section status dots), main layout
(back/home/menu/help icons), brand + homepage overview panels
(checklist ok/pending dots, contrast warning), footer/homepage/widgets
section editors (drag handles, move up/down, duplicate, remove), and
the HTML editor toolbar (list/link/image/table/divider/code/embed).
Notable correctness fix: PrimeIcons reused pi-bars for both the
hamburger menu toggle AND every drag handle - two different meanings
sharing one icon (exactly the kind of icon collision the audit calls
out). Added a dedicated 'grip' icon (GripVertical) for drag handles so
menu and drag-to-reorder are visually distinct.
Added a global .spin utility (icon-registry has no built-in spinner
animation) for the one loading-spinner icon in the builder overview
checklist.
All icon-bearing fields (BuilderGroup.icon, BlockCatalogEntry.icon,
WIDGET_ICONS, STATUS_ICON, HtmlEditorToolbarCommand.icon, etc.) are
now typed AppIconName instead of string.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace every PrimeIcons pi-* class (static and data-driven) in the
admin dashboard area with app-icon: dashboard-card, dashboard-shortcut-
card, dashboard-status-row, dashboard-timeline, and the icon data in
admin-dashboard.facade.ts / admin-dashboard-page.component.ts. Icon
fields on AdminDashboardQuickAction/Shortcut/DashboardTimelineEntry
are now typed AppIconName instead of string, so a typo or unmapped
icon name is a compile error instead of a silently blank icon.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add @lucide/angular dependency and a single shared entry point for
every icon in the app: app-icon (src/app/shared/ui/icon), backed by a
canonical name -> Lucide-icon registry (icon-registry.ts) covering
every concept the RC icon audit needs across storefront, builder, and
backoffice. One name per meaning, one default size/stroke-width, so
every screen renders the same icon the same way.
Package note: lucide-angular (unscoped) is deprecated upstream in
favor of @lucide/angular - installed the maintained package directly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
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.
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.