ReviewOS

stacks/lastsoulultra

Sport select renders empty: the profile form and the API use different sport vocabularies

#26
Open glennmichael123 opened this 3 days ago ·
3 days ago

The Sport field on the Athlete Profile renders completely empty, where Gender shows its value and Fitness Level shows its "Select..." placeholder. The cause is that the profile form and the API disagree about what a sport is called, and nothing reconciles them.

Observed on training.lsu.stacksjs.com/profile, signed in. The code is in hq.training, resources/views/profile.stx.

The select has no option selected at all

Read off the live DOM:

selectvalueselectedIndexrenders
Sport""-1nothing
Gender"male"1Male
Fitness Level""0Select...

selectedIndex: -1 is the tell. Fitness Level also has an empty value, and it correctly lands on the placeholder at index 0. Sport does not land anywhere, so the browser draws an empty box. The markup is not the difference: both carry the identical <option value="">profile.selectDots</option>.

Two different vocabularies

The profile form offers (profile.stx:1366):

"", Running, Cycling, Triathlon, Swimming, Trail Running, Other

The plan builder uses a different set, and its comment says where that set comes from (components/plan-builder.stx:33):

// The sports as the API takes them, and as the buttons say them.
const PB_SPORT_NAMES = { Run: ..., Bike: ..., Swim: ..., Strength: ..., Mobility: ..., Hike: ..., Row: ..., Ski: ... }

Run is not Running. Bike is not Cycling. The two lists do not share a single value.

And the profile form assigns the API's value straight into the select with no mapping and no validation (profile.stx:522):

sport.set(a.sport || "")

So any athlete whose stored sport came from the API vocabulary gets a value matching none of the options, the selection is cleared, and the field renders blank.

It may also quietly erase the stored value

The same state is submitted back on save (profile.stx:584):

body.sport = sport()

With nothing selected, sport() is "". So opening this form and saving it for an unrelated reason, changing weight or height say, would appear to send an empty sport. Worth confirming against what the API does with an empty string before assuming it clears the record, but the shape is there.

What it should do

Pick one vocabulary and convert at the boundary. Either the form offers the values the API stores, or it maps between the two in both directions, at profile.stx:522 on the way in and :584 on the way out. Whichever way, an incoming value that matches no option should fall back to the placeholder rather than leaving the select unselected, and should not be written back as an empty string.

Smaller, same block

Every <label> in this form is a plain <label> with no for, and the selects have no id, so the visible labels are not programmatically associated with their controls. The aria-label on each select means screen readers still get a name, so this is tidiness rather than a defect, but for/id would make the labels clickable too.

Sign in to comment on this issue.