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>
Products/Categories saveDraft() and deleteOne(), and Users
setRole()/setStatus()/invite(), had no error handler at all - a
failed mutation was completely silent.
Also found and fixed the same premature-navigation bug as the Phase 1
cart fix: both product and category editor pages called
router.navigate() immediately after facade.saveDraft(), before the
save had resolved - so even after adding error feedback, the user
would already be gone from the page before it could show. saveDraft()
now takes an onSuccess callback and only the page navigates on actual
success; on failure it stays put and shows a themed error dialog.
Added a mutationError signal to all three facades and a themed
app-dialog error alert on the products/categories editor + list pages
and the users page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AdminAnalyticsFacade.error() already existed but the reports page
template never checked it - a load failure just rendered the summary
cards with default/zero values, looking like a legitimate empty
report instead of a failure.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Orders/Products/Categories/Customers/Transactions loadList() caught
errors by silently clearing the list to [] with no error state - an
API failure looked identical to a genuine 'no results' empty state.
Added an error signal to each facade (set on failure, cleared on
retry) and an error branch in each list page/component, distinct from
both loading and the real empty state.
Order and Customer detail (loadDetail) had it worse: no error handler
at all, so a failure just left the page on 'Loading...' forever with
nothing to retry or navigate away with. Added selectedLoading/
selectedError to both facades and an error screen with a back button
to both detail pages.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Order cancel/refund and user suspend used window.confirm(). Migrated
all three to app-confirm-dialog, matching products/categories/orders
delete gates from earlier in this phase.
Remaining window.confirm() usages are the three canDeactivate dirty
guards (categories/products/project-editor) - left as-is, since
CanDeactivate needs a synchronous or Observable/Promise return and
browser navigation guards conventionally use the native dialog there;
converting those is a separate, larger refactor.
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>
The status <select> on the order detail page let an admin jump
straight to 'cancelled'/'refunded' with no confirmation, bypassing the
dedicated cancel()/requestRefund() buttons that do confirm. It also
stayed editable after an order reached a terminal status, so it could
be moved backward out of cancelled/refunded.
- Dropdown options now exclude terminal statuses; reaching them
requires the confirm-gated buttons.
- setStatus() guards against a terminal status slipping through
regardless.
- Once an order is terminal (isTerminal(), already computed but
unused), the dropdown and both action buttons are disabled.
- Same fix applied to the orders list page's bulk status dropdown,
which had the identical gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
createPayment() and recordOrder() hardcoded currency: 'RUB' regardless
of LanguageService.currentCurrency() (app supports RUB/USD/EUR/AMD).
Widened CartPaymentRequest.currency from a 'RUB' literal to string and
use the actual selected currency in both calls.
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>
Adds §12 to the living backend reference: admin role claim, HttpOnly
session cookie, server-side order pricing, and a real order audit
trail, each with the proposed API/JSON shape.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both server blocks were missing Content-Security-Policy and
Permissions-Policy entirely (dexarmarket.ru already had them). This is
defense-in-depth against XSS, not a fix for the underlying issue: the
customer session cookie is still non-HttpOnly and JS-readable, which
only a backend Set-Cookie change can close (BACKEND-API-REFERENCE.md
§12).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AdminOrderTimelineEntry had no actor field at all - order status
changes and refund requests were unattributed. Added actor: string,
populated from the signed-in admin's displayName (same pattern as
Users/Transactions), surfaced in the order detail timeline UI.
Real backend-issued orders still need a server-side audit trail;
this only covers the local mock gateway pending backend work
(BACKEND-API-REFERENCE.md §12).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
createOrder() sent a discount-applied price per line item that the
client computed itself, with no server revalidation. Items now only
carry productId/name/quantity - the backend must price from its own
catalog. createPayment()'s amount (required to actually charge the
payment gateway) is unchanged; backend must revalidate it instead,
tracked in BACKEND-API-REFERENCE.md §12.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
adminAuthGuard only checked isAuthenticated() - any signed-in admin
could reach any route. The live Telegram/QR auth (Mechanism A) carries
no role claim, so a real gate needs a backend change (tracked in
BACKEND-API-REFERENCE.md).
Added AdminPermissionsService + requireAdminPermission() guard factory
that derive a permission set locally by matching the Telegram username
against the mock Users domain's roleId - the same local-only stand-in
already used for the rest of that domain. Wired onto /backoffice/users
requiring 'users.manage'. Explicitly cosmetic: backend must
independently authorize every mutation regardless of what this guard
decides.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
audit entries hardcoded actor: 'admin' regardless of who performed the
action. Both local gateways now pull the signed-in admin's displayName
from AdminAuthService, falling back to 'admin' only when unavailable.
Moderation's actor field is a role classifier ('admin' | 'customer'),
not an identity string, and is left unchanged.
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>
Removes all tracked repo documentation (root status docs, docs/,
docs/architecture/foundation/**, docs/archive/**, docs/context/BACKEND-AUDIT.md
+ adrs, src/assets/mock/README.md) and replaces it with:
- GAPS-AND-IMPROVEMENTS.md — role-based findings (user, PO, QA, backend,
accessibility, engineering) plus automated code-review passes over the
storefront and backoffice, each with file:line references. Findings only,
no fixes applied.
- BACKEND-API-REFERENCE.md — single consolidated backend contract: auth
(both mechanisms), bootstrap, pagination/sorting/filtering conventions,
error model, every live/mock-only endpoint with JSON examples, and the
admin-domain DI-token seam gaps.
Open items and unresolved decisions from the deleted docs (KNOWN-ISSUES,
PRODUCT_BACKLOG, SPRINT-PLAN-NEXT, Seller-Management audits, etc.) were
harvested into the two new files before deletion, not lost.
CLAUDE.md/AGENTS.md/GEMINI.md/.claude/ and docs/context/{INDEX,LOG,
MAINTENANCE,README}.md are untouched — confirmed gitignored, never part of
git history, outside this cleanup's scope.
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>
11MB of the ~14MB initial bundle is the full @lucide/angular icon set
despite clean named imports for ~85 icons - confirmed by build inspection,
previously undocumented (existing note only covered the two lazy chunks).
Root cause is upstream tree-shaking, not app code. Real fixes (package
upgrade or dropping the dependency) need sign-off before touching.
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).
Principal-architect-level review of the entire Seller Management body
of work (7 prior docs + all touched code), verified against fresh
tsc --noEmit and arch:check runs, not recalled from memory.
Real bug found and fixed (in scope per this mission's "unless
absolutely required" carve-out - a one-line correctness fix to
already-committed code, not new feature work):
AdminSellerManagementPageComponent.sellerManagementEnabled read the
bootstrap snapshot once via a plain signal() at construction, not
reactively via bootstrapRevision() the way UiRuntimeFacade/SeoService
both correctly do elsewhere in this codebase. Fixed to computed() keyed
on bootstrapRevision(). Currently invisible (flag is always false,
signal was never even read in the template) but would have gone stale
the moment bootstrap ever reloaded with the flag true. tsc clean after
the fix.
Findings documented in Seller-Management-Final-Design-Review.md (no
Critical/High severity found anywhere):
- Medium: SellerConfig (bootstrap wire shape) and Seller/SellerBranding
(domain entity) are two unreconciled type hierarchies for the same
concept - self-flagged already in BACKEND.md SS11.6, restated here as
an independently-confirmed finding rather than letting it drift.
- Medium: no reusable capability-guard abstraction exists anywhere in
the codebase, despite ADR-009/ADR-011 both prescribing "check the
flag in one place" - ADR-009's own described FeatureFlagService was
never built. Fine with one consumer, a real drift risk the moment a
second one needs the same check.
- Medium: the flag's true branch has never been exercised, even
manually - every verification claim in this whole body of work was
tested at the flag's real value (false).
- Low/nice-to-have: sellerId typed as bare string instead of the UUID
alias used everywhere else in the new sellers domain; MarketplaceRef
vs TenantConfig overlap (deliberate, documented, but worth watching);
documentation-to-code ratio (8 docs, zero backend bytes) carries a
consolidation-burden risk, especially the Unified/Split-Orders
question restated independently in 4 different docs.
- Explicitly checked for and did NOT find: circular dependencies,
scattered tenant/seller conditionals, over-engineering relative to
the typed-models-only mandate, or any auth/payment code touched.
Verdict: not an unqualified "ready for implementation" - two Medium
findings should be resolved by decision/small build before real
backend work starts, not because they block anything today but
because both compound in cost the longer they're left unresolved.
Everything actually built (typed foundation, disabled-by-default flag,
Phase 1 UI, plus the bug this review fixed) is solid and ready to
stay exactly as-is. No Critical or High-severity issue found anywhere.
Documentation only, no code. Synthesizes the 3 prior audits
(Seller-Management.md, the Backoffice readiness audit, the Storefront
audit) plus BACKEND.md SS11 into one migration plan covering all 17
requested modules: Authentication, Authorization, Bootstrap, Products,
Categories, Orders, Payments, Transactions, Reviews, Analytics, Media,
Search, CMS, Builder, Settings, Notifications, Emails, Audit Logs.
Per module: current behavior, future behavior, migration strategy,
backward compatibility, risk, effort, and an endpoint classification
(No change / Minor change / Major change / New endpoint) grounded in
facts already established in the prior audits - no new exploration,
no invented specifics.
Headline finding: Orders (the Unified-vs-Split-Orders decision) is the
single highest-risk, most consequential item in the whole plan -
payments/refunds/reporting all depend on it, and it can't be resolved
by an additive field the way every other domain's seller-scoping can.
Payments stays untouched (ADR-010, frozen) under the Unified path;
only Split Orders would ever touch the payment flow, and only then
with the same scrutiny the original frozen implementation got.
Cross-cutting sections included per mission: Database changes
(one nullable seller_id column touches existing tables, everything
else is new tables - no NOT NULL migration ever required), Permission
changes, Caching (bootstrap cache key must include resolved seller
identity), Indexes, Security (seller-to-seller isolation treated with
tenant-isolation rigor), Performance, API Versioning (ties to the
already-open BACKEND.md SS2.10 decision), Migration order (14 numbered
dependency steps), and 6 recommended implementation phases (A:
Foundation through F: Operational polish).
Every phase explicitly re-asserts the non-negotiable constraint:
modules.sellerManagement.enabled=false must show zero behavioral
difference before/after each phase ships.
Linked from docs/architecture/foundation/README.md alongside the
other Seller Management docs.
docs/BACKEND_API.md no longer exists as a live file (merged into
BACKEND.md in an earlier consolidation pass, per that doc's own intro
- only docs/archive/BACKEND_API.md remains, historical only). This
mission's "update BACKEND_API.md" instruction is fulfilled by
extending the doc that actually supersedes it: new §11 "Seller
Management (Optional Capability)", added to the top-of-file table of
contents, no existing section renumbered or altered.
Every subsection explicitly tagged Implemented / Planned / Future,
matching the same legend used in docs/architecture/foundation/
Seller-Management.md (the frontend-side capability doc this section
is the backend counterpart to):
- 11.2 Future entities: Marketplace (Implemented, existing
TenantConfig unchanged), Seller/SellerBranding (Planned - frontend
types exist, no backend schema), SellerUser/SellerSettings/
SellerInvitation (Future - no type, no concept, named for roadmap
completeness only).
- 11.3 Future endpoints: Seller CRUD/Activation/Invitations/Branding/
Analytics/Dashboard - all Future, none designed, each noted as
following the existing mock-to-API-gateway pattern (SS8) once built.
- 11.4 Authentication: SellerPermissionRole (4 roles) explicitly
flagged as a separate vocabulary from the existing, live AdminRole -
not merged, no guard wired, zero auth change.
- 11.5 Domain resolution: market.com -> Marketplace is Implemented
today (ADR-001, backend-Host-resolved); nike.market.com -> Marketplace
-> Seller is Future, no backend resolves it - and per the storefront
audit, needs no frontend routing change once it does.
- 11.6 Bootstrap additions: modules/modules.sellerManagement documented
as Implemented-as-contract (typed, always false/absent today);
sellerScope/sellerBranding as Planned with an explicitly flagged open
question (SellerConfig vs SellerBranding nesting not reconciled);
permissions noted as existing/unrelated today.
- 11.7 Checkout modes: Unified Order vs Split Orders - Future, not
designed, flagged as the single most consequential undecided item
for backend design given payments/refunds/reporting all depend on it.
- 11.8 Product ownership: sellerId? on Item/AdminProduct/AdminOrder -
Implemented as schema only (optional, absent = marketplace-owned,
verified backward-compatible via tsc staying clean). Existing
products remain valid with no migration required - NULL/absent
ownership documented as a permanent state, not transitional.
No backend implemented. No frontend code touched. Documentation only.
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.
Audit only, no code changed - every fact gathered by reading current
facades/gateways/components on this branch, not assumed. Covers all
13 admin modules (Dashboard, Products, Categories, Orders, Customers,
Users, Analytics, Reviews/Moderation, Media, CMS, Builder, Settings,
Monitoring, Transactions).
Per module: answers the 3 readiness questions (does Marketplace Owner
see everything / would Seller see only their own / would Seller Staff
be limited), documents where a future scope would be injected (an
existing method/interface parameter to extend - no "if seller" checks
introduced anywhere), lists components that currently assume global
ownership, and classifies Ready / Needs scope / Needs permissions /
Needs API change.
Key findings:
- Only 3 of 13 gateways (Categories, Dashboard-metrics, Media) are
DI-token-swappable today; everything else needs that seam added
first, independent of seller scoping.
- Orders is the load-bearing blocker: Customers, Transactions, and
half of Analytics all derive from its same unscoped full-fetch order
list, and AdminOrderItem has no per-item seller attribution at all -
the concrete gap behind Seller-Management.md's open Unified-vs-Split-
Orders question.
- Users already carries an AdminUserScope/AdminRole concept (label-
only today) - the natural future home for the Marketplace Owner/
Seller/Seller Staff/Platform Admin role vocabulary.
- CMS/Static Pages and Builder/Project Editor are structurally not
about data scoping at all (marketplace-wide content, single global
config document respectively) - seller-level work there is new
product surface, not an extension.
- No admin module anywhere does role-based hiding of buttons or data
today - confirmed, not assumed.
Linked from docs/architecture/foundation/README.md alongside the
other Seller Management docs.
Master entry-point doc (Seller-Management.md) consolidating everything
built across the prior 4 commits (ADR-011, domain models, Phase 1 UI,
UX review) plus the full roadmap, with every section explicitly
tagged Implemented / Planned / Future so nothing reads as built that
isn't.
Covers: Overview, Architecture & Hierarchy, Marketplace, Seller,
Roles & Permissions, Feature Flags, Bootstrap, Future API, Seller
Storefronts, Seller Branding, Seller Ownership, Checkout Modes,
Unified/Split Orders, Migration & Compatibility (why existing
marketplaces stay unchanged, with the concrete verification evidence
for each claim), Developer Notes, Builder Notes, Backend Notes.
Explicitly marked Future (not designed, no shape decided) rather than
documented as if real: the API surface, seller storefronts, checkout
modes, and the unified-vs-split-order decision - none of these have
any code or ADR behind them yet, unlike the typed models/feature flag/
Phase 1 UI which are genuinely Implemented.
Added a rollout-stage diagram (types+flag -> Phase 1 UI -> backend
decisions -> CRUD -> branding/storefronts -> checkout modes) showing
work stops after "Phase 1 UI" today. Linked as the entry point from
docs/architecture/foundation/README.md and docs/PROJECT_INDEX.md,
ahead of ADR-011/diagrams/domain-models/UX-review which stay as
detail references.
No code changed.
Reviewed the Phase 1 UI against every other Backoffice page. Found and
fixed 2 real issues; everything else verified already consistent
(built entirely from shared components, so hover/focus/dialog-a11y/
dark-readiness/contrast come from those components, not reinvented).
Fixed:
- Message textarea had no id/aria-describedby wiring (app-input
self-wires this via injected FormFieldContext; the raw textarea -
no dedicated textarea component exists yet - never got it, so the
visible label's `for` pointed nowhere). Added explicit aria-label
bound to the same translation key as the visible label.
- Learn More dialog's feature list would render native browser
bullets (no global list-style reset exists outside details>summary
in styles.scss). Replaced with checkCircle icon + text rows,
consistent with how the rest of the app pairs icons with list/status
meaning.
Added docs/architecture/foundation/Seller-Management-UX-Review.md
documenting both fixes plus everything checked and confirmed already
consistent (empty-state usage, icon reuse, translations completeness
across en/ru/hy, responsive at 1280px/375px, dialog a11y verified via
accessibility tree not assumed).
tsc --noEmit clean, arch:check (boundaries + cycles) clean. Live-
verified: Learn More dialog shows all 6 items each with an icon
(confirmed via DOM query), textarea aria-label confirmed
"Сообщение", no console errors.