js/forms.js — live validation
In the code column — js/forms.js, and in it attachLiveValidation, which implements the progression. Read the blur and input listeners side by side:
el.addEventListener('blur', () => { // (2) leave a field → validate it
touched.add(el.name);
validateField(el.name);
});
el.addEventListener('input', () => { // (3) typing → validate ONLY if already touched
if (touched.has(el.name)) validateField(el.name);
});
The whole trick is the touched Set. A field becomes "touched" only when you leave it (or submit the form). And the input listener validates only touched fields. So:
- Until you leave a field — silence (no nagging while you type it the first time).
- Leave it with an error → the field is "touched" → from now every keystroke validates, and the error clears the moment you fix it.
validateAll() (the returned function) is called on submit: it validates everything and marks all fields "touched", so from then on feedback is live.
Settings that work. settings.js ties this to state: fillForm fills from getSettings(), and on submit saveSettings(...) persists to localStorage AND applyTheme(...) changes the theme immediately. Settings aren't decoration; they change the app and persist. (settings-store.js holds DEFAULT_SETTINGS; theme.js holds applyTheme.)
Verify. In the settings form:
1. Leave "Display name" empty → the error appears (blur).
2. Start typing → the error clears immediately (touched → live).
3. Save → the theme changes, and the values survive a reload.
Gotcha (CSS — a real bug we hit). We styled inputs like this:
input[type="text"] { ... } /* ← only type="text" */
input[type="text"]is an exact-match selector — it does not styletype="email",type="url",type="password". Half the fields (email, URL, password) looked different. The fix is to style by "everything except the toggles":input:not([type="checkbox"]):not([type="radio"]) { ... }That catches every text-like type at once, and leaves
checkbox/radio(which have their own look) alone.