github Fighter90/career-ops-ui v1.233.0

2 hours ago

[1.233.0] — 2026-09-10

Fixed — three findings in the #/config save path. CONFIG-3 was caused by v1.232.1's own fix.

Fixed

CONFIG-3 — a deliberate choice that happened to equal the seeded value was discarded as "untouched". Caused by v1.232.1's own fix, whose comment claimed the code could "tell a display default apart from a user who deliberately chose that same value". It could not. initial conflates two origins: seeded from .env (equal means the field was not edited — correct to skip) and seeded from defaultValue (equal is ambiguous — never opened, or opened and agreed). With HOST absent the control shows 127.0.0.1; typing that exact value and saving posted {} and left the key absent. The only route to pinning it was to enter a wrong value, save, then set the right one back — a dead end nobody would find. Nothing misbehaved, since an absent key and a key equal to the default resolve identically, but the UI showed a state the file did not contain and pressing Save again never reconciled them. Fixed by recording seededFromFile beside initial: where the seed came from the file nothing changes, and where it came from defaultValue, dirty becomes a valid second signal. dirty cannot be the general basis — it fires on type-and-revert — but here it is the only remaining evidence, and it costs at most the one field the user was working in, never CONFIG-2's eighteen, for which dirty is empty because nobody opened them.
Removing a key reported "· 0". updateEnvFile only ever returned what it wrote, so a deletion had nothing to count and the toast read Settings saved · 0 — which a user reasonably takes as "nothing happened" while the key had in fact been removed. POST /api/config now answers with removed as well, computed against the pre-write snapshot so it names keys that genuinely were present rather than every key the request asked to clear, and the toast counts both.
A select could be set but never cleared. None of the 18 dropdowns offered an empty option, so the form could not send '' for one — text fields can simply be emptied, dropdowns could not. Together with CONFIG-3 that closed a loop for an unset select: it could not be pinned, because its value equalled the seed, and once pinned it could not be released. Every select now leads with an explicit "Use the default (…)" entry, and an unset key selects that instead of the default value. v1.57.1's intent survives — the label still names the value the server will use — but it now says so rather than impersonating a choice, which is what made CONFIG-3 possible in the first place. select-remote needed care: the live OpenRouter catalogue calls replaceChildren, which wiped the entry, so it is re-created there too. One new i18n key, config.useDefault, across all 17 dictionaries.

Notes

Checked during this pass and deliberately left alone, so the record is complete. lang in every POST body is a literal allowlist entry, not a prefix rule — verified by behaviour: lang → 200 while langx, lang_evil and LANG all → 400, exactly like any unknown key. facts.json is absent from the deployed site by design — 22 files import it at build time and the values are inlined into the HTML, so the version token in the page is what to check, not the file; §2.3 of the v1.231.5 prompt was misleading on this. /api/cv/import treating an extensionless upload as text is documented and deliberate, so a clipboard save without an extension still works; the route converts and returns, never writes or executes, and .exe/.zip are refused explicitly.
Unit baseline unchanged at 3018 — every fix is in the client or in one route's response shape. The gates are browser cases: tests/playwright-config-save-scope.mjs grows from six to eleven (111 → 116 browser tests), and the five new ones were confirmed failing against the old code first. One of them exists purely to keep CONFIG-2 from returning: an untouched Save must still post nothing.

Don't miss a new career-ops-ui release

NewReleases is sending notifications on new releases.