Skip to content

fix(coach): CoachSetup's Save, the demo Coach's failure state, and the intake line - #289

Merged
DuarteSantos8 merged 1 commit into
DuarteSantos8:mainfrom
kurktchiev:gh/coach-setup-demo
Sep 28, 2026
Merged

DuarteSantos8 merged 1 commit into
DuarteSantos8:mainfrom
kurktchiev:gh/coach-setup-demo

Conversation

@kurktchiev

Copy link
Copy Markdown
Contributor

Six defects across the in-app Coach's setup, intake and demo, found by writing
the first tests that render those screens (100 tests across three new files
plus one end-to-end file). Each has a test that fails against the old code.

CoachSetup's Save accepted an endpoint that "List models" had refused.
prepare() checked validateBaseUrl(...).ok; save() took .value off an unchecked
result, so a typo saved baseUrl: null, said "The Coach is on", and pointed the
phone at nothing. Save runs the same check now. And an EMPTY endpoint is
refused for a provider that has no default: validateBaseUrl('') is
{ ok, value: null } by contract, because for anthropic, openai and gemini empty
means "back to the default", but compatible has no default, so both List and
Save were letting you configure the phone to call nowhere. The admin route had
the same hole one layer down and refuses it too, with a test.

The demo Coach had no failure state. A builder that threw inside the 2.2 s
timer left the job 'running' for ever and every later request answered 409
until reload; the one reachable trigger was a workout with no entries, which
buildDebrief guarded twice and then handed to workoutVolume unguarded. start()
catches now and demoStatus() reports lastError in the server's shape, so
CoachChat's existing job-ended effect writes the same error line it writes for
the real Coach. The end-to-end test mounts the real CoachChat against a
throwing workout and reads that line off the thread.

Three more in the demo. demoReview ran the whole wait and answered nothing on
a profile with no two-exercise routine; it refuses up front now, the way
demoDebrief already did with no workout. buildReview picked
reps[1] || routine.ex[1], which is the same entry as first when the only
rep-mode exercise sits at index 1, and applying both changes then aborted the
whole atomic change-set with "missing target". demoRefine built from nothing
and sent every revised plan back to Mon/Wed/Fri; it reads the saved intake
now, as the server's payload does.

CoachIntake wrote a second intake line into the thread whenever a first-timer
retried after a failed plan request, because the one-intake-line rule was
guarded by editing. And a daysPerWeek of 0 saved as 3 where -1 saved as 1,
because || 3 ate the zero before the clamp; ?? 3 lets the clamp turn it
into 1 like every other count below the floor.

Two new strings in all 14 packs.

🤖 Generated with Claude Code

…e intake line

Six defects across the in-app Coach's setup, intake and demo, found by writing
the first tests that render those screens (100 tests across three new files
plus one end-to-end file). Each has a test that fails against the old code.

CoachSetup's Save accepted an endpoint that "List models" had refused.
prepare() checked validateBaseUrl(...).ok; save() took .value off an unchecked
result, so a typo saved baseUrl: null, said "The Coach is on", and pointed the
phone at nothing. Save runs the same check now. And an EMPTY endpoint is
refused for a provider that has no default: validateBaseUrl('') is
{ ok, value: null } by contract, because for anthropic, openai and gemini empty
means "back to the default", but compatible has no default, so both List and
Save were letting you configure the phone to call nowhere. The admin route had
the same hole one layer down and refuses it too, with a test.

The demo Coach had no failure state. A builder that threw inside the 2.2 s
timer left the job 'running' for ever and every later request answered 409
until reload; the one reachable trigger was a workout with no `entries`, which
buildDebrief guarded twice and then handed to workoutVolume unguarded. start()
catches now and demoStatus() reports lastError in the server's shape, so
CoachChat's existing job-ended effect writes the same error line it writes for
the real Coach. The end-to-end test mounts the real CoachChat against a
throwing workout and reads that line off the thread.

Three more in the demo. demoReview ran the whole wait and answered nothing on
a profile with no two-exercise routine; it refuses up front now, the way
demoDebrief already did with no workout. buildReview picked
`reps[1] || routine.ex[1]`, which is the same entry as `first` when the only
rep-mode exercise sits at index 1, and applying both changes then aborted the
whole atomic change-set with "missing target". demoRefine built from nothing
and sent every revised plan back to Mon/Wed/Fri; it reads the saved intake
now, as the server's payload does.

CoachIntake wrote a second intake line into the thread whenever a first-timer
retried after a failed plan request, because the one-intake-line rule was
guarded by `editing`. And a daysPerWeek of 0 saved as 3 where -1 saved as 1,
because `|| 3` ate the zero before the clamp; `?? 3` lets the clamp turn it
into 1 like every other count below the floor.

Two new strings in all 14 packs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@DuarteSantos8
DuarteSantos8 merged commit c5bfe5d into DuarteSantos8:main Sep 28, 2026
4 checks passed
DuarteSantos8 added a commit that referenced this pull request Sep 28, 2026
…le message length (#280) and the refused empty endpoint (#289), and the published page is rebuilt with the session-only Coach config, the renewed phone token and the refused empty state
DuarteSantos8 added a commit that referenced this pull request Sep 28, 2026
… that the phone refuses http:// ones

The #289 flow tests used http://ollama.lan, which the platform batch's cleartext refusal turns down before anything is listed or saved; the placeholder is https://ollama.example.com there too.
@DuarteSantos8

Copy link
Copy Markdown
Owner

Thanks @kurktchiev, all six fixes are in, and the API reference now documents the refused empty endpoint.

Released in v1.3.9: https://github.com/DuarteSantos8/openGym/releases/tag/v1.3.9

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.

2 participants