diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index daf6fc6..8441c87 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Claude skills for Dynamicweb 10 — organized by task domain, bundled by role.", - "version": "4.7.0" + "version": "4.7.1" }, "plugins": [ { diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a6884..f4441c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,40 @@ All notable changes to the Dynamicweb Skills plugin are recorded here. The `version` field in `.claude-plugin/marketplace.json` tracks these entries. +## [4.7.1] + +Folds the learnings from a 2026-07 dual demo-host UI pass: two hosts had shipped +with silently-missing Style assets, and several MCP write tools were caught +reporting success without acting. + +### Changed +- **Theme staging is now a mandatory deserialize step + readiness gate** + (`dw-demo-swift/references/deserialize-flow.md` "Stage the theme's Style assets", + new `integrity-sweep.md` Check 8, `dw-demo-base/references/visual-qa.md` + definition-of-done): the Swift repo ships only `ColorScheme.config` — no + `.{json,css}` pairs — while serialized Area rows arrive wired to + `swift`/`buttons`/`fonts`, so `TryGet*Style` silently emits nothing and the + storefront renders in serif fallback that "looks almost right". Stage + theme-default's three pairs + rewire Areas, and gate readiness on the three + emitted style links plus a designed-looking full-page screenshot. +- **Silent no-op catalogue extended to deletes, index builds and passwords** + (`foundational/extend-mcp-tools.md` §5, `surface-priority.md`): `delete_area` / + `delete_users` / `delete_paragraphs` return `succeeded:1` and delete nothing; + `build_product_index` reports a completed build while writing only the + `LastUpdated` marker (Admin UI Repositories → Build Full is the working path; + verify by shard-file mtimes); `update_users` accepts and drops a `password` + property. Round-trip every demo-critical write. +- **PLP list layout + thumbnail lever** (`foundational/swift-building.md` §3 + symptom table): the surface-serialized shop repeater ships + `GridLayoutDesktop='list'` — list is the intended layout, keep it; the + full-bleed-image failure mode is the card image item's `Width='auto'` (→ + `w-100`), and a px value is the thumbnail lever. Also documents the + `ShowAlternativeImageOnHover` NRE on products without a `DefaultImage`. +- **Local demo-host bootstrap conventions** (`dw-demo-base/references/scaffold.md` + §3): admin login on local demo hosts is always `Admin`/`Admin1` (zero-lookup + during live demos; hosted installs keep real secrets), and the wizard-seeded + `Standard` area (AreaId 1) is deleted before the host counts as scaffolded + (SQL — MCP `delete_area` is a silent no-op). ## [4.7.0] Folds the last three Truvio Distribution release cycles into the demo skills diff --git a/skills/dw-demo-base/references/foundational/extend-mcp-tools.md b/skills/dw-demo-base/references/foundational/extend-mcp-tools.md index 49de714..1198f77 100644 --- a/skills/dw-demo-base/references/foundational/extend-mcp-tools.md +++ b/skills/dw-demo-base/references/foundational/extend-mcp-tools.md @@ -130,6 +130,9 @@ and silently drops part of the input: | MCP `save_pages` (update path) | `menuText` — the response even echoes the OLD value | DW 10.25.x | SQL `UPDATE Page SET PageMenuText` + host restart (the nav tree caches menu text) | | MCP `save_pages` (create + update) | `urlName` — the slug you pass is **ignored**; DW derives the slug from `menuText` instead | DW 10.27.x | Set the intended `menuText` (the slug follows it), or SQL `UPDATE Page SET PageUrlName` + host restart. Don't expect `urlName` to pin the slug independently. | | Management API `ParagraphSave` | `contentItem.groups[].fields[].value` mutations — the `ItemType_*` column never updates | DW 10.25.x | MCP `set_item_field_values` first; SQL UPDATE last resort. `ParagraphSave` IS still correct for paragraph-level scalars (Header, Sort, GridRow, Template) | +| MCP `delete_area`, `delete_users`, `delete_paragraphs` | The **entire delete** — `succeeded:1` returned, row still in the DB afterwards | DW 10.27.x + 10.28.1-Pre | SQL `DELETE` (children first: paragraphs → grid rows → pages → area), then restart for the page-tree cache. Always round-trip a delete with a `SELECT COUNT(*)` | +| MCP `build_product_index` (+ `wait_for_product_index`) | The **actual index build** — "queued" then "completed" is reported and the index's `LastUpdated` marker file is touched, but no Lucene segment files are written; queries keep serving the stale shards | DW 10.28.1-Pre | Admin UI → Settings → Repositories → *repository* → Build 'Full' — that path writes segments. Verify by shard-file mtimes under `Files/System/Indexes///`, not by the tool's status or the marker file | +| MCP `update_users` | A `password` property — the schema has no password field, and an extra `password` property is accepted (`succeeded:1`, `updatedOn` bumped) and dropped | DW 10.28.1-Pre | There is no MCP/API password surface at all — use the plaintext escape hatch in [`users-permissions.md`](users-permissions.md) §13 | **Rule:** after any demo-critical update through MCP / Management API, round-trip it — read the value back through a different surface (or curl the rendered page) before declaring it done. When a silent no-op is diff --git a/skills/dw-demo-base/references/foundational/swift-building.md b/skills/dw-demo-base/references/foundational/swift-building.md index 9280d4a..d7dc99a 100644 --- a/skills/dw-demo-base/references/foundational/swift-building.md +++ b/skills/dw-demo-base/references/foundational/swift-building.md @@ -120,6 +120,8 @@ comes from the item-type's `Title` field. | Downloads list shows giant thumbnails | same | `HideThumbnails` | `True` for a clean filename + filetype + size table. | | Long description has too-wide lines | `Swift-v2_ProductLongDescription` | `TextReadability` | `max-width-on` (default) constrains to a reading column; `max-width-off` fills the column. | | Paragraph renders as empty `
` after SQL-direct seed | `Swift-v2_ProductGroupGrid` | `SelectedGroups` | See `SelectedGroups` below — SQL CSV/JSON inserts silently return `null`. | +| PLP renders one product per row with a giant full-bleed image | `Swift-v2_ProductListItemRepeater` + `Swift-v2_ProductDefaultImage` | `GridLayoutDesktop` / `Width` | The surface-serialized shop repeater ships `GridLayoutDesktop='list'` — **list IS the intended shipped layout**, so keep it rather than "fixing" it to a column grid. The full-bleed image is the card image item's `Width='auto'`, which the template renders as `w-100`; a **px value** (e.g. `160`) is the thumbnail lever (`style="width:px"`). Column variants (`2-columns`…`6-columns` → `g-col-lg-*`) remain available when a grid is the deliberate choice. In list mode the row must earn its width — pair the thumbnail with decision-driving fields (price, and e.g. the default-unit shipping weight when the buying story is logistics-led). | +| Every PLP tile renders a Razor NRE error block | `Swift-v2_ProductDefaultImage` | `ShowAlternativeImageOnHover` | With hover-swap on, the stock template's `allAssetsImages.RemoveAll(x => x.Value == product.DefaultImage.Value)` throws when a product has no `DefaultImage` (common on data-first catalogs without imagery). Either turn the field off, or null-guard the predicate (`x?.Value == product?.DefaultImage?.Value`) as a logged template adjustment. | For `IEnumerable` fields (checkbox-list editors — `FieldDisplayGroups`, `ImageAssets`), pass a **comma-separated string**. Bracketed-array / JSON encodings are NOT recognised by the stock editors. diff --git a/skills/dw-demo-base/references/scaffold.md b/skills/dw-demo-base/references/scaffold.md index 60755d9..6f1bce7 100644 --- a/skills/dw-demo-base/references/scaffold.md +++ b/skills/dw-demo-base/references/scaffold.md @@ -64,9 +64,10 @@ The setup wizard is plain ASP.NET MVC forms and is **fully HTTP-drivable** — d - `GET /Admin/Installation/Start` — captures the session cookie + antiforgery token. - `POST /Admin/Installation/Step2` with `{MapFilesToExistingFolder=True, FilesPath=, ResetSettings=false}` — the files repository. - `POST /Admin/Installation/Step3` with `{DatabaseType=sql, Server, Database, CreateDatabase=false, Encrypt=false, IntegratedSecurity=true}` — **this is the schema step**: ~6 s, ~260 tables migrated, and DW itself writes `GlobalSettings.Database.config`. - - **Admin user:** the schema seed creates an inactive `Administrator` (id 2, empty password). Either `POST /Admin/Installation/Step4` with `{Name, Email, Username, Password, PasswordConfirm}`, or activate it via SQL (set active + copy a password hash from a known-good host's Administrator row — keeps the secret out of logs). Capture the credentials for `references/mcp-setup.md` Step 3. + - **Admin user:** the schema seed creates an inactive `Administrator` (id 2, empty password). `POST /Admin/Installation/Step4` with `{Name, Email, Username, Password, PasswordConfirm}` — and on a **local demo host, the username/password is always `Admin` / `Admin1`**. One memorable credential across every local demo host is a presenter requirement (zero-lookup during a live demo); per-host generated secrets and the `Administrator` spelling both cost demo-time fumbling. Record it in `notes/credentials.local.md` anyway (the file is the single credential home). Hosted / customer-facing installs use real secrets — this convention is local-demo-only. If the host already has a differently-named admin, rename it to `Admin` and set the password via the plaintext escape hatch ([`foundational/users-permissions.md`](foundational/users-permissions.md) §13) — there is no MCP/API password surface. 3. **License — also plain HTTP.** Probe `/Admin/` for the `/admin/license` redirect, then `GET` + `POST /Admin/License/TrialInstallStep` (antiforgery token + the preselected trialId radio). A **Suite Trial** is fine for demos; expiry lands ~30 days out — record it in the demo's `CUSTOMISATIONS.md`. Platform-level detail: [`foundational/setup-install.md`](foundational/setup-install.md) §7. 4. **Verify:** a Management API token auth returns 200, and `/Admin/` redirects to the login screen (`/Admin/UI`), not to `/Admin/Installation/Start`. +5. **Delete the leftover `Standard` area (AreaId 1).** Every wizard bootstrap seeds a `Standard` website that no demo uses; it must not survive into a demo host (it pollutes the area list and can grab root-URL resolution). Delete its pages, grid rows, paragraphs and the `Area` row via SQL — the MCP `delete_area` tool reports success without deleting ([`foundational/extend-mcp-tools.md`](foundational/extend-mcp-tools.md) §5). Verify `SELECT COUNT(*) FROM Area WHERE AreaId=1` returns 0. **Restore flavor — skipping the wizard entirely.** When a clean-template `.bak`/bacpac with schema + an active Administrator is available, restore it and pre-provision only: `wwwroot\Files\` (any skeleton — DW fills `System/`, `Images/`, `Files/`) plus `wwwroot\Files\GlobalSettings.Database.config` (``: `Type=ms_sqlserver`, `SQLServer`, `Database`, `IntegratedSecurity=True`). The restored DB is the actual wizard-skipper; `GlobalSettings.config` is optional (DW writes one on first run). diff --git a/skills/dw-demo-base/references/surface-priority.md b/skills/dw-demo-base/references/surface-priority.md index 03c3422..97e1ff8 100644 --- a/skills/dw-demo-base/references/surface-priority.md +++ b/skills/dw-demo-base/references/surface-priority.md @@ -86,8 +86,8 @@ Cloning a tree (Area / Page / Paragraph / GridRow / Item) via raw SQL `INSERT IN **The pitfall:** raw SQL clones get the visible page tree partly right, then break things you only notice 10 screens later (missing PDPs, headers appearing in the content tree, sibling-page links going to 404). Cleanup is then harder than just using MCP/API in the first place. -## Silent no-ops on UPDATE surfaces — verify by round-trip, not by status code +## Silent no-ops on write surfaces (updates, deletes, index builds) — verify by round-trip, not by status code -A `succeeded` / `status: ok` response from surfaces 1-2 does NOT guarantee the field you sent was applied. Some MCP / Management API writes report success, bump `updatedDate`, and silently drop part of the input (e.g. `save_pages` drops `menuText`; `ParagraphSave` drops item-field value mutations). The catalogue of these version-pinned no-ops and their working fallbacks lives with the tools themselves: [`foundational/extend-mcp-tools.md`](foundational/extend-mcp-tools.md) §5 (MCP/API tool behaviour) and [`foundational/content-modelling.md`](foundational/content-modelling.md) (the same two no-ops framed as paragraph/page save bookkeeping). +A `succeeded` / `status: ok` response from surfaces 1-2 does NOT guarantee the operation happened. Some MCP / Management API writes report success, bump `updatedDate`, and silently drop part of the input (e.g. `save_pages` drops `menuText`; `ParagraphSave` drops item-field value mutations) — and some delete/build tools report success while doing nothing at all (`delete_area` leaves the row; `build_product_index` touches a marker file without writing segments). The catalogue of these version-pinned no-ops and their working fallbacks lives with the tools themselves: [`foundational/extend-mcp-tools.md`](foundational/extend-mcp-tools.md) §5 (MCP/API tool behaviour) and [`foundational/content-modelling.md`](foundational/content-modelling.md) (the same two no-ops framed as paragraph/page save bookkeeping). **The always-on demo discipline:** after any update through MCP/API where the change is demo-critical, round-trip it (read the value back through a different surface, or curl the rendered page) before declaring it done. When a silent no-op is confirmed, the SQL fallback is sanctioned — log it in the demo's `CUSTOMISATIONS.md` and note the cache that needs flushing. diff --git a/skills/dw-demo-base/references/visual-qa.md b/skills/dw-demo-base/references/visual-qa.md index 9c44874..1754dd3 100644 --- a/skills/dw-demo-base/references/visual-qa.md +++ b/skills/dw-demo-base/references/visual-qa.md @@ -118,6 +118,7 @@ Batch at most a handful of fixes between re-checks, and never declare a page don ## Definition of done (per demo-critical page) - Detectors: `overflowX` 0, `broken`/`stretched` empty, no unexplained gap > 120px, console free of errors, no 404 assets. +- **Theme gate:** the page `` emits all three `Files/System/Styles/{ColorSchemes,Buttons,Typography}` links and the computed body font is the theme's, not the browser's serif fallback. A serif-fallback page renders "almost right" and still fails — that is the silent Style-asset empty-state (`foundational/swift-building.md` §7); the full-page screenshot must read as a *designed* page before the host counts as ready. - Interaction pass: every visible control changes state when used. - Eyeball checklist: pass at desktop AND mobile widths. - Keeper screenshots (both breakpoints) saved under `\notes\qa\` (the canonical QA-evidence home — see `SKILL.md` "Artifact hygiene"; never the demo root). diff --git a/skills/dw-demo-swift/references/deserialize-flow.md b/skills/dw-demo-swift/references/deserialize-flow.md index 16d6a05..b1695a6 100644 --- a/skills/dw-demo-swift/references/deserialize-flow.md +++ b/skills/dw-demo-swift/references/deserialize-flow.md @@ -47,9 +47,24 @@ From the **`surface-swift` layer** (`\distribution\layers\surface-swi From the **Swift clone** (`\dw-swift\`) to the host's `wwwroot/`: - `Files/Templates/Designs/Swift-v2/` -- `Files/System/Styles/` +- `Files/System/Styles/` (this lands `ColorScheme.config` ONLY — see the theme-staging step below) - `Files/Images/Icons/` (~80 SVGs incl. `Flags/` and `LoginProviders/` — the set integrity-sweep Check 6 verifies; verified present in the Swift clone. These stock icons are also what `theme-default`'s opt-in nav icons bind to — see [`header-menu.md`](header-menu.md)) +**Stage the theme's Style assets (mandatory — the Swift clone ships none).** The Swift repo's +`Files/System/Styles/` contains only `ColorSchemes/ColorScheme.config` — NO `.{json,css}` +pairs — while the surface's serialized `Area` rows arrive wired to +`AreaColorSchemeGroupId='swift'` / `AreaButtonStyleId='buttons'` / `AreaTypographyId='fonts'`. +`TryGet*Style` fails silently when the file behind an id is absent (the empty-state pitfall, +[`swift-building.md`](../../dw-demo-base/references/foundational/swift-building.md) §7), so a +host built without this step renders every page in the browser's serif fallback with unstyled +buttons and a `Styles/Buttons/buttons.css` 404 — and it looks "almost right", which is exactly +why it ships unnoticed. Stage `theme-default`'s three pairs +(`ColorSchemes/Buttons/Typography` `default.{json,css}`) per +[`styles-assets.md`](styles-assets.md) and rewire the Areas to `default` (keep +`AreaColorSchemeId='light'`). Verify: the home page `` emits all three +`/Files/System/Styles/{ColorSchemes,Buttons,Typography}/…css` links and each returns 200 — +integrity-sweep Check 8 owns the gate. + **Repositories skip rule.** For `Files/System/Repositories/`, copy **everything EXCEPT `ProductsBackend/` and `ProductsFrontend/`** — those two index Swift's bike-demo custom fields (`PlantHardiness`, `BikeFrameSize`, plant/bike-specific facets, etc.). Copying them into a host whose products use a different data-model causes `BuildIndex` Full to fail with "field not found in products" — the index builder validates every field reference against the live `EcomProductCategoryField` table. The other Swift-shipped indexes (`Content/`, `Files/`, `Post/`, `Secondary users/`) are demo-data-agnostic — they index Pages/Files/blog Posts/Users via standard fields plus item-type fields that DO resolve cleanly; copy those alongside. Hand-write a per-demo Products index targeting the demo's actual data-model fields instead — see [`../../dw-demo-pim/references/canonical-setup-order.md`](../../dw-demo-pim/references/canonical-setup-order.md) Step 16. (For PIM-data + Swift-frontend hybrid demos with N categories × M custom fields each, pick 5-10 demo-relevant fields per category for the index — not the full set; index size is rarely the constraint, but maintenance and admin-UI clarity are.) **Catalog-paragraph path rewrite (run AFTER the deserialize).** The `eCom_ProductCatalog` paragraphs from the Swift baseline reference `/Files/System/Repositories/ProductsFrontend/Products.query` and `Products.facets` in their `ParagraphModuleSettings` XML. Those paths point into the bike-demo repos you skipped — the Catalog module silently renders an empty product list when the paths break. After authoring your per-demo `Products.query` + `Products.facets` (sourced from `Repository="Products"`, with parameters matching your facet fields), bulk-rewrite the paragraph references via SQL: diff --git a/skills/dw-demo-swift/references/integrity-sweep.md b/skills/dw-demo-swift/references/integrity-sweep.md index 52803ee..4fba38f 100644 --- a/skills/dw-demo-swift/references/integrity-sweep.md +++ b/skills/dw-demo-swift/references/integrity-sweep.md @@ -11,9 +11,10 @@ - [Check 5: BuildIndex (by the `.index` Build Name) + wait for a fresh successful build](#check-5-buildindex-by-the-index-build-name--wait-for-a-fresh-successful-build) - [Check 6: Icon set populated under `Files/Images/Icons/` (Pitfall: blank-blue-button storefront)](#check-6-icon-set-populated-under-filesimagesicons-pitfall-blank-blue-button-storefront) - [Check 7: Raw SQL in paragraph templates (DW10 discipline)](#check-7-raw-sql-in-paragraph-templates-dw10-discipline) +- [Check 8: Style assets staged + emitted (the theme gate)](#check-8-style-assets-staged--emitted-the-theme-gate) - [Sweep complete](#sweep-complete) -> Mandatory post-deserialize integrity sweep. Seven sequential checks. Run after [`deserialize-flow.md`](deserialize-flow.md) returns 2xx. The skill refuses to declare deserialize complete until ALL seven pass. Strict-mode Serializer is the first line of defence — this sweep is the second, catching DW10-specific failures strict mode does not detect. +> Mandatory post-deserialize integrity sweep. Eight sequential checks. Run after [`deserialize-flow.md`](deserialize-flow.md) returns 2xx. The skill refuses to declare deserialize complete until ALL eight pass. Strict-mode Serializer is the first line of defence — this sweep is the second, catching DW10-specific failures strict mode does not detect. ## Prerequisites @@ -301,9 +302,48 @@ skill — staged in [`swift-building.md`](../../dw-demo-base/references/foundati pack covers URL substring scans, hard-coded slugs, category-name branching, master-inline `AddStylesheet`, etc. +## Check 8: Style assets staged + emitted (the theme gate) + +**What is verified:** the Areas' style wiring resolves to real files, and the storefront actually +emits the three style links. `TryGet*Style` fails **silently** when the file behind an Area's +style id is absent, and `swift.css` alone renders a page that looks "almost right" — structural +layout intact, every font in the browser's serif fallback, buttons unstyled. Hosts have shipped +in that state without anyone noticing, because nothing errors. + +**Probe:** + +```powershell +# 1. Files on disk match the Area wiring +$styles = "Dynamicweb.Host.Suite\wwwroot\Files\System\Styles" +$wiring = sqlcmd -S $server -E -d $db -h -1 -Q "SET NOCOUNT ON; + SELECT AreaColorSchemeGroupId + '|' + AreaButtonStyleId + '|' + AreaTypographyId + FROM Area WHERE AreaId = " +$ids = $wiring.Trim() -split '\|' +foreach ($pair in @("ColorSchemes\$($ids[0])", "Buttons\$($ids[1])", "Typography\$($ids[2])")) { + if (-not (Test-Path "$styles\$pair.css") -or -not (Test-Path "$styles\$pair.json")) { + throw "Style asset missing: $pair.{json,css} — stage the theme (deserialize-flow 'Stage the theme') before continuing." + } +} +# 2. The rendered page emits all three links, each returning 200 +$html = (Invoke-WebRequest "https://localhost:$port/" -SkipCertificateCheck -UseBasicParsing).Content +foreach ($dir in 'ColorSchemes','Buttons','Typography') { + if ($html -notmatch "Styles/$dir/[^""]+\.css") { throw "Home emits no $dir stylesheet — empty-state pitfall." } +} +``` + +**Beyond the mechanical probe:** a full-page screenshot of the home page must read as a +*designed* page — brand or neutral-theme typography, styled buttons, coherent color schemes. +A page in serif fallback with browser-default buttons fails this check even when it renders +without errors. Run the polish gate in +[`visual-qa.md`](../../dw-demo-base/references/visual-qa.md) before declaring the host ready. + +**Recovery:** stage the theme's three pairs and rewire the Areas per +[`deserialize-flow.md`](deserialize-flow.md) "Stage the theme's Style assets" + +[`styles-assets.md`](styles-assets.md); restart so the resolved style URLs reload. + ## Sweep complete -When all seven checks pass, deserialize is verified complete. The skill may now declare "baseline restored" to the user. +When all eight checks pass, deserialize is verified complete. The skill may now declare "baseline restored" to the user. Log the result + layer name + timestamp in the per-demo `CUSTOMISATIONS.md` as a deserialize event row, and record the checked-out layer/edition tag there too. This is structural — every deserialize is reproducible by re-running this flow against the same layer name + tag recorded in `CUSTOMISATIONS.md` (the Distribution repo pins by annotated tag `layers//`).