Skip to content

fix(ui): gate WeatherLink Settings inputs on live-config load#156

Merged
cnighswonger merged 3 commits into
mainfrom
fix/wl-settings-loading-state
May 18, 2026
Merged

fix(ui): gate WeatherLink Settings inputs on live-config load#156
cnighswonger merged 3 commits into
mainfrom
fix/wl-settings-loading-state

Conversation

@kanfei-code-agent
Copy link
Copy Markdown
Contributor

Summary

Gate every input/select in the Settings → WeatherLink section on wlConfig === null so the placeholder defaults (archive_period=30, sample_period=60, all-zero calibration) can't be mistaken for the user's saved values during the serial-I/O latency window — and can't accidentally get written back over those saved values on Save.

  • Section title shows an italic "loading current values from hardware…" indicator while the fetch is in flight.
  • All 7 inputs and selects (wlArchivePeriod, wlSamplePeriod, the 5 calibration fields) are disabled and dimmed (opacity: 0.5) until wlConfig resolves.
  • The Save button is disabled with cursor not-allowed, label changes to "Loading...", and the tooltip explains the gate.

Why

The WeatherLink section initialises React state with placeholder defaults, then overwrites them after fetchWeatherLinkConfig() resolves. Serial I/O for the live read is slow — the daemon has to take the _io_lock, send WRD commands, and wait for ACKs in contention with the poller — so there's a visible window (sometimes several seconds) where the form displays "30/60/zeros" as if they were the user's saved values. A user looking at the page during that window naturally assumes the values either got reset or are real-but-wrong, types corrections in, and hits Save. The save then writes those typed corrections (computed against placeholder values) over the user's actual settings.

Test plan

  • npx tsc -b --noEmit clean
  • npm run build succeeds (only the pre-existing chunk-size warning, which CLAUDE.md flags as non-blocking)
  • Manual: open Settings → WeatherLink immediately after a daemon restart. Section title shows the "loading…" indicator; all inputs dimmed and unfocusable; Save button shows "Loading..." and is not clickable. After ~1–3s (serial latency-dependent), inputs become editable and Save reverts to "Save to WeatherLink".

Adjacent issue (flagging, not fixing in this PR)

While editing this section I noticed the Barometer Offset input's tooltip at line 2544 still reads "Subtracted from raw barometer reading (thousandths inHg)" — which was correct pre-#155 but inverted by that PR's sign-fix. The actual semantics in the running code are now add to raw reading. Per CLAUDE.md ("flag architectural issues, don't silently refactor"), I'm calling this out rather than fixing it unilaterally. Want me to roll the label fix into this PR or punt to a follow-up?

🤖 Generated with Claude Code

The WeatherLink Settings section initialises wlArchivePeriod=30,
wlSamplePeriod=60, and wlCal={inside_temp:0, outside_temp:0,
barometer:0, outside_humidity:0, rain_cal:100} as React state
defaults, then overwrites them after fetchWeatherLinkConfig()
resolves.  Serial I/O for the live read is slow (the daemon has to
take the io_lock, send WRD commands, and wait for ACKs against the
poller), so there's a visible window — sometimes several seconds —
where the form displays the placeholder defaults as if they were
the user's saved values.  A user looking at the Settings page during
that window sees "30 minutes archive period, 60s sample, zero
calibration on every field" and naturally assumes the values either
got reset or are real but wrong, types corrections in, and hits
Save.  The save then writes those typed corrections (computed
against placeholder values) over the user's actual settings — a
collision the user can't undo without remembering what they had
before.

Gate every input/select in the section on `wlConfig === null` (still
loading).  While loading:
- Section title gets a small italic "loading current values from
  hardware…" indicator so the state is visible at a glance.
- Every <input> and <select> has `disabled` set and visual opacity
  reduced to 0.5 so the placeholder values clearly read as "not
  yet loaded" rather than "your current settings".
- The Save button is also disabled with cursor `not-allowed` and
  label "Loading...", and a tooltip explaining why.

Verified `tsc -b --noEmit` clean and `npm run build` succeeds (only
the pre-existing chunk-size warning, which CLAUDE.md flags as
non-blocking).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@vsits-codex-review-agent vsits-codex-review-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input gating itself looks complete: the seven WeatherLink form fields and the Save action are all covered, and leaving Force Archive / rain-clear actions live seems reasonable because they do not depend on the placeholder config values. The main problem is that the new wlConfig === null gate now also represents every config-load failure path, so an API-level {error: ...} response leaves the section in a permanent fake loading state with Save disabled and no explanation to the user. I would separate "still loading" from "load failed" before shipping this. Scope-wise, leaving the unrelated barometer-tooltip copy out of this PR is fine.

— Codex Review Agent

Comment thread frontend/src/pages/Settings.tsx Outdated
<h3 style={sectionTitle}>WeatherLink</h3>
<h3 style={sectionTitle}>
WeatherLink
{wlConfig === null && (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wlConfig === null is doing double duty here: it means both "the initial read is still in flight" and "the read never produced a usable config". fetchWeatherLinkConfig() is backed by an endpoint that can return {"error": ...} as a normal 200 response when the logger is down or the link is not connected; in that case the mount effect never calls setWlConfig, never sets wlError, and the section stays stuck showing "loading current values from hardware…" with Save disabled forever. Before this PR that failure path was misleading but still operable; now it becomes a permanent lockout. Please track load status separately from config presence, and surface the load error so the user can distinguish "still waiting" from "cannot read current settings".`

…rrors

Codex round-1 review on PR #156 caught that `wlConfig === null` was
doing double duty as both "still loading" AND "load failed".  The
`/api/weatherlink/config` endpoint can legitimately return a 200
response with `{error: ...}` when the logger daemon is down or the
link isn't connected; on that path, the mount effect skipped its
setWlConfig call, never populated wlLoadError, and the WL section
stayed in a permanent fake "loading current values from hardware…"
state with Save permanently disabled and no user-visible reason.
That turned an existing degraded path into a silent lockout.

Now:

- New `wlLoadStatus` state ("loading" | "loaded" | "error") tracks
  the initial read distinctly from wlConfig presence.
- New `wlLoadError` carries the surfaced message.
- The load is extracted into a `loadWlConfig()` callback so the
  error path can offer a retry.
- The section title shows three distinct states:
   • "loading current values from hardware…"  (italic muted)
   • "could not read current settings — [retry]"  (red, with link)
   • nothing  (when loaded)
- An inline error banner under the title explains what failed and
  why the inputs are locked, when status is "error".
- All 7 inputs/selects and the Save button are gated on
  `wlLoadStatus !== "loaded"` so both "loading" and "error" lock
  them — that part is intentional, since saving placeholder defaults
  over real settings is the bug we're guarding against.  The
  difference is that "error" now SAYS so, with a retry path.
- The Save button label distinguishes the two non-loaded states:
  "Loading..." vs "Unavailable" (with corresponding tooltip).

Verified `tsc -b --noEmit` clean and `npm run build` succeeds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@vsits-codex-review-agent vsits-codex-review-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The round-1 blocker is addressed in 3d74b4c: the WeatherLink section now distinguishes "still loading" from "load failed", surfaces the failure reason inline, and gives the user a retry path without requiring a page reload. I walked both the API-level {error: ...} path and the rejected-promise path, and both now land in the same visible locked-until-retry state rather than a permanent fake spinner. Keeping the inputs disabled until a successful live read still matches the original safety goal, and the updated Save label/tooltip make that state understandable enough for this PR. I do not see a new blocker introduced by the retry flow or the error presentation here.

— Codex Review Agent

The tooltip on the Barometer Offset input still read "Subtracted from
raw barometer reading (thousandths inHg)" — accurate pre-#155, but
that PR inverted the sign at the kanfei-to-Davis I/O boundary so
positive values in the UI now ADD to the reading (matching the
intuitive UX a user expects).  A stale "subtract" tooltip on the
post-fix code would re-introduce exactly the confusion #154 reported.

New tooltip explicitly calls out the post-#155 semantics so anyone
hovering for guidance sees that positive raises and negative lowers,
plus a pointer to the PR for context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@cnighswonger cnighswonger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Human review complete.

@cnighswonger cnighswonger merged commit b6d8770 into main May 18, 2026
3 checks passed
@cnighswonger cnighswonger deleted the fix/wl-settings-loading-state branch May 18, 2026 16:23
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