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.
This commit is contained in:
@@ -524,6 +524,93 @@ final result, not just the later commit.
|
|||||||
full ~178-key backfill is Sprint 29's explicit "translation validation"
|
full ~178-key backfill is Sprint 29's explicit "translation validation"
|
||||||
scope, not squeezed into this polish pass.
|
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`.
|
||||||
|
|
||||||
|
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
|
## Known gaps / backend needs
|
||||||
|
|
||||||
- **Dashboard metrics endpoint.** Categories/Products counts are computed
|
- **Dashboard metrics endpoint.** Categories/Products counts are computed
|
||||||
|
|||||||
@@ -76,6 +76,21 @@ don't fix inline unless asked.
|
|||||||
auth-system check first (does one exist yet?) before building the menu.
|
auth-system check first (does one exist yet?) before building the menu.
|
||||||
- Found: 2026-07-17, project-editor bug-hunt audit.
|
- 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
|
## Fixed
|
||||||
|
|
||||||
1. **Full-project UX/UI + motion pass across storefront, admin dashboard,
|
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,
|
has no runtime effect, the `dynamic-renderer/` pipeline is unwired,
|
||||||
and the header's Profile toggle has no corresponding menu.
|
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)
|
## Notes (not bugs, just flag before shipping)
|
||||||
|
|
||||||
- `src/environments/environment.ts`: `useMockData` was temporarily flipped to
|
- `src/environments/environment.ts`: `useMockData` was temporarily flipped to
|
||||||
|
|||||||
Reference in New Issue
Block a user