docs(project-editor): document 2026-07-17 bug-hunt audit pass
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
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:
@@ -26,12 +26,12 @@ Route: `/edit/:section` or `/{lang}/edit/:section`. `/backoffice/static-pages` (
|
||||
| Section | Component | Covers |
|
||||
|---|---|---|
|
||||
| General | `general-section` | marketplace name, domain, description, default/supported languages |
|
||||
| Branding | `branding-section` | logo, small logo, favicon, marketplace title |
|
||||
| Theme | `theme-section` | palette colors, theme mode, site layout mode |
|
||||
| Header | `header-section` | logo/search/categories/languages/cart/profile/wishlist/compare/region toggles |
|
||||
| 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 (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, layout (default/centered), sticky |
|
||||
| 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 |
|
||||
| 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) |
|
||||
| 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` |
|
||||
@@ -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).
|
||||
|
||||
`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)
|
||||
|
||||
`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)
|
||||
|
||||
@@ -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 |
|
||||
| `LocaleTabsComponent` | `app-locale-tabs` | (new capability) | languages, navigation |
|
||||
| `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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
**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.
|
||||
|
||||
Reference in New Issue
Block a user