Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"metadata": {
"description": "Claude skills for Dynamicweb 10 — organized by task domain, bundled by role.",
"version": "4.10.0"
"version": "4.11.0"
},
"plugins": [
{
Expand Down
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,53 @@
All notable changes to the Dynamicweb Skills plugin are recorded here. The
`version` field in `.claude-plugin/marketplace.json` tracks these entries.

## [4.11.0]

Folds eight learnings from two 2026-07 demo-build runs (a customer build dispatch and a
Swift 2.4 profiles key test) into the demo and staged-foundational skills.

### Added
- **Swift 2.4 sign-in profiles / switch user** (`dw-demo-swift/references/customer-center.md`
§6, routing row + description trigger): profiles (same-username `AccessUser` rows +
`AccessUserIsLogin`, `ListUserProfiles`/`UserProfilesTemplate` paragraph settings,
`DwSwitchUserUniqueId` → `StartSwitchUser`) vs impersonation
(`AccessUserSecondaryRelation` + `CanImpersonate`) — two separate mechanisms, easy to
conflate; the zero-custom-code picker recipe (clone rows + `NEWID()` unique ids + distinct
customer numbers, master `IsLogin=1`, one restart; per-profile isolation free via
`PriceUserCustomerNumber` + `UseUserID`); the `?ShowProfiles=1` sign-in-page picker quirk
(no stock header entry point); and the platform-honesty rule — the 10.29+ gate does not
bite on a 10.28.1-PreRelease build (a PreRelease is effectively the next stream), so say
so when a demo shows features the customer's GA version lacks.
- **Checkout delivery date / custom order fields**
(`dw-demo-swift/references/checkout-order-fields.md`, new reference + routing row): the
stock delivery-date beat needs NO custom order field (`EnableDeliveryDate` on the
checkout paragraph posts into the native `OrderShippingDate` column); order-field values
live in per-system-name `EcomOrders` columns, so an `EcomOrderField` definition without
its matching column breaks every order read (`IndexOutOfRangeException` in
`ExtractOrderFieldValues`); MCP `create_order_field` always violates
`DW_FK_EcomOrderField_EcomFieldType` (upstream bug — use the SQL contract).
- **Order-line price seeding rules**
(`dw-demo-base/references/foundational/commerce-orders.md`, pointer from the dw-demo-pim
order-seeding appendix): change the default currency → restart → THEN seed (pre-restart
seeding produces ×100 unit-price artifacts); qty-tier `EcomPrices` rows silently reprice
explicit unit prices; `add_products` writes only unit-price columns, so backfill line and
order totals in SQL and sanity-sweep for exponent artifacts.
- **In-place platform update: pre-update backup + content-count gate**
(`dw-demo-base/references/foundational/setup-upgrade.md`, `dw-demo-base/SKILL.md` routing
row): before any in-place update on a host with non-regenerable content,
`SELECT COUNT(*) FROM ItemList` + `BACKUP DATABASE`; after, counts must match — an
in-place update cycle has been observed to empty `ItemList`/`ItemListRelation`/child item
tables with no error, and without a backup the content is a hand re-author.

### Changed
- **Silent no-op catalogue extended** (`foundational/extend-mcp-tools.md` §5):
`patch_products_safe` against a variant `EcomProducts` row echoes the requested values
(the echo is the input model, not a post-write read) while the row stays NULL — the SQL
sweep is the canonical variant-enrichment surface; `copy_page` with
`destinationParentPageId=0` lands the copy in area 1 unless `areaId` is passed
explicitly. `dw-demo-pim/references/canonical-setup-order.md` step 14 now names the SQL
sweep as canonical and the never-trust-the-echo rule.

## [4.10.0]

Retires the external scrub-list file: the fold-back's sanitize gate now derives the
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Foundation skill for all demos. Scaffolds the dw10-suite host, wires the Backend
PIM modelling from a blank DB — product data built from scratch via MCP. Use after `dw-demo-base`.

**[dw-demo-swift](skills/dw-demo-swift/SKILL.md)**
Swift frontend — swift/2.3 baseline deserialize, feature-pack install, templates, paragraph types, Visual Editor, and the customer-center playbook. Use after `dw-demo-base`.
Swift frontend — baseline deserialize, feature-pack install, templates, paragraph types, Visual Editor, and the customer-center playbook (incl. the Swift 2.4 sign-in profiles / switch-user recipe and the checkout order-field recipe). Use after `dw-demo-base`.

**[dw-demo-erp](skills/dw-demo-erp/SKILL.md)**
ERP integration demo — DB-staged mock or live BC, Integration Framework rules. Use after `dw-demo-base`.
Expand Down
1 change: 1 addition & 0 deletions skills/dw-demo-base/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Cloning uses `git` (hence the setup-checks probe that `git` is present, plus `gh
| Audit customisations at end of phase | references/audit-customisations.md |
| Honor the customer-context read-only contract | references/customer-context.md |
| Recover from silent AddIn install failure (stuck `UpdateManager` queue) | references/db-update-recovery.md |
| Run an **in-place platform update** on an existing demo host (`Dynamicweb.Suite` bump, design/item-type re-deploy): the mandatory pre-update `BACKUP DATABASE` + `ItemList` content-count gate, update-queue mechanics, schema-drift across NuGet versions | references/foundational/setup-upgrade.md |
| Install the DW Serializer in the demo host; triage Serializer failure patterns; check baseline compatibility | references/serializer-reference.md ("Installation") |
| Understand Serializer internals — these live upstream in the Serializer repo's own docs; the reference carries the pointer block | references/serializer-reference.md ("Internals — upstream pointer block") |
| Run a Swift baseline deserialize (Swift demos only) | [`dynamicweb-swift-demo/references/deserialize-flow.md`](../dw-demo-swift/references/deserialize-flow.md) |
Expand Down
22 changes: 22 additions & 0 deletions skills/dw-demo-base/references/foundational/commerce-orders.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,28 @@ currency. If the `Area` row defaults to one currency/country and orders are seed
order list renders empty silently. Align the area's default currency to the seeded
`OrderCurrencyCode` (or seed orders in the area's default) **before** backfilling completion.

## Order-line prices: seed after the currency restart, then backfill totals

`add_products` (the order-line seeding tool) writes **only the unit-price columns**
(`OrderLineUnitPriceWithoutVAT`/`WithVAT`) — it computes neither the line totals
(`OrderLinePriceWithoutVAT`/`WithVAT`) nor the order totals. And the unit price you pass is not
always the one that lands:

- **Change the default currency → restart → THEN seed.** A changed default currency only
materializes on restart; order lines seeded before that restart can land with unit prices
**×100** (a two-decimal exponent artifact — e.g. an explicit `12.50` stored as `1250`).
- **Qty-tier `EcomPrices` rows silently reprice seeded lines.** A line whose product/quantity
matches a tier row is repriced to the tier price, ignoring the explicit `unitPriceWithoutVat`
passed to the tool.
- **After seeding, run a sanity sweep + backfill in SQL:** flag any
`OrderLineUnitPriceWithoutVAT` above a plausible maximum and ÷100-normalize it; then backfill
the line totals (unit × quantity into `OrderLinePriceWithoutVAT`/`WithVAT`) and the order
totals (`OrderPriceWithVAT`, `OrderPriceWithoutVAT`, `OrderPriceBeforeFees*`).

**Verify:** seed one order post-restart with an explicit price; assert
`OrderLineUnitPriceWithoutVAT` equals the requested value and the account-side order list shows a
non-zero total after an `OrderService` cache flush.

## SQL backfills vs runtime subscribers

The bulk SQL backfills above (`OrderComplete=1`, `OrderCustomerNumber`, and the related
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ and silently drops part of the input:
| 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/<repo>/<index>/`, 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 |
| MCP `patch_products_safe` against a **variant** `EcomProducts` row (`id` + `variantId`) | The **entire patch** — the success items echo the requested values (number, price, isActive) because the echo is the input model, not a post-write read; the variant row's columns stay NULL. The tool writes to the variant *combination* model, not the variant product row (same family as `create_variant_combinations` leaving `ProductActive`/`ProductPrice` NULL — [`pim-modelling.md`](pim-modelling.md) §2.5) | DW 10.27.x | SQL `UPDATE` on the variant `EcomProducts` row is the canonical variant-enrichment surface. Verify immediately: `SELECT ProductNumber FROM EcomProducts WHERE ProductId=@p AND ProductVariantId=@v` — NULL means the write didn't land |
| MCP `copy_page` with `destinationParentPageId=0` (top-level copy) | The **implied area** — the copy lands as a top-level page of area 1, not the source page's area, when no `areaId` is passed | DW 10.27.x | Always pass `areaId` explicitly on top-level copies; confirm the response's `areaId` equals the requested area |

**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
Expand Down
19 changes: 19 additions & 0 deletions skills/dw-demo-base/references/foundational/setup-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,25 @@ cluster-index UpdateId are then marked already-run.
including data migrations; on a populated DB this can corrupt. Mode A is safe only on a fresh DB with
no real data. For a populated/production DB, talk to Dynamicweb Service Desk first.

## In-place platform update — the pre-update backup + content-count gate

Before ANY in-place platform update on a host whose DB content is not regenerable (a
`Dynamicweb.Suite` version bump, a design/item-type re-deploy, an update cycle that runs
deserializes against the live DB), take two snapshots:

```sql
SELECT COUNT(*) FROM ItemList; -- and ItemListRelation
BACKUP DATABASE [<db>] TO DISK = '<path>\<db>-pre-update.bak';
```

After the update, **the counts must match the snapshot**. `ItemList` / `ItemListRelation` rows
back every repeatable-item component (slider slides, accordion items, named lists) and their loss
renders as silent empty bands, not errors — an in-place update cycle has been observed to leave
`ItemList`, `ItemListRelation`, `ItemNamedItemList`, and the `ItemType_*_Item` child tables empty
with no error surfaced anywhere, and without a backup the only recovery is re-authoring the
content by hand. Treat a count mismatch as a stop-the-line failure: restore the backup, then
isolate which update step dropped the rows before re-running.

## Schema-drift across NuGet versions (Serializer / migration crossover)

When a deserialize or cross-host data move warns `source column [T].[C] not present on target schema —
Expand Down
4 changes: 2 additions & 2 deletions skills/dw-demo-pim/references/canonical-setup-order.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ No `ShopType=1` shop. Products live on `EcomGroupProductRelation` rows under `SH
11. **Relate products to data model groups** — `assign_data_model_to_products` (creates `EcomGroupProductRelation` to the DataModel group; that's how fields flow to the product). See `structural-model.md` §2.8 for the four-row reference_category mechanic this depends on.
12. **Enrich** — `patch_products_safe` with customFields.
13. **Assets** — `import_product_images_from_urls` with groupId from `get_product_asset_categories`.
14. **Variants** — attach groups + insert combinations + INSERT variant product rows. Each variant `EcomProducts` row MUST have a unique `ProductNumber` (master + dash + variant-option suffix); collisions silently break the PIM-for-BC connector import. Master renames can re-sync variant numbers — see `structural-model.md` §2.5 for the rule, the regression vector, and the idempotent re-suffix UPDATE.
14. **Variants** — attach groups + insert combinations + INSERT variant product rows. **The SQL sweep is the canonical variant-enrichment step:** `patch_products_safe` against a variant row (`id` + `variantId`) reports success and echoes the requested values while the variant `EcomProducts` row stays untouched — the echo is the input model, not a post-write read (silent no-op catalogue, [`extend-mcp-tools.md`](../../dw-demo-base/references/foundational/extend-mcp-tools.md) §5). Never trust a tool echo for variants; `SELECT` the variant row immediately after any tool-based variant write. Each variant `EcomProducts` row MUST have a unique `ProductNumber` (master + dash + variant-option suffix); collisions silently break the PIM-for-BC connector import. Master renames can re-sync variant numbers — see `structural-model.md` §2.5 for the rule, the regression vector, and the idempotent re-suffix UPDATE.
15. **BOM components** — `EcomProductItems` rows per bundle. **RESTART HOST AFTER** to refresh ProductItem cache.
16. **Repository + Index** — create `Files/System/Repositories/Products/Products.index`: hand-write it from `ProductIndexBuilder.DefaultSettings` with the **inline `ProductIndexSchemaExtender`** and `<Index Name="Products.index">` (extension included), then `POST /admin/api/BuildIndex`. If PLP/PDP throw `numHits must be > 0` after a `state=success` build, the extender is missing (empty Lucene segment). Full XML, the Name-attribute gotcha, the do-NOT-copy-the-Swift-repo-index rule, and the symptom diagnostics: `structural-model.md` §2.4. Note: an index-level `State=Warning` whose only cause is an unbuilt secondary balancer instance is benign — judge by the **primary instance's** build result + doc count, not the index-level Warning (see [`../../dw-demo-base/references/foundational/search-indexing.md`](../../dw-demo-base/references/foundational/search-indexing.md)).
17. **Queries** — `create_or_update_product_queries` via MCP. Split by purpose:
Expand Down Expand Up @@ -107,7 +107,7 @@ Run this as a SQL sweep, not an eyeball pass: select products/variants where any

The two sections below are commerce / customer-center seeding, not PIM setup order — they apply when a demo seeds order history for Swift's account-side and CSR views.

The vendor-generic order-completion backfill facts — `create_orders` seeds `OrderComplete=0` carts (not completed orders), `OrderCustomerNumber` is not populated by `create_orders` (the `UseCustomerNumber` lookup), the area-currency-filters-order-history caveat, and the SQL-backfills-vs-runtime-subscribers distinction — live in [`../../dw-demo-base/references/foundational/commerce-orders.md`](../../dw-demo-base/references/foundational/commerce-orders.md). The `Order.BeforeSave` / `complete_order` / password-rehash runtime paths and the "subscriber ships without a ledger row" rule are covered there too.
The vendor-generic order-completion backfill facts — `create_orders` seeds `OrderComplete=0` carts (not completed orders), `OrderCustomerNumber` is not populated by `create_orders` (the `UseCustomerNumber` lookup), the area-currency-filters-order-history caveat, the order-line price-seeding rules (change the default currency → restart → THEN seed; `add_products` writes only unit-price columns so line/order totals need a backfill; qty-tier `EcomPrices` rows silently reprice explicit unit prices), and the SQL-backfills-vs-runtime-subscribers distinction — live in [`../../dw-demo-base/references/foundational/commerce-orders.md`](../../dw-demo-base/references/foundational/commerce-orders.md). The `Order.BeforeSave` / `complete_order` / password-rehash runtime paths and the "subscriber ships without a ledger row" rule are covered there too.

**Demo-sequence context:** in a Swift customer-center demo this seeding fires *after* the order rows are created, before the account-side / CSR verification walk. The area-default-vs-seed-currency pitfall is the one that most often produces a silently-empty My Orders tab on a deserialized Swift baseline — align the area currency to the seeded `OrderCurrencyCode` before backfilling completion. See [`../../dw-demo-swift/references/customer-center.md`](../../dw-demo-swift/references/customer-center.md) for the area-default pitfall and the CSR `AccessUserSecondaryRelation` grants.

Expand Down
Loading
Loading