bpmore/statamic-site-weather
A lightweight Statamic addon for displaying site weather: configure a location and show current conditions in the control panel or templates via a simple fieldtype/tag. Keeps weather data handy without leaving Statamic.
Install the package, then find the two files that define everything else: the State enum and the Reading value object. Read the enum first — its ordering (if any) is what makes "worst-wins" deterministic, and every behavioural decision you make downstream depends on where unknown sits relative to the other five states.
Registration is by container tag, not config file. In a service provider:
$this->app->tag([
\App\Weather\ForecastIoContributor::class,
\App\Weather\MetOfficeContributor::class,
], 'site-weather.contributors');
Each tagged class must implement WeatherContributor and return a Reading (state, headline, url, computed_at). The dashboard widget then resolves the tag, collects the readings, and aggregates them worst-wins.
First use case: one contributor, one widget. Wrap a single API you already trust, tag it, drop the widget onto your dashboard, and confirm you see a real headline. Don't start with three sources — with worst-wins aggregation, every additional source is an additional way for the tile to look bad, so add them one at a time and watch the tile after each.
Contributors are adapters, keep them dumb. One class per upstream source. Fetch, map to a State, fill headline/url, stamp computed_at, return. No aggregation logic, no formatting, no view concerns — the package owns all of that and will fight you if you duplicate it.
Cache inside the contributor, not around the widget. The widget is static and renders on page load, so a slow contributor is a slow dashboard. computed_at is the freshness contract the package exposes; let it reflect the age of the underlying data (i.e. when the API observation was taken), not when you happened to call it. Those differ, and the difference is what makes the tile honest.
Return unknown deliberately. unknown is first-class — it is a legitimate answer, not an error path. If an API times out, a key is missing, or the response shape changed, return unknown with a headline explaining what's missing. Swallow-and-return-sunny is exactly the failure mode this package was designed against.
Test at both layers. Unit-test each contributor against recorded fixtures to prove the state mapping. Then test the aggregation separately with hand-built Reading collections covering all six states — including the empty collection, which must not produce a sunny tile.
Wire the widget like any other dashboard widget (register it in your dashboard class). Because it's static, there is no polling to configure and no JS state to reset; that also means it won't self-refresh, so pair it with whatever refresh cadence your dashboard already has.
Accessibility is already handled — don't undo it. Every state has a word, every state has a distinct icon shape, and there's a one-sentence screen-reader summary. If you extend the widget, preserve all three. Notably, shape carries the state, so a colourblind or greyscale user still reads the tile correctly; a custom badge that only changes colour breaks that.
php artisan config:cache (or optimize:clear) after changing the contributor list, or you'll chase a ghost.unknown, it will drag the tile down permanently. Vet contributors before tagging them.unknown's rank. It's a first-class state, so it participates in the worst-wins comparison rather than being skipped. If it ranks as worst, one broken contributor pins the tile to unknown forever — that may be the design intent, but know which you're getting before you add a flaky source.unknown on a fresh install, the tag is empty and that's the correct output.computed_at and timezones. Stale data still aggregates. Decide explicitly whether your contributor should downgrade to unknown past an age threshold, and store timestamps consistently (UTC) so the comparison isn't off by your app timezone.Reading collection before aggregation. Worst-wins tells you the answer but not the culprit, and the culprit is almost always one contributor returning unknown on a schema change.How can I help you explore Laravel packages today?