title: Birthday Add-On Pegasus API Implementation description: Pegasus implementation scope for consuming the V5 birthday add-on contract.
Implementation target repository: hungryhub-team/hh-pegasus-monorepo.
Wire contract: src/HHPegasusMonorepo/superpowers/specs/2026-08-06-birthday-add-on-api-contract.md.
Scope
This document owns the Pegasus client implementation for the V5 birthday add-on API. The hh-server backend plan owns the server contract, and the Book-Bite plan owns partner-portal CRUD. The wire contract is documented separately in the linked contract document.
Goal
Hide a free birthday add-on from the generic restaurant add-on catalogue using the explicit API field while preserving the existing add-on detail renderer and booking/availability behavior.
API contract consumed
GET /api/v5/add_on_packages.json?restaurant_id=:id&is_visible_for_staff=false
GET /api/v5/add_on_packages/:restaurant_add_on_id.json?is_visible_for_staff=false
POST /api/v5/restaurants/:restaurant_id/find_available_add_ons.json
The response is JSON:API. After client camelization, the relevant fields are:
attributes.addOnType: "standard" | "birthday"
attributes.itemType: "items_list" | "items_image" | string
attributes.tag: string | null
attributes.menus.data.img: Array<{ id?: number; birthdayItem?: string; displayName?: string; description?: string | null; thumb: string | null; original: string | null }>
attributes.isFreeBirthdayAddOn: boolean
The only generic-catalogue hide condition is:
attributes.isFreeBirthdayAddOn === true
Do not infer birthday/free semantics from sellingPrice.price, menuType, or menus.type.
Existing renderer compatibility
For both birthday itemType values, the backend keeps the existing image-gallery contract:
attributes.menuType === "image"
attributes.menus.type === "image"
attributes.menus.data.img // [{ thumb, original }]
For items_list, the structured item metadata is carried inside menus.data.img together with thumb and original. For items_image, menus.data.img contains the normal image-gallery entries only.
Implementation checklist
- Add/maintain
isFreeBirthdayAddOnin the shared add-on type. - Apply the explicit filter in the SSR restaurant add-on service before grouping catalogue cards.
- Apply the same filter in the hydrated shared store so client state matches SSR.
- Keep direct detail loading available for an explicitly requested add-on ID.
- Keep availability requests and server-side booking validation unchanged.
- Preserve the existing image renderer for
items_listanditems_image. - Render the birthday tag and structured
items_listdetails frommenus.data.imgin the add-on detail view. - Add fixture coverage for a free birthday row, paid birthday row, and standard zero-price row.
- Verify the filter is idempotent and does not mutate the API response object unexpectedly.
File ownership
| Area | Files |
|---|---|
| API type | packages/shared-type/src/AddOnPackage.ts |
| SSR filtering | packages/shared-service/src/package/getRestaurantAddons.ts |
| Hydrated store filtering | packages/shared-store/src/restaurant/restaurantAddOnPackages.ts |
| Store tests | packages/shared-store/src/restaurant/__tests__/restaurantAddOnPackages.test.ts |
| Existing detail renderer compatibility | packages/pages-restaurants/src/detail/RestaurantBodyView.vue, packages/shared-helper/addOnPackage.ts |
Verification
./node_modules/.pnpm/node_modules/.bin/vitest run \
packages/shared-store/src/restaurant/__tests__/restaurantAddOnPackages.test.ts
Also run the repository’s typecheck and formatter. Manual verification must cover SSR and hydrated client paths, generic catalogue hiding, direct detail loading, standard zero-price add-ons, and availability responses.
Out of scope
- hh-server migrations, booking rules, or partner CRUD.
- Book-Bite form layout and multipart payload construction.
- A new V5
menus.typeor a new top-levelbirthdayAddOnItemsfield; structureditems_listmetadata remains insidemenus.data.img.