Re-verified GAPS-AND-IMPROVEMENTS.md's "duplicate search models" item:
core/search/models/search.model.ts was already a re-export shim (fine),
but core/search/models/search-state.model.ts is a real second copy, and
features/search/facade/search.facade.ts has a third, private
LegacySearchState interface with the same fields again. Documented as its
own scoped task rather than fixed here - reconciling three shapes on the
catalog rendering path needs full consumer tracing first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Doc was 4 days stale relative to source - each item below was independently
verified against the current file/line during this session's Track Z sweep,
not just marked off the todo list:
- Ed25519 auth-error body-code bug
- Dark mode selector (now actually fixed this session)
- Site Layout selector fallback
- setItemMeta() wiring
- og:locale dynamic locale
- stars.component.scss token usage
- AdminRole duplication
- PRODUCT_DATA_PROVIDER/CATEGORY_REPOSITORY dead mock branch
- sellerId UUID typing
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
theme-engine.service.ts already sets [data-theme-mode] on the root, but
no CSS anywhere consumed it - picking Dark/System never changed anything
visually. Adds a structural dark override block per theme (dexar/lavero/
novo): bg/text/border/shadow tokens only. Brand colors (primary/secondary/
accent/gradients) are left untouched - a distinct dark-mode brand palette
is a design decision for the theme owner, not made here.
Also verified during this pass, no change needed (GAPS-AND-IMPROVEMENTS.md
was stale on these):
- og:locale already reads languageService.currentLanguage() dynamically
- SeoService.setItemMeta() is already called from product-details-container
- stars.component.scss already uses var(--border-color), no literal hex
- sellerId is already typed UUID, no bare-string field remains
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- models/category.model.ts: Category -> CategoryApiModel, disambiguated
from core/categories/models/category-domain.model.ts's Category (admin
domain shape). Removes a dead unused import in item.utils.ts along the
way. Only live consumer was services/api.service.ts, updated in place.
- BACKEND-API-REFERENCE.md §5: corrected two rows documenting the
TOKEN_EXPIRED/INVALID_SIGNATURE auth-error bug as still open - the fix
(reading error.error.code before falling back to HTTP status) is
already in auth.service.ts. Doc was stale, not the code.
- Sprint 0.2 audit: AdminRole duplication and the
PRODUCT_DATA_PROVIDER/CATEGORY_REPOSITORY dead mock branches were
already resolved in a prior pass - verified, no code change needed.
- docs/backend/PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md: new wire contract
for Money/FxQuote/PriceSnapshot/payment state machine, so backend can
start Phase 1 the moment the frozen payment chain is unblocked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds InjectionToken + factory for Orders, Products, Users, Transactions,
Monitoring, Moderation (mirrors existing Categories/Dashboard pattern) and
repoints their facades plus the derived Analytics/Customers facades and
admin-order-watcher off the mock LocalGateway class directly. No behavior
change today - still resolves to the mock - but a real backend can now be
bound per domain with zero facade edits.
Docs: full gap analysis of Product Plan v3.1 against current repo state,
and a phased delivery plan (10 phases, 34 sprints, 5 tracks) breaking every
identified gap into scoped, sequenced work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Previous fix (1032891) stopped polling via AdminLayoutComponent's
DestroyRef, but logout() never navigates or destroys the component -
the watcher kept polling and toasting indefinitely after logout.
Now polling starts/stops off AdminAuthService.isAuthenticated() directly,
following the same effect() pattern already used by AdminDashboardFacade.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Component read item.currency (source) as both the display label and the
conversion target, so amounts never actually converted - only the label
technically matched. Now converts deliveryPrice/selectedDeliveryTotal via
CurrencyRatesService and labels with the shopper's selected currency.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- popularSearches sent translated display text as the actual search
query instead of the canonical term - useSuggestion() now prefers
target.query.q when present.
- CartService.addItem() dedup guard resolved immediately instead of
awaiting the real in-flight add; now tracks the pending Promise per
itemID so concurrent callers await the actual result.
- addItem()'s Promise never rejected on failure (resolve() in both
next/error branches) - now rejects on error; buyNow() catches and
shows an error toast instead of navigating on a failed add.
- Quick View had no stale-response guard - a slower earlier request
could overwrite a faster later one. Added a request-generation
counter.
- cart autoSubmitPurchase() set paymentStatus to null synchronously
right after firing the async submit call, blanking the success
screen while the request was still in flight. Removed the
redundant/harmful line.
- Order terminal-status guard (cancelled/refunded can't be reopened)
lived only in the page component. Moved enforcement into the
gateway (single write path) via a shared TERMINAL_ORDER_STATUSES
const, so no future caller can bypass it.
- TranslatePipe's per-instance memoization cache had no eviction,
so bindings with volatile params (pagination counts) grew it
unbounded for the component's lifetime. Capped at 50 entries.
Not changed: the dark-mode color override was flagged as clobbering
admin branding, but it's the exact palette explicitly requested this
session for the global dark default - not a bug.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both tokens switched on getProductProviderMode()/getCategoryProviderMode()
but every case (including 'mock') fell through to the same real API
provider - no mock implementation of either interface exists. Removed
the dead switch instead of leaving code that implies a mock mode which
was never built.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AdminRole was defined twice with unrelated shapes (core/auth's real
JWT role union vs. the Users admin page's display interface), flagged
in BACKEND-API-REFERENCE.md \u00a72b as needing a rename. Renamed the
Users-page one to AdminUserRoleRecord.
sellerId was bare string in admin-order/admin-product/item models
while core/sellers/models/seller-scope.model.ts already used the
shared UUID alias. Aligned all three to UUID for consistency (UUID is
currently just = string, so this is a documentation-level type change,
not a behavior change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
orders/products/categories/reviews don't depend on each other but were
fetched serially, 4 levels deep. forkJoin runs them in parallel.
Also fixes a real race: a rapid setDateRange() double-call previously
had no cancellation, so a stale in-flight chain could resolve after
and overwrite a newer one. Added a cancelPreviousLoad$ subject with
takeUntil.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
updateRange()/updateSlider() emitted stateChange synchronously on
every keystroke/drag event, triggering a full catalog filter
recompute each time. Debounced both (350ms, per filterId+key timer,
cleared on destroy).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pure:false stays (needed so language switches propagate without
touching every | translate template call site), but repeat calls with
unchanged key/params/language now hit a Map lookup instead of
re-splitting the key and re-walking the translation object tree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>
theme.mode wrote data-theme-mode to the DOM but no CSS ever reacted to
it, and mapThemeConfigToCssVariables() never looked at mode at all -
selecting Dark had zero visible effect.
Added dark-mode neutral overrides (background/text/border axis only -
brand colors stay as configured) using the palette provided by the
user (colorhunt.co/palette/091413285a48408a71b0e4cc):
--bg-primary: #091413 --bg-secondary: #285a48
--text-primary: #b0e4cc --text-secondary: #408a71
--border-color: #285a48
ThemeEngineService now resolves 'system' mode via
prefers-color-scheme and re-renders live on OS theme changes, and
sets data-theme-mode to the *effective* resolved mode instead of the
raw setting.
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>
The /contacts route had no bootstrap.staticPages entry at all (the old
hardcoded contacts page was intentionally removed per app.routes.ts's
comment, in favor of the generic staticPath -> CMS resolver). Added a
placeholder entry (route/title/html per locale) so the link resolves
instead of 404ing. Placeholder text explicitly says the real contact
details go through the admin panel - per user decision, real content
belongs to whoever runs a given marketplace, not something to fabricate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
JSON-LD was absent entirely (sitemap generation is separate backend
work, out of scope here). Added Product schema (name/description/
image/offers with price+availability) on item pages via setItemMeta(),
and a site-wide Organization schema via resetToDefaults(), both
injected as a single #seo-json-ld <script type=application/ld+json>
tag that gets replaced on navigation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bootstrap.layout.type (the 'Site Layout' selector in the theme editor)
was only ever consulted by a validator checking it against the known
list - nothing used it to actually pick a layout. SectionEngineService
already resolves a per-page layout.type with a hardcoded 'default'
fallback; that fallback now reads the site-wide setting first.
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>
CartService.addItem() fired an async dynamic import + API call for
new items but returned void immediately - buyNow() navigated to /cart
before the item was actually added, landing the user on an empty or
stale cart. addItem() now returns a Promise that resolves once the
cart signal actually contains the item; buyNow() awaits it before
navigating.
Also wired SeoService.setItemMeta()/resetToDefaults() into the product
detail page - built and working, but never called anywhere, so every
product page rendered the site-wide default OG/Twitter tags instead of
per-product ones.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both setItemMeta() and resetToDefaults() hardcoded 'ru_RU'. Added a
LanguageService-driven mapping (ru/en/hy -> ru_RU/en_US/hy_AM).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Known bug per BACKEND-API-REFERENCE.md §5: the client has dedicated
session-expired/invalid-signature recovery screens fully built, but
the mapper only ever derived the error code from HTTP status, never
the response body - so a real 401 with error.code: 'TOKEN_EXPIRED'
rendered the generic 'Unauthorized' screen instead.
Frontend half of the fix: prefer error.code from the body when present
(mapped via authErrorCodeFromBackendCode), fall back to status-derived
code otherwise. Stays dormant until the backend actually sends the
code, per the doc.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
resolveByKey/resolveByRoute subscribed with only a next callback -
a resolver failure left loading=true forever with no error branch to
recover from. Added an error signal, error subscribe handler, and a
distinct error state UI (separate from the existing 404 not-found
state) with a way back home.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A category/product facade error propagated through switchMap
uncaught, erroring the shared widget stream (shareReplay) in
WidgetHostService with no fallback - the widget just silently failed
to render, and the error stayed cached for every later subscriber.
Added catchError falling back to an empty { section, settings } shape,
same pattern as the widget-manifest fetch (falls back to { widgets: [] }
on any error, never throws to the UI).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>