Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


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 isFreeBirthdayAddOn in 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_list and items_image.
  • Render the birthday tag and structured items_list details from menus.data.img in 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

AreaFiles
API typepackages/shared-type/src/AddOnPackage.ts
SSR filteringpackages/shared-service/src/package/getRestaurantAddons.ts
Hydrated store filteringpackages/shared-store/src/restaurant/restaurantAddOnPackages.ts
Store testspackages/shared-store/src/restaurant/__tests__/restaurantAddOnPackages.test.ts
Existing detail renderer compatibilitypackages/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.type or a new top-level birthdayAddOnItems field; structured items_list metadata remains inside menus.data.img.