Fix /place 500s, /day rate-limit handling, calendar listener leak, error surfacing (#40)

- /api/v2/place still called grid.in_north_america(), which the worldwide-
  coverage change removed — every request raised AttributeError (500). The
  failure was invisible because compare.js treats the call as best-effort,
  so the instant chip-naming feature was silently dead. Drop the dead guard;
  the lat/lon Query validators already bound the inputs.
- /api/v2/day was the only data route not wrapping get_history in
  _weather_fetch_error, so a rate-limited cold cell returned a raw 500
  instead of the clean 503 the other routes emit.
- calendar: the #calendar pointerleave handler was re-registered inside
  attachHover on every render — and the comfort slider re-renders per input
  tick, stacking dozens of copies. Register it once at module scope next to
  the matching document-level dismiss handler.
- getJSON: check res.ok before parsing the body as JSON and fall back to the
  status line, so a non-JSON error body (a proxy 502 HTML page) reads as
  "Request failed (502 Bad Gateway)" instead of a JSON parse error.
This commit is contained in:
Emi Griffith 2026-07-11 12:26:42 -07:00 committed by GitHub
parent efd43dcbb5
commit a3c22ef6d7
2 changed files with 17 additions and 4 deletions

View file

@ -208,6 +208,14 @@ document.addEventListener("pointerdown", (e) => {
} }
}); });
// Mouse only: hovering off the grid hides the preview. On touch the tooltip is
// click-activated and must persist until a non-day tap (the handler above).
// Registered once — #calendar persists across re-renders (only its cells are
// replaced), so registering inside attachHover would stack a copy per render.
calEl.addEventListener("pointerleave", (e) => {
if (e.pointerType === "mouse") document.getElementById("cal-tip").hidden = true;
});
// ---- location picker ---- // ---- location picker ----
// The Find button opens the shared modal map picker; choosing a spot loads it. // The Find button opens the shared modal map picker; choosing a spot loads it.
const findBtn = document.getElementById("find-btn"); const findBtn = document.getElementById("find-btn");
@ -841,9 +849,6 @@ function attachHover(byDate) {
else armedCell = c; // first tap only showed the tooltip else armedCell = c; // first tap only showed the tooltip
}); });
}); });
// Mouse only: hovering off the grid hides the preview. On touch the tooltip is
// click-activated and must persist until a non-day tap (see the document handler).
calEl.addEventListener("pointerleave", (e) => { if (e.pointerType === "mouse") tip.hidden = true; });
} }
// ---- restore (hash from a shared link, else the last place you looked at) ---- // ---- restore (hash from a shared link, else the last place you looked at) ----

View file

@ -213,8 +213,16 @@ async function fetchStore(url, rec) {
cachePut({ ...rec, t: Date.now(), day: localDay() }); cachePut({ ...rec, t: Date.now(), day: localDay() });
return rec.d; return rec.d;
} }
if (!res.ok) {
// Error bodies aren't always JSON (a proxy 502 serves an HTML page) — fall
// back to the status line rather than surfacing a JSON parse error.
let detail = null;
try { detail = (await res.json()).detail; } catch (e) {}
const err = new Error(detail || `Request failed (${res.status} ${res.statusText})`.trim());
err.status = res.status;
throw err;
}
const d = await res.json(); const d = await res.json();
if (!res.ok) { const err = new Error(d.detail || "request failed"); err.status = res.status; throw err; }
cachePut({ url, t: Date.now(), day: localDay(), etag: res.headers.get("ETag") || null, d }); cachePut({ url, t: Date.now(), day: localDay(), etag: res.headers.get("ETag") || null, d });
return d; return d;
} }