event

Project Product Variant Projected

A catalog variant was written onto a Design project_product_variants row. Product Marketplace publishes missing Supabase product-image URLs.

EventStatus: Implemented in api-v1

Purpose

Integration event of design-management ProjectProductVariant. The row is the project-local identity for a Marketplace catalog variant (ADR-0083).

Add to Project is a Product Marketplace steward action. It calls Design’s POST /projects/:projectId/actions/add-variants write port. This event is recorded on the Design aggregate when that write (or a later catalog-snapshot refresh) succeeds. Design Management does not own the Add to Project trigger.

Thin payload. Handlers resolve unpublished catalog image ids from catalogVariantDomainId and call PublishMissingProductImagesUseCase. They do not trust snapshot imageUrls (Airtable attachments copied onto the Design row). Slides already require a published Supabase URL.

This is not ProductVariantCatalogChanged (refresh of Design catalog fields on an existing row, no Supabase publish) and not ProductVariantImagesChanged (catalog image-record create/delete).

Payload

Envelope is { name, occurredAt, payload } (apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts).

FieldTypeMeaning
projectIdstringDesign project domain id
catalogVariantDomainIdstringProduct Marketplace variant domain_id
takeoffUnitDomainIdstring | nullBIM unit domain_id for the takeoff unit. Null when the catalog snapshot has none. Additive.
retailUnitDomainIdstring | nullBIM unit domain_id for the retail unit. Null when the catalog snapshot has none. Additive.

takeoffUnitDomainId and retailUnitDomainId are additive. They are BIM unit domain ids, not display names and not Airtable record ids. The image-publish consumer still uses catalogVariantDomainId and ignores them.

No Airtable record ids.

Raised by

  • ProjectProductVariant.createForStockVariant and refreshCatalogSnapshot when catalogVariantDomainId is present.
  • Drained after a successful Airtable upsert or refresh, including an Airtable no-op write. A failed write dispatches nothing.
  • HTTP that can lead to dispatch: Product Marketplace Add to Project → POST /api/v1/design/projects/:projectId/actions/add-variants. Catalog refresh of an existing row (from ProductVariantCatalogChanged / RelatedProductAdded) also records this event so a row that never got an image can publish.

Not recorded when catalogVariantDomainId is missing.

Consumers

Product-marketplace handler registered on the design-management dispatcher. Publishes unpublished {imageRecordId}/original objects. It reads catalogVariantDomainId only and ignores takeoffUnitDomainId and retailUnitDomainId. Handler failure is logged and does not fail Add to Project or catalog refresh. Portal list/detail lazy-publish stays.