Principal-architect-level review of the entire Seller Management body
of work (7 prior docs + all touched code), verified against fresh
tsc --noEmit and arch:check runs, not recalled from memory.
Real bug found and fixed (in scope per this mission's "unless
absolutely required" carve-out - a one-line correctness fix to
already-committed code, not new feature work):
AdminSellerManagementPageComponent.sellerManagementEnabled read the
bootstrap snapshot once via a plain signal() at construction, not
reactively via bootstrapRevision() the way UiRuntimeFacade/SeoService
both correctly do elsewhere in this codebase. Fixed to computed() keyed
on bootstrapRevision(). Currently invisible (flag is always false,
signal was never even read in the template) but would have gone stale
the moment bootstrap ever reloaded with the flag true. tsc clean after
the fix.
Findings documented in Seller-Management-Final-Design-Review.md (no
Critical/High severity found anywhere):
- Medium: SellerConfig (bootstrap wire shape) and Seller/SellerBranding
(domain entity) are two unreconciled type hierarchies for the same
concept - self-flagged already in BACKEND.md SS11.6, restated here as
an independently-confirmed finding rather than letting it drift.
- Medium: no reusable capability-guard abstraction exists anywhere in
the codebase, despite ADR-009/ADR-011 both prescribing "check the
flag in one place" - ADR-009's own described FeatureFlagService was
never built. Fine with one consumer, a real drift risk the moment a
second one needs the same check.
- Medium: the flag's true branch has never been exercised, even
manually - every verification claim in this whole body of work was
tested at the flag's real value (false).
- Low/nice-to-have: sellerId typed as bare string instead of the UUID
alias used everywhere else in the new sellers domain; MarketplaceRef
vs TenantConfig overlap (deliberate, documented, but worth watching);
documentation-to-code ratio (8 docs, zero backend bytes) carries a
consolidation-burden risk, especially the Unified/Split-Orders
question restated independently in 4 different docs.
- Explicitly checked for and did NOT find: circular dependencies,
scattered tenant/seller conditionals, over-engineering relative to
the typed-models-only mandate, or any auth/payment code touched.
Verdict: not an unqualified "ready for implementation" - two Medium
findings should be resolved by decision/small build before real
backend work starts, not because they block anything today but
because both compound in cost the longer they're left unresolved.
Everything actually built (typed foundation, disabled-by-default flag,
Phase 1 UI, plus the bug this review fixed) is solid and ready to
stay exactly as-is. No Critical or High-severity issue found anywhere.
Reviewed the Phase 1 UI against every other Backoffice page. Found and
fixed 2 real issues; everything else verified already consistent
(built entirely from shared components, so hover/focus/dialog-a11y/
dark-readiness/contrast come from those components, not reinvented).
Fixed:
- Message textarea had no id/aria-describedby wiring (app-input
self-wires this via injected FormFieldContext; the raw textarea -
no dedicated textarea component exists yet - never got it, so the
visible label's `for` pointed nowhere). Added explicit aria-label
bound to the same translation key as the visible label.
- Learn More dialog's feature list would render native browser
bullets (no global list-style reset exists outside details>summary
in styles.scss). Replaced with checkCircle icon + text rows,
consistent with how the rest of the app pairs icons with list/status
meaning.
Added docs/architecture/foundation/Seller-Management-UX-Review.md
documenting both fixes plus everything checked and confirmed already
consistent (empty-state usage, icon reuse, translations completeness
across en/ru/hy, responsive at 1280px/375px, dialog a11y verified via
accessibility tree not assumed).
tsc --noEmit clean, arch:check (boundaries + cycles) clean. Live-
verified: Learn More dialog shows all 6 items each with an icon
(confirmed via DOM query), textarea aria-label confirmed
"Сообщение", no console errors.
Typed models only, per mission. Nothing outside the new files reads
or writes any of this yet.
New core/sellers/models/ (mirrors core/products/models,
core/auth/models convention):
- MarketplaceRef - minimal {id,slug,name} reference from a seller
back to its marketplace, distinct from bootstrap's TenantConfig.
- SellerStatus - 'pending'|'active'|'suspended'|'disabled', no
transition logic.
- SellerScope - {sellerId, marketplaceId}, domain-level counterpart
to BootstrapConfig.seller (SellerConfig from the ADR-011 pass).
- SellerBranding (+SellerContact/SellerAddress/SellerThemeOverrides)
- logo/banner/description/contacts/address/theme overrides, every
field optional. Marketplace branding/theme remain default;
nothing consumes this yet.
- SellerPermissionRole/SellerPermissions - marketplaceOwner/seller/
sellerStaff/platformAdmin. Separate vocabulary from the existing
AdminRole (core/auth/models/permission.model.ts) - not merged, not
wired into any guard, zero auth behavior change.
- Seller - the eventual entity, composed from the above.
Changed (optional-only, verified backward compatible):
- Item (models/item.model.ts) gained sellerId?: string
- AdminProduct (features/admin/products/models/) gained
sellerId?: string
- AdminOrder (features/admin/orders/models/) gained sellerId?: string
Absent means marketplace-owned in every case, exactly like every
existing product/order today. No consumer of any of these three
models needed updating. AdminOrderItem (per-line-item ownership) and
the existing PermissionsConfig/AdminRole system were deliberately not
touched - out of scope for this pass.
Added docs/architecture/foundation/Seller-Management-Domain-Models.md
documenting every new type, every changed field, and the explicit
non-goals list. Linked from the foundation README alongside ADR-011
and the diagrams doc.
tsc --noEmit clean, arch:check (boundaries + cycles) clean.
No backend, no CRUD, no API, no business logic - production-quality
UI only, built entirely from existing shared components (app-dialog,
app-empty-state, app-button, app-form-field, app-input, app-icon,
app-badge). Gated per ADR-011: reads
modules.sellerManagement.enabled from bootstrap (always false today,
no backend sets it) rather than hardcoding disabled state.
New:
- AdminSellerManagementPageComponent (features/admin/seller-management/
pages/) - renders the specified empty state (title/description/
Request Access + Learn More buttons) using existing shared/ui
primitives only, no new UI infrastructure.
- Request Access dialog: Company/Email/Message form via
app-form-field + app-input + a plain textarea (no dedicated
textarea component exists yet, styled to match app-input's own
tokens exactly). Submission is mocked (setTimeout), no API call.
On submit: closes and opens a success dialog ("Thank you...").
- Learn More dialog: 6 capability bullets (seller dashboards,
storefronts, permissions, analytics, product ownership, marketplace
administration) under a "Coming Soon" badge.
- New admin nav group "Partners" > "Seller Management" link
(admin-nav.model.ts), new route /backoffice/partners/seller-management
(app.routes.ts), using the same loadComponent/breadcrumb pattern as
every other admin route.
Translations: full en/ru/hy coverage, zero hardcoded strings - new
adminShell.nav.{partnersGroup,sellerManagement},
adminShell.pages.sellerManagement, and a new adminSellerManagement.*
namespace (emptyState/requestDialog/requestSuccessDialog/
learnMoreDialog) added to translations.ts (types) and all three
locale files.
Accessibility: inherited from app-dialog (role="dialog",
aria-modal, focus trap on Tab/Shift+Tab, Escape to close, focus
restored to trigger on close) - no new a11y code needed, reused as-is.
Responsive: existing --space-*/--font-size-* tokens throughout,
flex-wrap on button row, mobile breakpoint stacks actions full-width.
Verified live (ru locale, devBypassAdmin): nav group/link render
correctly, breadcrumb shows "Управление продавцами", empty state
copy matches spec exactly, Request Access dialog opens with all 3
fields + Cancel/Send Request, filled + submitted -> success dialog
with exact spec copy, Learn More dialog shows all 6 bullets + Coming
Soon badge, no console errors, verified again at 375px mobile
viewport. tsc --noEmit clean, ng build clean (pre-existing bundle-
budget warning only), arch:check (boundaries + cycles) clean.
Analytics, Reports, and Diagnostics were already clean (no raw
HTTP/queue-worker strings found on audit). Monitoring had three spots
speaking developer language by default:
- Background queue names ("order-notifications") -> friendly labels
("Order notifications").
- Webhook event keys ("order.created") -> friendly labels ("New order
placed").
- Activity log's "api" category showed the raw HTTP line
("GET /api/products responded 200 in 84ms") as the primary message.
Now shows a plain-language summary by default ("Product data
refreshed successfully"), with the raw string moved to a collapsed
"Technical details" <details> per event (api/error/warning rows).
Phase 8 (RC-01): these 4 list/dashboard pages had no error-state handling
on their primary data-load subscriptions — on a gateway error, `loading`
was either never reset (Users, Monitoring, Analytics: genuine infinite-
spinner risk, nested subscribe chain in Analytics never resolved on
failure) or there was no loading/empty/error handling at all (Reports
queue: raw table with zero skeleton or fallback).
- admin-users.facade.ts, admin-monitoring.facade.ts: add `error` signal,
error callback on the primary load subscribe so `loading` always
resolves.
- admin-analytics.facade.ts: add `error` signal; every level of the
4-deep nested gateway subscribe chain (orders -> products ->
categories -> reviews) now has an error handler that resolves loading
instead of leaving it stuck true.
- admin-moderation.facade.ts: add `reportsLoading`/`reportsError` signals
(reports list had none previously).
- Templates: reuse existing `app-skeleton`/`app-empty-state`/`app-button`
primitives for the new error branch, `common.retry` label, two new
generic `common.errorTitle`/`common.errorDescription` i18n keys added
to en/ru/hy (reused across all 4 fixes instead of one-off per-page
copy).
Verified: tsc --noEmit clean, `npm run build` green (pre-existing bundle-
budget warning only, unrelated). Live-checked Home (375px) and Backoffice
Products (1024px) — no console errors, tables/cards render without
overflow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Replaced hardcoded English audit/timeline text (order status changes,
review moderation events, user role/status changes) with proper
adminXxx.timelineEvent.*/adminUsers.audit.* i18n keys, so Recent
Activity/Timeline/Audit panels no longer mix English into ru/hy UI.
- Translated raw internal codes rendered directly to users: transaction
payment method ('card'/'qr'/'cash_on_delivery' -> adminTransactions.methodValue.*)
and user roles/permissions ('products.manage' etc -> adminUsers.roleValue.*/
adminUsers.permission.*), replacing developer-facing enum leakage with
real copy.
- Fixed wrong-noun list-footer counts: Orders/Transactions/Moderation
list pages all reused adminProducts.items ("N товаров"/"N products")
regardless of what was actually listed; each now has its own itemsCount
key ("N заказов", "N транзакций", "N отзывов").
- Fixed customer detail page's "Back" button reusing adminOrders.back
("Back to orders") instead of a customers-specific label.
- Added translation keys to en/ru/hy + translations.ts interface for all
of the above.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RC-01 Phase 1 mechanical fixes (verified against current repo state,
not blindly reapplied from TODO.md):
- admin/products create/edit/duplicate now protected by an unsaved-
changes guard (adminProductDirtyGuard), mirroring the existing
categories pattern. AdminProductsFacade had zero dirty-tracking
before this - added a dirty signal, set true on updateDraft(),
cleared on load/create/successful save. Added confirmLeaveUnsaved
to the adminProducts i18n section (en/ru/hy) - categories already
had its own copy of this key, products didn't.
- barry-cache bumped ^0.1.0 -> ^0.9.3 (the pinned range no longer
resolved on the registry - ETARGET - which had been silently
blocking every npm install/uninstall all cycle).
- Removed primeng/primeicons (npm uninstall, now unblocked) - the
only consumer (items-carousel) was already deleted in RC PERF-01.
- Removed core/search/services/search-history.service.ts, a dead
1-line re-export with zero importers (verified: the real
implementation is features/search/services/search-history.service.ts,
used by search.facade.ts). Left core/search/models/* alone - those
ARE live, imported by catalog components.
Verified before touching: HeaderConfig.showProfile toggle is already
removed from the header-section editor template (TODO.md was stale on
this one) - no change needed, will correct the tracking doc separately.
tsc --noEmit clean, npm run build green (bundle unchanged, primeng
was already tree-shaken out, this just removes the dead dependency
declaration itself).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Admin Categories CRUD (create/edit/delete/reorder) silently failed
end-to-end in local dev: ADMIN_CATEGORIES_GATEWAY resolved
strategy.getBackofficeProviderMode(), which (unlike
getBootstrapProviderMode()) has no localhost fallback, so it always
picked AdminCategoriesApiGateway (real HTTP, 404s here) over the
purpose-built AdminCategoriesLocalGateway mock. saveDraft()'s
subscribe() has no error branch, so a create/publish click gave zero
feedback: the category never saved, dirty stayed true forever, and
the unsaved-changes guard then blocked navigation with no
explanation. Live-verified end-to-end: created 3 categories, edited,
reordered via the keyboard move-up/move-down buttons - all persist
correctly now. Fixed by wiring the token to the category-specific
strategy.getCategoryProviderMode() (was already defined, just never
called) and giving it the same isLocalhost() mock fallback
getBootstrapProviderMode() already uses. Production behavior
(non-localhost) is unchanged - still resolves to the real API
gateway.
- Categories list (tree/table/grid views) mislabeled its Edit button
'Edit product' (adminProducts.edit) instead of 'Edit category' -
copy-pasted the wrong existing i18n key; adminCategories.edit
already exists with the correct translation in en/ru/hy. Not part
of the tracked ~178-key missing-translation gap (docs/KNOWN-ISSUES.md) -
this key exists and is simply wrong, not missing.
Verified live via browser walkthrough of every Backoffice route
(dashboard, products list/create/edit, categories list/create/edit/
reorder, orders list/detail, transactions list/detail+audit dialog,
customers list/detail, moderation list+reports queue, users, monitoring,
analytics, media library) at desktop and mobile widths. Console/network
noise from the mock backoffice API 404ing locally is pre-existing and
already documented (docs/ADMIN.md's prior bug-hunt audit pass) - not
re-reported. Product create/edit CRUD already worked end-to-end
(AdminProductsFacade injects its local gateway unconditionally, no
swappable-provider mistake there).
npx tsc --noEmit and npm run build both green (only the pre-existing
700kB initial-bundle budget warning, already tracked as out of scope).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RC A11Y-01 audit pass, Backoffice (admin/*) area. Builds on 712a7b4/63c9cee's
composition pass (scope="col", skeletons, empty-states) without redoing that
work.
- Sidebar nav landmark: admin-layout's <nav aria-label> reused the
"Dashboard" nav-item translation key as its landmark label, misleadingly
announcing the whole sidebar as "Dashboard" - added a dedicated
adminShell.sidebarLabel key ("Admin sidebar navigation") in en/ru/hy.
Skip link, #admin-content main landmark, Escape handling, and mobile-drawer
focus management were already correct - verified, not touched.
- Categories tree drag-and-drop keyboard fallback (WCAG 2.1.1): the category
tree's native HTML5 DnD (draggable/dragstart/drop) reorders siblings with
no keyboard equivalent - existing arrow-key tree navigation only expands/
collapses/selects, never reorders. Added per-row move-up/move-down icon
buttons (disabled at sibling boundaries), reusing the existing `reorder`
output so the facade's reorder logic is untouched; new
adminCategories.moveUp/moveDown keys in en/ru/hy.
- Screen-reader loading announcements: skeleton-row loading states across
Products, Categories, Customers, Orders, Transactions, Users, Reviews,
Monitoring (webhooks/events), and Analytics (summary cards + top products)
were purely visual (app-skeleton is aria-hidden by design) with no
accessible "loading" text, unlike the storefront/product-details pattern -
added role="status"/aria-live="polite"/aria-busy + sr-only text using the
existing common.loading key.
- Table row headers: added scope="row" to the primary identifying cell
(product/category/customer name, order number, transaction order number,
user name, review customer, report target, top-products/low-stock product
name, webhook endpoint) on 9 tables that only had scope="col". Added
matching `tbody th[scope='row'] { font-weight/color/text-align/
vertical-align }` + last-row border resets in each component's own scss so
the semantic change doesn't alter visuals (the shared app-table stylesheet
styles all <th> as bold/muted by default).
Verified via `git show --stat` of fb1afb7/a03260e and `docs/UI-COMPOSITION-
REVIEW.md`'s Backoffice sections first, per instructions - confirmed
scope="col" coverage already complete, all admin modals already route
through the shared app-dialog (focus-trap/Escape/return-focus already
correct, nothing to fix), and the bare-<select> filters still carry
aria-label per the accepted Sprint 28 decision (not re-migrated to
app-select).
Flagged, not fixed:
- No toast/notification system exists anywhere in this codebase (product/
category save and delete call the gateway with no success/error UI at
all, not even a subscribe error handler) - there is nothing to wire
aria-live onto without adding a new UI mechanism, which is out of scope
for an a11y-only pass. A prerequisite feature-level fix, not an a11y
regression.
- Dashboard's per-card metric/status-row/timeline skeletons (dashboard-
metric, dashboard-status-row, dashboard-timeline) were left without
aria-live wiring - wrapping each of the ~10 simultaneous mini-widgets in
its own live region would fire a burst of redundant announcements; needs
a single page-level "loading dashboard" region instead, a larger change
than this surgical pass.
- Monitoring's events table and the notifications dropdown (role="menu"
with a static empty-state message, aria-haspopup="true") were left as-is -
matches the same partial-widget-pattern precedent already accepted for
locale-tabs/product-tabs in the storefront and builder passes.
- Analytics `lowStockProducts` table's missing loading-skeleton branch
(already flagged, not fixed, in the RC-Visual-02 pass) - untouched again
here for the same reason.
Verified: npx tsc --noEmit clean; npm run build green (only the pre-existing
768.57 kB vs 700 kB initial-bundle budget warning, unrelated to this pass).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Transactions list: hardcoded #fff background replaced with var(--bg-primary); missing th scope=col added on all 7 headers
- Customers list: hardcoded #fff background replaced with var(--bg-primary); missing th scope=col added on all 7 headers
- Customer detail: fake CSS vars --border-subtle/--brand-primary (never defined, silently falling back to hex) replaced with real --border-color/--primary-color
- Reviews list: hardcoded #fff card background and sticky-header background replaced with var(--bg-primary)/var(--bg-secondary); missing th scope=col added across dynamic column table header
- Reports list: hardcoded #fff background replaced with var(--bg-primary); fake --brand-primary var replaced with --primary-color; missing th scope=col added
- Review health widget: fake --surface-muted/--brand-primary vars replaced with real --bg-tertiary/--primary-color (matches product-health-widget precedent)
- Users page: hardcoded #fff background replaced with var(--bg-primary); missing th scope=col added on both tables
- Monitoring page: hardcoded #fff background replaced with var(--bg-primary); missing th scope=col added on webhooks and events tables
- Analytics page: fake --color-primary var (undefined anywhere in codebase) replaced with real --primary-color across tabs/chart/focus rings; fake --surface-muted/--brand-primary on health bar replaced with --bg-tertiary/--primary-color; hardcoded #fff card/summary-card backgrounds replaced with var(--bg-primary); missing th scope=col added across 4 tables
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Fix var(--x, #hex) references to nonexistent theme variables (--brand-primary, --color-primary, --border-subtle, --surface-muted, --text-muted, --text-tertiary, --danger-color) across admin products/categories/orders forms, lists, health widgets, variants editor, timeline, and dashboard stat cards. These silently fell back to hardcoded hex and never responded to theming; remapped to the real tokens (--primary-color, --border-color, --bg-tertiary, --text-secondary, --text-light, --error-color, --bg-primary).
- Add scope="col" to table headers in admin-products-list, admin-categories-list, admin-orders-list for proper header/data-cell association.
Mechanical, value-preserving: font-weight: 400/500/600/700 -> var(--font-weight-normal/medium/semibold/bold, <same value>) across src/app.
Note: cart.component.scss now sits ~771 bytes over its per-file budget
in angular.json due to longer var() strings; non-fatal build warning,
noted in docs/UI-DESIGN-REVIEW.md as a follow-up (either bump the
component style budget slightly or accept the warning).
Mechanical sweep for border-radius: 4px/8px/12px/13px/999px replaced
with var(--radius-xs/sm/md/lg/full, <same value>) across src/app.
Only exact matches to existing token values were touched (20px, 16px,
10px, 6px, 3px, 2px etc. were left as-is since no token maps to them
without a visible size change on at least one tenant theme — see
docs/UI-DESIGN-REVIEW.md).
Mechanical, value-preserving substitution: every literal font-size
declaration across src/app (89 files) that matched one of the 9
typography scale steps introduced earlier (--font-size-xs..4xl) was
replaced with var(--font-size-STEP, <same-or-nearest-step-value>).
Values within ~0.03rem/1px of a scale step were snapped to that step
(e.g. 0.85rem and 0.8rem both -> --font-size-sm/0.8125rem; 0.9rem and
0.875rem -> --font-size-base/0.875rem) to consolidate roughly 15
near-duplicate sizes down to the 9-step scale, per the RC design-system
finalization brief. This eliminates most of the font-size fragmentation
found across the app (previously: 0.7/0.72/0.75/0.78/0.8/0.8125/0.85/
0.875/0.9/0.9375/0.95/1/1.05/1.1/1.125/1.15/1.2/1.25/1.3/1.35/1.4/1.5/
1.75/2rem all in live use simultaneously).
Not touched (deliberately, see docs/UI-DESIGN-REVIEW.md): 3rem+ display
sizes (too large a jump to any existing step, would need a --font-size-5xl
addition), font-size values expressed via clamp()/calc(), and any
component listed as intentionally distinct (code-editor syntax tokens,
theme brand colors).
Admin sidebar nav, topbar icon buttons (menu, search, quick-publish,
tenant selector, notifications), and the product-form translations
disclosure icon now render via app-icon instead of PrimeIcons classes.
AdminNavLink/AdminNavAction.icon retyped to AppIconName.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace every PrimeIcons pi-* class (static and data-driven) in the
admin dashboard area with app-icon: dashboard-card, dashboard-shortcut-
card, dashboard-status-row, dashboard-timeline, and the icon data in
admin-dashboard.facade.ts / admin-dashboard-page.component.ts. Icon
fields on AdminDashboardQuickAction/Shortcut/DashboardTimelineEntry
are now typed AppIconName instead of string, so a typo or unmapped
icon name is a compile error instead of a silently blank icon.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same select-all/row-checkbox accessible-name gap in table view; grid
view already had aria-label on its row checkbox (customer name),
table view was the gap. New adminModeration.selectAllRows key added
across en/ru/hy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same gap as products/categories: table select-all and per-row select
checkboxes had no accessible name. Added aria-label (row label
includes order number for context). New adminOrders.selectAllRows/
selectRow keys across en/ru/hy.
Order detail page and order-timeline component reviewed — no
interactive checkboxes or focus-visible gaps found there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add accessible names to select-all, per-row select, and per-row
visibility-toggle checkboxes in the table view — none had them,
including the visibility toggle whose <label> wrapped only the input
with no text content (empty accessible name). Tree/grid views already
had aria-label on their row checkboxes; table view was the gap.
New adminCategories.selectAllRows/selectRow/toggleVisibility keys
added across en/ru/hy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add accessible names to the table-view select-all and per-row
checkboxes — they had none, while the grid-view equivalent already
did (product name via aria-label). New adminProducts.selectAllRows/
selectRow keys added across en/ru/hy.
Reviewed toolbar, filters, bulk actions, empty/loading states, and
grid view: all buttons already route through app-button (own
:focus-visible), empty state already uses shared EmptyStateComponent.
No orphaned bindings or corrupted glyphs found.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: variants were just free-text name+price, and the user shared the real production payload - a flat array of {color, size, price, currency, remaining} rows, colors as '0x8B4513' hex.
- New model: AdminProductVariantAttributeDef (key/label/isColor/values) + AdminProductVariant (attributes: Record<string,string>, sku, image, remaining, prices: {currency,price}[]) - this IS the production shape, grouped by combo with one row per currency instead of flattened, so admins edit one variant card instead of 4 duplicate rows
- Attribute manager: add custom attributes (Color, Size, or anything), color attributes get a native color picker + live swatch preview instead of typing hex; other attributes get plain value chips
- 'Generate variants' computes the cartesian product of attribute values (Color x Size = 4 combos for 1 color x 4 sizes) and preserves existing sku/price/stock data for combos that still exist after regeneration
- Multi-currency pricing per variant (matches prod: same combo priced in RUB/USD/EUR/AMD) with per-currency add/remove
- Color hex kept in the exact '0x8B4513' production format (toBackendColor/toCssColor conversion helpers)
- Fixed an Angular v21 control-flow parser bug hit while building this: an @if/@else block whose only content is a bare {{ interpolation }} touching the block's closing brace fails to parse (NG5002 'Unclosed block for' cascading from a completely unrelated line) - worked around by keeping interpolation in its own element
- Verified end-to-end in browser: added Color (color picker) + Size (S/M/L/XL) attributes, generated 4 variant combos, added all 4 currencies to a variant - matches the shared production JSON exactly
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: badges were a comma-separated text input with no preview.
- Replaced with a chip editor: current badges render as removable pills in the exact colors customers see on the storefront (colors sourced from getBadgeClass/item.utils.ts so admin preview and product card never drift)
- The standard set (new/sale/exclusive/hot/limited/bestseller/featured) is offered as one-click add chips; custom badges added via text field, unlimited, shown in the storefront's custom-badge grey
- Deliberately NOT per-badge arbitrary colors: badge colors are part of the storefront design contract (string[] + fixed palette); introducing per-badge color storage would change the product data shape consumed by the future backend
- New adminProducts keys (badgesHint, addBadge, removeBadge, customBadgePlaceholder) in en/ru/hy; verified in browser
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: four parallel description fields (default/ru/en/hy) read as duplicates and confused the admin.
- The default-language name/short description/rich description now stand alone; per-language fields moved into a collapsed 'Translations' disclosure with a translated-count badge (e.g. 1/3)
- The disclosure explains the fallback rule in merchant language: empty translation means customers see the default text
- Translation inputs show the default value as placeholder, making the fallback visible instead of implied
- Locale codes shown uppercase (RU/EN/HY) to read as language labels, not field suffixes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback asked for PDF export on orders and transactions. Orders already had Print invoice (window.print). Rather than invent a backend PDF endpoint, the browser's print-to-PDF is the export path:
- Transactions list gains a Print button next to CSV export
- Admin shell now hides sidebar/topbar/inspector under @media print, so printing any admin page (order invoice, transactions, analytics) produces a clean document instead of capturing navigation chrome
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: category icon input and breadcrumbs ('Хлебные крошки') were unexplained, slug and SEO were manual.
- Title edits derive the slug and SEO title with the same never-overwrite-manual-input rule as products (verified in browser)
- SEO group gains the same 'Fill from product details' one-click action
- Icon field now explains itself (emoji/symbol shown in menus) and shows a live preview of the entered symbol
- Breadcrumb block explains in merchant language what customers see (Home › Electronics › Phones) and where it comes from; separator changed to › to match the storefront
- New adminCategories keys (navBreadcrumbHint, iconHint) in en/ru/hy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 user feedback: admins did not know what SKU means, had to hand-write slugs, and left SEO empty.
- Name edits now derive the URL slug automatically (supports Cyrillic/Armenian characters); the derivation stops the moment the slug no longer matches the auto value, so a manually edited slug is never overwritten - verified in browser
- SEO title mirrors the name under the same only-while-untouched rule
- SKU field explains itself (what a stock keeping unit is, that any unique text works) and gains a Generate button producing readable codes like WIR-GAM-7K2P from the product name
- SEO tab gains 'Fill from product details': fills only empty title/description/keywords from name and short description, existing text untouched
- New adminProducts keys (slugHint, skuHint, generate, seoGenerate, seoGenerateHint) in en/ru/hy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fix AdminLayoutComponent.readRouteData crash: leaf route snapshot/data is now optional with dashboard-title fallback, so admin deep links never crash the shell when route metadata is missing (root cause of every blocked browser test since Sprint 12)
- Never-settling promises fixed: all gateway ensureData() bridges (products, categories, moderation) and the catalog category resolver now resolve with an empty list on transport failure instead of hanging forever, so API outages surface as empty states with guidance rather than permanent skeletons plus global console errors
- Request de-duplication: BackofficeDataService caches products/categories with shareReplay - one in-flight request per endpoint shared by all consuming gateways (was 5+ duplicate requests per admin page load); failures clear the cache so the next call retries
- Gateway contract audit: all 8 admin gateways (products, categories, orders, customers/moderation, transactions, users, dashboard metrics, monitoring) now implement an explicit *Gateway interface - added the missing AdminMonitoringGateway; media already swaps via the abstract MediaRepository DI class
- Mock mode untouched: provider selection still flows through RuntimeProviderStrategyService/BACKOFFICE_DATA_PROVIDER
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Tokenized every admin card/container radius to the design-system scale: raw 16px/12px/8px replaced with var(--radius-md)/var(--radius-sm) per DESIGN.md (cards 12px, fields keep 10px rounded.field)
- All 16 admin surfaces now share one card language: 1px --border-color border, --radius-md, white fill, 16px padding
- Normalized type-ramp outliers: 0.88rem -> 0.85rem (order timeline), 0.9em -> 0.9rem (category form breadcrumb)
- Visual-only pass: no markup, logic, or layout changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Audited every 'key | translate' and translateService.t() usage under features/admin against en/ru/hy; 37 static keys and 8 dynamic namespaces were missing and rendered as raw keys
- Added full adminTransactions namespace (was entirely absent), adminUsers table/invite/session keys with scopeValue/statusValue/invitationStatus maps, adminMonitoring section titles and categoryValue/queueStatus/webhookStatus/levelValue maps, adminAnalytics.topProducts, adminProducts.variants/archived
- Resolved leaf/object key conflicts: dynamic 'adminMonitoring.category.*' renamed to categoryValue.*, 'adminTransactions.type.*' to typeValue.* so the leaf labels translate correctly
- Monitoring event severity now translated (levelValue) instead of raw English enum
- Merchant wording over technical: 'Background queues', 'Webhook deliveries', 'Activity log', 'Running smoothly/Slowed down/Stopped'
- Verified zero missing keys in en/ru/hy via AST-level key extraction
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Business Dashboard: revenue/orders/customers/AOV/top products/low stock/recent activity/warnings/completion% (Overview tab)
- Marketplace Health: real completion checks (images, SEO, categories, reviews, orders, translations, homepage, backend, performance) with clickable recommendations
- Product Analytics: top selling/most reviewed/worst rated/hidden/archived counts; top-viewed honestly marked Unknown (no view tracking exists)
- Customer Analytics: new/returning customers, average spend, retention - derived from real order data
- Search & Traffic tabs: honest 'Unknown - available after backend integration' placeholders, no fabricated numbers
- Recommendations engine: actionable cards (missing images/SEO/category, empty categories, incomplete homepage, unpublished static pages) linking to the relevant admin page
- System Monitoring: added real draft/publish/sync/backend-connectivity state from AdminDashboardFacade
- CSV export extended to top products and health checks; added print action
- New adminAnalytics.* and adminMarketplaceHealth.* i18n namespaces (en/ru/hy), no raw i18n keys
- Reused existing shared UI (app-table/app-badge/app-skeleton/app-empty-state) and content-health-widget completion-meter pattern - no new backend APIs, no storage changes
New Reviews & Moderation feature (frontend only): moderation dashboard (real pending/approved/rejected/reported/spam counts, average rating, recent activity, moderation-health%, computed from the full review queue); reviews list with table/cards, density, saved column visibility, search/status/rating filters, bulk approve/reject/spam/hide/export; review detail shows customer/product/rating/text/photos/timeline/moderator-notes with a real moderation workflow (approve/reject/spam/hide/restore/pin/feature, feature disabled with an explanation unless the review is approved); reusable ReviewHealthWidget (rating/text/media/moderated/report-status/visible + completion%); reports queue lists reports against products/reviews/customers/categories with resolve/dismiss, and honestly renders 'Not available yet' rather than fabricating a value wherever a target can't be resolved (e.g. photos, customer-target reports). Backed by a new in-memory AdminModerationLocalGateway seeded from real product data - the same mock-gateway pattern already used by every other admin feature in this app (orders/products/categories), since no review/report backend exists to reuse. Replaced the 'Reviews' comingSoon nav placeholder with a working link; added full adminModeration i18n coverage.
Orders dashboard (real orders-today/pending/paid/cancelled/refunded/customers/returning-customers/average-order/recent-activity/system-alerts, computed from the full order book not just the current page); orders list gains density, saved column visibility, bulk status change/archive/delete/export/print alongside existing search+status filter+pagination, sticky table header; order detail gets a visual status workflow stepper in business language (Pending/Packing/Shipping/Completed, with Cancelled/Refunded as terminal states) and a reusable OrderTimelineComponent replacing the flat text log; added a derived Customers feature (list + profile detail: statistics/lifetime value/addresses/orders/activity/notes) built entirely by grouping existing AdminOrder records by email - no new backend, no invented gateway, since no customer entity existed. Added archive/restore/delete to the orders gateway (soft-delete pattern mirroring categories) and filled in the adminOrders/adminCustomers i18n namespaces, which previously didn't exist at all (every adminOrders.* label was rendering as a raw key).
Categories dashboard (real total/visible/hidden/empty/root/subcategory/missing-image/missing-SEO/last-modified/completion% stats, recommended next action); tree view is now a real expand/collapse hierarchy (state persisted via LocalStorageService) with keyboard navigation (arrow keys, jump-to-parent), search that force-expands and keeps only matching branches + their ancestors/descendants instead of silently dropping deep matches; added Table/Cards views alongside the tree with density and saved column visibility; real bulk actions (show/hide/delete/assign parent/assign image via the Media Library picker/duplicate via the existing createCategory call/CSV export) plus the pre-existing single-item actions; reusable CategoryHealthWidget (image/SEO/description/valid-parent/visibility/products-assigned + completion%); editor reorganized into General/Media/SEO/Visibility/Navigation/Attributes/Advanced tabs, media now uses the shared ImageFieldComponent, SEO explains fields in plain language with a live preview, Navigation tab shows real breadcrumb/children/visibility-based nav status, Attributes uses the shared KeyValueEditorComponent. Filled in the adminCategories i18n namespace (previously only 2 of ~90 referenced keys existed) across en/ru/hy. Also fixed the pre-existing bug where a filtered/searched category list could silently drop a matched descendant whose ancestor's title didn't match, by loading the full catalog once and filtering client-side.
Products dashboard (real total/published/drafts/out-of-stock/hidden/missing-images/missing-SEO/low-quality counts, recently-edited list, recommended next action); list gains table/grid view toggle, density, saved column visibility and saved sort (persisted via LocalStorageService), plus real bulk assign-category/assign-tags/duplicate/CSV-export alongside existing publish/hide/delete; per-row and per-editor reusable ProductHealthWidget (images/SEO/price/category/description/inventory checklist + completion %); editor reorganized into General/Media/Pricing/Inventory/Categories/Attributes/SEO/Visibility/Advanced tabs, media now uses the shared MediaPickerComponent/ImageFieldComponent (primary image + reorderable gallery) instead of raw URL textareas, specifications/attributes/variants moved off pipe-delimited textareas onto the shared KeyValueEditorComponent, SEO tab explains fields in plain language with a live search-result preview, inventory relabeled in business language, toggles/badges use the shared Toggle/Badge components. Filled in the adminProducts i18n namespace (previously ~98% missing, rendering raw translation keys) across en/ru/hy.
Renamed the experience consistently to Marketplace Builder everywhere
visible (page title/subtitle, breadcrumbs, sidebar, dashboard quick
action/shortcut copy) - internal ProjectEditor class/selector names
kept as-is to avoid regressions. builder.title/subtitle no longer say
'bootstrap-config editing surface' to end users.
New business-oriented IA (builder-groups.model.ts): 12 existing
ProjectEditorSectionIds regrouped into 8 groups (Marketplace, Branding
and Design, Homepage, Content, Languages, Marketplace Features,
Navigation and Search, Preview) - every existing section mapped to
exactly one group, none dropped, no group points at a page that
doesn't exist.
New Builder landing page at /edit (was a redirect straight into the
General form): readiness percent, recommended next step, one overview
card per group (purpose + real complete/in-progress/not-started/unknown
status, icon+text never color-alone), a Marketplace Readiness
checklist, quick links. All derived from real facade/schema signals
(required-field fill state, modifiedSections, staticPages, catalog
counts via BackofficeDataService) or explicitly marked unknown
(the "preview reviewed" check has no tracking - shown as unknown,
never guessed).
Section pages (/edit/:section) now share one consistent header:
breadcrumb (Builder > Group > Section), draft/published + modified
badges, and a contextual help panel (what is this / where visible /
what happens if you skip it) sourced per group. Sidebar nav rewritten
as grouped, icon-led sections with per-section and per-group status
indicators; layout converted from a top pill-tab bar to a responsive
sidebar (desktop 280px, tablet 72px icon rail, mobile drawer with
Escape-to-close), matching the Sprint 1 admin shell pattern. Section
form components themselves untouched.
Routes: 'edit' is now its own landing route instead of redirecting to
'edit/general'; /builder and /project-editor redirect to 'edit'.
Sprint 1/2 destinations that pointed at edit/general now point at the
new edit landing page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaced the placeholder dashboard with a real business homepage built
on 6 new reusable widget components (DashboardSection, DashboardCard,
DashboardMetric, DashboardStatusRow, DashboardTimeline,
DashboardShortcutCard), all wired through shared app-card/app-skeleton/
app-empty-state.
Sections: Welcome (tenant, env badge, current user, last publish/save -
all real facade signals, never blank), Quick Actions (large cards: add
product, create category, open builder, edit homepage, media, orders),
Draft Status (real dirty/modifiedFields/publish/discard from
ProjectEditorFacade), Marketplace Health (9 checks - config, product/
category counts, missing translations, draft, static-pages-unpublished,
homepage/theme configured, all real; images-without-alt shown as
'not tracked yet' rather than fabricated), Recent Activity (existing
localStorage-backed history service, loading/empty states), Useful
Shortcuts, Documentation (honest coming-soon list, no dead links).
12-column responsive grid: 3-across cards on desktop, 2-column on
tablet, single column on mobile, no horizontal scroll. Keyboard/focus/
ARIA per shortcut card and status row; health status never conveyed by
color alone (icon + text every time).
AdminDashboardHealthCheck (boolean 'healthy' shape) and its facade
signal are untouched - AdminMonitoringPageComponent also consumes them.
New richer status data lives in a separate homeHealthChecks signal/
AdminDashboardHomeHealthCheck type instead of widening the shared one.
Added ~50 new dashboard.* / kept existing i18n strings across ru/en/hy;
no raw keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sidebar (Dashboard/Catalog group/Products/Categories/Orders/Transactions/
Reviews/Reports/Content/Media/Marketplace Builder/Users/Settings/
Monitoring/Analytics + Documentation/Help/Logout), sticky topbar
(breadcrumbs, page title/description, search, notifications, tenant
selector and quick-publish placeholders, current user), reserved
right-rail slot, scrollable content area. Desktop 280px sidebar, tablet
icon rail, mobile drawer with focus management and Escape-to-close.
Nav items without a built page (Reviews, Reports, Settings, Docs, Help)
render disabled with a coming-soon badge instead of dead links; Content
and Marketplace Builder route to the existing project-editor pages
(static-pages / general) rather than duplicating them.
All 15 /backoffice/** routes now render through AdminLayoutComponent;
the public storefront header/back-button/footer no longer render on
admin routes (app.ts/app.html gate on a new isAdminRoute signal).
Added the adminShell i18n namespace (ru/en/hy) for every new shell
string so this doesn't add to the existing untranslated-admin-UI gap
tracked in KNOWN-ISSUES.md.
Colors/type sizes follow DESIGN.md tokens; the two rgba() modal-scrim
values are a documented, intentional exception (neutral overlay,
not a themed token).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bug: admin-product-form.component.html and admin-category-form.component.html
both `@for (locale of ['en','ru','hy']; ...)` over a fixed literal array
instead of the tenant's actual configured locales. A tenant with fewer,
more, or differently-ordered supported locales got translation tabs for
languages it doesn't support and none for ones it does - same class of
bug as the already-fixed general-section/LocaleSyncService gap in
project-editor (docs/EDITOR.md), just never wired here at all.
Fix: AdminProductsFacade and AdminCategoriesFacade each gained a
`supportedLocales` computed (reads ProjectEditorFacade.bootstrap()
.localization.supportedLocales, falling back to ['en'] before bootstrap
loads) 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 page components call
ensureLocalesLoaded() in their constructor and pass
`[locales]="facade.supportedLocales()"` down to the form components,
which now expose a `locales: string[]` @Input() and iterate that
instead of the hardcoded array.
Verified live via window.ng.getComponent() on
/ru/backoffice/{categories,products}/create?devBypassAdmin=true: both
facade.supportedLocales() and the form's bound `locales` input now
read the real tenant order ['ru','en','hy'] (default locale first, as
configured) instead of the previous hardcoded ['en','ru','hy'] -
confirmed by the rendered translation-tab order changing accordingly
in both admin/products and admin/categories editors. tsc --noEmit
clean.
Bug: AdminCategoriesFacade.reorder(id, targetOrder) took the target
row's numeric order and wrote it straight onto the dragged category
(order: targetOrder). That leaves two siblings tied on the same order
value instead of actually repositioning the dragged item - and since
the local gateway's list sort (Array.prototype.sort, stable) breaks
ties by original array position, drops in certain directions have no
visible effect at all. All seeded categories additionally start at
order: 0 (AdminCategoriesLocalGateway.toAdminCategory), so on fresh
data literally every drag silently no-ops.
Fix: reorder(id, targetId) now takes the target category's id (not
its order value, which can be ambiguous/duplicated), computes the
full 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. Restricted to same-parent
siblings (dragged.parentId !== target.parentId is a no-op, matching
the tree UI's existing scope - no cross-parent move support).
Updated the drag payload end to end: AdminCategoriesListComponent's
`reorder` output now emits { id, targetId } instead of
{ id, targetOrder }; the list page binding follows.
Verified live via window.ng.getComponent() on
/ru/backoffice/categories (devBypassAdmin=true; real backend
unreachable in this environment, so verified against
facade.categories.set([...]) synthetic siblings, consistent with the
gateway calls the facade actually issues):
- Before fix: 3 siblings order 0/1/2, drag 'c' onto 'a' ->
gateway.updateCategory received only { id: 'c', order: 0 }, tying
'a' and 'c' at order 0 (with 0-order seed data, no siblings ever
become distinguishable at all).
- After fix: same drag -> gateway.updateCategory called for 'c', 'a',
'b' with the correct distinct sequence (c:0, a:1, b:2).
Bug: startCreate() generated a fresh id via category-${Date.now()}
every call and wrote/read the autosave draft under
admin-category-draft:<that id>. Since the id changes every time
startCreate() runs, a draft saved during one "create category" visit
can never be found by a later visit (even seconds later, same tab) -
draft recovery for new (unsaved) categories was completely dead, and
every abandoned attempt left an orphaned, never-cleaned localStorage
entry.
Fix: create-mode drafts now persist under a fixed key
(admin-category-draft:new) tracked via a new draftStorageKey field,
independent of the draft's own id. Edit-mode drafts are unaffected -
they already keyed on the real, stable category id.
Verified live via window.ng.getComponent() on
/ru/backoffice/categories/create (devBypassAdmin=true):
- Before fix: updateDraft({title}) -> localStorage key
admin-category-draft:category-<ts1>; calling startCreate() again
(simulating navigate-away/back) generated category-<ts2> and never
recovered - draft.title reset to '', dirty=false, old key orphaned.
- After fix: same sequence recovers title/dirty correctly under
admin-category-draft:new; saveDraft() clears that key as expected.
Sprint 28, scoped to admin/* (user decision — full marketplace audit
declined in favor of a bounded pass over the 8 admin features from
Sprints 20-27).
- a11y: aria-label added to every bare <select> not already inside a
<label> across categories/products/orders/transactions/users/monitoring
- loading states: app-skeleton rows/cards added to list pages that
previously rendered blank during the initial fetch (categories, orders,
transactions, users, monitoring's event feed, analytics summary cards)
- admin-dashboard-card's custom shimmer CSS replaced with the shared
SkeletonComponent (same visual result, one less duplicated animation)
- bundle-size budget warning (~198kB over) confirmed pre-existing —
present at Sprint 20's first build before any admin/* code existed,
and new admin pages are all lazy-loaded — documented as out of scope
for this pass rather than chased
docs/ADMIN.md + docs/SPRINT-PLAN.md updated with the scope decision and
what was explicitly not done (Lighthouse, animations, SEO/sitemap,
storefront/editor a11y).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 27.
New features/admin/analytics/ module + net-new /:lang/backoffice/analytics
route + Dashboard Quick Action.
- revenue/orders/avg-order-value/sales-over-time/top-products computed by
composing AdminOrdersLocalGateway (Sprint 23's seeded mock orders) - real
aggregation over mock data, not a separate fabricated dataset
- products/categories counts from AdminProductsLocalGateway/
AdminCategoriesLocalGateway
- visitors/funnels/heatmaps render pending-backend badges (no analytics
pipeline exists anywhere in this system) rather than fabricated numbers,
same convention as the Sprint 19 dashboard's pre-Sprint-23 Orders/Revenue
cards
- plain div-bar chart (no charting library), 7/30/90-day range toggle,
CSV export
docs/ADMIN.md + docs/BACKEND.md (new item 16) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 26.
New features/admin/monitoring/ module + net-new /:lang/backoffice/monitoring
route + Dashboard Quick Action.
- Health section reuses AdminDashboardFacade.healthChecks directly (real
data, unchanged since Sprint 19) instead of duplicating the logic
- unified AdminMonitoringEvent feed covering audit/security/login/
failed-login/api/error/warning, category filter + search, 40 seeded
synthetic entries (no logging backend exists anywhere in this system)
- mock queue depth/status cards, mock webhook delivery log
- intentionally kept separate from Sprint 24's per-transaction audit and
Sprint 25's per-user audit - different scopes, no consolidation attempted
Also fixed a real type error: AdminDashboardQuickActionId's union was
missing 'users' and 'monitoring' (added when wiring those Quick Actions),
caught by ng build's template type-checking even though plain tsc --noEmit
passed - a reminder that ng build is the authoritative check here.
docs/ADMIN.md + docs/BACKEND.md (new item 15) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 25.
New features/admin/users/ module, net-new /:lang/backoffice/users route +
Dashboard Quick Action.
- users: name, Telegram username, scope (marketplace vs office admin),
role (inline change), status (active/invited/suspended), last login
- 4 built-in roles (owner/admin/editor/viewer) with flat permission lists
- invitations: email + role + scope form, pending list + revoke (no email
actually sends - local record only)
- passwordless login confirmed already real (AdminAuthService Telegram QR,
docs/BACKEND.md item 1) - linked, not reimplemented
- per-user mock session list (device/IP/last-active, revoke) - flagged as
mock since the real AdminAuthService only ever tracks the current
browser's session
- per-user audit log dialog (role/status changes), same pattern as
Sprint 24's per-transaction audit, intentionally separate from the
system-wide log planned for Sprint 26
docs/ADMIN.md + docs/BACKEND.md (new item 14) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 24.
New features/admin/transactions/ module. AdminTransactionsLocalGateway
derives one synthetic transaction per Sprint 23's seeded mock order rather
than a separate dataset, keeping order numbers/totals consistent across
the two mock feature areas.
- list: search, status filter, type filter (payment/refund/qr_payment),
pagination, CSV export
- retry failed transactions (appends an audit entry)
- fraud flag toggle
- per-transaction audit log (creation/retry/fraud-flag-change), viewed via
dialog - intentionally separate from the system-wide audit/security log
planned for Sprint 26 (Monitoring)
- wired into /:lang/backoffice/transactions, replacing the coming-soon
placeholder
docs/ADMIN.md + docs/BACKEND.md (new item 13) updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 23.
New features/admin/orders/ module, same container/facade/service split as
admin/products and admin/categories.
- AdminOrder model + AdminOrdersLocalGateway seeding 24 deterministic
synthetic orders (no real order data source exists anywhere in this
repo - explicitly a placeholder, not a mock of production volume)
- list: search, status filter, pagination, CSV export (client-side Blob
download)
- detail: customer/payment/shipping, itemized total, status timeline,
change-status dropdown, refund request + cancel (window.confirm-gated),
separate customer-facing vs internal notes, print invoice via
window.print() with @media print hiding non-invoice chrome
- wired into /:lang/backoffice/orders(/:id), replacing the coming-soon
placeholder
docs/ADMIN.md + docs/BACKEND.md updated; dashboard's Orders/Revenue cards
(Sprint 19) remain intentionally un-wired to this mock and still render
pending-backend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sprint 21.
- archived (soft archive/restore, distinct from visible) with an
include-archived list filter
- barcode field alongside sku
- variants: lightweight name|price|quantity list, same textarea-parse
convention as specifications/attributes
- relatedProductIds: checkbox picker in the editor
- gallery images now added/removed via the shared MediaPickerComponent
instead of a raw URL textarea
- read-only discounted-price preview in the editor
- infinite-scroll toggle on the list (loadMore() appends a page instead
of replacing it; pagination UI swaps for a Load more button)
- category dropdown now sourced from AdminCategoriesGateway (Sprint 20)
instead of AdminProductsLocalGateway's own BackofficeDataService seed
docs/ADMIN.md + docs/BACKEND.md updated with the new field list and the
known trade-off that related-products search is scoped to the currently
loaded page, not the full catalog.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>