Design Management
Bounded context for designer-curated artifacts in the design base. Owns ProjectCollection, collection items, ProjectProductVariant, product references, PartType, and Slides.com deck jobs. Product catalog records stay in product-marketplace.
Overview
Design Management owns designer-curated artifacts in apps/api-v1/src/modules/design-management. A ProjectCollection (short name: Collection) is a designer-created collection or favorites list of products for a project or design workflow. Catalog records remain owned by Product Marketplace; this module references them through ProductReference and read ports.
The module follows api-v1 hexagonal structure. Collections, items, part types, and deck jobs persist in the Airtable design base (appDr2xVXbdJ5b08R, AIRTABLE_DESIGN_BASE_ID). Portal publish writes a Postgres snapshot via PrismaPortalSyncAdapter; the client portal reads that snapshot from api-v2. There is no nested System: api-v1 and api-v2 already collaborate under Backend Platform.
This domain does not send domain events. Earlier event types were removed; use cases return application results. Do not invent events.
Implementation Status
Status: Implemented in api-v1
Portal reads already live on api-v2 (portal-read + GET /api/v2/client-portal/...). That is a BFF over published snapshots, not a replacement for Airtable collection curation. See Backend Platform — Portal read path.
Aggregates and supporting types
| Concept | Persistence today | Badge |
|---|---|---|
ProjectProductVariant | Airtable project_product_variants (ADR-0083); referenced by PDG product assignment | Implemented in api-v1 |
ProjectCollection | Airtable user_product_collections; portal snapshot in Postgres | Implemented in api-v1 |
ProjectCollectionItem | Airtable user_product_collection_items | Implemented in api-v1 |
ProductReference | Value object on each item (PM catalog product / variant / MTO id — not a Design product_variants rec) | Implemented in api-v1 |
PartType | Airtable part types, generated from an MTO template | Implemented in api-v1 |
ProductCollectionDeckJob | Airtable deck jobs; Slides.com is an external publisher | Implemented in api-v1 |
ProjectCollection is the aggregate root. Collection item add/remove/select and portal publish/unpublish run on that aggregate. PartType and ProductCollectionDeckJob are separate persisted types in this module.
Boundary with Product Marketplace
Product browse, search, filter, refresh/reload/delete, image publishing, onboarding, and dimension migration stay in product-marketplace. Manufacturer or vendor product lines are VendorProductCollection, not ProjectCollection.
This module looks up MTO templates through AirtableMtoTemplateAdapter. Product Marketplace Add to Project creates Project Product Variant rows and the default Project Collection. Graph product assignment is owned by project-delivery-graph and references variant domain_id. Extra collection membership uses POST /project-collections/:id/add-items. This module does not own catalog records.
Portal publish
Publish Collection To Portal is the write path:
- api-v1
PublishToPortalUseCase/UnpublishFromPortalUseCase(ForPublishingToPortal,ForUnpublishingFromPortal) - Aggregate
publishToPortal/unpublishFromPortal(draft↔published) ForSyncingPortalDataimplemented byPrismaPortalSyncAdapter(upsert or unpublish in Postgres)- Airtable save of collection portal fields
Reads stay on api-v2: apps/api-v2/src/modules/portal-read and GET /api/v2/client-portal/projects/:projectId/project-collections. Documented on Backend Platform. Do not treat this split as the product-marketplace or BIM migration.
Slides.com
ProductCollectionDeckJob is a publication job that calls Slides.com through SlidesComProductCollectionDeckAdapter. Slides.com is an external publisher, not a catalog service. Do not add a Slides.com EventCatalog service.
Primary Code References
- Module root:
apps/api-v1/src/modules/design-management - Glossary:
apps/api-v1/src/modules/design-management/GLOSSARY.md - Composition:
apps/api-v1/src/modules/design-management/composition/design-management.composition.ts - REST:
apps/api-v1/src/modules/design-management/adapters/inbound/rest/design - Airtable base id:
appDr2xVXbdJ5b08R(AIRTABLE_DESIGN_BASE_ID) - Portal write adapter:
apps/api-v1/src/modules/design-management/adapters/outbound/persistence/portal/PrismaPortalSyncAdapter.ts
HTTP surface
Canonical mount: /api/v1/design.
| Method | Path | Behavior |
|---|---|---|
POST | /project-collections/:id/add-items | Add existing collection items to a collection |
POST | /project-collections/:id/remove-items | Soft-delete items on the aggregate |
POST | /project-collections/:id/select-items | Script-flag select; propagates to active items |
POST | /project-collections/delete | Destroy collections and/or items in Airtable |
POST | /projects/:projectId/actions/add-variants | Add catalog variants to {projectName}-default |
POST | /projects/:projectId/actions/publish-to-portal | Publish collections to the portal snapshot |
POST | /projects/:projectId/actions/unpublish-from-portal | Move collections back to draft |
POST | /product-mtos/:productMtoId/generate-part | Materialize a PartType from an MTO template |
POST | /project-collections/:productCollectionId/publish-slides | Sync Slides.com deck publish |
POST | /slides/publish-collection | Async deck job (ProductCollectionDeckJob) |
GET | /slides/jobs/:jobId/status | Deck job status |
GET | /slides/launch | Blind Slides.com launcher |
There is no colocated *.openapi.ts for this module yet — do not invent OpenAPI operation ids. Legacy /api/app/design/* redirects to /api/v1/design/*.
Documentation Links
- api-v1 service:
/docs/services/api-v1 - api-v2 service:
/docs/services/api-v2 - Ubiquitous language:
/docs/domains/design-management/language - Product Marketplace:
/docs/domains/product-marketplace - Backend platform (portal read path):
/docs/systems/backend-platform - ADRs:
docs/adr/README.md
Open Questions
- When collection curation moves to api-v2, which Airtable tables remain a steward write model versus a read model?
- Whether a domain event publisher should be reintroduced for portal publish. Today there is none; do not invent events until a dispatcher exists.