From a3c22ef6d714b08cc5095e1ed9606ce94a3752a5 Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Sat, 11 Jul 2026 12:26:42 -0700 Subject: [PATCH] Fix /place 500s, /day rate-limit handling, calendar listener leak, error surfacing (#40) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /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. --- static/calendar.js | 11 ++++++++--- static/nav.js | 10 +++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/static/calendar.js b/static/calendar.js index 70e870c..1094dff 100644 --- a/static/calendar.js +++ b/static/calendar.js @@ -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 ---- // The Find button opens the shared modal map picker; choosing a spot loads it. const findBtn = document.getElementById("find-btn"); @@ -841,9 +849,6 @@ function attachHover(byDate) { 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) ---- diff --git a/static/nav.js b/static/nav.js index 7e5a9f1..781f0a2 100644 --- a/static/nav.js +++ b/static/nav.js @@ -213,8 +213,16 @@ async function fetchStore(url, rec) { cachePut({ ...rec, t: Date.now(), day: localDay() }); 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(); - 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 }); return d; }