Repository navigation
Widgets: weather and location from the profile coordinates (stacked on #153) - #156
Merged
Merged
Conversation
Two more cards on the widget rail from #118, using the same collapsible / refreshable / quiet-placeholder machinery. Coordinates are explicit method parameters on the service rather than being read out of a CurrentUser field inside it. That keeps the service testable and, deliberately, keeps this change independent of the in-flight work adding Latitude/Longitude to Models/CurrentUser.cs — which this branch does not touch. The UI sources them from ProfileLocationProvider (one semaphore-gated cached GET /api/user shared with the transit and bike-share cards, so the two new widgets cost no extra request). No OS geolocation, per the product docs: the web's browser-permission flow has no desktop equivalent, so the stored profile location is the parity-appropriate source. With no coordinates set, both cards point the user at Settings instead of erroring. Live findings from probing on 2026-09-16: - Parameter names are the LONG spellings, latitude/longitude, from the live OpenAPI spec — unlike the bike-share widget's lat/lon. The two families genuinely differ; neither can be inferred from the other. Weather also takes `extended` and `refresh`. - Both endpoints are UNITED STATES ONLY. Seattle, NYC, Chicago, Anchorage and Honolulu all returned 200; Toronto, Tokyo, lat/lon 0,0 and out-of-range coordinates all returned 404 "Failed to fetch weather/location data". That is the National Weather Service's coverage showing through the proxy, so a 404 is caught specifically and reported as "no data for your location" rather than falling into the generic failure placeholder. - Only one of the pair, or non-numeric values, is a 400 as documented. - `extended=true` adds hourly[25] and weekly[14] (NWS alternates day/night periods, so 14 rows is ~7 days) and inflates the payload ~37x, 166 -> 6178 bytes. The rail shows one line, so it is not requested. - `humidity` was null in all seven observed responses across five cities. It is modelled and rendered if it ever appears, but is not expected. - Weather returns `timeZone`, location returns `timezone` — absorbed only because the client's JsonSerializerDefaults.Web options are case-insensitive. Weather is cached server-side for 30 minutes, so the widget's cache window matches it exactly and the refresh button intentionally does NOT send refresh=true — a user tapping refresh should not punch through to the upstream. Location is a geocode of a fixed point, so it caches for six hours. Closes #119 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Live probe evidence (2026-09-16, real API, test account)
Both endpoints are bearer-reachable and need nothing but a coordinate pair.
GET /api/weather(bare)400{"error":"Latitude and longitude are required","code":"bad_request"}GET /api/weather?latitude=47.6062(one of the pair)400GET /api/weather?latitude=abc&longitude=def400{"error":"Invalid latitude or longitude","code":"bad_request"}GET /api/weather?latitude=47.6062&longitude=-122.3321200GET /api/location(bare)400{"error":"Latitude and longitude are required","code":"bad_request"}GET /api/location?latitude=47.6062&longitude=-122.3321200Query-parameter names — from the live OpenAPI spec, not guessed
These are the long spellings — which matters, because the sibling
/api/widgets/bike-sharein #153 useslat/loninstead. The two families genuinely disagree and neither can be inferred from the other; don't normalise them onto one spelling without re-probing.Real payload shapes (key union across every row of every response captured)
Endpoints that returned errors or empty data
Both are United States only. This was the main thing worth pinning down, since the brief warned some endpoints might be region-gated to Seattle. They are not Seattle-gated — they are NWS-coverage-gated:
/api/weather/api/location200Seattle200Seattle, WA200New York, 78°200New York, NY200Chicago, 69°200Chicago, IL200Anchorage, 58°200Anchorage, AK200Urban Honolulu, 88°200Urban Honolulu, HI404404404404404404404404bodies are{"error":"Failed to fetch weather data","code":"not_found"}/{"error":"Failed to fetch location data","code":"not_found"}. Both ViewModels catch404specifically and report "No weather data for your location." / "Your location could not be resolved." rather than letting it fall into the generic failure placeholder — it's an ordinary empty state, not an outage.humidityis present in the envelope but wasnullin all seven observed responses, across five cities. Modelled and rendered if it ever appears, documented as not expected.extended=trueinflates the payload ~37× (166 → 6178 bytes) for forecast arrays a one-line rail card doesn't render, so it isn't requested. Noteweeklyhas 14 rows, not 14 days — NWS alternates day and night periods, so that's ~7 days.refresh=trueis accepted (200) but the widget's refresh button deliberately does not send it: the whole point of the 30-minute server cache is to shield the NWS upstream, and a user tapping refresh shouldn't punch through it.Casing inconsistency upstream: weather returns
timeZone, location returnstimezone. Absorbed only because the client'sJsonSerializerDefaults.Weboptions are case-insensitive — flagged in both models so nobody "fixes" one to match the other.Models proven to deserialize, not eyeballed
The same throwaway
net10.0harness outside the repo from #153, extended with the new types —<Compile Include>s the model files by absolute path and runs the captured payloads throughnew JsonSerializerOptions(JsonSerializerDefaults.Web):Every
startTimein both forecast arrays round-tripped intoDateTimeOffset(25/25 and 14/14 non-null), so the offset-bearing ISO strings parse cleanly.Acceptance criteria
GetWeatherAsync(GeoPoint at, bool extended, bool refresh, ct)andGetLocationAsync(GeoPoint at, ct). The UI sources them fromProfileLocationProvider, which readslatitude/longitudeoff the rawGET /api/userenvelope.WeatherWidgetViewModel.CacheForis exactly 30 minutes, matching the documented server-side cache; nav switches, expand/collapse and repeated load-alls inside the window are free. Location caches for 6 hours — a geocode of a fixed point doesn't move.Notes
Models/CurrentUser.csis untouched. Open PR fix(models): reconcile Message and CurrentUser against live payloads #131 addsLatitude/Longitudethere; this branch does not need it and does not conflict with it.ProfileLocationProvider→GetProfileLocationAsyncreads the two fields off the rawGET /api/userJSON, and the XML doc notes it can collapse intoCurrentUseronce those properties land.GeoPointandProfileLocationProviderso the decision survives.ProfileLocationProvideris semaphore-gated and cached, so these two cards plus transit and bike-share cost a singleGET /api/userbetween them, not four.TimeZoneInfo.FindSystemTimeZoneByIdis guarded — .NET on Windows understands IANA ids from .NET 6 on, but a machine with stale ICU data can still miss one, and a widget must not throw over a timezone name.72°: the API proxies the U.S. National Weather Service and reports Fahrenheit, but the payload never states a unit, so the UI doesn't claim one.conditionIcon(bx-sun,bx-cloud-rain) is a web BoxIcons class the desktop app doesn't ship — retained on the model for fidelity, not rendered;conditiontext is what's shown.Build
Clean in both configurations, 0 warnings / 0 errors, with
--no-incremental:Closes #119
🤖 Generated with Claude Code