diff --git a/.gitignore b/.gitignore index 6acf860..4f4559f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Compiled output /dist +packages/*/dist /tmp /out-tsc /bazel-out diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..2b779d8 --- /dev/null +++ b/.npmrc @@ -0,0 +1,10 @@ +# @marketplaces/* packages are published to a private Verdaccio registry on +# the dev server (213.21.246.138:4873), not npmjs. The port isn't open in the +# server firewall (only 80/443/SSH are) - reach it via an SSH tunnel: +# ssh -L 4873:127.0.0.1:4873 seto@213.21.246.138 +# See docs/PACKAGE-EXTRACTION.md for the full setup and the follow-up needed +# to expose this properly for CI (reverse proxy + TLS, or open the port). +@marketplaces:registry=http://127.0.0.1:4873/ +# Auth token is NOT committed here. Set it locally (npm login --registry=http://127.0.0.1:4873/ +# --scope=@marketplaces, or a user-level ~/.npmrc _authToken line) or via CI secret. + diff --git a/BACKEND-API-REFERENCE.md b/BACKEND-API-REFERENCE.md index 4d6e074..7bfda30 100644 --- a/BACKEND-API-REFERENCE.md +++ b/BACKEND-API-REFERENCE.md @@ -2,6 +2,8 @@ One document, everyone reads it: product, backend, frontend, QA. It answers three questions for every domain — **what does the frontend already call**, **what shape does it send/expect**, and **is it real or mocked today**. Generated from the actual Angular frontend source (this repo has no backend code — it is a pure client consuming an external API), cross-checked against the frontend's own tolerant adapters, not aspirational. +**For what doesn't exist yet:** this doc describes the live surface only. The full set of forward-looking wire contracts for Product Plan v3.1 (money/FX, orders, catalog/offer split, connectors, seller portal, identity, tenant registry, RBAC, analytics — 10 phases + 2 tracks) lives in [docs/backend/](docs/backend/README.md). + Maturity tags used throughout: | Tag | Meaning | @@ -243,7 +245,7 @@ No cursor/keyset pagination exists anywhere. No server-side page-size cap is enf ## 5. Error model -**The frontend does not currently parse any backend error envelope for any real endpoint** — no interceptor inspects error responses; every caller reacts at the raw `HttpErrorResponse.status`/`.message` level. The one partial exception (Ed25519 admin auth) derives its error code from **HTTP status only**, ignoring any body field, which is itself a known bug (see below). Everything in this section is therefore a **recommended envelope to adopt going forward**, not something already wired end-to-end — apply it to new endpoints and treat the frontend gaps below as follow-up work, not something this doc can silently paper over. +**The frontend does not currently parse any backend error envelope for any real endpoint** — no interceptor inspects error responses; every caller reacts at the raw `HttpErrorResponse.status`/`.message` level. The one partial exception (Ed25519 admin auth) now reads `error.error.code` from the body when present (`authErrorCodeFromBackendCode()`), falling back to HTTP status only when no body code is sent. Everything in this section is therefore a **recommended envelope to adopt going forward**, not something already wired end-to-end — apply it to new endpoints and treat the frontend gaps below as follow-up work, not something this doc can silently paper over. ### The envelope @@ -281,8 +283,8 @@ No cursor/keyset pagination exists anywhere. No server-side page-size cap is enf | 503 (infra down) | `SERVICE_UNAVAILABLE` | Same "backend unavailable, retry" screen as 500, on the Ed25519 flow only. | | 503 (maintenance) | `MAINTENANCE_MODE` (+`maintenanceUntil`) | **No maintenance-mode concept exists in the frontend at all today.** Same HTTP status as infra-down 503 — `error.code` is the only way to distinguish them. | | 403 (tenant disabled) | `TENANT_DISABLED` | **No handling exists.** No code path today distinguishes "tenant exists but is disabled" from any other 403. | -| 401 (token expired) | `TOKEN_EXPIRED` | **Known bug, not just a gap:** the client has a dedicated "Session expired" screen wired and ready, but `toAuthErrorShape()` only reaches it via a no-refresh-token-present client-side branch — a *real* backend 401 on `/refresh` always renders the generic "Unauthorized" screen instead, because the mapping function ignores any body code and derives purely from HTTP status. Fix requires the backend to send `error.code: "TOKEN_EXPIRED"` **and** a small frontend change to prefer it. | -| 401 (bad signature) | `INVALID_SIGNATURE` | Same bug class as above — dedicated screen exists, unreachable from a real HTTP response for the identical reason. | +| 401 (token expired) | `TOKEN_EXPIRED` | **Fixed** — `toAuthErrorShape()` (`core/auth/services/auth.service.ts`) now reads `error.error.code` via `authErrorCodeFromBackendCode()` before falling back to HTTP status. A backend 401 on `/refresh` sending `error.code: "TOKEN_EXPIRED"` reaches the dedicated "Session expired" screen. | +| 401 (bad signature) | `INVALID_SIGNATURE` | **Fixed**, same mechanism — reaches the dedicated screen when the backend sends `error.code: "INVALID_SIGNATURE"`. | **Every admin backoffice list page** (Users/Orders/Monitoring/Moderation/Transactions/Products/Categories/Analytics/Customers/Dashboard) shares one generic pattern: a boolean `error` signal → "Something went wrong" + retry button. None of them branch on status or `code` today — every status above collapses into the same generic UI until facades are individually updated. @@ -371,7 +373,7 @@ WebSessionID: 3f1c2a0e-… { "qrId": "QR-77f0", "nspkurl": "https://qr.nspk.ru/AD10…", "status": "created", "qrExpirationDate": "2026-07-26T04:10:00Z" } ``` -**Payments are frozen** — this call chain is explicitly out of scope for changes; document only, don't modify. +**Payments were frozen; unfrozen 2026-08-17** (Sprint 0.1 decision, see `docs/PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md`). This call chain is now in scope for the Phase 1 rework specified in `docs/backend/PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md` — the server-authoritative-amount contract there replaces the client-trusted `amount`/`price` fields described below. --- diff --git a/GAPS-AND-IMPROVEMENTS.md b/GAPS-AND-IMPROVEMENTS.md index 259b89e..be9edb8 100644 --- a/GAPS-AND-IMPROVEMENTS.md +++ b/GAPS-AND-IMPROVEMENTS.md @@ -6,16 +6,16 @@ Findings only — nothing in this document has been fixed as part of writing it. ## As a Customer / End User -1. **Ed25519 admin-auth "session expired" and "invalid signature" recovery screens are dead UI.** Both are fully built and wired, but `toAuthErrorShape()` (`core/auth/services/auth.service.ts:110-118`) derives the error code from HTTP status only, never a body-level code — so a real backend 401 always shows the generic "Unauthorized" screen instead. Also an [Engineering](#as-backend--api-engineer) and [Backend](#as-backend--api-engineer) item. -2. **Dark mode selector does nothing.** The light/dark/system dropdown saves correctly, but no CSS anywhere reads the `data-theme-mode` attribute it sets — picking anything but Light changes nothing visually. -3. **"Site Layout" selector (Theme section) has no effect.** `layout.type` is edited but page rendering only ever reads each page's own `layout`, never the top-level selector. -4. **Footer "Contacts" link has nothing behind it.** No static-page content exists for it at all in the bootstrap data (unlike other footer legal pages, which are populated). -5. **Product pages get no per-product SEO.** `SeoService.setItemMeta(item)` — the method that would set per-product Open Graph/canonical tags — exists but is **never called anywhere in the codebase**. Every product page ships only the site-wide default meta tags. -6. **`og:locale` is hardcoded to `'ru_RU'`** in both SEO meta-tag code paths, regardless of the active locale — a real gap for EN/HY visitors' social-share previews. -7. **No structured data (JSON-LD) and no sitemap generation exist anywhere** — confirmed absent, not partially built. Sitemap is backend-only work; JSON-LD would need net-new frontend code. -8. **Checkout's payment-description fallback is a hardcoded Russian string** (`'Покупка на Маркетплейсе'`) used as a last resort when no brand name or hostname is available — single-tenant-framed wording in a multi-tenant product. -9. **Brand color contrast fails WCAG AA.** `--border-color` measures 1.24–1.42:1 against a 3:1 UI-component requirement in every theme; `--success`/`--warning`/`--error`/`--info-color` fail 4.5:1 when used as plain text. Real palette colors, not a token bug — see [Accessibility](#as-accessibility-reviewer). -10. **`stars.component.scss:10` uses a literal hex color** (`#cdd6d5`) with no design token behind it — any future palette change will silently miss this one glyph. +1. **FIXED (verified 2026-08-17).** ~~Ed25519 admin-auth "session expired"/"invalid signature" screens were dead UI~~ — `toAuthErrorShape()` now reads `error.error.code` via `authErrorCodeFromBackendCode()` before falling back to HTTP status. See `BACKEND-API-REFERENCE.md` §5. +2. **FIXED (2026-08-17).** ~~Dark mode selector did nothing~~ — structural dark overrides (bg/text/border/shadow) now wired for all three tenant themes under `[data-theme-mode="dark"]`. Brand colors intentionally unchanged pending a theme-owner-approved dark palette. +3. **FIXED (verified 2026-08-17).** ~~"Site Layout" selector had no effect~~ — `SectionEngineService.resolveLayoutType()` now falls back to `bootstrap.layout.type` when a page has no layout of its own. +4. **Not a code gap (re-verified 2026-08-17), a content gap.** The mechanism is already fully generic: `features/project-editor/sections/footer-section.component.ts`'s Footer Builder lets an admin create any static page via the CMS and link it into a footer column by `pageKey`, resolved by `FooterResolverService`. "Contacts" just has no authored static page yet on whichever tenant's bootstrap this was checked against — that's a per-tenant content task, not a frontend fix. +5. **FIXED (verified 2026-08-17).** ~~Product pages got no per-product SEO~~ — `SeoService.setItemMeta(item)` is called from `product-details-container.component.ts`. +6. **FIXED (verified 2026-08-17).** ~~`og:locale` was hardcoded to `ru_RU`~~ — reads `languageService.currentLanguage()` via `OG_LOCALE_MAP` at both call sites. +7. **FIXED (verified 2026-08-17) on JSON-LD; sitemap remains backend-only work.** ~~No structured data (JSON-LD) exists anywhere~~ — `SeoService.setJsonLd()` injects a real `