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:
sdarbinyan
2026-07-17 22:19:03 +04:00
parent aa308d8258
commit cb3a6ac98a
2 changed files with 125 additions and 0 deletions

View File

@@ -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:<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
- **Dashboard metrics endpoint.** Categories/Products counts are computed

View File

@@ -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