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"
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user