Skip to content

[cleanup] Point coach avatar editing at the function that exists (closes #22) - #39

Merged
Harrolee merged 1 commit into
mainfrom
cleanup/coach-avatar-edit-contract
Aug 11, 2026
Merged

Harrolee merged 1 commit into
mainfrom
cleanup/coach-avatar-edit-contract

Conversation

@Harrolee

Copy link
Copy Markdown
Owner

Closes #22.

CoachAvatarEdit POSTed {prompt, style, count} to /motivational-images and expected {images: []}. That endpoint is sendMotivationalImages(event, context) — a Cloud Scheduler background job. Not an HTTP handler, takes no such body, returns no such shape. The Generate button could never have worked.

It is not orphaned — step 1 of the issue

App.jsx:130 routes /my-coaches/:coachId/avatar to it and CoachEdit.jsx:384 navigates there. So this is a repoint, not a delete.

Repointing meant changing the interaction, not just the URL

The real generator is PhotoMaker: it restyles a real face and cannot invent one from a description. generateCoachAvatars calls storeSelfie unconditionally, so a photo is required. There is no prompt-only avatar generation anywhere in the backend — the old UI was asking for something that does not exist.

So the Generate tab now takes a photo, and the description becomes optional steering on top of the style's own prompt. The request matches the JSON path the generator implements:

{ coachId, selfie_base64, selfie_mime, style, prompt }  // → { avatars, failedStyles }

Details that fall out of matching the real contract:

  • Style list mirrors AVATAR_STYLES in avatar-generation.js, including the model's own "Disney Charactor" misspelling. The old list (realistic, professional, cartoon, fitness, athletic) matched nothing server-side — and was never rendered anyway. The generator silently falls back to every style when it does not recognise one, so a wrong string is an expensive no-op, not an error.
  • Partial success is surfaced. failedStyles comes back alongside whatever succeeded; a short grid is otherwise unexplained.
  • Errors use the generator's own message rather than a generic toast.
  • VITE_COACH_AVATAR_GENERATOR_URL is honoured, as AvatarUpload already does. Without it this component breaks in any deploy pointing the generator at its own URL.
  • Default prompt removed — "A ... fitness coach" assumed the fitness-era product and overrode the creator's chosen style.

A second phantom endpoint, found via step 4

Checking for other bad callers turned up CoachBuilderContext.jsx:533 POSTing to /coach-avatar-generator/save-avatar — which has never existed either. The generator does not route on path, so that request could only ever be read as a malformed generation request.

It was redundant as well as dead: the insert directly above it already persists avatar_url, avatar_style and original_selfie_url. And it could not fail honestly — fetch does not reject on a 4xx, so its catch almost never ran, and when it did it told the creator "the avatar failed to save" after the insert had already saved it. Removed.

Verification

  • webapp builds clean
  • No caller in webapp/, mobile/src or mobile/app invokes /motivational-images or /save-avatar as an HTTP endpoint any more

Not verified: the generation round trip against a live generator. It needs Replicate and GCS credentials the local stack does not carry, so the contract is matched by reading functions/coach-avatar-generator/index.js rather than by running it. Worth a manual pass on a deploy that has those keys.

Noted, not fixed

coach-avatar-generator has no authorization — CORS is * and no token is checked, so anyone can burn Replicate credits generating avatars for any coachId. Out of scope here; it belongs with the #25 sweep.

🤖 Generated with Claude Code

CoachAvatarEdit POSTed {prompt, style, count} to `/motivational-images` and
expected {images: []}. That endpoint is `sendMotivationalImages(event, context)`
— a Cloud Scheduler background job. It is not an HTTP handler, takes no such
body and returns no such shape, so the Generate button could never have worked.
Closes #22.

The component is NOT orphaned: App.jsx routes /my-coaches/:coachId/avatar to it
and CoachEdit.jsx:384 navigates there, so this is a repoint rather than a
delete.

Repointing meant changing the interaction, not just the URL. The real generator
is PhotoMaker, which restyles a real face and cannot invent one from a
description — `generateCoachAvatars` calls `storeSelfie` unconditionally, so a
photo is required. The UI now asks for one, and the description becomes optional
steering on top of the style's own prompt. The request matches the JSON path the
generator actually implements ({coachId, selfie_base64, selfie_mime, style,
prompt}) and reads {avatars, failedStyles} back.

Details that follow from matching the real contract:

  - The style list mirrors AVATAR_STYLES in avatar-generation.js, including the
    model's own "Disney Charactor" misspelling. The previous list (realistic,
    professional, cartoon, fitness, athletic) matched nothing server-side and
    was never rendered anyway — the generator silently falls back to every
    style when it does not recognise one.
  - Partial success is surfaced. The generator returns `failedStyles` alongside
    whatever succeeded; a short grid is otherwise unexplained.
  - Errors come from the generator's own message rather than a generic toast.
  - The VITE_COACH_AVATAR_GENERATOR_URL override is honoured, as AvatarUpload
    already does. Without it this component would break in any deploy that
    points the generator at its own URL.
  - The default prompt ("A ... fitness coach") is gone. It assumed the
    fitness-era product and overrode the style the creator picked.

Also removes a second phantom endpoint found while checking for other bad
callers: CoachBuilderContext POSTed to `/coach-avatar-generator/save-avatar`,
which has never existed either — the generator does not route on path, so the
request could only ever be read as a malformed generation request. It was
redundant as well as dead, since the insert directly above it already persists
avatar_url, avatar_style and original_selfie_url. It could not even fail
honestly: `fetch` does not reject on a 4xx, so its catch almost never ran, and
when it did it told the creator the avatar had failed to save after the insert
had saved it.

Verified: webapp builds clean; no caller in webapp/, mobile/src or mobile/app
invokes /motivational-images or /save-avatar as an HTTP endpoint any more. The
generation round trip itself is unverified against a live generator — it needs
Replicate and GCS credentials, which the local stack does not carry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Harrolee
Harrolee merged commit e5881c0 into main Aug 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[cleanup] CoachAvatarEdit posts to a /motivational-images contract that never existed

1 participant