docs(project-editor): document 2026-07-17 bug-hunt audit pass
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

- EDITOR.md: updated sections table (branding OG/gallery + image-field,
  header layout/sticky, widgets JSON error feedback), the inline-validation
  paragraph (now lists every wired fieldKey, not just the original 3
  sections), the primitives table (app-image-field, app-code-editor), and a
  new dated section detailing all 9 fixed bugs plus the 3 real gaps found
  but deferred (theme mode dead at runtime, dynamic-renderer unwired,
  header profile menu missing).
- KNOWN-ISSUES.md: added the 3 deferred gaps as new Open items, added a
  Fixed entry summarizing the 9 bugs (points to EDITOR.md for full detail
  rather than duplicating it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-17 21:12:10 +04:00
parent c069cafe45
commit a8a5de5392
2 changed files with 92 additions and 6 deletions

View File

@@ -26,12 +26,12 @@ Route: `/edit/:section` or `/{lang}/edit/:section`. `/backoffice/static-pages` (
| Section | Component | Covers | | Section | Component | Covers |
|---|---|---| |---|---|---|
| General | `general-section` | marketplace name, domain, description, default/supported languages | | General | `general-section` | marketplace name, domain, description, default/supported languages |
| Branding | `branding-section` | logo, small logo, favicon, marketplace title | | Branding | `branding-section` | logo, small logo, favicon, social share (OG) image, gallery, marketplace title — all image fields use `app-image-field` (thumbnail preview + replace/remove) |
| Theme | `theme-section` | palette colors, theme mode, site layout mode | | Theme | `theme-section` | palette colors (live, applied as CSS custom properties), theme mode (**not applied at runtime, see Known gaps**), site layout mode |
| Header | `header-section` | logo/search/categories/languages/cart/profile/wishlist/compare/region toggles | | Header | `header-section` | logo/search/categories/languages/cart/profile/wishlist/compare/region toggles, layout (default/centered), sticky |
| Footer | `footer-section` | company info, address, phone, email, copyright, payment icons, social links, static pages list | | Footer | `footer-section` | company info, address, phone, email, copyright, payment icons, social links, static pages list |
| Homepage | `homepage-section` | homepage section list: visibility, order (drag-and-drop), layout strategy, columns | | Homepage | `homepage-section` | homepage section list: visibility, order (drag-and-drop), layout strategy, columns |
| Widgets | `widgets-section` | homepage widget configuration — typed editors for hero/categories/product-collection, JSON fallback for everything else | | Widgets | `widgets-section` | homepage widget configuration — typed editors for hero/categories/product-collection, JSON fallback (with draft-preserving inline error, not silent-discard) for everything else |
| Static Pages | `static-pages-editor` (`features/content-management/`) | Full CRUD, media, SEO, per-page draft/publish, device preview, nav integration — see `docs/StaticPages.md` (Sprint X+2) | | Static Pages | `static-pages-editor` (`features/content-management/`) | Full CRUD, media, SEO, per-page draft/publish, device preview, nav integration — see `docs/StaticPages.md` (Sprint X+2) |
| Marketplace Features | `features-section` | feature flags, catalog navigation mode, search suggestions/history, recently viewed, reviews/questions/recommendations | | Marketplace Features | `features-section` | feature flags, catalog navigation mode, search suggestions/history, recently viewed, reviews/questions/recommendations |
| Languages | `languages-section` | add/remove supported locale, set default locale; syncs translation keys across static pages and nav labels via `LocaleSyncService` | | Languages | `languages-section` | add/remove supported locale, set default locale; syncs translation keys across static pages and nav labels via `LocaleSyncService` |
@@ -64,11 +64,11 @@ The schema is currently consumed by the facade (validation issue → field mappi
`primitives.ts` holds pure, framework-free functions — one per concern, reused everywhere that concern appears: `isValidHexColor`, `isValidHttpUrl`, `isValidEmail`, `validateJson`, `validateCss` (brace-balance check, comments stripped), `extractStyleBlocks` (pulls `<style>` bodies out of static-page HTML), `normalizeRoute` (trim/strip-slashes/lowercase for duplicate comparison). `primitives.ts` holds pure, framework-free functions — one per concern, reused everywhere that concern appears: `isValidHexColor`, `isValidHttpUrl`, `isValidEmail`, `validateJson`, `validateCss` (brace-balance check, comments stripped), `extractStyleBlocks` (pulls `<style>` bodies out of static-page HTML), `normalizeRoute` (trim/strip-slashes/lowercase for duplicate comparison).
`ProjectValidator` (`services/project-validator.service.ts`) composes these primitives into checks and tags every `ProjectValidationIssue` with `section`, `fieldKey`, and `severity` (`'error'` blocks Publish, `'warning'` is advisory). Checks: missing `branding.logoUrl`, no supported locales, invalid `tenant.websiteBaseUrl`, duplicate static-page slugs, **duplicate routes** across `pages`/`staticPages` (warning), empty homepage, a homepage widget with no `type`, **malformed widget config** — missing `id`/`type`/`version`/`props` (error), duplicate header nav links, invalid theme colors, **invalid CSS** inside static-page `<style>` blocks (warning), missing translations for a supported locale, and layout/section-layout values outside the known enums. `ProjectValidator` (`services/project-validator.service.ts`) composes these primitives into checks and tags every `ProjectValidationIssue` with `section`, `fieldKey`, and `severity` (`'error'` blocks Publish, `'warning'` is advisory). Checks: missing `branding.logoUrl`, no supported locales, **default locale not itself in the supported-locales list** (error — catches General's free-text default-language field pointing at an unsupported code), invalid `tenant.websiteBaseUrl`, duplicate static-page slugs, **duplicate routes** across `pages`/`staticPages` (warning), empty homepage, a homepage widget with no `type`, **malformed widget config** — missing `id`/`type`/`version`/`props` (error), duplicate header nav links, invalid theme colors, **invalid CSS** inside static-page `<style>` blocks (warning), missing translations for a supported locale, layout/section-layout values outside the known enums, **invalid company contact email** (error), **invalid footer social-link URL** (warning), and **incomplete footer payment icon** — only one of `src`/`alt` set (warning).
### Live inline feedback (facade) ### Live inline feedback (facade)
`ProjectEditorFacade` exposes, on top of `validationIssues`: `blockingIssues` / `hasBlockingIssues` (severity-filtered), `issuesByField: Map<string, ProjectValidationIssue[]>`, `issuesBySection: Map<ProjectEditorSectionId, number>`, and `fieldError(key)` (first message for a field, or `null`). `publish()` gates on `hasBlockingIssues()`, not "any issue" — a duplicate-route or invalid-CSS warning no longer blocks publishing. Sections bind `[error]` on `app-form-field` for schema-backed fields (currently: theme palette, general name/domain, branding logo — the rest read `validationIssues`/`issuesBySection` via the save bar and nav badges rather than per-field, since not every section has been wired yet). `project-editor-nav` shows a red badge with the blocking-issue count per section. `ProjectEditorFacade` exposes, on top of `validationIssues`: `blockingIssues` / `hasBlockingIssues` (severity-filtered), `issuesByField: Map<string, ProjectValidationIssue[]>`, `issuesBySection: Map<ProjectEditorSectionId, number>`, and `fieldError(key)` (first message for a field, or `null`). `publish()` gates on `hasBlockingIssues()`, not "any issue" — a duplicate-route or invalid-CSS warning no longer blocks publishing. Sections bind `[error]` on `app-form-field` (or a standalone `<p class="editor-error">` where the target isn't a single form-field, e.g. a whole list) for every field that has a matching `ProjectValidator` `fieldKey` today: theme palette, general name/domain/default-locale, branding logo, languages (`localization.supportedLocales`), homepage/widgets (`pages`), navigation (`navigation.header`), static-pages (`staticPages`), and footer (contact email, social links, payment icons). Header/features have no matching validator checks (every field there is a bool/enum, always valid by construction), so nothing is wired there — not an oversight. `project-editor-nav` shows a red badge with the blocking-issue count per section.
### Undo / redo (`schema/history.util.ts` + facade) ### Undo / redo (`schema/history.util.ts` + facade)
@@ -102,6 +102,8 @@ All 11 section components (`sections/*.component.html`) share these 6 `shared/ui
| `SectionCardComponent` | `app-section-card` | the copy-pasted `editor-section-card`/`<h2>` shell | all 11 sections | | `SectionCardComponent` | `app-section-card` | the copy-pasted `editor-section-card`/`<h2>` shell | all 11 sections |
| `LocaleTabsComponent` | `app-locale-tabs` | (new capability) | languages, navigation | | `LocaleTabsComponent` | `app-locale-tabs` | (new capability) | languages, navigation |
| `KeyValueEditorComponent<T>` | `app-key-value-editor` | pipe-delimited `<textarea>` lists | footer (payment icons, social links) | | `KeyValueEditorComponent<T>` | `app-key-value-editor` | pipe-delimited `<textarea>` lists | footer (payment icons, social links) |
| `ImageFieldComponent` | `app-image-field` | manual URL `<input>` + separate "choose image" button, no preview | branding (logo/small-logo/favicon/social/gallery), footer (logo, payment icons) — thumbnail preview + Replace/Remove, opens its own `app-media-picker` |
| `CodeEditorComponent` | `app-code-editor` | plain `<textarea>` for raw-HTML mode | `MarketplaceHtmlEditorComponent`'s "Код" toggle — overlay-textarea syntax highlighting (no Monaco/CodeMirror dependency); tokenizes HTML tags/comments and delegates `<style>` block contents to a CSS tokenizer (selector/property/value/string/comment-aware) |
`section.shared.scss`'s `.editor-grid.*` classes are unchanged and still used inside `SectionCard` bodies; only the outer `.editor-section-card` shell and per-section `<h2>` were replaced (that rule has been removed from the shared stylesheet since it has no remaining consumers). `section.shared.scss`'s `.editor-grid.*` classes are unchanged and still used inside `SectionCard` bodies; only the outer `.editor-section-card` shell and per-section `<h2>` were replaced (that rule has been removed from the shared stylesheet since it has no remaining consumers).
@@ -139,3 +141,23 @@ Every field across the 10 editor section templates now carries a one-line, i18n'
Each of these components defines a local `readonly` options array of `{ value, labelKey, descriptionKey }` (per ADR-006, these are section/container components so this is allowed without a new shared UI library). Each of these components defines a local `readonly` options array of `{ value, labelKey, descriptionKey }` (per ADR-006, these are section/container components so this is allowed without a new shared UI library).
**Still plain text/checkbox, with a description added, and why:** marketplace name, domain, description, logo/favicon/small-logo URLs, palette colors (already `<input type="color">`, which is the correct native widget), company/address/phone/email, copyright, payment icons/social links (JSON-ish textarea), homepage section `columns` (a number, not an enum), widget-specific props (`hero`/`categories`/`product-collection` typed fields like layout/height/overlay/autoplay/cardsPerRow — these are widget `props` strings/booleans, not modeled as TypeScript unions anywhere, so they stay free text/checkbox with a description rather than a fabricated enum), navigation link label/URL, and the widget JSON fallback textarea for any widget type without a dedicated editor. These are genuinely open-ended or already have the correct native input type; converting them to `<select>` would either be wrong (URLs/colors/free text) or invent an enum that doesn't exist in the schema. **Still plain text/checkbox, with a description added, and why:** marketplace name, domain, description, logo/favicon/small-logo URLs, palette colors (already `<input type="color">`, which is the correct native widget), company/address/phone/email, copyright, payment icons/social links (JSON-ish textarea), homepage section `columns` (a number, not an enum), widget-specific props (`hero`/`categories`/`product-collection` typed fields like layout/height/overlay/autoplay/cardsPerRow — these are widget `props` strings/booleans, not modeled as TypeScript unions anywhere, so they stay free text/checkbox with a description rather than a fabricated enum), navigation link label/URL, and the widget JSON fallback textarea for any widget type without a dedicated editor. These are genuinely open-ended or already have the correct native input type; converting them to `<select>` would either be wrong (URLs/colors/free text) or invent an enum that doesn't exist in the schema.
## Bug-hunt audit pass (2026-07-17)
A section-by-section correctness audit (not a feature pass) — for each section, checked whether its controls actually do what they claim at runtime, not just whether they render. 9 real, verified defects found and fixed (each confirmed live via `window.ng.getComponent()` reproducing the exact bug, then re-verified fixed):
- **Footer**: `createSocialLinkRow`'s id was derived from array length (`social-${length+1}`) — add/remove/add reliably collides with a surviving row's id, corrupting `footer.component.html`'s `@for (... track item.id)` DOM identity on the public storefront. Payment-icon `@for` tracked by `icon.src`, which collides whenever two rows share a src (most commonly two blank ones). Both switched to safe keys.
- **Features**: wishlist/compare visibility is gated by *two* flags at runtime (`featureFlags.<key>` AND `userExperience.<key>.enabled` — see `feature-config.service.ts`), but the editor only exposed a toggle for the first. Both default `true` so it was silent, but a config with the second explicitly `false` left the toggle looking "on" with no way to fix it from this screen. Now one toggle drives both.
- **Widgets**: the JSON-fallback textarea's `updateJson()` caught parse errors and did nothing, but the textarea was bound to `propsJson(committed props)` — so an in-progress invalid edit got silently overwritten on the next change-detection pass. Now keeps the user's draft on screen with an inline error until it's valid.
- **Languages**: `addLocale()` cleared the input regardless of whether `LocaleSyncService` actually accepted the code — adding an already-supported locale silently no-opped. Now shows an inline error and leaves the input untouched.
- **Preview**: `importBootstrap()` replaced `state.bootstrap` directly instead of routing through `updateBootstrap()` — so an import never got a `draftStorage.save()` (lost on refresh before an explicit Save) and was never an undo-able history step. Now routed through the same pipeline as every other edit.
- **Static Pages**: `createPage()`'s slug (`custom-page-${length+1}`) and `duplicatePage()`'s slug/route (fixed `-copy` suffix) both reproducibly collide the same way as the footer bug above (create/delete/create; duplicate the same page twice). Added a shared `uniqueValue()` helper (appends `-2`, `-3`, ... until free).
- **General**: "Supported Languages" is a free-text comma list that bypassed `LocaleSyncService` entirely, so adding a locale here never seeded the empty translation entries Languages' add-button produces — the two UI paths silently diverged. Now diffs and routes through `facade.addLocale()`/`removeLocale()`. Also added the "default locale not itself supported" validator check described above, since this field had (and still has, by design — it's free text) no format guard.
- **Branding → SEO**: `branding.socialImageUrl` (added earlier this same pass) wasn't actually read by `SeoService.resetToDefaults()` — the OG/Twitter image fallback stayed on `appIconUrl || logoUrl`. Fixed to check it first.
- **Media picker**: `MediaLibraryFacade` is a root-provided singleton shared by *every* `app-media-picker` instance on a page (branding alone renders 4). `ngOnInit` loaded unconditionally on mount regardless of dialog state, and `search`/`folder`/`page` filters leaked between independently-opened picker dialogs. Replaced with an `effect()` that resets those filters and loads only when that instance's own `open` input actually becomes `true`.
### Known gaps found but not fixed (real, out of scope for this pass)
- **Theme Mode has no runtime effect.** `theme-section`'s light/dark/system selector correctly saves and sets a `data-theme-mode` attribute (`theme-engine.service.ts`), but zero CSS anywhere in the app reads that attribute — picking Dark or System currently changes nothing visually. (Theme palette colors *are* live — real CSS custom properties consumed throughout the stylesheets — only the mode switch is dead.) Fixing this is a real dark-mode implementation project (dark palette + CSS strategy + `matchMedia` for "system"), not a wiring fix.
- **`layout.type` (Site Layout) and the homepage section's `type` field both feed a rendering pipeline that was never wired up.** `src/app/dynamic-renderer/` has services/models for page/section/widget rendering but zero components or templates (every directory has only a `.gitkeep`) — the storefront homepage renders through a separate, older path that ignores both fields. `homepage-section.component.ts`'s `updateSection(id, 'type', ...)` has no UI calling it because of this; not built, since building UI for a field nothing reads would be inventing dead controls.
- **`HeaderConfig.showProfile`** is a real toggle in `header-section` with no corresponding profile/account menu anywhere in `header.component.html` — the toggle currently does nothing. Building the actual menu is a feature (needs an auth-system check first), not an editor-wiring fix.

View File

@@ -45,6 +45,37 @@ don't fix inline unless asked.
itself (empty-state copy for the skeleton/empty-state consistency fix), itself (empty-state copy for the skeleton/empty-state consistency fix),
it does not touch the ~178 pre-existing gap. it does not touch the ~178 pre-existing gap.
3. **Theme Mode selector has no runtime effect.** `theme-section`'s light/dark/
system dropdown saves correctly and `theme-engine.service.ts` sets a
`data-theme-mode` attribute on `<html>`, but no CSS anywhere in the app
reads that attribute — picking Dark or System changes nothing visually
today. Theme palette colors are unaffected (they're real CSS custom
properties, genuinely live). Fixing this means implementing actual
dark-mode CSS (a dark palette + `[data-theme-mode]`/`prefers-color-scheme`
strategy + a `matchMedia` listener for "system", since that can change
without a reload) — a real feature project, not a wiring fix.
- Found: 2026-07-17, project-editor bug-hunt audit (`docs/EDITOR.md`).
4. **`dynamic-renderer/` pipeline exists but is never wired up.**
`src/app/dynamic-renderer/{page-renderer,section-renderer,section-engine,
page-resolver,widget-host}` has services and models but zero components
or templates (every directory has only a `.gitkeep`). The storefront
homepage renders through a separate, older path that doesn't consume it.
Two editor fields feed this dead pipeline with no visible effect:
`layout.type` (Theme section, "Site Layout") and the homepage section's
own `type` field (`homepage-section.component.ts`'s
`updateSection(id, 'type', ...)` has no UI calling it, because of this).
Needs a decision: finish wiring it in (if it's WIP for a planned
replacement) or delete it as abandoned scaffolding.
- Found: 2026-07-17, project-editor bug-hunt audit.
5. **`HeaderConfig.showProfile` toggle has no corresponding UI.** The header
editor's "Profile" toggle updates a real config field, but
`header.component.html` never references `showProfile` — there's no
profile/account menu in the storefront header to show or hide. Needs an
auth-system check first (does one exist yet?) before building the menu.
- Found: 2026-07-17, project-editor bug-hunt audit.
## Fixed ## Fixed
1. **Full-project UX/UI + motion pass across storefront, admin dashboard, 1. **Full-project UX/UI + motion pass across storefront, admin dashboard,
@@ -132,6 +163,39 @@ don't fix inline unless asked.
`/:lang/backoffice/dashboard?devBypassAdmin=true`. `/:lang/backoffice/dashboard?devBypassAdmin=true`.
- Fixed: 2026-07-15, added the 3 keys to the interface + all 3 locales. - Fixed: 2026-07-15, added the 3 keys to the interface + all 3 locales.
5. **Project Editor: 9 real correctness bugs across footer, features,
widgets, languages, preview, static-pages, general, branding/SEO, and the
shared media picker.** Found via a section-by-section "does this control
actually do what it claims at runtime" audit, not a feature pass. Full
detail (repro steps, fix, live verification) in `docs/EDITOR.md`'s
"Bug-hunt audit pass (2026-07-17)" section — summary:
- Footer social-link/payment-icon id generation reproducibly collided
(array-length-derived / fixed suffix), corrupting `@for (track item.id)`
identity on the public storefront footer.
- Features' wishlist/compare toggle only drove one of the two flags that
actually gate visibility at runtime.
- Widgets' JSON-fallback textarea silently discarded invalid edits instead
of showing an error.
- Languages' add-locale silently no-opped on a duplicate code.
- Preview's import bypassed undo history and draft `localStorage`
persistence entirely.
- Static Pages' create/duplicate-page slug generation had the same
collision bug as the footer one.
- General's free-text language fields bypassed `LocaleSyncService`
(no translation-entry propagation) and had no guard against an
unsupported default locale.
- Branding's `socialImageUrl` field (added earlier the same session) was
never actually read by `SeoService` — dead on arrival until wired in.
- The shared `app-media-picker`'s backing facade is a root singleton;
search/folder/page filters leaked between independently-opened picker
dialogs on the same page.
- Found & fixed: 2026-07-17. Each bug was reproduced live via
`window.ng.getComponent()` before fixing and re-verified after.
- 3 further gaps were found but are real feature work, not wiring bugs —
moved to Open (items 3-5 above) rather than fixed inline: Theme Mode
has no runtime effect, the `dynamic-renderer/` pipeline is unwired,
and the header's Profile toggle has no corresponding menu.
## 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