From 619f069358a8db343a9ee0fd8ab5c7f966783d3a Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 17 Apr 2026 16:40:57 -0400 Subject: [PATCH] feat(weather): add refresh button and return card data from refresh endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a refresh button (↻) to the weather section header in BriefingView so users can manually re-fetch weather data (needed after deploying the hourly precip changes to populate the cache). Update the existing POST /api/briefing/weather/refresh endpoint to return full card data instead of just location keys. Co-Authored-By: Claude Opus 4.6 --- frontend/src/views/BriefingView.vue | 63 ++++++++++++++++++++++---- src/fabledassistant/routes/briefing.py | 14 ++++-- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/frontend/src/views/BriefingView.vue b/frontend/src/views/BriefingView.vue index 27f5fec..31560a3 100644 --- a/frontend/src/views/BriefingView.vue +++ b/frontend/src/views/BriefingView.vue @@ -92,6 +92,17 @@ async function loadWeather() { } catch { /* silent */ } } +const refreshingWeather = ref(false) +async function refreshWeather() { + refreshingWeather.value = true + try { + const data = await apiPost<{ locations: WeatherData[]; temp_unit: string }>('/api/briefing/weather/refresh', {}) + weatherData.value = data.locations ?? [] + tempUnit.value = data.temp_unit ?? 'C' + } catch { /* silent */ } + finally { refreshingWeather.value = false } +} + // News panel (right column) const newsItems = ref([]) @@ -279,14 +290,23 @@ onMounted(async () => {
-
+
+
+ +
+ class="weather-refresh-btn" + :class="{ spinning: refreshingWeather }" + :disabled="refreshingWeather" + @click="refreshWeather" + title="Refresh weather" + >↻
{ margin-bottom: 0; } +.weather-section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.5rem; +} + +.weather-refresh-btn { + background: none; + border: 1px solid var(--color-border); + border-radius: 6px; + color: var(--color-text-muted); + font-size: 1rem; + cursor: pointer; + padding: 0.2rem 0.45rem; + line-height: 1; + transition: all 0.15s; +} +.weather-refresh-btn:hover { + border-color: var(--color-primary); + color: var(--color-primary); +} +.weather-refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; } +.weather-refresh-btn.spinning { + animation: spin 0.8s linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } + .weather-tabs { display: flex; gap: 0.25rem; - margin-bottom: 0.5rem; } .weather-tab { diff --git a/src/fabledassistant/routes/briefing.py b/src/fabledassistant/routes/briefing.py index 0cb56c9..d70c63b 100644 --- a/src/fabledassistant/routes/briefing.py +++ b/src/fabledassistant/routes/briefing.py @@ -167,6 +167,7 @@ async def get_weather(): return jsonify({"locations": cards, "temp_unit": temp_unit}) + @briefing_bp.route("/weather/current", methods=["GET"]) @_REQUIRE async def get_current_weather(): @@ -225,11 +226,14 @@ async def refresh_weather(): raw = await get_setting(g.user.id, "briefing_config", "{}") try: config = json.loads(raw) if isinstance(raw, str) else {} + temp_unit = config.get("temp_unit", "C") + if temp_unit not in ("C", "F"): + temp_unit = "C" except Exception: config = {} + temp_unit = "C" locations = config.get("locations", {}) - refreshed = [] for key, loc in locations.items(): if not loc.get("lat") or not loc.get("lon"): continue @@ -241,11 +245,15 @@ async def refresh_weather(): lat=loc["lat"], lon=loc["lon"], ) - refreshed.append(key) except Exception: logger.warning("Failed to refresh weather for %s", key, exc_info=True) - return jsonify({"refreshed": refreshed}) + rows = await weather_svc.get_cached_weather_rows(g.user.id) + cards = [ + card for row in rows + if (card := weather_svc.parse_weather_card_data(row, temp_unit)) is not None + ] + return jsonify({"locations": cards, "temp_unit": temp_unit}) # ── Briefing Conversations ─────────────────────────────────────────────────────