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

Partner Cover Image Self-Serve

History

DateChangesLinkBy
Sep 09, 2026Initial KB doc created from ClickUp PRDClickUp Task 86d3uhmk7 / ClickUp PRD 8ca1fpw-69496Engineering
Sep 09, 2026Decisions resolved, glossary + ADRs writtenThis docEngineering
Sep 09, 2026Figma reviewed — design covers cover-image flows only; menu-item image edit (Flow 2) has no design yetFigma — Add: Package Image Self-ServiceEngineering

About

ContextPartner Portal — package/menu image management improvement
Work TypeImprovement (two new instant image-edit endpoints + a permission fix + informational badge, all on existing package infrastructure)
PurposeOperational Efficiency + Usability — partners can swap package cover images and menu-item photos instantly, without Admin review or risk of destroying unrelated menu items. Partners see which packages Admin created.
ProblemPartners can’t reliably fix a package’s or menu item’s photo without hitting a review queue, risking an unrelated menu item getting silently deleted, or being told to go ask Admin. They have no way to tell which packages they’re even allowed to touch beyond a photo.
SolutionTwo purpose-built, instant, creator-agnostic image-edit endpoints (cover image + single menu-item photo) that sit outside the review queue and ownership check. Plus a “Created by Admin” informational badge on package cards, and a per-restaurant Flipper flag (:partner_full_package_control) for Admin to test lifting the toggle/edit gate on specific restaurants.
Judge byNorth star: 50% reduction in Admin manual image-update requests within 60 days of launch. Adoption: 20% of active partner restaurants use either endpoint within 90 days.
SprintTBD (2–4 weeks estimated, complex feature)
PlatformApp (Partner Portal / hungryhubpartner, backed by api/partner/v1)
Design LinkFigma — Add: Package Image Self-Service (covers package cover image flows only — menu-item image edit has no design yet)
Related DocsPackage Creation (existing self-serve flow), Add-On System

Glossary

Vocabulary for terms unique to this PRD. Same terms must be used across hh-server, book-bite (partner portal), and analytics — distinct, consistent naming is the whole point.

Cover Image:

The primary visual for a package, shown on package cards and detail screens. Stored on the HhPackage::Package::* subtype record. Currently has no dedicated self-serve edit path.

Avoid: “thumbnail”, “banner”.

Menu Item Image:

A photo attached to a single HhPackage::PackageMenu record within a package’s menu section. Distinguished from the package’s own cover image.

Avoid: “menu photo” (ambiguous — could mean the whole menu), “dish image”.

Created by Admin:

An informational label indicating a package was set up by Hungry Hub Admin (not the partner). Computed via !HhPackage::PackageSubmission.package_created_by_partner?(package). Affects toggle/edit permissions, but not image-edit permissions.

Avoid: “admin package”, “HH-owned”.

Creator Ownership (package_created_by_partner?):

The existing check that determines whether a partner has full toggle and non-image-field-edit control over a package. Used in UpdatePackageService and the change_active endpoint.

Avoid: “ownership check” (too vague — restaurant ownership is a separate gate).

Restaurant Ownership:

The multi-tenant isolation check: does this package/menu item belong to a restaurant this partner manages? Applies to everything in this feature, non-negotiable.

Avoid: “partner ownership” (the partner doesn’t own the package, the restaurant does).

Per-Restaurant Flipper Override (:partner_full_package_control):

A restaurant-scoped Flipper flag that, when enabled, bypasses the creator-ownership gate for a specific restaurant. Used for testing. Does not affect restaurant ownership or image-edit permissions (which are already unrestricted).

Avoid: “override flag”, “test flag” (too vague).

Instant Apply:

The behavior of the new image-edit endpoints: changes take effect immediately, no review queue, no draft state, no Admin approval required. Contrast with the existing UpdatePackageService which queues through PackageSubmission.

Destroy-by-Omission Risk:

The existing bug in the whole-package edit flow (package_menus_attributes): any existing menu item not included in the submitted array gets automatically destroyed. The new single-item menu-photo endpoint (Flow 2) is safe by construction because it touches exactly one record.

Design Decisions

TopicDecisionWhy
Image-edit endpoints are instant, no reviewCover image and menu-item image edits apply immediately, bypass PackageSubmission/PackageDraftVersion entirely.The review queue exists to catch pricing/schedule/name changes that affect customers. A photo swap doesn’t. Adding review to photo changes would eliminate the efficiency gain this feature is supposed to deliver.
Image-edit endpoints are creator-agnosticNo package_created_by_partner? check on Flows 1–2. The only gate is restaurant ownership.A partner who manages a restaurant should be able to fix any package’s photo regardless of who created it. The ownership gate on toggle/edit-other-fields stays because those affect customer-facing behavior (package availability, pricing). Photos don’t.
Two separate edit mechanisms kept intentionallyThe pre-existing whole-package package_menus_attributes path (reviewed, partner-created-only, whole-array resubmit) stays. The new single-item endpoint is the instant, safe path.Merging them would re-introduce the destroy-by-omission risk. The safety property of Flow 2 (touches exactly one record) depends on staying separate from the array-based nested-attribute flow.
“Created by Admin” badge is informational onlyBadge renders on package card/detail. Does not control any behavior; toggle/edit controls are driven by backend can_toggle/can_edit_other_fields booleans, not the badge.Prevents the badge and actual permissions from drifting out of sync when the Flipper override is toggled.
Two independent permission gates kept separateRestaurant ownership (multi-tenant isolation) vs. creator ownership (toggle/edit-other-fields). Image edits only check restaurant ownership.Avoids conflating “can I fix a photo?” with “can I toggle this package off?” — they have different risk profiles and should not share a gate.
Rate limit per package/menu itemSoft per-record rate limit of 10 image changes per rolling 24h.Prevents accidental loops or abuse without being restrictive enough to block normal use.
Cover image is optional & removablecover_image can be empty; the design labels it “Cover Image for Marketing (Optional)” and includes a “Remove image” flow with a confirm modal. After removal, the package falls back to a placeholder and the row action returns to “Add Cover Image”.A mandatory cover image would force partners into the review queue for packages that don’t need one. Removal must also be instant + audited, same as a change.

🎨 Design Notes (from Figma)

Source: Figma — Add: Package Image Self-Service. Reviewed Sep 09, 2026.

What the design covers: package cover image flows only (list row actions + modal). Menu-item image edit (PRD Flow 2) has no design yet — flag for a follow-up screen.

AspectDesign
Entry pointRow action on the Main Package list table: “Edit Cover Image” (image present) / “Add Cover Image” (no image). Rows in Pending Review / Rejected status show no image action.
Modal“Edit Cover Image” / “Add Cover Image” title; field label “Cover Image for Marketing (Optional)”; dropzone “Drop image here or click to upload”; when an image is present: “Change Image” overlay + “Remove image” link; helper text “Recommended dimensions: 420x240px (Max size: 10 MB)”; buttons Cancel / Save (Save drawn disabled/gray until an image is selected — confirm).
Error statesInline red text in the modal: “File too large (Max 10MB). Please upload a smaller image.” / “Unsupported format. Please upload a supported image (JPEG, JPG, PNG, HEIC, TIFF, WEBP).” / “Upload failed. Please check your file or network, then try again.”
SuccessToast: “Great success!” / “Your cover image is updated.”
Remove flow“Remove image” → confirm modal “Are you sure you want to remove this cover image?” → Back / Remove (red gradient).
“Created by Admin”Small badge on the package row; hover tooltip: “This package was set up by the Hungry Hub team. You can still update its photo — turning it on/off or changing its other details isn’t available here. Contact your Account Manager if you need those changed.”
Design wobblesDelete-confirm modal reuses a “Reject this booking” component (copy-paste, cosmetic). “change Image” is camelCased in the mock (should be “Change Image”). Save disabled state drawn on 4 of 7 modal copies — confirm intended.

PRD-vs-design deltas to confirm before build:

  • PRD text says “Edit Cover Image” lives on the package detail screen; design shows it as a list row action. Likely both are fine, but design only covers the list entry point — confirm whether the detail screen also carries the affordance.
  • PRD Flow 2 (menu-item photo edit) has no design — needs a follow-up screen.
  • Design marks cover image as “(Optional)” and adds a “Remove image” flow — consistent with an optional, replaceable field.
  • Design hides the image action on Pending Review / Rejected rows — PRD didn’t state this; confirm whether the backend endpoint should also enforce a status gate or whether it’s UI-only.

User Stories & Functional Requirements

Flow 1: Partner Changes a Package’s Cover Image

FieldDetail
TriggerPartner taps “Edit Cover Image” (or “Add Cover Image” if the package has no image yet) on the package’s row in the Main Package list, or on the package’s detail screen (design shows list row; detail-screen placement to confirm — see 🎨 Design Notes).
Pre-conditionsPartner is logged in; the package belongs to a restaurant the partner manages (the only check that applies); the package is not soft-deleted; package status is Active or Inactive (design shows no image action on Pending Review / Rejected rows — confirm whether status gate is UI-only). Works identically for every package type (AYCE, Party Pack, Xperience, DIY Set, Set Menu, Hungry Set, Hungry at Home, Buffet Extra, Add-On) and regardless of who created it.
What happens1. “Add/Edit Cover Image” modal opens: field label “Cover Image for Marketing (Optional)”, dropzone “Drop image here or click to upload”, helper “Recommended dimensions: 420x240px (Max size: 10 MB)”. 2. Partner selects a new image from device gallery/camera. When an image is already present, the modal shows “Change Image” over the image + a “Remove image” link. 3. Client-side validation: file ≤ 10MB, format in {jpeg, jpg, png, heic, tif, tiff, webp}, reusing Restaurants::Picture’s existing constants/messages. 4. New narrow endpoint updates cover_image directly on the relevant HhPackage::Package::* subtype; does not route through PartnerPackages::UpdatePackageService or touch PackageSubmission/PackageDraftVersion. 5. Writes a lightweight audit record so the change is traceable without a review step. 6. No pending-review state, no creator check, live immediately. Save button disabled until an image is selected (per design).
Remove flow“Remove image” → confirm modal “Are you sure you want to remove this cover image?” → Back / Remove. Remove clears cover_image; package returns to placeholder state; row action label returns to “Add Cover Image”. Audit entry recorded. Removal is instant + audited, same as a change.
What happens aftercover_image is updated; success toast “Great success! / Your cover image is updated.”; propagates through existing TouchWorker/search-sync paths the same way an Admin-side cover image change would. Audit entry recorded.
Watch forPackage belongs to a different restaurant → reject. Package was Admin-created → allowed anyway (“Created by Admin” badge may still show — informational only). Package has a pending edit_request submission in the old review flow → cover image change still applies instantly; pending draft’s stale cover_image snapshot could overwrite the newer image if/when that unrelated submission is later approved (flag for Eng). Upload fails → keep previous image, show retry. Unsupported format / file too large → block with inline error. Package toggled off or non-toggleable → cover image edit still works regardless.

Flow 2: Partner Changes or Inserts a Single Menu Item’s Photo

FieldDetail
TriggerPartner taps “Edit” on an existing menu item’s photo within a package’s Menu tab.
Pre-conditionsPartner is logged in; the menu item (HhPackage::PackageMenu) belongs to a package belonging to a restaurant the partner manages; the menu item is not soft-deleted. Applies wherever a package actually has menu-item records (packages using menu_type: hh_menu, img, or a combo). Packages using menu_type: imenupro typically have no package_menus content to edit (shows empty state, not a permission block).
What happens1. Implement the currently-missing PATCH/PUT /api/partner/v1/package_menus/:id action (route exists, controller action doesn’t). Scoped strictly to the image field. 2. Verify the menu item’s package belongs to default_restaurant. 3. No creator/package_created_by_partner? check — works whether the parent package or the item was set up by Admin or partner. 4. Client-side + server-side validation uses HhPackage::PackageMenuUploader’s actual allowed formats: {jpg, jpeg, png, pdf, heic} (differs from restaurant-photo uploader: additionally allows PDF, doesn’t allow tif/tiff/webp). 5. Single-record, single-field update — no destroy-by-omission risk. 6. Applies immediately, no review queue.
What happens afterHhPackage::PackageMenu#image updated; image_menu_url reflects the new photo (or falls back to AdminSetting.default_hh_menu_image if cleared); MenuSectionGeneratorWorker fan-out fires as today.
Watch forMenu item belongs to Admin-created package → allowed. Menu item’s package_id is nil (nested under a Menu Section via hh_menu construct) → resolve restaurant-ownership through the section’s package. Package’s menu is purely imenupro-sourced with zero package_menus records → nothing to show/edit. Menu item was soft-deleted → edit unavailable. Pending whole-package edit request for same item → last-applied-wins; flag for Eng. Menu item belongs to different restaurant → reject. Concurrent edits (partner + Admin) → last write wins for v1; log both in audit trail.

Flow 3: Existing Whole-Package Edit Flow (Not New — Context Only)

FieldDetail
What it is todayPATCH /api/partner/v1/restaurant_packages/:id → PartnerPackages::UpdatePackageService accepts package_menus_attributes as part of a broader package edit. Queues into PackageSubmission/PackageDraftVersion review. Partner-created packages only.
Why it staysBundled edits (e.g. update three menu photos and the schedule in one submission) are a reasonable use case that the new narrow endpoints don’t cover.
Risk to flagassign_existing_package_menu_ids! matches submitted items by index_number. Any existing menu item not in the submitted array gets destroyed. This isn’t new but is a real risk worth a defensive fix.

Flow 4: Partner Toggles Package On/Off, Edits Other Fields (With Per-Restaurant Override)

FieldDetail
TriggerPartner uses the existing on/off toggle or “edit package” flow (Flow 3) on a package.
Pre-conditionsPackage’s restaurant_package exists and is addressable by the partner.
What happens1. POST /api/partner/v1/restaurant_packages/change_active currently has no ownership check — add the same package_created_by_partner? guard used in UpdatePackageService. 2. Partner-created path: unchanged, toggle flips active. 3. Admin-created path (and flag is off): return existing change_active_fail error, don’t show toggle control in UI. 4. Editing other fields continues through Flow 3 review path, which already blocks Admin-created packages. 5. Per-restaurant Flipper flag :partner_full_package_control: when on, that restaurant’s partner can toggle and edit non-image fields on any package under it, Admin-created or not. 6. Expose effective permission from backend (can_toggle, can_edit_other_fields booleans).
Watch forPartners currently toggling Admin-created packages lose that ability by default → behavior change, permission tightening. Flag on: Admin-created packages become toggleable/editable, but badge stays visible. Flag off after being on: already-submitted edit requests still resolve normally. Toggling off a package with active/upcoming bookings → follows existing behavior. Photo edits remain independent of toggle/other-field permissions.

Flow 5: Partner Sees Which Packages Admin Created

FieldDetail
TriggerPartner opens any package detail screen.
Pre-conditionsNone, applies to every package view. Does not apply at menu-item level.
What happens1. Expose created_by_admin on Api::Partner::RestaurantPackageSerializer, computed as !package_created_by_partner?(package). Purely informational. 2. Render “Created by Admin” on the package card/detail screen only when true, never on menu-item cards. 3. Drive toggle and “edit other fields” controls off backend can_toggle/can_edit_other_fields booleans (Flow 4), not directly off created_by_admin, so Flipper override and badge don’t contradict. 4. Cover-image and menu-item-image controls stay enabled regardless.
Watch forA partner-created package’s approval record is later altered by a data fix → package_created_by_partner? must stay single source of truth for both badge and permission booleans. Package types with no self-serve path should never show toggle or “edit other fields” affordance (not even disabled) but should always show photo-edit affordance.

Rules

  • Cover-image and menu-item-image edit controls are always rendered and enabled, everywhere, regardless of created_by_admin or the Flipper flag. The on/off toggle and “edit other fields” controls follow can_toggle/can_edit_other_fields from the backend.
  • Image edits are instant-apply, always, for every package/menu item with restaurant ownership satisfied. No review queue, no creator check.
  • Two edit mechanisms for menu photos, on purpose: the pre-existing whole-package path (reviewed, partner-created-only, whole-array resubmit) stays. The new single-item endpoint is the instant, safe path. Don’t merge them.
  • “Created by Admin” badge is informational only. Does not control any behavior. Toggle/edit controls are driven by backend booleans.
  • Two independent permission gates, don’t conflate them: (1) Restaurant ownership — applies to everything, non-negotiable. (2) Creator ownership — applies only to toggle and non-image-field edits. Image edits only check restaurant ownership.
  • A soft rate limit of 10 image changes per package/menu item per rolling 24h prevents accidental loops.
  • Audit trail without review: image changes bypass admin review, but are logged so Admin/Support can see who changed an image and when.

Why image edits skip the review queue

The review queue exists to catch pricing/schedule/name changes that affect what customers see and pay. A photo swap doesn’t change the booking experience, pricing, or availability. Adding review to photo changes would eliminate the efficiency gain this feature is supposed to deliver, and would leave partners in the same “wait for Admin” state this feature is designed to fix.

Why creator ownership doesn’t gate image edits

A partner who manages a restaurant should be able to fix any package’s photo regardless of who created it. The creator gate on toggle/edit-other-fields exists because those actions affect customer-facing behavior (package availability, pricing, schedule). Photos don’t. If a partner can’t fix a blurry photo on a package Admin created, they’re stuck filing a support ticket — the exact pain this feature eliminates.

How We’ll Know If This Worked

MetricGoalWhat it tells usHow we’d checkStatus
Admin manual image-update requests (North Star)↓ 50% within 60 days of launchWhether partners are self-serving instead of asking AdminSupport/Admin ticket tagging, pre/post launchNot started
Partner-initiated image edits≥ 20% of active partner restaurants use either endpoint within 90 daysWhether the new endpoints are actually usableProduct analytics event on successful image updateNot started
Failed/rejected uploads< 5% of upload attemptsValidation/UX isn’t overly restrictive or confusingUpload attempt vs. success/failure ratioNot started
change_active permission errorsNo sustained spike post-launchNew ownership check doesn’t break legitimate workflowsError rate on change_active endpointNot started
Menu items lost via Flow 30 unintended deletions/monthPre-existing destroy-by-omission risk isn’t actively harming partnersManual audit of PackageMenu deletions correlated with whole-package edit submissionsNot started

Events we’ll track

EventFires when
partner_cover_image_updatedPackage cover image is successfully updated via Flow 1
partner_menu_image_updatedMenu item image is successfully updated via Flow 2
partner_created_by_admin_badge_viewedPartner views a package with the “Created by Admin” badge

These names must be distinct from existing partner-portal analytics events.

Risks & What We’re Depending On

RiskTypeOwnerMitigation
Permission tightening: partners currently toggling Admin-created packages lose that abilityBehavior change, not a bugBackend eng + PMDocument in release notes; flag as intentional tightening. Flipper override available for gradual rollout.
Pending draft clobbers newer instant image editTechnical edge caseBackend engFlag for Eng: if a pending edit_request in the old review flow has a stale cover_image snapshot, confirm it doesn’t overwrite the newer instant edit when approved. Likely needs a conditional skip on the image field during draft application.
Destroy-by-omission risk in Flow 3 remainsPre-existing, not introduced by this PRDBackend engThis PRD’s research surfaced the risk. Worth a defensive fix (e.g. require full current list, or explicit destroy flags) but not blocking for this feature’s launch.
change_active permission change breaks legitimate partner workflowsDelivery riskBackend engFlipper override allows gradual rollout. Monitor change_active error rates post-launch.
App release cadence for Partner Portal UIDelivery coordinationFrontend eng + app teamBackend flags can flip independently; UI needs an app release to reach users. Coordinate timing.

What has to be done before launch

ItemOwnerNeeded byStatus
hh-server — New PATCH/PUT /api/partner/v1/package_menus/:id action (Flow 2, image-field only)Backend engSprint TBDNot started
hh-server — New cover-image endpoint (Flow 1, direct update, bypasses review; supports both set + remove)Backend engSprint TBDNot started
hh-server — Ownership check on change_active endpoint (Flow 4)Backend engSprint TBDNot started
hh-server — can_toggle / can_edit_other_fields booleans on serializerBackend engSprint TBDNot started
hh-server — created_by_admin on Api::Partner::RestaurantPackageSerializerBackend engSprint TBDNot started
hh-server — Audit trail for instant image editsBackend engSprint TBDNot started
hh-server — Rate limit (10 per 24h per package/menu item)Backend engSprint TBDNot started
hh-server — :partner_full_package_control Flipper flag (restaurant-scoped)Backend engSprint TBDNot started
hh-server — ff_partner_cover_image_selfserve_v1 global Flipper flagBackend engSprint TBDNot started
book-bite — Cover image edit UI: “Add/Edit Cover Image” row action on package list (hidden on Pending Review / Rejected rows) + modal (drop/change/remove, validation, errors, success toast)Frontend engSprint TBDNot started
book-bite — Cover image remove confirm modal (“Are you sure you want to remove this cover image?”)Frontend engSprint TBDNot started
book-bite — Menu item image edit UI on package Menu tab (design pending)Frontend engSprint TBDNot started
book-bite — “Created by Admin” badge on package cards/detailFrontend engSprint TBDNot started
book-bite — Toggle / edit-other-fields permission-driven controlsFrontend engSprint TBDNot started
Analytics — partner_cover_image_updated + partner_menu_image_updated eventsFE + BE engSprint TBDNot started
Copy/UX writing — all toast messages, error messages, badge text for TH/SG/MYContent / LocalizationPre-launchNot started

Still Open

QuestionWhere we landed
Figma design linkResolved: Figma — Add: Package Image Self-Service. Covers cover-image flows only.
Menu-item image edit UI (Flow 2)Still open: no Figma design yet — needs a follow-up screen before FE can build it.
Cover image edit entry point: list row action vs detail screenDesign shows list row action only; PRD text mentions detail screen. Confirm whether the detail screen should also carry the affordance.
Status gate on image action (Pending Review / Rejected rows hide it)Design hides the action on those rows; confirm whether the backend endpoint should also enforce a status gate or whether it’s UI-only.
Cover image removal flowDesign adds it; endpoint must support clearing cover_image (optional field). Confirm clear = set to nil + audit record + TouchWorker/search-sync behavior.
Exact admin-side Restaurants::Picture uploader constants to reuse for cover image validationConfirmed reuse; verify with Eng whether to formalize as its own uploader class or keep sharing the constant.
Pending draft clobbers newer instant image edit — what exactly happens?Flagged for Eng to confirm. Likely needs conditional skip on image field during draft application.
Sprint assignmentTBD — PRD says 2–4 weeks, PM to confirm.
Real active partner restaurant count (for adoption target calculation)PM to confirm; PRD uses illustrative ~3,000.
Imenupro-only packages: empty state for menu image editContent-availability state, not a permission block. No PackageMenu content underneath to attach a photo to.

FAQ

(placeholder — populate post-launch from support tickets / partner feedback)