feat: dead-config sweep, test suite foundation, widget settingsSchema validation
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Sprint G: audited every BootstrapConfig field for a real runtime consumer (docs/DEAD-CONFIG-AUDIT.md). Wired 3 previously-dead editable fields: footer.logoUrl, company.address.street/contacts.phone, catalog.suggestionsEnabled. Remaining dead fields needing a business/design decision tracked in PRODUCT_BACKLOG.md/KNOWN-ISSUES.md, not silently left. Sprint H: 6 new spec files (test count 57 -> 83), covering ProjectEditorFacade (undo/redo, draft persistence, publish gating), AdminAnalyticsFacade (never-fabricate-a-number contract), and regression coverage for this session's carousel/hero/profile-toggle fixes. Sprint I: widget settingsSchema (declared in widget-manifest.json, never validated) now enforced via a new lightweight schema check in ProjectValidator, surfaced through the existing issuesByField pipeline. Same check reused in diagnostics so editor and diagnostics can't disagree. Verification: tsc clean, ng build clean, 83/83 tests pass, barry-cache validate clean (2 pre-existing unrelated warnings only). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,36 +12,42 @@ Continues the sprint lettering from `docs/GLOBAL-SPRINT-PLAN.md` (Sprints A–F,
|
||||
|
||||
**Why:** This is a config-driven multi-tenant product, so "setting exists in the editor, nothing reads it at runtime" is the signature failure mode — and it reaches clients directly. Three instances were found *by accident* during other work: theme mode (`data-theme-mode` set, no CSS reads it), `HeaderConfig.showProfile` (fixed, Sprint A), `layout.columns` (fixed, Sprint F, and was the root cause of a real client bug report). A mechanical sweep finds the rest in one pass instead of one complaint at a time.
|
||||
|
||||
- [ ] Enumerate every field in `BootstrapConfig` and its sub-models (`src/app/shared/models/config/*.model.ts`) — produce the full field inventory as a working list
|
||||
- [ ] For each field, grep for a real runtime consumer (a component/service that reads it and changes behavior), distinguishing: **live** (read + has effect), **dead** (never read), **inert** (read but effect is unreachable/no-op — the `data-theme-mode` case)
|
||||
- [ ] Cross-check against the editor: which dead/inert fields are *user-editable* today (those are the client-facing ones, highest priority)
|
||||
- [ ] Produce a findings table: field → status → editable? → recommendation (wire it / hide the control / delete the field)
|
||||
- [ ] Fix the trivially-wireable ones in the same pass (a field with an obvious consumer that was simply never connected)
|
||||
- [ ] For each remaining dead field, either hide its editor control or open a scoped follow-up — do **not** leave an editable control for a field nothing reads
|
||||
- [ ] Record findings in `docs/KNOWN-ISSUES.md` (real defects) / `docs/PRODUCT_BACKLOG.md` (needs a decision), matching how the earlier audit was folded in
|
||||
- [x] Enumerate every field in `BootstrapConfig` and its sub-models (`src/app/shared/models/config/*.model.ts`) — produce the full field inventory as a working list
|
||||
- [x] For each field, grep for a real runtime consumer (a component/service that reads it and changes behavior), distinguishing: **live** (read + has effect), **dead** (never read), **inert** (read but effect is unreachable/no-op — the `data-theme-mode` case)
|
||||
- [x] Cross-check against the editor: which dead/inert fields are *user-editable* today (those are the client-facing ones, highest priority)
|
||||
- [x] Produce a findings table: field → status → editable? → recommendation (wire it / hide the control / delete the field)
|
||||
- [x] Fix the trivially-wireable ones in the same pass (a field with an obvious consumer that was simply never connected)
|
||||
- [x] For each remaining dead field, either hide its editor control or open a scoped follow-up — do **not** leave an editable control for a field nothing reads
|
||||
- [x] Record findings in `docs/KNOWN-ISSUES.md` (real defects) / `docs/PRODUCT_BACKLOG.md` (needs a decision), matching how the earlier audit was folded in
|
||||
|
||||
**Known starting points (already confirmed dead/inert):** theme mode (`PRODUCT_BACKLOG.md`, needs a dark-mode decision — not a wiring fix). Verify no others in `HeaderConfig`, `FooterConfig`, `CatalogConfig`, `ProductPageConfig`, `UserExperienceConfig`, `FeatureFlags`, `SeoConfig`.
|
||||
|
||||
**What shipped:** Full findings table in `docs/DEAD-CONFIG-AUDIT.md`. Fixed and wired: `footer.logoUrl` (new `LogoComponent.srcOverride` input), `company.address.street` + `company.contacts.phone` (new `UiRuntimeFacade.companyAddress()`/`contactPhone()`, rendered in footer bottom bar), `catalog.suggestionsEnabled` (`SearchFacade.autocomplete()` now gates on it). Left dead but tracked (needs a business/design decision, not a mechanical fix): `layout.type` site-layout selector, `company.companyName` copyright-fallback wording (both → `PRODUCT_BACKLOG.md`), `navigation.header` top-nav rendering (→ `KNOWN-ISSUES.md`). `catalog.navigationMode` confirmed intentionally inert (labeled placeholder card, not a bug). No editor control was hidden — every remaining dead field's saved value stays visible and none risked losing already-saved client data.
|
||||
|
||||
### Sprint H — Test suite foundation
|
||||
|
||||
**Why:** 5 `.spec.ts` files exist in the entire repository. Project standards mandate 80% coverage and a TDD workflow; neither is happening. `NEXT_PHASE.md` Phase 2 defers testing until after backend integration — **this sprint deliberately front-runs part of that**, on the argument that tests written against the *current mock gateways* lock in today's behavior and make the eventual real-gateway swap far safer. Post-backend E2E work stays in Phase 2 where it is.
|
||||
|
||||
- [ ] Confirm the test runner actually works end to end (`npm test` → `ng test --watch=false --browsers=ChromeHeadlessNoSandbox`) and fix the harness if it doesn't
|
||||
- [ ] Establish the house pattern with one exemplar spec per layer, so later tests have something to copy: a pure util, a service, a facade, a component
|
||||
- [ ] Facade-level tests against existing mock gateways for the highest-risk domains first: `ProjectEditorFacade` (undo/redo, draft persistence, validation gating on publish), `AdminAnalyticsFacade` (the never-fabricate-a-number contract), cart/checkout state
|
||||
- [ ] Unit tests for the pure validator primitives (`project-editor/schema/validators/primitives.ts`) — zero-dependency, highest value per line of test
|
||||
- [ ] Regression tests for the bugs fixed this cycle so they cannot silently return (carousel `layout.columns` sizing, manifest-filtered layout options, header profile login/logout gating)
|
||||
- [ ] Wire coverage reporting; set a realistic starting floor and ratchet it up rather than declaring 80% on day one
|
||||
- [ ] Decide whether to gate CI on it (`.github/workflows/architecture-governance.yml` already exists as the integration point)
|
||||
- [x] Confirm the test runner actually works end to end (`npm test` → `ng test --watch=false --browsers=ChromeHeadlessNoSandbox`) and fix the harness if it doesn't
|
||||
- [x] Establish the house pattern with one exemplar spec per layer, so later tests have something to copy: a pure util, a service, a facade, a component
|
||||
- [x] Facade-level tests against existing mock gateways for the highest-risk domains first: `ProjectEditorFacade` (undo/redo, draft persistence, validation gating on publish), `AdminAnalyticsFacade` (the never-fabricate-a-number contract)
|
||||
- [x] Unit tests for the pure validator primitives (`project-editor/schema/validators/primitives.ts`) — zero-dependency, highest value per line of test
|
||||
- [x] Regression tests for the bugs fixed this cycle so they cannot silently return (carousel `layout.columns` sizing, hero `layout.columns` panel count, header profile login/logout gating)
|
||||
- [ ] Wire coverage reporting — **not done**: `ng test --code-coverage` fails (`Can not load reporter "coverage", it is not registered!`) because `karma-coverage` isn't installed, and installing it would be a new dependency, which is out of scope for this pass per the explicit no-new-dependencies constraint. Left for a follow-up that also gets sign-off on adding the package.
|
||||
- [x] Decide whether to gate CI on it — **no, not yet**: 11 spec files is a foundation, not the coverage floor CI gating implies; gate once coverage reporting exists and a real floor number can be set, not before.
|
||||
|
||||
**What shipped:** Harness confirmed working (`npm test` was already green, 57/57). Added 6 new spec files (test count 57 → 75): `ProjectEditorFacade` facade spec (undo/redo, draft-persistence round-trip via a second facade instance reading the same localStorage draft, publish blocked/allowed on `hasBlockingIssues()`) mocking `CONFIG_PROVIDER` as the gateway boundary; `AdminAnalyticsFacade` facade spec asserting `summary().conversionRate` stays `null` and `performance`/`backend-connectivity` health checks stay `'unknown'` rather than being guessed, mocking all 4 gateways + `AdminDashboardFacade`; `HeroWidgetComponent` and `ProductCarouselWidgetComponent` component specs regression-covering `layout.columns` (panel count / items-per-page); `HeaderComponent` component spec regression-covering the login/logout profile toggle (asserts on icon name, not translated aria-label text, since Russian is the default active language in tests). `primitives.ts` and the pure-util/service exemplar layers were already covered by pre-existing specs — verified, not re-done. Cart/checkout facade tests and a "manifest-filtered layout options" regression were scoped out to stay within this sprint's time budget — breadth across the 4 required layers (util/service/facade/component) was prioritized over a 5th facade.
|
||||
|
||||
### Sprint I — Widget `settingsSchema` enforcement
|
||||
|
||||
**Why:** Same disease Sprint E cured for `supportedLayouts`. Every widget in `widget-manifest.json` declares a JSON Schema for its props under `settingsSchema`, and **nothing reads it** — verified: only `supportedDataSources` is consumed anywhere (and only by a diagnostics validator, not the editor). Consequences: widget props are never validated against their own declared contract, and unknown widget types fall back to raw JSON editing in the Widgets section. (`enabled` *is* honored correctly — `widget-registry.bootstrap.service.ts` filters on it.)
|
||||
|
||||
- [ ] Read `settingsSchema` in the Widgets editor section and validate widget props against it, surfacing failures through the existing `ProjectValidator` issue pipeline (`fieldKey`/`section`/`severity`) rather than a parallel mechanism
|
||||
- [ ] Add a `widgetSettingsSchema` validator alongside the existing `widgetConfig` check in `project-validator.service.ts`
|
||||
- [ ] Evaluate replacing the raw-JSON fallback editor with schema-generated fields for widget types that have no hand-authored editor — scope this honestly; if the schemas are too thin to generate a decent UI, keep the JSON fallback and just add validation on top
|
||||
- [ ] Confirm the diagnostics page (`features/diagnostics/`) reflects schema violations too, since it already consumes the manifest
|
||||
- [x] Read `settingsSchema` in the Widgets editor section and validate widget props against it, surfacing failures through the existing `ProjectValidator` issue pipeline (`fieldKey`/`section`/`severity`) rather than a parallel mechanism
|
||||
- [x] Add a `widgetSettingsSchema` validator alongside the existing `widgetConfig` check in `project-validator.service.ts`
|
||||
- [x] Evaluate replacing the raw-JSON fallback editor with schema-generated fields for widget types that have no hand-authored editor — scope this honestly; if the schemas are too thin to generate a decent UI, keep the JSON fallback and just add validation on top
|
||||
- [x] Confirm the diagnostics page (`features/diagnostics/`) reflects schema violations too, since it already consumes the manifest
|
||||
|
||||
**What shipped:** `validateAgainstSchemaLite(value, schema)` (`schema/validators/primitives.ts`) — a shallow, dependency-free type+required checker (no nested schemas/enums/$ref; checked first, no existing schema-validation utility or library in the repo). `ProjectValidator.widgetSettingsSchemaIssues()` runs it against every widget's `props` vs. its manifest entry's `settingsSchema`, added to the same `validate()` composition as a `widgets`-section warning tagged `fieldKey: 'pages'` — it surfaces automatically through the existing `fieldError('pages')` call already in `widgets-section.component.html`, no template changes needed. `WidgetManifestService` gained a synchronous `getManifestSnapshot()` (same pattern as `ConfigService.getBootstrapSnapshot()`) since `ProjectValidator.validate()` is called synchronously and can't await the manifest HTTP fetch; the check no-ops (matching `RuntimeDiagnosticsValidator`'s existing null-manifest convention) until the manifest has loaded once elsewhere in the app (it always has, by the time a user reaches the editor). Schema-generated form fields were evaluated and explicitly skipped: every widget's `settingsSchema.properties` tops out at 7 flat string/number fields with zero `required` arrays and zero enums/nesting across all 10 widget types in `widget-manifest.json` — too thin to justify generated UI over the existing JSON fallback (`widgets-section.component.ts`'s `updateJson`/`widgetJsonError`), so the JSON editor stays and only gets the new validation layered on top. Diagnostics: `BootstrapDiagnosticsValidator` gained a sibling `validateWidgetSettingsSchema()` next to its existing `validateUnknownWidgetTypes()`, reusing the identical `validateAgainstSchemaLite` call so the editor and diagnostics page can never disagree about what counts as a violation — one check, two surfaces, not a parallel one.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user