From cb3a6ac98ad111e81b6c4fa82e8f0a2d986c84bd Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Fri, 17 Jul 2026 22:19:03 +0400 Subject: [PATCH] docs(admin): document bug-hunt audit pass over admin/products + admin/categories Adds docs/ADMIN.md's "Bug-hunt audit pass (2026-07-17)" section (mirrors docs/EDITOR.md's) covering both fixes from this session (dead create-category draft recovery, duplicate-order drag-reorder) with repro and live-verification detail, plus the one deferred finding (hardcoded en/ru/hy translation-tab locales in both admin form components instead of the tenant's configured supportedLocales - real cross-feature plumbing, not a bounded fix). Mirrors the same summary into docs/KNOWN-ISSUES.md: both bugs into Fixed, the locale-hardcoding gap into Open as item 6. --- docs/ADMIN.md | 87 ++++++++++++++++++++++++++++++++++++++++++++ docs/KNOWN-ISSUES.md | 38 +++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/docs/ADMIN.md b/docs/ADMIN.md index 3c8d345..3be276c 100644 --- a/docs/ADMIN.md +++ b/docs/ADMIN.md @@ -524,6 +524,93 @@ final result, not just the later commit. 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:`. 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:` 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-`; calling `startCreate()` again + (simulating navigate-away/back) generated `category-` 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`. + +One real gap was found but **not** fixed inline (real feature work, not a +wiring bug - see `docs/KNOWN-ISSUES.md`): both `admin-product-form.component` +and `admin-category-form.component` hardcode their translation-tab locales to +`['en', 'ru', 'hy']` rather than reading the tenant's actual configured +`supportedLocales` (which live on `ProjectEditorFacade.bootstrap()`, the same +source `static-pages-editor.component.ts` already reads correctly). Wiring +this up means threading `supportedLocales` from `ProjectEditorFacade` through +`AdminProductsFacade`/`AdminCategoriesFacade` (neither currently depends on +project-editor state at all) down to two presentational form components - +real cross-feature plumbing, not a bounded bug fix. + +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 diff --git a/docs/KNOWN-ISSUES.md b/docs/KNOWN-ISSUES.md index 3d0621a..d623b27 100644 --- a/docs/KNOWN-ISSUES.md +++ b/docs/KNOWN-ISSUES.md @@ -76,6 +76,21 @@ don't fix inline unless asked. auth-system check first (does one exist yet?) before building the menu. - Found: 2026-07-17, project-editor bug-hunt audit. +6. **Admin product/category translation tabs hardcode `['en', 'ru', 'hy']` + instead of reading the tenant's configured `supportedLocales`.** + `admin-product-form.component.html` and `admin-category-form.component.html` + both `@for (locale of ['en','ru','hy']; ...)` over a fixed literal array + rather than `ProjectEditorFacade.bootstrap().localization.supportedLocales` + (the same source `static-pages-editor.component.ts` already reads + correctly). A tenant with fewer or different supported locales gets + translation tabs for languages it doesn't support, and no tab at all for + ones it does. Not fixed inline: neither `AdminProductsFacade` nor + `AdminCategoriesFacade` currently depends on project-editor state, so this + needs real cross-feature plumbing (facade -> facade -> two presentational + form components), not a bounded wiring fix. + - Found: 2026-07-17, `admin/products` + `admin/categories` bug-hunt audit + (`docs/ADMIN.md`'s "Bug-hunt audit pass" section). + ## Fixed 1. **Full-project UX/UI + motion pass across storefront, admin dashboard, @@ -196,6 +211,29 @@ don't fix inline unless asked. has no runtime effect, the `dynamic-renderer/` pipeline is unwired, and the header's Profile toggle has no corresponding menu. +6. **`admin/categories` facade: dead create-draft recovery + broken + drag-reorder.** Found via the same bug-hunt method as project-editor's + audit, applied to `admin/products` + `admin/categories`. Full detail + (repro steps, fix, live verification) in `docs/ADMIN.md`'s "Bug-hunt audit + pass (2026-07-17)" section - summary: + - `startCreate()` generated a fresh `category-${Date.now()}` id every + call and keyed the `localStorage` autosave draft off it, so create-mode + draft recovery could never find a match (even within the same tab, + seconds apart) and orphaned an entry every abandoned attempt. + - `reorder(id, targetOrder)` wrote the dropped-on row's `order` value + straight onto the dragged category, tying two siblings on the same + `order` instead of repositioning - and since every seeded category + starts at `order: 0`, every drag on fresh data was a silent no-op. + - Found & fixed: 2026-07-17. Each reproduced live via + `window.ng.getComponent()` before fixing, re-verified after (real + backend unreachable in this environment, so via + `facade.categories.set([...])` synthetic siblings feeding the same + facade methods/gateway calls the UI drives). + - One further gap found but deferred as real feature work, not a wiring + bug - see Open item 6 above: `admin-product-form`/`admin-category-form` + hardcode translation-tab locales to `['en','ru','hy']` instead of the + tenant's actual `supportedLocales`. + ## Notes (not bugs, just flag before shipping) - `src/environments/environment.ts`: `useMockData` was temporarily flipped to