Skip to content

Store the units preference on the account, and tell the coach - #12

Merged
aderaaij merged 3 commits into
mainfrom
feat/units-preference
Nov 12, 2026
Merged

aderaaij merged 3 commits into
mainfrom
feat/units-preference

Conversation

@aderaaij

Copy link
Copy Markdown
Owner

Metric / imperial lived in the dashboard's localStorage (#9), so the phone and the coach could not see it. The app kept its own copy, and the coach kept authoring kilometers for a runner who reads miles.

What changes

  • Storage and API. users.units (migration c9d0e1f2a3b4) behind GET/PUT /api/me/units. The value is metric, imperial or null. null means never chosen and is deliberately not the same as metric: the iOS app defaults from the phone's region and has to be able to tell the two apart. Unknown values are a 422.
  • Coach. get_plan_context, the call the coach makes first in every conversation, returns units and a units_hint. For an imperial athlete the hint says to talk in miles, author distance goals in miles with round figures, keep track reps in meters and keep speed alerts in m/s. It lives there and not in the MCP instructions because those are fixed per server, not per athlete.
  • Dashboard. The Settings toggle saves to the account, and the dashboard reads the account's choice whenever a session appears. localStorage stays as a cache so the first paint is in the right units.

Display only, as before: everything stored and sent stays metric.

Rules worth reviewing

  • The server wins once it holds a choice. A choice already sitting in a browser from Add metric/imperial display units to the dashboard #9 is handed to the server once, if the account has none.
  • Sign-out clears the browser's cached choice, so it isn't handed to the next person who signs in on that browser.
  • Unset reads as metric for the dashboard and the coach.

Checked

  • Backend: 272 tests pass, 8 of them new (tests/test_units.py); the migration applies on a fresh database.
  • MCP: its 14 tests pass. None cover the hint text; that is tested on the backend side only.
  • Dashboard, by hand against a local stack: a browser-only "imperial" was carried to the account on first load; the toggle saved to the account; a change made through the API showed up after a reload; the cached choice was gone after the session ended.

Follow-ups, not in this PR

  • iOS app: pull on launch, push on change, and raise its required server version.
  • Demo seed: speed alerts on the interval and tempo sessions.

Metric / imperial lived in the dashboard's localStorage, so the phone
and the coach could not see it: the app kept its own copy, and the
coach kept authoring kilometers for a runner who reads miles.

`users.units` holds it now, behind `GET/PUT /api/me/units`. NULL means
never chosen, which is deliberately not the same as metric: the phone
defaults from the device's region and needs to tell the two apart.

The coach reads it from `get_plan_context`, the call it already makes
first in every conversation, as `units` plus a `units_hint` saying how
to talk and how to author distance goals. It can't go in the MCP's
instructions: those are fixed per server, not per athlete.

Display only. Everything stored and sent stays metric.
The Settings toggle wrote to localStorage only. It now saves to
/api/me/units as well, and the dashboard reads the account's choice
whenever a session appears, so it is the same in every browser and the
same one the phone and the coach see.

localStorage stays as a cache so the first paint is in the right units.
A choice made in a browser before this change is handed to the server
once, if the account has none yet. The cache is cleared on sign-out so
it isn't handed to the next person who signs in on the same browser.
The coach reads the preference but has no tool to set it. Say so, and
say where the athlete can, so a request to switch units gets a useful
answer and not a promise the coach can't keep.
@aderaaij
aderaaij merged commit ee451f3 into main Nov 12, 2026
3 checks 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.

1 participant