Project Collection
Designer-created collection or favorites list of products. Aggregate root for add, remove, select, and portal publish.
Purpose
A user product collection is a designer-curated list of products for a project or design workflow. Short domain name: Collection. Catalog identity lives in product-marketplace; this aggregate holds ProductReference values on its items.
Manufacturer or vendor product lines are VendorProductCollection, not this aggregate.
Products enter a project from Product Marketplace Add to Project. That action calls Design’s add-variants write port, which finds or creates the default Project Collection (collectionKind=default, name {projectName}-default) and upserts Project Product Variant rows (ADR-0083). There is no Design-owned Add to Project. Graph assignment is a separate PDG command. Extra lists on the same project add membership on the existing item (add-items). Do not create a second item for a variant already on the project.
Removing an item from a curated collection re-homes it onto the default collection and does not destroy the project_product_variants row. Destroying the item on the project (POST /project-collections/delete with children, which runs releaseIfUnreferenced) emits ProjectProductVariantReleased from that aggregate.
Invariants
nameis required and non-empty after trim.coverImageUrl, when set, must be an absolutehttporhttpsURL.collectionKindisdefaultorcurated.create()writes curated.createDefault()writes default, requiresprojectId, and names the collection{projectName}-default.- Default collections cannot be renamed (
DefaultCollectionCannotBeRenamedError). - Copy Collection is Airtable-only and always writes
collection_kind=curatedso a default source cannot produce a second default. - Mutations (
addItem,addProductReferences,removeItems,select,publishToPortal,unpublishFromPortal,rename) require statusactive. - Two active items cannot share the same product reference (
DuplicateProjectCollectionItemError).addProductReferencescounts duplicates and continues;addItemfails. - INV-7: a collection item is created only as a member of this collection.
addItemandaddProductReferencesstamp this collection’s id.rehydratestamps a missing id and rejects a mismatched one. removeItemsmarks matching active itemsdeleted. Persistence unlinks deleted items from the parent collection rather than always destroying the Airtable row.select(true|false)sets the collection script flag and copies that value onto every active item.unpublishFromPortalis a no-op when alreadydraft(keepslastUnpublishedAt).
Domain Events
This aggregate does not emit. Collection commands return application results. Membership that creates or destroys a ProjectProductVariant is recorded on that aggregate, not here.