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