- ID
- d4e171f5-9b1a-4720-9741-505056aa6119
Cookbook Simplification
Overview
Three related cleanup efforts on the cookbook data model and API:
1. Simplify cookbook types (THEMATIC/PERSONALITY → MARKETING; FAVORITES unchanged)
2. Deprecate sections from the API
3. Add tag-based filtering to replace section grouping
4. Flatten the Cookbook LIST API
See also: flat-cookbook-list-api.org for the flatter list endpoint spec.
Need: Simplify Cookbook Types
Current state
~cookbook_type~ has values: ~THEMATIC~, ~PERSONALITY~, ~FAVORITES~
Target state
| Value | Replaces | Description |
|-------|----------|-------------|
| ~MARKETING~ | ~THEMATIC~, ~PERSONALITY~ | Staff/marketing-controlled cookbooks. All current cookbooks are this type. |
| ~FAVORITES~ | ~FAVORITES~ | Synthesized per-customer cookbook. Unchanged. |
| ~USER~ | (future) | User-created cookbooks. Added when user-created cookbook CRUD lands. |
Work items
- Data migration: ~THEMATIC~ → ~MARKETING~, ~PERSONALITY~ → ~MARKETING~
- Update ~CookbookType~ enum/choices in ~models.py~ (remove ~THEMATIC~, ~PERSONALITY~; add ~MARKETING~)
- Update any code filtering or branching on ~cookbook_type~
Need: Deprecate Sections (Remove from API)
Sections were used to group content in marketing-oriented cookbooks (e.g. "Breakfast",
"Lunch + Dinner", "Snacks"). That grouping is moving to tag-based filtering (see below).
Sections remain in the data model for now; only the API surface changes.
See flat-cookbook-list-api.org for the full spec. Summary:
- List endpoint: ~sections~ removed, replaced by flat ~pairing_ids~ + ~product_ids~
- ~author~ collapsed to ~author_id~
- ~collections~ removed (deprecated model)
- Detail endpoint: unchanged for now
Ordering: decided
Pairings render at the top of the grid; products below. True interleaving (random mix) is
not viable because it would require waiting for both ~/api/v3/pairings/~ and
~/api/v3/products/~ to fully resolve before rendering any cards, defeating the
skeleton/progressive hydration pattern.
Products may load faster and render first, then get pushed down the page when pairings
load in. This is an acceptable UI concern to handle client-side, not a performance
concern.
Need: Allow Cookbook Detail Filtering by DC Tag Keys (like exp242)
Sections provided browse-by-category UX ("Breakfast", "Lunch + Dinner", "Snacks"). That
grouping moves to tag-based filtering on the ~GET /api/v3/cookbooks/<slug>/~ detail
endpoint, matching the pattern already implemented for the customer favorites cookbook
endpoint.
Target behavior
Clients pass tag IDs to filter the ~pairing_ids~/~product_ids~ returned by a single
cookbook:
#+begin_example
GET /api/v3/cookbooks/<slug>/?tag_id=<breakfast-tag-id>
GET /api/v3/cookbooks/<slug>/?tag_id=<lunch-dinner-tag-id>
GET /api/v3/cookbooks/<slug>/?tag_id=<snacks-tag-id>
#+end_example
Work items
- Port tag-filter logic from the customer favorites cookbook endpoint to ~CookbookDetailView~
- Identify the DC dictionary tag IDs that map to the former section names
- Document the tag ID → display label mapping for the frontend
Need: Flatten the Cookbook LIST Endpoint
The ~GET /api/v3/cookbooks/~ list response currently nests ~sections~, a full ~author~
object, and ~collections~ inside each cookbook. Replace all of that with flat scalar
fields. Full spec: flat-cookbook-list-api.org.
Response shape changes
| Field | Before | After |
|-------|--------|-------|
| ~author~ | nested object (id, name, slug, bio, …) | removed |
| ~author_id~ | absent | nullable int |
| ~sections~ | nested list of section+item objects | removed |
| ~pairing_ids~ | absent | flat list of ints |
| ~product_ids~ | absent | flat list of ints |
| ~collections~ | nested list of collection objects | removed |
All other scalar fields (~id~, ~slug~, ~name~, ~description~, ~hero_image~,
~is_featured~, ~is_active~, ~sort_order~, ~cookbook_type~, ~visibility~, ~kind~,
~owner_id~, ~dietary_tag_ids~, ~pairing_count~, ~product_count~, ~referral_code~,
~count~, ~next~, ~previous~) are unchanged.
Migration: two phases
To avoid breaking client code, roll this out in two phases:
*Phase 1 — add new properties alongside old ones*
Add ~author_id~, ~pairing_ids~, ~product_ids~ to the response while keeping ~author~,
~sections~, ~collections~. Client code updates to use the new properties; no user impact.
*Phase 2 — remove old properties*
Once client code no longer references ~author~, ~sections~, ~collections~, drop them from
the serializer.
Work items
- Add ~CookbookListSerializer~ in ~cookbooks/serializers.py~
- Wire ~CookbookListView~ to use ~CookbookListSerializer~ instead of ~CookbookSerializer~
- Drop ~collections~ prefetch from list queryset; keep ~sections__items~ (needed for flat ID lists)
- Update ~CookbookV3ReadAPITestCase~ tests to assert new shape
Detail endpoint
~GET /api/v3/cookbooks/<slug>/~ keeps the existing ~CookbookSerializer~ and nested
structure unchanged for now.
Frontend Fetch Architecture
Cookbook landing pages use a hybrid SSR + client-side fetch pattern:
SSR (fast, SEO-critical)
Single request to ~GET /api/v3/cookbooks/<slug>/~ (detail endpoint). Populates title,
hero image, description, author info, and previously section headings. Section headings
go away with the new grid/list view. This is what gets indexed.
Client-side (parallel, progressive)
Two independent requests fire after SSR:
- ~GET /api/v3/pairings/?ids=[]&expand=ingredients,tags~ — pairing card content
- ~GET /api/v3/products/?ids=[]&expand=tags~ — product card content
Cards render with skeleton placeholders immediately; each hydrates as its request
resolves. Products often resolve faster than pairings, so product cards may appear first.
Client-side (follow-up)
Badge and ingredient thumbnail data — depends on the pairing/product responses above.
Scope Notes
- MARKETING cookbooks: staff controls content. Not expected to have a large number of
products/pairings per cookbook — keep lists bounded.
- FAVORITES cookbooks: synthesized per-customer. Shared via referral link. Ordering deferred
(no favorites signal available in listing context today).
- USER type reserved for future user-created cookbook CRUD; not implemented yet.
- Landing page context: no per-customer favorites signal available. Ordering deferred.