"Fetch rates now" failed on the deployed site with nothing but a CORS
error. api.frankfurter.app now answers every request with a 301 to
api.frankfurter.dev/v1, and a redirect response must itself carry
Access-Control-Allow-Origin for a browser to follow it cross-origin. That
301 carries no CORS headers at all, so the request died before the
redirect could be taken.
Measured rather than guessed, with an Origin header set:
api.frankfurter.app -> 301, no access-control-* headers
api.frankfurter.dev -> 200, access-control-allow-origin: *
The response shape is unchanged, so parseFxResponse needed no edit.
Nothing was wrong at build time, which is why this went unnoticed: a
server-side fetch follows the 301 freely, so the baked rates were always
correct. Only the browser path was broken.
Two changes, because a new default alone fixes only some datasets:
- DEFAULT_FX_API_URL points at .dev/v1, with a test that fails if the
redirecting host ever comes back.
- A 3 -> 4 migration drops settings.fxApiUrl when it holds exactly the
dead default, which is the case for anyone who edited that field. It is
DROPPED rather than rewritten, so the dataset follows whatever the
current default is and the next move of the service needs no second
migration for the same user; a URL the owner chose is left strictly
alone. No stored figure changes, so it cannot alter a number in the
budget.
Verified by clicking the button in a real browser: "Updated 2 rate(s)",
USD 6.456195 and EUR 7.474959 both stored with source "api" — and the USD
figure differs from the baked constant, so it is a genuine live fetch.
Two migration tests asserted the chain ends at 3; they now assert
SCHEMA_VERSION, since what they mean is "all the way through".
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
"Why is this the way it is" was answered by pointing at a directory of
five specs, which means reading five specs to find out whether the
question is even addressed. DECISIONS.md answers it in one line per
decision, grouped by area, each entry naming what the choice was made
*over* and linking into the spec that argues it.
Around forty entries. Where no spec exists — the currency model, the
baked rates, the reset — the commit message is cited as the record
instead of inventing one.
Written by reading the specs rather than from memory, which surfaced
history worth having in one place: dates were widened to accept "YYYY-MM"
rather than restructured into month plus optional day, specifically to
avoid running a migration in the browser against the user's only copy of
their financial data for an identical visible outcome.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>