Birthday Add-On
🧾 History
| Date | Changes | Link | By |
|---|---|---|---|
| July 16, 2026 | - | ClickUp PRD 8ca1fpw-66296 (v7) | Umam (PM via Claude) |
| July 16, 2026 | KB doc created from v7, decisions resolved, glossary + ADRs written | This doc | Engineering |
| July 16, 2026 | v1 scope revised (defer all diner-facing flows pending hh-pegasus access). See 📦 v1 Scope (Revised). | This doc | Engineering |
📕 About
| Context | Add-On — birthday-specific extension |
|---|---|
| Work Type | Feature (two genuinely new things: a diner-facing “Birthday Celebration” section + discovery badge, and a restaurant-facing partner-portal creation screen) |
| Purpose | Growing attach rate / order value on eligible bookings; also helps restaurant partnerships + diner engagement |
| Problem | Diners planning a birthday have nothing built for them — only generic add-ons or guesswork from reviews. Restaurants have no self-service way (until now) to offer anything special for birthdays. |
| Solution | Restaurants get a new partner-portal screen to create their own birthday add-on(s) — their own name, price, cover image, what’s included. These show up in a fixed “Birthday Celebration” section on the restaurant page. Restaurants can price at ฿0 for a free treat (hidden from store page / cart; only selectable after diner picks “Birthday” occasion at checkout). Restaurant cards get a small discovery icon pulled dynamically from whichever birthday add-on(s) the restaurant actually has. |
| Judge v1 by | One metric: does it lift attach rate / order value on eligible bookings? (≥1% target within 60 days of launch.) Everything else is a supporting benefit. |
| Sprint | Starting July 20, 2026 — flagged as tight given newly confirmed partner-portal build |
| Platform | Web + mobile app (diner side), partner portal (restaurant side). v1 ships hh-server + book-bite only — hh-pegasus flows are deferred, see 📦 v1 Scope (Revised). |
| Design Link | Birthday Add-On Figma (partner-portal creation screen not yet mocked, only labeled placeholder) |
| Related Docs | 2002_AddOn20250910164246.md (general add-on system), 2002_Limit20250910164300.md (limits), 2002_Price_Type20250910164316.md (per-person vs per-item) |
🧭 How This Fits With Other Birthday-Related Work
We have three different things touching the word “birthday” right now. Each is scoped separately so work doesn’t overlap or get confused in standup.
| Initiative | What it really is | Relation to this PRD |
|---|---|---|
| This PRD, Birthday Add-On | Restaurants selling their own birthday package on their restaurant page at the moment someone is already booking there. | — |
| Occasion Vault (“The Diary”) | Reminder system in diner’s profile. ~2 weeks before birthday it nudges them to book. Uses same “occasion” concept as checkout. | Sits upstream of us. The free-add-on mechanism here reuses the same occasion plumbing Occasion Vault already stores. |
| Celebration Landing Pages | SEO/discovery page listing restaurants with tags like “Birthday,” “Party Pack,” “Private Room.” | Runs in parallel. Restaurant can show up here just by having tags, with or without an actual birthday add-on. The badge and store-page section here only care about real add-ons, never tags. |
One thing regardless: all three must use consistent naming in analytics (Birthday Add-On, Birthday Landing Page, Birthday Diary Card) so dashboards don’t end up three ways labeled “birthday.”
📦 v1 Scope (Revised)
Decision (July 2026): hh-pegasus (diner-facing frontend) is currently out of reach. v1 ships only the data-layer and restaurant-facing surface. Diner-facing flows are deferred and will follow a separate workstream once hh-pegasus is back in scope.
In scope (this v1)
| Component | Where |
|---|---|
Data model — add_on_type enum column on add_ons table (per 🧠 Design Decisions) | hh-server |
Admin filter — global WHERE add_on_type = 'standard' across all admin catalog list views | hh-server |
| Commission — reuse existing package-creation inheritance (restaurant override → latest non-partner-created package → 12.50% fallback), no new rule | hh-server |
Backend API contracts — has_birthday_add_on exposed on restaurant API; occasion-plumbing backend ready (no consumer yet) | hh-server |
| Flow 8 — Partner-portal Create/Edit Add-On screen with full validation: image specs (cover 420×240/10MB, menu 2048×2048/10MB), at least one “Cake” item required, price zero-or-positive, time-in-advance default to restaurant-level, items-included repeatable list with minimum 1 row (removable when > 1), delete-with-confirm | book-bite |
| Flow 9 — Staff-side create/edit booking in partner portal: mutual exclusivity for birthday add-ons (one active selectable per booking) | book-bite |
| Group-login restriction — select blocked for group login accounts; create/edit allowed per restaurant (conservative for selection only) | hh-server + book-bite |
| Zero-price bug — real test covering ฿0 add-on specifically (defensive; pays back when pegasus ships) | hh-server |
Feature flag — ff_birthday_add_on_v1 wired in Flipper (default off) | hh-server |
Deferred (gated on hh-pegasus access)
| Flow / Event | What |
|---|---|
| Flow 1 — Discovery Icon | Icon on 4 restaurant-card components (homepage, search, group-landing-page, bite-list), deep-link scroll to section |
| Flow 2 — Birthday Celebration Section | Fixed section + cards on restaurant page, “swap on tap” UX |
| Flow 4 — Cart Swap | Exactly-one birthday add-on in cart, client-side swap UX |
| Flow 5 — Checkout Authority | Backend authoritative gate for one-per-booking + ฿0 payment quirk |
| Flow 6 — Free Add-On Unlock | Free add-on only selectable after diner picks Birthday occasion at checkout |
| Flow 7 — Confirmation Line Items | Confirmation screen + email line items |
birthday_icon_impression, birthday_icon_click, birthday_section_view, birthday_add_to_cart, birthday_occasion_selected, birthday_free_addon_added, birthday_checkout_success | All seven frontend-fired analytics events |
has_birthday_add_on API field consumer | Whoever displays the discovery icon / section (i.e., pegasus) |
birthday_addon_created event is measurable in v1 — fires from book-bite when a restaurant creates a birthday add-on, no pegasus dependency.
Implications for success metrics
| Metric | Status in v1 | Why |
|---|---|---|
| Birthday attach rate (≥1% of eligible bookings) | ❌ Blocked | Requires diner-side add-to-cart to measure |
| Restaurant self-service adoption (150+ restaurants create ≥1) | ✅ Measurable | birthday_addon_created event + count of restaurant-created birthday add-ons |
| Icon effectiveness (≥3% click-through) | ❌ Blocked | Icon lives on pegasus |
| Is this actually new revenue? | ❌ Blocked | Needs full diner funnel |
| Free item usage | ❌ Blocked | Free item only selectable at checkout (Flow 6) |
| Are we cannibalizing other add-ons? | ❌ Blocked | Needs diner-side measurements |
| Checkout reliability (<0.5%) | ❌ Blocked | No checkout path yet |
| Refunds | ❌ Blocked | No diner-side purchases yet |
Bottom line: in v1, the only top-line metric that moves is “restaurant self-service adoption”. The attach-rate, cannibalization, and checkout-reliability metrics are unblocked the moment hh-pegasus ships the diner-facing flows. Tracking for these deferred metrics should start fresh at pegasus release, not retroactively from v1 launch.
📕 Glossary
Vocabulary for terms unique to this PRD. Same terms must be used across hh-server, hh-pegasus, partner portal, and analytics — distinct, consistent naming is the whole point.
Add-On:
A supplementary item a restaurant sells alongside a booking, priced per-person or per-item, with its own date range, image, and limits. Stored in the add_ons table.
Avoid: “package” (means Party Pack), “promotion”, “extra” (overloaded with delivery upsells).
Birthday Add-On:
An add-on (in add_ons table) the restaurant has marked as occasion-gated for birthdays, via add_on_type = 'birthday'. Surfaced in the fixed “Birthday Celebration” section on the restaurant page, drives the discovery icon on restaurant cards, and is excluded from the admin catalog list.
Avoid: “birthday package” (already means Party Pack), “birthday perk” (overlaps with the free variant below).
Premium Birthday Add-On:
A birthday add-on priced above zero. Appears in the Birthday Celebration section and is treated like a standard add-on at checkout.
Avoid: “paid birthday package”, “birthday upsell”.
Free Birthday Add-On:
A birthday add-on priced at zero. Hidden from the store page and cart; only selectable after the diner picks “Birthday” as the booking occasion at checkout. The restaurant sets their own daily limit on how many they’ll give away.
Avoid: “comp”, “freebie”, “gift” (gift has a separate voucher meaning).
Occasion:
A diner-declared context for a booking — birthday, anniversary, business dinner, etc. — picked from a fixed selector at checkout. Used to unlock occasion-gated features (free add-ons) and to drive CRM reminders via the separate Occasion Vault initiative.
Avoid: “event” (overloaded), “celebration” (overlaps with Celebration Landing Pages).
Occasion Vault:
A separate HH initiative (codename “The Diary”) that stores each user’s occasions in their profile and sends CRM reminders roughly two weeks before the date. Upstream of any booking. Shares the “occasion” vocabulary with checkout but solves a different problem (pre-booking nudge vs. in-checkpoint unlock).
Celebration Landing Page:
A separate SEO/discovery surface listing restaurants whose tags fit a celebration theme (Birthday, Party Pack, Private Room). Built from the same restaurant-tagging system that powers other themed landing pages (e.g. Romantic Dinner). Parallel to, not downstream of, birthday add-ons.
Discovery Icon:
The small icon shown on a restaurant card when the restaurant has at least one active birthday add-on. Pulled dynamically from that add-on’s own cover image, not a fixed cake graphic. Multiple icons can show if the restaurant has multiple birthday add-ons.
Avoid: “birthday badge” (overloaded — the partner-portal creation screen also calls itself a “badge”), “cake icon” (no longer accurate since the icon is dynamic).
Add-On Type (add_on_type):
The classification column on add_ons that distinguishes occasion-gated add-ons from standard ones. Currently only standard and birthday. Reserved so other occasion types can be added later without schema changes.
Commission Inheritance:
The rule for setting commission on a new add-on (or package) when the restaurant has no explicit override: take the rate from the restaurant’s most recent active, non-partner-created package of any type; if none exists, fall back to a flat 12.50%. Already implemented for package creation; birthday add-ons plug into the same inheritance.
Avoid: “default commission” (the 12.50% is a fallback, not a default override).
🧠 Design Decisions
| Topic | Decision | Why |
|---|---|---|
| Data model | Reuse existing add_ons table; add enum column add_on_type (default 'standard', value 'birthday'); filter admin list with WHERE add_on_type = 'standard'. | Every behavior in the PRD (pricing, image, limits, cart, checkout, reporting) already has a working slot in the existing table — a parallel table would duplicate those slots and force every read path to UNION two shapes. The enum also leaves room for future occasion types (anniversary, business dinner) without another schema change. |
| Admin visibility | Birthday add-ons invisible to admin by default, no toggle, no separate tab. | PRD: “they should not appear in the regular add-on list in the admin dashboard.” Strict global filter matches the spec; a toggle adds accidental-edit risk without operational benefit. |
| Group login restriction scope | Selection blocked for group login accounts; creation and editing are allowed for the authenticated active restaurant context (default_restaurant), rather than trusting a request-body restaurant_id. | Selection is blocked because a multi-outlet staff member should not attach a birthday add-on while creating a booking on behalf of a customer. Creation and editing are per-restaurant actions, so group-login staff can manage each outlet separately. |
| Commission | Reuse existing package-creation inheritance: restaurant-level override → most recent active non-partner-created package (any type) → 12.50% fallback. No new “birthday default” or “free treat” carve-out. | Already implemented for packages. Reusing keeps commission predictable across surfaces and avoids drift that would force every commission report to reason about two inheritance paths. Edge case worth flagging: a brand-new restaurant with no pinned rate and no legacy staff-created package lands on the 12.50% fallback when creating their first self-service birthday add-on — needs explicit confirmation before launch. |
🎯 User Stories & Functional Requirements
Flow 1: Spotting a Birthday-Friendly Restaurant (Discovery Icon)
| Field | Detail |
|---|---|
| Trigger | A restaurant card appears anywhere diners browse (homepage, search, category pages, bite-lists). |
| Pre-conditions | Restaurant currently has ≥1 live, visible birthday add-on (paid or free). |
| What happens | Backend exposes has_birthday_add_on (and qualifying add-on(s)) through restaurant API response. Frontend shows a small icon on the restaurant card. Dynamic per add-on (no longer a fixed cake graphic) — uses the add-on’s own icon/cover image. Multiple icons can show if multiple birthday add-ons exist. Icon goes into all four restaurant-card components: homepage sliders, search results, group landing pages, bite-list. Tap icon or card → restaurant page scrolled to Birthday Celebration section. |
| What happens after | Diner lands on restaurant page, already scrolled to birthday section. |
When exactly should the icon show?
| Situation | Icon shown? | Why |
|---|---|---|
| Has ≥1 active paid birthday add-on | ✅ Yes, using that add-on’s icon | Normal case |
| Has only free (฿0) birthday add-on active | ✅ Yes | Restaurant still gets discovery credit |
| Has multiple birthday add-ons active | ✅ Yes, multiple icons | Layout still needs design pass |
| Had one, just turned it off, listing stale | ⚠️ Might briefly show | Restaurant page checks live, no broken purchase |
| Add-on is in preview/staff-only mode | ❌ No | Not meant to be public yet |
| Add-on is inside a blackout date | ❌ No | Icon must reflect “can book this today” |
| Restaurant tagged “Birthday” for landing page, but no actual add-on | ❌ No | Two systems intentionally separate |
Deep link
Our app is a wrapper around web pages (universal links), so one web approach covers web, mobile web, and app.
Link shape: web.hungryhub.com/{lang}/restaurants/{restaurant_slug}?section=birthday-celebration
Restaurant page already scrolls to specific sections (used for on-page tabs). What’s new: read section value from URL on page load, trigger scroll once birthday section has actually rendered (it may load slightly later than rest of page). If section not found: wait a few seconds before giving up; if genuinely gone, land normally at top; if URL value broken, ignore silently.
Flow 2: Browsing and Picking a Paid Birthday Add-On on the Restaurant Page
| Field | Detail |
|---|---|
| Trigger | Diner opens/scrolls restaurant page; restaurant has ≥1 active paid birthday add-on. |
| What happens | Fixed section “Birthday Celebration” with hardcoded title + description (same for every restaurant, not editable by anyone). Paid birthday add-on(s) show as cards reusing existing add-on card style. Free (฿0) add-ons never appear here (see Flow 6). Diner can pick exactly one birthday add-on, quantity one; picking a different one swaps the selection instantly. Checkout is the real enforcement (Flow 5). |
| What happens after | Selected add-on shows in cart. |
| Watch for | If restaurant only has free add-ons (no paid), this section doesn’t render — but the icon (Flow 1) can still show. If every paid birthday add-on is sold out / hidden, section doesn’t render (consistent with other add-ons). Swapping add-on when one already in cart: quick confirmation message. |
Flow 3: Viewing an Add-On’s Details
Reuses existing add-on detail page (cover image, description, what’s included, refund policy). Nothing birthday-specific here.
Flow 4: Adding to Cart
Cart itself needs nothing new. What’s new: exactly one birthday add-on, quantity one, can sit in cart at a time. Client-side convenience logic with checkout as the real gate (Flow 5). This rule applies only to birthday add-ons; other regular add-ons unaffected.
Watch for: existing rule that blocks mixing add-ons with certain ticketed packages still works correctly once birthday add-ons are in the mix.
Flow 5: Checkout & Confirming the Booking
| Field | Detail |
|---|---|
| What happens | Checkout itself needs little new — payment system handles ฿0 total fine. Checkout (not the app UI) is the authoritative gate for “one birthday add-on per booking” — app’s instant swap is convenience, not the gate. Vouchers apply to birthday add-ons same as any other add-on (no special exclusion). They sit outside the separate demand-based pricing system (real edge case). Free add-on shows as a literal “฿0” line in order summary (numeric breakdown, not “Free” word). |
| Watch for | Zero-price bug in existing pricing code — ฿0 currently treated as “not calculated yet”, fallback substituted in. Lands on zero today by coincidence; needs a real test before launch. Also: ฿0 alongside a real charge (deposit) shouldn’t confuse payment flow. If free-item daily limit already hit at checkout: clearly block + say so. |
Flow 6: Selecting Birthday as the Occasion Unlocks the Free Add-On
| Field | Detail |
|---|---|
| Trigger | Diner picks “Birthday” from the existing occasion selector at checkout. |
| What happens | Free add-on never appears on store page or cart; only becomes selectable here, at checkout, after the occasion is picked. No extra validation, no minimum spend, no separate confirmation step. If restaurant has one, it’s offered right there; diner adds or skips. |
| What happens after | If added, shows in order summary as ฿0 line, on confirmation as a normal add-on. |
| Watch for | Only one free add-on per booking (same rule as paid). If restaurant’s free daily limit already hit, it shouldn’t be offered at all (rather than offered then failing). |
Flow 7: Booking Confirmation & Reporting
A birthday add-on is treated exactly like any other add-on everywhere in reporting, booking list, booking detail, exports, and analytics — both admin dashboard and partner portal. No new design, no special highlighting, no separate section. Deliberate call, not oversight.
Free item reads as “Free,” not “฿0.00” or blank, in confirmation screen and email.
Flow 8: Creating & Editing a Birthday Add-On (Partner Portal)
This is genuinely new: the partner portal’s current add-on screen is read-only (list only, no create/edit form). This is a real screen to design and build from scratch.
| Field | Detail |
|---|---|
| What happens | Birthday add-ons created only through this partner-portal screen — no path through internal admin dashboard, and they don’t appear in the standard admin add-on catalog list. Likely approach: keep using the same underlying add_ons table (see 🧠 Design Decisions above), mark them with add_on_type = 'birthday', filtered out of admin’s list view. Restaurant sets own price; ฿0 = free (Flow 6), anything above = normal in Birthday Celebration section (Flow 2). |
| UX detail | See the navigation hub for the BE integration doc that defines the form fields, validation rules, image upload specs, and API contracts. |
| BE detail | See the hh-server implementation plan for data model, admin catalog filter, and commission inheritance. |
Flow 9: Picking a Birthday Add-On When Staff Manually Create / Edit a Booking (Partner Portal)
Different screen from the diner-facing store page and cart. Partner portal’s own booking create/edit tool has its own add-on selection component. Same core rule applies here: once staff pick one birthday add-on, the other birthday add-on(s) in the list become inactive / unselectable, so staff can’t attach two to the same booking. Non-birthday add-ons in the list are unaffected.
Watch for: needs its own implementation, separate from the diner-facing cart logic in Flow 4 — different screen with its own add-on selection component.
UX detail: see the navigation hub for the BE integration doc that defines the selection-gate behavior.
🧠 Rules
- Exactly one birthday add-on per booking, quantity one. Applies regardless of how many birthday add-ons a restaurant has. Other add-ons unaffected. App swaps instantly for feel; checkout is the authoritative gate (matters for stale sessions, multiple tabs, direct API hits).
- A restaurant can have at most one free (฿0) birthday add-on configured at any time. The partner-portal list page shows “Free Birthday Add-On (Max 1)” and the server enforces the cap at creation time.
- A ฿0 add-on behaves differently, on purpose. Hidden from store page and cart. Selectable only after diner picks Birthday occasion at checkout (Flow 6). No extra validation beyond that.
- Free item capped at one per booking. Restaurant sets own daily limit on how many they’ll give away.
- No verification of actual birthday. Self-declared, same as any other add-on or occasion.
- Vouchers work the same as any other add-on — no special exclusion. Treated as a standard add-on everywhere (Flow 7). Still sits outside the separate demand-based pricing system (real edge case).
- Created only through partner portal, not admin. Restaurants set these up themselves. Not in the regular admin add-on list.
- Commission follows package-creation inheritance — see 🧠 Design Decisions above.
- Section title and description are fixed — no setting anywhere, hardcoded in app.
- Discovery icon is dynamic — pulled from whichever birthday add-on(s) restaurant has, not one universal graphic. Multiple icons if multiple add-ons.
- Available all day, every day it’s active. No opening-hours / time-of-day schedule. On or off for whatever active date range restaurant sets, no intraday scheduling.
- Group-login accounts may create and edit birthday add-ons per restaurant, but cannot select them while creating or editing a booking on a customer’s behalf.
Why the free item works the way it does
The restaurant’s gesture, not HH subsidizing. Restaurant sets up ฿0 add-on = their own choice, their own cost to bear (like comping something today without our involvement). Because it’s the restaurant’s cost, they’re the natural check on how far it goes — they set own daily limit, can turn off any time. Gating behind “diner already told us it’s a birthday booking” naturally limits to people in a birthday-booking moment, not opportunistic grabs. If abused post-launch: tighten the daily limit, don’t rethink the approach.
📊 How We’ll Know If This Worked
~10M users/year, ~20k daily app users — goals sized for v1.
| Metric | Goal | What it tells us | How we’d check | Status |
|---|---|---|---|---|
| Birthday attach rate (main one) | ≥1% of eligible bookings include a birthday add-on within 60 days of launch | Whether this actually converts, not just getting looked at | Bookings with birthday add-on ÷ total bookings at restaurants that have ≥1 active | Not started |
| Restaurant self-service adoption (new) | 150+ restaurants create ≥1 birthday add-on themselves by launch, with low support-ticket rate for doing so | Whether the new partner-portal screen is actually usable without hand-holding | Count of restaurant-created birthday add-ons; support tickets tagged to this flow | Not started |
| Icon effectiveness | ≥3% of people who see the icon go on to view the section | Whether discovery icon actually drives people somewhere | See event names below | Not started |
| Is this actually new revenue? | Birthday-add-on revenue at participating restaurants should be additional, not just diners swapping one add-on for another | Attach rate alone can’t tell us if someone bought a birthday cake instead of a mocktail they’d have bought anyway | Compare each restaurant’s total add-on revenue before and after | Not started |
| Free item usage | No target yet, just watching | Understand actual usage before setting limits/goals | Count of free redemptions via checkout-occasion path | Not started |
| Are we cannibalizing other add-ons? | Existing add-on attach rate shouldn’t drop >10% at restaurants that add a birthday add-on | Making sure this grows the pie instead of just moving revenue around | Compare non-birthday add-on attach rate before/after | Not started |
| Checkout reliability | <0.5% of birthday checkouts fail | Directly watching for the zero-price bug | Payment error logs, filtered to birthday bookings | Not started |
| Refunds | No worse than normal add-on refund rate | Confirms pricing / expectations are clear | Refunded birthday bookings ÷ total sold | Not started |
Events we’ll track
| Event | Fires when |
|---|---|
birthday_icon_impression | Discovery icon is actually visible on a restaurant card |
birthday_icon_click | Someone taps the icon specifically |
birthday_section_view | Birthday Celebration section becomes visible on restaurant page |
birthday_add_to_cart | Someone adds a paid birthday add-on to cart |
birthday_occasion_selected | Diner picks Birthday as occasion at checkout |
birthday_free_addon_added | Free add-on is added after Birthday occasion selection |
birthday_checkout_success | Booking with birthday add-on successfully confirmed |
birthday_addon_created | Restaurant successfully creates birthday add-on in partner portal |
These names must be distinct from anything Occasion Vault or Celebration Landing Pages use.
⚠️ Risks & What We’re Depending On
| Risk | Type | Owner | Mitigation |
|---|---|---|---|
| Partner-portal creation screen is genuinely new, not a small tweak | Delivery risk, launch-blocking | Frontend + backend eng | This is the foundation. Without it, no restaurant has anything to show. Top delivery priority. |
| Keeping birthday add-ons out of admin catalog list | Technical, needs early decision | Backend eng | Reuse add_ons table with add_on_type enum + global WHERE add_on_type = 'standard' filter — see 🧠 Design Decisions above. Needs to be settled early since it shapes data model. |
| Zero-price bug in existing pricing code | Technical, must fix before launch | Backend eng | Write a real test covering the free item specifically. |
| Existing bug: “Add” button on add-ons doesn’t update after adding to cart | Pre-existing, not yet started | Frontend eng | Birthday add-ons run through this exact flow. Review before launch. |
| Existing bug: “Add” button redirects to add-on detail page unexpectedly | Pre-existing, not yet started | Frontend eng | Same reasoning — review before relying on detail-page flow. |
| Everything at once (icon + paid + free + partner-portal creation + 3 markets) | Delivery risk | PM + eng leads | Known, accepted risk. |
| Commission edge case for restaurant’s first-ever add-on | Minor, worth confirming | Backend eng + PM | Existing commission logic skips other partner-created packages, so a restaurant with no legacy staff-created package lands on 12.50% fallback by default. Worth confirming intended outcome. |
What has to be done before launch (v1 scope — see 📦 v1 Scope (Revised))
| Item | Owner | Needed by | Status |
|---|---|---|---|
| Confirm 12.50%-fallback edge case is intended for restaurant’s first add-on | PM + Backend eng | Start of Sprint 1 | Not started |
hh-server — add_ons.add_on_type enum migration + global admin catalog filter | Backend eng | End of Sprint 1 | Not started |
| Decision made on how birthday add-ons stay out of admin catalog list | Backend eng + PM | Start of Sprint 1 | Resolved via see 🧠 Design Decisions above |
| hh-server — Commission inheritance: confirm it reuses package-creation logic, write spec test for birthday add-on | Backend eng | End of Sprint 1 | Not started |
| hh-server — Zero-price bug has a real passing test covering ฿0 birthday add-on | Backend eng | End of Sprint 1 | Not started |
| hh-server — Birthday add-ons confirmed excluded from any restaurant’s demand-based / dynamic pricing rules | Ops + Backend eng | End of Sprint 1 | Not started |
hh-server — Feature flag ff_birthday_add_on_v1 wired (default off) | Backend eng | End of Sprint 1 | Not started |
| book-bite — Partner-portal Create/Edit Add-On screen built with Flow 8 validation rules (cover 420×240/10MB, menu 2048×2048/10MB, at least one “Cake” item required, price zero-or-positive, time-in-advance default to restaurant-level, repeatable items list with minimum 1 row and removable when > 1) | Frontend + backend eng | End of Sprint 1 | Not started |
| book-bite — Delete-with-confirmation built for add-on list | Frontend eng | End of Sprint 1 | Not started |
| book-bite — Mutual-exclusivity built in staff-side create/edit booking screen (Flow 9) | Frontend eng | End of Sprint 1 | Not started |
| hh-server + book-bite — Group-login restriction enforced (selection blocked; create/edit allowed per restaurant) | Backend + frontend eng | End of Sprint 1 | Not started |
| 150+ restaurants have created ≥1 birthday add-on themselves | Ops / Account Management | Aug 17 | Not started |
| All copy translated for TH/SG/MY (only copy used in Flow 8 + Flow 9 + confirmation-style placeholder text needs translation now; defer diner-side copy until hh-pegasus in scope) | Content / Localization | Aug 7 | Not started |
Implementation-status note (August 6, 2026): the table above is the original launch-planning snapshot and its historical
Not startedvalues are not a current code-status board. The current backend/API verification is recorded in the HHServer KB contract section (§29). Backend staging verification is green for the focused birthday, booking add-on, existing add-on, pricing, and reservation specs. Native birthday error translations and explicit zero-price/dynamic-pricing regression coverage remain launch-preparation items; diner-facing Pegasus flows remain intentionally deferred under the v1 scope decision.
Items moved to v2 (gated on hh-pegasus access)
| Item | Was | Why deferred |
|---|---|---|
| Occasion-selection-unlocks-free-add-on flow (Flow 6) | Backend + frontend | Free item selectable only at checkout; checkout is in pegasus |
| Both existing cart bugs review | Frontend | Cart swap lives in pegasus |
| Discovery icon on 4 restaurant-card components (Flow 1) | Frontend (pegasus) | Icon lives on pegasus |
| Birthday Celebration section + deep link (Flow 2) | Frontend (pegasus) | Store-page section lives on pegasus |
| Cart swap one-per-booking (Flow 4) | Frontend (pegasus) | Cart lives on pegasus |
| Checkout authority for one-per-booking (Flow 5) | Backend + frontend (pegasus) | Checkout lives in pegasus |
| Confirmation line item (Flow 7) | Frontend (pegasus) | Confirmation screen in pegasus |
birthday_addon_created event is measured | Backend + frontend | Now in v1 (fires from book-bite) |
Frontend birthday_* events (7 events) | Frontend (pegasus) | Icon-click / cart / checkout events all pegasus-side |
🚀 Getting This Live
On the trade-offs we considered
| Suggestion | Our call | Why |
|---|---|---|
| Launch just the store-page section first, add icon + free item later | Keep everything together (icon can slip if genuinely needed) | Stronger launch story, accepted coordination risk |
| Hold off on free item until we have paid-tier data | Keep it in from day one | Want the full pitch ready for restaurant conversations and launch |
| Launch Thailand first, add SG and MY later | Launch all three together | Pricing risk is lower now that restaurants set their own price, but translations and content still need to be ready across all three |
Rollout plan
- Feature flag:
ff_birthday_add_on_v1, turned on restaurant-by-restaurant so we can ramp gradually. - Internal testing (week of July 20): our own team creates test birthday add-ons through the partner portal and runs the full flow end-to-end, including the pricing test.
- Small rollout (Aug 3–7): partner-portal screen opens to a small group of restaurants across all three markets — catch usability issues in the creation flow early. Matters more than before since restaurants are now doing this themselves.
- Wider rollout (Aug 10–14): opens up to more restaurants; icon goes live on listing surfaces once there’s enough real inventory to show.
- Full launch (Aug 17–20): everyone, targeting August 20.
What can / can’t slip
The partner-portal creation screen can’t slip — it’s the foundation everything else sits on. The discovery icon is the one piece that could launch a week or two late if needed, without blocking the core experience.
🔍 Still Open
| Question | Where we landed |
|---|---|
| Can restaurants configure this themselves? | Resolved: yes, but only through partner portal, not admin. New engineering. |
| What commission applies to a birthday add-on? | Resolved: no new decision — follows package-creation inheritance. See 🧠 Design Decisions above. |
| What exactly are the image upload rules? | Resolved (Flow 8 table). |
| How should multiple discovery icons lay out when restaurant has >1 birthday add-on? | Still open: needs design pass. |
| What happens when restaurant card would show both the icon and an existing “Ad” label? | Still open: needs design call. |
| Does the free add-on need any diner-facing eligibility check beyond selecting Birthday occasion? | Resolved: no. |
| Should restaurant-facing reporting / exports treat this specially? | Resolved: no, regular add-on everywhere in reporting. |
| Is this truly a 3-market launch? | Yes: pricing risk lower now, but translations/content still need to be ready across all three by launch. |
| Should the group-login restriction also apply to selection? | Resolved: selection is blocked for group-login accounts; creation and editing are allowed per restaurant. |
📋 What We’re Not Doing (For Now)
- Letting diners filter / search by “has a birthday add-on” — touches search infrastructure, a bigger separate project.
- Letting restaurants configure this through the admin dashboard, or letting HH staff create these on a restaurant’s behalf — partner portal is the only path, by design.
- Giving these add-ons any special, more prominent treatment in restaurant-facing booking lists, reports, or exports — deliberately regular add-on everywhere in reporting.
- Verifying anyone’s actual birthday, or sending reminders about it — separate Occasion Vault work.
- A big new homepage entry point beyond the restaurant-card icon — no dedicated birthday landing tab in this phase.
- Any diner-facing eligibility check or minimum spend to unlock the free add-on beyond picking Birthday as the occasion.
- A settings screen for editing the Birthday Celebration section’s title / description — fixed copy, not configurable by anyone.
❓ FAQ
(placeholder — populate post-launch from support tickets / partner feedback)