Files
marketplaces/docs/architecture/foundation/Seller-Management-Storefront-Audit.md
sdarbinyan f4b92c7909 docs: Storefront audit for market.com/seller.market.com compatibility
Audit only, no code changed - facts gathered by reading current
source (routes, containers, header/footer, SeoService), not assumed.
Covers Homepage, Categories, Products, Search, Favorites, Cart,
Checkout, Reviews, SEO, Breadcrumbs, Header, Footer.

Core finding: tenant resolution is already entirely backend-side by
request Host (ADR-001) - the frontend just consumes whatever bootstrap
comes back for whatever hostname it's running on. A seller subdomain
is architecturally closer to already working than any part of the
Backoffice audit found; the real gaps are all about whether the
*data* rendered carries a seller-aware value, not about routing/
hosting.

Key findings:
- Canonical URLs already correct today - SeoService.siteUrl derives
  from location.origin dynamically, not hardcoded. Nothing to change.
- Header/Footer/SEO branding all read through one shared facade
  (UiRuntimeFacade.reloadFromBootstrap()) - a single future injection
  point that would cascade to all three for free, rather than three
  separate fixes.
- SeoService.setItemMeta() (per-product OG/canonical tags) is defined
  but never called anywhere in the codebase today - a pre-existing
  dead hook, unrelated to seller-scoping but blocking any future
  per-product/per-seller SEO work until wired.
- No dedicated breadcrumb component/service exists anywhere in the
  storefront - the only breadcrumb logic in the app is one local
  signal in catalog-container.component.ts.
- Checkout is not a separate route - it's an inline popup flow in
  cart.component.ts, with no multi-vendor/multi-seller cart concept
  at all. This is where Checkout Modes and Unified/Split Orders (both
  marked Future in Seller-Management.md) would actually need to land.
- Structured data (JSON-LD) and sitemap generation don't exist for
  anyone today, marketplace or seller - net-new work either way, not
  seller-specific gaps.
- One pre-existing, unrelated issue noted in passing: og:locale is
  hardcoded 'ru_RU' in SeoService - flagged, not fixed (out of scope).

Linked from docs/architecture/foundation/README.md alongside the
other Seller Management docs.
2026-07-26 22:32:41 +04:00

12 KiB

Seller Management — Storefront Audit (market.com / seller.market.com)

Audit only, no code changed. Every fact below was gathered by reading current source on feature/seller-management-foundation, not assumed. Companion to Seller-Management.md §6 (Seller Storefronts, Seller Branding — both marked Future there) and Seller-Management-Backoffice-Readiness-Audit.md (the equivalent admin-side audit).

The core question: does the architecture already support a seller subdomain?

Yes, at the resolution layer — no frontend change needed there. Tenant resolution is entirely backend-side by request Host (ADR-001): the frontend calls GET /bootstrap and renders whatever comes back, with no client-side knowledge of what hostname it's running on beyond what it reads from window.location. If a backend ever resolves seller.market.com to a seller-scoped bootstrap response, the frontend's fetch-and-render pipeline doesn't need to know that happened — it already just consumes BootstrapConfig. This is the single most important finding of this audit: the hard problem is not "can the frontend handle a second hostname" (it already architecturally can, by design), it's "does the data the frontend renders (branding, breadcrumbs, SEO, contact info) carry a seller-aware value to render instead of the marketplace-wide one." That's a bootstrap-response and per-surface question, audited area by area below — not a routing or hosting question.

Global structure

Every storefront route sits under one :lang prefix (app.routes.ts) — /${lang}/catalog/:id, /${lang}/product/:id, /${lang}/wishlist, /${lang}/cart, /${lang}/search. No seller/tenant segment exists in the route tree today, and none needs to for the subdomain approach — the hostname carries the seller scope, not a path segment. /search is not a distinct page — it lazy-loads the same CatalogContainerComponent as /catalog. Checkout is not a separate route or component — it's a payment-popup flow inline inside cart.component.ts (features/website/checkout/ and features/website/cart/ are empty placeholder folders, .gitkeep only).

Area by area

Homepage — Ready structurally

pages/home/home.component.ts is a thin wrapper: gets a page-render model from WebsiteRuntimeFacade.getPageRenderModelForUrl() and renders it. No branding/URL logic of its own, nothing hardcoded. Future: a seller-scoped home would need WebsiteRuntimeFacade's page-model resolution to become seller-aware — out of scope of this file, belongs to whichever facade resolves the page model once a seller bootstrap concept exists.

Categories / Search — single injection point identified

features/website/catalog/containers/catalog-container.component.ts (shared by both /catalog and /search) builds its own breadcrumb via CategoryFacade.getBreadcrumb()CategoryTreeUtils.getBreadcrumb() — pure category-parent-chain traversal, no marketplace-root assumption baked into the algorithm itself, but no seller dimension either. Reads catalogConfig/userExperienceConfig straight from the bootstrap snapshot (marketplace-wide today). Future — Seller breadcrumbs: since no dedicated breadcrumb component or service exists anywhere in the storefront (confirmed repo-wide — this is the only breadcrumb logic that exists at all), a future "Seller X > Category > Product" trail has exactly one call site to touch: this component's breadcrumb signal build.

Products — reviews live here too, one dead SEO hook found

features/website/product/containers/product-details-container.component.ts. Product URLs and share links (shareProduct()) are built from window.location.origin dynamically, never hardcoded. Reviews and Q&A render inside this same container (ReviewListComponent, QuestionListComponent) — there is no separate Reviews page/route to audit independently. Real finding, not seller-specific but directly relevant: SeoService.setItemMeta(item) — the method that would set per-product OG/canonical tags — is defined but never called anywhere in the codebase. Product pages today get only the site-wide default meta tags, not per-product ones. Future — Seller SEO on product pages depends on first wiring this already-existing but currently dead hook, not on inventing a new one.

Favorites / Wishlist — Ready, no change needed today

features/website/user-experience/wishlist/containers/wishlist-page.component.ts. Thin list view, no branding, no URL construction, no SEO. Future: if sellers want their own "favorited from my store" filtering, that's a filter on the already-prepared Item.sellerId? field (Seller-Management-Domain-Models.md) applied at read time — no structural change to this page.

Cart / Checkout — one literal string worth flagging

pages/cart/cart.component.ts. Checkout logic (openPaymentPopup, createPayment, status polling) lives entirely in this one file — there is no separate checkout page. getPaymentDescription() falls back, in order: bootstrap.branding.brandNameTenantResolverService.getHostname() (non-localhost) → the literal string 'Покупка на Маркетплейсе' ("Purchase on Marketplace") for the payment-provider's description field. This is a generic last-resort fallback, not a hardcoded brand name, but it is single-tenant-framed. recordOrder() posts to apiService.createOrder with no explicit marketplace/seller field — backend infers via Host (ADR-001), same pattern as everywhere else. Future — this is where Checkout Modes and Unified/Split Orders (both marked Future, undecided, in Seller-Management.md §6) would actually land: today one cart always produces one order via one popup flow; a cart containing items from multiple sellers has no defined behavior here at all yet.

Header — clean, single injection point for branding

components/header/header.component.ts. brandNameUiRuntimeFacade.marketplaceDisplayName(); logoUiRuntimeFacade.logoUrl(). Both fully dynamic, sourced from bootstrap.branding via UiRuntimeFacade.reloadFromBootstrap() (facades/runtime/ui-runtime.facade.ts). No hardcoded name/logo anywhere. homeUrl is /${lang} (root-relative — correct as-is for a seller subdomain, since the subdomain itself carries the scope, not a path segment). Future — Seller logo / Seller banner / Seller branding: this facade's reloadFromBootstrap() method is the single highest-leverage place to add a seller-branding override — if bootstrap.seller?.branding (the already-typed but unused SellerBranding, Seller-Management-Domain-Models.md) is ever populated, this one method could prefer it over bootstrap.branding before setting facade state, and Header/Footer would pick it up automatically with zero changes of their own, since they already read exclusively through this facade.

components/footer/footer.component.ts. brandNameuiRuntime.marketplaceName(), contactEmailuiRuntime.contactEmail() — identical facade source as Header. Footer link groups/payment icons come from FooterResolverService.resolveFooterModelFromBootstrap(), entirely bootstrap-driven, nothing hardcoded. Future — Seller contact page: no such page or concept exists today; the footer currently only ever surfaces one marketplace-wide contact email/address. A seller contact page would be net-new routed content, not an extension of the footer's existing contact surfacing — the footer would, at most, link to it once it exists.

SEO — canonical URLs already correct; structured data and sitemap are 100% net-new

services/seo.service.ts is the sole SEO surface in the app — confirmed no sitemap generator, no robots.txt handler, and no JSON-LD/structured-data code exists anywhere in the repository.

  • Canonical URLs — Ready today, no change needed. siteUrl is derived from this.doc?.location?.origin (actual browser location), not hardcoded — a page served from seller.market.com already gets a correct seller.market.com canonical URL with zero code changes. This is the one area in the whole audit that needs nothing further.
  • Seller branding in meta tagssiteName getter reads this.uiRuntime.marketplaceDisplayName() || 'Marketplace'; the resetToDefaults() effect reads bootstrap.seo.default + bootstrap.branding (including og:image from branding.socialImageUrl/logoUrl). Future: the same single injection point as Header/Footer above (UiRuntimeFacade) — once that facade can prefer seller branding, SeoService inherits it automatically without its own changes, since it already reads through the same facade.
  • Seller structured data (JSON-LD)does not exist for anything today, marketplace or seller. This is entirely Future/net-new work, not an extension of an existing pattern.
  • Seller sitemap — no client-side sitemap code exists at all; dynamic sitemap generation is already flagged in BACKEND.md as a server-side-only remaining-work item with no frontend action. A per-seller sitemap is the same story: entirely a backend concern.
  • Pre-existing, unrelated to seller-scoping, worth flagging anyway: og:locale is hardcoded to 'ru_RU' in both setItemMeta() and resetToDefaults() — a real gap for multi-locale SEO generally, not something to fix as part of Seller Management, but adjacent enough to note here since it lives in the same service this audit reviewed closely.

Breadcrumbs — no shared component, one call site

Already covered under Categories/Search above — repeating for completeness since the mission listed it separately: there is no dedicated breadcrumb component or service anywhere in the storefront. The only breadcrumb logic in the entire codebase is catalog-container.component.ts's local signal, built from CategoryFacade.getBreadcrumb().

Summary — future changes only (nothing here is implemented)

Area Current state Future seller-scoped change
Homepage Ready — thin page-model wrapper Depends on WebsiteRuntimeFacade becoming seller-aware
Categories / Search One breadcrumb call site, no seller dimension Extend CategoryFacade.getBreadcrumb() call in catalog-container.component.ts
Products URLs already dynamic; setItemMeta() exists but is dead code Wire the existing (currently unused) per-page SEO hook before adding seller data to it
Reviews Lives inside Product detail, no separate page No independent seller-scoping work — follows Products
Favorites Ready — no branding/URL logic Optional future filter on already-prepared Item.sellerId?
Cart / Checkout One inline flow, one popup, no seller/multi-vendor concept Where Checkout Modes + Unified/Split Orders (both Future in Seller-Management.md) would land
Header Fully dynamic via UiRuntimeFacade Highest-leverage single injection point: prefer bootstrap.seller?.branding in reloadFromBootstrap()
Footer Same facade source as Header Inherits the Header fix automatically; Seller contact page is net-new routed content
SEO — canonical URLs Already correct, derived from location.origin None needed
SEO — branding in meta tags Reads through UiRuntimeFacade Inherits the Header/Footer fix automatically
SEO — structured data (JSON-LD) Does not exist for anything today 100% net-new, not an extension
SEO — sitemap No client-side code at all; backend-only concern (BACKEND.md) No frontend action, ever
Breadcrumbs One ad hoc signal, no shared component Same single call site as Categories/Search

Conclusion

The storefront's existing discipline — everything reads through UiRuntimeFacade/ConfigService/bootstrap, nothing hardcodes marketplace identity, canonical URLs derive from actual browser location — means a seller.market.com subdomain is structurally closer to already working than any other part of this audit found. The entire future-work surface collapses to two real gaps: (1) UiRuntimeFacade.reloadFromBootstrap() needs to prefer seller branding when present (one method, cascades to Header/Footer/SEO for free), and (2) Cart/Checkout's single-seller-per-order assumption needs the Unified-vs-Split-Orders decision from Seller-Management.md before multi-vendor carts can be handled at all. Structured data and sitemap are not seller-specific gaps — they're simply unbuilt for anyone today.