docs: replace 62 scattered/stale markdown files with two living references
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
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>
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
# Angular 22 Upgrade Plan (research only — not applied)
|
||||
|
||||
Feasibility assessment for upgrading from the current Angular 21.1.5 to Angular 22. **No upgrade was performed** — this is a plan, per mission instructions ("Do NOT upgrade automatically. Stop.").
|
||||
|
||||
## Current state (verified against `package.json` + npm registry, 2026-07-25)
|
||||
|
||||
| Package | Current | Latest available |
|
||||
|---|---|---|
|
||||
| `@angular/core` (+ animations/cdk/common/compiler/forms/platform-browser/router/service-worker) | 21.1.5 | 21.2.18 (latest 21.x) / 22.1.0-rc.0 (latest 22.x) |
|
||||
| `typescript` | ~5.9.3 | 6.0.3 stable |
|
||||
| `rxjs` | ~7.8.0 | compatible with both 21 and 22 |
|
||||
| `zone.js` | ~0.16.0 | compatible with 22 (`~0.15.0 \|\| ~0.16.0` required) |
|
||||
| `primeng` | ^21.0.3 | 22.0.0 stable exists |
|
||||
| `@lucide/angular` | ^1.25.0 | no upper Angular bound (`>=17.0.0`) — not a blocker |
|
||||
| Node.js (this environment) | v22.16.0 | Angular 22 CLI requires `^22.22.3 \|\| ^24.15.0 \|\| >=26.0.0` — **current Node does not satisfy this** |
|
||||
|
||||
Correction to the project's own status tracking: `docs/PROJECT_INDEX.md`/`CLAUDE.md` describe this as "Angular 18+" — the repo is actually already on **21.1.5**, one major behind the latest stable (22.1). This is a much smaller jump than "18→22" would imply.
|
||||
|
||||
## Verdict: upgrade is safe, with 2 concrete pre-requisites
|
||||
|
||||
Nothing found in this codebase blocks the jump on its own merits — the risk is entirely in the dependency chain, not the app code:
|
||||
|
||||
1. **`primeng@^21.0.3` peer-depends on `@angular/core@^21.0.7` only** — it does not accept Angular 22 today. **However**, this dependency is already dead code (`docs/KNOWN-ISSUES.md` item 12): its only consumer, `items-carousel`, was deleted during RC PERF-01, and its removal is already planned, just blocked on an unrelated `npm uninstall` failure (see #2). Once `primeng`/`primeicons` are actually removed from `package.json`, this blocker disappears entirely — no need to wait for/adopt `primeng@22`.
|
||||
2. **`barry-cache@^0.1.0` in `package.json` no longer resolves** (`ETARGET`) — confirmed via `npm view barry-cache`: the real published range is now `0.9.3` (20 versions total), and `^0.1.0` doesn't intersect anything currently on the registry. This is what's been silently blocking `npm install`/`npm uninstall` all cycle (referenced in `docs/PERFORMANCE_REPORT.md`, `docs/KNOWN-ISSUES.md` item 12). **This must be fixed first** — bump `barry-cache` to a current version — or `ng update` itself will fail the same way `npm uninstall primeng` already does.
|
||||
3. **Node.js**: this dev environment runs v22.16.0; Angular 22's CLI requires `^22.22.3 \| ^24.15.0 \| >=26.0.0`. A Node bump is required before `ng update` will even run, independent of the app.
|
||||
|
||||
Once those 3 are resolved, the app itself is well-positioned:
|
||||
- 100% standalone components already (no NgModules to migrate).
|
||||
- 190/191 components already `ChangeDetectionStrategy.OnPush` (per `docs/PERFORMANCE_REPORT.md`) — directly aligned with v22 making OnPush the default; this app barely changes behavior from that shift.
|
||||
- Heavy existing signals usage (facades are signal-based per `docs/ARCHITECTURE.md`/ADR-007) — aligned with where Angular is going (Signal Forms, `resource()`), no fighting the framework.
|
||||
- Zero usage found of the specific APIs v22 removes: `ComponentFactoryResolver`, `ComponentFactory`, `provideRoutes()`, `CanMatchFn` (grepped `src/app/**`, zero hits).
|
||||
- Zone-based (not zoneless) via `provideZoneChangeDetection({eventCoalescing: true})` in `app.config.ts` — this continues to work under v22, no forced zoneless migration needed to upgrade.
|
||||
|
||||
## Benefits
|
||||
|
||||
- Bug fixes and perf improvements shipped between 21.1 and 22.1 (6+ months of patches this repo isn't getting).
|
||||
- OnPush-by-default aligns with where this codebase already is — near-zero migration cost for that specific change, unlike a codebase still on default change detection.
|
||||
- Keeps pace with `primeng`/ecosystem packages that are already moving to v22-only releases (relevant once primeng is actually removed and no longer a constraint either way).
|
||||
- Closes the gap before the next major (v23) makes this a two-major jump instead of one.
|
||||
|
||||
## Risks / breaking changes relevant to this codebase
|
||||
|
||||
1. **Route parameter inheritance changes from `emptyOnly` to `always`.** No explicit `paramsInheritanceStrategy` override was found in `app.routes.ts` or `app.config.ts` — meaning this app is on the default, and the default is changing. **Concrete risk**: any component reading `ActivatedRoute.params`/`paramMap` that currently expects to NOT see a parent route's params (e.g. a child route under `/:lang/backoffice/:id/edit` reading only its own segment) could start receiving inherited params it didn't before. Needs a manual audit of nested routes with route params at each level — `src/app/app.routes.ts` has several (product detail, category, admin edit routes) — not just a blanket "run the test suite and hope."
|
||||
2. **TypeScript 6.0 minimum** — current is 5.9.3, a straightforward `npm install typescript@^6.0.3` bump, but TS 6 does include its own (separate) breaking changes to check independently of Angular (stricter inference in some cases) — budget a pass for TS compiler errors post-bump, not just Angular's.
|
||||
3. **`primeng`/`primeicons` removal must land first** (see Verdict #1) — sequencing matters: remove dead deps → fix `barry-cache` → bump Node → `ng update`, not the reverse.
|
||||
4. **No automated test suite beyond the default Jasmine/Karma scaffold** was confirmed running in this session (`docs/SPRINT-PLAN.md` Sprint 29 notes: "Translation validation / lint... No lint script exists") — meaning post-upgrade regression detection leans entirely on `tsc --noEmit` + `ng build` + manual verification, the same constraint every other pass this cycle has worked under. The route-params risk above in particular needs *manual* route-by-route verification, not just a green build, since it's a runtime behavior change a type-checker can't catch.
|
||||
|
||||
## Migration steps (sequenced)
|
||||
|
||||
1. **Unblock tooling**: bump `barry-cache` in `package.json` to a currently-published version (`0.9.3` or latest at execution time) — verify with `npm view barry-cache versions` first.
|
||||
2. **Remove dead `primeng`/`primeicons`** (already-planned, `docs/KNOWN-ISSUES.md` item 12) — now unblocked by step 1. Verify `npm run build` still green afterward (it was already confirmed code-dead in RC PERF-01, this just finishes the dependency removal).
|
||||
3. **Bump Node.js** in the dev/CI environment to satisfy `^22.22.3 | ^24.15.0 | >=26.0.0`.
|
||||
4. **Bump TypeScript** to `^6.0.3`, run `tsc --noEmit`, fix any TS-6-specific compiler errors before touching Angular.
|
||||
5. **Run `ng update @angular/core@22 @angular/cli@22`** (and `@angular/cdk@22` if still a dependency) — let the official schematic handle the mechanical parts.
|
||||
6. **Audit route-param inheritance** manually across every nested route with params in `app.routes.ts` (product detail, category, admin edit/detail routes) — the one behavior change with no automated safety net.
|
||||
7. **Full verification pass**: `tsc --noEmit`, `npm run build`, `npm run arch:check`, plus a live browser walkthrough of the same route list used in `docs/RELEASE_REPORT.md` (storefront/builder/backoffice) — this upgrade deserves the same rigor as that pass, not just a build check.
|
||||
8. **Commit, do not push** without explicit sign-off, same as every other pass this cycle.
|
||||
|
||||
## Estimated effort
|
||||
|
||||
- Steps 1-4 (unblock tooling, remove dead deps, Node/TS bump): **0.5-1 day** — mechanical, low risk, mostly already-planned work.
|
||||
- Step 5 (`ng update`): **0.5 day** — the schematic does most of the work given zero deprecated-API usage found.
|
||||
- Step 6 (route-param audit): **0.5-1 day** — the one genuinely manual, judgment-requiring step; depends on how many nested-param routes actually exist and how many read parent params today (needs a route-by-route trace, not estimated further without doing that trace).
|
||||
- Step 7 (verification): **0.5-1 day** — matches the RC walkthrough pass's effort, since that's the closest analog in this codebase's own history.
|
||||
|
||||
**Total: ~2-3.5 days** for one engineer, assuming no surprises in the route-param audit (the one genuinely unknown risk). This is a small-to-medium upgrade, not a large one — the app's existing standalone/signals/OnPush posture did the hard work already.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Safe to schedule. Not urgent (still only one major behind), but low-risk and the gap only grows if deferred further. Do the two prerequisite fixes (`barry-cache`, `primeng` removal) regardless of upgrade timing — they're blocking other things too (this dependency chain is also what's stopping the `primeng` bundle-size win noted in `docs/PERFORMANCE_REPORT.md`).
|
||||
@@ -1,73 +0,0 @@
|
||||
# ARCHITECTURE
|
||||
|
||||
## Platform principles
|
||||
|
||||
- One codebase, unlimited tenants. No tenant-specific implementation code in the frontend.
|
||||
- Tenant behavior is controlled entirely by configuration loaded at bootstrap (`GET /bootstrap`, tenant resolved server-side by domain).
|
||||
- Prefer configuration over conditionals, composition over inheritance.
|
||||
- Authentication, payment, and authorization contracts/behavior are frozen and must not be redesigned as part of platform work (`docs/architecture/foundation/adr/ADR-010-backward-compatibility-for-auth-payment-authorization.md`).
|
||||
- No circular dependencies; shared/UI layers are feature-agnostic.
|
||||
|
||||
These rules are enforced, not aspirational — see `docs/architecture/foundation/README.md` and the ADR set below, plus `npm run arch:check` (import-boundary + circular-dependency checks).
|
||||
|
||||
## Architecture Decision Records (source of truth — read directly, do not treat this file as a paraphrase)
|
||||
|
||||
All under `docs/architecture/foundation/adr/`:
|
||||
|
||||
- **ADR-001** — platform model (multi-tenant, config-driven).
|
||||
- **ADR-002** — layered feature architecture.
|
||||
- **ADR-003** — import boundaries and dependency direction.
|
||||
- **ADR-004** — configuration bootstrap and provider abstraction.
|
||||
- **ADR-005** — dynamic page/section/widget rendering.
|
||||
- **ADR-006** — UI component purity and container/facade pattern.
|
||||
- **ADR-007** — state management and facade boundaries.
|
||||
- **ADR-008** — theme engine and design-token runtime.
|
||||
- **ADR-009** — feature flags and capability guards.
|
||||
- **ADR-010** — backward compatibility for auth/payment/authorization.
|
||||
- **ADR-011** — optional Seller Management module (typed foundation only, not built; see `docs/architecture/foundation/Seller-Management-Diagrams.md`).
|
||||
|
||||
Companion standards docs (also `docs/architecture/foundation/`, kept as-is, enforced): `Coding-Standards.md`, `Naming-Conventions.md`, `Dependency-Rules.md`, `Folder-Blueprint.md`, `Import-Boundary-Matrix.md`, `State-Management-Standards.md`, `Configuration-Standards.md`, `Component-Standards.md`, `Service-Standards.md`.
|
||||
|
||||
## Layered architecture
|
||||
|
||||
```
|
||||
Component (container) --> Facade --> Domain Service --> Repository/Provider --> Mock | API
|
||||
```
|
||||
|
||||
- **Container/page components** own routing, orchestration, and DI of a facade. They hold no business logic.
|
||||
- **Presentational components** are `@Input()`/`@Output()`-only: no `HttpClient`, no storage, no environment access, no facade injection (ADR-006). The Project Editor's *sections* (`features/project-editor/sections/*`) are an accepted exception — they are container/section components, not shared presentational UI, so they may inject the facade directly (see `docs/EDITOR.md`).
|
||||
- **Facades** (`facades/**`, or feature-local `facade/`) are the only thing components talk to. They expose signals/observables and imperative methods; they compose one or more domain services (ADR-007).
|
||||
- **Domain services** (`core/<domain>/*.service.ts`) convert backend DTOs into domain models via a **mapper**, and expose domain-shaped methods. DTOs never leak past the mapper boundary.
|
||||
- **Repositories/providers** are swappable via injection tokens (e.g. `PRODUCT_DATA_PROVIDER`, `CATEGORY_REPOSITORY`, `BACKOFFICE_DATA_PROVIDER`, `ADMIN_DASHBOARD_METRICS_GATEWAY`) so mock and real-API implementations can be swapped without touching facades or components — the same pattern used throughout `core/`, `features/admin/*`, and `features/backoffice/*`.
|
||||
|
||||
## Bootstrap / configuration engine
|
||||
|
||||
- `ConfigService` loads `BootstrapConfig` (see `docs/BACKEND.md#1-bootstrap`) once at startup; `PlatformRuntimeService` applies it (theme, branding, runtime state) and can `reloadFromBootstrap()` for in-memory preview without a full page reload.
|
||||
- The bootstrap is the single source of truth for pages, sections, widgets, theme, navigation, footer, static pages, and feature flags (ADR-004).
|
||||
- The Project Editor mutates an in-memory draft of the same `BootstrapConfig` — there is no parallel editor-only model.
|
||||
|
||||
## Dynamic page / section / widget rendering (ADR-005)
|
||||
|
||||
Render pipeline: `page config -> section engine -> section renderer -> widget host -> registered widget component`.
|
||||
|
||||
- **Section Engine** (`dynamic-renderer/section-engine/section-engine.service.ts`) builds an ordered page render model from `PageConfig.sections`, applying `order`, `layout` (`SectionLayoutConfig.strategy`: `stack | grid | hero | carousel | split`), and `visibility` (desktop/tablet/mobile).
|
||||
- **Page Renderer** (`dynamic-renderer/page-renderer/page-renderer.service.ts`) delegates to the Section Engine.
|
||||
- **Widget Host** (`dynamic-renderer/widget-host/widget-host.service.ts`) resolves each widget's component via the **Widget Manifest** (`widgets/registry/widget-manifest.service.ts`, `widgets/contracts/widget-manifest.contract.ts`) and its data via the **Data Source Resolver** (`widgets/resolvers/data-source-resolver.service.ts`), which delegates to `CategoryFacade`/`ProductFacade` — widgets never call APIs directly.
|
||||
- Widgets receive only `{ section config, resolved data }` as inputs; they render presentation only, never fetch or mutate.
|
||||
- Unknown/unregistered widget types render a safe fallback; this is also surfaced in `features/diagnostics` (dev-only, route `/__diagnostics`).
|
||||
- `dynamic-page-layout.component.ts` (`layouts/containers/`) is the top-level container that composes Section Engine output using `PlatformLayoutConfig.type` (`default | sidebar-left | carousel-home | minimal`).
|
||||
|
||||
## Theme engine (ADR-008)
|
||||
|
||||
- `ThemeConfig` (`shared/models/config/theme.model.ts`): `themeId`, `mode` (`light | dark | system`), `palette` (12 semantic colors), `typography`, `spacing`, `borderRadiusScale`, `shadows`, `iconSet`.
|
||||
- Applied as CSS custom properties at runtime; components/widgets consume tokens, never hardcoded brand colors.
|
||||
- Three tenant theme stylesheets live under `src/styles/themes/*.theme.scss` — see `docs/FRONTEND.md` for the CSS custom property convention.
|
||||
|
||||
## Feature flags / capability guards (ADR-009)
|
||||
|
||||
- `bootstrap.featureFlags` (typed) plus the broader `bootstrap.features` (`MarketplaceFeaturesConfig`) surface for UI-facing toggles (wishlist, compare, reviews, recommendations, search history, etc.).
|
||||
- Feature resolution falls back across older config surfaces to preserve behavior as the flag model evolved across sprints — see `docs/BACKEND.md#1-bootstrap` for the full field list.
|
||||
|
||||
## Diagnostics (dev-only)
|
||||
|
||||
`features/diagnostics/` (route `/__diagnostics`, excluded from production) validates bootstrap structure (missing fields, unknown widget types, duplicate ids, unknown layout values, missing translations) and runtime health (widget render failures, missing datasources), scored 0-100. Useful when investigating a bootstrap authored by the Project Editor.
|
||||
5214
docs/BACKEND.md
5214
docs/BACKEND.md
File diff suppressed because it is too large
Load Diff
@@ -1,68 +0,0 @@
|
||||
# Dead-Config Audit (Sprint G)
|
||||
|
||||
Mechanical sweep of every field in `BootstrapConfig` and its sub-models
|
||||
(`src/app/shared/models/config/*.model.ts`), cross-referenced against
|
||||
`src/app/features/project-editor/schema/editor-schema.ts` (`SECTION_FIELD_SCHEMAS`)
|
||||
to find fields that are editable in the Project Editor but have no real runtime
|
||||
consumer — the same bug class as `HeaderConfig.showProfile` and `layout.columns`
|
||||
(both fixed earlier this cycle). Non-editable fields are listed for completeness
|
||||
but were not a priority (nothing in the editor lets a client set them, so there's
|
||||
no ghost-setting UX to fix).
|
||||
|
||||
Status legend: **live** (read, has effect) / **dead** (never read outside the
|
||||
editor) / **inert** (read, but the effect is unreachable or a stub) / **n/a**
|
||||
(not client-editable today, lower priority per sprint scope).
|
||||
|
||||
## Editable fields (client-facing — checked first)
|
||||
|
||||
| Field | Status | Recommendation | Outcome |
|
||||
|---|---|---|---|
|
||||
| `header.show*` (8 flags) | live | none | `header.component.html` reads every one |
|
||||
| `theme.palette.*` (12 colors) | live | none | `theme-css-vars.mapper.ts` |
|
||||
| `theme.mode` | inert | needs decision | already tracked in `PRODUCT_BACKLOG.md` |
|
||||
| `layout.type` ("Site Layout") | **dead** | needs decision | see below — not fixed this pass |
|
||||
| `branding.brandName/logoUrl/logoCompactUrl/faviconUrl` | live | none | header/footer/meta consumers |
|
||||
| `seo.default.title/description` | live | none | `seo.service.ts` |
|
||||
| `localization.defaultLocale/supportedLocales` | live | none | language switching |
|
||||
| `tenant.host/websiteBaseUrl` | inert by design | none | frontend never resolves its own tenant (ADR-001) — this is backend routing metadata, not something the SPA is meant to read back |
|
||||
| `company.companyName` | **dead** | needs decision | see below — not fixed this pass |
|
||||
| `company.address.street` | **dead → fixed** | wire | now shown in footer bottom bar |
|
||||
| `company.contacts.phone` | **dead → fixed** | wire | now shown in footer bottom bar (`tel:` link) |
|
||||
| `company.contacts.email` | live | none | `ui-runtime.facade.ts` fallback chain |
|
||||
| `footer.copyrightText/paymentIcons/socialLinks/columns` | live | none | `footer-resolver.service.ts` |
|
||||
| `footer.logoUrl` | **dead → fixed** | wire | `LogoComponent` gained `srcOverride`, footer passes it |
|
||||
| `catalog.navigationMode` | inert (deliberate placeholder) | leave as-is | renders a labeled placeholder card + `catalog.navigationPlaceholder` i18n string; the alternate nav UIs (mega-menu, top-carousel, left-nav) don't exist yet — building them is a real feature, not a wiring fix |
|
||||
| `catalog.suggestionsEnabled` | **dead → fixed** | wire | `SearchFacade.autocomplete()` now short-circuits to no suggestions when false |
|
||||
| `catalog.searchHistoryEnabled` | live | none | `catalog-container.component.ts` |
|
||||
| `productPage.questions.*` | live | none | `product-details-container.component.ts` |
|
||||
| `userExperience.recentlyViewed.enabled` | live | none | multiple consumers |
|
||||
| `navigation.header` | **dead** | needs decision | see below — not fixed this pass |
|
||||
| `navigation.footer` | live | none | `footer-resolver.service.ts` fallback tier |
|
||||
| `pages` / `staticPages` | live | none | core rendering pipeline |
|
||||
|
||||
## Non-editable fields (lower priority — `n/a`)
|
||||
|
||||
`branding.legalName/slogan/supportPhone/appIconUrl/galleryUrls`,
|
||||
`company.registrationNumber/taxId`, `tenant.defaultCurrency/supportedCurrencies/timezone`,
|
||||
`featureFlags.blog/chat/coupons/loyalty/giftCards/invoices`,
|
||||
`features.brands/manufacturers`, `permissions.definitions/roles` (used elsewhere,
|
||||
not via this config path), `userExperience.recentlyViewed.widgetEnabled`,
|
||||
`catalog.showBreadcrumbs/showCategoryBanner/showSubcategoryChips/enabledFilters/availableSorts/defaultSort`
|
||||
— none of these have an editor control today, so no client can create a false
|
||||
expectation by setting them. Flagged here for completeness; no action taken.
|
||||
|
||||
## Fixed this pass (trivially wireable)
|
||||
|
||||
1. **`footer.logoUrl`** — `LogoComponent` (`src/app/components/logo/logo.component.ts`) gained an optional `srcOverride` input; `FooterResolverService`/`FooterComponent` now resolve and pass `footer.logoUrl`, falling back to the brand logo exactly as before when unset.
|
||||
2. **`company.address.street` / `company.contacts.phone`** — `UiRuntimeFacade` gained `contactPhone()`/`companyAddress()` (same fallback pattern as the existing `contactEmail()`); footer bottom bar now renders a `tel:` link and the address next to the existing email link when present.
|
||||
3. **`catalog.suggestionsEnabled`** — `SearchFacade.autocomplete()` now reads the bootstrap snapshot and returns no suggestions when the flag is `false`, instead of always running autocomplete regardless of the toggle.
|
||||
|
||||
## Left dead, tracked (needs a decision, not a mechanical fix)
|
||||
|
||||
- **`layout.type`** ("Site Layout" selector, Theme section) — top-level `BootstrapConfig.layout` is edited but never applied to any page; page layout comes entirely from each `PageConfig.layout` (see `SectionEngineService.resolveLayoutType`), which this global selector doesn't touch. Wiring it requires deciding *which* page(s) it should drive (homepage only? every page without its own override?) — a product decision, not a mechanical fix. Tracked in `docs/PRODUCT_BACKLOG.md`.
|
||||
- **`company.companyName`** — Footer editor has a "Company Name" field with zero runtime consumers. The footer already has a copyright fallback (`© {year} {brandName}`, `footer.component.html`) using `branding.brandName`, not `company.companyName` — these are meant to be distinct (brand vs. legal entity name), so blindly reusing one for the other would be a content decision, not a safe mechanical fix. Tracked in `docs/PRODUCT_BACKLOG.md`.
|
||||
- **`navigation.header`** — editable list of header nav items in the Navigation section, but `HeaderComponent` never reads `NavigationConfig.header` at all; the header's own category menu comes from `CategoryFacade`, not this list. Rendering an actual configurable top-nav (positioning, active-state, children/dropdowns) is real feature work, not a one-line wire. Tracked in `docs/KNOWN-ISSUES.md`.
|
||||
|
||||
## Not touched
|
||||
|
||||
`theme.mode` (dark mode) stays exactly as already tracked in `docs/PRODUCT_BACKLOG.md` — no new information found, confirmed still inert.
|
||||
163
docs/EDITOR.md
163
docs/EDITOR.md
@@ -1,163 +0,0 @@
|
||||
# EDITOR (Project Editor)
|
||||
|
||||
Replaces the old `docs/Project-Editor.md` (content merged in below and extended with the Sprint 19 field-description/dropdown work).
|
||||
|
||||
The Project Editor (`src/app/features/project-editor/`) edits the tenant's `BootstrapConfig` (`docs/BACKEND.md#1-bootstrap`) directly — no parallel model. It is out of scope for products, categories, orders, or analytics management (those live under `features/admin/*`/`features/backoffice/*`, see `docs/BACKEND.md` §3 CRUD Contracts).
|
||||
|
||||
```
|
||||
src/app/features/project-editor/
|
||||
pages/ route container
|
||||
sections/ one component per editor tab (see below)
|
||||
components/ shared editor UI (save bar, HTML editor)
|
||||
models/ ProjectEditorState, EDITOR_SECTION_BOOTSTRAP_KEYS
|
||||
schema/ field-schema registry, validators/, history.util (Sprint X+1, see below)
|
||||
services/ ProjectValidator, ProjectEditorDraftStorageService, LocaleSyncService
|
||||
facade/ ProjectEditorFacade
|
||||
```
|
||||
|
||||
Route: `/edit/:section` or `/{lang}/edit/:section`. `/backoffice/static-pages` (the Admin dashboard) redirects here (`/edit/static-pages`) rather than hosting a second CRUD surface over the same `bootstrap.staticPages` data (Sprint X+2 — see `docs/StaticPages.md`).
|
||||
|
||||
## Facade
|
||||
|
||||
`ProjectEditorFacade` exposes: `loadBootstrap()`, `updateBootstrap(updater)`, `exportBootstrap()`, `importBootstrap()`, `preview()`, `save()`, `publish()`, `undo()`, `redo()`, plus signals `bootstrap`, `status` (`draft|published`), `dirty`, `canUndo`, `canRedo`, `lastSavedAt`, `lastPublishedAt`, `validationIssues`, `blockingIssues`, `hasBlockingIssues`, `issuesByField`, `issuesBySection`, `modifiedFields`, `modifiedSections`, `changeSummary`, `homepageWidgets`, `homepagePage`, plus the `fieldError(key)` method. Components in `sections/*` inject this facade directly (an accepted exception to the presentational-component rule, per ADR-006 — these are container/section components, not shared UI). See "Configuration schema, form engine, and validation architecture" below for the schema/validator/undo internals.
|
||||
|
||||
## Sections
|
||||
|
||||
| Section | Component | Covers |
|
||||
|---|---|---|
|
||||
| General | `general-section` | marketplace name, domain, description, default/supported languages |
|
||||
| Branding | `branding-section` | logo, small logo, favicon, social share (OG) image, gallery, marketplace title — all image fields use `app-image-field` (thumbnail preview + replace/remove) |
|
||||
| Theme | `theme-section` | palette colors (live, applied as CSS custom properties), theme mode (**not applied at runtime, see Known gaps**), site layout mode |
|
||||
| Header | `header-section` | logo/search/categories/languages/cart/profile/wishlist/compare/region toggles, layout (default/centered), sticky |
|
||||
| Footer | `footer-section` | company info, address, phone, email, copyright, payment icons, social links, static pages list |
|
||||
| Homepage | `homepage-section` | homepage section list: visibility, order (drag-and-drop), layout strategy, columns |
|
||||
| Widgets | `widgets-section` | homepage widget configuration — typed editors for hero/categories/product-collection, JSON fallback (with draft-preserving inline error, not silent-discard) for everything else |
|
||||
| Static Pages | `static-pages-editor` (`features/content-management/`) | Full CRUD, media, SEO, per-page draft/publish, device preview, nav integration — see `docs/StaticPages.md` (Sprint X+2) |
|
||||
| Marketplace Features | `features-section` | feature flags, catalog navigation mode, search suggestions/history, recently viewed, reviews/questions/recommendations |
|
||||
| Languages | `languages-section` | add/remove supported locale, set default locale; syncs translation keys across static pages and nav labels via `LocaleSyncService` |
|
||||
| Navigation | `navigation-section` | header nav: add/remove/reorder/edit label/URL/visibility, per-locale via `app-locale-tabs`. Flat footer nav: same. Grouped (column-based) footer nav is read-only here — edit via Footer tab. |
|
||||
| Preview | `preview-section` | export/import JSON, in-memory runtime preview without full reload |
|
||||
|
||||
## Save / publish / draft / reset model
|
||||
|
||||
- **Save**: `save()` snapshots the current in-memory bootstrap as "last saved" (`lastSavedAt`). `ProjectEditorDraftStorageService` persists the full draft to `localStorage` (`projectEditor.draftBootstrap.v1`, scoped by `tenant.id`) on every `updateBootstrap()`, `save()`, and `publish()` call.
|
||||
- **Publish**: runs `ProjectValidator`; if clean, calls `PlatformRuntimeService.reloadFromBootstrap()`, sets `status = 'published'`, sets `lastPublishedAt`, and becomes the new `originalBootstrap` baseline used by reset.
|
||||
- **Draft restore**: on `loadBootstrap()`, if a stored draft exists for the same tenant it loads instead of the fresh fetch, and `draftRestored` is set (shown as a dismissible banner in the save bar).
|
||||
- **Reset section**: reverts one section's bootstrap keys (per `EDITOR_SECTION_BOOTSTRAP_KEYS` in `models/project-editor.model.ts`) to `originalBootstrap`. Confirmation required.
|
||||
- **Reset draft**: reverts the entire bootstrap to `originalBootstrap` and clears the persisted local draft. Confirmation required.
|
||||
- **Per-field reset is not implemented** — no per-field default registry exists; only section- and project-level reset.
|
||||
- **No backend persistence exists for any of this today** — see `docs/BACKEND.md` §1 (Bootstrap: Draft vs Published) and §8 (Real Backend Implementation Guide) for the endpoints needed.
|
||||
|
||||
## Configuration schema, form engine, and validation architecture (Sprint X+1)
|
||||
|
||||
**Approach: metadata-augmented, not fully schema-driven.** Section templates stay hand-authored (`sections/*.component.html`); a field-schema registry sits alongside them as the single source of truth for field identity, labels, and validator wiring. This was chosen over a schema-driven renderer to preserve every existing template/UX pixel-for-pixel while still centralizing metadata and validation — the highest-value, lowest-regression-risk option given 11 mature section templates already built on the `shared/ui` primitives (see the primitives table above).
|
||||
|
||||
### Field-schema registry (`schema/`)
|
||||
|
||||
- `field-schema.model.ts` — `FieldSchema`: `{ key, section, type, labelKey, hintKey?, default?, required?, validators? }`. `key` is a dot path into `BootstrapConfig` (e.g. `theme.palette.primary`), unique per section. `validators` references reusable validator names (`hexColor`, `url`, `email`, `json`, `css`, `localeCompleteness`, `duplicateRoutes`, `widgetConfig`) rather than embedding logic.
|
||||
- `editor-schema.ts` — `SECTION_FIELD_SCHEMAS`: every editable field, one entry per section, sourced from what each template already renders. `ALL_FIELD_SCHEMAS` flattens it.
|
||||
- `editor-schema.service.ts` (`EditorSchemaService`, `providedIn: 'root'`) — `getFields(section)`, `getField(key)`, `all()`, `getByPath(source, key)` (safe dot-path resolver, never throws on a missing segment).
|
||||
|
||||
The schema is currently consumed by the facade (validation issue → field mapping, modified-field diffing, change-summary labels), not by the templates directly — templates keep calling `facade.updateBootstrap()` the same way they always did.
|
||||
|
||||
### Centralized validators (`schema/validators/`)
|
||||
|
||||
`primitives.ts` holds pure, framework-free functions — one per concern, reused everywhere that concern appears: `isValidHexColor`, `isValidHttpUrl`, `isValidEmail`, `validateJson`, `validateCss` (brace-balance check, comments stripped), `extractStyleBlocks` (pulls `<style>` bodies out of static-page HTML), `normalizeRoute` (trim/strip-slashes/lowercase for duplicate comparison).
|
||||
|
||||
`ProjectValidator` (`services/project-validator.service.ts`) composes these primitives into checks and tags every `ProjectValidationIssue` with `section`, `fieldKey`, and `severity` (`'error'` blocks Publish, `'warning'` is advisory). Checks: missing `branding.logoUrl`, no supported locales, **default locale not itself in the supported-locales list** (error — catches General's free-text default-language field pointing at an unsupported code), invalid `tenant.websiteBaseUrl`, duplicate static-page slugs, **duplicate routes** across `pages`/`staticPages` (warning), empty homepage, a homepage widget with no `type`, **malformed widget config** — missing `id`/`type`/`version`/`props` (error), duplicate header nav links, invalid theme colors, **invalid CSS** inside static-page `<style>` blocks (warning), missing translations for a supported locale, layout/section-layout values outside the known enums, **invalid company contact email** (error), **invalid footer social-link URL** (warning), and **incomplete footer payment icon** — only one of `src`/`alt` set (warning).
|
||||
|
||||
### Live inline feedback (facade)
|
||||
|
||||
`ProjectEditorFacade` exposes, on top of `validationIssues`: `blockingIssues` / `hasBlockingIssues` (severity-filtered), `issuesByField: Map<string, ProjectValidationIssue[]>`, `issuesBySection: Map<ProjectEditorSectionId, number>`, and `fieldError(key)` (first message for a field, or `null`). `publish()` gates on `hasBlockingIssues()`, not "any issue" — a duplicate-route or invalid-CSS warning no longer blocks publishing. Sections bind `[error]` on `app-form-field` (or a standalone `<p class="editor-error">` where the target isn't a single form-field, e.g. a whole list) for every field that has a matching `ProjectValidator` `fieldKey` today: theme palette, general name/domain/default-locale, branding logo, languages (`localization.supportedLocales`), homepage/widgets (`pages`), navigation (`navigation.header`), static-pages (`staticPages`), and footer (contact email, social links, payment icons). Header/features have no matching validator checks (every field there is a bool/enum, always valid by construction), so nothing is wired there — not an oversight. `project-editor-nav` shows a red badge with the blocking-issue count per section.
|
||||
|
||||
### Undo / redo (`schema/history.util.ts` + facade)
|
||||
|
||||
A pure, framework-free reducer (`emptyHistory`, `commit`, `undo`, `redo`) over immutable `BootstrapConfig` snapshots, capped at 50 entries. The facade wraps it with **debounced commits** (~300ms): `updateBootstrap()` captures the pre-burst snapshot on the first call in a burst and only pushes it to history once edits settle, so a run of rapid typing collapses into one undo step instead of one per keystroke. `undo()`/`redo()` route through the same draft-save path as every other mutation, so the `localStorage` autosave never desyncs from the in-memory undo stack. History is cleared on `loadBootstrap()`, `publish()`, and `resetDraft()` (a fresh baseline invalidates old snapshots). UI: save-bar Undo/Redo buttons (`canUndo`/`canRedo`), `Ctrl/Cmd+Z` / `Ctrl/Cmd+Shift+Z` / `Ctrl/Cmd+Y` page-level shortcuts (skipped while a text field has focus, so native per-field text undo still works).
|
||||
|
||||
### Modified-field tracking
|
||||
|
||||
`modifiedFields` (facade, `computed<Set<string>>`) diffs every schema field's current value against `originalBootstrap`. `modifiedSections` rolls that up per section for an amber dot in the nav (shown only when a section has no blocking-issue badge). `changeSummary` builds the before/after rows (schema label + stringified value, truncated for objects) consumed by the Preview section below.
|
||||
|
||||
### Pre-publish preview
|
||||
|
||||
The Preview tab (`preview-section`) now opens with a "changes since last publish" card: the full validation issue list (warning/error styled) plus a before/after table from `changeSummary`, ahead of the existing export/import/live-preview card. Reuses the existing `ProjectEditorPreviewService.preview()` — no new preview mechanism, just more visibility before triggering it.
|
||||
|
||||
### What stayed the same
|
||||
|
||||
No changes to `ProjectEditorIoService` (export/import), `ProjectEditorDraftStorageService` (draft `localStorage` format), or the publish/draft/reset flow described above — draft/publish/import/export compatibility is fully preserved. Section templates are unchanged except for `[error]` bindings on already-existing `app-form-field` usages.
|
||||
|
||||
## Admin Authentication (QR reuse)
|
||||
|
||||
Admin login shares the exact same Telegram QR/session backend and `TelegramLoginComponent` as customer login (`mode: 'admin'` vs `'customer'`) — only the cookie name/`SameSite` policy, token storage keys, and guard differ. **Backend gap:** because both flows hit the same session endpoint, the backend cannot distinguish an admin scan from a customer scan today — real admin authorization must be enforced server-side. Full detail: `docs/BACKEND.md` §4 Authentication and §5 Security (permission matrix).
|
||||
|
||||
## Design system primitives (post-Sprint 30 redesign)
|
||||
|
||||
All 11 section components (`sections/*.component.html`) share these 6 `shared/ui/*` primitives instead of copy-pasted markup. They mirror the existing `InputComponent` CVA idiom (standalone, `OnPush`, `NG_VALUE_ACCESSOR` where they're form controls):
|
||||
|
||||
| Primitive | Selector | Replaces | Used in |
|
||||
|---|---|---|---|
|
||||
| `ToggleComponent` | `app-toggle` | raw `<input type="checkbox">` + `$any($event.target).checked` | header, homepage, widgets, navigation, features |
|
||||
| `SelectComponent` | `app-select` | raw `<select>` | theme (`theme.mode`, `layout.type`) |
|
||||
| `ColorPickerComponent` | `app-color-picker` | raw `<input type="color">` | theme (8 palette colors) |
|
||||
| `SectionCardComponent` | `app-section-card` | the copy-pasted `editor-section-card`/`<h2>` shell | all 11 sections |
|
||||
| `LocaleTabsComponent` | `app-locale-tabs` | (new capability) | languages, navigation |
|
||||
| `KeyValueEditorComponent<T>` | `app-key-value-editor` | pipe-delimited `<textarea>` lists | footer (payment icons, social links) |
|
||||
| `ImageFieldComponent` | `app-image-field` | manual URL `<input>` + separate "choose image" button, no preview | branding (logo/small-logo/favicon/social/gallery), footer (logo, payment icons) — thumbnail preview + Replace/Remove, opens its own `app-media-picker` |
|
||||
| `CodeEditorComponent` | `app-code-editor` | plain `<textarea>` for raw-HTML mode | `MarketplaceHtmlEditorComponent`'s "Код" toggle — overlay-textarea syntax highlighting (no Monaco/CodeMirror dependency); tokenizes HTML tags/comments and delegates `<style>` block contents to a CSS tokenizer (selector/property/value/string/comment-aware) |
|
||||
|
||||
`section.shared.scss`'s `.editor-grid.*` classes are unchanged and still used inside `SectionCard` bodies; only the outer `.editor-section-card` shell and per-section `<h2>` were replaced (that rule has been removed from the shared stylesheet since it has no remaining consumers).
|
||||
|
||||
## Interaction / motion pass (2026-07-16)
|
||||
|
||||
Interaction feedback + motion applied consistently, all gated behind `prefers-reduced-motion`:
|
||||
|
||||
- `section.shared.scss` `button`/`button.secondary` gained hover/active/`focus-visible`/`disabled` states (previously flat, no feedback across all 11 sections).
|
||||
- `project-editor-page.component.scss`: the active section fades/slides in (220ms) when the `@switch` swaps components; the "reset section" button got matching hover/focus states.
|
||||
- `project-editor-save-bar` buttons now use the shared `app-button` primitive (danger / secondary / primary variants) instead of unstyled native `<button>`s.
|
||||
|
||||
## HTML editor (Static Pages)
|
||||
|
||||
`MarketplaceHtmlEditorComponent` (`components/html-editor/marketplace-html-editor.component.ts`) is a `contenteditable` WYSIWYG used inside the Static Pages editor (`features/content-management/.../static-pages-editor.component.html`), one instance per locale, bound `[html]` / `(htmlChange)`.
|
||||
|
||||
**Status: working.** Verified live 2026-07-16 — typing captured, toolbar commands functional (bold toggles, `insertUnorderedList` wraps `<ul><li>`, H2/H3/link/image/table), `htmlChange` emits on every edit, and a "Код" toggle swaps to raw-HTML editing.
|
||||
|
||||
**Toolbar (Sprint X+2 additions):** horizontal rule, code block (`<pre>`), embed (prompt for a URL, inserts a sandboxed `<iframe sandbox="allow-scripts allow-same-origin">`) — alongside the original bold/italic/underline/H2/H3/lists/link/image/table set.
|
||||
|
||||
**HTML-mode validation (Sprint X+2):** switching from raw-HTML back to the visual surface now runs `schema/validators/primitives.validateHtml` (stack-based tag-balance check) first; a malformed edit (unclosed/mismatched tag) stays in code mode with an inline error instead of silently corrupting the visual editor.
|
||||
|
||||
**Caveat:** implemented on the deprecated `document.execCommand` API. It works in all current browsers today but is a legacy web API with no modern drop-in replacement; if a future browser drops it, this component needs a rewrite (e.g. a maintained rich-text library). By design it emits **raw, unsanitized** HTML — sanitization is a storefront-render concern, not an authoring one (see `docs/BACKEND.md` §3 CRUD Contracts, CMS, on server-side content moderation on publish; `StaticPageComponent` and `StaticPagePreviewComponent` both run content through `DomSanitizer` before render).
|
||||
|
||||
## Field-description / dropdown UX (Sprint 19+)
|
||||
|
||||
Every field across the 10 editor section templates now carries a one-line, i18n'd description under its label explaining what it does in plain language (all new copy routed through `TranslateService`/`TranslatePipe`, added to `Translations` + `en.ts`/`ru.ts`/`hy.ts` following the existing `builder.*` key pattern — see `src/app/i18n/translations.ts`).
|
||||
|
||||
**Converted from free-text `<input>` to `<select>`** (backed by a closed TypeScript union), each option carrying a human label and a short description (via `title` attribute) instead of the raw enum value:
|
||||
|
||||
- `section.layout.strategy` (Homepage section) — `SectionLayoutStrategy`: `stack | grid | hero | carousel | split`.
|
||||
- `theme.mode` (Theme section) — `light | dark | system`.
|
||||
- `layout.type` (Theme section, "Site Layout") — `PlatformLayoutType`: `default | sidebar-left | carousel-home | minimal`.
|
||||
- `catalog.navigationMode` (Marketplace Features section) — `CatalogNavigationModeConfig`: `default | left-category-navigation | mega-category-layout | top-category-carousel`.
|
||||
|
||||
Each of these components defines a local `readonly` options array of `{ value, labelKey, descriptionKey }` (per ADR-006, these are section/container components so this is allowed without a new shared UI library).
|
||||
|
||||
**Still plain text/checkbox, with a description added, and why:** marketplace name, domain, description, logo/favicon/small-logo URLs, palette colors (already `<input type="color">`, which is the correct native widget), company/address/phone/email, copyright, payment icons/social links (JSON-ish textarea), homepage section `columns` (a number, not an enum), widget-specific props (`hero`/`categories`/`product-collection` typed fields like layout/height/overlay/autoplay/cardsPerRow — these are widget `props` strings/booleans, not modeled as TypeScript unions anywhere, so they stay free text/checkbox with a description rather than a fabricated enum), navigation link label/URL, and the widget JSON fallback textarea for any widget type without a dedicated editor. These are genuinely open-ended or already have the correct native input type; converting them to `<select>` would either be wrong (URLs/colors/free text) or invent an enum that doesn't exist in the schema.
|
||||
|
||||
## Bug-hunt audit pass (2026-07-17)
|
||||
|
||||
A section-by-section correctness audit (not a feature pass) — for each section, checked whether its controls actually do what they claim at runtime, not just whether they render. 9 real, verified defects found and fixed (each confirmed live via `window.ng.getComponent()` reproducing the exact bug, then re-verified fixed):
|
||||
|
||||
- **Footer**: `createSocialLinkRow`'s id was derived from array length (`social-${length+1}`) — add/remove/add reliably collides with a surviving row's id, corrupting `footer.component.html`'s `@for (... track item.id)` DOM identity on the public storefront. Payment-icon `@for` tracked by `icon.src`, which collides whenever two rows share a src (most commonly two blank ones). Both switched to safe keys.
|
||||
- **Features**: wishlist/compare visibility is gated by *two* flags at runtime (`featureFlags.<key>` AND `userExperience.<key>.enabled` — see `feature-config.service.ts`), but the editor only exposed a toggle for the first. Both default `true` so it was silent, but a config with the second explicitly `false` left the toggle looking "on" with no way to fix it from this screen. Now one toggle drives both.
|
||||
- **Widgets**: the JSON-fallback textarea's `updateJson()` caught parse errors and did nothing, but the textarea was bound to `propsJson(committed props)` — so an in-progress invalid edit got silently overwritten on the next change-detection pass. Now keeps the user's draft on screen with an inline error until it's valid.
|
||||
- **Languages**: `addLocale()` cleared the input regardless of whether `LocaleSyncService` actually accepted the code — adding an already-supported locale silently no-opped. Now shows an inline error and leaves the input untouched.
|
||||
- **Preview**: `importBootstrap()` replaced `state.bootstrap` directly instead of routing through `updateBootstrap()` — so an import never got a `draftStorage.save()` (lost on refresh before an explicit Save) and was never an undo-able history step. Now routed through the same pipeline as every other edit.
|
||||
- **Static Pages**: `createPage()`'s slug (`custom-page-${length+1}`) and `duplicatePage()`'s slug/route (fixed `-copy` suffix) both reproducibly collide the same way as the footer bug above (create/delete/create; duplicate the same page twice). Added a shared `uniqueValue()` helper (appends `-2`, `-3`, ... until free).
|
||||
- **General**: "Supported Languages" is a free-text comma list that bypassed `LocaleSyncService` entirely, so adding a locale here never seeded the empty translation entries Languages' add-button produces — the two UI paths silently diverged. Now diffs and routes through `facade.addLocale()`/`removeLocale()`. Also added the "default locale not itself supported" validator check described above, since this field had (and still has, by design — it's free text) no format guard.
|
||||
- **Branding → SEO**: `branding.socialImageUrl` (added earlier this same pass) wasn't actually read by `SeoService.resetToDefaults()` — the OG/Twitter image fallback stayed on `appIconUrl || logoUrl`. Fixed to check it first.
|
||||
- **Media picker**: `MediaLibraryFacade` is a root-provided singleton shared by *every* `app-media-picker` instance on a page (branding alone renders 4). `ngOnInit` loaded unconditionally on mount regardless of dialog state, and `search`/`folder`/`page` filters leaked between independently-opened picker dialogs. Replaced with an `effect()` that resets those filters and loads only when that instance's own `open` input actually becomes `true`.
|
||||
|
||||
### Known gaps found but not fixed (real, out of scope for this pass)
|
||||
|
||||
- **Theme Mode has no runtime effect.** `theme-section`'s light/dark/system selector correctly saves and sets a `data-theme-mode` attribute (`theme-engine.service.ts`), but zero CSS anywhere in the app reads that attribute — picking Dark or System currently changes nothing visually. (Theme palette colors *are* live — real CSS custom properties consumed throughout the stylesheets — only the mode switch is dead.) Fixing this is a real dark-mode implementation project (dark palette + CSS strategy + `matchMedia` for "system"), not a wiring fix. Tracked: `docs/PRODUCT_BACKLOG.md`.
|
||||
- ~~`HeaderConfig.showProfile` has no corresponding profile/account menu~~ — **fixed**: `header.component.html`/`.ts` now render a login/logout-only control (no dropdown, no account links) gated by this toggle, reusing the customer Telegram `AuthService`. See `docs/KNOWN-ISSUES.md` "Fixed (this cycle)" and `docs/GLOBAL-SPRINT-PLAN.md` Sprint A.
|
||||
- ~~`layout.type`/homepage `type` field feed an unwired `dynamic-renderer/`~~ — **stale, corrected**: `dynamic-renderer/` (`PageRendererService`/`SectionRendererService`/`WidgetHostService`) is the live homepage rendering pipeline, wired through `dynamic-page-layout.component.ts`. Verified fixed/non-issue in `docs/KNOWN-ISSUES.md` "Fixed (this cycle)".
|
||||
@@ -1,54 +0,0 @@
|
||||
# FRONTEND
|
||||
|
||||
Angular 18+, standalone components throughout (no NgModules). See `docs/PROJECT-STRUCTURE.md` for the full `src/app/**` folder tour and `docs/ARCHITECTURE.md` for the layered container/facade/service pattern.
|
||||
|
||||
## App structure at a glance
|
||||
|
||||
```
|
||||
src/app/
|
||||
core/ domain services, DTOs, mappers, repositories (per domain: categories, products, search, admin-auth)
|
||||
facades/ cross-feature facades (platform/category.facade.ts, platform/search.facade.ts, ...)
|
||||
features/ feature modules (project-editor, admin/*, backoffice/*, website/catalog, website/product, diagnostics, content-management, search)
|
||||
shared/ models/config (BootstrapConfig + ~20 sub-configs), shared UI, utils — feature-agnostic
|
||||
widgets/ widget contracts, registry/manifest, resolvers, ui components
|
||||
dynamic-renderer/ section-engine, page-renderer, section-renderer, widget-host
|
||||
layouts/ page-chrome containers (dynamic-page-layout, header/footer shells)
|
||||
i18n/ translations.ts (interface), en.ts, ru.ts, hy.ts, translate.pipe.ts, TranslateService
|
||||
pages/ top-level routed pages (home, cart, category, ...)
|
||||
components/ reusable standalone components used across features (product-card, telegram-login, ...)
|
||||
guards/ route guards (admin-auth guard, etc.)
|
||||
```
|
||||
|
||||
## Routing (`app.routes.ts`)
|
||||
|
||||
- Locale-prefixed routes: `/:lang/...` (lang from `LanguageService.currentLanguage()`), plus root redirects.
|
||||
- Storefront: `/`, `/catalog`, `/catalog/:id`, `/product/:id` (legacy `/item/:id` and `/category/:id[/items]` redirect for compatibility).
|
||||
- Static/CMS pages resolve dynamically: `/:lang/:staticPath` (legacy `/:lang/page/:key` kept for compatibility) — no hardcoded page list, resolved from `bootstrap.staticPages`.
|
||||
- Project Editor: `/edit/:section` or `/{lang}/edit/:section`.
|
||||
- Admin/backoffice: `/:lang/backoffice/**`, guarded by `adminAuthGuard` (`core/admin-auth/admin-auth.guard.ts`) — dashboard, products, categories, orders, transactions, users, moderation, media, monitoring, analytics. See `docs/BACKEND.md` for the data-source contract for each.
|
||||
- Dev-only diagnostics: `/__diagnostics` (excluded from production).
|
||||
|
||||
## i18n system
|
||||
|
||||
- `src/app/i18n/translations.ts` defines the `Translations` interface — the single schema every locale file must satisfy (TypeScript enforces this at compile time: a missing key in any locale is a build error).
|
||||
- `en.ts`, `ru.ts`, `hy.ts` implement that interface, keyed identically and nested by feature area (`header`, `footer`, `home`, `builder`, `dashboard`, ...).
|
||||
- `TranslateService` resolves the active locale and exposes translated strings; `TranslatePipe` (`| translate`) is the template-facing API — **never hardcode user-facing strings in templates**, always add a key to all three locale files.
|
||||
- 3 locales: `en`, `ru`, `hy` (Armenian). `LanguageService` tracks the active locale and drives the `/:lang/` route prefix.
|
||||
- Adding a new UI string: add the key to the `Translations` interface first, then to `en.ts`/`ru.ts`/`hy.ts` in the same position (see `docs/EDITOR.md` for the pattern used by the field-description work).
|
||||
|
||||
## Theming
|
||||
|
||||
- 3 tenant theme stylesheets: `src/styles/themes/*.theme.scss`.
|
||||
- Convention: each theme file defines CSS custom properties (`--color-primary`, `--text-primary`, `--border-color`, etc.) that mirror `ThemeConfig.palette`/`typography`/`shadows`/`borderRadiusScale`; components and widgets consume only these custom properties, never hardcoded hex values (ADR-008).
|
||||
- `theme.mode` (`light | dark | system`) and the palette are runtime-configurable per tenant via bootstrap and editable via the Project Editor's Theme section (`docs/EDITOR.md`).
|
||||
|
||||
## State management
|
||||
|
||||
- **Signals-based facades, no NgRx.** Every feature/domain exposes a facade (`ProjectEditorFacade`, `CategoryFacade`, `ProductFacade`, `SearchFacade`, `AdminDashboardFacade`, ...) built on Angular signals (`signal`, `computed`, `effect`), following ADR-007.
|
||||
- Components inject exactly one facade and read/write through it; no direct service or HTTP access from components (ADR-006).
|
||||
- Local component state (e.g. draft form values) stays in the component; cross-cutting/shared state lives in the facade.
|
||||
- Persistence for local-only features (Project Editor drafts, admin dashboard activity history) uses scoped `localStorage` keys behind a dedicated service (`ProjectEditorDraftStorageService`, `AdminDashboardHistoryService`) — never raw `localStorage` calls from components/facades.
|
||||
|
||||
## Dynamic widget/section rendering from bootstrap JSON
|
||||
|
||||
Full detail in `docs/ARCHITECTURE.md` and `docs/BACKEND.md#1-bootstrap`. Summary: `page config (bootstrap.pages) -> Section Engine (order/layout/visibility) -> Page Renderer -> Widget Host (resolves component via Widget Manifest + data via Data Source Resolver) -> widget component (props + resolved data only)`. Nothing in this pipeline calls an API directly except the Data Source Resolver, which delegates to `CategoryFacade`/`ProductFacade`.
|
||||
@@ -1,27 +0,0 @@
|
||||
# Future Features
|
||||
|
||||
Nice-to-have, non-blocking work — no client decision needed, just not worth doing now. Verified against current repo state 2026-07-26.
|
||||
|
||||
## Cart payment modal → `app-dialog` migration
|
||||
|
||||
**Done, 2026-08-06.** `.payment-modal`/`.bank-payment-modal` on the cart page now render through the shared `app-dialog` primitive instead of hand-rolled overlays. Two earlier same-session attempts were reverted before landing (one stopped cleanly after finding real conflicts, one botched the sequencing — deleted the old focus-trap before finishing the swap); this pass fixed the actual API gaps first, then migrated, then verified live in a browser before shipping:
|
||||
|
||||
- `DialogComponent` gained `closeOnEscape`/`closeOnBackdropClick` inputs (default `true`, backward-compatible with its other 13 call sites) and an `ariaLabel` input (for dialogs with no visible title header — cart's modals render their own close button in content instead). `FOCUSABLE_SELECTOR` now includes `iframe` (needed 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]="!showBankPaymentPopup()"` on the QR/status dialog (so Escape closes the bank iframe first, falls back to the QR view, matches the original nested-modal priority).
|
||||
- Exact original geometry (500px QR modal, 40px padding; 960×760 bank iframe modal, 56/16/16 padding, both mobile breakpoints) preserved via `:host ::ng-deep` overrides on `.app-dialog-panel`/`.app-dialog-panel__body`/`.app-dialog-backdrop`, scoped per-instance via `.payment-dialog`/`.bank-payment-dialog` host classes — same `::ng-deep` pattern already used by `product-carousel-widget.component.ts`.
|
||||
- `cart.component.ts` lost its hand-rolled `@ViewChild`/`@HostListener`/focus-trap methods (~90 lines) — `app-dialog` owns all of that now.
|
||||
- Verified live: both dialogs render at correct size/padding/aria-label at mobile and desktop breakpoints, backdrop-click confirmed inert, Escape-priority confirmed (closes bank first, then QR), initial focus confirmed landing on the close button. 83/83 tests pass, tsc/build clean.
|
||||
|
||||
## Angular 22 upgrade
|
||||
|
||||
Researched, not executed. Estimated ~2–3.5 days, needs the `barry-cache` dependency fix and a Node version bump first. Explicitly out of scope for the Backend Finalization Sprint. Plan: `docs/ANGULAR22_PLAN.md`.
|
||||
|
||||
## Bundle splitting
|
||||
|
||||
**Initial (eagerly-loaded) bundle carries an ~11 MB chunk that is the entire `@lucide/angular` icon set**, confirmed 2026-08-05 by inspecting build output — `app-icon`/`IconComponent` only ever needs the ~85 icons named in `icon-registry.ts`, but esbuild is not eliminating the other ~1500+ unused icon classes from `@lucide/angular`'s single-file `fesm2022/lucide-angular.mjs` bundle, despite the package declaring `sideEffects: false` and every usage in this codebase being clean named imports (no wildcard imports found). Root cause not fully diagnosed — likely each icon's Angular component metadata assignment isn't PURE-annotated in that build, so esbuild can't drop unreferenced classes within the single shared module even though it can drop unreferenced *exports*. The package ships no per-icon deep-import path as a workaround (single fesm file only). Real fix options, neither attempted here (touches a dependency, needs sign-off): (a) check for a newer `@lucide/angular` release with better tree-shaking, (b) drop the dependency and hand-roll inline SVG path data for just the ~85 used icons (removes a dependency, matches this repo's minimal-deps convention, but is real work — extracting/verifying 85 icon paths). This alone is roughly **6x the size of the two lazy chunks below combined** and, unlike them, ships to every visitor on first load.
|
||||
|
||||
Two lazy chunks are also large: `project-editor` (~1.0 MB), `catalog-container` (~330–375 kB, varies by build). No mechanical split found yet for either — needs a dedicated profiling task, ideally under real backend latency per `docs/NEXT_PHASE.md` Phase 3.
|
||||
|
||||
## Homepage hero-to-categories spacing investigation
|
||||
|
||||
A dead-space gap between the hero and categories section on the storefront home page traces to bootstrap mock config (widget/section padding values in the dev fixture), not a confirmed code defect. Needs reproduction with real tenant data before it's worth investigating further — not a bug until it's confirmed to happen outside the mock fixture.
|
||||
@@ -1,76 +0,0 @@
|
||||
# Global Sprint Plan — "Coming Soon" Stub Closure
|
||||
|
||||
Supersedes `docs/COMING-SOON-AUDIT.md` §5 sprint breakdown. One consolidated tracker for the four stub-closure sprints. Approved decisions (from AskUserQuestion): Reports/Settings ship as minimal real pages (not fake data, not empty shells); Documentation/Help nav uses an external-link approach; `docs/COMING-SOON-AUDIT.md` is deleted once all sprints land, folded into `docs/KNOWN-ISSUES.md`. Profile control constraint: **login/logout only — no dropdown, no account links.**
|
||||
|
||||
## Sprint A — Profile menu (storefront header)
|
||||
|
||||
- [x] i18n: `header.login` / `header.logout` keys in en/ru/hy (`translations.ts` type already updated)
|
||||
- [x] `header.component.ts`: inject `AuthService`, expose `isAuthenticated`, add `login()`/`logout()`
|
||||
- [x] `header.component.ts`: import `TelegramLoginComponent`
|
||||
- [x] `header.component.html`: profile control gated by `headerConfig().showProfile`, login/logout only, `<app-telegram-login />` rendered once
|
||||
- [x] SCSS matches existing header button conventions (reused `.platform-ux-btn`, no new SCSS needed)
|
||||
|
||||
**What shipped:** Header profile control wired to the customer `AuthService` (Telegram QR login). Gated by `headerConfig().showProfile` (already a real toggle in Project Editor, previously dead). Logged-out shows a login button (`user` icon), logged-in shows a logout button (`logOut` icon) — no dropdown, no account links, per the explicit constraint.
|
||||
|
||||
## Sprint B — Admin Reports page
|
||||
|
||||
- [x] `admin-reports-page.component.ts/.html/.scss` (mirrors `admin-analytics-page` structure), reuses `AdminAnalyticsFacade`
|
||||
- [x] Report cards: Sales, Top Products, Marketplace Health
|
||||
- [x] CSV export wired to existing facade export methods / existing download helper (same Blob pattern as `admin-analytics-page.component.ts`)
|
||||
- [x] Route `backoffice/reports` in `app.routes.ts`, i18n keys `adminShell.pages.reports.*` + new `adminReports.*` block
|
||||
- [x] Remove `comingSoon: true` from `reports` nav entry
|
||||
|
||||
**What shipped:** Minimal real Reports page with 3 cards (Sales, Top Products, Marketplace Health), each showing a live summary from `AdminAnalyticsFacade` and a CSV export button. Orders card was scoped out — see final report for why (reuse would require mutating a shared singleton facade's pagination state).
|
||||
|
||||
## Sprint C — Admin Settings page
|
||||
|
||||
- [x] `AdminPreferencesService` (density signal, localStorage-backed, key `adminPreferences.density.v1`)
|
||||
- [x] `admin-layout.component` applies `admin-density-compact` class to `#admin-content` shell wrapper
|
||||
- [x] `admin-settings-page.component.ts/.html/.scss` — density toggle (`app-toggle`), auto-persists on change, no separate Save button
|
||||
- [x] Route `backoffice/settings`, i18n keys `adminShell.pages.settings.*` + `adminSettings.*` block
|
||||
- [x] Remove `comingSoon: true` from nav entry AND dashboard shortcut; shortcut route → `['backoffice','settings']`
|
||||
- [x] Compact-density CSS rule added to the shared `app-table` component stylesheet (`.admin-density-compact .app-table th/td`) — applies to every admin list page built on `app-table` (orders, products, categories, etc.), not just one
|
||||
|
||||
**What shipped:** Genuinely real, backend-independent UI density preference. No maintenance-mode toggle built (explicitly deferred per `docs/NEXT_PHASE.md` Phase 4).
|
||||
|
||||
## Sprint D — Documentation / Help nav
|
||||
|
||||
- [x] Help: `mailto:` using existing `supportEmail` read path (`UiRuntimeFacade.contactEmail()`, same one `header.component.ts` already uses for `bootstrap.branding.supportEmail`)
|
||||
- [x] `AdminNavLink` gains optional `externalHref?: string`; nav renderer renders `<a>` branch (bottom nav)
|
||||
- [x] Documentation: added `tenant.documentationUrl?: string` to `TenantConfig`, populated mock with `https://docs.marketplace.local`
|
||||
- [x] `help`/`documentation` resolved dynamically in `admin-layout.component.ts` (`navBottom` computed) — real `<a>` when bootstrap data present, static `comingSoon: true` entries kept as defensive fallback for the (currently unreachable, since mock always has both fields) case where the backend omits them
|
||||
|
||||
**What shipped:** Both Help and Documentation wired to real external links, not just Help. `comingSoon: true` remains in `admin-nav.model.ts` source as a fallback flag only — it is overridden to `false` at render time whenever bootstrap actually has the data, which it does today.
|
||||
|
||||
## Sprint E — Widget layout config correctness (manifest-aware editor)
|
||||
|
||||
Root cause confirmed 2026-08-05: `widget-manifest.json` already declares `supportedLayouts` per widget type (`hero`→`[hero, split]`, `categories`→`[grid]`, `product-collection`→`[carousel, grid]`), but `homepage-section.component.ts`'s `layoutStrategyPickerOptions` is a static 5-option list (`stack/grid/hero/carousel/split`) shown identically for every homepage section regardless of which widget backs it — it never reads the manifest. The `columns` field (`homepage-section.component.html:49`) is shown for every section too, but **no widget component reads `layout.columns`** — it is currently dead everywhere.
|
||||
|
||||
- [x] `homepage-section.component.ts`: resolve each section's widget type (via its bound widget id → `widget-registry`/manifest lookup) and filter `layoutStrategyPickerOptions` down to that widget's `supportedLayouts` before rendering the picker
|
||||
- [x] Hide/disable the `columns` field for any section whose resolved widget doesn't consume it (only `product-collection` and, after Sprint F, `hero` will)
|
||||
- [x] No behavior change for widgets that already worked (categories/recently-viewed/footer-nav keep their single valid layout, picker just stops offering the other 4 nonsensically)
|
||||
|
||||
**What shipped:** `homepage-section.component.ts` now injects `WidgetManifestService`, resolves each section's manifest entry directly by `section.type` (confirmed identical to the manifest `type` key — no separate widget-id lookup needed), and derives `layoutOptionsFor(section)` by filtering the static option list down to that entry's `supportedLayouts`. A stale/unsupported saved `strategy` value is appended back into the options list rather than dropped, so `app-visual-layout-picker` never renders with no active card. `showColumnsFor(section)` gates the `columns` field to the two componentKeys that actually read it (`hero` always, `product-collection` only in `carousel` strategy — grid mode ignores it). One correction to the plan's assumption: `recently-viewed`'s actual manifest entry declares `supportedLayouts: ["stack", "grid", "carousel"]` (3 options, not 1) — the picker now correctly reflects that per the manifest rather than the plan's guess.
|
||||
|
||||
## Sprint F — Carousel items-per-page (closes the client bug report)
|
||||
|
||||
Confirmed real, reported by a client, not fixed anywhere: neither carousel widget has an "items/slides per page" concept. Design: reuse the existing (currently dead) `layout.columns` field rather than inventing a new one — it is already editable in the Homepage section editor once Sprint E gates it to the right widgets.
|
||||
|
||||
- [x] `ProductCarouselWidgetComponent`: read `section.layout.columns` (default 4, min 1) to size `.catalog-product-shell` width as a fraction of the scroller instead of the hardcoded `220px` — gives real "items per page" control, arrows/scroll logic unchanged (already works)
|
||||
- [x] `HeroWidgetComponent`: add manual prev/next arrows (parity with the product carousel's arrow buttons) in addition to the existing dots — closes "not scrollable manually"
|
||||
- [x] `HeroWidgetComponent`: add swipe/drag (pointer events) support for touch — closes "not scrollable manually" on mobile
|
||||
- [x] `HeroWidgetComponent`: support `layout.columns` = 1 or 2 to show one or two slide panels at once ("big carousel one or two slides per page") — 2-panel mode shows the active slide plus the next one side by side
|
||||
- [x] Verify autoplay (`props.autoplay`, already exists, editor toggle already exists per `widgets-section.component.html:61`) still functions correctly alongside the new manual controls (manual interaction should not fight the autoplay timer — reset/pause timer on manual nav, matching common carousel UX)
|
||||
- [x] i18n: any new aria-labels for the new hero arrows (reuse `common.previousProducts`/`common.nextProducts` keys if wording fits, or add `common.previousSlide`/`common.nextSlide`)
|
||||
|
||||
**What shipped:** `ProductCarouselWidgetComponent` sets `--items-per-page` as a CSS custom property (`[style.--items-per-page]`) driven by `itemsPerPage()` (default 4, min 1, floored), and `.catalog-product-shell` width is now `calc((100% - (var(--items-per-page, 4) - 1) * var(--space-md, 16px)) / var(--items-per-page, 4))` instead of a fixed `220px`. `HeroWidgetComponent` gained prev/next arrow buttons (same circular/bordered visual language as the product carousel's arrows), touch-event swipe (same threshold-based approach as `cart.component.ts`'s `onSwipeStart`, 50px threshold, left swipe = next, right swipe = prev), and 2-panel support via `layout.columns` (defaults to 1; `columns === 2` shows the active slide plus the next one side by side, falling back to 1 panel when there's only one slide total). All manual navigation (arrows, swipe, dots) routes through the existing `goTo()`, which already clears+restarts the autoplay timer, so no duplicate timer logic was needed. New i18n keys `common.previousSlide` / `common.nextSlide` added to `translations.ts`, `en.ts`, `ru.ts`, `hy.ts`.
|
||||
|
||||
Verification: `npx tsc --noEmit` and `npx ng build --configuration=development` both clean. Visually verified in the browser preview (`ng serve` on port 4200) by temporarily patching the embedded home-page sections in `src/assets/mock/bootstrap/bootstrap.json` (the actual runtime source for `/` — `src/assets/mock/bootstrap/homepage.json` is a separate, unused-by-this-route file) to `columns: 2` + a second slide for hero and `columns: 3` for the product carousel, confirming via DOM/computed-style inspection: hero rendered 2 slide panels with 2 working arrows, arrow clicks and simulated touch swipe both advanced/reversed the active dot correctly, and the carousel's `--items-per-page` CSS var read `3` with each `.catalog-product-shell` measuring ~348px (vs. the fixed 1110px/220px before). All temporary mock-data edits were reverted afterward (`git checkout`) — `bootstrap.json` and `homepage.json` are unchanged in the final diff. The Sprint E manifest-aware picker itself could only be verified by code inspection, not live in the browser — `/edit/:section` requires Telegram admin login, which cannot be completed in this environment.
|
||||
|
||||
## Housekeeping
|
||||
|
||||
- [x] Delete `docs/COMING-SOON-AUDIT.md`
|
||||
- [x] Fold summary into `docs/KNOWN-ISSUES.md` "Fixed (this cycle)"; remove the `HeaderConfig.showProfile` dead-toggle entry from "Open"
|
||||
- [x] Update `docs/BACKEND.md` (`tenant.documentationUrl` field added §1.3; no `docs/backend/BACKEND-INTEGRATION.md` exists in this repo)
|
||||
- [x] `npm run barry -- validate` (clean, only pre-existing unrelated warnings)
|
||||
- [x] Typecheck touched files (`tsc --noEmit` + full `ng build` both clean)
|
||||
@@ -1,57 +0,0 @@
|
||||
# Known Issues
|
||||
|
||||
Real, reproducible, currently-open frontend bugs only. Everything that needed a product/business decision moved to `docs/PRODUCT_BACKLOG.md`; everything nice-to-have moved to `docs/FUTURE_FEATURES.md`; everything backend-shaped moved to `docs/BACKEND.md`. Re-verified against source 2026-07-26.
|
||||
|
||||
## Open
|
||||
|
||||
1. **Ed25519 admin-auth error codes `session-expired` and `invalid-signature` are unreachable — dead UI.**
|
||||
`AuthError.code` is documented as routing to a dedicated recovery screen per code
|
||||
(`core/auth/models/auth-error.model.ts:1-4`), but `toAuthErrorShape()` in
|
||||
`core/auth/services/auth.service.ts:110-118` derives the code for any real
|
||||
`HttpErrorResponse` *exclusively* from `authErrorCodeFromStatus(error.status)`
|
||||
(line 112) — it never reads the caller-supplied `fallbackCode` parameter for
|
||||
real HTTP errors, and never reads any body-level error code from the response.
|
||||
`authErrorCodeFromStatus()` (`auth-error.model.ts:21-32`) only ever returns
|
||||
`'unauthorized'`, `'forbidden'`, or `'backend-unavailable'` — there is no status
|
||||
or body condition anywhere in the codebase that produces `'session-expired'` or
|
||||
`'invalid-signature'`. Both screens exist and are wired, but are permanently
|
||||
unreachable from any real backend response today.
|
||||
- **Fix requires both sides**: a backend that returns a distinguishable
|
||||
`error.code` in the response body (see `docs/BACKEND.md` §6 Error Model), and a small
|
||||
frontend change to `toAuthErrorShape()` to prefer that body code over the
|
||||
blanket status-based fallback.
|
||||
- Found: 2026-07-26, Backend Finalization Sprint documentation pass (traced while
|
||||
writing `docs/BACKEND.md` §4 Authentication / §6 Error Model).
|
||||
|
||||
2. **`NavigationConfig.header` dead editable field — top nav links list has no renderer.**
|
||||
The Navigation editor section lets a client edit a list of header nav items
|
||||
(`navigation.header`), but `HeaderComponent` never reads `NavigationConfig.header`
|
||||
anywhere — its category menu comes from `CategoryFacade` instead. Editing this
|
||||
list currently has zero visible effect on the storefront.
|
||||
- **Fix requires real feature work**, not a wiring change: rendering a
|
||||
configurable top-nav means deciding positioning relative to the existing
|
||||
category menu, active-route styling, and whether `children` (dropdowns) are
|
||||
supported — out of scope for a mechanical fix.
|
||||
- Found: 2026-08-05, Sprint G dead-config sweep (`docs/DEAD-CONFIG-AUDIT.md`).
|
||||
|
||||
## Fixed (this cycle)
|
||||
|
||||
Condensed — full detail in commit history and `docs/RELEASE_REPORT.md`.
|
||||
|
||||
- App-wide query-param routing broken (P0) — `language.guard.ts` legacy redirect percent-encoded query strings into the path.
|
||||
- Backoffice Categories CRUD broken end-to-end (P0) — wrong provider-mode fallback always picked the real HTTP gateway with no backend present.
|
||||
- Cart/builder native `confirm()`/`alert()` (16 call sites) replaced with shared `app-confirm-dialog` / toast service.
|
||||
- `getMainImage()` no-photo fallback and footer payment-icon assets referenced files that didn't exist — both fixed, `onerror` fallback added everywhere.
|
||||
- Backoffice Monitoring showed raw HTTP/queue/webhook strings by default — now friendly wording with technical detail collapsed behind a `<details>`.
|
||||
- Category/subcategory empty states used apology wording ("Oops!") for a normal zero-results state.
|
||||
- `pages/category`, `pages/search`, `pages/item-detail`, `pages/info/**`, `pages/legal/**` (40+ files) were unrouted dead code — deleted.
|
||||
- `dynamic-renderer/` was believed unwired — verified it's the live homepage rendering pipeline, no action needed.
|
||||
- `admin/products/:id/edit` missing `canDeactivate` guard — added, mirrors categories.
|
||||
- `primeng`/`primeicons` unused dependency — removed.
|
||||
- Builder static-page body editor hidden inside a mislabeled collapsed section — un-hidden, relabeled.
|
||||
- Several project-editor/admin-categories correctness bugs (footer icon id collisions, features toggle only driving one flag, languages silent duplicate no-op, static-pages slug collision, branding `socialImageUrl` never read, media-picker facade filter leakage between dialogs, categories draft-recovery/drag-reorder bugs, hardcoded locale-tab order) — see git history for the full per-bug list.
|
||||
- `HeaderConfig.showProfile` dead toggle — wired up (login/logout only, no dropdown), reuses the existing customer Telegram `AuthService`.
|
||||
- Admin `reports` nav stub — real page (`backoffice/reports`), reuses `AdminAnalyticsFacade` for Sales/Top Products/Marketplace Health cards with CSV export.
|
||||
- Admin `settings` nav stub — real page (`backoffice/settings`), UI density preference (comfortable/compact), persisted to `localStorage`, applied to admin list tables.
|
||||
- Admin `documentation`/`help` nav stubs — both wired to real external links (`mailto:` support email, `tenant.documentationUrl`).
|
||||
- Sprint G dead-config sweep: `footer.logoUrl`, `company.address.street`, `company.contacts.phone`, `catalog.suggestionsEnabled` were editable with no runtime consumer — all four wired up. Full findings table in `docs/DEAD-CONFIG-AUDIT.md`.
|
||||
@@ -1,23 +0,0 @@
|
||||
# Next Phase — Roadmap
|
||||
|
||||
The one roadmap. Everything after this point assumes the previous phase is done — don't start Phase 2 work before Phase 1 lands.
|
||||
|
||||
## Phase 1 — Backend integration
|
||||
|
||||
Implement the backend per `BACKEND.md`, then swap every frontend mock gateway for a real one behind its DI token, in the dependency order `BACKEND.md` §8 specifies (auth/tenant/bootstrap first, then read-heavy catalog, then write-heavy customer domains, then admin, then builder/CMS). Wire the currently-dormant Ed25519 admin-auth interceptor/guard once the backend can issue/verify challenges. Enforce the admin role model in route guards once real roles exist server-side.
|
||||
|
||||
## Phase 2 — Production testing
|
||||
|
||||
Add the automated test suite that doesn't exist yet: facade-level integration tests against real endpoints (not mocks), and E2E coverage for the critical flows — storefront checkout, admin product/category CRUD, builder draft → publish → live storefront reflects the change, admin auth once Ed25519 is live.
|
||||
|
||||
## Phase 3 — Performance
|
||||
|
||||
Re-profile under real backend latency (mock responses are instant today, real ones won't be) — loading states, skeleton timing. Revisit the two known large lazy chunks (`project-editor`, `catalog-container`) with real data before committing to a bundle-splitting approach.
|
||||
|
||||
## Phase 4 — Monitoring
|
||||
|
||||
Wire real error tracking/APM and a real event source for the admin Monitoring page (currently mock activity data). Implement the maintenance-mode frontend UI gaps `BACKEND.md` §10 flags as not existing yet (full-page takeover, per-module banners, scheduled-maintenance countdown), once the backend maintenance contract is live.
|
||||
|
||||
## Phase 5 — Version 2 ideas
|
||||
|
||||
Everything in `docs/PRODUCT_BACKLOG.md` (dark mode, brand-color contrast decision, advanced analytics, additional payment providers, Contacts page content) and `docs/FUTURE_FEATURES.md` (Angular 22 upgrade, cart-modal composition cleanup) — none of it scheduled, all of it deliberately deferred past initial launch. The former stub-page/dead-toggle inventory (profile menu, admin Reports, admin Settings, Documentation/Help) is closed — see `docs/GLOBAL-SPRINT-PLAN.md` and `docs/KNOWN-ISSUES.md` "Fixed (this cycle)".
|
||||
@@ -1,62 +0,0 @@
|
||||
# Product Backlog
|
||||
|
||||
Items that need a client/business decision before any code is written — not blockers, not bugs, not backend work. Verified against current repo state 2026-07-26.
|
||||
|
||||
## Dark mode / Theme selector
|
||||
|
||||
`theme-section`'s light/dark/system dropdown saves correctly and `theme-engine.service.ts` sets a `data-theme-mode` attribute on `<html>`, but no CSS anywhere in the app reads that attribute — picking Dark or System changes nothing visually today. Theme palette colors themselves are unaffected (real CSS custom properties, genuinely live).
|
||||
|
||||
**Decision needed:** does the client want a real dark mode? If yes, this is a real feature project (dark palette + `[data-theme-mode]`/`prefers-color-scheme` strategy + a `matchMedia` listener for "system"), not a wiring fix.
|
||||
|
||||
## Brand color contrast (WCAG AA)
|
||||
|
||||
`--border-color` fails 3:1 UI-component contrast in every theme (1.24–1.42:1 measured); `--success`/`--warning`/`--error`/`--info-color` fail 4.5:1 when used as plain text-on-white in a handful of places. These are real palette colors, not a token bug — fixing means visibly changing the brand.
|
||||
|
||||
**Decision needed:** theme-owner sign-off on adjusted brand colors before any change ships.
|
||||
|
||||
## Design-token gap: `stars.component` rating glyph color
|
||||
|
||||
`src/app/features/website/product/engagement/components/stars/stars.component.scss:10` uses a literal hex (`#cdd6d5`) with no matching design token.
|
||||
|
||||
**Decision needed:** add a token for this exact shade, or intentionally reuse an existing token (visual shift either way) — needs a design-system owner's call, not an engineering guess.
|
||||
|
||||
## `layout.type` ("Site Layout" selector) — dead editable field
|
||||
|
||||
The Theme section's "Site Layout" dropdown edits top-level `BootstrapConfig.layout.type`,
|
||||
but page rendering (`SectionEngineService.resolveLayoutType`) only ever reads each
|
||||
individual `PageConfig.layout`, never the top-level `bootstrap.layout` — so the
|
||||
selector has no visible effect regardless of what's chosen.
|
||||
|
||||
**Decision needed:** which page(s) should this selector actually drive — only the
|
||||
homepage, or every page that doesn't set its own `layout`? That decision determines
|
||||
the wiring, not an engineering guess. Found: Sprint G dead-config sweep, `docs/DEAD-CONFIG-AUDIT.md`.
|
||||
|
||||
## `company.companyName` — dead editable field, needs a copyright-fallback decision
|
||||
|
||||
The Footer section's "Company Name" field has no runtime consumer. The footer
|
||||
already has a copyright fallback (`© {year} {brandName}`) using `branding.brandName`
|
||||
when `footer.copyrightText` is empty — reusing `company.companyName` there instead
|
||||
(or in addition) is a content/legal-wording decision (brand name vs. legal entity
|
||||
name are intentionally different fields), not a safe mechanical fix.
|
||||
|
||||
**Decision needed:** should the copyright fallback use the legal company name
|
||||
instead of (or alongside) the brand name? Found: Sprint G dead-config sweep,
|
||||
`docs/DEAD-CONFIG-AUDIT.md`.
|
||||
|
||||
## Footer "Contacts" page content
|
||||
|
||||
The footer's "Contacts" link (`footer-contacts` / `nav.contacts`) has no static-page content in the bootstrap mock data at all — unlike "About" (which was a route-name mismatch, already fixed), there's simply nothing written for Contacts.
|
||||
|
||||
**Decision needed:** what should the Contacts page actually say (address, phone, hours, map?) — a content question, not a code fix.
|
||||
|
||||
## Advanced analytics (traffic, funnels, heatmaps)
|
||||
|
||||
No data source exists for site traffic, conversion funnels, or heatmaps anywhere in the frontend or backend plan — this is a from-scratch analytics pipeline, not a missing endpoint.
|
||||
|
||||
**Decision needed:** does the client want this for launch or later, and which analytics vendor/build to use (build vs. buy).
|
||||
|
||||
## Payment providers
|
||||
|
||||
Current checkout supports QR and card via the existing custom payment flow (`bank-payment-modal`, `payViaCard`). No alternative payment providers are wired or planned.
|
||||
|
||||
**Decision needed:** if additional payment providers (e.g. wallets, buy-now-pay-later) are wanted, needs a business decision on which providers before any integration work starts.
|
||||
@@ -1,60 +0,0 @@
|
||||
# PROJECT STRUCTURE
|
||||
|
||||
Folder-by-folder tour of `src/app/**`, then one worked example (the Sprint 19 admin dashboard) followed as a literal file-by-file walk-through, ending with a checklist for adding your own feature.
|
||||
|
||||
Standards referenced below are enforced, not suggestions: `docs/architecture/foundation/Folder-Blueprint.md`, `Naming-Conventions.md`, `Dependency-Rules.md`, `Import-Boundary-Matrix.md`.
|
||||
|
||||
## Top-level folders
|
||||
|
||||
| Folder | What belongs here | Why |
|
||||
|---|---|---|
|
||||
| `core/` | Per-domain: DTOs, mappers, domain models, repositories, domain services (e.g. `core/categories/`, `core/products/`, `core/search/`, `core/admin-auth/`). | Isolates backend-shaped data (DTOs) from the rest of the app. Only the mapper inside a domain's `core/<domain>/` folder is allowed to see both DTO and domain model shapes (ADR-003 import boundaries). |
|
||||
| `facades/` | Cross-feature facades not owned by a single feature, e.g. `facades/platform/category.facade.ts`, `facades/platform/search.facade.ts`. | The only thing components are allowed to inject for data/state (ADR-006/007). Feature-local facades instead live inside that feature's own `facade/` folder (see `features/project-editor/facade/`, `features/admin/dashboard/facade/`). |
|
||||
| `features/` | One folder per feature/domain: `project-editor/`, `admin/<subfeature>/`, `backoffice/<subfeature>/`, `website/catalog/`, `website/product/`, `search/`, `content-management/`, `diagnostics/`. | Organized by feature, not by file type — a feature's models/services/facade/components/pages all live together (`docs/architecture/foundation/Folder-Blueprint.md`). |
|
||||
| `shared/` | `shared/models/config/*` (the `BootstrapConfig` and ~20 sub-configs), reusable presentational UI, utils. | Feature-agnostic by contract — `shared/` must never import from `features/` (Import-Boundary-Matrix). |
|
||||
| `widgets/` | `contracts/` (widget manifest contract), `registry/` (manifest service), `resolvers/` (data-source resolver), `ui/` (widget components). | The dynamic rendering engine — see `docs/ARCHITECTURE.md`. |
|
||||
| `dynamic-renderer/` | `section-engine/`, `page-renderer/`, `section-renderer/`, `widget-host/`. | The page-composition pipeline that turns bootstrap JSON into rendered pages. |
|
||||
| `layouts/` | Page-chrome containers, e.g. `layouts/containers/dynamic-page-layout.component.ts`. | Top-level layout composition, one level above pages. |
|
||||
| `i18n/` | `translations.ts` (interface), `en.ts`/`ru.ts`/`hy.ts`, `translate.pipe.ts`, `TranslateService`. | Single source of truth for all user-facing copy — see `docs/FRONTEND.md`. |
|
||||
| `pages/` | Top-level routed pages not part of a larger feature module (`home`, `cart`, `category`). | Simpler routed pages that don't warrant a full `features/` module. |
|
||||
| `components/` | Reusable standalone components shared across features/pages (`product-card`, `telegram-login`). | Presentational, input/output-only (ADR-006) — no facade/HttpClient/storage access. |
|
||||
| `guards/` | Route guards. | Kept separate from `core/admin-auth/` because `admin-auth.guard.ts` is domain-specific; generic guards live here. |
|
||||
|
||||
## Worked example, end to end: the Sprint 19 admin dashboard
|
||||
|
||||
`src/app/features/admin/dashboard/` — read in the order a new engineer would build it.
|
||||
|
||||
1. **Model** — `models/admin-dashboard.model.ts`. Plain interfaces/types for card data, card status (`loading|empty|error|pending-backend|ready`), health-check entries. No behavior, no imports from Angular DI.
|
||||
|
||||
2. **Gateway interface** — `services/admin-dashboard-metrics.gateway.interface.ts`. An abstract contract (`AdminDashboardMetricsGateway`) for "however we get category/product counts" — deliberately decoupled from *how* (local computation vs. real API) so the facade never knows which implementation is active.
|
||||
|
||||
3. **Gateway implementation** — `services/admin-dashboard-metrics.local.gateway.ts`. `AdminDashboardMetricsLocalGateway implements AdminDashboardMetricsGateway`, composing `BackofficeDataService.loadCategories()/loadProducts()` (already used elsewhere) into counts. A future `AdminDashboardMetricsApiGateway` would implement the same interface against a real endpoint (see `docs/BACKEND.md` §3 CRUD Contracts / §8 migration guide) — nothing above this layer changes when that happens.
|
||||
|
||||
4. **DI token** — `services/admin-dashboard-metrics-gateway.token.ts`. `const ADMIN_DASHBOARD_METRICS_GATEWAY = new InjectionToken<AdminDashboardMetricsGateway>(...)`, bound to the local gateway by default in `app.config.ts`. This is the swap point: rebinding this token to a real API gateway is the *only* change needed to go from mock to real data.
|
||||
|
||||
5. **Supporting service** — `services/admin-dashboard-history.service.ts`. `localStorage`-backed activity log, scoped per tenant — a second, narrower concern (recent activity) that doesn't belong in the metrics gateway.
|
||||
|
||||
6. **Facade** — `facade/admin-dashboard.facade.ts`. `AdminDashboardFacade` is the *only* thing the components below are allowed to inject. It composes `ProjectEditorFacade` (existing — bootstrap/status/validation), `ADMIN_DASHBOARD_METRICS_GATEWAY` (via the token, not the concrete class), and `AdminDashboardHistoryService`, and exposes computed signals per card (status + value) plus the health-check list and quick-actions list.
|
||||
|
||||
7. **Presentational components** — `components/admin-dashboard-card.component.*`, `admin-dashboard-quick-actions.component.*`, `admin-dashboard-activity.component.*`, `admin-dashboard-health.component.*`. Each takes only `@Input()`s (card data, health entries, quick-action list) — no `HttpClient`, no `localStorage`, no route access, no facade injection. This is what makes them independently testable and reusable.
|
||||
|
||||
8. **Page container** — `pages/admin-dashboard-page.component.*`. Injects `AdminDashboardFacade`, computes per-card status from bootstrap-loaded/metrics-error/empty conditions, prefixes `routerLink`s with the current locale (`LanguageService.currentLanguage()`), and passes plain data down to the presentational components above. This is the only place in the feature that knows about routing or the facade.
|
||||
|
||||
9. **Route wiring** — `app.routes.ts`. `/:lang/backoffice/dashboard -> AdminDashboardPageComponent`, guarded by `adminAuthGuard`; `/:lang/backoffice` (empty path) redirects to `dashboard`.
|
||||
|
||||
Full narrative and known gaps: `docs/archive/ADMIN.md` (historical build log) and `docs/BACKEND.md` (current contract).
|
||||
|
||||
## Steps to add a new feature (derived from the example above)
|
||||
|
||||
1. Decide: does this belong in `features/<area>/<feature>/`, or is it simple enough for `pages/`? Route-guarded, multi-component admin/backoffice work goes in `features/admin/*` or `features/backoffice/*`.
|
||||
2. Define the domain model(s) first (`models/*.model.ts`) — no behavior, no DI.
|
||||
3. If the feature needs data that might later come from a real backend, define a gateway/repository **interface** before writing any implementation.
|
||||
4. Implement a local/mock gateway against existing data sources where possible (reuse, don't duplicate — check `core/*` and other features' services first).
|
||||
5. Create an `InjectionToken` for the gateway and bind it to the local implementation in `app.config.ts` (or the relevant provider scope). This is the seam a backend integration will use later — never inject the concrete class directly from a facade or component.
|
||||
6. Write the facade. It is the only consumer of the gateway token, and the only thing components inject.
|
||||
7. Build presentational components as `@Input()`/`@Output()`-only — verify none of them import `HttpClient`, storage, or a facade.
|
||||
8. Build the container/page component that injects the facade and wires routing.
|
||||
9. Add routes in `app.routes.ts`, with `adminAuthGuard` (or the relevant guard) if it's an admin surface.
|
||||
10. Add every new user-facing string to `i18n/translations.ts` (interface) then `en.ts`/`ru.ts`/`hy.ts` — never hardcode copy in a template.
|
||||
11. Document backend gaps (if any) in `docs/BACKEND.md` §3 (CRUD Contracts, endpoints by domain), marking proposed/unimplemented endpoints as such.
|
||||
12. Run `npm run arch:check` (import boundaries + circular dependencies) and `npx tsc -p tsconfig.app.json --noEmit` before committing.
|
||||
@@ -1,90 +0,0 @@
|
||||
# Marketplace Platform — Documentation Index
|
||||
|
||||
This is the entry point. Read this first — it links to everything else and tells you what's actually true right now versus what's historical.
|
||||
|
||||
## What this is
|
||||
|
||||
A configuration-driven, multi-tenant SaaS marketplace platform (Angular 21.1, standalone components). One frontend codebase serves unlimited tenants ("marketplaces"). Tenant identity, theme, navigation, page/section/widget composition, and static content all resolve from a per-tenant `bootstrap.json` fetched at runtime — no tenant-specific code paths exist in the frontend. New tenants are onboarded by domain + config + backend data, never by forking the frontend.
|
||||
|
||||
Every tenant has three surfaces on this one codebase:
|
||||
- **Website** — the public storefront (catalog, product pages, cart, static pages).
|
||||
- **Builder** (Project Editor, `/edit/**`) — an in-app editor that edits the tenant's `BootstrapConfig`.
|
||||
- **Backoffice** (Admin, `/:lang/backoffice/**`) — the admin area: products, categories (live-wired to a real gateway), orders, transactions, users, monitoring, analytics, media.
|
||||
|
||||
## System overview
|
||||
|
||||
- **Architecture**: `Component (container) → Facade → Domain Service → Repository/Provider (DI token, swappable mock↔API) → Mock | API`. Enforced by `npm run arch:check` (import boundaries + circular deps), not just convention. Full detail: [ARCHITECTURE.md](ARCHITECTURE.md), governance docs at `docs/architecture/foundation/**` (11 ADRs + 9 standards docs).
|
||||
- **Seller Management** (optional, not built): typed foundation + Phase 1 Backoffice placeholder UI only — `modules.sellerManagement.enabled` gate on `BootstrapConfig`, disabled by default, zero effect on existing marketplaces. Full capability doc: `docs/architecture/foundation/Seller-Management.md`.
|
||||
- **State**: Signals-based facades everywhere, no NgRx (ADR-007).
|
||||
- **Rendering**: Bootstrap JSON → Section Engine → Page Renderer → Widget Host → registered widget component (ADR-005). 100% lazy-loaded routes.
|
||||
- **Theming**: CSS custom properties per tenant, 3 theme stylesheets, never hardcoded hex in a component (ADR-008). Design system spec: [`DESIGN.md`](../DESIGN.md) (root of repo).
|
||||
- **i18n**: 3 locales (en/ru/hy), compile-time-enforced key parity across locale files.
|
||||
- **Backend**: mostly PLANNED (mock gateways behind swappable provider tokens) — see [BACKEND.md](BACKEND.md), the single canonical backend spec (architecture, bootstrap, auth, JWT, Ed25519, permissions, maintenance mode, error model, every endpoint, DTOs, uploads, pagination/filters/sorting, publish workflow, media, builder, implementation checklist). Categories is the one domain fully wired to a real HTTP gateway; everything else is local/mock.
|
||||
|
||||
## Doc index (living documents)
|
||||
|
||||
Read these directly — they're the current source of truth, not one-off reports:
|
||||
|
||||
| Doc | What it covers |
|
||||
|---|---|
|
||||
| [ARCHITECTURE.md](ARCHITECTURE.md) | Layered architecture, container/facade/service pattern, bootstrap/theme/widget engines, links to the enforced ADRs |
|
||||
| [BACKEND.md](BACKEND.md) | **The one canonical backend spec** — auth, JWT, Ed25519, permissions, maintenance mode, every endpoint, DTOs, uploads, error model, migration guide, checklist |
|
||||
| [FRONTEND.md](FRONTEND.md) | App structure, routing, i18n, theming, state management, dynamic rendering |
|
||||
| [EDITOR.md](EDITOR.md) | The Project Editor: every section, save/publish/draft/reset model |
|
||||
| [StaticPages.md](StaticPages.md) | The Static Pages CMS module (the thing that actually serves About/Contacts/etc. today) |
|
||||
| [PROJECT-STRUCTURE.md](PROJECT-STRUCTURE.md) | Folder-by-folder tour of `src/app/**` with a worked feature-add example |
|
||||
| [PROJECT_STATUS.md](PROJECT_STATUS.md) | **Current status** — completion %, readiness for demo/production/backend, honest limitations |
|
||||
| [NEXT_PHASE.md](NEXT_PHASE.md) | The one roadmap — backend integration → testing → performance → monitoring → v2 ideas |
|
||||
| [TODO.md](TODO.md) | Release blockers only — currently empty |
|
||||
| [KNOWN-ISSUES.md](KNOWN-ISSUES.md) | Real, reproducible, currently-open frontend bugs only |
|
||||
| [PRODUCT_BACKLOG.md](PRODUCT_BACKLOG.md) | Items needing a client/business decision (dark mode, brand colors, page content, etc.) |
|
||||
| [FUTURE_FEATURES.md](FUTURE_FEATURES.md) | Nice-to-have, non-blocking future work (Angular 22, bundle splitting, etc.) |
|
||||
| [ANGULAR22_PLAN.md](ANGULAR22_PLAN.md) | Angular 22 upgrade feasibility (research only, not yet executed — tracked in FUTURE_FEATURES.md) |
|
||||
| [SALES-GUIDE.md](SALES-GUIDE.md) | Plain-language guide for the sales team — what to demo, what's not live yet |
|
||||
| [`../DESIGN.md`](../DESIGN.md) | Visual design system: colors, typography, elevation, component specs |
|
||||
| [`../PRODUCT.md`](../PRODUCT.md) | Product positioning, users, brand personality, anti-references |
|
||||
| [`../CHANGELOG.md`](../CHANGELOG.md) | Keep-a-Changelog-format history of shipped features |
|
||||
| `docs/architecture/foundation/**` | Enforced ADRs (ADR-001…ADR-011) and standards docs — governance, read directly |
|
||||
| `docs/context/**` | Barry Cache's own source-backed memory system — infrastructure, not project documentation, do not edit by hand |
|
||||
| `docs/archive/**` | Superseded docs, kept for history only — do not implement against these |
|
||||
|
||||
**One topic, one place**: routing lives in FRONTEND.md, not repeated here. Backend contract lives entirely in BACKEND.md — nowhere else. Design tokens live in DESIGN.md, not repeated elsewhere.
|
||||
|
||||
## What's still open
|
||||
|
||||
[TODO.md](TODO.md) — release blockers only. [PRODUCT_BACKLOG.md](PRODUCT_BACKLOG.md) and [FUTURE_FEATURES.md](FUTURE_FEATURES.md) hold everything else that isn't a blocker.
|
||||
|
||||
## Historical reports
|
||||
|
||||
19 one-off audit/sprint/review reports were archived, then deleted once every open finding worth keeping was confirmed merged into [KNOWN-ISSUES.md](KNOWN-ISSUES.md)/[TODO.md](TODO.md). A 20th (`FRONTEND-ROADMAP.md`, despite its name a shipped-history changelog, not a forward roadmap) was archived to `docs/archive/` on 2026-07-26 for the same reason. Full original text recoverable via `git log --diff-filter=D -- docs/archive` or `docs/archive/FRONTEND-ROADMAP.md` itself.
|
||||
|
||||
## How to run it
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run start # ng serve
|
||||
npm run start:dexar # ng serve --configuration=development --port 4200
|
||||
npm run build # ng build
|
||||
npm run build:dexar # ng build --configuration=production
|
||||
npm run arch:check # boundary + circular-dependency checks
|
||||
```
|
||||
|
||||
Barry Cache (repo memory, optional but recommended before/after non-trivial work):
|
||||
|
||||
```bash
|
||||
npm run barry -- resume --task "<task>"
|
||||
npm run barry -- validate
|
||||
```
|
||||
|
||||
See root `CLAUDE.md` for the full Barry Cache workflow and memory policy.
|
||||
|
||||
## Current status
|
||||
|
||||
Full detail (completion %, per-area readiness, known limitations): [PROJECT_STATUS.md](PROJECT_STATUS.md). Short version:
|
||||
|
||||
- **Frontend**: Release Candidate, feature-complete. `TODO.md` has no blockers.
|
||||
- **Backend**: not implemented, fully specified. Categories is the one domain wired to a real gateway; everything else is mock. See [BACKEND.md](BACKEND.md).
|
||||
- **Documentation**: consolidated (Final Documentation Consolidation pass, 2026-07-26) — one canonical backend doc, one roadmap, one status doc, historical/sprint docs moved to `docs/archive/`.
|
||||
- **First client demo**: ready, with one caveat — admin role enforcement doesn't exist yet, see `PROJECT_STATUS.md`.
|
||||
|
||||
Draft/publish for the Project Editor is still **frontend-only** (localStorage), no backend persistence — the single largest backend gap, see [BACKEND.md §1 (Bootstrap: Draft vs Published)](BACKEND.md#1-bootstrap) and §8 (Real Backend Implementation Guide).
|
||||
@@ -1,44 +0,0 @@
|
||||
# Project Status
|
||||
|
||||
Date: 2026-07-26. Branch: `B2B`. Honest snapshot, verified against source — not aspirational.
|
||||
|
||||
## Completion estimates
|
||||
|
||||
Frontend-engineering estimates only (not effort/story-point estimates) — how much of the intended surface is built and working against mock data.
|
||||
|
||||
| Area | Completion | Basis |
|
||||
|---|---|---|
|
||||
| **Frontend (overall)** | **~95%** | `TODO.md` has zero release blockers; one known minor bug open (`KNOWN-ISSUES.md`); several items deliberately deferred as product decisions, not gaps. |
|
||||
| **Backend** | **~10%** | Only Categories has a real HTTP implementation. Every other domain is a working mock. The *specification* is 100% done (`BACKEND.md`); the *implementation* is not started. |
|
||||
| **UI (visual/component layer)** | **~95%** | No native browser dialogs, no known broken-image paths, no raw dev jargon in default admin views, no apology-toned empty states, consistent shared primitives across all three surfaces. |
|
||||
| **Admin (backoffice)** | **~85%** | UI built and working for every domain (dashboard, products, categories, orders, customers, transactions, users, moderation, media, monitoring, analytics) against mock data. Missing: role enforcement (model exists, nothing checks it), real data everywhere except Categories. |
|
||||
| **Storefront** | **~95%** | Feature-complete for the audited surfaces (home, catalog, product detail, cart, checkout UI, wishlist/compare, search, static/CMS pages). i18n complete (en/ru/hy near-parity). Runs against mock data. |
|
||||
|
||||
## Ready for first customer?
|
||||
|
||||
**Yes, for a demo. No, for production.** The storefront and builder demo end-to-end with no visible rough edges. Production readiness is blocked entirely on the backend not existing yet — see `BACKEND.md`.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- One real frontend bug open: Ed25519 admin-auth error codes `session-expired`/`invalid-signature` are currently unreachable (see `KNOWN-ISSUES.md`).
|
||||
- Admin role model exists in code but isn't enforced by any route guard or UI gate — anyone who passes admin auth has full access regardless of assigned role.
|
||||
- No automated test suite exists for the components touched across recent RC passes (none existed before either).
|
||||
- Two large lazy chunks (`project-editor` 320 kB, `catalog-container` 126 kB) — not release-blocking (`FUTURE_FEATURES.md`).
|
||||
- 53 local `B2B` commits not yet pushed to `origin` (verified 2026-07-26) — pending explicit go-ahead, a process step not a code blocker.
|
||||
- Several product-decision items (dark mode, brand-color contrast, Contacts page content, advanced analytics, additional payment providers) documented but not scheduled — `PRODUCT_BACKLOG.md`.
|
||||
|
||||
## Backend waiting items
|
||||
|
||||
Everything in `BACKEND.md` §9 (Backend Checklist) — 34 items across 6 phases, from foundation (auth, tenant resolution, bootstrap, error envelope) through hardening (rate limiting, CSP, audit logging, maintenance mode). The single largest gap: the Project Editor (builder) has **no save/publish HTTP call at all** today — drafts live in-memory and in `localStorage` only.
|
||||
|
||||
## Authentication status
|
||||
|
||||
**Storefront: live.** Telegram/QR session login is the only way customers authenticate today, and it works end-to-end. **Admin: dormant.** Ed25519 challenge/response admin auth is fully built client-side (keypair service, signing flow, guard, interceptor) but the interceptor isn't registered in `app.config.ts` and the guard isn't attached to any route — it doesn't run in production today. No token refresh exists for either flow. Full contract: `BACKEND.md` §4.
|
||||
|
||||
## Builder status
|
||||
|
||||
Fully functional editor of in-memory/`localStorage` draft state (homepage sections, widgets, languages, navigation, footer, branding, theme, static pages). "Publish" today only promotes the local draft signal — nothing reaches a backend.
|
||||
|
||||
## Documentation status
|
||||
|
||||
Consolidated in this closeout pass. One canonical backend doc (`BACKEND.md`, merges everything that used to be five overlapping files). One roadmap (`NEXT_PHASE.md`). One status doc (this file). Historical sprint/audit reports live in `docs/archive/`, not in root `docs/`. `PROJECT_INDEX.md` is the entry point and every remaining doc is reachable from it. Not fully swept: a handful of low-traffic architecture docs (`docs/architecture/foundation/adr/**`, `FRONTEND.md`, `EDITOR.md`, `ARCHITECTURE.md`, `PROJECT-STRUCTURE.md`, `StaticPages.md`) still contain a few old filename references from before this consolidation — historical-context docs, not the navigation entry point, left as a known gap rather than swept blindly.
|
||||
@@ -1,123 +0,0 @@
|
||||
# Release Candidate RC-02 — Final Release Report
|
||||
|
||||
Date: 2026-07-26
|
||||
Branch: `B2B`
|
||||
|
||||
## Completed
|
||||
|
||||
1. **Storefront localization.** Replaced remaining hardcoded English strings
|
||||
(rating/discount aria-labels, hero-carousel dots, product-carousel prev/next
|
||||
buttons, dialog close button, toast dismiss, QR-code alt text, bank-payment
|
||||
iframe title, guest checkout fallback name) with `translate` pipe/service
|
||||
calls, backed by new `common.*` i18n keys in en/ru/hy.
|
||||
Commit: `1163bfd`.
|
||||
|
||||
2. **Empty-store wording.** Audited every empty-collection branch across
|
||||
storefront, builder, and backoffice. Found and fixed one real defect: the
|
||||
category/subcategory empty states used "Oops!"/"Упс!" apology framing for a
|
||||
normal zero-results condition. Everywhere else in the codebase already
|
||||
correctly separates a real `error()` branch from an empty-collection
|
||||
branch with distinct, neutral wording (verified across catalog, product,
|
||||
cart, wishlist/compare, admin list pages, dashboard, media, builder).
|
||||
Commit: `1163bfd`.
|
||||
|
||||
3. **Merchant-friendly wording (Monitoring/Analytics/Reports/Diagnostics).**
|
||||
Analytics, Reports (moderation/reports), and Diagnostics were already
|
||||
clean — no raw HTTP/queue-worker strings found. Monitoring had three
|
||||
developer-facing spots: background queue slugs, webhook event keys, and
|
||||
the activity log's "api" category showing a raw
|
||||
`GET /api/products responded 200 in 84ms` line as the primary message.
|
||||
All three now show plain-language labels by default, with the raw string
|
||||
for API/error/warning events moved behind a collapsed "Technical details"
|
||||
`<details>`. Commit: `ca343c4`.
|
||||
|
||||
4. **Dialog consistency.** Replaced all 12 native `confirm()` calls and 4
|
||||
native `alert()` calls across cart, media library, static-pages editor,
|
||||
and 5 builder components. Confirms now use a new shared
|
||||
`app-confirm-dialog` (composes the existing `app-dialog` + `app-button` —
|
||||
no new dependency), following the same local-signal pattern already used
|
||||
in admin-categories. Cart's alerts route through the existing
|
||||
`UserNotificationService` toast pipeline instead. Zero native
|
||||
`confirm`/`alert`/`prompt` remain in production code (verified by grep).
|
||||
Commit: `6c6fa00`.
|
||||
|
||||
5. **Images.** Found and fixed a real defect: `getMainImage()`'s no-photo
|
||||
fallback pointed at `/assets/images/placeholder.svg`, but that file (and
|
||||
the whole `assets/images/` directory) never existed — any item with zero
|
||||
photos rendered a browser broken-image icon. Added the asset. Also added
|
||||
an `(error)` handler on every dynamic `<img>` that renders a
|
||||
user/admin-supplied URL (product card, cart line item, cart payment QR,
|
||||
product gallery main + thumbnails), so a 404'd image URL swaps to the
|
||||
placeholder instead of shipping broken. Commit: `3e54e88`.
|
||||
|
||||
6. **Legacy cleanup.** Investigated `pages/category`, `pages/search`,
|
||||
`pages/item-detail`, `pages/info/**`, `pages/legal/**` (40+ files) and
|
||||
`dynamic-renderer/`. First five were confirmed unrouted dead code (each
|
||||
had a live replacement already serving its traffic) — deleted outright.
|
||||
`dynamic-renderer/` was confirmed **active** (it's the live homepage
|
||||
rendering pipeline via `HomeComponent` → `WebsiteRuntimeFacade` →
|
||||
`PageRendererService`/`PageResolverService` →
|
||||
`DynamicPageLayoutComponent`) — a prior doc note calling it "unwired" was
|
||||
stale and has been corrected. `docs/TODO.md`, `docs/KNOWN-ISSUES.md`,
|
||||
`docs/FRONTEND-ROADMAP.md`, `docs/PROJECT_INDEX.md` updated accordingly.
|
||||
Commit: `a670ca9`.
|
||||
|
||||
7. **Final QA.** `npx tsc --noEmit` clean after every commit above.
|
||||
`ng serve` production-mode build compiles with no errors. Manually
|
||||
smoke-tested in-browser: home page loads with zero console errors; cart
|
||||
page loads with mock data; the new clear-cart confirm dialog opens with
|
||||
correctly translated title/message/buttons, Cancel closes it without
|
||||
side effects, zero console errors throughout. Backoffice route requires
|
||||
an authenticated admin session (existing `adminAuthGuard` behavior,
|
||||
unrelated to this pass) so the Monitoring page's new wording was verified
|
||||
by reading the compiled template/component, not by an authenticated
|
||||
click-through.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- The i18n string audit and empty-state audit were scoped to storefront/
|
||||
customer-facing surfaces per the task list; backoffice/builder templates
|
||||
were spot-checked but not exhaustively re-audited for hardcoded strings.
|
||||
- `app-confirm-dialog` is a new small shared component (composes existing
|
||||
`app-dialog`/`app-button`, no new library). It intentionally does not
|
||||
cover every dialog in the codebase — only the sites that were previously
|
||||
using native `confirm()`/`alert()`.
|
||||
- Backoffice Monitoring's Technical-details fix only touches the mock local
|
||||
gateway (`AdminMonitoringLocalGateway`); once a real API-backed gateway
|
||||
exists, it will need to populate `technicalDetail` the same way to keep
|
||||
the "Technical details" affordance working.
|
||||
- No new automated tests were added for this pass (none existed for the
|
||||
touched components beforehand either); verification was typecheck +
|
||||
manual smoke test as described above.
|
||||
|
||||
## Deferred items
|
||||
|
||||
- Everything already tracked in `docs/TODO.md` under "Backend — skipped,
|
||||
doing together" remains deferred (bootstrap real content, builder
|
||||
publish/validate backend, backoffice CRUD, media pipeline) — explicitly
|
||||
out of scope per this task's "NO BACKEND CHANGES" instruction.
|
||||
- Non-blocking pre-existing items from prior RC passes noted in
|
||||
`docs/KNOWN-ISSUES.md` (genuine brand-color contrast failures needing
|
||||
theme-owner sign-off, 2 large lazy chunks needing a dedicated split task,
|
||||
`primeng`/`primeicons` removal blocked on an unrelated `barry-cache`
|
||||
dependency issue) are unchanged by this pass.
|
||||
|
||||
## Launch recommendation
|
||||
|
||||
**Ready to ship** from a customer-demo-polish standpoint: no native browser
|
||||
dialogs, no broken-image paths on the audited surfaces, no raw developer
|
||||
jargon in Monitoring's default view, no apology-toned empty states, and the
|
||||
five dead-code page directories are gone rather than lingering as
|
||||
demo-confusing zombies. The remaining known limitations above are scope
|
||||
boundaries (backend, exhaustive re-audit, test coverage) rather than found
|
||||
defects — recommend proceeding, with the backoffice-auth-gated smoke test
|
||||
as the one item worth a human doing a real authenticated click-through on
|
||||
before the actual demo.
|
||||
|
||||
## Commits (this pass)
|
||||
|
||||
- `1163bfd` fix(storefront): replace hardcoded strings with i18n, neutral empty-state wording
|
||||
- `a670ca9` chore(cleanup): delete unrouted legacy pages, update docs
|
||||
- `6c6fa00` fix(ui): replace native confirm()/alert() with shared dialogs and toasts
|
||||
- `3e54e88` fix(storefront): add missing placeholder image asset and onerror fallback
|
||||
- `ca343c4` fix(backoffice): merchant-friendly wording in Monitoring
|
||||
@@ -1,79 +0,0 @@
|
||||
# Sales Guide — How to Use & Demo the Marketplace Platform
|
||||
|
||||
Audience: sales team. Plain-language guide to what the product does and how to show it. No code. When something isn't live yet, it's marked **Coming soon** so you never over-promise in a demo.
|
||||
|
||||
## What we're selling in one sentence
|
||||
|
||||
A **multi-tenant marketplace platform**: one codebase runs many branded marketplaces, and each customer gets their own storefront + a self-service admin panel to run it — no developer needed for day-to-day changes.
|
||||
|
||||
## The two halves of the product
|
||||
|
||||
1. **The storefront** — what shoppers see: homepage, catalog, product pages, search, cart, wishlist, compare, multi-language, multi-currency.
|
||||
2. **The admin / editor** — what the marketplace owner uses to run and customize it, without touching code.
|
||||
|
||||
## The headline demo: "change your whole store without a developer"
|
||||
|
||||
This is the strongest pitch. Open the **Project Editor** and show that a marketplace owner can restyle and reconfigure the entire storefront themselves. It has 11 tabs:
|
||||
|
||||
| Tab | What you show the prospect |
|
||||
|---|---|
|
||||
| General | Set the marketplace name, domain, description, and languages |
|
||||
| Branding | Upload logo, small logo, favicon |
|
||||
| Theme | Pick brand colors with a color picker, light/dark mode, choose a site layout |
|
||||
| Header | Toggle which features appear in the top bar (search, cart, wishlist, languages…) |
|
||||
| Footer | Company info, address, contacts, payment icons, social links |
|
||||
| Homepage | Drag-and-drop the order of homepage sections, choose layouts |
|
||||
| Widgets | Configure homepage blocks (hero banner, category grid, product rows) |
|
||||
| Marketplace Features | Turn features on/off (reviews, recommendations, recently-viewed, search history…) |
|
||||
| Languages | Add or remove a language for the whole store |
|
||||
| Navigation | Edit the menu links, per language |
|
||||
| Static Pages | Write pages like "About Us" with a rich text editor |
|
||||
|
||||
**Demo flow that lands well:**
|
||||
1. Change the brand color in **Theme** → show the store instantly reflecting it in **Preview**.
|
||||
2. Reorder homepage sections in **Homepage** by dragging.
|
||||
3. Edit an "About Us" page in **Static Pages** using the text editor (bold, headings, lists, links, images, tables).
|
||||
4. Point out the **Save / Publish** bar: work is saved as a **draft** first, and only goes live when they hit **Publish** — safe to experiment.
|
||||
|
||||
> The rich-text editor for static pages **works today**: type text, make it bold, add headings, bullet lists, links, images, and tables, or switch to a "Code" view for raw HTML.
|
||||
|
||||
## The admin backoffice (running the business)
|
||||
|
||||
Beyond styling, there's a full back-office. In demos, show the **layout and workflow** — the screens are built and polished. Be aware most of these currently run on **sample data** for demo purposes; real live data connects during onboarding (that's a backend integration step, not missing product).
|
||||
|
||||
| Area | What it does | Demo note |
|
||||
|---|---|---|
|
||||
| Dashboard | At-a-glance status: store status, theme, languages, counts, health, recent activity | Cards are live for config; sales counts show "pending backend" until integrated |
|
||||
| Products | Add/edit products: price, variants, images, categories, badges, bulk actions | **Sample data** in demo |
|
||||
| Categories | Category tree with drag-reorder, SEO, translations, soft-delete/restore | **Sample data** in demo |
|
||||
| Orders | Order list/detail, status changes, refunds, cancel, notes, CSV export, invoices | **Sample data** in demo |
|
||||
| Transactions | Payment records, retry failed, fraud flags, audit log, CSV | **Sample data** in demo |
|
||||
| Users & Roles | Team members, roles/permissions, invitations, session/audit history | **Sample data** in demo |
|
||||
| Monitoring | System health + security/event feeds, queues, webhooks | Health is live; feeds are sample |
|
||||
| Analytics | Revenue, orders, top products, plus visitors/funnels | Revenue/orders demo from sample; traffic analytics **Coming soon** |
|
||||
|
||||
## What's polished and worth showing off
|
||||
|
||||
A full UX pass was done across storefront, dashboard, admin, and editor:
|
||||
- Clean, consistent buttons and controls everywhere (one design system).
|
||||
- Smooth, tasteful motion — cards and sections animate in, buttons respond to hover/press — and it automatically respects "reduce motion" accessibility settings.
|
||||
- Works responsively down to phone size.
|
||||
|
||||
## Honest "coming soon" list (don't promise these as live)
|
||||
|
||||
- **Live business data** (real products/orders/customers) — connects per-customer during onboarding; demos use sample data.
|
||||
- **Saving edits to the cloud** — today the editor saves drafts in the browser; server-side save/publish is an onboarding integration.
|
||||
- **Traffic analytics** (visitors, funnels, heatmaps) — the screens exist; the data pipeline is not built yet.
|
||||
- **Per-customer sitemaps / advanced SEO automation** — baseline SEO is in; full automation is roadmap.
|
||||
|
||||
## Quick answers to likely prospect questions
|
||||
|
||||
- **"Do we need a developer to change our store?"** No — the Project Editor covers branding, colors, layout, pages, menus, languages, and feature toggles self-service.
|
||||
- **"Can we have our own domain and branding?"** Yes — each marketplace is its own tenant with its own domain, logo, colors, and content.
|
||||
- **"Multiple languages?"** Yes — add/remove languages in the editor; content is editable per language.
|
||||
- **"Is it safe to experiment?"** Yes — changes are drafts until Published.
|
||||
- **"Is it mobile-friendly?"** Yes — responsive across phone/tablet/desktop.
|
||||
|
||||
## One rule for demos
|
||||
|
||||
If a screen shows sample/placeholder data or a "pending backend" label, say **"this connects to your live data during onboarding"** — it's a real, built screen waiting on integration, not a gap in the product.
|
||||
@@ -1,94 +0,0 @@
|
||||
# Sprint Plan — Next Wave (G onward)
|
||||
|
||||
Continues the sprint lettering from `docs/GLOBAL-SPRINT-PLAN.md` (Sprints A–F, all closed 2026-08-05: stub-page closure + widget layout/carousel fixes). Created 2026-08-05.
|
||||
|
||||
**Relationship to `docs/NEXT_PHASE.md`:** that file stays the one *phase-level* roadmap and owns the backend-integration sequencing. This file is the *task-level* tracker for work that is actionable now, plus an explicit parking list for what is blocked and on what. Where the two overlap, `NEXT_PHASE.md` wins on ordering.
|
||||
|
||||
---
|
||||
|
||||
## Tier 1 — Actionable now (nothing blocks these)
|
||||
|
||||
### Sprint G — Dead-config sweep
|
||||
|
||||
**Why:** This is a config-driven multi-tenant product, so "setting exists in the editor, nothing reads it at runtime" is the signature failure mode — and it reaches clients directly. Three instances were found *by accident* during other work: theme mode (`data-theme-mode` set, no CSS reads it), `HeaderConfig.showProfile` (fixed, Sprint A), `layout.columns` (fixed, Sprint F, and was the root cause of a real client bug report). A mechanical sweep finds the rest in one pass instead of one complaint at a time.
|
||||
|
||||
- [x] Enumerate every field in `BootstrapConfig` and its sub-models (`src/app/shared/models/config/*.model.ts`) — produce the full field inventory as a working list
|
||||
- [x] For each field, grep for a real runtime consumer (a component/service that reads it and changes behavior), distinguishing: **live** (read + has effect), **dead** (never read), **inert** (read but effect is unreachable/no-op — the `data-theme-mode` case)
|
||||
- [x] Cross-check against the editor: which dead/inert fields are *user-editable* today (those are the client-facing ones, highest priority)
|
||||
- [x] Produce a findings table: field → status → editable? → recommendation (wire it / hide the control / delete the field)
|
||||
- [x] Fix the trivially-wireable ones in the same pass (a field with an obvious consumer that was simply never connected)
|
||||
- [x] For each remaining dead field, either hide its editor control or open a scoped follow-up — do **not** leave an editable control for a field nothing reads
|
||||
- [x] Record findings in `docs/KNOWN-ISSUES.md` (real defects) / `docs/PRODUCT_BACKLOG.md` (needs a decision), matching how the earlier audit was folded in
|
||||
|
||||
**Known starting points (already confirmed dead/inert):** theme mode (`PRODUCT_BACKLOG.md`, needs a dark-mode decision — not a wiring fix). Verify no others in `HeaderConfig`, `FooterConfig`, `CatalogConfig`, `ProductPageConfig`, `UserExperienceConfig`, `FeatureFlags`, `SeoConfig`.
|
||||
|
||||
**What shipped:** Full findings table in `docs/DEAD-CONFIG-AUDIT.md`. Fixed and wired: `footer.logoUrl` (new `LogoComponent.srcOverride` input), `company.address.street` + `company.contacts.phone` (new `UiRuntimeFacade.companyAddress()`/`contactPhone()`, rendered in footer bottom bar), `catalog.suggestionsEnabled` (`SearchFacade.autocomplete()` now gates on it). Left dead but tracked (needs a business/design decision, not a mechanical fix): `layout.type` site-layout selector, `company.companyName` copyright-fallback wording (both → `PRODUCT_BACKLOG.md`), `navigation.header` top-nav rendering (→ `KNOWN-ISSUES.md`). `catalog.navigationMode` confirmed intentionally inert (labeled placeholder card, not a bug). No editor control was hidden — every remaining dead field's saved value stays visible and none risked losing already-saved client data.
|
||||
|
||||
### Sprint H — Test suite foundation
|
||||
|
||||
**Why:** 5 `.spec.ts` files exist in the entire repository. Project standards mandate 80% coverage and a TDD workflow; neither is happening. `NEXT_PHASE.md` Phase 2 defers testing until after backend integration — **this sprint deliberately front-runs part of that**, on the argument that tests written against the *current mock gateways* lock in today's behavior and make the eventual real-gateway swap far safer. Post-backend E2E work stays in Phase 2 where it is.
|
||||
|
||||
- [x] Confirm the test runner actually works end to end (`npm test` → `ng test --watch=false --browsers=ChromeHeadlessNoSandbox`) and fix the harness if it doesn't
|
||||
- [x] Establish the house pattern with one exemplar spec per layer, so later tests have something to copy: a pure util, a service, a facade, a component
|
||||
- [x] Facade-level tests against existing mock gateways for the highest-risk domains first: `ProjectEditorFacade` (undo/redo, draft persistence, validation gating on publish), `AdminAnalyticsFacade` (the never-fabricate-a-number contract)
|
||||
- [x] Unit tests for the pure validator primitives (`project-editor/schema/validators/primitives.ts`) — zero-dependency, highest value per line of test
|
||||
- [x] Regression tests for the bugs fixed this cycle so they cannot silently return (carousel `layout.columns` sizing, hero `layout.columns` panel count, header profile login/logout gating)
|
||||
- [x] Wire coverage reporting — **done**: installed `karma-coverage` as a devDependency, added it to `karma.conf.js` (`coverage` reporter + `coverageReporter` block emitting `text-summary`, `html`, and `lcovonly` into `coverage/`). `npx ng test --watch=false --code-coverage` runs clean (83/83 specs pass). Baseline: Statements 32.02% (1025/3201), Branches 18.53% (353/1904), Functions 21.73% (220/1012), Lines 32.76% (946/2887).
|
||||
- [x] Decide whether to gate CI on it — **no, not yet**: 11 spec files is a foundation, not the coverage floor CI gating implies; gate once coverage reporting exists and a real floor number can be set, not before.
|
||||
|
||||
**What shipped:** Harness confirmed working (`npm test` was already green, 57/57). Added 6 new spec files (test count 57 → 75): `ProjectEditorFacade` facade spec (undo/redo, draft-persistence round-trip via a second facade instance reading the same localStorage draft, publish blocked/allowed on `hasBlockingIssues()`) mocking `CONFIG_PROVIDER` as the gateway boundary; `AdminAnalyticsFacade` facade spec asserting `summary().conversionRate` stays `null` and `performance`/`backend-connectivity` health checks stay `'unknown'` rather than being guessed, mocking all 4 gateways + `AdminDashboardFacade`; `HeroWidgetComponent` and `ProductCarouselWidgetComponent` component specs regression-covering `layout.columns` (panel count / items-per-page); `HeaderComponent` component spec regression-covering the login/logout profile toggle (asserts on icon name, not translated aria-label text, since Russian is the default active language in tests). `primitives.ts` and the pure-util/service exemplar layers were already covered by pre-existing specs — verified, not re-done. Cart/checkout facade tests and a "manifest-filtered layout options" regression were scoped out to stay within this sprint's time budget — breadth across the 4 required layers (util/service/facade/component) was prioritized over a 5th facade.
|
||||
|
||||
### Sprint I — Widget `settingsSchema` enforcement
|
||||
|
||||
**Why:** Same disease Sprint E cured for `supportedLayouts`. Every widget in `widget-manifest.json` declares a JSON Schema for its props under `settingsSchema`, and **nothing reads it** — verified: only `supportedDataSources` is consumed anywhere (and only by a diagnostics validator, not the editor). Consequences: widget props are never validated against their own declared contract, and unknown widget types fall back to raw JSON editing in the Widgets section. (`enabled` *is* honored correctly — `widget-registry.bootstrap.service.ts` filters on it.)
|
||||
|
||||
- [x] Read `settingsSchema` in the Widgets editor section and validate widget props against it, surfacing failures through the existing `ProjectValidator` issue pipeline (`fieldKey`/`section`/`severity`) rather than a parallel mechanism
|
||||
- [x] Add a `widgetSettingsSchema` validator alongside the existing `widgetConfig` check in `project-validator.service.ts`
|
||||
- [x] Evaluate replacing the raw-JSON fallback editor with schema-generated fields for widget types that have no hand-authored editor — scope this honestly; if the schemas are too thin to generate a decent UI, keep the JSON fallback and just add validation on top
|
||||
- [x] Confirm the diagnostics page (`features/diagnostics/`) reflects schema violations too, since it already consumes the manifest
|
||||
|
||||
**What shipped:** `validateAgainstSchemaLite(value, schema)` (`schema/validators/primitives.ts`) — a shallow, dependency-free type+required checker (no nested schemas/enums/$ref; checked first, no existing schema-validation utility or library in the repo). `ProjectValidator.widgetSettingsSchemaIssues()` runs it against every widget's `props` vs. its manifest entry's `settingsSchema`, added to the same `validate()` composition as a `widgets`-section warning tagged `fieldKey: 'pages'` — it surfaces automatically through the existing `fieldError('pages')` call already in `widgets-section.component.html`, no template changes needed. `WidgetManifestService` gained a synchronous `getManifestSnapshot()` (same pattern as `ConfigService.getBootstrapSnapshot()`) since `ProjectValidator.validate()` is called synchronously and can't await the manifest HTTP fetch; the check no-ops (matching `RuntimeDiagnosticsValidator`'s existing null-manifest convention) until the manifest has loaded once elsewhere in the app (it always has, by the time a user reaches the editor). Schema-generated form fields were evaluated and explicitly skipped: every widget's `settingsSchema.properties` tops out at 7 flat string/number fields with zero `required` arrays and zero enums/nesting across all 10 widget types in `widget-manifest.json` — too thin to justify generated UI over the existing JSON fallback (`widgets-section.component.ts`'s `updateJson`/`widgetJsonError`), so the JSON editor stays and only gets the new validation layered on top. Diagnostics: `BootstrapDiagnosticsValidator` gained a sibling `validateWidgetSettingsSchema()` next to its existing `validateUnknownWidgetTypes()`, reusing the identical `validateAgainstSchemaLite` call so the editor and diagnostics page can never disagree about what counts as a violation — one check, two surfaces, not a parallel one.
|
||||
|
||||
---
|
||||
|
||||
## Tier 2 — Blocked on backend
|
||||
|
||||
Sequencing is owned by `docs/BACKEND.md` §9 and `docs/NEXT_PHASE.md` Phase 1. Not re-planned here — that checklist is already the authoritative task list. Frontend-side items that unblock the moment backend lands:
|
||||
|
||||
- [ ] **Ed25519 auth error codes** (`docs/KNOWN-ISSUES.md` Open #1) — `session-expired` and `invalid-signature` recovery screens are built and wired but permanently unreachable, because `toAuthErrorShape()` derives the code purely from HTTP status and never reads a body-level code. Needs: backend returning a distinguishable `error.code` (`BACKEND.md` §6), then a small frontend change to prefer it over the status fallback.
|
||||
- [ ] **Swap every mock gateway for its real counterpart** behind the existing DI tokens, in the dependency order `BACKEND.md` §8 specifies
|
||||
- [ ] **Maintenance-mode frontend UI** (`BACKEND.md` §10 flags full-page takeover, per-module banners, scheduled countdown as not existing) — deliberately not built during Sprint C for exactly this reason
|
||||
- [ ] **Real Monitoring data source** — page currently renders mock activity (`NEXT_PHASE.md` Phase 4)
|
||||
- [ ] **Re-profile performance under real latency** (`NEXT_PHASE.md` Phase 3) — mock responses are instant, real ones won't be; loading/skeleton timing is untested against reality
|
||||
|
||||
## Tier 3 — Blocked on a business decision
|
||||
|
||||
No engineering work should start on these until answered. Full detail in `docs/PRODUCT_BACKLOG.md`.
|
||||
|
||||
- [ ] **Dark mode** — does the client want it? If yes it's a real project (dark palette + CSS strategy + `matchMedia` for "system"), not a wiring fix. Blocks the theme-mode selector, which is inert today.
|
||||
- [ ] **Brand color contrast (WCAG AA)** — `--border-color` fails 3:1 in every theme; several status colors fail 4.5:1 as text. Fixing means visibly changing the brand — needs theme-owner sign-off.
|
||||
- [ ] **Stars rating glyph token** — literal hex with no matching design token; add a token or reuse an existing one (visual shift either way).
|
||||
- [ ] **Contacts page content** — nothing written for it at all. Content question.
|
||||
- [ ] **Advanced analytics** — no data source exists for traffic/funnels/heatmaps. Build vs. buy, and launch vs. later.
|
||||
- [ ] **Additional payment providers** — which ones, if any, before integration work starts.
|
||||
|
||||
## Tier 4 — Deferred, non-blocking
|
||||
|
||||
From `docs/FUTURE_FEATURES.md`. No decision needed, just not worth doing now.
|
||||
|
||||
- [ ] **Angular 22 upgrade** — researched, ~2–3.5 days, needs a dependency fix and Node bump first. Plan: `docs/ANGULAR22_PLAN.md`. **Run as its own dedicated session** — framework upgrades don't share a session with feature work.
|
||||
- [ ] **Bundle splitting** — `project-editor` (~896 kB) and `catalog-container` (~330 kB) lazy chunks are large; no mechanical split found, needs a dedicated profiling task, ideally under real backend latency
|
||||
- [ ] **Cart payment modal → `app-dialog`** — composition cleanup, functionally and accessibly complete as-is
|
||||
- [ ] **Homepage hero-to-categories spacing** — traces to mock fixture padding values, not a confirmed defect; needs reproduction with real tenant data before it's worth investigating
|
||||
|
||||
## Tier 5 — Infrastructure
|
||||
|
||||
- [ ] **Server deploy** — no deploy pipeline exists in this repo (only `.github/workflows/architecture-governance.yml`). Deploys are currently manual/out-of-band. Worth deciding whether a real pipeline should exist; separately, SSH from the agent harness is blocked, so agent-driven deploys need either a permission rule or a different mechanism.
|
||||
|
||||
---
|
||||
|
||||
## Suggested order
|
||||
|
||||
**G → H → I.** Sprint G is cheap, mechanical, and directly prevents more client-reported ghost settings (it is the same class of bug as the one already reported). Sprint H is the highest-value thing available that isn't blocked on anything, and it gets more valuable the earlier it lands, since every later change rides on it. Sprint I is real but narrower — it hardens an editor path rather than fixing something users hit today.
|
||||
|
||||
Tier 2 starts the moment backend Phase 1 lands. Tier 3 needs answers, not engineering. Tier 4 is genuinely optional.
|
||||
@@ -1,84 +0,0 @@
|
||||
# Static Pages (Project Editor module)
|
||||
|
||||
Sprint X+2. Full-featured CRUD editor for tenant static content (About, Privacy, Terms, Contacts, custom pages, etc.), living inside the Project Editor at `/edit/static-pages`. Edits `bootstrap.staticPages` directly — the same model the storefront renders from (`docs/BACKEND.md` §3 CRUD Contracts, CMS), no parallel content store.
|
||||
|
||||
`/backoffice/static-pages` (Admin dashboard) redirects here rather than hosting a second CRUD UI over the same data.
|
||||
|
||||
## Where it lives
|
||||
|
||||
```
|
||||
src/app/features/content-management/
|
||||
models/content-page.model.ts ContentPage — the CRUD-facing shape
|
||||
services/content-page.service.ts normalize / resolve / validate / serialize <-> StaticPageConfig
|
||||
facade/content-management.facade.ts
|
||||
components/
|
||||
static-pages-editor.component.* the editor UI (list + per-page card)
|
||||
static-page-preview/ device preview (desktop/tablet/mobile)
|
||||
|
||||
src/app/shared/models/config/static-page.model.ts StaticPageConfig — the bootstrap wire format
|
||||
src/app/features/project-editor/components/html-editor/ MarketplaceHtmlEditorComponent (rich text)
|
||||
src/app/core/config/static-page-resolver.service.ts storefront resolver
|
||||
```
|
||||
|
||||
`ContentPageService` is the single translation layer between the editor's `ContentPage[]` and the bootstrap's `Record<string, StaticPageConfig>` (or the legacy array format) — normalize/resolve/validate/serialize all live there. Nothing else should hand-roll that mapping.
|
||||
|
||||
## Field reference
|
||||
|
||||
### General
|
||||
- `id` — stable key, also the bootstrap record key.
|
||||
- `slug` — used for duplicate-slug detection and as the `route` default.
|
||||
- `route` — **independently editable** from `slug` (defaults from it, but can diverge — e.g. a legacy redirect path). Validated for duplicates against every other page's route.
|
||||
- `enabled` — master on/off switch. A disabled page never resolves on the storefront, regardless of `status`.
|
||||
- Navigation visibility — `showInHeader`, `showInFooter`, `showInSitemap` (independent per-surface flags, unrelated to `enabled`).
|
||||
- `order` — sort position in the editor list and (for footer pages) the auto-generated footer nav group.
|
||||
- `icon` — optional icon identifier.
|
||||
|
||||
### Localization
|
||||
- `title` (per locale, in `translations[locale].title`) and a top-level `title` fallback.
|
||||
- `translations[locale].html` — the rich-text/HTML body, one per supported locale.
|
||||
- `customTemplate` — optional template identifier; consumed by nothing yet (data-only field, forward-compatible with a future template-selection feature).
|
||||
|
||||
### SEO
|
||||
Per top-level `seo` and per-translation `translations[locale].seo` (locale-specific overrides win when resolving): `title`, `description`, `keywords`, `canonical`, `robots`, `ogTitle`, `ogDescription`, `ogImage`.
|
||||
|
||||
### Media
|
||||
- `heroImage`, `thumbnail` — wired through the shared `MediaPickerComponent` (same picker used by Branding/Footer logos).
|
||||
- `gallery: string[]` — a lightweight comma-separated URL list ("future ready" per the brief; no dedicated multi-upload UI yet).
|
||||
|
||||
### Publishing
|
||||
- `status: 'draft' | 'published'` — **per-page** publish lifecycle, independent of the whole-bootstrap draft/publish cycle (see below).
|
||||
- Modified indicator — an "unsaved changes" badge per page, diffed against the originally loaded/published snapshot (`ProjectEditorFacade.originalStaticPages`).
|
||||
|
||||
## The enabled + status gating story
|
||||
|
||||
A static page resolves on the storefront (`ContentPageService.resolvePage`, used by both `StaticPageResolverService` for the page route and `FooterResolverService` for the auto-generated footer nav group) **only when `enabled === true` AND `status === 'published'`.** This is independent of whether the surrounding bootstrap itself has been published — a page marked `draft` stays invisible even after the tenant hits "Publish" on the whole config, and only becomes visible once its own status flips to `published`.
|
||||
|
||||
**Compatibility default:** normalizing existing bootstrap data (loaded from the backend, imported, or read from a legacy array-format `staticPages`) defaults missing `enabled`/`status` to `enabled: true, status: 'published'` — pre-existing pages never get silently un-published by this feature landing. Only the editor's **create-page** action opts a brand-new page into `status: 'draft'` by default, so newly authored content doesn't go live until an author explicitly publishes it.
|
||||
|
||||
The Static Pages editor's own **Live Preview** (desktop/tablet/mobile, `StaticPagePreviewComponent`) intentionally bypasses this gate — it renders straight from the page's current in-memory HTML, so a draft page can still be previewed before publishing.
|
||||
|
||||
## CRUD, search, filter, bulk actions
|
||||
|
||||
- Create, duplicate (clones a page as a new `draft`), delete (confirm dialog), reorder (up/down — not drag-and-drop; see below).
|
||||
- Search across id/slug/route/title (all locales); filter by status (draft/published) or by locale (hides pages missing a translation for the selected locale).
|
||||
- Bulk actions (multi-select checkboxes): delete, enable, disable, publish, unpublish.
|
||||
- **Important implementation detail:** every mutation (create/duplicate/delete/move/bulk) operates on the full, unfiltered page list, never the search/filter-narrowed view — reading from the filtered view before writing back would silently delete whatever the active filter was hiding. See the `persist()` comment in `static-pages-editor.component.ts`.
|
||||
- Reorder is up/down (`move()`), not literal drag handles — a deliberate, lower-complexity scope call; swapping in drag-and-drop later is additive.
|
||||
|
||||
## Validation
|
||||
|
||||
`ContentPageService.validatePages()` returns: `duplicateSlugs`, `duplicateRoutes` (checked independently — a route can diverge from its slug), `emptyTitles`, `invalidHtml` (via `schema/validators/primitives.validateHtml`), `invalidSeo` (canonical/OG-image URL shape, and `robots` against a known-token set: `index`, `noindex`, `follow`, `nofollow`, and their comma-joined combinations). Surfaced as per-page badges in the editor.
|
||||
|
||||
This is layered under (not a replacement for) `ProjectValidator`'s existing platform-wide checks (`docs/EDITOR.md`'s "Configuration schema..." section), which already cover duplicate slugs and cross-surface duplicate routes (pages vs. static pages) at the whole-bootstrap level.
|
||||
|
||||
## Rich text editor
|
||||
|
||||
`MarketplaceHtmlEditorComponent` — see `docs/EDITOR.md`'s "HTML editor (Static Pages)" section for the full toolbar list, the Sprint X+2 additions (horizontal rule, code block, embed), and the HTML-mode validation contract.
|
||||
|
||||
## Navigation integration
|
||||
|
||||
The Navigation section (`navigation-section.component`) has an "Insert page link" control (page picker + button) next to both header and footer "Add link." It creates a `NavigationItemConfig { type: 'staticPage', key: <pageId> }` — a shape the resolvers (`StaticPageResolverService`, `FooterResolverService`) already understood before this sprint; only the editor-side create path was missing. A static-page-linked nav row shows a "Linked to page" indicator instead of editable label/URL fields, since both are derived dynamically from the linked page.
|
||||
|
||||
## Export / import / draft / publish
|
||||
|
||||
No changes to `ProjectEditorIoService` or `ProjectEditorDraftStorageService` — static pages flow through `bootstrap.staticPages` exactly as before, so JSON export/import and the draft-autosave/publish cycle work unmodified. All Sprint X+2 fields are additive and optional at the wire level.
|
||||
@@ -1,7 +0,0 @@
|
||||
# TODO
|
||||
|
||||
No frontend blockers.
|
||||
|
||||
Frontend Release Candidate complete.
|
||||
|
||||
Waiting for backend integration.
|
||||
@@ -1,44 +0,0 @@
|
||||
# Coding Standards
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## Core Principles
|
||||
|
||||
- Keep modules small and cohesive.
|
||||
- Prefer pure functions where possible.
|
||||
- Prefer composition over inheritance.
|
||||
- Prefer configuration over conditionals.
|
||||
- Avoid duplication; extract shared behavior above 70 percent overlap.
|
||||
|
||||
## Type Safety
|
||||
|
||||
- No any in domain and configuration contracts.
|
||||
- Strict typing for API payloads and configuration schemas.
|
||||
- Use discriminated unions for widget and section types.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Errors normalized at service/integration boundaries.
|
||||
- UI displays user-safe messages from facades/view models.
|
||||
- No unhandled promise rejections.
|
||||
|
||||
## API and IO
|
||||
|
||||
- IO is performed in services/integrations only.
|
||||
- Use facades to orchestrate calls and map outputs.
|
||||
- Keep presentation layer side-effect free.
|
||||
|
||||
## Testing Expectations
|
||||
|
||||
- Unit tests for facades, services, and mapping logic.
|
||||
- Contract tests for bootstrap schema compatibility.
|
||||
- Boundary tests for forbidden imports.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- Does this code violate layer boundaries?
|
||||
- Is tenant behavior configuration-driven?
|
||||
- Is logic duplicated and extractable?
|
||||
- Are auth/payment contracts unchanged?
|
||||
- Is the app still compilable?
|
||||
@@ -1,41 +0,0 @@
|
||||
# Component Standards
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## Component Categories
|
||||
|
||||
- UI Component: presentational, reusable, stateless or locally visual state only.
|
||||
- Container Component: binds facades and maps view model to UI inputs.
|
||||
- Layout Component: structural composition of sections/widgets.
|
||||
|
||||
## Reusable UI Rules
|
||||
|
||||
A reusable component must:
|
||||
|
||||
- Receive data via Inputs.
|
||||
- Emit user intent via Outputs.
|
||||
- Contain no HttpClient usage.
|
||||
- Contain no localStorage/sessionStorage usage.
|
||||
- Import no environment data.
|
||||
- Have no tenant-specific behavior.
|
||||
- Have no project-name-specific behavior (including legacy variant naming paths).
|
||||
- Have no auth/payment/authorization logic.
|
||||
- Have no route navigation logic.
|
||||
- Render no hardcoded UI copy when translation keys are expected.
|
||||
|
||||
## Container Rules
|
||||
|
||||
- Orchestrate business behavior through facades.
|
||||
- Map facade state into UI-friendly view model.
|
||||
- Handle route and guard interactions.
|
||||
- Never leak domain internals to UI components.
|
||||
|
||||
## Reuse and Duplication Rule
|
||||
|
||||
- If two components share more than 70 percent behavior or template structure, extract reusable component.
|
||||
|
||||
## Accessibility and UX
|
||||
|
||||
- Components must provide semantic markup and keyboard support.
|
||||
- Outputs must represent intent, not implementation details.
|
||||
@@ -1,62 +0,0 @@
|
||||
# Configuration Standards
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## Source of Truth
|
||||
|
||||
- All runtime application configuration originates from bootstrap payload.
|
||||
- ConfigService is the only component allowed to load configuration.
|
||||
- No direct JSON loading outside ConfigService.
|
||||
|
||||
## Provider Abstraction
|
||||
|
||||
- Configuration provider must be swappable.
|
||||
- Mock and API providers must return identical schema.
|
||||
- Consumer code remains unchanged when provider changes.
|
||||
|
||||
## Bootstrap Contract Scope
|
||||
|
||||
Bootstrap includes at minimum:
|
||||
|
||||
- Tenant
|
||||
- Branding
|
||||
- Theme
|
||||
- Company
|
||||
- Feature flags
|
||||
- Navigation
|
||||
- Pages, sections, widgets
|
||||
- Localization
|
||||
- SEO
|
||||
- Permissions and capability model
|
||||
- Endpoint descriptors
|
||||
|
||||
## Backend Compatibility
|
||||
|
||||
- Frontend calls GET /bootstrap.
|
||||
- Backend resolves tenant from Host.
|
||||
- Frontend does not send tenant id/project key.
|
||||
|
||||
## Validation and Versioning
|
||||
|
||||
- Bootstrap payload must include schema version.
|
||||
- Validate payload before applying to runtime.
|
||||
- Invalid payload fails fast with controlled fallback.
|
||||
|
||||
## Mock Rules
|
||||
|
||||
- Mock payloads must match future API responses exactly.
|
||||
- No mock-only fields.
|
||||
- No mock-only nesting conventions.
|
||||
|
||||
## Sprint 11.5 Bootstrap Audit Addendum
|
||||
|
||||
- Every configurable website behavior must be representable in bootstrap contracts or widget metadata.
|
||||
- Missing configuration must be documented before implementation work starts.
|
||||
- Frontend teams must not implement backend contract changes in standardization sprints.
|
||||
|
||||
Current documented gaps:
|
||||
|
||||
- Widget role/permission enforcement requires richer auth session claims than currently available.
|
||||
- Catalog popular-search defaults should move from facade constants into bootstrap `catalog` config.
|
||||
- Optional override support for persistent-storage key prefixes is not yet represented in bootstrap schema.
|
||||
@@ -1,35 +0,0 @@
|
||||
# Dependency Rules
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## Rule Set
|
||||
|
||||
1. One-way dependency direction only.
|
||||
2. No circular dependencies.
|
||||
3. No feature imports another feature directly.
|
||||
4. Shared is dependency-minimal and feature-agnostic.
|
||||
5. Core is platform base and does not consume feature modules.
|
||||
6. UI Library is pure presentation and cannot depend on facades/services with business behavior.
|
||||
7. Widgets depend on UI Library and contracts, not on feature internals.
|
||||
8. Pages compose layouts/widgets via contracts and facades.
|
||||
9. Facades depend on services/contracts, never on UI components.
|
||||
10. Integrations isolate external systems and expose stable interfaces.
|
||||
|
||||
## Dependency Injection Rules
|
||||
|
||||
- Depend on interfaces/tokens where replacement is expected.
|
||||
- Avoid direct concrete service references across bounded contexts.
|
||||
- Use adapter pattern for legacy stable modules.
|
||||
|
||||
## Cross-Domain Communication
|
||||
|
||||
- Allowed through contracts, events, and facade APIs.
|
||||
- Forbidden through direct state mutation across domains.
|
||||
|
||||
## Forbidden Patterns
|
||||
|
||||
- Component to HttpClient direct calls in reusable visual components.
|
||||
- Direct environment import in visual components.
|
||||
- Direct localStorage/sessionStorage usage in UI components.
|
||||
- Route navigation logic in UI library components.
|
||||
@@ -1,122 +0,0 @@
|
||||
# Folder Blueprint
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## Objective
|
||||
|
||||
Define the target folder layout for the Foundation Phase and all subsequent phases.
|
||||
|
||||
## Blueprint
|
||||
|
||||
src
|
||||
- app
|
||||
- core
|
||||
- bootstrap
|
||||
- providers
|
||||
- loaders
|
||||
- validators
|
||||
- config
|
||||
- application-config.token.ts
|
||||
- config.service.ts
|
||||
- feature-flag.service.ts
|
||||
- runtime
|
||||
- app-runtime.service.ts
|
||||
- platform-context.service.ts
|
||||
- guards
|
||||
- interceptors
|
||||
- error-handling
|
||||
- shared
|
||||
- models
|
||||
- api
|
||||
- config
|
||||
- domain
|
||||
- ui
|
||||
- types
|
||||
- enums
|
||||
- contracts
|
||||
- utils
|
||||
- constants
|
||||
- ui-library
|
||||
- atoms
|
||||
- molecules
|
||||
- organisms
|
||||
- directives
|
||||
- pipes
|
||||
- widgets
|
||||
- registry
|
||||
- contracts
|
||||
- containers
|
||||
- ui
|
||||
- layouts
|
||||
- shells
|
||||
- sections
|
||||
- containers
|
||||
- pages
|
||||
- public
|
||||
- builder
|
||||
- backoffice
|
||||
- features
|
||||
- website
|
||||
- catalog
|
||||
- product
|
||||
- cart
|
||||
- checkout
|
||||
- builder
|
||||
- theme-editor
|
||||
- page-editor
|
||||
- navigation-editor
|
||||
- seo-editor
|
||||
- feature-flag-editor
|
||||
- backoffice
|
||||
- products
|
||||
- categories
|
||||
- orders
|
||||
- customers
|
||||
- inventory
|
||||
- media
|
||||
- settings
|
||||
- facades
|
||||
- website
|
||||
- builder
|
||||
- backoffice
|
||||
- platform
|
||||
- integrations
|
||||
- auth
|
||||
- payment
|
||||
- authorization
|
||||
- theme
|
||||
- tokens
|
||||
- mappers
|
||||
- runtime
|
||||
- dynamic-renderer
|
||||
- page-renderer
|
||||
- section-renderer
|
||||
- widget-host
|
||||
- app.routes.ts
|
||||
- app.config.ts
|
||||
- app.ts
|
||||
- app.html
|
||||
- assets
|
||||
- mock
|
||||
- bootstrap
|
||||
- website
|
||||
- builder
|
||||
- backoffice
|
||||
|
||||
## Foundation Phase Scope
|
||||
|
||||
During Phase 1:
|
||||
|
||||
- Create folder structure and placeholders only.
|
||||
- Do not create business feature implementations.
|
||||
- Keep app runnable and compilable.
|
||||
|
||||
## Placement Rules
|
||||
|
||||
- Contracts and interfaces go to shared models/contracts/types.
|
||||
- Pure visual components go to ui-library.
|
||||
- Configuration-driven blocks go to widgets.
|
||||
- Page composition logic goes to layouts and dynamic-renderer.
|
||||
- Domain orchestration belongs to facades.
|
||||
- Stable auth/payment integrations stay in integrations wrappers.
|
||||
@@ -1,38 +0,0 @@
|
||||
# Import Boundary Matrix
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## Allowed Import Matrix
|
||||
|
||||
Legend:
|
||||
|
||||
- Yes: Allowed
|
||||
- No: Forbidden
|
||||
- Limited: Allowed only via published contracts
|
||||
|
||||
| From \ To | Core | Shared | UI Library | Widgets | Layouts | Pages | Features | Facades | Integrations |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| Core | Yes | Yes | No | No | No | No | No | No | Limited |
|
||||
| Shared | Yes | Yes | No | No | No | No | No | No | No |
|
||||
| UI Library | Shared only | Yes | Yes | No | No | No | No | No | No |
|
||||
| Widgets | Shared/Core contracts | Yes | Yes | Yes | No | No | No | Limited | No |
|
||||
| Layouts | Shared/Core contracts | Yes | Yes | Yes | Yes | No | No | Limited | No |
|
||||
| Pages | Shared/Core contracts | Yes | Yes | Yes | Yes | Yes | No | Yes | No |
|
||||
| Features | Shared/Core contracts | Yes | Yes | Yes | Yes | Yes | No direct feature-to-feature | Yes | Limited |
|
||||
| Facades | Shared/Core contracts | Yes | No | No | No | No | Limited | Yes | Yes |
|
||||
| Integrations | Shared/Core contracts | Yes | No | No | No | No | No | Limited | Yes |
|
||||
|
||||
## Additional Constraints
|
||||
|
||||
- Features cannot import other features directly.
|
||||
- Shared cannot import any feature, page, layout, widget, or UI layer.
|
||||
- UI Library cannot import facades, integrations, router, or HttpClient.
|
||||
- Core cannot import features.
|
||||
- Circular dependencies are forbidden in all directions.
|
||||
|
||||
## Enforcement
|
||||
|
||||
- Enforce with lint module boundaries.
|
||||
- Enforce with dependency graph checks in CI.
|
||||
- Merge blocked on violations.
|
||||
@@ -1,56 +0,0 @@
|
||||
# Naming Conventions
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## General
|
||||
|
||||
- Use clear domain-oriented names.
|
||||
- Prefer explicit names over abbreviations.
|
||||
- Keep naming consistent across Website, Builder, Backoffice.
|
||||
|
||||
## Files and Folders
|
||||
|
||||
- Folders: kebab-case.
|
||||
- TypeScript files: kebab-case with suffix.
|
||||
- Interfaces: PascalCase.
|
||||
- Types: PascalCase.
|
||||
- Enums: PascalCase.
|
||||
- Constants: UPPER_SNAKE_CASE for true constants.
|
||||
|
||||
## Angular Artifacts
|
||||
|
||||
- Component: name.component.ts
|
||||
- Container component: name.container.component.ts
|
||||
- Facade: name.facade.ts
|
||||
- Service: name.service.ts
|
||||
- Adapter: name.adapter.ts
|
||||
- Token: name.token.ts
|
||||
- Guard: name.guard.ts
|
||||
- Resolver: name.resolver.ts
|
||||
- Pipe: name.pipe.ts
|
||||
|
||||
## Configuration Contracts
|
||||
|
||||
- Bootstrap payload root: BootstrapConfig.
|
||||
- Domain segments named by function:
|
||||
- TenantConfig
|
||||
- BrandingConfig
|
||||
- ThemeConfig
|
||||
- FeatureFlagsConfig
|
||||
- NavigationConfig
|
||||
- PageConfig
|
||||
- SectionConfig
|
||||
- WidgetConfig
|
||||
|
||||
## Event and Action Naming
|
||||
|
||||
- Outputs: actionRequested, valueChanged, selectionChanged.
|
||||
- Facade commands: loadX, updateX, saveX, publishX.
|
||||
- Selectors/signals: xState, xViewModel, isXEnabled.
|
||||
|
||||
## Prohibited Names
|
||||
|
||||
- Generic names without domain meaning such as DataService or UtilsService.
|
||||
- Tenant-coded names in frontend source.
|
||||
- Brand-specific class names in reusable layers.
|
||||
@@ -1,128 +0,0 @@
|
||||
# Marketplace Platform Architecture Foundation
|
||||
|
||||
Status: Approved
|
||||
Owner: Lead Software Architect
|
||||
Date: 2026-07-03
|
||||
|
||||
## Purpose
|
||||
|
||||
This folder defines the mandatory engineering governance for transforming this codebase into a reusable, configuration-driven, multi-tenant Marketplace Platform (Marketplace-as-a-Service).
|
||||
|
||||
This repository is not treated as a single marketplace website.
|
||||
It is a platform runtime that must support unlimited tenants from one Angular application.
|
||||
|
||||
## Platform Principles
|
||||
|
||||
- One codebase, unlimited tenants.
|
||||
- Every tenant has three surfaces: Website, Builder, Backoffice.
|
||||
- Frontend contains no tenant-specific implementation code.
|
||||
- Tenant behavior is controlled by configuration loaded at bootstrap.
|
||||
- Authentication, payment, authorization behavior and contracts remain unchanged.
|
||||
- Prefer composition over inheritance.
|
||||
- Prefer configuration over conditionals.
|
||||
- No circular dependencies.
|
||||
- Shared and UI layers are feature-agnostic.
|
||||
|
||||
## Non-Negotiable Constraints
|
||||
|
||||
- Authentication behavior remains exactly as current implementation.
|
||||
- Payment API behavior remains exactly as current implementation.
|
||||
- Authorization behavior remains exactly as current implementation.
|
||||
- Existing authentication and payment API contracts cannot be changed.
|
||||
- Proven modules are reused, wrapped, and isolated, not redesigned.
|
||||
|
||||
## Document Set
|
||||
|
||||
### Architecture Decision Records
|
||||
|
||||
- [ADR-001](adr/ADR-001-platform-model.md)
|
||||
- [ADR-002](adr/ADR-002-layered-feature-architecture.md)
|
||||
- [ADR-003](adr/ADR-003-import-boundaries-and-dependency-direction.md)
|
||||
- [ADR-004](adr/ADR-004-configuration-bootstrap-and-provider-abstraction.md)
|
||||
- [ADR-005](adr/ADR-005-dynamic-page-section-widget-rendering.md)
|
||||
- [ADR-006](adr/ADR-006-ui-component-purity-and-container-facade-pattern.md)
|
||||
- [ADR-007](adr/ADR-007-state-management-and-facade-boundaries.md)
|
||||
- [ADR-008](adr/ADR-008-theme-engine-and-design-token-runtime.md)
|
||||
- [ADR-009](adr/ADR-009-feature-flags-and-capability-guards.md)
|
||||
- [ADR-010](adr/ADR-010-backward-compatibility-for-auth-payment-authorization.md)
|
||||
- [ADR-011](adr/ADR-011-optional-seller-management-module.md)
|
||||
|
||||
### Seller Management (optional, in preparation — not built)
|
||||
|
||||
- [Seller-Management.md](Seller-Management.md) — capability overview, start here
|
||||
- [ADR-011](adr/ADR-011-optional-seller-management-module.md) — decision record
|
||||
- [Seller-Management-Diagrams.md](Seller-Management-Diagrams.md) — hierarchy, bootstrap gate, type diagram
|
||||
- [Seller-Management-Domain-Models.md](Seller-Management-Domain-Models.md) — typed models, optional sellerId fields
|
||||
- [Seller-Management-UX-Review.md](Seller-Management-UX-Review.md) — UX/accessibility review of the Phase 1 UI
|
||||
- [Seller-Management-Backoffice-Readiness-Audit.md](Seller-Management-Backoffice-Readiness-Audit.md) — per-module scoping/permissions readiness audit
|
||||
- [Seller-Management-Storefront-Audit.md](Seller-Management-Storefront-Audit.md) — `market.com`/`seller.market.com` storefront readiness audit
|
||||
- [Seller-Management-Backend-Migration-Plan.md](Seller-Management-Backend-Migration-Plan.md) — full backend migration plan, module-by-module, phased
|
||||
- [Seller-Management-Final-Design-Review.md](Seller-Management-Final-Design-Review.md) — principal-architect review, findings, verdict
|
||||
|
||||
### Engineering Rule Documents
|
||||
|
||||
- [Folder Blueprint](Folder-Blueprint.md)
|
||||
- [Import Boundary Matrix](Import-Boundary-Matrix.md)
|
||||
- [Dependency Rules](Dependency-Rules.md)
|
||||
- [Naming Conventions](Naming-Conventions.md)
|
||||
- [Coding Standards](Coding-Standards.md)
|
||||
- [Component Standards](Component-Standards.md)
|
||||
- [Service Standards](Service-Standards.md)
|
||||
- [Configuration Standards](Configuration-Standards.md)
|
||||
- [State Management Standards](State-Management-Standards.md)
|
||||
|
||||
## Compliance
|
||||
|
||||
All new work must comply with this foundation.
|
||||
If an implementation conflicts with these rules, implementation must be adjusted.
|
||||
If a rule must change, an ADR update is required first.
|
||||
|
||||
## Sprint 11.5 Standardization Audit (2026-07-09)
|
||||
|
||||
Platform-wide standardization was executed before Admin Platform work.
|
||||
|
||||
### Completed Standardization
|
||||
|
||||
- Verified and enforced container/facade/domain/infrastructure boundaries across active website features.
|
||||
- Removed remaining legacy variant naming in application-layer templates/styles.
|
||||
- Removed duplicate legacy search-history implementation in catalog feature module.
|
||||
- Standardized design-token surface with explicit spacing, radius, shadow, and transition tokens.
|
||||
- Added widget metadata support for title/subtitle/visibility/layout/animation/style/permissions in shared contracts and dynamic rendering path.
|
||||
- Converted remaining identified hardcoded UI strings in audited runtime pages/components to translation keys.
|
||||
|
||||
### Bootstrap/Configuration Gaps Identified
|
||||
|
||||
- Widget permission model supports auth gating but role/permission enforcement is limited by current auth session shape (no role list in session model).
|
||||
- Popular search defaults are currently facade-local and should be moved to bootstrap-configurable catalog search settings.
|
||||
- Storage key naming conventions for local persistence are platform-scoped but still static constants; optional bootstrap override could improve tenant isolation.
|
||||
|
||||
### Validation Baseline
|
||||
|
||||
- Build and architecture checks are required for acceptance of this sprint.
|
||||
- Final details and file-level changes are tracked in `Platform-Standardization-Report.md`.
|
||||
|
||||
## Mandatory Phase Order
|
||||
|
||||
Implementation must proceed only in this order:
|
||||
|
||||
1. Foundation structure only, app compiles.
|
||||
2. Shared interfaces and types only.
|
||||
3. Mocked configuration payloads only.
|
||||
4. ConfigService abstraction only.
|
||||
5. Theme engine.
|
||||
6. Dynamic rendering engine.
|
||||
7. Reusable widget library.
|
||||
8. Website from configuration.
|
||||
9. Builder from configuration domain.
|
||||
10. Backoffice from business domain.
|
||||
11. Backend documentation.
|
||||
|
||||
For each phase:
|
||||
|
||||
1. Explain what will be created.
|
||||
2. Explain why.
|
||||
3. List files to create.
|
||||
4. Explain dependencies.
|
||||
5. Implement only that phase.
|
||||
6. Verify build integrity.
|
||||
7. Stop and wait.
|
||||
@@ -1,567 +0,0 @@
|
||||
# Seller Management — Backend Migration Plan
|
||||
|
||||
Documentation only. No code, no implementation. This plan synthesizes the
|
||||
three prior audits — do not re-derive facts already established there,
|
||||
read them for detail:
|
||||
|
||||
- [Seller-Management.md](Seller-Management.md) — capability overview,
|
||||
Implemented/Planned/Future legend
|
||||
- [Seller-Management-Backoffice-Readiness-Audit.md](Seller-Management-Backoffice-Readiness-Audit.md)
|
||||
— per-admin-module scoping facts
|
||||
- [Seller-Management-Storefront-Audit.md](Seller-Management-Storefront-Audit.md)
|
||||
— storefront/subdomain facts
|
||||
- `BACKEND.md` §11 — the backend documentation this plan assumes as its
|
||||
starting contract
|
||||
|
||||
**Non-negotiable constraint repeated from the mission, and load-bearing for
|
||||
every classification below:** existing marketplaces without sellers must
|
||||
continue working exactly as today. Seller Management stays optional
|
||||
forever, not just at launch. No endpoint may require seller support unless
|
||||
`modules.sellerManagement.enabled` is true. Every classification and every
|
||||
migration strategy in this document is written to satisfy that constraint
|
||||
first — where a module can't satisfy it without a structural rework, that's
|
||||
called out explicitly, not glossed over.
|
||||
|
||||
## Endpoint classification legend
|
||||
|
||||
- **No change** — endpoint's request/response/behavior is untouched.
|
||||
- **Minor change** — an optional field/parameter added (e.g. `sellerId?` in
|
||||
a filters object or DTO); absent value behaves identically to today.
|
||||
- **Major change** — data model or endpoint semantics change beyond an
|
||||
optional field (e.g. a new join, a new required decision like split
|
||||
orders, a new aggregation dimension).
|
||||
- **New endpoint** — doesn't exist today, net-new surface.
|
||||
|
||||
## Module-by-module
|
||||
|
||||
### Authentication
|
||||
- **Current:** Two live mechanisms (`BACKEND.md` §4) — Telegram/QR
|
||||
customer session login (live), Ed25519 admin challenge/response (built
|
||||
client-side, not wired live). No seller concept in either.
|
||||
- **Future:** `SellerPermissionRole` (`marketplaceOwner`/`seller`/
|
||||
`sellerStaff`/`platformAdmin`) as a role a session can carry, and a
|
||||
decision on whether Seller/Seller Staff authenticate via a third
|
||||
mechanism or reuse Telegram/Ed25519 with a different claim.
|
||||
- **Migration strategy:** additive only — a new optional claim/role field
|
||||
on the existing JWT structure (§4 §3), never a new auth mechanism forced
|
||||
onto existing flows. Existing customer and admin login must not gain any
|
||||
new required step.
|
||||
- **Backward compatibility:** total, if done as an additive claim. A
|
||||
session with no seller claim today is indistinguishable from a session
|
||||
before this work existed.
|
||||
- **Risk:** **Medium** — auth is the most consequence-sensitive surface in
|
||||
the app (ADR-010 froze it once already); any change here needs
|
||||
security review, not just a schema add.
|
||||
- **Effort:** Medium (claim/role addition) once the role-vs-`AdminRole`
|
||||
reconciliation decision (§11.4) is made; that decision itself is the
|
||||
bigger unknown, not the wiring.
|
||||
- **Endpoint classification:** existing login/verify/refresh/logout —
|
||||
**Minor change** (optional claim), contingent on the role decision above.
|
||||
New seller-specific login/enrollment flow (if Seller/Seller Staff need
|
||||
one) — **New endpoint**, Future, not designed.
|
||||
|
||||
### Authorization
|
||||
- **Current:** Coarse `AdminRole` (Owner/Manager/Support/ReadOnly) mapped
|
||||
to `backoffice.read`/`backoffice.write`/`builder.read`/`builder.write`/
|
||||
`settings.manage` (§4 §9.1). No route currently enforces even this — the
|
||||
admin role model exists but nothing gates on it yet (confirmed by the
|
||||
Backoffice audit: "no admin module anywhere does role-based hiding of
|
||||
buttons or data today").
|
||||
- **Future:** `SellerPermissionRole` layered in, deciding whether it
|
||||
extends or sits alongside `AdminRole`.
|
||||
- **Migration strategy:** since authorization enforcement doesn't exist yet
|
||||
even for the current roles, there is no existing enforcement to break —
|
||||
this is genuinely additive design space, not a migration of live
|
||||
behavior.
|
||||
- **Backward compatibility:** trivial today (nothing to preserve because
|
||||
nothing enforces roles yet) but becomes a real compatibility question the
|
||||
moment `AdminRole` enforcement is eventually added — sequence matters:
|
||||
whichever role model ships first should be designed with the other in
|
||||
mind, or the second one becomes a breaking migration of the first.
|
||||
- **Risk:** **Low today, Medium if sequenced wrong** — the risk is entirely
|
||||
about doing `AdminRole` enforcement and `SellerPermissionRole` design in
|
||||
the wrong order, not about either individually.
|
||||
- **Effort:** Low to design (no live behavior to preserve); Medium to
|
||||
implement once both role systems' relationship is decided.
|
||||
- **Endpoint classification:** all admin endpoints — **No change** today
|
||||
(no enforcement exists to change); **Major change** whenever
|
||||
role-enforcement is added generally, seller-aware or not.
|
||||
|
||||
### Bootstrap
|
||||
- **Current:** `GET /bootstrap`, backend resolves tenant by Host (ADR-001,
|
||||
§1). `BootstrapConfig` already has optional `modules?`/`seller?` fields
|
||||
(§11.6), always absent/false — no backend populates them.
|
||||
- **Future:** backend resolving `{seller}.{marketplace-domain}` (§11.5) and
|
||||
populating `modules.sellerManagement.enabled` + `seller` for a real
|
||||
seller-scoped request.
|
||||
- **Migration strategy:** the fields already exist and are optional — the
|
||||
only backend work is *populating* them correctly for a resolved seller
|
||||
scope, not adding new frontend-facing shape. Existing marketplaces'
|
||||
bootstrap responses need zero changes; they simply never populate the
|
||||
new fields.
|
||||
- **Backward compatibility:** already verified — these are optional fields
|
||||
added to a live contract with zero consumer changes required (confirmed
|
||||
`tsc --noEmit` clean at the time they were added).
|
||||
- **Risk:** **Low** — this is the best-prepared module in the whole
|
||||
migration, precisely because the frontend contract was already extended
|
||||
in advance without needing backend cooperation.
|
||||
- **Effort:** Medium — the resolution logic (subdomain → seller lookup) is
|
||||
new backend work, but the response shape is already spoken for.
|
||||
- **Endpoint classification:** `GET /bootstrap` — **Minor change** (two new
|
||||
optional response fields to populate, conditionally).
|
||||
|
||||
### Products
|
||||
- **Current:** No real backend yet (§8) — mock gateway. `AdminProductListFilters`
|
||||
already has `search/categoryId/visibility/stock/includeArchived/sort/page/pageSize`.
|
||||
`Item`/`AdminProduct` already carry optional `sellerId?` (§11.8),
|
||||
read/written by nobody today.
|
||||
- **Future:** `sellerId` filter on list endpoints; ownership enforcement on
|
||||
create/update/delete once a real seller session exists.
|
||||
- **Migration strategy:** add `sellerId` as one more optional field to the
|
||||
existing filters object and DTOs — the field already exists in the
|
||||
frontend type, so there's no frontend change required at all, only
|
||||
backend query logic (`WHERE seller_id = ? OR seller_id IS NULL` pattern
|
||||
or equivalent) gated behind the feature flag.
|
||||
- **Backward compatibility:** guaranteed at the frontend type level
|
||||
already; backend guarantee depends on making the new column/filter
|
||||
nullable and defaulting existing rows to `NULL` (marketplace-owned) —
|
||||
see Database changes below.
|
||||
- **Risk:** **Low** for the filter addition; **Medium** for ownership
|
||||
*enforcement* (a bug here could hide a marketplace owner's own products
|
||||
from themselves, or leak a seller's products to another seller).
|
||||
- **Effort:** Small (list/filter) once the DI-token seam this domain
|
||||
currently lacks (per Backoffice audit) is added — that seam is
|
||||
independent prerequisite work, not seller-specific.
|
||||
- **Endpoint classification:** list/get — **Minor change**. Create/update
|
||||
(ownership assignment) — **Minor change** if `sellerId` is just an
|
||||
optional write field; **Major change** if ownership transfer or
|
||||
seller-side write restrictions are added.
|
||||
|
||||
### Categories
|
||||
- **Current:** The most backend-mature domain — real HTTP already
|
||||
(`AdminCategoriesApiGateway`, DI-token-bound, §3.2). Facade currently
|
||||
hardcodes a full unfiltered fetch and does all real filtering
|
||||
client-side to preserve tree parent/child chains (Backoffice audit
|
||||
finding).
|
||||
- **Future:** categories are conceptually marketplace-wide (shared
|
||||
taxonomy) — the real future question is scoping *products within* a
|
||||
category by seller, not scoping categories themselves.
|
||||
- **Migration strategy:** likely **no backend change at all** for
|
||||
Categories proper; ownership scoping happens one level down, at Products.
|
||||
- **Backward compatibility:** trivial — no change anticipated.
|
||||
- **Risk:** **Low.**
|
||||
- **Effort:** Low (frontend-only fix: the facade's hardcoded full-fetch
|
||||
pattern, unrelated to backend).
|
||||
- **Endpoint classification:** **No change** anticipated.
|
||||
|
||||
### Orders
|
||||
- **Current:** `AdminOrderListFilters` has `search/status/page/pageSize`;
|
||||
`AdminOrder` has optional `sellerId?` (§11.8) at the order level.
|
||||
`AdminOrderItem` (per-line-item) has **no seller attribution field at
|
||||
all** — the concrete gap behind Checkout Modes (§11.7).
|
||||
- **Future:** per-item seller attribution, plus the Unified-vs-Split-Orders
|
||||
decision (§11.7) — the single most consequential undecided item in this
|
||||
entire plan.
|
||||
- **Migration strategy:** cannot be resolved by an additive field alone.
|
||||
**Unified Order** path: add optional `sellerId` to each order-item row
|
||||
(minor, additive). **Split Orders** path: checkout must decide, at
|
||||
creation time, whether to write N order records instead of one for a
|
||||
multi-seller cart — that changes `POST /orders`' semantics for any cart
|
||||
containing mixed-seller items, which is a **major** change regardless of
|
||||
how carefully it's gated, because "how many order records does this
|
||||
checkout produce" is not optional-field-shaped.
|
||||
- **Backward compatibility:** guaranteed for any cart containing only
|
||||
marketplace-owned items (the only kind that exists today) under either
|
||||
path. The compatibility risk is entirely about *new* multi-seller carts,
|
||||
which cannot exist until Products have real seller ownership — so there
|
||||
is a natural sequencing safety net here, not just a promise.
|
||||
- **Risk:** **High** — this is the highest-risk item in the whole plan.
|
||||
Payments, refunds, and financial reporting all depend on the
|
||||
Unified-vs-Split decision; getting it wrong after sellers exist means a
|
||||
breaking change to live financial data, not a code refactor.
|
||||
- **Effort:** Large — this decision should be made and locked before any
|
||||
seller onboarding is possible, not discovered mid-rollout.
|
||||
- **Endpoint classification:** `GET /orders` (list/filter) — **Minor
|
||||
change**. `POST /orders` (create) — **Major change** (semantics change
|
||||
based on the Unified/Split decision). `PATCH` status transitions — likely
|
||||
**Minor change** if orders stay 1:1 with a single seller scope (Split
|
||||
path) or **Major change** if partial per-seller status exists within one
|
||||
unified order.
|
||||
|
||||
### Payments
|
||||
- **Current:** **Frozen** (ADR-010) — Telegram QR/card payment flow,
|
||||
`POST /cart` (`CartPaymentRequest` → `QrCreateResponse`), status polling.
|
||||
Explicitly preserved unchanged through every prior platform-refactoring
|
||||
pass in this codebase's history.
|
||||
- **Future:** if Split Orders is chosen (§ Orders above), a single checkout
|
||||
may need to create multiple payment records or split a captured payment
|
||||
across sellers — a genuinely new payment-flow shape, not a parameter
|
||||
addition.
|
||||
- **Migration strategy:** **do not touch the frozen flow for the Unified
|
||||
Order path** — a unified order with mixed-seller items can still use
|
||||
today's exact single-payment flow, only the *order* record differs
|
||||
internally. Only the Split Orders path forces any payment-flow change,
|
||||
and even then, the existing flow should remain the path for
|
||||
seller-disabled marketplaces with zero exceptions.
|
||||
- **Backward compatibility:** absolute requirement, restated from ADR-010 —
|
||||
this is the one area of the whole codebase with an explicit prior
|
||||
"freeze, do not redesign" decision, and Seller Management does not
|
||||
override it.
|
||||
- **Risk:** **High** if Split Orders is chosen — payment/financial flows
|
||||
are the least forgiving place for a design misstep in this entire system.
|
||||
- **Effort:** Large, only if Split Orders is chosen; **zero** for Unified
|
||||
Order.
|
||||
- **Endpoint classification:** **No change** under Unified Order. **Major
|
||||
change or New endpoint** under Split Orders (undecided, Future).
|
||||
|
||||
### Transactions
|
||||
- **Current:** Derived entirely from the same unscoped Orders list
|
||||
(`AdminTransactionsLocalGateway` mirrors `AdminOrdersLocalGateway`'s
|
||||
pattern, Backoffice audit finding). `AdminTransactionListFilters` has
|
||||
`search/status/type/page/pageSize`.
|
||||
- **Future:** per-seller transaction filtering/reporting.
|
||||
- **Migration strategy:** inherits whatever Orders decides — Transactions
|
||||
cannot be scoped correctly until Orders resolves per-item seller
|
||||
attribution (§ Orders above). Adding a `sellerId` filter here today would
|
||||
be cosmetic without that prerequisite.
|
||||
- **Backward compatibility:** same guarantee as Orders — blocked on the
|
||||
same prerequisite, not an independent risk.
|
||||
- **Risk:** **Medium** — inherits Orders' risk one level removed (financial
|
||||
reporting, not the payment flow itself).
|
||||
- **Effort:** Small once Orders is resolved; not independently schedulable
|
||||
before that.
|
||||
- **Endpoint classification:** **Minor change** (filter field), contingent
|
||||
on Orders' Major change landing first.
|
||||
|
||||
### Reviews
|
||||
- **Current:** Storefront submission is live; admin moderation
|
||||
(`AdminModerationGateway`) is mock-only. `AdminReviewListFilters` has
|
||||
`search/status/rating/page/pageSize`. Reviews carry `productId` — no
|
||||
direct seller field.
|
||||
- **Future:** a seller sees reviews on their own products, via a join
|
||||
through Products' `sellerId`, not a new field on the review itself.
|
||||
- **Migration strategy:** add `sellerId` as a filter that internally joins
|
||||
through the product's ownership — additive at the API surface, but
|
||||
implemented as a join, not a stored column on reviews.
|
||||
- **Backward compatibility:** total — the filter is purely additive and
|
||||
optional.
|
||||
- **Risk:** **Low.**
|
||||
- **Effort:** Small, contingent on Products having real `sellerId` data to
|
||||
join against.
|
||||
- **Endpoint classification:** review list/detail — **Minor change**.
|
||||
Reports (`loadReports()`, currently bare no-arg, no filters object at
|
||||
all) — **Minor change** too, but requires adding a filters parameter
|
||||
that doesn't exist on the endpoint today, so slightly more surface than
|
||||
the reviews list.
|
||||
|
||||
### Analytics
|
||||
- **Current:** No aggregation endpoint exists at all (§3.20) — every
|
||||
number (revenue, top products, health, recommendations) is computed
|
||||
client-side by summing the *entire* orders/products/categories/reviews
|
||||
corpus. This gap exists independent of Seller Management.
|
||||
- **Future:** per-seller analytics, requiring the underlying domains
|
||||
(Orders, Products, Reviews) to be seller-scoped first, then a real
|
||||
aggregation endpoint partitioned by seller.
|
||||
- **Migration strategy:** do not attempt to seller-scope analytics before
|
||||
a real aggregation endpoint exists for the marketplace as a whole — that
|
||||
is a prerequisite gap, not a Seller Management task. Once it exists,
|
||||
seller-partitioning is an additional dimension on the same aggregation
|
||||
query, not a new endpoint family.
|
||||
- **Backward compatibility:** unaffected — a marketplace with no sellers
|
||||
gets marketplace-wide aggregates exactly as it always has, whenever the
|
||||
real aggregation endpoint is eventually built.
|
||||
- **Risk:** **Medium** — mostly the risk of building the wrong aggregation
|
||||
shape before seller-partitioning is even a consideration, and having to
|
||||
redo it.
|
||||
- **Effort:** Large — this is explicitly the last domain recommended for
|
||||
real backend work in `BACKEND.md` §9, and Seller Management adds a
|
||||
further dimension on top of an already-large lift.
|
||||
- **Endpoint classification:** **New endpoint** (the aggregation endpoint
|
||||
itself doesn't exist yet, seller-aware or not).
|
||||
|
||||
### Media
|
||||
- **Current:** `MediaRepository` abstract class, DI-token-bound already
|
||||
(mock live, `ApiMediaRepository` not yet written). `MediaListParams`
|
||||
already accepts `page/pageSize/search/folder/kind/sort`. `MediaAsset` has
|
||||
no owner/uploader field at all.
|
||||
- **Future:** `sellerId` filter, requiring an owner field added to
|
||||
`MediaAsset` first.
|
||||
- **Migration strategy:** two small additive changes — a new optional
|
||||
`sellerId`/`ownerId` field on the asset model, and a matching optional
|
||||
filter field on `MediaListParams`. This module and Categories are the
|
||||
two best-positioned in the entire audit for a low-risk addition.
|
||||
- **Backward compatibility:** total — both changes are optional-field
|
||||
additions to an already-flexible params object.
|
||||
- **Risk:** **Low.**
|
||||
- **Effort:** Small.
|
||||
- **Endpoint classification:** list — **Minor change**. Upload/update
|
||||
(§7) — **Minor change** (one new optional field in the request body).
|
||||
|
||||
### Search
|
||||
- **Current:** Standard search contract exists (§2.5) as a framework
|
||||
convention (keyword, no dedicated backend endpoint beyond catalog list
|
||||
filtering — `/search` reuses the same catalog container/endpoint as
|
||||
`/catalog` per the Storefront audit). No autocomplete/trending endpoint
|
||||
exists; explicitly flagged as its own open "Requires backend decision"
|
||||
in §2.12.
|
||||
- **Future:** filtering search results by seller scope, same mechanism as
|
||||
Products (search is not architecturally distinct from catalog browsing
|
||||
today).
|
||||
- **Migration strategy:** inherits Products' migration entirely — no
|
||||
separate search-specific backend work anticipated beyond what Products
|
||||
already needs.
|
||||
- **Backward compatibility:** total, same guarantee as Products.
|
||||
- **Risk:** **Low.**
|
||||
- **Effort:** None beyond Products — not independently schedulable.
|
||||
- **Endpoint classification:** **Minor change**, identical to Products'
|
||||
classification (same underlying endpoint).
|
||||
|
||||
### CMS (Static Pages)
|
||||
- **Current:** No backend call at all — reads/writes
|
||||
`BootstrapConfig.staticPages` in-memory (§1, §3.8). Marketplace-wide
|
||||
content (legal, about) by nature.
|
||||
- **Future:** **conceptually does not apply** — no per-seller static page
|
||||
concept exists in this document or in product intent. If sellers ever
|
||||
need their own content pages, that is new product surface, not an
|
||||
extension of this module.
|
||||
- **Migration strategy:** none anticipated. Flag if product strategy later
|
||||
decides otherwise — treat as a new capability, not a CMS migration.
|
||||
- **Backward compatibility:** unaffected — no change proposed.
|
||||
- **Risk:** **None.**
|
||||
- **Effort:** **None.**
|
||||
- **Endpoint classification:** **No change.**
|
||||
|
||||
### Builder
|
||||
- **Current:** No backend write path at all (§1.10, §8) — draft/publish is
|
||||
`localStorage`-only, editing one global `BootstrapConfig` document per
|
||||
marketplace. The single strongest one-owner assumption in the codebase
|
||||
(Backoffice audit finding).
|
||||
- **Future:** a seller-scoped builder (per-seller storefront layout/
|
||||
branding) would be an entirely new product surface built on a different
|
||||
premise than "one config document per marketplace" — not an extension of
|
||||
the existing builder.
|
||||
- **Migration strategy:** none proposed for the existing Builder. If a
|
||||
seller-facing builder is ever wanted, it should be scoped as its own ADR
|
||||
and its own backend surface, not bolted onto the marketplace Builder's
|
||||
existing draft/publish contract.
|
||||
- **Backward compatibility:** unaffected — no change proposed to the
|
||||
existing module.
|
||||
- **Risk:** **None** for the existing module; **High** if a future team
|
||||
attempts to retrofit seller-awareness into the existing single-document
|
||||
model rather than building fresh — flagged explicitly as an
|
||||
anti-pattern to avoid.
|
||||
- **Effort:** **None** now; a seller-facing builder, if ever built, is a
|
||||
large, independent effort, not a migration of this one.
|
||||
- **Endpoint classification:** **No change** to the existing (still
|
||||
nonexistent) builder write path. Any future seller-builder surface is
|
||||
**New endpoint**, Future, not designed.
|
||||
|
||||
### Settings
|
||||
- **Current:** No route exists — `comingSoon: true` in the admin nav, no
|
||||
component backs it (confirmed, Backoffice audit).
|
||||
- **Future:** if Settings is ever built, it's the natural home for
|
||||
marketplace-level Seller Management configuration (e.g. enabling the
|
||||
module, default seller policies) — speculative, not designed.
|
||||
- **Migration strategy:** none — there's nothing to migrate.
|
||||
- **Backward compatibility:** not applicable.
|
||||
- **Risk:** **None.**
|
||||
- **Effort:** **None** attributable to Seller Management specifically.
|
||||
- **Endpoint classification:** **No change** — no endpoint exists.
|
||||
|
||||
### Notifications
|
||||
- **Current:** A marketplace-facing feature flag exists
|
||||
(`FeatureFlagsConfig.notifications: boolean`) but this gates a storefront
|
||||
UI feature, not a backend notification/email service — no such service
|
||||
exists in this codebase for anyone today.
|
||||
- **Future:** seller onboarding (invitation accepted, application
|
||||
approved/rejected) would need real notification delivery — entirely
|
||||
net-new, not an extension of the existing flag.
|
||||
- **Migration strategy:** none — nothing exists to migrate.
|
||||
- **Backward compatibility:** not applicable.
|
||||
- **Risk:** **Low** (net-new work carries normal build risk, not migration
|
||||
risk).
|
||||
- **Effort:** Medium, whenever built — but zero today and not a
|
||||
prerequisite for anything else in this plan.
|
||||
- **Endpoint classification:** **New endpoint**, Future, not designed.
|
||||
|
||||
### Emails
|
||||
- **Current:** No email-sending capability exists anywhere in this
|
||||
codebase.
|
||||
- **Future:** the mocked "Request Access" form on the Phase 1 UI
|
||||
(`admin-seller-management-page.component.ts`) implies a real email would
|
||||
eventually be sent on submission — today it's a `setTimeout`-mocked
|
||||
success dialog with zero delivery.
|
||||
- **Migration strategy:** none — nothing exists to migrate.
|
||||
- **Backward compatibility:** not applicable.
|
||||
- **Risk:** **Low** (net-new, not migration).
|
||||
- **Effort:** Small to Medium, whenever built (transactional email for one
|
||||
form submission is a contained scope).
|
||||
- **Endpoint classification:** **New endpoint**, Future, not designed.
|
||||
|
||||
### Audit Logs
|
||||
- **Current:** Documented in `BACKEND.md` §5.14 as a proposed convention
|
||||
(structured audit-log entries with actor/action/target/timestamp) — no
|
||||
backend implementation confirmed to exist; this is itself already
|
||||
Planned/Future in the base backend doc, independent of Seller
|
||||
Management.
|
||||
- **Future:** any seller-specific action (seller created, activated,
|
||||
suspended, branding changed) should flow through the same audit-log
|
||||
convention once it exists, with an added seller-scope dimension on the
|
||||
log entry — not a parallel logging system.
|
||||
- **Migration strategy:** none specific to Seller Management beyond
|
||||
ensuring, whenever audit logging is built, that its schema includes an
|
||||
optional seller-scope field from day one rather than retrofitting it
|
||||
later.
|
||||
- **Backward compatibility:** not applicable — nothing exists yet to
|
||||
preserve.
|
||||
- **Risk:** **Low.**
|
||||
- **Effort:** None additional if sequenced correctly (add the field when
|
||||
audit logging is first built); Medium if audit logging ships first
|
||||
without it and needs a schema migration later.
|
||||
- **Endpoint classification:** **New endpoint** (audit log query surface,
|
||||
if one is ever exposed to admins) — Future, not designed.
|
||||
|
||||
## Database changes
|
||||
|
||||
No schema exists yet for any of this (§8 — no backend implemented at all).
|
||||
Recommendations, not decisions:
|
||||
|
||||
- A `sellers` table (id, marketplace_id, name, slug, status, branding
|
||||
JSON/columns, timestamps) — new table, no impact on existing schema.
|
||||
- A nullable `seller_id` foreign key on `products`/`items` and `orders` (or
|
||||
order-items, pending the Unified/Split decision) — nullable by
|
||||
design, defaulting existing rows to `NULL` (marketplace-owned). This is
|
||||
the one schema change that touches existing tables; every other addition
|
||||
is a new table.
|
||||
- A nullable `seller_id`/`owner_id` on the media-assets table, if Media
|
||||
ownership scoping is pursued.
|
||||
- No column should ever be added as `NOT NULL` without a default — that
|
||||
would force a backfill migration on existing data, which this plan's
|
||||
constraint explicitly rules out.
|
||||
|
||||
## Permission changes
|
||||
|
||||
- New `sellers` and `seller_users` (or equivalent) authorization checks —
|
||||
entirely new policy, not a modification of existing `AdminRole` checks
|
||||
(which, per the Backoffice audit, don't enforce anything yet regardless).
|
||||
- Existing admin/customer authorization paths: **no change** required for
|
||||
marketplaces with `modules.sellerManagement.enabled = false`.
|
||||
- Recommendation: implement seller-scoped authorization as an additional
|
||||
policy layer evaluated only when a request resolves to a seller scope
|
||||
(§11.5) — never as a modification of the existing marketplace-level
|
||||
policy evaluation path.
|
||||
|
||||
## Caching
|
||||
|
||||
- Bootstrap responses are already cached client-side per tenant (§1.7); a
|
||||
seller-scoped bootstrap response should be cached under a cache key that
|
||||
includes the resolved seller identity (e.g. keyed by full resolved Host,
|
||||
which it already effectively is), not the marketplace alone — otherwise
|
||||
a seller subdomain risks serving a cached marketplace-level response or
|
||||
vice versa.
|
||||
- No existing cache invalidation logic needs to change for marketplaces
|
||||
without sellers.
|
||||
|
||||
## Indexes
|
||||
|
||||
- `sellers(marketplace_id)` — every seller lookup is scoped by marketplace.
|
||||
- `products(seller_id)` / `orders(seller_id)` (or order-items equivalent)
|
||||
— nullable-column indexes; most databases index `NULL` efficiently, but
|
||||
this should be verified against the chosen database engine before
|
||||
assuming query performance is unaffected for the common (`NULL`) case.
|
||||
- No index changes required on any table for marketplaces that never
|
||||
populate `seller_id`.
|
||||
|
||||
## Security
|
||||
|
||||
- Seller-scoped data access is a new cross-tenant-adjacent boundary (a
|
||||
seller must never see another seller's data within the same
|
||||
marketplace) — recommend treating this with the same rigor as tenant
|
||||
isolation (§5.10/§4 §10), not as a lesser internal boundary.
|
||||
- The existing frozen payment flow (ADR-010) must not be reopened for the
|
||||
Unified Order path — only the Split Orders path (if chosen) touches
|
||||
payment security surface at all, and that touch should get the same
|
||||
security review rigor as the original payment implementation.
|
||||
|
||||
## Performance
|
||||
|
||||
- Analytics is already the heaviest computation path in the app (full
|
||||
client-side aggregation over the entire corpus, §3.20) — building the
|
||||
real aggregation endpoint this plan's Analytics section calls for should
|
||||
happen with seller-partitioning in mind from the start, to avoid a
|
||||
second heavy migration shortly after the first.
|
||||
- No performance regression is anticipated for marketplaces without
|
||||
sellers — every proposed change is either a new table/endpoint (zero
|
||||
cost when unused) or a nullable-field filter (negligible cost when the
|
||||
filter is never applied).
|
||||
|
||||
## API Versioning
|
||||
|
||||
Already documented as an open, undecided item in `BACKEND.md` §2.10 (no
|
||||
path/header versioning scheme exists today). Recommendation specific to
|
||||
this migration: whichever versioning decision is made generally should
|
||||
land **before** any Seller Management endpoint ships, so new endpoints
|
||||
(Seller CRUD, Activation, Invitations, Branding, Analytics, Dashboard) are
|
||||
versioned consistently with the rest of the API from their first day,
|
||||
rather than being retrofitted later as the one inconsistent set.
|
||||
|
||||
## Migration order
|
||||
|
||||
Strict dependency order, not a preference:
|
||||
|
||||
1. **Bootstrap** (already prepared, lowest risk) — backend starts
|
||||
populating `modules`/`seller` fields for a resolved seller scope.
|
||||
2. **Products** — `seller_id` column + filter, the foundation every
|
||||
downstream domain depends on.
|
||||
3. **Categories** — confirm no change needed (likely true, low effort to
|
||||
verify).
|
||||
4. **Media** — independent, can happen in parallel with Products.
|
||||
5. **Reviews** — depends on Products (join through product ownership).
|
||||
6. **Orders** — the Unified-vs-Split-Orders decision must be made here,
|
||||
informed by Products already existing. This is the hard gate — nothing
|
||||
past this point should start before it's resolved.
|
||||
7. **Payments** — only touched if Split Orders is chosen; otherwise
|
||||
untouched, in parallel with everything above.
|
||||
8. **Transactions** — depends on Orders.
|
||||
9. **Analytics** — depends on Orders, Products, Reviews all being scoped;
|
||||
also depends on the pre-existing (non-seller-specific) real aggregation
|
||||
endpoint being built first.
|
||||
10. **Authentication/Authorization** — the `SellerPermissionRole` design
|
||||
and its relationship to `AdminRole` should be settled in parallel with
|
||||
steps 2-6, not deferred to the end, since Seller Activation and any
|
||||
real seller-facing endpoint depend on it.
|
||||
11. **Seller CRUD/Activation/Invitations/Branding** — depends on
|
||||
Authentication/Authorization being settled.
|
||||
12. **Seller Dashboard/Analytics endpoints** — last, depends on Analytics'
|
||||
real aggregation endpoint existing.
|
||||
13. **Notifications/Emails/Audit Logs** — can start any time after step 11
|
||||
(Seller Invitations existing gives them something to notify about);
|
||||
not blocking anything else.
|
||||
14. **CMS, Builder, Settings** — no migration anticipated; revisit only if
|
||||
product strategy changes.
|
||||
|
||||
## Recommended implementation phases
|
||||
|
||||
- **Phase A — Foundation** (steps 1-4, 10 above): bootstrap population,
|
||||
Products/Categories/Media scoping, and the permission-role design done
|
||||
in parallel. Nothing customer-visible yet. Lowest risk, unblocks
|
||||
everything else.
|
||||
- **Phase B — The hard decision** (step 6, Orders): Unified-vs-Split-Orders
|
||||
locked before any real seller can transact. This phase is a design
|
||||
decision plus its implementation, not a feature — treat it as a gate,
|
||||
not a sprint item.
|
||||
- **Phase C — Dependent domains** (steps 5, 7, 8): Reviews, Payments (if
|
||||
Split chosen), Transactions — mechanical once Phase B lands.
|
||||
- **Phase D — Seller-facing surface** (step 11): Seller CRUD, Activation,
|
||||
Invitations, Branding — the first point where a seller account can
|
||||
actually exist and do something.
|
||||
- **Phase E — Analytics & reporting** (steps 9, 12): the largest single
|
||||
remaining lift, deliberately last since it depends on everything above.
|
||||
- **Phase F — Operational polish** (step 13): Notifications, Emails, Audit
|
||||
Logs — improves the experience of Phase D/E but blocks nothing.
|
||||
|
||||
At every phase boundary: re-verify the non-negotiable constraint. A
|
||||
marketplace with `modules.sellerManagement.enabled = false` must show zero
|
||||
behavioral difference before and after each phase ships. If a phase can't
|
||||
satisfy that, it isn't ready to ship — regardless of how much of it is
|
||||
"done."
|
||||
@@ -1,319 +0,0 @@
|
||||
# Seller Management — Backoffice Readiness Audit
|
||||
|
||||
Audit only. **No code changed by this pass** — every fact below was gathered
|
||||
by reading current source (facades, gateway interfaces, local
|
||||
implementations, components) on `feature/seller-management-foundation`, not
|
||||
inferred or assumed. Companion to
|
||||
[Seller-Management.md](Seller-Management.md) §5 (Roles & Permissions) and §6
|
||||
(Seller Ownership).
|
||||
|
||||
## How to read this
|
||||
|
||||
For each module: three readiness questions, then **where** a future scope
|
||||
would be injected (not "if seller" conditionals — an injection point in an
|
||||
existing method signature or facade call), then components that currently
|
||||
assume there is exactly one owner of the whole dataset, then a
|
||||
classification.
|
||||
|
||||
**Classification legend:**
|
||||
- **Ready** — either already scope-injectable with no structural change, or
|
||||
conceptually marketplace-wide data that a seller scope shouldn't apply to
|
||||
at all.
|
||||
- **Needs scope** — a filters object or DI-token seam already exists; adding
|
||||
a scope field is additive, not structural.
|
||||
- **Needs permissions** — the open question is *who sees this at all*
|
||||
(Marketplace Owner vs Seller vs Seller Staff vs Platform Admin), not data
|
||||
filtering.
|
||||
- **Needs API change** — the method signature itself has no parameter to
|
||||
extend (bare no-arg calls), the data model has no owner attribution field
|
||||
to filter on, or the module's whole premise assumes one global record.
|
||||
|
||||
Repo-wide baseline established by this audit: **only Categories, Dashboard-
|
||||
metrics, and Media have a DI-token-swappable gateway today** (per
|
||||
`BACKEND.md` §8's pattern — interface + `*LocalGateway` + `*ApiGateway` +
|
||||
`InjectionToken`). Every other admin domain's facade injects its
|
||||
`*LocalGateway` concretely; that seam has to be added before any real
|
||||
scoping work lands, independent of the scoping question itself. No module
|
||||
currently does role-based hiding of any button or data — Users displays
|
||||
role/permission *labels* only, nothing gates on them.
|
||||
|
||||
## Module-by-module
|
||||
|
||||
### Dashboard
|
||||
- Owner sees everything? Yes — today's only mode.
|
||||
- Seller sees only their own? Not possible today — `loadMetrics()` has no
|
||||
parameters at all.
|
||||
- Seller Staff limited? Undetermined — no permission model touches this yet.
|
||||
- **Injection point:** `AdminDashboardMetricsGateway.loadMetrics()` would
|
||||
need a new parameter (interface change, not a filters-object field —
|
||||
there is no object to extend).
|
||||
- **Assumes global ownership:** `admin-dashboard-metrics.local.gateway.ts`
|
||||
computes `categoriesCount`/`productsCount` as raw `.length` over the
|
||||
entire catalog.
|
||||
- **Classification: Needs API change.**
|
||||
|
||||
### Products
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Not yet, but the shape is close — filters
|
||||
object already exists.
|
||||
- Seller Staff limited? Undetermined.
|
||||
- **Injection point:** `AdminProductListFilters` (already has
|
||||
`search/categoryId/visibility/stock/includeArchived/sort/page/pageSize`)
|
||||
— an optional `sellerId` field slots in next to the existing ones; one
|
||||
more `.filter()` line in `AdminProductsLocalGateway`.
|
||||
- **Assumes global ownership:** `loadDashboardStats()` calls
|
||||
`loadProducts({page:1, pageSize:100000, includeArchived:true, ...})` to
|
||||
sum stock/health stats with no per-seller split.
|
||||
- **Classification: Needs scope** (small, once the DI-token seam this
|
||||
module still lacks is added — see baseline note above).
|
||||
|
||||
### Categories
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Categories are conceptually a **shared,
|
||||
marketplace-wide taxonomy** — the real future question is "which products
|
||||
in category X belong to seller Y," not "which categories belong to seller
|
||||
Y." Likely Owner-only editable regardless of seller scope.
|
||||
- Seller Staff limited? Undetermined.
|
||||
- **Injection point:** `AdminCategoryListFilters` already has
|
||||
`search/visibility/includeDeleted` and the gateway is already DI-token-
|
||||
swappable (`ADMIN_CATEGORIES_GATEWAY`) — **but** the facade's `loadList()`
|
||||
currently calls the gateway with a hardcoded
|
||||
`{search:'', visibility:'all', includeDeleted:true}` and does all real
|
||||
filtering client-side in `filteredCategories()`/`visibleTreeRows()` to
|
||||
preserve tree parent/child chains. A `sellerId` filter passed to the
|
||||
gateway would be silently bypassed unless this hardcoded call is updated
|
||||
too.
|
||||
- **Assumes global ownership:** `loadDashboardStats()` sums the whole
|
||||
category tree.
|
||||
- **Classification: Needs scope** — gateway/interface layer is Ready, the
|
||||
facade's full-fetch-then-client-filter pattern is the actual gap.
|
||||
|
||||
### Orders
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? **Not modeled at all** — `AdminOrderItem` has
|
||||
no seller/vendor attribution field; a multi-vendor order (one order,
|
||||
items from several sellers) has no representation today. This is the
|
||||
concrete blocker behind the Unified-vs-Split-Orders open question in
|
||||
`Seller-Management.md` §6.
|
||||
- Seller Staff limited? Undetermined.
|
||||
- **Injection point:** `AdminOrderListFilters` has `search/status/page/
|
||||
pageSize` — a `sellerId` field is syntactically cheap to add, but
|
||||
filtering by it means nothing until orders/order-items carry seller
|
||||
attribution in the data model.
|
||||
- **Assumes global ownership:** `loadDashboardStats()` fetches
|
||||
`pageSize:100000` and sums orders/revenue/customers globally; this same
|
||||
full-fetch feeds Customers, Transactions, and Analytics (see below),
|
||||
compounding the single-owner assumption across four modules.
|
||||
- **Classification: Needs API change** — the data-model gap (per-item
|
||||
seller attribution, unified-vs-split decision) is the real blocker, not
|
||||
the filter object.
|
||||
|
||||
### Customers
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Not possible today — "customer" is a
|
||||
**derived aggregate** grouping the full order list by email in-memory;
|
||||
there is no first-class Customers gateway to add a filter to at all.
|
||||
- Seller Staff limited? Undetermined.
|
||||
- **Injection point:** none exists yet. Either (a) derive from an
|
||||
already-scoped Orders call once Orders itself supports `sellerId`, or (b)
|
||||
introduce a first-class `AdminCustomersGateway` — `BACKEND.md` already
|
||||
recommends the latter independent of Seller Management.
|
||||
- **Assumes global ownership:** `buildCustomers()` groups the entire
|
||||
unscoped order list; hardcoded `pageSize:100000` fetch, "search" is
|
||||
client-side only.
|
||||
- **Classification: Needs API change.**
|
||||
|
||||
### Users
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Not modeled — `loadUsers()` is bare no-arg,
|
||||
no filters object exists to extend.
|
||||
- Seller Staff limited? **This is where the answer will actually live** —
|
||||
`AdminUser` already carries an `AdminUserScope` field (`'marketplace' |
|
||||
'office'` in seed data) and an `AdminRole` with a permission-array shape.
|
||||
This is the closest existing hook to the future `SellerPermissionRole`
|
||||
vocabulary (`marketplaceOwner`/`seller`/`sellerStaff`/`platformAdmin`,
|
||||
`Seller-Management.md` §5) — but today it's used for labels only
|
||||
(`roleLabel`/`permissionLabel` in the page component), nothing gates
|
||||
actions or visibility on it anywhere in the app.
|
||||
- **Injection point:** `loadUsers()` needs a parameter added (interface
|
||||
change — no object to extend); `AdminUserScope` is the natural place a
|
||||
seller-scope value would eventually live.
|
||||
- **Assumes global ownership:** `loadAll()` fetches every user
|
||||
unconditionally, no per-seller user set exists.
|
||||
- **Classification: Needs API change** (data fetch) **+ Needs permissions**
|
||||
(this module is the eventual home of the Marketplace Owner / Seller /
|
||||
Seller Staff / Platform Admin distinction — right now it's purely
|
||||
informational).
|
||||
|
||||
### Analytics
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Not possible today — every number (revenue,
|
||||
top products, health, recommendations) is summed across the *entire*
|
||||
orders+products+categories+reviews corpus with no per-owner dimension
|
||||
anywhere, and there's no gateway of its own to add a filter to (it
|
||||
composes five other gateways/facades directly).
|
||||
- Seller Staff limited? Undetermined.
|
||||
- **Injection point:** none today — depends entirely on Orders/Products/
|
||||
Categories/Moderation each supporting `sellerId` first, then every one of
|
||||
this facade's ~6 nested subscribe calls would need the field passed
|
||||
through, plus every aggregate builder (`buildSeries`, `buildTopProducts`,
|
||||
`buildCustomerAnalytics`) reworked to partition by seller instead of
|
||||
summing globally.
|
||||
- **Assumes global ownership:** the strongest case in the audit — literally
|
||||
every displayed number.
|
||||
- **Classification: Needs API change** — `BACKEND.md` already independently
|
||||
flags this as the last domain to get a real backend; Seller Management
|
||||
scoping compounds on top of that, not ahead of it.
|
||||
|
||||
### Reviews / Moderation
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Reviews carry `productId`/`productName` — a
|
||||
seller would see reviews on *their own products*, which means joining
|
||||
through product ownership (once Products has `sellerId`), not a direct
|
||||
seller field on the review itself. Reports (`loadReports()`) has no
|
||||
filters object at all today, separate code path from reviews.
|
||||
- Seller Staff limited? Undetermined.
|
||||
- **Injection point:** `AdminReviewListFilters`
|
||||
(`search/status/rating/page/pageSize`) — cheap field add, correctness
|
||||
depends on a product-ownership join. `loadReports()` needs a signature
|
||||
change first (no params exist).
|
||||
- **Assumes global ownership:** `loadDashboardStats()` sums the full review
|
||||
queue with `pageSize:100000`, no per-product/per-seller split.
|
||||
- **Classification: Needs scope** (reviews, small, blocked on Products)
|
||||
**+ Needs API change** (reports, no params today).
|
||||
|
||||
### Media
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Not modeled — `MediaAsset` has no
|
||||
uploader/owner field at all (`filename/mimeType/size/tags/folder` only);
|
||||
"folder" is the closest existing scoping primitive, used generically
|
||||
today.
|
||||
- Seller Staff limited? Undetermined.
|
||||
- **Injection point:** `MediaListParams` (`page/pageSize/search/folder/
|
||||
kind/sort`) is already a rich optional-params object — a `sellerId` field
|
||||
is a cheap addition, and the repository is already DI-token-bound
|
||||
(`MediaRepository` abstract class, mock vs API swap already established).
|
||||
`MediaAsset` itself needs an owner field added for the filter to mean
|
||||
anything.
|
||||
- **Assumes global ownership:** none beyond the missing owner field itself.
|
||||
- **Classification: Needs scope** (small — best-positioned module in the
|
||||
audit alongside Categories).
|
||||
|
||||
### CMS / Static Pages
|
||||
- Owner sees everything? Yes — and likely always will.
|
||||
- Seller sees only their own? **Conceptually doesn't apply.** Static pages
|
||||
(legal, about, etc.) are inherently marketplace-wide; there is no
|
||||
per-seller "static page" concept in the domain, and no fetch method
|
||||
exists to add a filter to in the first place — this module reads/writes
|
||||
`BootstrapConfig.staticPages` in-memory, no gateway, no HTTP call
|
||||
(confirmed by `BACKEND.md`: "has no backend call today").
|
||||
- Seller Staff limited? Not applicable.
|
||||
- **Injection point:** none — would require inventing an entirely new data
|
||||
source, not adding a filter to an existing one.
|
||||
- **Assumes global ownership:** the whole module's premise, but
|
||||
appropriately so — this is marketplace-wide content by nature.
|
||||
- **Classification: Ready** — no scoping work belongs here; flag if product
|
||||
strategy later decides sellers need their own static pages, which is a
|
||||
new capability, not a gap in this one.
|
||||
|
||||
### Builder / Project Editor
|
||||
- Owner sees everything? Yes — the entire module edits one global
|
||||
`BootstrapConfig` document.
|
||||
- Seller sees only their own? Not modeled, and not a filter question at
|
||||
all — there is no `load*`/`list*` gateway method anywhere in this module
|
||||
to extend; `save()`/`publish()`/`resetDraft()` all operate on the single
|
||||
in-memory config object, with no backend write path yet either
|
||||
(`BACKEND.md`: "no client write call exists today").
|
||||
- Seller Staff limited? Not applicable at this structural level.
|
||||
- **Injection point:** none exists. A seller-scoped builder (per-seller
|
||||
storefront layout/branding) would be a **different product concept**, not
|
||||
an extension of this module's current single-document model.
|
||||
- **Assumes global ownership:** total and structural — the single strongest
|
||||
one-owner assumption in the codebase.
|
||||
- **Classification: Needs API change** — biggest structural gap in the
|
||||
audit if seller-level storefront customization is ever wanted; this is
|
||||
new work, not scoping.
|
||||
|
||||
### Settings
|
||||
No route exists — `admin-nav.model.ts` marks it `comingSoon: true`, no
|
||||
component backs it. **Skipped, nothing to audit.**
|
||||
|
||||
### Monitoring
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Likely **shouldn't** — events/queues/webhooks
|
||||
(logins, API health, queue depth) are platform-operational data. A seller
|
||||
has no legitimate reason to see other users' login events or system
|
||||
queue depth regardless of any future scoping.
|
||||
- Seller Staff limited? Same reasoning — this looks like a Marketplace
|
||||
Owner / Platform Admin-only surface once roles exist, not something a
|
||||
Seller role should reach at all.
|
||||
- **Injection point:** `AdminMonitoringEventFilters` (`category/search`)
|
||||
exists for events; `loadQueues()`/`loadWebhooks()` are bare no-arg.
|
||||
- **Assumes global ownership:** appropriately so — this is genuinely
|
||||
system-wide data.
|
||||
- **Classification: Needs permissions** — the open question is route-level
|
||||
visibility per role, not data filtering.
|
||||
|
||||
### Transactions
|
||||
- Owner sees everything? Yes.
|
||||
- Seller sees only their own? Not modeled — transactions are derived 1:1
|
||||
from the same unscoped global order list as Customers, inheriting the
|
||||
same seller-attribution gap.
|
||||
- Seller Staff limited? Undetermined.
|
||||
- **Injection point:** `AdminTransactionListFilters`
|
||||
(`search/status/type/page/pageSize`) exists — cheap field syntactically,
|
||||
but real correctness is blocked on Orders resolving per-item seller
|
||||
attribution first.
|
||||
- **Assumes global ownership:** derives wholesale from
|
||||
`AdminOrdersLocalGateway`, same pattern as Customers.
|
||||
- **Classification: Needs scope** (small syntactically, blocked on Orders'
|
||||
**Needs API change** classification for real correctness).
|
||||
|
||||
## Summary table
|
||||
|
||||
| Module | Classification |
|
||||
|---|---|
|
||||
| Dashboard | Needs API change |
|
||||
| Products | Needs scope |
|
||||
| Categories | Needs scope |
|
||||
| Orders | Needs API change |
|
||||
| Customers | Needs API change |
|
||||
| Users | Needs API change + Needs permissions |
|
||||
| Analytics | Needs API change |
|
||||
| Reviews / Moderation | Needs scope (reviews) + Needs API change (reports) |
|
||||
| Media | Needs scope |
|
||||
| CMS / Static Pages | Ready (not applicable) |
|
||||
| Builder / Project Editor | Needs API change |
|
||||
| Settings | N/A — no route |
|
||||
| Monitoring | Needs permissions |
|
||||
| Transactions | Needs scope (blocked on Orders) |
|
||||
|
||||
**Nothing in this repo is currently classified "Ready" for actual seller
|
||||
data scoping** — CMS/Static Pages is "Ready" only in the sense that it
|
||||
correctly needs no scoping at all. Categories and Media are the
|
||||
best-positioned modules for a future scope field (filters object + DI seam
|
||||
either fully or mostly in place already). Orders is the load-bearing
|
||||
blocker — Customers, Transactions, and half of Analytics all derive from
|
||||
it, so its data-model gap (no per-item seller attribution, unified-vs-split
|
||||
undecided) should be resolved before scoping any of its three dependents.
|
||||
|
||||
## Cross-cutting findings
|
||||
|
||||
- **No admin module anywhere does role-based hiding of buttons or data
|
||||
today.** Users is the only module with a role *concept* in its data
|
||||
(`AdminRole`, permission arrays) and even there it's label-only.
|
||||
- **Only 3 of 13 audited gateways are DI-token-swappable today**
|
||||
(Categories, Dashboard-metrics, Media) — everything else needs that seam
|
||||
added before any scoping work, independent of Seller Management.
|
||||
- **The heaviest single dependency chain**: Orders → Customers,
|
||||
Transactions, and Analytics all derive from the same unscoped, full-fetch
|
||||
order list. Fixing Orders' data model is the one change with the largest
|
||||
downstream effect.
|
||||
- **Two modules are structurally not about data scoping at all**: CMS/
|
||||
Static Pages (marketplace-wide by nature) and Builder/Project Editor
|
||||
(single global document, no query surface) — seller-level work here
|
||||
would be new product surface, not an extension.
|
||||
- **No "if seller" conditional exists anywhere in the codebase** — this
|
||||
audit deliberately did not introduce any. Every injection point above is
|
||||
described as a parameter/field addition to an existing method or
|
||||
interface, never a runtime branch.
|
||||
@@ -1,72 +0,0 @@
|
||||
# Seller Management — Architecture Diagrams
|
||||
|
||||
Companion diagrams for [ADR-011](adr/ADR-011-optional-seller-management-module.md).
|
||||
Architecture only — no UI, no backend, no business logic exists yet.
|
||||
|
||||
## 1. Hierarchy
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Platform["Platform<br/>(one Angular runtime)"]
|
||||
Marketplace["Marketplace (tenant)<br/>always present · backend-resolved from Host<br/>ADR-001"]
|
||||
SellerA["Seller A<br/>optional, 0..N"]
|
||||
SellerB["Seller B<br/>optional, 0..N"]
|
||||
NoSeller["No sellers<br/>(default — most marketplaces today)"]
|
||||
|
||||
Platform --> Marketplace
|
||||
Marketplace --> SellerA
|
||||
Marketplace --> SellerB
|
||||
Marketplace -.default state.-> NoSeller
|
||||
```
|
||||
|
||||
Marketplace is the only primary tenant. Seller is a child scope of exactly
|
||||
one marketplace — never a sibling tier, never resolved on its own.
|
||||
|
||||
## 2. Bootstrap module gate
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Request["GET /bootstrap"] --> Backend["Backend resolves:<br/>tenant (always)<br/>seller (only if applicable)"]
|
||||
Backend --> Bootstrap["BootstrapConfig"]
|
||||
Bootstrap --> ModulesCheck{"modules.sellerManagement.enabled?"}
|
||||
ModulesCheck -->|false / absent, default| Identical["Behavior identical to today.<br/>No new routes, menus, or API calls."]
|
||||
ModulesCheck -->|true| Available["Seller-aware behavior becomes available<br/>(not built yet — future work, own ADR)"]
|
||||
Bootstrap -.optional field.-> SellerField["BootstrapConfig.seller<br/>(SellerConfig, present only when<br/>backend resolved a seller scope)"]
|
||||
```
|
||||
|
||||
The frontend performs no resolution — it reads whatever the backend already
|
||||
decided into `BootstrapConfig.modules` / `BootstrapConfig.seller`, exactly
|
||||
the same discipline as tenant resolution (ADR-001) and feature-flag gating
|
||||
(ADR-009).
|
||||
|
||||
## 3. Type contracts introduced (this ADR only)
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BootstrapConfig {
|
||||
+TenantConfig tenant
|
||||
+PlatformModulesConfig? modules
|
||||
+SellerConfig? seller
|
||||
...existing fields unchanged
|
||||
}
|
||||
class PlatformModulesConfig {
|
||||
+SellerManagementModuleConfig sellerManagement
|
||||
}
|
||||
class SellerManagementModuleConfig {
|
||||
+boolean enabled
|
||||
}
|
||||
class SellerConfig {
|
||||
+UUID id
|
||||
+UUID marketplaceId
|
||||
+string slug
|
||||
+string name
|
||||
+string defaultLocale
|
||||
+string[] supportedLocales
|
||||
}
|
||||
BootstrapConfig --> PlatformModulesConfig
|
||||
BootstrapConfig --> SellerConfig
|
||||
PlatformModulesConfig --> SellerManagementModuleConfig
|
||||
```
|
||||
|
||||
`modules` and `seller` are both optional on `BootstrapConfig`. Every field
|
||||
already on `BootstrapConfig` is untouched.
|
||||
@@ -1,64 +0,0 @@
|
||||
# Seller Management — Domain Models (Preparation)
|
||||
|
||||
Companion to [ADR-011](adr/ADR-011-optional-seller-management-module.md) and
|
||||
[Seller-Management-Diagrams.md](Seller-Management-Diagrams.md). This document
|
||||
covers the second preparation pass: typed domain models for a future Seller
|
||||
entity, and optional seller-ownership fields on existing Product/Order
|
||||
models. **Typed models only — no repository, gateway, facade, CRUD, API, or
|
||||
authentication/authorization change exists as a result of this work.**
|
||||
|
||||
## New: `core/sellers/models/`
|
||||
|
||||
A new domain model group, mirroring the existing `core/products/models/`,
|
||||
`core/auth/models/` convention. Nothing outside this directory imports from
|
||||
it yet — these types exist for future work to build against.
|
||||
|
||||
| Type | File | Purpose |
|
||||
|---|---|---|
|
||||
| `MarketplaceRef` | `marketplace-ref.model.ts` | Minimal `{id, slug, name}` reference from a seller record back to its owning marketplace. Not a replacement for `TenantConfig` (bootstrap's runtime tenant contract, ADR-001) — just enough to say which marketplace a seller belongs to. |
|
||||
| `SellerStatus` | `seller-status.model.ts` | Lifecycle vocabulary: `'pending' \| 'active' \| 'suspended' \| 'disabled'`. No transition logic. |
|
||||
| `SellerScope` | `seller-scope.model.ts` | `{sellerId, marketplaceId}` — the domain-level counterpart to `BootstrapConfig.seller` (`SellerConfig`). Backend-resolved only, same rule as tenant resolution (ADR-001, ADR-011). |
|
||||
| `SellerBranding` (+ `SellerContact`, `SellerAddress`, `SellerThemeOverrides`) | `seller-branding.model.ts` | Logo, banner, description, contacts, address, theme overrides — **all fields optional**. Absent means marketplace branding/theme applies, unchanged (`BrandingConfig`/`ThemeConfig`). Nothing consumes this yet. |
|
||||
| `SellerPermissionRole`, `SellerPermissions` | `seller-permissions.model.ts` | Four future roles: `marketplaceOwner`, `seller`, `sellerStaff`, `platformAdmin`. **A separate vocabulary from the existing `AdminRole`** (Owner/Manager/Support/ReadOnly, `core/auth/models/permission.model.ts`) — not merged, not wired into any guard, no auth behavior change. |
|
||||
| `Seller` | `seller.model.ts` | The eventual entity: `id`, `marketplace: MarketplaceRef`, `name`, `slug`, `status: SellerStatus`, optional `branding: SellerBranding`, `createdAt`/`updatedAt`. |
|
||||
|
||||
All exported via `core/sellers/models/index.ts`.
|
||||
|
||||
## Changed: optional seller ownership on existing entities
|
||||
|
||||
Three existing entities gained one new **optional** field each. In every
|
||||
case: absent = marketplace-owned (today's only reality for every existing
|
||||
product/order), nothing reads the field yet, no consumer needed updating,
|
||||
`tsc`/`arch:check` both verified clean after the change.
|
||||
|
||||
| Entity | File | Field added |
|
||||
|---|---|---|
|
||||
| `Item` (storefront product) | `models/item.model.ts` | `sellerId?: string` |
|
||||
| `AdminProduct` (admin product editor) | `features/admin/products/models/admin-product.model.ts` | `sellerId?: string` |
|
||||
| `AdminOrder` (admin order editor) | `features/admin/orders/models/admin-order.model.ts` | `sellerId?: string` |
|
||||
|
||||
Deliberately **not** touched: `AdminOrderItem` (per-line-item seller
|
||||
ownership is a finer-grained decision than this preparation pass covers —
|
||||
order-level `sellerId` is enough for now), and both existing bootstrap
|
||||
`PermissionsConfig`/`AdminRole` (no authentication change, per mission).
|
||||
|
||||
## Non-goals (explicitly out of scope)
|
||||
|
||||
- No repository, gateway, facade, or API call reads or writes `sellerId`,
|
||||
`Seller`, or any type in this document.
|
||||
- No route, guard, or UI surfaces any of this.
|
||||
- No change to `AdminRole`, `ROLE_PERMISSIONS`, or any existing
|
||||
authentication/authorization code path.
|
||||
- No change to marketplace branding/theme defaults or precedence — a
|
||||
marketplace with no sellers, or a seller with no branding overrides,
|
||||
behaves exactly as today.
|
||||
|
||||
## What this unblocks later
|
||||
|
||||
Once Seller Management is actually implemented (its own ADR/implementation
|
||||
pass, per ADR-011 §"Scope of this ADR"): a `SellerRepository`/`SellerGateway`
|
||||
can return `Seller` objects instead of inventing a shape; product/order
|
||||
CRUD can start populating `sellerId` without a breaking schema change;
|
||||
permission guards can consume `SellerPermissionRole` once a real role system
|
||||
decision is made; branding resolution can check `Seller.branding` before
|
||||
falling back to marketplace `BrandingConfig`/`ThemeConfig`.
|
||||
@@ -1,190 +0,0 @@
|
||||
# Seller Management — Final Design Review
|
||||
|
||||
Principal-architect-level review of everything built and documented for
|
||||
Seller Management so far. One real bug found and fixed (below, in scope per
|
||||
this mission's "unless absolutely required" carve-out); everything else is
|
||||
findings only, no further code changed. Reviewed against source directly
|
||||
(fresh `tsc --noEmit` and `arch:check` run for this review, not recalled
|
||||
from memory) plus every doc in the series:
|
||||
[Seller-Management.md](Seller-Management.md),
|
||||
[ADR-011](adr/ADR-011-optional-seller-management-module.md),
|
||||
[Seller-Management-Diagrams.md](Seller-Management-Diagrams.md),
|
||||
[Seller-Management-Domain-Models.md](Seller-Management-Domain-Models.md),
|
||||
[Seller-Management-UX-Review.md](Seller-Management-UX-Review.md),
|
||||
[Seller-Management-Backoffice-Readiness-Audit.md](Seller-Management-Backoffice-Readiness-Audit.md),
|
||||
[Seller-Management-Storefront-Audit.md](Seller-Management-Storefront-Audit.md),
|
||||
[Seller-Management-Backend-Migration-Plan.md](Seller-Management-Backend-Migration-Plan.md),
|
||||
and `BACKEND.md` §11.
|
||||
|
||||
## Checklist verification (evidence-based, not asserted)
|
||||
|
||||
| Item | Status | Evidence |
|
||||
|---|---|---|
|
||||
| No existing marketplace breaks | ✓ Verified | Fresh `tsc --noEmit` clean, fresh `arch:check` clean (this review); live browser tests at 3 separate checkpoints across storefront home + backoffice dashboard/route |
|
||||
| Feature is optional | ✓ Verified | `DEFAULT_PLATFORM_MODULES_CONFIG.sellerManagement.enabled = false`; no backend anywhere sets it true |
|
||||
| Bootstrap remains backward compatible | ✓ Verified | `modules?`/`seller?` both optional on `BootstrapConfig`; `tsc` stayed clean the moment they were added, no consumer touched |
|
||||
| No API breaking changes | ✓ Verified (trivially) | No API exists to break — documentation-only for the backend side |
|
||||
| Existing frontend continues working | ✓ Verified | Live-tested at every UI-touching commit, zero regressions found |
|
||||
| Existing backend continues working | N/A | No backend exists; not applicable until implementation begins |
|
||||
| Dependency direction (ADR-002) | ✓ Verified | `arch:check:boundaries` clean, run fresh for this review |
|
||||
| Import boundaries (ADR-003) | ✓ Verified | Same tool, same clean result |
|
||||
| No tenant-specific conditions | ✓ Verified | Reviewed every new file directly — zero `if (tenant...)`/`if (seller===...)` conditionals exist anywhere |
|
||||
| Seller scope is additive | ✓ Verified | Every new field on every touched type is optional; nothing required changed |
|
||||
| UI consistency | ✓ Verified, with 2 fixes already applied | Dedicated UX-review pass found and fixed a label/a11y gap and a native-bullet inconsistency |
|
||||
| Translation readiness | ✓ Verified | en/ru/hy all carry every new key, confirmed by exact-count grep |
|
||||
| Accessibility readiness | ✓ Verified structurally, **one caveat** | Confirmed via accessibility-tree inspection (`role=dialog`, `aria-modal`, focus trap, `aria-label`) — **no real screen-reader software (NVDA/VoiceOver) pass was ever done**, only automated tree inspection. Flagged below (Low). |
|
||||
| Performance considerations | ✓ Verified | New route is its own lazy chunk (confirmed in build output), doesn't touch the initial bundle |
|
||||
| Future scalability | ✓ Addressed at design level | `Seller-Management-Backend-Migration-Plan.md` covers indexes, caching, phased rollout |
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. `SellerConfig` vs. `Seller`/`SellerBranding` — two unreconciled type hierarchies — **Medium**
|
||||
|
||||
`shared/models/config/seller.model.ts` (`SellerConfig`, the bootstrap wire
|
||||
shape: `id, marketplaceId, slug, name, defaultLocale, supportedLocales`) and
|
||||
`core/sellers/models/seller.model.ts` (`Seller`, the domain entity:
|
||||
`id, marketplace: MarketplaceRef, name, slug, status, branding?,
|
||||
createdAt, updatedAt`) describe overlapping concepts with different shapes
|
||||
and no conversion function between them. This was **self-identified during
|
||||
this same body of work** (`BACKEND.md` §11.6 already flags it as an open
|
||||
question) — restating it here as an independently-confirmed architectural
|
||||
finding, not a new discovery, because a final design review should not
|
||||
let a self-flagged gap quietly become "someone else's problem later."
|
||||
**Recommendation:** resolve before real backend work starts — either
|
||||
`SellerConfig` becomes a strict projection of `Seller` (documented mapping),
|
||||
or they're merged into one type with bootstrap-specific fields marked
|
||||
optional. Either is fine; leaving it unreconciled through implementation
|
||||
risks two competing "seller" shapes drifting further apart.
|
||||
|
||||
### 2. No reusable capability-guard abstraction exists — **Medium**
|
||||
|
||||
ADR-011 (and ADR-009 before it) both prescribe checking a capability flag
|
||||
"in one place, not scattered conditionals." In practice, **no such
|
||||
reusable guard exists anywhere in this codebase** — not for
|
||||
`sellerManagement.enabled`, and not for any existing feature flag either.
|
||||
ADR-009 itself describes a `FeatureFlagService` that was never actually
|
||||
built (confirmed: no file of that name exists in `src/app/core`). The one
|
||||
current consumer (`AdminSellerManagementPageComponent`) hand-rolls the
|
||||
optional-chain read directly. With one consumer this is harmless; the
|
||||
moment a second consumer needs the same check, it will either duplicate
|
||||
the same expression (drift risk: `?? false` vs `=== true` vs missing a
|
||||
null-check) or someone will need to build the guard ADR-009 already
|
||||
promised. **Recommendation:** build one small `SellerManagementGuardService`
|
||||
(or equivalent) the first time a second consumer needs the flag — don't
|
||||
let a third or fourth hand-rolled copy accumulate first.
|
||||
|
||||
### 3. Reactive-signal bug in the Phase 1 page — **Fixed during this review**
|
||||
|
||||
`AdminSellerManagementPageComponent.sellerManagementEnabled` read
|
||||
`configService.getBootstrapSnapshot()` once via a plain `signal()` at
|
||||
construction time — not reactively tied to `configService.bootstrapRevision()`
|
||||
the way `UiRuntimeFacade` and `SeoService` both correctly do. If bootstrap
|
||||
ever reloaded after initial page load (tenant context switch, revalidation)
|
||||
with the flag now `true`, this signal would never update — a real
|
||||
staleness bug, currently invisible because the flag is always `false` and
|
||||
the signal was never even read in the template. **Fixed in this review**
|
||||
(changed to `computed()` keyed on `bootstrapRevision()`, matching the
|
||||
established codebase pattern exactly) — a one-line correctness fix to
|
||||
already-committed code, not new feature work, so it fell inside this
|
||||
mission's "unless absolutely required" carve-out. Verified `tsc --noEmit`
|
||||
clean after the change.
|
||||
|
||||
### 4. `sellerId` typed as bare `string`, not `UUID` — **Low / Nice to have**
|
||||
|
||||
`Item.sellerId?`, `AdminProduct.sellerId?`, `AdminOrder.sellerId?` are all
|
||||
typed `string`, while every ID in the new `core/sellers/models/` uses the
|
||||
`UUID` type alias (`type UUID = string` — functionally identical, purely a
|
||||
signaling convention used consistently elsewhere in this codebase, e.g.
|
||||
`TenantConfig.id: UUID`). Zero functional impact since `UUID` is a bare
|
||||
alias, but a future reader will reasonably wonder why the new sellerId
|
||||
fields didn't follow the convention the sellers domain itself established
|
||||
one file away. **Recommendation:** trivial fix, do it opportunistically
|
||||
next time any of these three files is touched — not worth a dedicated pass.
|
||||
|
||||
### 5. `MarketplaceRef` vs. `TenantConfig` — acceptable but worth flagging — **Low**
|
||||
|
||||
`MarketplaceRef {id, slug, name}` and the existing `TenantConfig` (id, slug,
|
||||
code, host, name, locales, currencies, timezone, base URLs) both represent
|
||||
"a marketplace," from two different vantage points (seller-record reference
|
||||
vs. full runtime tenant contract). This is a deliberate, documented
|
||||
distinction (`Seller-Management-Domain-Models.md` explains it), not an
|
||||
accidental duplication — but it's the kind of decision that reads clearly
|
||||
today and could easily read as "why are there two Marketplace types" to
|
||||
someone joining later without the context. **Recommendation:** no action
|
||||
needed now; if a third marketplace-shaped type is ever proposed, that's the
|
||||
signal to consolidate, not before.
|
||||
|
||||
### 6. The flag's "true" branch has never been exercised, even manually — **Medium**
|
||||
|
||||
Every verification claim in this document's checklist table (and every
|
||||
prior audit) was tested with `modules.sellerManagement.enabled` at its
|
||||
real-world value: `false` (or absent). **Nobody has ever manually set it to
|
||||
`true`** — not in a browser dev-tools override, not in a mock fixture — to
|
||||
confirm the flag-reading code path actually behaves as intended when the
|
||||
condition it exists to detect is met. Today that's low-stakes (there's no
|
||||
enabled-state UI to differ), but the review checklist item "Seller scope is
|
||||
additive" was verified by reading the code, not by observing the `true`
|
||||
branch execute. **Recommendation:** the first time any enabled-state UI is
|
||||
built, that's also the moment to add one manual (or fixture-based) test
|
||||
confirming the `true` path — don't let a second feature get built on top of
|
||||
an assumption that was never actually observed.
|
||||
|
||||
### 7. Documentation-to-code ratio is unusually high — **Low / Nice to have**
|
||||
|
||||
Eight documents (this one included) exist for a capability that has zero
|
||||
backend bytes and one placeholder frontend page. That's not inherently
|
||||
wrong — the mission explicitly asked for staged documentation-first work —
|
||||
but it carries two real risks worth naming: (a) maintenance burden keeping
|
||||
eight cross-linked documents consistent if any single decision changes
|
||||
(e.g., if Unified-vs-Split-Orders resolves one way, at least three of these
|
||||
docs reference it and would need a coordinated update), and (b) the more
|
||||
times an undecided item ("Future," "not designed") is repeated across
|
||||
documents, the more it can start to feel settled by sheer repetition even
|
||||
though nothing has actually been decided. **Recommendation:** before
|
||||
backend implementation begins, do one consolidation pass collapsing
|
||||
overlapping content (the Unified/Split-Orders question in particular
|
||||
appears in `Seller-Management.md`, the Storefront audit, `BACKEND.md` §11,
|
||||
and the Migration Plan) into a single canonical statement the others link
|
||||
to, rather than four independent restatements.
|
||||
|
||||
### 8. No automated test coverage — **Low / Nice to have, not new**
|
||||
|
||||
Zero unit or integration tests cover any file introduced in this work —
|
||||
consistent with the rest of the codebase (`PROJECT_STATUS.md` already
|
||||
documents "no automated test suite exists," a pre-existing, repo-wide gap,
|
||||
not something this work introduced or made worse). Noting it here for
|
||||
completeness, not as a Seller-Management-specific defect.
|
||||
|
||||
## What I did NOT find
|
||||
|
||||
No architectural weaknesses beyond the above. Specifically checked for and
|
||||
did **not** find: circular dependencies (verified fresh, clean), scattered
|
||||
tenant/seller conditionals (none exist anywhere), over-engineering relative
|
||||
to the "typed models only" mandate (the six new model files map 1:1 to the
|
||||
six concepts explicitly requested, nothing extra), hidden coupling between
|
||||
`core/sellers/models` and any `features/` folder (the new domain models
|
||||
import only from `shared/types`, nothing reaches into a feature module),
|
||||
or any security-sensitive code path touched (no auth, no payment code was
|
||||
modified anywhere in this entire body of work).
|
||||
|
||||
## Verdict
|
||||
|
||||
**Not an unqualified "ready for implementation."** Two Medium findings
|
||||
(#1, the unreconciled `SellerConfig`/`Seller` type split, and #2, the
|
||||
missing capability-guard abstraction) are genuine architectural loose ends
|
||||
that should be resolved by decision or by a small build, respectively,
|
||||
before real backend/CRUD work begins — not because either blocks anything
|
||||
today, but because both compound in cost the longer they're left
|
||||
unresolved (more consumers = more places to reconcile later; the type
|
||||
duality especially, since a real `SellerRepository`/`SellerGateway` would
|
||||
otherwise have to pick one shape or invent a mapping ad hoc under time
|
||||
pressure). Finding #6 (the flag's true-branch never observed) is a
|
||||
process gap to close at the next milestone, not before it.
|
||||
|
||||
**Everything that has actually been built — the typed foundation, the
|
||||
disabled-by-default feature flag, the Phase 1 UI, and the one real bug
|
||||
this review found and fixed — is solid and ready to stay exactly as it
|
||||
is.** The design as a *whole plan* is sound and internally consistent; the
|
||||
two Medium findings are refinements to make before the next phase starts,
|
||||
not defects in what exists today. No Critical or High-severity issue was
|
||||
found anywhere in this review.
|
||||
@@ -1,197 +0,0 @@
|
||||
# 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](Seller-Management.md) §6 (Seller
|
||||
Storefronts, Seller Branding — both marked Future there) and
|
||||
[Seller-Management-Backoffice-Readiness-Audit.md](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.brandName` → `TenantResolverService.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`. `brandName` →
|
||||
`UiRuntimeFacade.marketplaceDisplayName()`; `logo` →
|
||||
`UiRuntimeFacade.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.
|
||||
|
||||
### Footer — same source as Header, plus contact info
|
||||
`components/footer/footer.component.ts`. `brandName` →
|
||||
`uiRuntime.marketplaceName()`, `contactEmail` → `uiRuntime.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 tags** — `siteName` 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.
|
||||
@@ -1,80 +0,0 @@
|
||||
# Seller Management — UX Review
|
||||
|
||||
Review pass over the Phase 1 UI (`admin-seller-management-page.component.*`)
|
||||
against the rest of the Backoffice. Two real issues found and fixed; the
|
||||
rest of the checklist was verified as already consistent because the page
|
||||
is built entirely from existing shared components.
|
||||
|
||||
## Fixed this pass
|
||||
|
||||
1. **Missing label association on the Message field (real a11y bug).**
|
||||
`app-input` self-wires `id`/`aria-describedby` from its injected
|
||||
`FormFieldContext` (confirmed in `input.component.html`); the raw
|
||||
`<textarea>` used for the optional Message field — no dedicated textarea
|
||||
component exists yet anywhere in the app — never received that wiring.
|
||||
The visible label's `for` pointed at an id the textarea never got, so a
|
||||
screen reader wouldn't announce "Message" on focus via the label
|
||||
association (proximity only). Fixed with an explicit `[attr.aria-label]`
|
||||
bound to the same translation key already used for the visible label —
|
||||
correct regardless of the broken `for` linkage.
|
||||
|
||||
2. **Native browser bullets in the Learn More dialog (visual inconsistency).**
|
||||
No global `list-style: none` reset exists for plain `<ul>` anywhere in
|
||||
`src/styles.scss` (only `details > summary` gets one, for the expander
|
||||
chevron). The feature list would have rendered default browser discs —
|
||||
the one place in this page not reusing an existing shared visual
|
||||
language. Replaced with `checkCircle` icon + text rows (`app-icon`,
|
||||
`--success-color` token), consistent with how the rest of the app pairs
|
||||
icons with status/list meaning rather than bare bullets.
|
||||
|
||||
## Verified already consistent (no change needed)
|
||||
|
||||
- **Empty state usage**: every other empty-state consumer in the app
|
||||
(`admin-products-list`, `media-library-page`, `admin-reviews-list`, etc.)
|
||||
uses `app-empty-state` bare — no card wrapper. This page matches that. It
|
||||
is the only one filling the `icon` slot (a subtle primary-tinted circle
|
||||
behind a `store` icon); no other page does this, but this page is also
|
||||
the only one that's *entirely* an empty state as its whole content
|
||||
(every other example sits inside a page that also has a toolbar/table),
|
||||
so a slightly more deliberate visual treatment for the "coming soon"
|
||||
moment is a reasonable, isolated deviation rather than drift.
|
||||
`app-empty-state`'s own description already caps at `max-width: 32rem` —
|
||||
no extra width-constraint code needed.
|
||||
- **Icon reuse**: `store` (empty-state) and `checkCircle` (feature list) —
|
||||
neither icon is reused with a conflicting meaning elsewhere in the app
|
||||
(checked against `icon-registry.ts`'s existing 85-icon map from the prior
|
||||
icon audit).
|
||||
- **Buttons/dialogs/inputs/hover/focus**: 100% shared components
|
||||
(`app-button`, `app-dialog`, `app-input`, `app-form-field`, `app-badge`).
|
||||
Hover, focus-visible, disabled, and loading states are whatever those
|
||||
components already define — verified by inspecting each component's own
|
||||
`.scss`, not re-implemented here. Same reasoning covers contrast (reused
|
||||
tokens, not new color decisions) and dark-theme readiness (every value in
|
||||
this page's own `.scss` is `var(--token, fallback)`, same fallback values
|
||||
already used in `input.component.scss` — nothing hardcoded that a future
|
||||
dark theme couldn't override).
|
||||
- **Dialog accessibility**: `app-dialog` provides `role="dialog"`,
|
||||
`aria-modal="true"`, Tab/Shift+Tab focus trap, Escape-to-close, and
|
||||
focus-restore-on-close — confirmed via the accessibility tree
|
||||
(`role=dialog`, correct `aria-label` matching each dialog's title) and by
|
||||
live-testing focus behavior, not assumed.
|
||||
- **Merchant wording**: every string matches the original brief's exact
|
||||
business-facing copy (Company/Email/Message, "Coming Soon", capability
|
||||
bullets in plain language) — no developer terminology introduced.
|
||||
- **Responsive**: re-verified at 1280px, and at 375px mobile (button rows
|
||||
stack full-width per the existing breakpoint in this page's `.scss`,
|
||||
dialog/list content re-rendered correctly, no layout break).
|
||||
- **Translations**: all new keys (`adminShell.nav.partnersGroup`,
|
||||
`adminShell.nav.sellerManagement`, `adminShell.pages.sellerManagement`,
|
||||
and the full `adminSellerManagement.*` namespace) exist in `en.ts`,
|
||||
`ru.ts`, `hy.ts`, and `translations.ts` (types) — verified by exact-count
|
||||
grep across all three locale files, no hardcoded string found in the
|
||||
component's template or TypeScript.
|
||||
|
||||
## Verification
|
||||
|
||||
`tsc --noEmit` clean. `arch:check` (boundaries + cycles) clean. Live-tested
|
||||
(ru locale, `devBypassAdmin`, desktop 1280px + mobile 375px): Learn More
|
||||
dialog now shows all 6 items with a check icon each (confirmed via DOM
|
||||
query - `svg` present on every `<li>`), Message textarea confirmed to carry
|
||||
`aria-label="Сообщение"`, no console errors at any point.
|
||||
@@ -1,214 +0,0 @@
|
||||
# Seller Management — Capability Documentation
|
||||
|
||||
Status legend used throughout this document:
|
||||
|
||||
- **Implemented** — exists in source on `feature/seller-management-foundation` today, verified (`tsc`, `arch:check`, or live browser test).
|
||||
- **Planned** — has a typed contract or explicit ADR decision, but no code reads/writes it yet.
|
||||
- **Future** — a concept named in this document for roadmap completeness only. No shape, contract, or decision exists yet. Do not build against this section without a new ADR.
|
||||
|
||||
This document is the entry point. Detail lives in its companion docs:
|
||||
[ADR-011](adr/ADR-011-optional-seller-management-module.md) (decision),
|
||||
[Seller-Management-Diagrams.md](Seller-Management-Diagrams.md) (hierarchy/bootstrap-gate diagrams),
|
||||
[Seller-Management-Domain-Models.md](Seller-Management-Domain-Models.md) (every type, field by field),
|
||||
[Seller-Management-UX-Review.md](Seller-Management-UX-Review.md) (Phase 1 UI review).
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Seller Management is an **optional platform capability** that would let one
|
||||
marketplace host multiple independent sellers, each with their own
|
||||
inventory/orders/branding, under one centralized administration. It is not
|
||||
another tenant — a seller is a child scope beneath exactly one marketplace
|
||||
(ADR-011).
|
||||
|
||||
**Implemented today:** typed contracts for the whole hierarchy, a disabled-
|
||||
by-default feature flag, one Backoffice page that explains the capability
|
||||
and collects interest ("Request Access" / "Learn More"). **Nothing else** —
|
||||
no CRUD, no backend, no seller-facing UI, no checkout/order behavior change.
|
||||
|
||||
## 2. Architecture & Hierarchy — Implemented (types only)
|
||||
|
||||
```
|
||||
Platform
|
||||
└── Marketplace (tenant) — always present, backend-resolved (ADR-001)
|
||||
└── Seller (optional) — 0..N per marketplace, backend-resolved (ADR-011)
|
||||
```
|
||||
|
||||
Full diagram set: [Seller-Management-Diagrams.md](Seller-Management-Diagrams.md).
|
||||
|
||||
Rules (ADR-011, enforced by review, not yet by any lint rule):
|
||||
Marketplace is the sole primary tenant. Seller is a child scope, never a
|
||||
sibling tier. The frontend never resolves seller identity itself — same
|
||||
rule as tenant resolution. All seller-aware behavior must check one
|
||||
capability flag, never scattered marketplace/seller conditionals.
|
||||
|
||||
## 3. Marketplace — Implemented (existing, unchanged)
|
||||
|
||||
The marketplace is the existing `TenantConfig`
|
||||
(`shared/models/config/tenant.model.ts`) — resolved by the backend from
|
||||
request Host, exactly as before this work started. Seller Management adds a
|
||||
new `MarketplaceRef` (`core/sellers/models/marketplace-ref.model.ts`): a
|
||||
minimal `{id, slug, name}` view of a marketplace *as seen from a seller
|
||||
record*, not a replacement for `TenantConfig`.
|
||||
|
||||
## 4. Seller — Implemented (types only)
|
||||
|
||||
`Seller` (`core/sellers/models/seller.model.ts`): `id`, `marketplace:
|
||||
MarketplaceRef`, `name`, `slug`, `status: SellerStatus`, optional `branding:
|
||||
SellerBranding`, `createdAt`/`updatedAt`. No repository, gateway, facade, or
|
||||
UI reads or writes this type yet — it exists so future CRUD work has a
|
||||
settled shape instead of inventing one ad hoc.
|
||||
|
||||
**Planned:** a `SellerRepository`/`SellerGateway` pair following the same
|
||||
mock↔API DI-token pattern every other admin domain already uses
|
||||
(`BACKEND.md` §8). **Future:** the actual CRUD screens, list/detail pages,
|
||||
onboarding flow.
|
||||
|
||||
## 5. Roles & Permissions — Implemented (types only)
|
||||
|
||||
`SellerPermissionRole` (`core/sellers/models/seller-permissions.model.ts`):
|
||||
four values — `marketplaceOwner`, `seller`, `sellerStaff`, `platformAdmin`.
|
||||
This is a **separate vocabulary** from the existing `AdminRole` (Owner/
|
||||
Manager/Support/ReadOnly, `core/auth/models/permission.model.ts`) — not
|
||||
merged, not wired into any guard. **No authentication or authorization
|
||||
change exists anywhere in this work.**
|
||||
|
||||
**Planned:** once a real permission model is designed, these roles gate
|
||||
seller-scoped routes/actions the same way `AdminRole` gates admin routes
|
||||
today (ADR-009 capability-guard pattern). **Future:** the actual
|
||||
permission-to-action mapping, custom/finer-grained roles per marketplace.
|
||||
|
||||
## 6. Future Roadmap
|
||||
|
||||
### Feature Flags — Implemented (contract), Planned (real use)
|
||||
|
||||
`BootstrapConfig.modules.sellerManagement.enabled`
|
||||
(`shared/models/config/platform-modules.model.ts`), default `false`
|
||||
(`DEFAULT_PLATFORM_MODULES_CONFIG`). Implemented as a typed contract read by
|
||||
the Phase 1 page (`admin-seller-management-page.component.ts`); no backend
|
||||
sets it to `true` anywhere today, so it is always `false` in practice.
|
||||
|
||||
### Bootstrap — Implemented (contract), Planned (real data)
|
||||
|
||||
`BootstrapConfig.modules?` and `BootstrapConfig.seller?` (`SellerConfig`,
|
||||
`shared/models/config/seller.model.ts`) — both optional, both absent in
|
||||
every real bootstrap response today. When a backend eventually resolves a
|
||||
seller scope, it populates `seller`; until then this field simply doesn't
|
||||
exist on the wire.
|
||||
|
||||
### Future API — Future
|
||||
|
||||
No endpoint exists. When built, it should follow `BACKEND.md`'s existing
|
||||
mock↔API-gateway pattern (§8) rather than a new convention — this is a
|
||||
statement of intent, not a designed contract. No URL, DTO, or status-code
|
||||
behavior is decided.
|
||||
|
||||
### Seller Storefronts — Future
|
||||
|
||||
Concept: a seller-branded storefront view within a marketplace (e.g. a
|
||||
seller's own product listing page reachable from the marketplace). **Not
|
||||
designed.** No route, component, or URL scheme exists or is decided.
|
||||
|
||||
### Seller Branding — Implemented (types only), Future (usage)
|
||||
|
||||
`SellerBranding` (`core/sellers/models/seller-branding.model.ts`): logo,
|
||||
banner, description, contacts, address, theme overrides — every field
|
||||
optional. **Implemented as a type only.** Nothing renders it, nothing falls
|
||||
back from it to marketplace branding — that precedence logic is **Future**
|
||||
work, not yet designed.
|
||||
|
||||
### Seller Ownership — Implemented (schema only), Future (logic)
|
||||
|
||||
`sellerId?: string` added to `Item` (storefront), `AdminProduct`, and
|
||||
`AdminOrder` — optional, absent means marketplace-owned (every existing
|
||||
product/order today). **No code reads or writes this field anywhere.**
|
||||
Ownership rules, transfer, and enforcement are **Future** work.
|
||||
|
||||
### Checkout Modes — Future
|
||||
|
||||
Concept: how checkout behaves when a cart contains items from multiple
|
||||
sellers (e.g. single combined checkout vs. per-seller checkout flows).
|
||||
**Not designed.** No decision exists on this; today every product is
|
||||
marketplace-owned and checkout has exactly one flow, unchanged by this work.
|
||||
|
||||
### Unified Orders / Split Orders — Future
|
||||
|
||||
Concept: whether one customer purchase spanning multiple sellers becomes
|
||||
one order record or splits into one order per seller. **Not designed.**
|
||||
This is a real business decision (payments, refunds, and reporting all
|
||||
depend on the answer) with no default assumed — explicitly listed as an
|
||||
open question for whenever Seller Management moves past preparation.
|
||||
|
||||
## 7. Migration & Compatibility
|
||||
|
||||
### Why existing marketplaces remain unchanged
|
||||
|
||||
- `modules.sellerManagement.enabled` defaults to `false` and no backend
|
||||
sets it — every marketplace today gets identical behavior whether the
|
||||
field is present-and-false or entirely absent from its bootstrap
|
||||
response.
|
||||
- `BootstrapConfig.modules` and `BootstrapConfig.seller` are optional
|
||||
fields; no existing field's type changed.
|
||||
- `sellerId?` on `Item`/`AdminProduct`/`AdminOrder` is optional; no
|
||||
consumer of any of these three types needed updating, verified by
|
||||
`tsc --noEmit` staying clean after each change.
|
||||
- Zero components, facades, services, or routes branch on marketplace or
|
||||
seller identity anywhere in this work (ADR-011 compliance requirement) —
|
||||
there is no conditional to accidentally trigger.
|
||||
- Every commit in this line of work was verified with `tsc --noEmit`,
|
||||
`arch:check` (import boundaries + circular deps), and — for the UI
|
||||
commits — a live browser pass, specifically to confirm no regression to
|
||||
existing pages.
|
||||
|
||||
## 8. Developer Notes
|
||||
|
||||
- All seller domain types live in `core/sellers/models/` (mirrors
|
||||
`core/products/models`, `core/auth/models`). Extend there, not ad hoc in
|
||||
feature folders.
|
||||
- When real seller-aware behavior is eventually built, gate it behind
|
||||
`modules.sellerManagement.enabled` in one place (a capability guard,
|
||||
ADR-009's pattern) — never scattered `if` checks on tenant/seller identity.
|
||||
- The Phase 1 page (`features/admin/seller-management/`) is disposable —
|
||||
it exists to communicate the capability to merchants, not as a
|
||||
foundation to extend. Real seller CRUD UI should be planned fresh once
|
||||
the backend contract exists, not bolted onto this page.
|
||||
|
||||
## 9. Builder Notes
|
||||
|
||||
The Project Editor / Marketplace Builder has **zero seller-awareness**
|
||||
today. Its draft/publish model (`localStorage`-only, no backend write path
|
||||
per `BACKEND.md` §1.10) is entirely marketplace-scoped. If/when a seller
|
||||
needs their own builder-like surface (branding, storefront layout), it must
|
||||
be designed as its own ADR — do not assume the existing builder can be
|
||||
reused as-is for a seller scope without that review, since its facades and
|
||||
schema (ADR-005, ADR-007) were built assuming exactly one config document
|
||||
per marketplace.
|
||||
|
||||
## 10. Backend Notes
|
||||
|
||||
No backend implementation exists for any part of Seller Management. When
|
||||
work begins, follow `BACKEND.md`'s established pattern exactly: a
|
||||
`SellerRepository`/`SellerGateway` behind a DI token, `MockSellerGateway`
|
||||
first, `ApiSellerGateway` swapped in later, same convention every other
|
||||
admin domain in this codebase already uses (`BACKEND.md` §8). The typed
|
||||
models in `core/sellers/models/` are the DTO shapes to implement against —
|
||||
treat them as the contract, not a suggestion to redesign.
|
||||
|
||||
## Diagrams
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A["Types & feature flag<br/>(this + prior 3 commits)"] -->|Implemented| B["Phase 1 UI<br/>(Partners > Seller Management page)"]
|
||||
B -->|Implemented| C["Backend contract decisions<br/>(BACKEND.md gaps, own ADR)"]
|
||||
C -->|Future| D["Seller CRUD + real gateway"]
|
||||
D -->|Future| E["Seller Branding rendering<br/>+ Storefronts"]
|
||||
E -->|Future| F["Checkout Modes +<br/>Unified/Split Orders"]
|
||||
|
||||
classDef done fill:#2e7d3222,stroke:#2e7d32,color:inherit;
|
||||
classDef future fill:#6b728022,stroke:#6b7280,color:inherit;
|
||||
class A,B done;
|
||||
class C,D,E,F future;
|
||||
```
|
||||
|
||||
Rollout is strictly left-to-right — no stage after "Phase 1 UI" has started.
|
||||
See [Seller-Management-Diagrams.md](Seller-Management-Diagrams.md) for the
|
||||
hierarchy and bootstrap-gate diagrams (unchanged, still accurate).
|
||||
@@ -1,38 +0,0 @@
|
||||
# Service Standards
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## Service Categories
|
||||
|
||||
- Domain Service: business operations and rules.
|
||||
- Integration Service: external API/system communication.
|
||||
- Platform Service: cross-cutting platform concerns.
|
||||
|
||||
## Rules
|
||||
|
||||
- Services must have one clear responsibility.
|
||||
- Services should expose typed contracts only.
|
||||
- Services should avoid UI-specific formatting.
|
||||
- Services should not depend on component classes.
|
||||
- Shared services must not depend on feature modules.
|
||||
- Business decisions must not be driven by `environment.*` flags.
|
||||
- Environment values are limited to infrastructure concerns (API base URLs, provider strategy wiring, auth endpoint origins).
|
||||
|
||||
## Facade Interaction
|
||||
|
||||
- Components call facades.
|
||||
- Facades call services.
|
||||
- Services do not call UI components.
|
||||
|
||||
## Stable Module Protection
|
||||
|
||||
- Existing authentication, payment, authorization services remain behavior-compatible.
|
||||
- Wrap legacy stable behavior with adapters where needed.
|
||||
- No contract changes for auth/payment APIs.
|
||||
|
||||
## Storage and Runtime Access
|
||||
|
||||
- Browser storage access allowed only in approved service boundaries.
|
||||
- Prefer abstraction interfaces for storage access.
|
||||
- Never use storage APIs in UI components.
|
||||
@@ -1,42 +0,0 @@
|
||||
# State Management Standards
|
||||
|
||||
Status: Mandatory
|
||||
Date: 2026-07-03
|
||||
|
||||
## Objectives
|
||||
|
||||
- Keep state predictable, scoped, and replaceable.
|
||||
- Support Website, Builder, and Backoffice without coupling.
|
||||
|
||||
## State Layers
|
||||
|
||||
- Platform State: bootstrap, feature flags, theme, localization, session status.
|
||||
- Domain State: feature-specific bounded context state.
|
||||
- UI State: ephemeral visual state local to component/container.
|
||||
|
||||
## Facade Rules
|
||||
|
||||
- Every domain exposes state through facades.
|
||||
- Facades expose readonly projections/selectors/signals.
|
||||
- Mutations happen through explicit facade commands.
|
||||
|
||||
## Isolation Rules
|
||||
|
||||
- No direct cross-domain state mutation.
|
||||
- No component writes directly into service internals.
|
||||
- Shared state contracts must be explicit and typed.
|
||||
|
||||
## Persistence Rules
|
||||
|
||||
- Persisted state access must be centralized in approved services.
|
||||
- UI components never access localStorage/sessionStorage directly.
|
||||
|
||||
## Feature Flag Interaction
|
||||
|
||||
- State branches for optional capabilities must be capability-driven.
|
||||
- Missing capability paths must return safe defaults.
|
||||
|
||||
## Migration and Compatibility
|
||||
|
||||
- Existing auth/payment behavior remains intact while wrapped by facade boundaries.
|
||||
- Refactoring must preserve observable behavior for critical flows.
|
||||
@@ -1,38 +0,0 @@
|
||||
# ADR-001: Platform Model and Tenancy Strategy
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-03
|
||||
|
||||
## Context
|
||||
|
||||
The existing repository has evolved from marketplace website delivery.
|
||||
The target product is Marketplace-as-a-Service with unlimited tenants on one runtime.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a platform runtime model:
|
||||
|
||||
- One Angular application serves all tenants.
|
||||
- Tenant identity is resolved by backend from request Host.
|
||||
- Frontend does not pass tenant id or project key.
|
||||
- Frontend starts by requesting GET /bootstrap.
|
||||
- Tenant-specific website, builder, and backoffice behavior derives from bootstrap configuration.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Tenant onboarding becomes configuration-driven.
|
||||
- Eliminates tenant forks and branch divergence.
|
||||
- Strong separation of platform engine and tenant data.
|
||||
|
||||
Negative:
|
||||
|
||||
- Requires strict discipline against tenant conditionals in UI code.
|
||||
- Requires robust bootstrap schema governance.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- No environment-based tenant branching in presentation logic.
|
||||
- No tenant-specific routes hardcoded in feature components.
|
||||
- Tenant behavior is represented in typed configuration contracts.
|
||||
@@ -1,43 +0,0 @@
|
||||
# ADR-002: Layered Feature Architecture with Single Responsibility
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-03
|
||||
|
||||
## Context
|
||||
|
||||
The platform must support Website, Builder, and Backoffice while keeping shared capabilities reusable and independent.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt the following architecture layers and responsibilities:
|
||||
|
||||
- Core: bootstrap, app wiring, global policies, base adapters.
|
||||
- Shared: pure contracts, pure utilities, generic primitives.
|
||||
- UI Library: reusable presentational components only.
|
||||
- Widgets: configurable functional blocks built from UI components.
|
||||
- Layouts: page section composition and structural orchestration.
|
||||
- Pages: route containers mapping configuration to layouts/widgets.
|
||||
- Website: public commerce experience.
|
||||
- Builder: configuration editing domain.
|
||||
- Backoffice: business data management domain.
|
||||
|
||||
Single responsibility is mandatory for each layer.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Predictable layering and ownership.
|
||||
- Higher reuse across Website, Builder, and Backoffice.
|
||||
- Reduced accidental coupling.
|
||||
|
||||
Negative:
|
||||
|
||||
- Requires import boundary enforcement.
|
||||
- Requires upfront contracts before feature implementation.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- Shared and UI layers cannot depend on feature layers.
|
||||
- Feature layers interact through contracts/facades, not direct imports.
|
||||
- New artifacts must be placed in the correct layer folder.
|
||||
@@ -1,39 +0,0 @@
|
||||
# ADR-003: Import Boundaries and Dependency Direction
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-03
|
||||
|
||||
## Context
|
||||
|
||||
Without strict dependency direction, large Angular codebases accumulate circular dependencies and feature coupling that block reuse.
|
||||
|
||||
## Decision
|
||||
|
||||
Enforce one-way dependency flow:
|
||||
|
||||
Website/Builder/Backoffice -> Pages -> Layouts -> Widgets -> UI Library -> Shared -> Core
|
||||
|
||||
Additional constraints:
|
||||
|
||||
- No circular dependencies.
|
||||
- No feature importing another feature directly.
|
||||
- Core does not depend on any feature.
|
||||
- Shared does not depend on features.
|
||||
- UI Library does not depend on features.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Stable architecture evolution.
|
||||
- Easier testability and extraction.
|
||||
- Faster onboarding with clear module contracts.
|
||||
|
||||
Negative:
|
||||
|
||||
- Some existing direct imports must be replaced by contracts.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- Enforce via lint boundaries and dependency checks.
|
||||
- Violations block merge.
|
||||
@@ -1,11 +0,0 @@
|
||||
# ADR-004: Configuration Bootstrap and Provider Abstraction
|
||||
|
||||
Status: Superseded
|
||||
Date: 2026-07-03
|
||||
Superseded by: `docs/BACKEND_API.md` §4 (Bootstrap) and §14 (Backend replacement pattern)
|
||||
|
||||
## Original decision (preserved for history)
|
||||
|
||||
Configuration must initially come from mock JSON and later from backend API without changing consumers. `ConfigService` is the only configuration entrypoint; consumers depend on typed selectors only; provider implementation is swappable (`MockBootstrapProvider` / `ApiBootstrapProvider`); the frontend calls `GET /bootstrap` when the API provider is enabled and never passes a tenant id.
|
||||
|
||||
This decision remains in effect. The full, verified contract — endpoint, caching, field-by-field DTO reference, and the generalized mock↔API provider-swap pattern this ADR introduced (now used by every admin domain, not just bootstrap) — lives in `docs/BACKEND_API.md`. Read that document for current, code-verified detail; this file is kept only so ADR-numbered references in `docs/architecture/foundation/README.md` continue to resolve.
|
||||
@@ -1,35 +0,0 @@
|
||||
# ADR-005: Dynamic Page, Section, and Widget Rendering
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-03
|
||||
|
||||
## Context
|
||||
|
||||
Platform websites must be generated from configuration. Hardcoded page composition blocks tenant scalability.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt dynamic rendering engine:
|
||||
|
||||
- A page definition contains ordered sections.
|
||||
- A section contains ordered widgets.
|
||||
- WidgetHost resolves widget type through registry.
|
||||
- Registry-based resolution avoids renderer edits for every new widget.
|
||||
- Hero, Carousel, Header, Footer, and other blocks are widgets/layout entries from configuration.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- New tenant pages created by configuration.
|
||||
- Supports Builder-driven composition.
|
||||
|
||||
Negative:
|
||||
|
||||
- Requires robust schema validation.
|
||||
- Requires widget compatibility and versioning discipline.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- Page templates must not hardcode specific widget combinations.
|
||||
- Widget rendering must be data-driven from configuration contracts.
|
||||
@@ -1,42 +0,0 @@
|
||||
# ADR-006: UI Component Purity and Container-Facade Pattern
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-03
|
||||
|
||||
## Context
|
||||
|
||||
Reusable platform components cannot contain business and integration concerns.
|
||||
|
||||
## Decision
|
||||
|
||||
Separate visual and business responsibilities:
|
||||
|
||||
- UI components are presentational only.
|
||||
- Container components connect facades to UI components.
|
||||
- Facades own orchestration and use services.
|
||||
- Services handle IO and integration.
|
||||
|
||||
UI component restrictions:
|
||||
|
||||
- No HttpClient.
|
||||
- No localStorage/sessionStorage access.
|
||||
- No environment import.
|
||||
- No tenant awareness.
|
||||
- No authentication/payment logic.
|
||||
- No route logic.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Maximum reuse and testability.
|
||||
- Supports widget library portability.
|
||||
|
||||
Negative:
|
||||
|
||||
- Requires refactoring of mixed legacy components.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- Components must use Inputs for data and Outputs for events.
|
||||
- Business behavior belongs to facades/containers only.
|
||||
@@ -1,33 +0,0 @@
|
||||
# ADR-007: State Management and Facade Boundaries
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-03
|
||||
|
||||
## Context
|
||||
|
||||
State must be predictable and isolated by domain to support Website, Builder, and Backoffice without cross-domain leakage.
|
||||
|
||||
## Decision
|
||||
|
||||
Use facade-centered state management by bounded context:
|
||||
|
||||
- Each feature domain exposes one or more facades.
|
||||
- Facades expose read models and command methods.
|
||||
- State is local to domain and projected as readonly selectors/signals.
|
||||
- Shared/global state is limited to platform concerns (configuration, theme, localization, session status).
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Clear ownership of state transitions.
|
||||
- Improved maintainability and testability.
|
||||
|
||||
Negative:
|
||||
|
||||
- Requires disciplined facade boundaries.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- Components do not mutate service internals directly.
|
||||
- Cross-domain communication is contract-based, not direct state access.
|
||||
@@ -1,32 +0,0 @@
|
||||
# ADR-008: Theme Engine and Runtime Design Tokens
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-03
|
||||
|
||||
## Context
|
||||
|
||||
Tenant branding must be configuration-driven and must not require tenant-specific code branches.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce theme engine based on runtime design tokens:
|
||||
|
||||
- Branding, color palette, typography, spacing, icons, logos, favicon derive from configuration.
|
||||
- Theme tokens are applied at runtime through token service and CSS variable mapping.
|
||||
- Feature code consumes semantic tokens, not tenant constants.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Tenant branding changes are configuration-only.
|
||||
- Removes environment-based visual branching.
|
||||
|
||||
Negative:
|
||||
|
||||
- Requires token schema governance and fallback policy.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- No tenant-specific style imports in feature components.
|
||||
- UI styling must resolve through semantic token set.
|
||||
@@ -1,32 +0,0 @@
|
||||
# ADR-009: Feature Flags and Capability Guards
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-03
|
||||
|
||||
## Context
|
||||
|
||||
Platform tenants have optional capabilities. Features cannot be assumed always present.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce capability model backed by bootstrap feature flags:
|
||||
|
||||
- FeatureFlagService exposes tenant capabilities.
|
||||
- Routes, widgets, and actions are guarded by capability checks.
|
||||
- Missing capability must degrade gracefully with fallback behavior.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- One runtime supports variable tenant feature sets.
|
||||
- Reduces tenant branching and dead code.
|
||||
|
||||
Negative:
|
||||
|
||||
- Requires explicit defaults and fallback UX.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- Components and pages cannot assume optional feature availability.
|
||||
- Capability checks must be centralized, not scattered conditionals.
|
||||
@@ -1,11 +0,0 @@
|
||||
# ADR-010: Backward Compatibility for Authentication, Payment, and Authorization
|
||||
|
||||
Status: Superseded
|
||||
Date: 2026-07-03
|
||||
Superseded by: `docs/BACKEND_API.md` §2 (Authentication), §2.8 (Payments), §2.5 (admin authorization gap)
|
||||
|
||||
## Original decision (preserved for history)
|
||||
|
||||
Authentication and payment flows are proven and contract-sensitive. Platform refactoring must not break existing integrations. Freeze behavior and contracts for authentication flow, payment API interactions, and authorization logic. Allow only encapsulation and integration-layer isolation, not contract redesign.
|
||||
|
||||
This decision remains in effect. The full, verified contract — the Telegram QR/session flow, cookie policy, the frozen payment endpoint shapes, and the still-unresolved admin-authorization gap this ADR's constraint interacts with — lives in `docs/BACKEND_API.md` §2 and §2.5–§2.8. Read that document for current, code-verified detail; this file is kept only so ADR-numbered references in `docs/architecture/foundation/README.md` continue to resolve.
|
||||
@@ -1,117 +0,0 @@
|
||||
# ADR-011: Optional Seller Management Module
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-07-26
|
||||
|
||||
## Context
|
||||
|
||||
The platform today has a two-level hierarchy: Platform → Marketplace (tenant),
|
||||
per ADR-001. Some marketplaces will eventually need a third, optional level:
|
||||
individual Sellers operating storefronts within one marketplace (a
|
||||
marketplace-of-marketplaces / multi-vendor model). Not every marketplace
|
||||
needs this — most tenants today have none.
|
||||
|
||||
Seller Management must not become a second tenancy model. ADR-001 already
|
||||
established that tenant identity is backend-resolved from request Host and
|
||||
the frontend never passes or resolves tenant identity itself. Introducing
|
||||
sellers must not weaken that discipline or introduce a second, parallel
|
||||
resolution mechanism the frontend has to reason about.
|
||||
|
||||
## Decision
|
||||
|
||||
Seller Management is an **optional platform capability module**, not a new
|
||||
tenancy tier equal to Marketplace:
|
||||
|
||||
```
|
||||
Platform
|
||||
└── Marketplace (tenant) — always present, resolved by backend (ADR-001)
|
||||
└── Seller (optional) — 0..N per marketplace, resolved by backend
|
||||
```
|
||||
|
||||
- **Marketplace remains the sole primary tenant.** A seller is a child scope
|
||||
of exactly one marketplace, never a sibling of Marketplace and never
|
||||
resolved independently of it.
|
||||
- **The frontend never resolves seller identity itself** — same rule as
|
||||
tenant resolution (ADR-001). The backend decides whether the current
|
||||
request scope is marketplace-level or seller-level and reflects that
|
||||
decision in the bootstrap response.
|
||||
- **The frontend consumes bootstrap only.** No new endpoint, header, or
|
||||
client-side resolution logic is introduced by this ADR. If a seller scope
|
||||
applies, `BootstrapConfig.seller` (see `SellerConfig`) is present; if not,
|
||||
it's absent. There is no other channel.
|
||||
- **Gated by a module flag, not scattered conditionals.** The capability is
|
||||
controlled by one typed flag — `BootstrapConfig.modules.sellerManagement.
|
||||
enabled` (see `PlatformModulesConfig`) — checked in one place if/when
|
||||
seller-aware behavior is built, never as ad hoc `if (tenant.id === 'x')`
|
||||
or similar marketplace-specific conditionals anywhere in feature code.
|
||||
This follows the same capability-guard discipline ADR-009 already
|
||||
established for feature flags.
|
||||
|
||||
## Backward Compatibility (non-negotiable)
|
||||
|
||||
- `modules` and `seller` are both optional fields on `BootstrapConfig`.
|
||||
Existing marketplaces whose bootstrap response never includes them are
|
||||
unaffected — untyped-absent is not a special case to handle, it's the
|
||||
default.
|
||||
- `DEFAULT_PLATFORM_MODULES_CONFIG` defaults `sellerManagement.enabled` to
|
||||
`false`. A marketplace that has never heard of this feature, and a
|
||||
marketplace where the backend explicitly disables it, behave identically:
|
||||
no new routes, no new menu entries, no new API calls, no visual change.
|
||||
- No existing `BootstrapConfig` field, route, guard, or component changes as
|
||||
a result of this ADR. This ADR adds types; it changes nothing that already
|
||||
runs.
|
||||
|
||||
## Scope of this ADR
|
||||
|
||||
This ADR and its accompanying typed contracts (`PlatformModulesConfig`,
|
||||
`SellerManagementModuleConfig`, `SellerConfig`) are **architecture only**:
|
||||
|
||||
- No UI is introduced — no seller-facing pages, no admin seller-management
|
||||
screens, no navigation entries.
|
||||
- No backend is implemented — no endpoints, no seller data model, no
|
||||
resolution logic.
|
||||
- No business logic is introduced — no seller CRUD, no seller-scoped
|
||||
permissions, no seller onboarding flow.
|
||||
|
||||
Those are all future work, gated behind `modules.sellerManagement.enabled`,
|
||||
and each will need its own ADR/implementation pass once the module is
|
||||
actually being built out (routing strategy under a seller scope, admin UI,
|
||||
backend data model and resolution, permission model for seller-level roles).
|
||||
This ADR exists so that future work has a typed foundation to build on
|
||||
without retrofitting the platform/marketplace hierarchy after the fact.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- Marketplaces that don't need multi-vendor support pay zero cost — no new
|
||||
code path executes, no new field is even present in their bootstrap
|
||||
response.
|
||||
- Future Seller Management work has a settled hierarchy and typed contract
|
||||
to build against instead of ad hoc per-feature decisions about where
|
||||
"seller" fits.
|
||||
- Consistent with the platform's existing capability-guard discipline
|
||||
(ADR-009) — one flag, checked in one place, not scattered conditionals.
|
||||
|
||||
Negative:
|
||||
|
||||
- Adds two optional fields to `BootstrapConfig` that most of the codebase
|
||||
will never populate — acceptable, matches the existing pattern of several
|
||||
other optional bootstrap fields (`header?`, `catalog?`, `layout?`, etc.).
|
||||
- Defers real design decisions (seller-scoped routing, seller admin
|
||||
permissions, seller data ownership) to whenever the module is actually
|
||||
implemented — intentional; this ADR does not pre-invent that design.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- No component, facade, or service may branch on marketplace identity or
|
||||
seller identity directly. All seller-aware behavior, once built, must
|
||||
check `modules.sellerManagement.enabled` (or a capability-guard built on
|
||||
top of it) as the single gate.
|
||||
- No frontend code may attempt to resolve which seller is active by itself
|
||||
(URL parsing, local storage, guessed convention, etc.) — that information
|
||||
only ever comes from `BootstrapConfig.seller`, backend-resolved, exactly
|
||||
like tenant resolution today.
|
||||
- Any future work that adds seller-facing routes, UI, or backend calls must
|
||||
keep all of it inert and unreachable while `modules.sellerManagement.
|
||||
enabled` is `false`, with no exception.
|
||||
@@ -1,640 +0,0 @@
|
||||
> **ARCHIVED 2026-07-26.** Historical sprint log (Sprint 19-28 admin backoffice build-out). Living admin architecture reference now lives in [`docs/BACKEND.md`](../BACKEND.md) (backend contract) and [`docs/ARCHITECTURE.md`](../ARCHITECTURE.md) (frontend architecture). Kept for history only.
|
||||
|
||||
# Marketplace Admin Dashboard - Sprint 19
|
||||
|
||||
## Scope
|
||||
|
||||
Sprint 19 adds the production Admin Dashboard and makes it the default landing
|
||||
page for the admin area. It also wires the previously-unrouted `admin/products`
|
||||
feature and adds route placeholders for backoffice sections that don't have a
|
||||
feature built yet.
|
||||
|
||||
## Routing
|
||||
|
||||
All admin routes live under `/:lang/backoffice/**` (`app.routes.ts`), guarded
|
||||
by the existing `adminAuthGuard` (`core/admin-auth/admin-auth.guard.ts`):
|
||||
|
||||
```text
|
||||
/:lang/backoffice -> redirects to dashboard
|
||||
/:lang/backoffice/dashboard -> AdminDashboardPageComponent
|
||||
/:lang/backoffice/products -> AdminProductsListPageComponent
|
||||
/:lang/backoffice/products/create -> AdminProductEditorPageComponent
|
||||
/:lang/backoffice/products/:id/edit -> AdminProductEditorPageComponent
|
||||
/:lang/backoffice/products/:id/duplicate -> AdminProductEditorPageComponent
|
||||
/:lang/backoffice/categories -> AdminCategoriesListPageComponent
|
||||
/:lang/backoffice/categories/create -> AdminCategoryEditorPageComponent
|
||||
/:lang/backoffice/categories/:id/edit -> AdminCategoryEditorPageComponent
|
||||
/:lang/backoffice/static-pages -> BackofficeComingSoonPageComponent
|
||||
/:lang/backoffice/transactions -> BackofficeComingSoonPageComponent
|
||||
/:lang/backoffice/orders -> BackofficeComingSoonPageComponent
|
||||
/:lang/backoffice/media -> BackofficeComingSoonPageComponent
|
||||
```
|
||||
|
||||
`admin/products` (`features/admin/products/`) was already fully implemented
|
||||
in an earlier sprint but was never wired into `app.routes.ts` and its internal
|
||||
navigation hardcoded the `ru` locale segment. Both are fixed in this sprint:
|
||||
routes are wired, and `admin-products-list-page.component.ts` /
|
||||
`admin-product-editor-page.component.ts` now build the locale segment from
|
||||
`LanguageService.currentLanguage()`.
|
||||
|
||||
**Dashboard as default admin page:** on successful admin Telegram QR login,
|
||||
`TelegramLoginComponent` (`mode="admin"`) navigates to
|
||||
`/:lang/backoffice/dashboard` (`components/telegram-login/telegram-login.component.ts`).
|
||||
The `backoffice` route's empty path also redirects to `dashboard`, so any bare
|
||||
`/:lang/backoffice` link lands there too.
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
src/app/features/admin/dashboard/
|
||||
models/ admin-dashboard.model.ts
|
||||
services/ admin-dashboard-metrics.gateway.interface.ts
|
||||
admin-dashboard-metrics.local.gateway.ts
|
||||
admin-dashboard-metrics-gateway.token.ts
|
||||
admin-dashboard-history.service.ts
|
||||
facade/ admin-dashboard.facade.ts
|
||||
components/ admin-dashboard-card.component.*
|
||||
admin-dashboard-quick-actions.component.*
|
||||
admin-dashboard-activity.component.*
|
||||
admin-dashboard-health.component.*
|
||||
pages/ admin-dashboard-page.component.*
|
||||
|
||||
src/app/features/backoffice/shared/
|
||||
backoffice-coming-soon-page.component.*
|
||||
```
|
||||
|
||||
Follows the existing container/facade/service split (ADR-006, ADR-007):
|
||||
`AdminDashboardPageComponent` is the container, `AdminDashboardFacade` owns
|
||||
orchestration, presentational card/quick-actions/activity/health components
|
||||
take only `@Input()`s and have no HttpClient/localStorage/route access.
|
||||
|
||||
### Data sources (future-ready)
|
||||
|
||||
Cards never read `ConfigService`, `localStorage`, or an HTTP client directly -
|
||||
everything routes through `AdminDashboardFacade`, which composes:
|
||||
|
||||
- **`ProjectEditorFacade`** (already existed) - `bootstrap`, `status`,
|
||||
`lastSavedAt`, `lastPublishedAt` (new, see below), `validationIssues`,
|
||||
`homepageWidgets`. Backs Marketplace Status, Project Name, Current Theme,
|
||||
Languages, Last Publish, Last Draft Save, Bootstrap Version, Active Layout,
|
||||
Enabled Widgets, and the System Health checks.
|
||||
- **`ADMIN_DASHBOARD_METRICS_GATEWAY`** (new `InjectionToken`, same swap
|
||||
pattern as `BACKOFFICE_DATA_PROVIDER`) - defaults to
|
||||
`AdminDashboardMetricsLocalGateway`, which composes
|
||||
`BackofficeDataService.loadCategories()/loadProducts()` (already used by
|
||||
`AdminProductsLocalGateway`) into counts. Backs Categories Count and
|
||||
Products Count. Swapping to a real dashboard-metrics endpoint later means
|
||||
implementing `AdminDashboardMetricsGateway` and rebinding the token - the
|
||||
facade and cards don't change.
|
||||
- **`AdminDashboardHistoryService`** (new) - localStorage-backed activity log,
|
||||
scoped per tenant, same pattern as `ProjectEditorDraftStorageService`. The
|
||||
facade appends an entry whenever `lastSavedAt`/`lastPublishedAt` change
|
||||
(detected via an `effect()`, primed on first read so the initial bootstrap
|
||||
load doesn't get logged as an activity event). Backs Recent Activity.
|
||||
|
||||
### Orders / Revenue
|
||||
|
||||
No backend or local data model exists for orders or revenue anywhere in the
|
||||
codebase (`features/backoffice/orders` is an empty placeholder folder). These
|
||||
two cards render an honest **`pending-backend`** card state ("Awaiting backend
|
||||
integration") rather than fabricated numbers - not a "no data" empty state,
|
||||
since the gap is structural, not a temporarily-empty dataset.
|
||||
|
||||
### Card states
|
||||
|
||||
`AdminDashboardCardComponent` (`components/admin-dashboard-card.component.ts`)
|
||||
renders one of: `loading` (skeleton), `empty`, `error`, `pending-backend`, or
|
||||
the ready value + optional subtitle. The container computes each card's status
|
||||
per data source (bootstrap not yet loaded -> `loading`; metrics gateway error
|
||||
-> `error`; no supported locales -> `empty`; Orders/Revenue -> always
|
||||
`pending-backend`).
|
||||
|
||||
### System Health
|
||||
|
||||
`ProjectValidator` (`features/project-editor/services/project-validator.service.ts`)
|
||||
already covered 5 of the 6 required checks. This sprint added two more:
|
||||
|
||||
- `translationIssues()` - flags a supported non-default locale missing a
|
||||
header nav label translation or a static-page `translations` entry.
|
||||
- `layoutIssues()` - flags `bootstrap.layout.type` or any section's
|
||||
`layout.strategy` that isn't one of the known enum values
|
||||
(`PlatformLayoutType` / `SectionLayoutStrategy`). Runtime validation matters
|
||||
here because bootstrap JSON isn't type-checked at load time.
|
||||
|
||||
Dashboard mapping (`AdminDashboardFacade.healthChecks`):
|
||||
|
||||
| Dashboard label | Validator code |
|
||||
|---|---|
|
||||
| Bootstrap valid | structural: `bootstrap !== null && schemaVersion` set |
|
||||
| Configuration valid | no validation issues at all |
|
||||
| Missing translations | `missing-translations` (new) |
|
||||
| Invalid colors | `invalid-colors` (existing) |
|
||||
| Invalid widget references | `missing-widget` (existing - a homepage widget with no `type`) |
|
||||
| Invalid layouts | `invalid-layouts` (new) |
|
||||
|
||||
### Quick Actions
|
||||
|
||||
Static list in `AdminDashboardFacade` (`route` arrays relative to the lang
|
||||
root); the page component prefixes the current locale
|
||||
(`LanguageService.currentLanguage()`) before binding `routerLink`. Categories,
|
||||
Static Pages, Transactions, Orders, and Media Library currently land on
|
||||
`BackofficeComingSoonPageComponent` since those features aren't built yet -
|
||||
this is a routing placeholder, not a dashboard card placeholder.
|
||||
|
||||
### `lastPublishedAt` (ProjectEditorFacade change)
|
||||
|
||||
Before this sprint, `publish()` only updated `lastSavedAt`, so "last draft
|
||||
save" and "last publish" were indistinguishable after a publish. Added
|
||||
`lastPublishedAt: number | null` to `ProjectEditorState` /
|
||||
`ProjectEditorFacade`, set only inside `publish()`. `lastSavedAt` behavior is
|
||||
unchanged (still updated by both `save()` and `publish()`).
|
||||
|
||||
## Sprint 20 - Category Management
|
||||
|
||||
`features/admin/categories/` (model/gateway/facade/pages/components), same
|
||||
container/facade/service split as `admin/products` and `admin/dashboard`:
|
||||
|
||||
```text
|
||||
src/app/features/admin/categories/
|
||||
models/ admin-category.model.ts
|
||||
services/ admin-categories-gateway.interface.ts
|
||||
admin-categories-local.gateway.ts
|
||||
admin-categories-form.factory.ts
|
||||
facade/ admin-categories.facade.ts
|
||||
guards/ admin-category-dirty.guard.ts
|
||||
components/ admin-categories-list.component.*
|
||||
admin-category-form.component.*
|
||||
pages/ admin-categories-list-page.component.ts
|
||||
admin-category-editor-page.component.ts
|
||||
```
|
||||
|
||||
- **Hierarchy**: `AdminCategory.parentId` (nullable). List page renders a
|
||||
flattened, indented tree (`AdminCategoriesFacade.rootCategories()` /
|
||||
`childrenOf(id)`); the editor's parent `<select>` excludes the category
|
||||
itself and its descendants to prevent cycles.
|
||||
- **Reordering**: native HTML5 drag-and-drop in
|
||||
`admin-categories-list.component.ts` (`draggable`, `dragstart`/`drop`),
|
||||
persists via `AdminCategoriesFacade.reorder()` which just rewrites `order`.
|
||||
- **Delete/restore**: soft delete (`deletedAt` timestamp). Blocked
|
||||
client-side (`facade.canDelete()`) if the category has children or
|
||||
`itemsCount > 0`; list has an "include deleted" filter with a Restore
|
||||
action for soft-deleted rows.
|
||||
- **Draft/publish**: `status: 'draft' | 'published'`, set by the editor's
|
||||
"Save Draft" vs "Publish" buttons (`AdminCategoriesFacade.saveDraft(publish)`).
|
||||
- **Local draft recovery + unsaved-changes guard**: every `updateDraft()`
|
||||
call persists the in-progress category to `localStorage` under
|
||||
`admin-category-draft:<id>` (via the existing `LocalStorageService`,
|
||||
same pattern as Project Editor autosave); the editor reloads that draft
|
||||
ahead of the saved value if present, and is cleared on save.
|
||||
`adminCategoryDirtyGuard` (mirrors `projectEditorDirtyGuard`) blocks
|
||||
navigation away from an unsaved edit with `window.confirm`.
|
||||
- **Image**: reuses the existing `MediaPickerComponent` (same one used by
|
||||
Media Manager) rather than a free-text URL field.
|
||||
- **Seed data**: `AdminCategoriesLocalGateway` seeds its in-memory cache from
|
||||
`BackofficeDataService.loadCategories()` (`CategoryCardConfig`, currently
|
||||
flat/no hierarchy) - same swappable-provider pattern as
|
||||
`AdminProductsLocalGateway`.
|
||||
- **Not yet wired**: `admin/products`' category `<select>` still uses
|
||||
`AdminProductsGateway.loadCategories()` (its own `AdminProductCategoryOption`
|
||||
seed), not `AdminCategoriesGateway` - unifying them is Sprint 21 scope
|
||||
(`docs/SPRINT-PLAN.md`).
|
||||
|
||||
## Sprint 21 - Product Management completion
|
||||
|
||||
- **Categories now real**: `AdminProductsLocalGateway` seeds its category dropdown from `AdminCategoriesLocalGateway.loadCategories()` (Sprint 20) instead of raw `BackofficeDataService.loadCategories()` - product `categoryId` now points at real admin-managed categories.
|
||||
- **Archive/restore**: `AdminProduct.archived` (soft, distinct from `visible`). List has an "include archived" filter + per-row Archive/Restore action; archived products excluded by default (mirrors categories' `deletedAt`/restore pattern).
|
||||
- **Barcode**: added alongside `sku`.
|
||||
- **Variants**: lightweight `AdminProductVariant[]` (`name`/`price`/`quantity`), edited as `name|price|quantity` lines (same textarea-parse convention as `specifications`/`attributes`). Not a full options-matrix variant system - scoped to what the model/backend contract actually needs today.
|
||||
- **Related products**: `relatedProductIds: string[]`, checkbox picker in the editor sourced from `AdminProductFormComponent`'s `allProducts` input - which is `AdminProductsFacade.products()`, i.e. whatever page is currently loaded in the facade (usually primed by navigating from the list). Not a full catalog search; fine for the current mock-data scale, worth revisiting if `AdminProductsLocalGateway` is ever swapped for a real API with more than a page of products.
|
||||
- **Gallery**: `media.gallery` now built via the shared `MediaPickerComponent` (add/remove thumbnails) instead of a raw URL textarea; `media.images`/`media.videos` unchanged (still textarea, out of this ticket's scope).
|
||||
- **Preview**: simple read-only line in the editor showing computed discounted price.
|
||||
- **Infinite scroll**: `AdminProductsFacade.infiniteScroll` toggle - when on, `loadMore()` appends the next page to `products()` instead of replacing it; pagination UI swaps for a "Load more" button. Off by default (existing paginated behavior unchanged).
|
||||
|
||||
## Sprint 22 - Media System hardening
|
||||
|
||||
`core/media/` (`MediaRepository` abstraction, `MockMediaRepository` IndexedDB
|
||||
implementation) + `features/backoffice/media/` + the shared
|
||||
`shared/media/media-picker/`:
|
||||
|
||||
- **Folders**: flat `folder?: string` tag on `MediaAsset` (no nesting) -
|
||||
"New folder" just sets the active filter to a name typed via
|
||||
`window.prompt` (mirrors the `window.confirm` pattern already used for
|
||||
destructive actions elsewhere); the folder is created implicitly the next
|
||||
time something uploads into it. `MediaRepository.listFolders()` derives
|
||||
the folder list from existing records rather than a separate folder
|
||||
entity - intentionally light, matches the flat-storage reality of an
|
||||
IndexedDB mock.
|
||||
- **Tags**: already existed on `MediaAsset`; added an edit affordance
|
||||
(`window.prompt`, comma-separated) and `MediaLibraryFacade.updateTags()`.
|
||||
- **Validation**: `MockMediaRepository.validateFile()` rejects anything over
|
||||
10MB or outside the allow-list (`jpeg/png/webp/gif/svg+xml/pdf`); errors
|
||||
now propagate as real messages through `MediaLibraryFacade.error` (both
|
||||
`media-library-page` and `media-picker` display it - previously upload
|
||||
failures were swallowed into a generic string).
|
||||
- **SVG sanitization**: `sanitizeSvg()` strips `<script>` tags and
|
||||
`on*="..."` attributes from uploaded SVG markup before storing it, since
|
||||
SVG is the one accepted format that can carry inline script.
|
||||
- **Compression/resize**: raster images (not SVG/GIF) are downscaled to a
|
||||
2000px max dimension and re-encoded (JPEG/PNG, quality 0.85) via
|
||||
`<canvas>` before being stored - client-side only, no crop UI. A full
|
||||
interactive cropper was out of scope for this ticket; revisit if a real
|
||||
design need for manual cropping shows up.
|
||||
- **Reuse confirmed**: `MediaPickerComponent` is now wired into Category
|
||||
images (Sprint 20), Product gallery (Sprint 21), and Project Editor
|
||||
branding (logo / compact logo / favicon, this sprint) - one media library
|
||||
for the whole platform, per the sprint goal. Static Pages editor has no
|
||||
image fields to wire (confirmed, not a gap). Hero image: no dedicated
|
||||
hero-image field exists in `BootstrapConfig` today - nothing to wire.
|
||||
- **Storage abstraction**: already existed via `MediaRepository` (abstract
|
||||
class + DI token `providedIn: 'root'` on `MockMediaRepository`) - swapping
|
||||
to a real CDN/backend means implementing `MediaRepository` against a real
|
||||
API and rebinding the provider; no consumer (`MediaLibraryFacade`,
|
||||
`MediaPickerComponent`, or any of the pickers above) changes.
|
||||
|
||||
## Sprint 22 - Media System hardening
|
||||
|
||||
`core/media/` (`MediaRepository` abstraction, `MockMediaRepository` IndexedDB
|
||||
implementation) + `features/backoffice/media/` + the shared
|
||||
`shared/media/media-picker/`:
|
||||
|
||||
- **Folders**: flat `folder?: string` tag on `MediaAsset` (no nesting) -
|
||||
"New folder" just sets the active filter to a name typed via
|
||||
`window.prompt` (mirrors the `window.confirm` pattern already used for
|
||||
destructive actions elsewhere); the folder is created implicitly the next
|
||||
time something uploads into it. `MediaRepository.listFolders()` derives
|
||||
the folder list from existing records rather than a separate folder
|
||||
entity - intentionally light, matches the flat-storage reality of an
|
||||
IndexedDB mock.
|
||||
- **Tags**: already existed on `MediaAsset`; added an edit affordance
|
||||
(`window.prompt`, comma-separated) and `MediaLibraryFacade.updateTags()`.
|
||||
- **Validation**: `MockMediaRepository.validateFile()` rejects anything over
|
||||
10MB or outside the allow-list (`jpeg/png/webp/gif/svg+xml/pdf`); errors
|
||||
now propagate as real messages through `MediaLibraryFacade.error` (both
|
||||
`media-library-page` and `media-picker` display it - previously upload
|
||||
failures were swallowed into a generic string).
|
||||
- **SVG sanitization**: `sanitizeSvg()` strips `<script>` tags and
|
||||
`on*="..."` attributes from uploaded SVG markup before storing it, since
|
||||
SVG is the one accepted format that can carry inline script.
|
||||
- **Compression/resize**: raster images (not SVG/GIF) are downscaled to a
|
||||
2000px max dimension and re-encoded (JPEG/PNG, quality 0.85) via
|
||||
`<canvas>` before being stored - client-side only, no crop UI. A full
|
||||
interactive cropper was out of scope for this ticket; revisit if a real
|
||||
design need for manual cropping shows up.
|
||||
- **Reuse confirmed**: `MediaPickerComponent` is now wired into Category
|
||||
images (Sprint 20), Product gallery (Sprint 21), and Project Editor
|
||||
branding (logo / compact logo / favicon, this sprint) - one media library
|
||||
for the whole platform, per the sprint goal. Static Pages editor has no
|
||||
image fields to wire (confirmed, not a gap). Hero image: no dedicated
|
||||
hero-image field exists in `BootstrapConfig` today ('hero' only appears
|
||||
as a `SectionLayoutStrategy` enum value) - nothing to wire.
|
||||
- **Storage abstraction**: already existed via `MediaRepository` (abstract
|
||||
class + DI token `providedIn: 'root'` on `MockMediaRepository`) - swapping
|
||||
to a real CDN/backend means implementing `MediaRepository` against a real
|
||||
API and rebinding the provider; no consumer (`MediaLibraryFacade`,
|
||||
`MediaPickerComponent`, or any of the pickers above) changes.
|
||||
|
||||
## Sprint 23 - Orders (mock/local)
|
||||
|
||||
`features/admin/orders/` (model/gateway/facade/pages), same
|
||||
container/facade/service split as the rest of `admin/*`:
|
||||
|
||||
- **No real data source exists for orders anywhere in this repo** (already
|
||||
called out in Sprint 19's dashboard gap and `docs/BACKEND_API.md#611-backoffice--orders-planned`) -
|
||||
`AdminOrdersLocalGateway` seeds 24 deterministic synthetic orders in
|
||||
memory (cycling through all statuses/customers) rather than reading from
|
||||
`BackofficeDataService`, since there is nothing there to read. This is
|
||||
explicitly a placeholder to unblock the admin UI, not a real mock of
|
||||
production order volume.
|
||||
- List: search (order number/customer/email), status filter, pagination,
|
||||
CSV export (client-side `Blob` download, no server round-trip).
|
||||
- Detail: customer/payment/shipping info, itemized line items + total,
|
||||
status timeline, change-status dropdown, refund request and cancel
|
||||
(both `window.confirm`-gated), customer-visible notes vs internal-only
|
||||
notes (two separate free-text logs), print invoice via `window.print()`
|
||||
with a `@media print` rule hiding all non-invoice chrome (`.no-print`) -
|
||||
no PDF generation library, deliberately minimal.
|
||||
- Wired into `/:lang/backoffice/orders` and `/:lang/backoffice/orders/:id`,
|
||||
replacing the coming-soon placeholder.
|
||||
|
||||
## Sprint 24 - Transactions (mock/local)
|
||||
|
||||
`features/admin/transactions/`. `AdminTransactionsLocalGateway` derives its
|
||||
mock data from `AdminOrdersLocalGateway`'s 24 seeded orders (one
|
||||
transaction per order, deterministic type/status/method assignment) rather
|
||||
than a separate synthetic dataset - keeps order numbers/totals consistent
|
||||
between the two mock feature areas.
|
||||
|
||||
- List: search, status filter, type filter (payment/refund/qr_payment),
|
||||
pagination, CSV export.
|
||||
- Retry failed transactions (`status: 'failed' -> 'retried'`, appends an
|
||||
audit entry).
|
||||
- Fraud flag toggle per transaction.
|
||||
- Audit log: each transaction carries its own `audit: AdminTransactionAuditEntry[]`
|
||||
(creation, retries, fraud-flag changes), viewed via a dialog - this is a
|
||||
per-transaction audit trail, not the system-wide audit/security log
|
||||
planned for Sprint 26 (Monitoring); the two are intentionally separate
|
||||
scopes.
|
||||
- Wired into `/:lang/backoffice/transactions`, replacing the coming-soon
|
||||
placeholder.
|
||||
|
||||
## Sprint 25 - Users & Roles (mock/local)
|
||||
|
||||
`features/admin/users/`. Single consolidated page (`admin-users-page`) at
|
||||
`/:lang/backoffice/users` - not previously in the Quick Actions list or
|
||||
routes at all, this is a net-new admin section.
|
||||
|
||||
- **Users**: name, Telegram username, `scope` (`marketplace` vs `office`
|
||||
admin - distinguishes tenant-level owners/admins from internal staff),
|
||||
role, status (`active`/`invited`/`suspended`), last login. Role change is
|
||||
an inline `<select>`; suspend/reactivate is confirm-gated for suspend
|
||||
only.
|
||||
- **Roles/permissions**: 4 built-in roles (`owner`/`admin`/`editor`/`viewer`)
|
||||
with a flat permission-string list (`products.manage`, `*` for owner,
|
||||
etc.) - a real permission catalog and custom-role creation don't exist,
|
||||
intentionally scoped down to what's needed to demonstrate the model.
|
||||
- **Invitations**: email + role + scope form, pending list with revoke.
|
||||
No email actually sends - `AdminUsersLocalGateway.inviteUser()` only
|
||||
creates the local record.
|
||||
- **Passwordless login**: already existed before this sprint -
|
||||
`AdminAuthService`'s Telegram QR flow (`docs/ADMIN.md`'s existing admin
|
||||
login section, `docs/BACKEND_API.md#25-the-admin-authorization-gap-critical--security-relevant-unresolved`). This sprint's Users page links
|
||||
to it via a hint, doesn't reimplement it.
|
||||
- **Session manager / device manager**: per-user session list (device, IP,
|
||||
last active, current-session badge) with per-session revoke, mocked
|
||||
(`AdminUsersLocalGateway.loadSessions()` fabricates 2 sessions per user
|
||||
on first view) - the real `AdminAuthService`/session-cookie flow only
|
||||
ever tracks the *current* browser's session, so multi-device session
|
||||
listing has no real backend counterpart yet (see `docs/BACKEND_API.md#25-the-admin-authorization-gap-critical--security-relevant-unresolved`).
|
||||
- **Audit**: per-user audit log (role/status changes), same dialog pattern
|
||||
as Sprint 24's per-transaction audit - not the system-wide security/audit
|
||||
log planned for Sprint 26.
|
||||
- Wired into `AdminDashboardFacade`'s Quick Actions list (`dashboard.actionUsers`
|
||||
-> `/:lang/backoffice/users`).
|
||||
|
||||
## Sprint 26 - Monitoring (mock/local, health reuses real data)
|
||||
|
||||
`features/admin/monitoring/`, single page at `/:lang/backoffice/monitoring`
|
||||
(new Dashboard Quick Action).
|
||||
|
||||
- **Health**: reuses `AdminDashboardFacade.healthChecks` directly (the same
|
||||
real, non-mocked bootstrap-validation checks from Sprint 19's dashboard)
|
||||
instead of duplicating the logic - this is the one section on this page
|
||||
backed by real data.
|
||||
- **Audit / security / login / failed-login / API / error / warning
|
||||
events**: one unified `AdminMonitoringEvent` feed (`category` + `level`
|
||||
discriminators) with category filter + search, seeded with 40
|
||||
deterministic synthetic entries by `AdminMonitoringLocalGateway` - no
|
||||
logging backend exists anywhere in this system, so there is nothing real
|
||||
to read from.
|
||||
- **Queue monitoring**: 3 mock named queues with depth + status.
|
||||
- **Webhook monitoring**: mock delivery log (endpoint/event/status/time).
|
||||
- This is deliberately a separate, system-wide log from the two
|
||||
narrower-scoped audit trails added earlier: Sprint 24's per-transaction
|
||||
audit and Sprint 25's per-user audit. No consolidation attempted - they
|
||||
track different things.
|
||||
|
||||
## Sprint 27 - Analytics
|
||||
|
||||
`features/admin/analytics/`, net-new `/:lang/backoffice/analytics` route
|
||||
+ Dashboard Quick Action.
|
||||
|
||||
- **Real, derived data**: revenue/orders/avg-order-value/sales-over-time
|
||||
chart/top-products are computed by composing the existing
|
||||
`AdminOrdersLocalGateway` (Sprint 23's seeded mock orders) - not a
|
||||
separate fabricated dataset. Products/Categories counts come from
|
||||
`AdminProductsLocalGateway`/`AdminCategoriesLocalGateway`. All of this is
|
||||
still ultimately backed by mock order/product/category data (per those
|
||||
sprints), but the *aggregation* is real arithmetic over that data, not
|
||||
invented numbers.
|
||||
- **Visitors, funnels, heatmaps**: no analytics/tracking pipeline exists
|
||||
anywhere in this codebase, so these render an explicit
|
||||
"Awaiting backend integration" (`pending-backend`) badge, same convention
|
||||
as the Sprint 19 dashboard's Orders/Revenue cards before Sprint 23 -
|
||||
not fabricated numbers, not a generic empty state.
|
||||
- **Chart**: a plain inline `<div>`-bar chart driven by `[style.height.%]`,
|
||||
no charting library pulled in - reasonable for one sales-over-time series
|
||||
at this scale; revisit if more chart types are actually needed.
|
||||
- **Date ranges**: 7/30/90-day toggle filters orders by `createdAt`.
|
||||
- **Export**: CSV of the sales series (client-side `Blob` download, same
|
||||
pattern as Orders/Transactions).
|
||||
|
||||
## Sprint 28 - Marketplace Polish
|
||||
|
||||
Full scope per `docs/SPRINT-PLAN.md`: Lighthouse/a11y sweep, animations,
|
||||
skeleton/empty/error state consistency, responsive fixes, SEO/meta/social
|
||||
preview/robots/sitemap. Landed across two commits in the same session (an
|
||||
earlier, narrower "admin/*-only" pass, then this session's follow-up
|
||||
completing the rest of the brief) - this section describes the combined,
|
||||
final result, not just the later commit.
|
||||
|
||||
- **Design-system consistency (skeleton/empty states)**: audited every
|
||||
admin section built in Sprints 20-27 against the shared `app-skeleton` /
|
||||
`app-empty-state` primitives (`shared/ui/skeleton`, `shared/ui/empty-state`,
|
||||
see their own "add reusable ... primitive" commits). Before this sprint,
|
||||
`admin/products`, `admin/users`, `admin/monitoring`, and `admin/analytics`
|
||||
had a `loading` facade signal that was never read in the template (blank
|
||||
table during fetch, no empty-state fallback); `admin/categories`,
|
||||
`admin/orders`, `admin/transactions`, and the media library already had
|
||||
`app-empty-state` but no loading skeleton; `admin/dashboard`'s card
|
||||
component used a hand-rolled shimmer `<div>` + ad-hoc `<p>` text that
|
||||
pre-dated the shared primitives. Fixed: all eight now show `app-skeleton`
|
||||
rows/cards while `loading()` is true, then either `app-empty-state` (new
|
||||
`adminProducts.emptyTitle`/`adminUsers.emptyTitle`/
|
||||
`adminMonitoring.eventsEmptyTitle`/`adminAnalytics.topProductsEmptyTitle`
|
||||
+ description keys added to `translations.ts`/`en.ts`/`ru.ts`/`hy.ts`) or
|
||||
the populated table. `admin-dashboard-card.component.html`'s loading case
|
||||
now renders `<app-skeleton shape="rect" height="24px" width="60%" />`
|
||||
instead of its own shimmer CSS (removed the now-dead
|
||||
`dashboard-card__skeleton` rule + keyframes). Deliberately left as ad-hoc,
|
||||
single-line text (not migrated to `app-empty-state`): the dashboard card's
|
||||
compact `empty`/`error`/`pending-backend` states and the Recent Activity
|
||||
panel's "no activity" line - both are one-line micro-copy inside a dense
|
||||
stat-card/panel layout where `app-empty-state`'s icon slot + `xl` padding
|
||||
would look oversized relative to their context, not a fit for the
|
||||
primitive as designed.
|
||||
- **Accessibility**: every bare `<select>` across `admin/categories`,
|
||||
`admin/products`, `admin/orders`, `admin/transactions`, `admin/users`,
|
||||
and `admin/monitoring` that wasn't already inside a `<label>` (which
|
||||
provides implicit association) now has an explicit `aria-label`. Selects
|
||||
already nested in `<label>` (e.g. product form's category/stock-status
|
||||
selects, category form's parent select) were left as-is - already
|
||||
correct. Manual audit otherwise: `DialogComponent` (`shared/ui/dialog/`)
|
||||
already had a real focus trap, Escape-to-close, `aria-modal`, and
|
||||
`aria-label` from an earlier sprint - no changes needed. Every `<img>` in
|
||||
`src/app/**` was checked for missing `alt` (grepped for `<img` without an
|
||||
`alt`/`[alt]`/`[attr.alt]` binding) - none found; all images already have
|
||||
real or bound alt text.
|
||||
- **Animations**: added a global `prefers-reduced-motion: reduce` override
|
||||
in `src/styles.scss` that neutralizes animation/transition durations and
|
||||
smooth-scroll everywhere, so the many existing hover transforms
|
||||
(`.card:hover`, `.btn:hover`, `.product-card:hover`), the `.section`
|
||||
fade-in, and every skeleton shimmer respect the OS accessibility setting
|
||||
in one place, rather than requiring each component to opt in individually
|
||||
(a few, like `shared/ui/skeleton`, already had their own local override).
|
||||
- **SEO**: `SeoService.resetToDefaults()` (`src/app/services/seo.service.ts`)
|
||||
previously hardcoded the site-wide `<title>`/description/OG/Twitter
|
||||
defaults (including a reference to a nonexistent `/og-image.jpg`)
|
||||
regardless of tenant. It now reads the real `bootstrap.seo.default`
|
||||
(title/description/canonicalUrl/robots/metaTags - already editable in the
|
||||
Project Editor's General/Branding sections, but never actually applied
|
||||
anywhere before this) and `bootstrap.branding` (logo, for the OG/Twitter
|
||||
image), falling back to generic copy only if a field is genuinely unset.
|
||||
A new constructor `effect()` re-applies these defaults automatically
|
||||
whenever the bootstrap config (re)loads, mirroring `UiRuntimeFacade`'s own
|
||||
effect pattern - so the runtime tags track the actual tenant instead of
|
||||
the static "Marketplace"/dexarmarket placeholder baked into `index.html`
|
||||
(which remains as the pre-JS/no-JS-crawler fallback only, unavoidable
|
||||
without SSR).
|
||||
- **Sitemap/robots**: added `public/sitemap.xml` (new) with the statically-
|
||||
known top-level marketplace routes (home/catalog/search/wishlist/compare)
|
||||
for the default `ru` locale segment, referenced from a new `Sitemap:`
|
||||
directive in `public/robots.txt` (which also now blocks
|
||||
`/*/backoffice`, `/*/edit`, `/*/project-editor`, and `/__diagnostics`
|
||||
from crawling). Documented limitation (not faked): this is a config-driven,
|
||||
multi-tenant platform - locales/categories/products/static pages are only
|
||||
known at runtime per tenant, not enumerable client-side at build time. A
|
||||
real per-tenant sitemap needs a backend/build-time generator - see
|
||||
`docs/BACKEND_API.md#619-sitemap-future--static-baseline-only-today`.
|
||||
- **Responsive**: spot-checked the admin backoffice and customer-facing
|
||||
marketplace at mobile/tablet/desktop widths. `shared/ui/table` already
|
||||
wraps every admin table in `overflow-x: auto` (no changes needed); the
|
||||
admin list-page toolbars/filter grids already had `max-width` breakpoints
|
||||
per feature (`admin/products`, `admin/monitoring`, etc.) - added the same
|
||||
`.skeleton-rows` grid class alongside those existing breakpoints rather
|
||||
than introducing a new layout system.
|
||||
- **Lighthouse**: no live browser/Lighthouse run in this environment (same
|
||||
constraint noted in every prior sprint's admin verification - the guarded
|
||||
admin route is blocked from live click-through here); the SEO/a11y/
|
||||
animation items above are the manual-audit equivalent of what a
|
||||
Lighthouse pass would flag (missing meta tags, missing alt text, motion
|
||||
without a reduced-motion fallback, missing loading feedback).
|
||||
- **Bundle size**: the `700 kB` initial-bundle budget warning (~198 kB over,
|
||||
configured in `angular.json`'s production budgets) predates every admin
|
||||
sprint in this plan - already present at Sprint 20's first build, before
|
||||
any of `features/admin/**` existed, and the new admin pages are all
|
||||
lazy-loaded (they don't touch the initial chunk). Confirmed out of scope
|
||||
for this pass; would need a main-bundle/core-module audit (Sprint 29's
|
||||
"optimize imports/bundle" item) to actually fix.
|
||||
- **Found but deferred to Sprint 29** (see `docs/KNOWN-ISSUES.md`): almost
|
||||
every string across `admin/products`/`admin/categories`/`admin/orders`/
|
||||
`admin/transactions`/`admin/users`/`admin/monitoring`/`admin/analytics`
|
||||
(~178 distinct `adminXxx.*` translate-pipe keys) has no corresponding
|
||||
entry in `translations.ts`/`en.ts`/`ru.ts`/`hy.ts` and renders as a raw
|
||||
key string - the same bug class as the dashboard Quick Actions fix in
|
||||
`1db63ac`, at much larger scale. Sprint 28 only adds the small number of
|
||||
new keys its own empty-state work introduces (see above); authoring the
|
||||
full ~178-key backfill is Sprint 29's explicit "translation validation"
|
||||
scope, not squeezed into this polish pass.
|
||||
|
||||
## Bug-hunt audit pass (2026-07-17)
|
||||
|
||||
Same method as the project-editor audit (`docs/EDITOR.md`'s "Bug-hunt audit
|
||||
pass" section): read `admin/products` and `admin/categories` end to end
|
||||
(facades, gateways, form factories, guards, presentational components), find
|
||||
real reproducible bugs (not cosmetic nitpicks), reproduce each live via
|
||||
`window.ng.getComponent()` on `/:lang/backoffice/{products,categories}/...
|
||||
?devBypassAdmin=true` before fixing, re-verify after. The real backend is
|
||||
unreachable in this environment (same constraint as every prior admin
|
||||
sprint's verification note) - `AdminCategoriesLocalGateway`/
|
||||
`AdminProductsLocalGateway` sit behind `BackofficeDataService`, which itself
|
||||
calls out to an HTTP provider that 404s here, so both facades' `loadList()`
|
||||
error handlers reset to an empty array. Where the list couldn't populate via
|
||||
the real click-through, bugs were reproduced by seeding
|
||||
`facade.categories.set([...])`/`facade.products.set([...])` directly with
|
||||
synthetic rows and driving the exact same facade methods the UI calls - the
|
||||
gateway calls captured are identical either way, since the facade doesn't
|
||||
branch on how its signals got populated.
|
||||
|
||||
Found 2 real bugs in `admin/categories`, both fixed:
|
||||
|
||||
- **Create-category draft recovery was permanently dead + leaked
|
||||
`localStorage` forever.** `AdminCategoriesFacade.startCreate()` generated
|
||||
the draft's id via `category-${Date.now()}` and read/wrote its autosave
|
||||
entry under `admin-category-draft:<that id>`. Since the id is different
|
||||
every single call, a draft written during one "create category" visit can
|
||||
never be found by a later `startCreate()` call (even seconds later, same
|
||||
tab) - the recovery feature the sprint 20 changelog describes ("the editor
|
||||
reloads that draft ahead of the saved value if present") never actually
|
||||
triggered for new categories, only for edits (stable real `id`). Every
|
||||
abandoned create attempt also left an orphaned, never-cleaned
|
||||
`localStorage` entry. Fixed by tracking a `draftStorageKey` field on the
|
||||
facade, set to a fixed `admin-category-draft:new` key in create mode
|
||||
(stable across calls) and to `admin-category-draft:<id>` in edit mode
|
||||
(unchanged, already correct); `saveDraft()`/`discardDraftRecovery()` clear
|
||||
whichever key is current instead of re-deriving it from the (possibly
|
||||
stale) draft id.
|
||||
- Verified live: `updateDraft({title})` -> localStorage key
|
||||
`admin-category-draft:category-<ts1>`; calling `startCreate()` again
|
||||
(simulating navigate-away/back) generated `category-<ts2>` and recovered
|
||||
nothing (`title` reset to `''`, `dirty=false`, old key orphaned). After
|
||||
the fix, the same sequence recovers the title/dirty state correctly under
|
||||
the stable key, and `saveDraft()` clears it.
|
||||
- **Drag-and-drop category reordering silently did nothing (or moved items
|
||||
to the wrong spot) because it wrote duplicate `order` values instead of
|
||||
repositioning.** `AdminCategoriesFacade.reorder(id, targetOrder)` took the
|
||||
dropped-on row's numeric `order` and wrote that exact value onto the
|
||||
dragged category - leaving two siblings tied on the same `order` instead of
|
||||
actually reordering. `AdminCategoriesLocalGateway.loadCategories()` sorts
|
||||
by `left.order - right.order` using `Array.prototype.sort` (stable), so
|
||||
ties break by original array position, not by drop intent - some drags
|
||||
silently no-op. Worse, every seeded category starts at `order: 0`
|
||||
(`AdminCategoriesLocalGateway.toAdminCategory`), so on fresh data *every*
|
||||
drag was a no-op: dropping item C onto item A sent `{ id: 'c', order: 0 }`,
|
||||
which was already A's (and C's) value. Fixed by changing the drag payload
|
||||
to carry the target's `id` (not its ambiguous/duplicable `order` value);
|
||||
`reorder(id, targetId)` now computes the full same-parent sibling sequence
|
||||
with the dragged item spliced into the target's position and persists
|
||||
sequential `0..n-1` order values for every sibling whose order actually
|
||||
changed. Cross-parent drops (`dragged.parentId !== target.parentId`) are a
|
||||
no-op, matching the tree UI's existing scope (no reparent-via-drag support
|
||||
before or after this fix). Updated end to end:
|
||||
`AdminCategoriesListComponent`'s `reorder` output now emits
|
||||
`{ id, targetId }` instead of `{ id, targetOrder }`; the list page binding
|
||||
follows.
|
||||
- Verified live: seeded 3 siblings with distinct orders (0/1/2), dragged
|
||||
the last onto the first. Before the fix, the gateway only received
|
||||
`{ id: 'c', order: 0 }` (tying `a` and `c`). After the fix, the gateway
|
||||
receives the correct 3-way reshuffle: `c:0, a:1, b:2`.
|
||||
|
||||
A third bug, found in the same pass, was fixed in a follow-up commit:
|
||||
`admin-product-form.component` and `admin-category-form.component` both
|
||||
hardcoded their translation-tab locales to `['en', 'ru', 'hy']` instead of
|
||||
reading the tenant's actual configured `supportedLocales` (which live on
|
||||
`ProjectEditorFacade.bootstrap()`, the same source
|
||||
`static-pages-editor.component.ts` already reads correctly) - neither
|
||||
`AdminProductsFacade` nor `AdminCategoriesFacade` depended on project-editor
|
||||
state at all before this. Fixed by giving both facades a `supportedLocales`
|
||||
computed (`bootstrap()?.localization.supportedLocales ?? ['en']`) and an
|
||||
`ensureLocalesLoaded()` that calls `ProjectEditorFacade.loadBootstrap()` if
|
||||
it hasn't loaded yet (same lazy-load pattern
|
||||
`AdminDashboardFacade.ensureLoaded()` already uses for the same dependency);
|
||||
both editor pages call it in their constructor and pass
|
||||
`[locales]="facade.supportedLocales()"` down to the form components, which
|
||||
now iterate a `locales: string[]` `@Input()` instead of the literal array.
|
||||
Verified live: both `facade.supportedLocales()` and the form's bound
|
||||
`locales` changed from the hardcoded `['en','ru','hy']` to the real tenant
|
||||
order `['ru','en','hy']` (default locale first), confirmed by the rendered
|
||||
tab order in both editors.
|
||||
|
||||
The already-documented, deliberately-scoped-down items from earlier sprints
|
||||
(related-products picker limited to the current page, not a full catalog
|
||||
search; the ~178 untranslated `adminXxx.*` i18n keys) were re-confirmed
|
||||
during this pass and are unchanged - see their existing sections above and
|
||||
`docs/KNOWN-ISSUES.md`.
|
||||
|
||||
## Known gaps / backend needs
|
||||
|
||||
- **Dashboard metrics endpoint.** Categories/Products counts are computed
|
||||
client-side from `BackofficeDataService` (itself mock/API-switchable via
|
||||
`BACKOFFICE_DATA_PROVIDER`). A dedicated `/builder/dashboard/summary`-style
|
||||
endpoint would let `AdminDashboardMetricsGateway` return richer data
|
||||
(real-time counts, trend deltas) without touching the facade or cards.
|
||||
- **Orders/Revenue have no backend at all** (see above) - needs an order
|
||||
domain and revenue aggregation before these cards can show real data.
|
||||
- **Recent Activity is local-only**, scoped to the browser/tenant via
|
||||
localStorage (`adminDashboard.activityHistory.v1`), same limitation as the
|
||||
existing draft-save local storage. It will not show another editor's
|
||||
activity until a real audit-log endpoint exists.
|
||||
- **Admin authorization is still not enforced server-side** (see
|
||||
`Project-Editor.md` - "Admin Authentication" section); this sprint does not
|
||||
change that. Nothing new here beyond routing/dashboard.
|
||||
@@ -1,274 +0,0 @@
|
||||
> **ARCHIVED 2026-07-26.** Superseded by [`docs/BACKEND_INTEGRATION.md`](../BACKEND_INTEGRATION.md), the single canonical backend integration document. Kept for history only — do not implement against this file.
|
||||
|
||||
# Admin Authentication — Ed25519 Foundation
|
||||
|
||||
Status: **FRONTEND PREPARED, NOT LIVE.** Everything in this document describes
|
||||
code that exists in `src/app/core/auth/` today, wired to endpoints that do
|
||||
not exist on the backend yet. No route currently requires this flow — the
|
||||
live admin gate remains the Telegram-QR-based `AdminAuthService` /
|
||||
`adminAuthGuard` (`src/app/core/admin-auth/`, documented in
|
||||
`docs/BACKEND_API.md` §2.4–2.5). This module is the
|
||||
integration target once the backend ships the endpoints below.
|
||||
|
||||
Do not point any live route's `canActivate` at `ed25519AuthGuard` until the
|
||||
backend endpoints in §API Contracts exist and have been verified — doing so
|
||||
before then would lock every admin out.
|
||||
|
||||
## 1. Why this exists
|
||||
|
||||
`docs/BACKEND_API.md` §2.5 documents the current system's
|
||||
biggest security gap: admin and customer login hit the *same* Telegram
|
||||
session endpoint, so the backend has no way to distinguish an admin login
|
||||
attempt from a customer one at the moment of login — authorization is
|
||||
effectively unenforced. Ed25519 challenge/response auth closes this by
|
||||
requiring proof of possession of a specific, pre-registered private key
|
||||
before a session is ever issued, instead of "any Telegram account that
|
||||
happened to scan the right QR code."
|
||||
|
||||
## 2. Sequence diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Admin as Admin (browser)
|
||||
participant FE as Frontend (AuthService)
|
||||
participant BE as Backend
|
||||
|
||||
Admin->>FE: Click "Sign in"
|
||||
FE->>BE: GET /api/admin/auth/challenge
|
||||
BE-->>FE: { nonce, issuedAt, expiresAt }
|
||||
FE->>FE: Ed25519KeypairService.sign(nonce)<br/>(WebCrypto, non-extractable private key)
|
||||
FE->>BE: POST /api/admin/auth/verify<br/>{ publicKey, signature, nonce }
|
||||
alt signature valid & publicKey is a provisioned admin key
|
||||
BE-->>FE: 200 { token, refreshToken }
|
||||
FE->>FE: SessionService.activate(tokens)<br/>decode JWT claims, schedule refresh
|
||||
FE-->>Admin: Redirect to /backoffice
|
||||
else invalid signature / unknown key / expired nonce
|
||||
BE-->>FE: 401/403
|
||||
FE-->>Admin: Redirect to /admin-login/error/invalid-signature
|
||||
end
|
||||
```
|
||||
|
||||
### Refresh sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant FE as Frontend (SessionService)
|
||||
participant IC as authInterceptor
|
||||
participant BE as Backend
|
||||
|
||||
Note over FE: Timer fires ~60s before JWT exp
|
||||
FE->>BE: POST /api/admin/auth/refresh { refreshToken }
|
||||
alt refresh token still valid
|
||||
BE-->>FE: 200 { token, refreshToken }
|
||||
FE->>FE: activate(tokens) - reschedules next refresh
|
||||
else refresh token expired/revoked
|
||||
BE-->>FE: 401
|
||||
FE->>FE: SessionService.markExpired()
|
||||
FE-->>FE: Route to /admin-login/error/session-expired
|
||||
end
|
||||
|
||||
Note over IC: Reactive path - any 401 on an admin request
|
||||
IC->>BE: Admin API request (expired token)
|
||||
BE-->>IC: 401
|
||||
IC->>BE: POST /api/admin/auth/refresh (single retry)
|
||||
alt refresh succeeds
|
||||
BE-->>IC: 200 tokens
|
||||
IC->>BE: Retry original request with new token
|
||||
else refresh fails
|
||||
IC-->>FE: Propagate error, route to session-expired
|
||||
end
|
||||
```
|
||||
|
||||
## 3. Ed25519 flow, step by step
|
||||
|
||||
1. **Key generation (once per device):** `Ed25519KeypairService.getOrCreateKeyPair()`
|
||||
generates a non-extractable Ed25519 keypair via `crypto.subtle.generateKey`
|
||||
and persists the `CryptoKey` handles in IndexedDB (`admin-auth-ed25519` DB).
|
||||
The private key is never exported, serialized, or transmitted — by
|
||||
construction, not by convention.
|
||||
2. **Registering the public key with the backend is out of scope for this
|
||||
frontend.** An Owner/Administrator must associate a new device's
|
||||
`publicKeyBase64` with an admin account through some out-of-band
|
||||
mechanism (e.g. a backend admin tool, a one-time enrollment link) before
|
||||
that device can complete step 4. This document does not prescribe that
|
||||
mechanism — it is a backend/ops concern.
|
||||
3. **Challenge:** `GET /api/admin/auth/challenge` returns a fresh `nonce` the
|
||||
client must sign before `expiresAt`.
|
||||
4. **Sign:** the raw `nonce` string is signed with the device's private key
|
||||
(`Ed25519KeypairService.sign`), producing a base64 signature.
|
||||
5. **Verify:** `POST /api/admin/auth/verify` sends `{ publicKey, signature,
|
||||
nonce }`. The backend re-derives the signed message from the nonce it
|
||||
issued, verifies the signature against its own record of that
|
||||
`publicKey → admin account` mapping, and only then issues tokens.
|
||||
6. **Session:** the returned `{ token, refreshToken }` pair is stored
|
||||
(`SessionService`, `localStorage: ed25519AdminToken` /
|
||||
`ed25519AdminRefreshToken`) and the JWT is decoded client-side for
|
||||
`role`/`exp` — decoding only, never signature verification (the frontend
|
||||
has no trusted key to check it against).
|
||||
|
||||
## 4. API contracts
|
||||
|
||||
All under `{environment.authApiUrl}/api/admin/auth` (see
|
||||
`src/environments/environment.ts`). None of these exist on the backend
|
||||
today — this is the contract the frontend was built against, not a
|
||||
confirmed backend spec.
|
||||
|
||||
| Method | Path | Request body | Response | Notes |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/challenge` | — | `200 AuthChallenge` | `{ nonce, issuedAt, expiresAt }`, all ISO 8601 except `nonce` |
|
||||
| POST | `/verify` | `VerifySignatureRequest` | `200 AuthTokenPair` \| `401` \| `403` | `{ publicKey, signature, nonce }` → `{ token, refreshToken }` |
|
||||
| POST | `/refresh` | `RefreshTokenRequest` | `200 AuthTokenPair` \| `401` | `{ refreshToken }` → new pair (rotation expected — old refresh token should be invalidated server-side) |
|
||||
| POST | `/logout` | `{ refreshToken }` | `204` | Should revoke the refresh token server-side; frontend clears local state regardless of response |
|
||||
|
||||
Types: `src/app/core/auth/models/auth-api.model.ts`.
|
||||
|
||||
## 5. JWT claims
|
||||
|
||||
```ts
|
||||
interface JwtClaims {
|
||||
sub: string; // admin account id
|
||||
role: AdminRole; // 'Owner' | 'Administrator' | 'Editor' | 'Support' | 'ReadOnly'
|
||||
iat: number; // seconds since epoch
|
||||
exp: number; // seconds since epoch
|
||||
publicKey: string; // the Ed25519 public key this token was issued for
|
||||
}
|
||||
```
|
||||
|
||||
The frontend decodes these (`JwtService.decode`) for UX only — role-based UI
|
||||
gating, expiry countdowns, refresh scheduling. **Every admin API request
|
||||
must be independently authorized server-side**; a decoded-but-unverified
|
||||
claim is not proof of anything to the backend.
|
||||
|
||||
## 6. Permission model
|
||||
|
||||
Five roles, coarse-grained permission keys (`src/app/core/auth/models/permission.model.ts`):
|
||||
|
||||
| Role | Permissions |
|
||||
|---|---|
|
||||
| Owner | `backoffice.read`, `backoffice.write`, `builder.read`, `builder.write`, `users.manage`, `settings.manage` |
|
||||
| Administrator | `backoffice.read`, `backoffice.write`, `builder.read`, `builder.write`, `users.manage` |
|
||||
| Editor | `backoffice.read`, `backoffice.write`, `builder.read`, `builder.write` |
|
||||
| Support | `backoffice.read` |
|
||||
| ReadOnly | `backoffice.read`, `builder.read` |
|
||||
|
||||
This is deliberately coarse and mirrors the existing bootstrap-level
|
||||
`PermissionsConfig` shape (`src/app/shared/models/config/permissions.model.ts`).
|
||||
Finer-grained, per-domain permissions (e.g. "can edit prices but not delete
|
||||
products") stay server-side until a real permission model exists there —
|
||||
see `docs/BACKEND_API.md` §"Admin-role-required". Use
|
||||
`PermissionService.has(permission)` / `permissionGuard(permission)` to gate
|
||||
UI and routes; never treat a passing client-side check as authorization by
|
||||
itself.
|
||||
|
||||
## 7. Error screens
|
||||
|
||||
Single component (`AuthErrorPageComponent`, `/admin-login/error/:code`)
|
||||
renders all five, keyed by route param:
|
||||
|
||||
| Code | Trigger | User action offered |
|
||||
|---|---|---|
|
||||
| `session-expired` | Refresh token rejected/expired | Sign in again |
|
||||
| `invalid-signature` | `verify` returns 401/403 during login | Try again |
|
||||
| `unauthorized` | Route guard sees no active session | Sign in |
|
||||
| `forbidden` | `permissionGuard` denies (authenticated but insufficient role) | Back to dashboard |
|
||||
| `backend-unavailable` | Network error / 5xx / status 0 | Retry |
|
||||
|
||||
`authErrorCodeFromStatus` (`models/auth-error.model.ts`) maps HTTP status →
|
||||
code: `401→unauthorized`, `403→forbidden`, `0→backend-unavailable`,
|
||||
`5xx→backend-unavailable`, else `unauthorized`. `AuthService.login()`
|
||||
additionally maps any failure during the challenge/sign/verify sequence to
|
||||
`invalid-signature` when it isn't a clearer HTTP-status-derived code.
|
||||
|
||||
## 8. Refresh lifecycle
|
||||
|
||||
- On `SessionService.activate(tokens)`, a timer is scheduled for
|
||||
`max(exp - now - 60s, 5s)` — refresh fires ~60 seconds before expiry so a
|
||||
concurrent request never races an expiring token.
|
||||
- **Proactive path:** the timer fires `AuthService.refresh()` directly.
|
||||
- **Reactive path:** `authInterceptor` catches a 401 on any admin-gated
|
||||
request, attempts one `refresh()`, retries the original request once on
|
||||
success, and routes to `session-expired` on failure. It does not retry
|
||||
more than once — a second 401 after a successful-looking refresh means
|
||||
something is wrong server-side, not a transient race.
|
||||
- `SessionService.restore()` runs on app bootstrap (call
|
||||
`AuthFacade.restoreSession()` from an app initializer once this flow goes
|
||||
live) — reads persisted tokens, decodes claims, and either resumes with a
|
||||
scheduled refresh or marks `expired` without any network call, so a stale
|
||||
session is caught before it reaches any component.
|
||||
|
||||
## 9. Module map
|
||||
|
||||
```
|
||||
src/app/core/auth/
|
||||
├── auth.routes.ts # /admin-login, /admin-login/error/:code
|
||||
├── models/
|
||||
│ ├── auth-api.model.ts # AuthChallenge, VerifySignatureRequest, AuthTokenPair, JwtClaims
|
||||
│ ├── auth-error.model.ts # AuthErrorCode, authErrorCodeFromStatus()
|
||||
│ └── permission.model.ts # AdminRole, Permission, ROLE_PERMISSIONS
|
||||
├── services/
|
||||
│ ├── ed25519-keypair.service.ts # WebCrypto keygen/sign, IndexedDB persistence
|
||||
│ ├── auth-api.service.ts # HttpClient calls to the 4 endpoints in §4
|
||||
│ ├── jwt.service.ts # decode-only JWT parsing
|
||||
│ ├── session.service.ts # token/claims state, persistence, refresh scheduling
|
||||
│ ├── permission.service.ts # role -> permission set
|
||||
│ ├── auth.service.ts # orchestrates challenge -> sign -> verify -> refresh -> logout
|
||||
│ └── auth-facade.service.ts # public surface for components
|
||||
├── interceptors/
|
||||
│ └── auth.interceptor.ts # Authorization: Bearer + 401 refresh-and-retry
|
||||
├── guards/
|
||||
│ ├── ed25519-auth.guard.ts # requires SessionService.isAuthenticated()
|
||||
│ └── permission.guard.ts # permissionGuard(permission) factory
|
||||
└── pages/
|
||||
├── admin-login-page.component.* # sign-in UI
|
||||
└── auth-error-page.component.* # parameterized error screen (§7)
|
||||
```
|
||||
|
||||
`AuthFacade` is the only thing components/pages should depend on;
|
||||
`AuthService`/`SessionService`/`PermissionService` are internal
|
||||
collaborators reachable through it.
|
||||
|
||||
## 10. Cutover plan (when the backend ships)
|
||||
|
||||
1. Verify the four endpoints in §4 against a real backend, including error
|
||||
shapes.
|
||||
2. Register `authInterceptor` in `app.config.ts`'s `withInterceptors([...])`
|
||||
list (currently not registered).
|
||||
3. Decide the relationship to the existing Telegram flow: replace
|
||||
`adminAuthGuard` with `ed25519AuthGuard` outright, or run both and let
|
||||
role/tenant config pick — this is a product decision, not made here.
|
||||
4. Wire `AuthFacade.restoreSession()` into an `APP_INITIALIZER` (or root
|
||||
component `ngOnInit`) so a page refresh restores state before any guard
|
||||
runs.
|
||||
5. Only after 1–4: point `/backoffice` and `/edit`'s `canActivate` at
|
||||
`ed25519AuthGuard` (and `permissionGuard(...)` where a route needs a
|
||||
specific role).
|
||||
|
||||
## 11. Security considerations
|
||||
|
||||
- **Private key never leaves the device.** Generated non-extractable via
|
||||
WebCrypto; `Ed25519KeypairService` has no export path. Losing the device
|
||||
means losing the key — key rotation/recovery (revoking a lost device's
|
||||
public key, provisioning a new one) is a backend/ops process, not
|
||||
implemented here.
|
||||
- **The frontend is not the authorization boundary.** Every admin
|
||||
request must be independently checked server-side against the caller's
|
||||
actual role, exactly as `docs/BACKEND_API.md` §2.5
|
||||
already states for the Telegram flow. A decoded JWT claim or a passing
|
||||
`PermissionService.has()` check is UX, not proof.
|
||||
- **Refresh tokens should rotate.** Every `POST /refresh` response is
|
||||
expected to include a *new* refresh token; the backend should invalidate
|
||||
the one just used. The frontend always stores whatever pair it receives
|
||||
and never reuses an old refresh token after a successful rotation.
|
||||
- **CSRF/replay:** the nonce from `/challenge` must be single-use and
|
||||
time-boxed server-side (`expiresAt`) — the frontend enforces nothing here
|
||||
beyond passing the nonce back unmodified; replay protection is the
|
||||
backend's responsibility.
|
||||
- **No fallback to unsigned auth.** There is no code path in this module
|
||||
that issues a session without a valid signature. If the backend is
|
||||
unreachable, the user sees `backend-unavailable`, never a degraded or
|
||||
bypassed login.
|
||||
- **Dev bypass exclusion:** unlike `AdminAuthService.devBypassLogin()` in
|
||||
the Telegram flow, this module intentionally has no dev bypass — an
|
||||
Ed25519 keypair is cheap to generate locally, so local testing should
|
||||
point at a real (even if mocked-in-dev) `/challenge`/`/verify` pair
|
||||
rather than fabricating a session.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,92 +0,0 @@
|
||||
> **ARCHIVED 2026-07-26.** Superseded by [`docs/BACKEND_INTEGRATION.md`](../BACKEND_INTEGRATION.md), the single canonical backend integration document. Kept for history only — do not implement against this file.
|
||||
|
||||
# Remaining backend work (everything except auth/session)
|
||||
|
||||
Companion to the `API-CONTRACT.md` backend delivered separately (covers `GET /bootstrap`
|
||||
transport + `/users/sessions/*` — done, see prior conversation). This file lists what's
|
||||
still outstanding. Full request/response shapes, TypeScript
|
||||
interfaces, and validation rules for every item below already exist in
|
||||
[`docs/BACKEND_API.md`](BACKEND_API.md) — this is a prioritized
|
||||
punch list with links into that spec, not a duplicate of it. **Do not re-document
|
||||
endpoint shapes here** — edit the master spec if a shape needs to change.
|
||||
|
||||
Status legend (same as master spec): **PLANNED** = shape fully specified client-side,
|
||||
served by a mock gateway today, nothing built server-side yet. **FUTURE** = reserved
|
||||
contract only, no urgency. Bootstrap's *content* (branding/theme/nav values, not the
|
||||
`GET /bootstrap` transport itself) is also still outstanding — see P0 below.
|
||||
|
||||
---
|
||||
|
||||
## Status legend for this list
|
||||
|
||||
**DONE** = wired end-to-end on the frontend (real HTTP gateway or real call site, no mock
|
||||
left in the path). **PLANNED** = shape fully specified client-side, still served by a mock
|
||||
gateway, nothing wired yet. Everything below that isn't marked DONE is still open.
|
||||
|
||||
## P0 — blocks going live at all
|
||||
|
||||
| # | Item | Status | Spec section |
|
||||
|---|---|---|---|
|
||||
| 1 | `bootstrap.json` real content (branding, theme, navigation, seo) — currently default stubs per backend's own note in API-CONTRACT.md | open | [§4](BACKEND_API.md#4-bootstrap) |
|
||||
| 2 | Builder — bootstrap draft/publish/validate (`GET/PUT /builder/bootstrap/draft`, `POST /builder/bootstrap/publish`, `POST /builder/bootstrap/validate`) — this is how the Marketplace Builder actually saves anything | open | [§6.7](BACKEND_API.md#67-builder--bootstrap-draftpublishvalidate-planned-highest-priority) |
|
||||
| 3 | Backoffice — Products CRUD + variants | open | [§6.10](BACKEND_API.md#610-backoffice--products-planned), DTOs [§7.2](BACKEND_API.md#72-products--srcappfeaturesadminproductsmodelsadmin-productmodelts) |
|
||||
| 4 | Backoffice — Categories CRUD (tree) | **DONE** — `admin-categories-api.gateway.ts` + `admin-categories-gateway.token.ts` wired, swaps on `RuntimeProviderStrategyService` | [§6.9](BACKEND_API.md#69-backoffice--categories-planned), DTOs [§7.1](BACKEND_API.md#71-categories--srcappfeaturesadmincategoriesmodelsadmin-categorymodelts) |
|
||||
| 5 | Media upload/delete/replace pipeline | open | [§6.18](BACKEND_API.md#618-media-planned--adr-0002), [§10](BACKEND_API.md#10-media) |
|
||||
|
||||
## P1 — needed for real order/commerce flow
|
||||
|
||||
| # | Item | Status | Spec section |
|
||||
|---|---|---|---|
|
||||
| 6 | Backoffice — Orders CRUD + status transitions | open | [§6.11](BACKEND_API.md#611-backoffice--orders-planned), state machine [§8.1](BACKEND_API.md#81-orders--adminorderstatus) |
|
||||
| 7 | Backoffice — Transactions (list/detail, tied to orders) | open | [§6.12](BACKEND_API.md#612-backoffice--transactions-planned) |
|
||||
| 8 | Order creation — checkout calls `POST /orders` on payment success | **DONE** — `ApiService.createOrder()` + `CartComponent.recordOrder()`, fire-and-forget alongside `clearCart()`, doesn't touch the frozen payment call chain | [§16.9](BACKEND_API.md#169-order-creation-future--no-order-creation-endpoint-exists-anywhere-yet) |
|
||||
| 9 | Backoffice — Users/roles/invitations | open | [§6.13](BACKEND_API.md#613-backoffice--users-roles-invitations-planned) |
|
||||
| 10 | Backoffice — Moderation (review + report status transitions) | open | [§6.14](BACKEND_API.md#614-backoffice--moderation-reviews--reports-planned), state machines [§8.4](BACKEND_API.md#84-reviews--adminreviewstatus)/[§8.5](BACKEND_API.md#85-reports--adminreportstatus) |
|
||||
|
||||
## P2 — dashboards / operational visibility
|
||||
|
||||
| # | Item | Status | Spec section |
|
||||
|---|---|---|---|
|
||||
| 11 | Backoffice — Dashboard metrics & recent activity | open | [§6.15](BACKEND_API.md#615-backoffice--dashboard-metrics--recent-activity-planned) |
|
||||
| 12 | Backoffice — Monitoring (all but Health) | open | [§6.16](BACKEND_API.md#616-backoffice--monitoring-planned-except-health) |
|
||||
| 13 | Backoffice — Analytics summary (real once orders are real) | open | [§6.17](BACKEND_API.md#617-backoffice--analytics-mostly-future--no-data-source) |
|
||||
| 14 | Builder — Content pages / CMS | open | [§6.8](BACKEND_API.md#68-builder--content-pages--cms-planned) |
|
||||
|
||||
## P3 — nice-to-have, no urgency
|
||||
|
||||
| # | Item | Status | Spec section |
|
||||
|---|---|---|---|
|
||||
| 15 | Search suggestions / catalog filters | open | [§6.6](BACKEND_API.md#66-search--autocomplete--trending-planned) |
|
||||
| 16 | Cross-device wishlist/compare/saved-searches sync — backend confirmed id-only stays, added `GET /items/batch?ids=` for hydration. Frontend needs `UserExperienceRepository` redesign: id-array + local product cache hydrated via the batch endpoint, replacing today's fully-synchronous denormalized-object storage | open (unblocked, not started) | [§6.6](BACKEND_API.md#66-search--autocomplete--trending-planned) |
|
||||
| 17 | Analytics traffic/funnels/heatmaps — needs a tracking pipeline that doesn't exist yet, not just an endpoint | open | [§6.17](BACKEND_API.md#617-backoffice--analytics-mostly-future--no-data-source) |
|
||||
| 18 | Sitemap — dynamic generation (static baseline today) | open (server-side, no frontend action) | [§6.19](BACKEND_API.md#619-sitemap-future--static-baseline-only-today) |
|
||||
|
||||
---
|
||||
|
||||
## Explicitly not in this list
|
||||
|
||||
- Auth / Telegram session (`GET /bootstrap` transport, `/users/sessions/*`) — covered by
|
||||
backend's `API-CONTRACT.md`, frontend wiring matches it exactly.
|
||||
- `authApiUrl` env value — **fixed**, now points at the same host as `apiUrl`
|
||||
(`https://api.dexarmarket.ru:445`) in both `environment.ts` and `environment.production.ts`.
|
||||
- `AdminWebSessionID` header — **fixed a real bug**: the interceptor only attached it to
|
||||
URLs containing `/admin/`, but every real backend path is `/backoffice/*`, `/builder/*`,
|
||||
`/media/*` — none of those matched, so every new admin call would have silently gone out
|
||||
with no admin auth header at all. Broadened the guard in `admin-auth-headers.interceptor.ts`.
|
||||
- `telegramBot` username — still unverified against `bot.go`'s `startbot()`.
|
||||
- Frontend deploy domain vs. CORS allow-list — **decided**: frontend and API stay on the
|
||||
same domain, so this is a non-issue by design rather than something to reconcile against
|
||||
an allow-list.
|
||||
- Payments — frozen, unchanged, out of scope per [§2.8](BACKEND_API.md#28-payments-frozen-documented-for-completeness).
|
||||
- Storefront reads/writes (categories, items, search, cart, reviews) — already real HTTP,
|
||||
already working, no backend work needed. See [§6.1](BACKEND_API.md#61-storefront-reads-current--frozen-shapes-srcappservicesapiservicets)–[§6.2](BACKEND_API.md#62-storefront-writes-current--frozen-shapes).
|
||||
|
||||
## For every open item above, when implementing
|
||||
|
||||
Read the interface + model file cited in the linked spec section before writing the
|
||||
endpoint — the shape is already fixed by the frontend gateway interface, not up for
|
||||
renegotiation without a frontend change. Follow the pattern now established for Categories
|
||||
(`admin-categories-api.gateway.ts` + `admin-categories-gateway.token.ts`): one `*ApiGateway`
|
||||
class implementing the existing `*Gateway` interface, plus one `InjectionToken` factory that
|
||||
picks mock vs. real off `RuntimeProviderStrategyService`, then switch the facade(s) to inject
|
||||
the token instead of the concrete mock class. See [§14](BACKEND_API.md#14-backend-replacement-pattern) for the general pattern.
|
||||
@@ -1,44 +0,0 @@
|
||||
> **ARCHIVED 2026-07-26.** Despite its filename this was a shipped-history changelog, not a forward roadmap — superseded by [`../NEXT_PHASE.md`](../NEXT_PHASE.md) (the one roadmap), [`../CHANGELOG.md`](../../CHANGELOG.md) (shipped history), and [`../PROJECT_STATUS.md`](../PROJECT_STATUS.md)/[`../KNOWN-ISSUES.md`](../KNOWN-ISSUES.md)/[`../PRODUCT_BACKLOG.md`](../PRODUCT_BACKLOG.md)/[`../FUTURE_FEATURES.md`](../FUTURE_FEATURES.md) (open items, now category-split). Kept for history only.
|
||||
|
||||
# Frontend Roadmap
|
||||
|
||||
Status snapshot, refreshed from recent commits only. Full sprint history: `SPRINT-PLAN.md` (removed, see git history). Open bugs: `docs/KNOWN-ISSUES.md`.
|
||||
|
||||
## Recently shipped
|
||||
|
||||
**RC-Visual-02 — Composition audit** (storefront, builder, backoffice)
|
||||
Fixed undefined CSS theme vars, hand-rolled skeletons/empty-states migrated to shared `app-skeleton`/`app-empty-state`/`app-button`, missing `<th scope="col">`, dead CSS (~530-line unused cart `.alt` theme). Detail: `UI-COMPOSITION-REVIEW.md` (removed, see git history).
|
||||
|
||||
**RC-Premium-01 — Storefront premium UX polish**
|
||||
Visual/interaction polish on top of the RC-Visual-02 baseline — no redesign, no logic/route changes. Fixed color-only state signaling app-wide (added `aria-pressed`/`aria-current`/`aria-live` + icon/checkmark pairing to selected swatches, active filters/tabs/sort, toggle buttons), normalized remaining hardcoded hex to design tokens, added hover/focus-visible/active/disabled states across interactive controls, capped legal/CMS prose at 70ch, converted FAQ to native `<details>` disclosures. Four commits (Home/Catalog/Search, Product/Compare/Wishlist, Cart/Checkout, Static Pages). Detail: `STORE_FRONT_UX_REVIEW.md` (removed, see git history).
|
||||
|
||||
**RC STORE-01 — Storefront cleanup**
|
||||
Closed 2 of the 5 gaps RC-Premium-01 deliberately deferred: category/search skeleton markup migrated to shared `app-skeleton`, dead cart `.email-form` markup/CSS removed. The other 3 (payment modal, cart confirm() dialog, untokenized colors) need an architecture/design-system decision, correctly left alone again. Detail: `STORE_REVIEW.md` (removed, see git history).
|
||||
|
||||
**RC PERF-01 — Performance audit** (production-readiness, app-wide)
|
||||
Initial bundle **1.47 MB → 1.12 MB raw (−24%)**: biggest win was lazy-loading en/hy i18n packs (346 KB were eagerly loaded regardless of visitor language), plus a dead `items-carousel`/primeng-only component deleted, dead global CSS removed. RxJS/change-detection audit found the codebase already clean (0 leaks, 190/191 components already OnPush). Detail: `PERFORMANCE_REPORT.md` (removed, see git history).
|
||||
|
||||
**RC A11Y-01 — WCAG 2.1 AA audit** (storefront, builder, backoffice)
|
||||
Added the app's first skip link (didn't exist anywhere before), fixed cart's custom payment modals having zero focus-trap, fixed `app-icon`'s "decorative by default" claim never actually being implemented, fixed 2 keyboard-inaccessible drag-and-drop reorder UIs (Builder homepage/footer, Backoffice categories), fixed an undefined `--color-primary` token in Builder, fixed admin sidebar nav announcing itself as "Dashboard" everywhere. Contrast fixes applied where safe; genuine brand-color contrast failures flagged for theme-owner sign-off, not changed unilaterally. Detail: `ACCESSIBILITY_REPORT.md` (removed, see git history).
|
||||
|
||||
**Release Candidate — live browser walkthrough** (storefront, builder, backoffice)
|
||||
Found and fixed **2 P0s**: (1) `language.guard.ts`'s legacy-URL redirect broke query params on every route app-wide (silently dead-ended any bookmarked/shared deep link with query params); (2) Backoffice Categories CRUD was completely broken end-to-end — wrong gateway-resolution fallback always picked the real HTTP gateway instead of the local mock in this environment, so every create/publish silently failed with zero user feedback. Plus 6 P1s (cart description, compare table raw enum values, search empty-state messaging, footer link 404, missing placeholder image, builder save-bar reset-state bug, backoffice mislabeled button). Detail: `RELEASE_REPORT.md` (removed, see git history).
|
||||
|
||||
## Sprint status
|
||||
|
||||
**Sprint 30 — Final Release**: verify pass re-run 2026-07-23 (tsc --noEmit, `npm run build`, `arch:check:boundaries`, `arch:check:cycles`) — all green, only pre-existing bundle-budget warning. Working tree otherwise clean. Only remaining item: `git push` of 10 local `B2B` commits to `origin/B2B` — awaiting explicit user go-ahead (declined once already this sprint, per safety rules re-ask each time). Full checklist: `SPRINT-PLAN.md` (removed, see git history).
|
||||
|
||||
## Known open items (not yet scheduled)
|
||||
|
||||
As of the 2026-07-26 Final Project Closeout, open items are split by category instead of one mixed list:
|
||||
- Real, reproducible frontend bugs: `docs/KNOWN-ISSUES.md` (one open item).
|
||||
- Items needing a client/business decision (dark mode, brand-color contrast, Contacts page content, advanced analytics, payment providers): `docs/PRODUCT_BACKLOG.md`.
|
||||
- Nice-to-have, non-blocking future work (Angular 22, bundle splitting, cart-modal composition cleanup, hero-spacing investigation): `docs/FUTURE_FEATURES.md`.
|
||||
- Backend integration: fully specified, not yet implemented — the single canonical spec is `docs/BACKEND.md`.
|
||||
- Release blockers: `docs/TODO.md` — currently none.
|
||||
|
||||
Overall status: `docs/PROJECT_STATUS.md`.
|
||||
|
||||
## Not audited / out of scope
|
||||
|
||||
Settings (no route exists), Diagnostics (dev-only, excluded from production).
|
||||
@@ -1,823 +0,0 @@
|
||||
# Backend Surface Audit
|
||||
|
||||
Machine-oriented, exhaustive audit of every backend touch-point the Angular frontend
|
||||
expects — derived from the current source tree on branch `B2B`, not copied from prior
|
||||
docs. Purpose: single input for downstream backend-integration documentation tasks.
|
||||
|
||||
Legend for maturity (mirrors `docs/BACKEND_API.md`'s tagging so the two stay reconcilable):
|
||||
|
||||
- **LIVE** — real `HttpClient` call exists in code today (file cited).
|
||||
- **MOCK-SWAPPABLE** — interface + mock implementation exist, wired through an Angular
|
||||
DI token so a real `*Api*` class can be dropped in without touching UI. A real impl
|
||||
may or may not exist yet.
|
||||
- **MOCK-ONLY (no seam)** — mock/local implementation exists but the facade injects the
|
||||
concrete local class **directly** (no DI token). Adding a backend here first requires
|
||||
introducing a token seam. This is the single most important structural finding below.
|
||||
- **LOCAL-ONLY** — never talks to a backend by design (localStorage / in-memory /
|
||||
derived from already-loaded bootstrap). Listed for completeness.
|
||||
|
||||
## Table of contents
|
||||
|
||||
1. [Executive summary & key findings](#1-executive-summary--key-findings)
|
||||
2. [Runtime provider strategy & environment](#2-runtime-provider-strategy--environment)
|
||||
3. [HTTP interceptor pipeline](#3-http-interceptor-pipeline)
|
||||
4. [Live HTTP endpoints (verified in code)](#4-live-http-endpoints-verified-in-code)
|
||||
5. [Domain: Auth (customer + admin)](#5-domain-auth-customer--admin)
|
||||
6. [Domain: Bootstrap / config / tenant](#6-domain-bootstrap--config--tenant)
|
||||
7. [Domain: Products & catalog](#7-domain-products--catalog)
|
||||
8. [Domain: Categories](#8-domain-categories)
|
||||
9. [Domain: Backoffice storefront data](#9-domain-backoffice-storefront-data)
|
||||
10. [Domain: Cart / orders / payments](#10-domain-cart--orders--payments)
|
||||
11. [Domain: Reviews & questions (engagement)](#11-domain-reviews--questions-engagement)
|
||||
12. [Domain: Location / regions](#12-domain-location--regions)
|
||||
13. [Domain: Widgets / dynamic renderer](#13-domain-widgets--dynamic-renderer)
|
||||
14. [Admin gateways (feature area)](#14-admin-gateways-feature-area)
|
||||
15. [Domain: Media library](#15-domain-media-library)
|
||||
16. [Domain: Content management / static pages](#16-domain-content-management--static-pages)
|
||||
17. [Domain: Project editor / builder](#17-domain-project-editor--builder)
|
||||
18. [Domain: Search](#18-domain-search)
|
||||
19. [Domain: User experience (wishlist/compare/etc.)](#19-domain-user-experience-wishlistcomparetc)
|
||||
20. [Domain: Diagnostics](#20-domain-diagnostics)
|
||||
21. [Facade catalog](#21-facade-catalog)
|
||||
22. [Gateway / provider master table](#22-gateway--provider-master-table)
|
||||
23. [Model / DTO catalog](#23-model--dto-catalog)
|
||||
24. [Endpoint URL literals found in code](#24-endpoint-url-literals-found-in-code)
|
||||
25. [Cross-check against existing docs](#25-cross-check-against-existing-docs)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary & key findings
|
||||
|
||||
- **~9 real HTTP-speaking domains** exist today: product/catalog, categories, cart/orders/
|
||||
payments, reviews/questions, telegram session auth, bootstrap, backoffice storefront data,
|
||||
widget manifest, location/regions. Plus **Ed25519 admin auth** — wired to real `HttpClient`
|
||||
but the endpoints are not implemented server-side yet (calls 404 today, by design).
|
||||
- **Two provider seams are token-bound and API-ready today**: `PRODUCT_DATA_PROVIDER`
|
||||
(→ `ApiProductDataProvider`, LIVE) and `CATEGORY_REPOSITORY` (→ `ApiCategoryRepository`, LIVE),
|
||||
plus `CONFIG_PROVIDER` and `BACKOFFICE_DATA_PROVIDER` which switch mock↔api by strategy.
|
||||
- **KEY STRUCTURAL FINDING — most admin CRUD domains have no swap seam.** Of the 11 admin
|
||||
gateway domains, only **categories** (`ADMIN_CATEGORIES_GATEWAY`) and **dashboard-metrics**
|
||||
(`ADMIN_DASHBOARD_METRICS_GATEWAY`) are injected via DI token. The other 9 (orders, products,
|
||||
users, transactions, monitoring, moderation, customers, analytics, and the products/orders
|
||||
gateways reused by analytics/customers) have their facades inject the concrete
|
||||
`Admin*LocalGateway` **class directly**. A backend engineer cannot "just rebind a token" for
|
||||
those — a token must be introduced first. This partially contradicts the blanket
|
||||
"PLANNED / rebind the token" framing in `docs/BACKEND_API.md`.
|
||||
- **Only one real `*Api*Gateway` exists in the admin area**: `AdminCategoriesApiGateway`
|
||||
(`src/app/features/admin/categories/services/admin-categories-api.gateway.ts`). Every other
|
||||
admin domain is local-mock only.
|
||||
- **Media** is bound by class token (`MediaRepository` abstract class → `MockMediaRepository`
|
||||
via `app.config.ts`), so it is MOCK-SWAPPABLE but no real impl exists.
|
||||
- **Content-management and project-editor never hit a dedicated backend** — they read/mutate
|
||||
the in-memory `BootstrapConfig` (loaded once from `GET /bootstrap`) and persist drafts to
|
||||
localStorage. Publishing a marketplace = writing bootstrap back, for which no client write
|
||||
call exists yet (LOCAL-ONLY today; a builder publish endpoint is FUTURE).
|
||||
- **Two API base URLs are in play**: the tenant marketplace API (`ApiConfigService.getBaseUrl()`,
|
||||
default `https://api.dexarmarket.ru:445`, `/api` on localhost) and a separate payment/QR API
|
||||
(`environment.qrApiUrl` = `https://qr.vitanova.network/api`). Auth session API uses
|
||||
`environment.authApiUrl` (= `https://api.dexarmarket.ru:445`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Runtime provider strategy & environment
|
||||
|
||||
`src/app/core/providers/runtime-provider-strategy.service.ts` — `RuntimeProviderStrategyService`
|
||||
decides mock vs api per domain. Modes: `'mock' | 'api' | 'remote-config'`.
|
||||
|
||||
| Method | Returns `mock` when | Else |
|
||||
|---|---|---|
|
||||
| `getBootstrapProviderMode()` | `useMockData` true, OR `useMockBootstrapOnLocal && isLocalhost()` | `api` |
|
||||
| `getBackofficeProviderMode()` | `useMockData` true | `api` |
|
||||
| `getProductProviderMode()` | `useMockData` true | `api` (mock/remote-config fall through to api in token factory) |
|
||||
| `getCategoryProviderMode()` | `useMockData` true, OR `useMockBootstrapOnLocal && isLocalhost()` | `api` |
|
||||
|
||||
Note: `PRODUCT_DATA_PROVIDER` and `CATEGORY_REPOSITORY` token factories currently return the
|
||||
**Api** provider for every mode (the `case 'mock'` falls through) — there is no mock product/
|
||||
category provider class bound. `getCategoryProviderMode()` returning `mock` only matters for
|
||||
`ADMIN_CATEGORIES_GATEWAY` (which does honor it → `AdminCategoriesLocalGateway`).
|
||||
|
||||
`src/environments/environment.ts` relevant keys:
|
||||
|
||||
```
|
||||
useMockData: false
|
||||
useMockBootstrapOnLocal: true
|
||||
allowBootstrapApiOverride: false
|
||||
localhostApiUrl: '/api'
|
||||
tenantApiTemplate: 'https://{tenant}.api.dexarmarket.ru:445'
|
||||
tenantApiBaseUrls: { default: 'https://api.dexarmarket.ru:445', dexarmarket: 'https://api.dexarmarket.ru:445' }
|
||||
apiUrl: '/api'
|
||||
authApiUrl: 'https://api.dexarmarket.ru:445'
|
||||
qrApiUrl: 'https://qr.vitanova.network/api'
|
||||
telegramBot: 'myAMLKYCBOT' (fallback in code: 'DexarSupport_bot')
|
||||
```
|
||||
|
||||
`src/app/core/config/api-config.service.ts` — `ApiConfigService.getBaseUrl()` resolves the
|
||||
tenant marketplace API base: localhost → `localhostApiUrl`; else `tenantApiBaseUrls[tenantKey]`;
|
||||
else `tenantApiTemplate` with `{tenant}` substituted; else optional bootstrap override
|
||||
(gated by `allowBootstrapApiOverride`, reads `bootstrap.apiEndpoints.website.baseUrl` /
|
||||
`bootstrap.tenant.apiBaseUrl`). `isApiRequest(url)` = starts with `/api` or the base URL.
|
||||
`toApiUrl(url)` rewrites a `/api`-prefixed relative URL onto the resolved base.
|
||||
|
||||
Tenant key comes from `TenantResolverService` (`src/app/core/config/tenant-resolver.service.ts`).
|
||||
|
||||
---
|
||||
|
||||
## 3. HTTP interceptor pipeline
|
||||
|
||||
Registered in `src/app/app.config.ts` in this order:
|
||||
|
||||
```
|
||||
withInterceptors([mockDataInterceptor, apiBaseUrlInterceptor, apiHeadersInterceptor, adminAuthHeadersInterceptor, cacheInterceptor])
|
||||
```
|
||||
|
||||
| Interceptor | File | Responsibility |
|
||||
|---|---|---|
|
||||
| `mockDataInterceptor` | `src/app/interceptors/mock-data.interceptor.ts` | When `environment.useMockData`, short-circuits marketplace endpoints with in-memory fixtures (categories, items, search, cart, qr, callback, purchase-email, sessions). Matches URL patterns — see §24. |
|
||||
| `apiBaseUrlInterceptor` | `src/app/interceptors/api-base-url.interceptor.ts` | Rewrites `/api/*` relative URLs to `ApiConfigService.toApiUrl()`. |
|
||||
| `apiHeadersInterceptor` | `src/app/interceptors/api-headers.interceptor.ts` | For marketplace API requests, sets headers: `X-Region`, `X-Language` (RU/EN/AM), `Currency` (default RUB), `WebSessionID` (auth session id or persisted anonymous 32-hex id in localStorage key `web_session_id`). |
|
||||
| `adminAuthHeadersInterceptor` | `src/app/core/admin-auth/admin-auth-headers.interceptor.ts` | For requests whose URL contains `/admin/`, `/backoffice/`, `/builder/`, `/media/`, sets `AdminWebSessionID` header (from `AdminAuthService.session()`) and `Authorization: Bearer <token>` if an admin token is stored. |
|
||||
| `cacheInterceptor` | `src/app/interceptors/cache.interceptor.ts` | Client-side GET response caching. |
|
||||
|
||||
Header value maps (from `apiHeadersInterceptor`): language `ru→RU, en→EN, hy→AM`;
|
||||
region `moscow→Moscow, spb→ST. Petersburg, yerevan→Yerevan`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Live HTTP endpoints (verified in code)
|
||||
|
||||
All paths relative to `ApiConfigService.getBaseUrl()` unless a full origin is shown. Payment
|
||||
endpoints use `environment.qrApiUrl`; session endpoints use `environment.authApiUrl`.
|
||||
|
||||
### Marketplace API — `src/app/services/api.service.ts` (`ApiService`)
|
||||
|
||||
| Method | HTTP | Path | Notes |
|
||||
|---|---|---|---|
|
||||
| `ping()` | GET | `/ping` | `{ message }` |
|
||||
| `getCategories()` | GET | `/category` | normalized to `Category[]` |
|
||||
| `getCategoryItems(id,count,skip)` | GET | `/category/{categoryID}?count&skip` | `Item[]` |
|
||||
| `getItem(id)` | GET | `/items/{itemID}` | single `Item` |
|
||||
| `searchItems(search,count,skip,opts)` | GET | `/searchitems?search&count&skip[&categoryIDs&minPrice&maxPrice&tag&sort]` | `{ items, total }` |
|
||||
| `getRandomItems(count,categoryID?)` | GET | `/items/randomitems?count[&category]` | `Item[]` (featured) |
|
||||
| `addToCart(sessionId,items)` | POST | `/websession/{sessionId}` | body = item array |
|
||||
| `submitReview(data)` | POST | `/items/{itemID}/callback` | body: rating, comment, sessionID, timestamp |
|
||||
| `submitQuestion(data)` | POST | `/items/{itemID}/questiion` | **NOTE: literal typo `questiion`** matches backend spec |
|
||||
| `createCartPayment(payload)` | POST | `/cart` | `CartPaymentRequest` → `QrCreateResponse` |
|
||||
| `createOrder(payload)` | POST | `/orders` | `CreateOrderRequest` → `CreateOrderResponse`; fire-and-forget after payment |
|
||||
| `submitPurchaseEmail(data)` | POST | `/purchase-email` | email receipt |
|
||||
| `createPayment(payload,headers)` | POST | `{qrApiUrl}/qr` | headers `authorization-key`, `userid-value` |
|
||||
| `checkCartPaymentStatus(qrId)` | GET | `{qrApiUrl}/qr/dynamic/{partnerId}/{qrId}` | partnerId const `web-97ec-9c57-4dde-9037-3a68f7f83750` |
|
||||
| `checkCartCardPaymentStatus(orderId)` | GET | `{qrApiUrl}/card/{partnerId}/{orderId}` | |
|
||||
| `checkPaymentStatus(partnerQrId,qrId)` | GET | `{qrApiUrl}/qr/dynamic/{partnerQrId}/{qrId}` | |
|
||||
|
||||
Also builds an external QR image URL (`https://api.qrserver.com/v1/create-qr-code/...`) — not a backend of this platform.
|
||||
|
||||
### Other live callers
|
||||
|
||||
| Caller (file) | HTTP | Path | Base |
|
||||
|---|---|---|---|
|
||||
| `ApiHealthService` (`src/app/services/api-health.service.ts`) | GET | `/ping` | marketplace base |
|
||||
| `ApiCategoryRepository` (`src/app/core/categories/repositories/api-category.repository.ts`) | GET | `/category` | marketplace base; retry x2 |
|
||||
| `ApiBootstrapProvider` (`src/app/core/bootstrap/providers/api-bootstrap.provider.ts`) | GET | `/bootstrap` | relative |
|
||||
| `MockBootstrapProvider` | GET | `/assets/mock/bootstrap/bootstrap.json` | static asset |
|
||||
| `ApiBackofficeDataProvider` (`src/app/core/backoffice/providers/api-backoffice-data.provider.ts`) | GET | `/api/backoffice/products`, `/api/backoffice/categories` | |
|
||||
| `WidgetManifestService` (`src/app/widgets/registry/widget-manifest.service.ts`) | GET | `bootstrap.widgetRegistry.manifestUrl` or `/assets/mock/bootstrap/widget-manifest.json` | |
|
||||
| `LocationService` (`src/app/services/location.service.ts`) | GET | `/regions` (marketplace base); `http://ip-api.com/json/...` (external geo-IP) | |
|
||||
| `TelegramSessionApiService` (`src/app/services/telegram-session-api.service.ts`) | POST/GET/DELETE | `{authApiUrl}/users/sessions`, `/users/sessions/{id}` | session auth |
|
||||
| `AuthApiService` (`src/app/core/auth/services/auth-api.service.ts`) | GET/POST | `{authApiUrl}/api/admin/auth/challenge|verify|refresh|logout` | **not implemented server-side yet** |
|
||||
| `ApiProductDataProvider` | (delegates to `ApiService`) | see above | |
|
||||
|
||||
---
|
||||
|
||||
## 5. Domain: Auth (customer + admin)
|
||||
|
||||
Two distinct auth mechanisms coexist.
|
||||
|
||||
### 5a. Telegram session auth (LIVE) — customer AND admin
|
||||
|
||||
`src/app/services/telegram-session-api.service.ts` — `TelegramSessionApiService`. Single source
|
||||
for both customer (`AuthService`) and admin (`AdminAuthService`) login; there is no separate
|
||||
admin backend endpoint. Only storage is kept separate (distinct cookie/signals).
|
||||
|
||||
| Method | HTTP | Path | Request | Response (normalized) |
|
||||
|---|---|---|---|---|
|
||||
| `createSession()` | POST | `{authApiUrl}/users/sessions` | `{ webSessionID }` + header `WebSessionID` | `WebSessionStart { webSessionID, url }` (url = `https://t.me/{bot}?start={id}`) |
|
||||
| `checkSessionOnce(id)` | GET | `{authApiUrl}/users/sessions/{id}` | — | `AuthSession | null` (heavily field-tolerant normalizer) |
|
||||
| `logout(id)` | DELETE | `{authApiUrl}/users/sessions/{id}` | header `WebSessionID` | ignored |
|
||||
|
||||
Consumers: `AuthService` (`src/app/services/auth.service.ts`, customer),
|
||||
`AdminAuthService` (`src/app/core/admin-auth/admin-auth.service.ts`, admin — separate cookie
|
||||
`adminSessionID`, has dev-only `devBypassLogin()`), `AuthFacade`
|
||||
(`src/app/core/auth/services/auth-facade.service.ts`) wrapping AuthService/SessionService/
|
||||
PermissionService for components. Also `src/app/shared/qr-login/`.
|
||||
|
||||
Models: `AuthSession`, `WebSessionStart`, `AuthStatus` (`src/app/models/auth.model.ts`);
|
||||
`AdminAuthStatus` (`src/app/models/admin-auth.model.ts`).
|
||||
|
||||
### 5b. Ed25519 challenge/response admin auth (LIVE wiring, backend absent)
|
||||
|
||||
`src/app/core/auth/services/auth-api.service.ts` — `AuthApiService`. Real `HttpClient` wiring
|
||||
against a documented contract that the backend has NOT implemented yet (calls 404 today,
|
||||
mapped to a `backend-unavailable` error screen). No mocks fabricated.
|
||||
|
||||
| Method | HTTP | Path (`{authApiUrl}/api/admin/auth`) | Request | Response |
|
||||
|---|---|---|---|---|
|
||||
| `requestChallenge()` | GET | `/challenge` | — | `AuthChallenge { nonce, issuedAt, expiresAt }` |
|
||||
| `verifySignature(req)` | POST | `/verify` | `VerifySignatureRequest { publicKey, signature, nonce }` | `AuthTokenPair { token, refreshToken }` |
|
||||
| `refresh(req)` | POST | `/refresh` | `RefreshTokenRequest { refreshToken }` | `AuthTokenPair` |
|
||||
| `logout(refreshToken)` | POST | `/logout` | `{ refreshToken }` | void |
|
||||
|
||||
Models: `src/app/core/auth/models/auth-api.model.ts` (`AuthChallenge`, `VerifySignatureRequest`,
|
||||
`AuthTokenPair`, `RefreshTokenRequest`, `JwtClaims`). Supporting:
|
||||
`src/app/core/auth/services/ed25519-keypair.service.ts` (keypair gen/signing);
|
||||
`src/app/core/admin-auth/ed25519-verification.model.ts` +
|
||||
`noop-ed25519-verification.service.ts` (bound in `app.config.ts` via
|
||||
`{ provide: Ed25519VerificationService, useClass: NoopEd25519VerificationService }`).
|
||||
|
||||
Permissions/roles: `src/app/core/auth/models/permission.model.ts` — `AdminRole`
|
||||
(`Owner|Administrator|Editor|Support|ReadOnly`), `Permission` union.
|
||||
Errors: `src/app/core/auth/models/auth-error.model.ts` — `AuthErrorCode`, `AuthError`.
|
||||
Guards: `src/app/core/admin-auth/admin-auth.guard.ts`, `src/app/guards/**`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Domain: Bootstrap / config / tenant
|
||||
|
||||
The runtime configuration document that drives the entire multi-tenant platform.
|
||||
|
||||
- **Contract interface**: `ConfigProvider` (`src/app/core/config/config-provider.interface.ts`)
|
||||
— `loadBootstrap(): Observable<BootstrapConfig>`.
|
||||
- **DI token**: `CONFIG_PROVIDER` (`src/app/core/config/config-provider.token.ts`), factory
|
||||
switches on `getBootstrapProviderMode()`: `mock` → `MockBootstrapProvider`
|
||||
(`/assets/mock/bootstrap/bootstrap.json`), else → `ApiBootstrapProvider` (`GET /bootstrap`).
|
||||
- **Implementations**: `ApiBootstrapProvider`, `MockBootstrapProvider`
|
||||
(`src/app/core/bootstrap/providers/*`).
|
||||
- **Consuming services**: `ConfigService` (`src/app/core/config/config.service.ts`,
|
||||
holds the bootstrap snapshot), `ApiConfigService`, `FeatureConfigService`,
|
||||
`TenantResolverService`, `FooterResolverService`, `StaticPageResolverService`
|
||||
(all `src/app/core/config/*`).
|
||||
- **Consuming facades**: `UiRuntimeFacade` (`src/app/facades/runtime/ui-runtime.facade.ts`),
|
||||
`WebsiteRuntimeFacade` (`src/app/facades/website/website-runtime.facade.ts`),
|
||||
`ProjectEditorFacade`, `ContentManagementFacade`, `DiagnosticsFacade`.
|
||||
|
||||
`BootstrapConfig` (`src/app/shared/models/config/bootstrap-config.model.ts`) aggregates ~24
|
||||
sub-configs, each its own file under `src/app/shared/models/config/`:
|
||||
|
||||
`schemaVersion, generatedAt, tenant, branding, theme, company, featureFlags, features?,
|
||||
apiEndpoints, localization, seo, permissions, header?, catalog?, layout?, navigation, footer?,
|
||||
productPage?, userExperience?, pages[], staticPages?, widgetRegistry?`
|
||||
|
||||
Sub-config model files (all backend-shaped, served inside bootstrap):
|
||||
`api-endpoints.model.ts` (`ApiEndpointConfig{path,method,timeoutMs?}`, `ApiEndpointsConfig{
|
||||
bootstrap, website:Record<...>, builder:Record<...>, backoffice:Record<...>}`),
|
||||
`tenant.model.ts` (`TenantConfig{id,slug,code,host,name,websiteBaseUrl,builderBaseUrl,
|
||||
backofficeBaseUrl,defaultLocale,supportedLocales,defaultCurrency,supportedCurrencies,timezone}`),
|
||||
`branding.model.ts`, `theme.model.ts`, `company.model.ts`, `feature-flags.model.ts`,
|
||||
`features-config.model.ts`, `footer-config.model.ts`, `header-config.model.ts`, `layout.model.ts`,
|
||||
`localization.model.ts`, `navigation.model.ts`, `page.model.ts`, `permissions.model.ts`,
|
||||
`product-page-config.model.ts`, `catalog-config.model.ts`, `seo.model.ts`,
|
||||
`static-page.model.ts`, `user-experience-config.model.ts`, `widget-registry.model.ts`,
|
||||
`widget.model.ts`, `section.model.ts`. Barrel: `src/app/shared/models/config/index.ts`.
|
||||
|
||||
`ApiEndpointsConfig.website/builder/backoffice` are `Record<string, ApiEndpointConfig>` —
|
||||
i.e. the bootstrap document is where a tenant's PLANNED endpoint paths are declared at runtime.
|
||||
No literal builder/backoffice path constants exist in code (see §24).
|
||||
|
||||
---
|
||||
|
||||
## 7. Domain: Products & catalog
|
||||
|
||||
- **Contract interface**: `ProductDataProvider`
|
||||
(`src/app/core/products/providers/product-data-provider.interface.ts`).
|
||||
- **DI token**: `PRODUCT_DATA_PROVIDER` (`src/app/core/products/product-data-provider.token.ts`)
|
||||
— factory returns `ApiProductDataProvider` for all modes (no mock provider class bound).
|
||||
- **Real impl (LIVE)**: `ApiProductDataProvider`
|
||||
(`src/app/core/products/providers/api-product-data.provider.ts`) — delegates to `ApiService`
|
||||
+ `CategoryService`; contains inline mapping (item→reviews/questions/rating summary).
|
||||
- **Domain service**: `ProductDataService` (`src/app/core/products/product-data.service.ts`)
|
||||
injected by `ProductFacade`.
|
||||
- **Consuming facade**: `ProductFacade` (`src/app/facades/platform/product.facade.ts`).
|
||||
- **Consuming components**: `catalog-container.component.ts`,
|
||||
`product-details-container.component.ts` (`src/app/features/website/**`), home/catalog pages.
|
||||
|
||||
Interface methods: `getProducts(query?)`, `getProduct(productID)`, `getCategories()`,
|
||||
`searchProducts(query)`, `getFeaturedProducts(query?)`, `getLatestProducts(query?)`,
|
||||
`getProductsByCategory(categoryID,query?)`, `getRelatedProducts(query)`, `loadRating(productID)`,
|
||||
`loadReviews(productID,query?)`, `loadQuestions(productID,query?)`, `submitReview(productID,input)`,
|
||||
`submitQuestion(productID,input)`.
|
||||
|
||||
Models — `src/app/core/products/models/`:
|
||||
- `product-domain.model.ts`: `Product = Item` (alias), `ProductCategory = Category`,
|
||||
`ProductSort`, `ProductFilters`, `ProductListQuery`, `ProductSearchQuery`, `ProductListResult`,
|
||||
`RelatedProductsQuery`, `RelatedProductCollection`, `ProductVariantSelection`.
|
||||
- `product-engagement.model.ts`: `RatingStars`, `RatingDistributionEntry`, `RatingSummary`,
|
||||
`Review`, `Answer`, `Question`, `EngagementListQuery`, `EngagementListResult<T>`,
|
||||
`SubmitReviewInput`, `SubmitQuestionInput`.
|
||||
- `catalog-experience.model.ts`: `SearchCriteria`, `FilterDefinition`, `FilterOption`,
|
||||
`SortDefinition`, `CatalogView`, `SearchResult`, layout/nav mode types.
|
||||
|
||||
The **backend-shaped** product DTO is `Item` (`src/app/models/item.model.ts`) — the raw wire
|
||||
shape. `ApiService.normalizeItem()` is the adapter: it reconciles legacy marketplace format and
|
||||
newer backOffice format (string `id`↔numeric `itemID`, `imgs[]`↔`photos[]`, `names[]`↔
|
||||
`translations`, `itemDetails[]`, `description` key/value array↔string, `comments`↔`callbacks`,
|
||||
`specificationGroups`, `variantOptions`, `relatedCollections`, delivery normalization, color
|
||||
`0xRRGGBB`→`#RRGGBB`, remaining→stock band). This is the single largest inline mapper in the
|
||||
codebase — a backend engineer should treat `normalizeItem`/`normalizeCategory` as the tolerance
|
||||
contract. `Item` supporting types: `ProductMedia`, `DescriptionField`, `ItemName`,
|
||||
`ProductSpecificationField/Group`, `ProductVariantOption(Group)`, `RelatedProductCollection`,
|
||||
`DeliveryOption`, `ItemDetail`, `CartItem`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Domain: Categories
|
||||
|
||||
Two parallel category stacks exist (legacy + clean-architecture):
|
||||
|
||||
**Clean stack (MOCK-SWAPPABLE, real impl LIVE):**
|
||||
- **Interface**: `CategoryRepository` (`src/app/core/categories/repositories/category.repository.ts`)
|
||||
— `getCategories(): Observable<CategoryDto[]>`.
|
||||
- **DI token**: `CATEGORY_REPOSITORY` (`src/app/core/categories/category-repository.token.ts`)
|
||||
→ `ApiCategoryRepository` for all modes.
|
||||
- **Real impl (LIVE)**: `ApiCategoryRepository` — `GET /category`, retry x2.
|
||||
- **DTO**: `CategoryDto`, `CategoryNameDto` (`src/app/core/categories/dto/category.dto.ts`).
|
||||
- **Adapter**: `CategoryMapper` (`src/app/core/categories/mappers/category.mapper.ts`) —
|
||||
`CategoryDto → Category` domain (flattens subcategory tree, dedupes by id, language
|
||||
normalization `am→hy`).
|
||||
- **Domain model**: `Category`, `CategoryTranslation`
|
||||
(`src/app/core/categories/models/category-domain.model.ts`).
|
||||
- **Facade**: `CategoryFacade` (`src/app/facades/platform/category.facade.ts`) via
|
||||
`CategoryService` (`src/app/core/categories/category.service.ts`). Utils:
|
||||
`category-tree.utils.ts`.
|
||||
|
||||
**Legacy stack**: `ApiService.getCategories()` → `Category` (`src/app/models/category.model.ts`,
|
||||
with `Subcategory`) via `normalizeCategory()`. Used by `ApiProductDataProvider.getCategories()`.
|
||||
Note: two different `Category` types exist (`src/app/models/category.model.ts` vs
|
||||
`src/app/core/categories/models/category-domain.model.ts`) — a known duplication.
|
||||
|
||||
---
|
||||
|
||||
## 9. Domain: Backoffice storefront data
|
||||
|
||||
Storefront-facing "cards" data (distinct from the admin/backoffice feature area).
|
||||
|
||||
- **Interface**: `BackofficeDataProvider`
|
||||
(`src/app/core/backoffice/providers/backoffice-data-provider.interface.ts`) —
|
||||
`loadProducts(): Observable<ProductCardConfig[]>`, `loadCategories(): Observable<CategoryCardConfig[]>`.
|
||||
- **DI token**: `BACKOFFICE_DATA_PROVIDER` (`src/app/core/backoffice/backoffice-data-provider.token.ts`)
|
||||
— `mock` → `MockBackofficeDataProvider`, else → `ApiBackofficeDataProvider`.
|
||||
- **Impls**: `ApiBackofficeDataProvider` (LIVE, `GET /api/backoffice/products`,
|
||||
`GET /api/backoffice/categories`), `MockBackofficeDataProvider`
|
||||
(`src/app/core/backoffice/providers/*`).
|
||||
- **Models**: `ProductCardConfig` (`src/app/shared/models/ui/product-card.model.ts`),
|
||||
`CategoryCardConfig` (`src/app/shared/models/ui/category-card.model.ts`),
|
||||
`ButtonConfig` (`button.model.ts`). Barrel: `src/app/shared/models/ui/index.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Domain: Cart / orders / payments
|
||||
|
||||
Cart state is **LOCAL-ONLY** but checkout produces LIVE payment/order calls.
|
||||
|
||||
- **`CartService`** (`src/app/services/cart.service.ts`) — signal-based cart, persisted to
|
||||
localStorage key `marketplace_cart` (+ Telegram CloudStorage when in Telegram WebApp). No
|
||||
backend for cart contents. Models: `CartItem` (extends `Item`), `DeliveryOption`.
|
||||
- **Checkout → `ApiService`** (see §4): `POST /cart` (`CartPaymentRequest`),
|
||||
`POST /orders` (`CreateOrderRequest`→`CreateOrderResponse`), `POST /purchase-email`,
|
||||
QR/card status polling on `qrApiUrl`.
|
||||
- Request/response DTOs live inline in `api.service.ts`: `QrCreateRequest`, `QrCreateResponse`,
|
||||
`CartPaymentRequest`, `CreateOrderRequest`, `CreateOrderResponse`, `QrDynamicStatusResponse`.
|
||||
- Admin-side order/transaction views are a **separate** mock domain — see §14.
|
||||
|
||||
---
|
||||
|
||||
## 11. Domain: Reviews & questions (engagement)
|
||||
|
||||
Customer-facing. LIVE via `ApiService`. Interface methods on `ProductDataProvider`:
|
||||
`loadRating`, `loadReviews`, `loadQuestions`, `submitReview`, `submitQuestion`.
|
||||
Endpoints: `POST /items/{id}/callback` (review), `POST /items/{id}/questiion` (question, typo
|
||||
preserved). Reads derive reviews/questions/rating from `GET /items/{id}` payload (no dedicated
|
||||
list endpoints yet). Models in `product-engagement.model.ts` (§7). Admin **moderation** of
|
||||
reviews/reports is a separate mock domain — see §14.
|
||||
|
||||
---
|
||||
|
||||
## 12. Domain: Location / regions
|
||||
|
||||
`LocationService` (`src/app/services/location.service.ts`), LIVE:
|
||||
- `GET /regions` (marketplace base) → `Region[]`; falls back to 6 hardcoded regions on error.
|
||||
- `GET http://ip-api.com/json/?fields=...` (external geo-IP, no key) for auto-detect.
|
||||
Models: `Region`, `GeoIpResponse` (`src/app/models/location.model.ts`). Region id feeds the
|
||||
`X-Region` header (§3).
|
||||
|
||||
---
|
||||
|
||||
## 13. Domain: Widgets / dynamic renderer
|
||||
|
||||
Widget manifest is LIVE (static/remote JSON), widget data is derived from products/categories.
|
||||
|
||||
- **`WidgetManifestService`** (`src/app/widgets/registry/widget-manifest.service.ts`) — GETs
|
||||
`bootstrap.widgetRegistry.manifestUrl` or fallback
|
||||
`/assets/mock/bootstrap/widget-manifest.json` → `WidgetManifestFile`.
|
||||
- **`WidgetRegistryService`** (`src/app/widgets/registry/widget-registry.service.ts`),
|
||||
**`WidgetHostService`** (`src/app/dynamic-renderer/widget-host/widget-host.service.ts`).
|
||||
- Contracts (`src/app/widgets/contracts/`): `widget-manifest.contract.ts`
|
||||
(`WidgetManifestEntry/File`, `WidgetSettingsSchema`, `WidgetMetadataSupport`,
|
||||
`WidgetLayoutSupport`, `WidgetDataSourceName`), `widget-component.contract.ts`
|
||||
(`WidgetRenderContext`, `RegisteredWidget`, `ResolvedWidget`), `widget-data.contract.ts`
|
||||
(`HeroWidgetData`, `CategoriesWidgetData`, `ProductCollectionWidgetData`, `BannerWidgetData`,
|
||||
`HtmlWidgetData`, `PartnersWidgetData`, `FooterWidgetData`, `HeroSlideData`,
|
||||
`WidgetResolvedContext`).
|
||||
- Renderer models: `src/app/dynamic-renderer/{page-renderer,section-renderer,widget-host}/*.model.ts`.
|
||||
- Widget data sources (`featured|latest|category|manual|related|root|parent`) map back onto
|
||||
the product/category providers of §7–§8.
|
||||
|
||||
---
|
||||
|
||||
## 14. Admin gateways (feature area)
|
||||
|
||||
`src/app/features/admin/**`. Each domain follows Facade → Gateway (interface) → LocalGateway.
|
||||
**Only categories and dashboard-metrics use a DI token; all others inject the local class
|
||||
directly (MOCK-ONLY, no seam).** Only `AdminCategoriesApiGateway` is a real HTTP impl.
|
||||
|
||||
| Domain | Interface | Local (mock) impl | Real impl | DI token | Facade | Seam status |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Categories | `admin-categories-gateway.interface.ts` (`AdminCategoriesGateway`) | `admin-categories-local.gateway.ts` | `admin-categories-api.gateway.ts` (**HttpClient**) | `ADMIN_CATEGORIES_GATEWAY` (`admin-categories-gateway.token.ts`) | `AdminCategoriesFacade` | MOCK-SWAPPABLE (real impl exists) |
|
||||
| Dashboard metrics | `admin-dashboard-metrics.gateway.interface.ts` (`AdminDashboardMetricsGateway`) | `admin-dashboard-metrics.local.gateway.ts` | none | `ADMIN_DASHBOARD_METRICS_GATEWAY` (`admin-dashboard-metrics-gateway.token.ts`) | `AdminDashboardFacade` | MOCK-SWAPPABLE (token only) |
|
||||
| Orders | `admin-orders-gateway.interface.ts` (`AdminOrdersGateway`) | `admin-orders-local.gateway.ts` | none | **none** | `AdminOrdersFacade` (injects `AdminOrdersLocalGateway`) | MOCK-ONLY (no seam) |
|
||||
| Products | `admin-products-gateway.interface.ts` (`AdminProductsGateway`) | `admin-products-local.gateway.ts` | none | **none** | `AdminProductsFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Users | `admin-users-gateway.interface.ts` (`AdminUsersGateway`) | `admin-users-local.gateway.ts` | none | **none** | `AdminUsersFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Transactions | `admin-transactions-gateway.interface.ts` (`AdminTransactionsGateway`) | `admin-transactions-local.gateway.ts` | none | **none** | `AdminTransactionsFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Monitoring | `admin-monitoring-gateway.interface.ts` (`AdminMonitoringGateway`) | `admin-monitoring-local.gateway.ts` | none | **none** | `AdminMonitoringFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Moderation | `admin-moderation-gateway.interface.ts` (`AdminModerationGateway`) | `admin-moderation-local.gateway.ts` | none | **none** | `AdminModerationFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Customers | (no gateway of its own) | reuses `AdminOrdersLocalGateway` | none | **none** | `AdminCustomersFacade` (injects orders local) | MOCK-ONLY (derived) |
|
||||
| Analytics | (no gateway of its own) | reuses orders/products/moderation local + `ADMIN_CATEGORIES_GATEWAY` + `AdminDashboardFacade` | none | partial (categories token) | `AdminAnalyticsFacade` | MOCK-ONLY (derived) |
|
||||
|
||||
Gateway interface method contracts (the shapes a backend must satisfy):
|
||||
|
||||
- **`AdminCategoriesGateway`**: `loadCategories(filters)`, `loadCategory(id)`, `createCategory`,
|
||||
`updateCategory`, `deleteCategory`, `restoreCategory`, `isSlugTaken(slug,excludingId)`.
|
||||
- **`AdminDashboardMetricsGateway`**: `loadMetrics(): AdminDashboardMetrics`.
|
||||
- **`AdminOrdersGateway`**: `loadOrders(filters)`, `loadOrder(id)`, `updateStatus(id,status)`,
|
||||
`requestRefund(id)`, `addNote(id,note,internal)`, `archiveOrder`, `restoreOrder`, `deleteOrder`.
|
||||
- **`AdminProductsGateway`**: `loadProducts(filters)`, `loadProduct(id)`, `loadCategories()`,
|
||||
`createProduct`, `updateProduct`, `deleteProduct`, `duplicateProduct`, `archiveProduct`,
|
||||
`restoreProduct`.
|
||||
- **`AdminUsersGateway`**: `loadUsers`, `loadRoles`, `loadInvitations`, `loadSessions(userId)`,
|
||||
`loadAudit(userId)`, `setUserRole`, `setUserStatus`, `inviteUser(email,roleId,scope)`,
|
||||
`revokeInvitation`, `revokeSession`.
|
||||
- **`AdminTransactionsGateway`**: `loadTransactions(filters)`, `retryFailed(id)`,
|
||||
`setFraudFlag(id,flagged)`.
|
||||
- **`AdminMonitoringGateway`**: `loadEvents(filters)`, `loadQueues()`, `loadWebhooks()`.
|
||||
- **`AdminModerationGateway`**: `loadReviews(filters)`, `loadReview(id)`, `setReviewStatus`,
|
||||
`setReviewVisible`, `setReviewPinned`, `setReviewFeatured`, `addModeratorNote`, `deleteReview`,
|
||||
`loadReports()`, `setReportStatus(id,status)`.
|
||||
|
||||
Admin model files (all under `src/app/features/admin/<domain>/models/`) — see §23.
|
||||
|
||||
Note: `AdminRole` is defined **twice** with different meaning — `src/app/core/auth/models/
|
||||
permission.model.ts` (auth roles `Owner|Administrator|Editor|Support|ReadOnly`) vs
|
||||
`src/app/features/admin/users/models/admin-user.model.ts` (`AdminRole` interface {id,name,...}).
|
||||
Flag for backend/naming reconciliation.
|
||||
|
||||
Local gateways are localStorage / in-memory backed (facades also inject `LocalStorageService`
|
||||
for overlay persistence, e.g. orders/moderation/categories/products).
|
||||
|
||||
---
|
||||
|
||||
## 15. Domain: Media library
|
||||
|
||||
MOCK-SWAPPABLE via abstract-class token, no real impl.
|
||||
|
||||
- **Contract**: abstract class `MediaRepository` (`src/app/core/media/media-repository.ts`) —
|
||||
`list(params?)`, `upload(file,options?)`, `remove(id)`, `update(id,patch)`, `listFolders()`.
|
||||
- **Binding**: `app.config.ts` → `{ provide: MediaRepository, useClass: MockMediaRepository }`.
|
||||
- **Mock impl**: `MockMediaRepository` (`src/app/core/media/mock-media-repository.service.ts`,
|
||||
uses `HttpClient` to read seed assets). Also `MediaUsageService`
|
||||
(`src/app/core/media/media-usage.service.ts`).
|
||||
- **Facade**: `MediaLibraryFacade` (`src/app/features/backoffice/media/facade/media-library.facade.ts`),
|
||||
page `media-library-page.component.ts`.
|
||||
- **Models** (`src/app/core/media/models/media-asset.model.ts`): `MediaAsset`, `MediaAssetKind`,
|
||||
`MediaSort`, `MediaListParams`, `MediaUploadOptions`, `MediaListResult`.
|
||||
- Admin-auth interceptor already gates `/media/` paths (§3), anticipating a real media backend.
|
||||
|
||||
---
|
||||
|
||||
## 16. Domain: Content management / static pages
|
||||
|
||||
**LOCAL-ONLY** — operates on the already-loaded `BootstrapConfig.staticPages`, no dedicated
|
||||
backend calls. Publishing/writing bootstrap is not implemented client-side (FUTURE).
|
||||
|
||||
- **Facade**: `ContentManagementFacade` (`src/app/features/content-management/facade/content-management.facade.ts`)
|
||||
→ `ContentPageService` (`.../services/content-page.service.ts`). Public API: `pages(bootstrap)`,
|
||||
`hasSeoContent(page)`, `contentHealth(bootstrap)`, `resolvePage(bootstrap,keyOrSlug,locale)`,
|
||||
`validatePages(bootstrap)`, `toBootstrapRecord(bootstrap)`, `serializePages(pages)`,
|
||||
`normalizeSlug`.
|
||||
- `ContentPageService` maps between bootstrap `StaticPagesConfig` and the editor `ContentPage`
|
||||
view model (normalize / validate / `toBootstrapRecord`). This is the adapter.
|
||||
- **Models** (`src/app/features/content-management/models/`): `ContentPage`,
|
||||
`ContentPageTranslation`, `ContentPageSeoConfig`, `ContentPageStatus`,
|
||||
`ContentPageBootstrapInput` (`content-page.model.ts`); `LegalPageKey`, `LegalPageDefinition`
|
||||
(`legal-pages.model.ts`). Backend-shaped counterpart: `StaticPageConfig`,
|
||||
`StaticPagesConfig`, `ResolvedStaticPage`, `LocalizedHtmlContent`, `LocalizedTextContent`
|
||||
(`src/app/shared/models/config/static-page.model.ts`).
|
||||
- Consumers: `static-pages-editor.component.ts`, `page-editor.component.ts`,
|
||||
`static-page.component.ts` (`src/app/pages/static-page/`), resolved via
|
||||
`StaticPageResolverService`.
|
||||
|
||||
---
|
||||
|
||||
## 17. Domain: Project editor / builder
|
||||
|
||||
**LOCAL-ONLY** today — edits an in-memory `BootstrapConfig`, persists drafts to localStorage;
|
||||
no publish/save-to-backend HTTP call exists. A builder API is declared only as
|
||||
`BootstrapConfig.apiEndpoints.builder` (runtime-declared, FUTURE).
|
||||
|
||||
- **Facade**: `ProjectEditorFacade` (`src/app/features/project-editor/facade/project-editor.facade.ts`)
|
||||
— orchestrates undo/redo `History<BootstrapConfig>`, injects `ConfigService`,
|
||||
`ProjectEditorIoService` (JSON import/export of bootstrap), `ProjectEditorPreviewService`,
|
||||
`LocaleSyncService`, `PlatformRuntimeService`, `ProjectValidator`,
|
||||
`ProjectEditorDraftStorageService` (localStorage drafts), `EditorSchemaService`.
|
||||
- Services (`src/app/features/project-editor/services/`): `project-editor-io.service.ts`
|
||||
(`exportBootstrap`/`importBootstrap` = JSON.stringify/parse), `project-editor-draft-storage.service.ts`,
|
||||
`project-editor-preview.service.ts`, `project-validator.service.ts`, `locale-sync.service.ts`.
|
||||
Schema: `schema/editor-schema.service.ts`, `schema/field-schema.model.ts`, `schema/validators/`.
|
||||
- **Models**: `project-editor.model.ts` (`ProjectEditorState`, `ProjectEditorSectionId`,
|
||||
`ProjectEditorWidgetPreset`, `BuilderSectionStatus`), `builder/builder-groups.model.ts`.
|
||||
- Consumers: `project-editor-page.component.ts`, `homepage-section.component.ts`,
|
||||
`project-editor-nav.component.ts`. Also drives admin products/categories/dashboard facades
|
||||
(which inject `ProjectEditorFacade`).
|
||||
|
||||
---
|
||||
|
||||
## 18. Domain: Search
|
||||
|
||||
**LOCAL-ONLY orchestration over the product/category providers** — no dedicated search backend;
|
||||
`SearchFacade` composes `ProductFacade` + `CategoryFacade` results and manages history/trending/
|
||||
autocomplete/cache client-side.
|
||||
|
||||
- **Facade**: `SearchFacade` (`src/app/features/search/facade/search.facade.ts`) injects
|
||||
`ProductFacade`, `CategoryFacade`, `SearchAutocompleteService`, `SearchHistoryService`,
|
||||
`SearchTrendingService`, `SearchCacheService`, `SearchStore`, `TranslateService`.
|
||||
- Services (`src/app/features/search/services/`): `search-autocomplete.service.ts`,
|
||||
`search-history.service.ts` + `search-history.repository.ts` (interface
|
||||
`SearchHistoryRepository{load,save,clear}`, localStorage), `search-trending.service.ts`,
|
||||
`search-cache.service.ts`. Store: `store/search.store.ts`.
|
||||
- **Models**: `src/app/features/search/models/search.model.ts` (`SearchQuery`, `SearchResult<T>`,
|
||||
`SearchSuggestion`, `SearchFilterType`, `FilterGroup`, `FilterOption`, `SortOption`,
|
||||
`SearchHistory`, `SearchAnalyticsEvent`, `SearchNavigationTarget`), `search-state.model.ts`
|
||||
(`SearchState`). Duplicated under `src/app/core/search/models/`.
|
||||
- Underlying live traffic is `GET /searchitems` (§4) via `ProductFacade.searchProducts`.
|
||||
|
||||
---
|
||||
|
||||
## 19. Domain: User experience (wishlist/compare/etc.)
|
||||
|
||||
**LOCAL-ONLY** (guest-first). MOCK-SWAPPABLE token exists for a future authenticated backend.
|
||||
|
||||
- **Interface**: `UserExperienceRepository`
|
||||
(`src/app/core/user-experience/repositories/user-experience.repository.ts`).
|
||||
- **DI token**: `USER_EXPERIENCE_REPOSITORY`
|
||||
(`src/app/core/user-experience/user-experience-repository.token.ts`) → currently always
|
||||
`LocalUserExperienceRepository` (localStorage). Comment notes it "can be switched to
|
||||
authenticated repository later."
|
||||
- **Facade**: `UserExperienceFacade` (`src/app/facades/platform/user-experience.facade.ts`) —
|
||||
wishlist / compare / recently-viewed / saved-searches / continue-browsing, all signals.
|
||||
- **Models** (`src/app/core/user-experience/models/user-experience.model.ts`): `FavoriteItem`,
|
||||
`ComparedProduct`, `RecentlyViewedItem`, `SavedSearch`, `ContinueBrowsingState`. Config shape:
|
||||
`user-experience-config.model.ts` (limits, from bootstrap).
|
||||
|
||||
---
|
||||
|
||||
## 20. Domain: Diagnostics
|
||||
|
||||
**LOCAL-ONLY** — inspects runtime/bootstrap/widget state; the one live-ish probe is API ping.
|
||||
|
||||
- **Facade**: `DiagnosticsFacade` (`src/app/features/diagnostics/facade/diagnostics.facade.ts`)
|
||||
injects `ConfigService`, `TenantResolverService`, `PlatformRuntimeStateService`,
|
||||
`RuntimeDiagnosticsService`, `WidgetManifestService`, `WidgetRegistryService`,
|
||||
`RuntimeProviderStrategyService`, `DiagnosticsLoggerService`, `TranslateService`, `Router`.
|
||||
- Validators: `validators/runtime-diagnostics.validator.ts` (uses `HttpClient` for API health
|
||||
probe), `bootstrap-diagnostics.validator.ts`, `diagnostics-health-score.util.ts`.
|
||||
- **Models** (`src/app/features/diagnostics/models/diagnostics.model.ts`): `DiagnosticEntry`,
|
||||
`DiagnosticSeverity`, `DiagnosticsHealthSummary`, `DiagnosticsReport`.
|
||||
|
||||
---
|
||||
|
||||
## 21. Facade catalog
|
||||
|
||||
| Facade | File | Depends on | Consumed by (examples) |
|
||||
|---|---|---|---|
|
||||
| `ProductFacade` | `facades/platform/product.facade.ts` | `ProductDataService` → `PRODUCT_DATA_PROVIDER` | catalog/product containers, `SearchFacade` |
|
||||
| `CategoryFacade` | `facades/platform/category.facade.ts` | `CategoryService` → `CATEGORY_REPOSITORY` | catalog nav, `SearchFacade` |
|
||||
| `SearchFacade` | `features/search/facade/search.facade.ts` | ProductFacade, CategoryFacade, search services | search bar/pages |
|
||||
| `UserExperienceFacade` | `facades/platform/user-experience.facade.ts` | `USER_EXPERIENCE_REPOSITORY` | wishlist/compare UI |
|
||||
| `UiRuntimeFacade` | `facades/runtime/ui-runtime.facade.ts` | `ConfigService` | header/branding |
|
||||
| `WebsiteRuntimeFacade` | `facades/website/website-runtime.facade.ts` | config/page renderer | dynamic pages |
|
||||
| `AuthFacade` | `core/auth/services/auth-facade.service.ts` | AuthService, SessionService, PermissionService | login/guarded UI |
|
||||
| `MediaLibraryFacade` | `features/backoffice/media/facade/media-library.facade.ts` | `MediaRepository` | media page |
|
||||
| `ContentManagementFacade` | `features/content-management/facade/...` | `ContentPageService` (bootstrap) | content dashboard/editor |
|
||||
| `ProjectEditorFacade` | `features/project-editor/facade/...` | config + editor services (localStorage) | builder pages, admin facades |
|
||||
| `DiagnosticsFacade` | `features/diagnostics/facade/...` | runtime/config/widget services | diagnostics page |
|
||||
| `AdminCategoriesFacade` | `features/admin/categories/facade/...` | `ADMIN_CATEGORIES_GATEWAY`, ProjectEditorFacade | admin categories pages |
|
||||
| `AdminProductsFacade` | `features/admin/products/facade/...` | `AdminProductsLocalGateway`, ProjectEditorFacade | admin products pages |
|
||||
| `AdminOrdersFacade` | `features/admin/orders/facade/...` | `AdminOrdersLocalGateway` | admin orders pages |
|
||||
| `AdminUsersFacade` | `features/admin/users/facade/...` | `AdminUsersLocalGateway` | admin users pages |
|
||||
| `AdminTransactionsFacade` | `features/admin/transactions/facade/...` | `AdminTransactionsLocalGateway` | admin transactions pages |
|
||||
| `AdminMonitoringFacade` | `features/admin/monitoring/facade/...` | `AdminMonitoringLocalGateway` | admin monitoring page |
|
||||
| `AdminModerationFacade` | `features/admin/moderation/facade/...` | `AdminModerationLocalGateway` | moderation pages |
|
||||
| `AdminCustomersFacade` | `features/admin/customers/facade/...` | `AdminOrdersLocalGateway` (derives customers from orders) | customers pages |
|
||||
| `AdminAnalyticsFacade` | `features/admin/analytics/facade/...` | orders/products/moderation local + `ADMIN_CATEGORIES_GATEWAY` + `AdminDashboardFacade` | analytics page |
|
||||
| `AdminDashboardFacade` | `features/admin/dashboard/facade/...` | `ADMIN_DASHBOARD_METRICS_GATEWAY`, ProjectEditorFacade, AdminAuthService | admin dashboard |
|
||||
|
||||
`ProductFacade` public API: `getProducts, getProduct, getCategories, searchProducts,
|
||||
getFeaturedProducts, getLatestProducts, getProductsByCategory, getRelatedProducts, loadRating,
|
||||
loadReviews, loadQuestions, submitReview, submitQuestion, search(criteria), filter, sort,
|
||||
loadCatalog`. `CategoryFacade`: signals (`allCategories, categoryTree, rootCategories,
|
||||
selectedCategory, breadcrumb, children, loading, error`) + `loadCategories, selectCategory,
|
||||
getAllCategories, getCategoryTree, getRootCategories, getCategoryById, getBreadcrumb, getChildren`.
|
||||
`UserExperienceFacade`: `isInWishlist, toggleWishlist, clearWishlist, isInCompare, addToCompare,
|
||||
removeFromCompare, clearCompare, trackRecentlyViewed, saveSearch, removeSavedSearch,
|
||||
saveContinueBrowsing, getContinueBrowsing` + wishlist/compare signals & counts.
|
||||
|
||||
---
|
||||
|
||||
## 22. Gateway / provider master table
|
||||
|
||||
| Gateway/provider | Interface path | Mock/local impl | Real/API impl | DI token | Consuming facade(s) | Status |
|
||||
|---|---|---|---|---|---|---|
|
||||
| ConfigProvider | `core/config/config-provider.interface.ts` | `core/bootstrap/providers/mock-bootstrap.provider.ts` | `core/bootstrap/providers/api-bootstrap.provider.ts` | `CONFIG_PROVIDER` | UiRuntime, WebsiteRuntime, ProjectEditor, ContentMgmt, Diagnostics (via ConfigService) | LIVE (`GET /bootstrap`) |
|
||||
| ProductDataProvider | `core/products/providers/product-data-provider.interface.ts` | none bound | `core/products/providers/api-product-data.provider.ts` | `PRODUCT_DATA_PROVIDER` | ProductFacade | LIVE |
|
||||
| CategoryRepository | `core/categories/repositories/category.repository.ts` | none bound | `core/categories/repositories/api-category.repository.ts` | `CATEGORY_REPOSITORY` | CategoryFacade | LIVE |
|
||||
| BackofficeDataProvider | `core/backoffice/providers/backoffice-data-provider.interface.ts` | `mock-backoffice-data.provider.ts` | `api-backoffice-data.provider.ts` | `BACKOFFICE_DATA_PROVIDER` | storefront cards | LIVE (`/api/backoffice/*`) |
|
||||
| UserExperienceRepository | `core/user-experience/repositories/user-experience.repository.ts` | `local-user-experience.repository.ts` | none | `USER_EXPERIENCE_REPOSITORY` | UserExperienceFacade | LOCAL-ONLY |
|
||||
| MediaRepository | `core/media/media-repository.ts` (abstract class) | `core/media/mock-media-repository.service.ts` | none | `MediaRepository` class (app.config.ts) | MediaLibraryFacade | MOCK-SWAPPABLE |
|
||||
| SearchHistoryRepository | `features/search/services/search-history.repository.ts` | (localStorage impl) | none | (injected concretely) | SearchFacade (via SearchHistoryService) | LOCAL-ONLY |
|
||||
| AdminCategoriesGateway | `features/admin/categories/services/admin-categories-gateway.interface.ts` | `admin-categories-local.gateway.ts` | `admin-categories-api.gateway.ts` | `ADMIN_CATEGORIES_GATEWAY` | AdminCategoriesFacade, AdminAnalyticsFacade | MOCK-SWAPPABLE (real impl exists) |
|
||||
| AdminDashboardMetricsGateway | `features/admin/dashboard/services/admin-dashboard-metrics.gateway.interface.ts` | `admin-dashboard-metrics.local.gateway.ts` | none | `ADMIN_DASHBOARD_METRICS_GATEWAY` | AdminDashboardFacade | MOCK-SWAPPABLE (token only) |
|
||||
| AdminOrdersGateway | `features/admin/orders/services/admin-orders-gateway.interface.ts` | `admin-orders-local.gateway.ts` | none | **none** | AdminOrdersFacade, AdminCustomersFacade, AdminAnalyticsFacade | MOCK-ONLY (no seam) |
|
||||
| AdminProductsGateway | `features/admin/products/services/admin-products-gateway.interface.ts` | `admin-products-local.gateway.ts` | none | **none** | AdminProductsFacade, AdminAnalyticsFacade | MOCK-ONLY (no seam) |
|
||||
| AdminUsersGateway | `features/admin/users/services/admin-users-gateway.interface.ts` | `admin-users-local.gateway.ts` | none | **none** | AdminUsersFacade | MOCK-ONLY (no seam) |
|
||||
| AdminTransactionsGateway | `features/admin/transactions/services/admin-transactions-gateway.interface.ts` | `admin-transactions-local.gateway.ts` | none | **none** | AdminTransactionsFacade | MOCK-ONLY (no seam) |
|
||||
| AdminMonitoringGateway | `features/admin/monitoring/services/admin-monitoring-gateway.interface.ts` | `admin-monitoring-local.gateway.ts` | none | **none** | AdminMonitoringFacade | MOCK-ONLY (no seam) |
|
||||
| AdminModerationGateway | `features/admin/moderation/services/admin-moderation-gateway.interface.ts` | `admin-moderation-local.gateway.ts` | none | **none** | AdminModerationFacade, AdminAnalyticsFacade | MOCK-ONLY (no seam) |
|
||||
| (Auth session) | — (`TelegramSessionApiService`) | mock via `mockDataInterceptor` | `services/telegram-session-api.service.ts` | n/a (concrete) | AuthService, AdminAuthService, AuthFacade | LIVE |
|
||||
| (Ed25519 admin auth) | — (`AuthApiService`) | none | `core/auth/services/auth-api.service.ts` | n/a (concrete) | AuthService (Ed25519 flow) | LIVE wiring, backend absent |
|
||||
|
||||
---
|
||||
|
||||
## 23. Model / DTO catalog
|
||||
|
||||
Grouped by boundary role. B = backend-shaped/wire DTO, V = frontend view model, C = bootstrap
|
||||
config shape. Adapter column names the mapper if distinct.
|
||||
|
||||
### Core wire DTOs / domain (B)
|
||||
- `Item` + supporting (`src/app/models/item.model.ts`) — **primary product wire shape**; adapter
|
||||
`ApiService.normalizeItem()`.
|
||||
- `Category`, `Subcategory` (`src/app/models/category.model.ts`) — legacy category wire; adapter
|
||||
`ApiService.normalizeCategory()`.
|
||||
- `CategoryDto`, `CategoryNameDto` (`src/app/core/categories/dto/category.dto.ts`) — clean-stack
|
||||
wire DTO; adapter `CategoryMapper`.
|
||||
- `Region`, `GeoIpResponse` (`src/app/models/location.model.ts`).
|
||||
- Payment/order DTOs inline in `src/app/services/api.service.ts`: `QrCreateRequest`,
|
||||
`QrCreateResponse`, `CartPaymentRequest`, `CreateOrderRequest`, `CreateOrderResponse`,
|
||||
`QrDynamicStatusResponse`.
|
||||
- Auth: `AuthSession`, `WebSessionStart` (`src/app/models/auth.model.ts`); `AuthChallenge`,
|
||||
`VerifySignatureRequest`, `AuthTokenPair`, `RefreshTokenRequest`, `JwtClaims`
|
||||
(`src/app/core/auth/models/auth-api.model.ts`).
|
||||
|
||||
### Domain / view models (V)
|
||||
- Products: `Product`(=Item alias), `ProductListQuery`, `ProductSearchQuery`, `ProductListResult`,
|
||||
`ProductFilters`, `RelatedProductsQuery`, `RelatedProductCollection`, `ProductVariantSelection`
|
||||
(`core/products/models/product-domain.model.ts`).
|
||||
- Engagement: `Review`, `Answer`, `Question`, `RatingSummary`, `RatingDistributionEntry`,
|
||||
`EngagementListQuery`, `EngagementListResult<T>`, `SubmitReviewInput`, `SubmitQuestionInput`
|
||||
(`core/products/models/product-engagement.model.ts`).
|
||||
- Catalog experience: `SearchCriteria`, `FilterDefinition`, `FilterOption`, `SortDefinition`,
|
||||
`CatalogView`, `SearchResult` (`core/products/models/catalog-experience.model.ts`);
|
||||
catalog state (`features/website/catalog/models/catalog-state.model.ts`).
|
||||
- Category domain: `Category`, `CategoryTranslation` (`core/categories/models/category-domain.model.ts`).
|
||||
- Media: `MediaAsset` + params/results (`core/media/models/media-asset.model.ts`).
|
||||
- User experience: `FavoriteItem`, `ComparedProduct`, `RecentlyViewedItem`, `SavedSearch`,
|
||||
`ContinueBrowsingState` (`core/user-experience/models/user-experience.model.ts`).
|
||||
- Search: `search.model.ts` + `search-state.model.ts` (`features/search/models/`, dup in `core/search/models/`).
|
||||
- Content: `ContentPage`, `ContentPageTranslation`, `ContentPageSeoConfig`, `ContentPageStatus`,
|
||||
`ContentPageBootstrapInput`, `LegalPageKey`, `LegalPageDefinition`
|
||||
(`features/content-management/models/`); adapter `ContentPageService`.
|
||||
- Project editor: `ProjectEditorState`, `ProjectEditorSectionId`, `ProjectEditorWidgetPreset`,
|
||||
`BuilderSectionStatus` (`features/project-editor/models/`), `builder-groups.model.ts`.
|
||||
- Diagnostics: `DiagnosticEntry`, `DiagnosticsHealthSummary`, `DiagnosticsReport`
|
||||
(`features/diagnostics/models/diagnostics.model.ts`).
|
||||
- Widgets: contracts in `src/app/widgets/contracts/*` and renderer `*.model.ts` (see §13).
|
||||
|
||||
### Admin models (V, all under `features/admin/<domain>/models/`)
|
||||
- `admin-order.model.ts`: `AdminOrder`, `AdminOrderCustomer`, `AdminOrderPayment`,
|
||||
`AdminOrderShipping`, `AdminOrderItem`, `AdminOrderTimelineEntry`, `AdminOrderStatus`,
|
||||
`AdminOrderPaymentStatus`, `AdminOrderTimelineEventKey`, `AdminOrderListFilters`,
|
||||
`AdminOrdersListResult`.
|
||||
- `admin-product.model.ts`: `AdminProduct` (+ `AdminProductMedia`, `AdminProductSpecification`,
|
||||
`AdminProductVariant(Price)`, `AdminProductVariantAttributeDef`, `AdminProductAttribute`,
|
||||
`AdminProductTranslation`, `AdminProductSeo`, `AdminProductReview`, `AdminProductQuestion`),
|
||||
`AdminProductListFilters`, `AdminProductsListResult`, `AdminProductCategoryOption`, status/sort/mode types.
|
||||
- `admin-category.model.ts`: `AdminCategory`, `AdminCategoryTranslation`, `AdminCategorySeo`,
|
||||
`AdminCategoryAttribute`, `AdminCategoryListFilters`, status/mode types.
|
||||
- `admin-user.model.ts`: `AdminUser`, `AdminRole`, `AdminInvitation`, `AdminSession`,
|
||||
`AdminUserAuditEntry`, scope/status/invitation-status types.
|
||||
- `admin-transaction.model.ts`: `AdminTransaction`, `AdminTransactionAuditEntry`,
|
||||
`AdminTransactionListFilters`, `AdminTransactionsListResult`, type/status types.
|
||||
- `admin-monitoring.model.ts`: `AdminMonitoringEvent`, `AdminMonitoringEventFilters`,
|
||||
`AdminQueue`, `AdminWebhookDelivery`, category/level/queue/webhook status types.
|
||||
- `admin-review.model.ts`: `AdminReview`, `AdminReviewTimelineEntry`, `AdminReviewListFilters`,
|
||||
`AdminReviewsListResult`, status/timeline types.
|
||||
- `admin-report.model.ts`: `AdminReport`, `AdminReportTargetType`, `AdminReportStatus`.
|
||||
- `admin-customer.model.ts`: `AdminCustomer`.
|
||||
- `admin-analytics.model.ts`: `AdminAnalyticsSummary`, `AdminAnalyticsSeriesPoint`,
|
||||
`AdminAnalyticsTopProduct`, `AdminLowStockProduct`, `AdminRecentActivityEntry`,
|
||||
`AdminMarketplaceHealthCheck`, `AdminProductAnalytics(Row)`, `AdminCustomerAnalytics`,
|
||||
`AdminRecommendationCard`, date-range/severity/health types.
|
||||
- `admin-dashboard.model.ts`: `AdminDashboardMetrics`, `AdminDashboardCardState<T>`,
|
||||
`AdminDashboardQuickAction(Id)`, `AdminDashboardActivityEntry`, `AdminDashboardHealthCheck`,
|
||||
`AdminDashboardHomeHealthCheck`, `AdminDashboardDraftField`, `AdminDashboardShortcut`, status types.
|
||||
- Shell: `features/admin/shell/admin-nav.model.ts`.
|
||||
|
||||
### Bootstrap config shapes (C)
|
||||
All under `src/app/shared/models/config/` — see §6 for the full list (24 files + barrel).
|
||||
|
||||
---
|
||||
|
||||
## 24. Endpoint URL literals found in code
|
||||
|
||||
Marketplace API (relative to base): `/ping`, `/bootstrap`, `/category`, `/category/{id}`,
|
||||
`/items/{id}`, `/items/randomitems`, `/searchitems`, `/cart`, `/orders`, `/purchase-email`,
|
||||
`/regions`, `/websession/{sessionId}`, `/items/{id}/callback`, `/items/{id}/questiion`.
|
||||
|
||||
Backoffice storefront: `/api/backoffice/products`, `/api/backoffice/categories`.
|
||||
|
||||
Payment (`qrApiUrl` = `https://qr.vitanova.network/api`): `/qr`, `/qr/dynamic/{partnerId}/{qrId}`,
|
||||
`/card/{partnerId}/{orderId}`. Const partner id `web-97ec-9c57-4dde-9037-3a68f7f83750`.
|
||||
|
||||
Session auth (`authApiUrl`): `/users/sessions`, `/users/sessions/{id}`.
|
||||
|
||||
Ed25519 admin auth (`authApiUrl`): `/api/admin/auth/challenge|verify|refresh|logout`
|
||||
(not implemented server-side).
|
||||
|
||||
Static assets (not backend): `/assets/mock/bootstrap/bootstrap.json`,
|
||||
`/assets/mock/bootstrap/widget-manifest.json`.
|
||||
|
||||
External (not this platform): `http://ip-api.com/json/...` (geo-IP),
|
||||
`https://api.qrserver.com/v1/create-qr-code/...` (QR image), `https://t.me/{bot}`,
|
||||
`tg://resolve?...`.
|
||||
|
||||
`mockDataInterceptor` URL matchers (mock mode only): `/ping`, `/users/sessions[/{id}]`,
|
||||
`/category`, `/category/{id}`, `/items/{id}`, `/searchitems`, `/randomitems`, `/cart`,
|
||||
`/websession/{id}[/qr]`, `/qr`, `/items/{id}/callback`, `/purchase-email`, `/qr/payment/{id}`.
|
||||
|
||||
**No literal `/admin/*`, `/builder/*`, or per-admin-domain backoffice CRUD paths exist in code.**
|
||||
Those live only as `apiEndpoints.{builder,backoffice}` records inside the runtime bootstrap
|
||||
document, and admin gateways are in-memory (they never construct a URL). Any concrete admin CRUD
|
||||
path is therefore a proposal, not a verified literal — consistent with `docs/BACKEND_API.md`
|
||||
Assumption #2.
|
||||
|
||||
The admin-auth-headers interceptor gates these path **segments** (anticipatory, not called yet):
|
||||
`/admin/`, `/backoffice/`, `/builder/`, `/media/`.
|
||||
|
||||
---
|
||||
|
||||
## 25. Cross-check against existing docs
|
||||
|
||||
Skimmed: `docs/BACKEND_API.md` (canonical master spec, CURRENT/PLANNED/FUTURE tagging),
|
||||
`docs/AUTH.md`, `docs/ADMIN.md`, `docs/BACKEND_API_REMAINING_WORK.md`,
|
||||
`docs/architecture/foundation/**`, `docs/backend/BACKEND-INTEGRATION.md`.
|
||||
|
||||
Agreements (preserve these conventions downstream):
|
||||
- `docs/BACKEND_API.md` already uses `GET /bootstrap`, the `*LocalGateway` → `*ApiGateway`
|
||||
rebind pattern, and frozen auth/payment (ADR-010). Its CURRENT/PLANNED/FUTURE tagging maps
|
||||
cleanly onto LIVE / MOCK-SWAPPABLE / MOCK-ONLY here.
|
||||
- Assumption #2 (builder/backoffice paths are proposals, not literals) is confirmed by code.
|
||||
- `submitQuestion` typo `questiion` and `callback` review path confirmed against code.
|
||||
|
||||
Discrepancies / things to flag for a human:
|
||||
1. **`docs/BACKEND_API.md` PLANNED framing implies every admin domain is a token rebind.**
|
||||
In code, only `ADMIN_CATEGORIES_GATEWAY` and `ADMIN_DASHBOARD_METRICS_GATEWAY` are
|
||||
token-bound. Orders, products, users, transactions, monitoring, moderation (and derived
|
||||
customers/analytics) inject the concrete `*LocalGateway` directly — no seam. A backend
|
||||
integration for those requires adding a token first. This should be reconciled in the docs.
|
||||
2. **Only one real admin API impl exists** (`AdminCategoriesApiGateway`). Everything else admin
|
||||
is mock. Docs that describe admin endpoints as "PLANNED, served by local gateway" are
|
||||
accurate in spirit but the swap ergonomics differ per domain (see #1).
|
||||
3. **Duplicate `Category` types** (`src/app/models/category.model.ts` vs
|
||||
`core/categories/models/category-domain.model.ts`) and **duplicate `AdminRole`**
|
||||
(auth `permission.model.ts` string-union vs users `admin-user.model.ts` interface) — naming
|
||||
collisions a backend/contract author should be warned about.
|
||||
4. **Duplicate search models** under `features/search/models/` and `core/search/models/`.
|
||||
5. **Content-management & project-editor "save/publish" has no client HTTP call.** Docs that
|
||||
imply a builder publish endpoint should tag it FUTURE — there is no `PUT /bootstrap` or
|
||||
builder-write call anywhere in code today; changes live in localStorage drafts + in-memory
|
||||
bootstrap only.
|
||||
6. `PRODUCT_DATA_PROVIDER` / `CATEGORY_REPOSITORY` token factories return the Api provider even
|
||||
in `mock` mode (no mock class bound) — so `useMockData` does NOT mock products/categories at
|
||||
the provider layer; mocking there relies entirely on `mockDataInterceptor`. Worth noting if a
|
||||
doc claims a mock product provider exists.
|
||||
|
||||
---
|
||||
|
||||
_Generated from source on branch `B2B`. Every path above is repo-relative to
|
||||
`F:\dx\remote\marketplaces\`._
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
id: ADR-0001
|
||||
title: Multi-tenant marketplace platform vision and config-driven architecture
|
||||
status: active
|
||||
date: 2026-07-13
|
||||
tags: ["architecture", "philosophy", "multi-tenant", "bootstrap"]
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
This is not a single marketplace — it is a multi-tenant platform powering unlimited
|
||||
marketplaces (e.g. electronics.example.com, books.example.com) from one codebase.
|
||||
Every marketplace is configured from the backend via a bootstrap configuration
|
||||
(`GET /bootstrap`). No marketplace-specific code may exist in the frontend.
|
||||
|
||||
## Decision
|
||||
|
||||
- The frontend (Angular 20, standalone components, Signals, RxJS, SCSS) is a pure
|
||||
renderer. It owns render, navigation, interaction, validation, animations only.
|
||||
- The backend (ASP.NET Core REST API) owns branding, pages, layouts, languages,
|
||||
homepage, navigation, categories, products, footer, static pages, payment
|
||||
configuration, and enabled features.
|
||||
- Flow: Bootstrap → Runtime Provider → Configuration Store → Renderer → Widgets.
|
||||
Nothing depends on build-time environments; everything depends on runtime
|
||||
configuration.
|
||||
- Bootstrap contains only data needed before the app starts (name, logo, colors,
|
||||
languages, footer pages, homepage layout, navigation, enabled widgets). It must
|
||||
never contain products, orders, cart, or users.
|
||||
- Widgets never own page spacing — only their own internal layout. The renderer
|
||||
owns sections, spacing, and page width.
|
||||
- Homepage is composed from a configurable, ordered list of sections (Section
|
||||
Engine): Hero, Categories, Featured Products, Banner, Latest Products, Custom
|
||||
HTML, Newsletter, etc.
|
||||
- All layouts (homepage, PLP, etc.) must be backend-configurable without frontend
|
||||
changes.
|
||||
- All user-facing text is translatable via a `translations.{lang}` shape, not a
|
||||
flat `title` field. Adding/removing a supported language must automatically
|
||||
expose/remove translation fields across all translatable objects, generically —
|
||||
never per-field hardcoding.
|
||||
- Static pages (About Us, Privacy, Terms, Contacts, Return Policy, Delivery,
|
||||
custom pages) are backend-delivered HTML, multilingual, and drive the footer.
|
||||
- Admin and storefront share a domain but are fully separate applications: the
|
||||
marketplace bundle never ships admin code and vice versa. Bootstrap is public;
|
||||
Admin is protected by JWT + roles/permissions + tenant isolation (Super Admin,
|
||||
Marketplace Admin, Moderator, Editor, Support, Customer).
|
||||
|
||||
## Coding rules
|
||||
|
||||
- Never hardcode marketplace data or introduce marketplace-specific conditionals.
|
||||
- Never use environment flags to drive UI — everything is config-driven.
|
||||
- Keep components small; prefer composition and reusable widgets; never
|
||||
duplicate layouts.
|
||||
- Business logic lives in services/facades, not components.
|
||||
- Prefer Signals and standalone components.
|
||||
- Every new feature ships with docs: frontend docs, backend contract, bootstrap
|
||||
updates, API examples, migration notes if needed.
|
||||
|
||||
## Guiding question
|
||||
|
||||
Before implementing anything: "Will this still make sense after 50 marketplaces
|
||||
and 100 developers?" If not, redesign before coding.
|
||||
|
||||
## Consequences
|
||||
|
||||
Any feature (including the Sprint 16 Project Editor) must edit the same Bootstrap
|
||||
model the storefront consumes — no parallel/duplicate configuration models are
|
||||
permitted anywhere in the platform.
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: ADR-0002
|
||||
title: Media Manager backend contract and mock storage adapter
|
||||
status: active
|
||||
date: 2026-07-15
|
||||
tags: ["architecture", "media", "backend-gap", "repository-pattern"]
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Sprint 4 (Media Manager) needs a media library: upload, browse, delete, and pick
|
||||
images/files for use across Product Editor, Static Pages (CMS), and Branding.
|
||||
No media backend exists yet — `/media` currently routes to a "coming soon"
|
||||
placeholder (`BackofficeComingSoonPageComponent`), and `docs/BACKEND.md` does
|
||||
not document any upload/storage endpoint. This mirrors the already-documented
|
||||
draft-publish-flow gap in the Project Editor (see `PE-20260713T010000Z-0003`):
|
||||
build the real contract, then implement a client-side mock adapter behind the
|
||||
same interface so the UI never needs to change when the backend ships.
|
||||
|
||||
## Decision
|
||||
|
||||
- **Domain model** `MediaAsset`: `{ id, url, thumbnailUrl?, filename, mimeType,
|
||||
size, width?, height?, altText?: Record<locale, string>, tags?: string[],
|
||||
createdAt }`. `altText` follows the platform's `translations.{lang}` rule
|
||||
(ADR-0001) — never a flat string.
|
||||
- **Repository contract** (future backend, to be implemented server-side):
|
||||
- `GET /media?page=&pageSize=&search=` → paginated `MediaAsset[]`
|
||||
- `POST /media/upload` (multipart) → `MediaAsset`
|
||||
- `DELETE /media/:id` → 204
|
||||
- `PATCH /media/:id` (altText/tags only) → `MediaAsset`
|
||||
- **Frontend abstraction**: a `MediaRepository` interface (Repository pattern,
|
||||
per `docs/context/features/*` conventions) with two implementations selected
|
||||
via DI token:
|
||||
- `MockMediaRepository` — stores assets in IndexedDB (not localStorage: binary
|
||||
blobs need it) as an interim store until the backend exists. Data URLs are
|
||||
generated for rendering; the shape returned matches `MediaAsset` exactly.
|
||||
- `HttpMediaRepository` — thin wrapper over the endpoints above, added when
|
||||
the backend ships. Swapping providers is the only change required.
|
||||
- **Media never enters the Bootstrap model.** Like products/orders/users, media
|
||||
assets are runtime admin data, not tenant configuration — consistent with
|
||||
ADR-0001's rule that Bootstrap contains only what's needed before the app
|
||||
starts.
|
||||
- **Media Picker** is a standalone, reusable dialog (built on the existing
|
||||
`app-dialog` Design System primitive) so Product Editor and CMS editors
|
||||
consume the same selection UI instead of each building their own.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Any feature needing to reference an image (product gallery, static page
|
||||
hero, branding logo) does so via `MediaAsset.url`/`id`, obtained through the
|
||||
shared Media Picker — never a raw file input duplicated per feature.
|
||||
- When the backend ships, only `MediaRepository`'s DI provider changes; no
|
||||
component or facade code should need to change.
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
id: ADR-0002
|
||||
title: Project Editor field-schema registry, centralized validation, and metadata-augmented form engine
|
||||
status: active
|
||||
date: 2026-07-16
|
||||
tags: ["project-editor", "schema", "validation", "undo-redo"]
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The Project Editor (`src/app/features/project-editor/`) edits the tenant `BootstrapConfig` across 11 hand-authored section templates, all built on the `shared/ui` field primitives (ADR established post-Sprint 30 redesign; see `docs/EDITOR.md`). Field labels/hints/defaults lived inline per template, `ProjectValidator` issues were not addressable to a field, there was no undo/redo, and no per-field modified/error state. Sprint X+1 ("Configuration Engine & Dynamic Form Foundation") required: a field-schema registry, centralized validation (JSON/CSS/URL/color/locale/duplicate-route/widget-config), live inline validation with publish-gating, pre-publish preview, dirty/modified-field tracking with a leave-warning, and session undo/redo — without duplicating form logic or validators, and without breaking draft/publish/import/export.
|
||||
|
||||
## Decision
|
||||
|
||||
**Metadata-augmented, not fully schema-driven.** A field-schema registry (`schema/field-schema.model.ts`, `schema/editor-schema.ts`, `schema/editor-schema.service.ts`) declares every editable field (dot-path key, section, type, label/hint keys, default, required, validator refs) as the single source of truth for field identity and validator wiring — but section templates stay hand-authored. The schema drives validation and metadata; it does not render fields. This was chosen over a fully schema-driven renderer because 11 mature templates already exist on top of the `shared/ui` kit, and a renderer rewrite carried materially higher regression risk against "preserve all existing functionality" for no UX gain.
|
||||
|
||||
**Validators are pure, composed, and tagged.** `schema/validators/primitives.ts` holds one pure function per concern (hex color, HTTP URL, email, JSON, CSS brace-balance, style-block extraction, route normalization). `ProjectValidator` composes them and attaches `section`, `fieldKey`, and `severity` (`error` | `warning`) to every issue, so the same validator is never re-implemented per field or per section.
|
||||
|
||||
**Severity splits blocking from advisory.** `publish()` now gates on `hasBlockingIssues()` (`severity === 'error'`) instead of "any issue exists." All 9 pre-existing checks stayed `error` (no behavior change); the new duplicate-routes and invalid-CSS checks are `warning` — informative, non-blocking, by design.
|
||||
|
||||
**Undo/redo is a pure reducer wrapped in debounced facade state.** `schema/history.util.ts` is a framework-free `{past, future}` snapshot reducer (commit/undo/redo, depth-capped). The facade debounces commits (~300ms) so a typing burst collapses into one undo step, and routes undo/redo through the same `localStorage` draft-save path as every other mutation so the autosave never desyncs from the undo stack.
|
||||
|
||||
**Modified-field tracking is a schema diff, not a form-state library.** `modifiedFields` walks every schema field and compares current vs. `originalBootstrap` by dot-path — no new dependency, reuses `EditorSchemaService.getByPath`.
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
- One registry answers "what fields exist, what validates them, what do they mean" — new fields register once and get validation + inline-error wiring for free.
|
||||
- No validator is duplicated: JSON/CSS/color/URL/email logic lives in exactly one place each.
|
||||
- Zero changes to `ProjectEditorIoService`, `ProjectEditorDraftStorageService`, or the draft/publish/reset flow — full backward compatibility.
|
||||
- Undo/redo and modified-field tracking added without a state-management library.
|
||||
|
||||
Negative / accepted debt:
|
||||
- Inline `[error]` binding is wired on a subset of fields (theme palette, general name/domain, branding logo) — not yet every schema-backed field across all 11 sections. Section-level visibility (nav badges, save-bar issue list) covers the rest today.
|
||||
- The field-schema registry is not yet consumed by templates for label/hint rendering (still inline i18n keys in each template) — only for validation, diffing, and change-summary labels. A future pass could fully drive labels from the schema.
|
||||
- CSS/JSON validators have a thin binding surface today (CSS only via static-page `<style>` blocks; JSON only via import) since no dedicated `customCss`/raw-JSON field exists yet in `BootstrapConfig`.
|
||||
|
||||
## Compliance Requirements
|
||||
|
||||
- New editable `BootstrapConfig` fields should get a `FieldSchema` entry in `editor-schema.ts` alongside their template addition.
|
||||
- New validation rules must be added as a pure function in `schema/validators/primitives.ts` and composed into `ProjectValidator` — never inlined ad hoc in a section component.
|
||||
- `severity: 'error'` is reserved for checks that must block Publish; anything advisory is `'warning'`.
|
||||
Reference in New Issue
Block a user