Back to all case studies

Case study

Aura Weather

A weather dashboard built on one rule: missing data is shown as missing, never as a confident zero. This case study covers what came after that rule was in place. I directed coding agents through a severe-weather alerts milestone, where a wrong answer is a safety problem, using written operating rules, decisions I kept for myself, and verification that says what it did not prove.

  • Written operating rules for coding agents
  • Decisions measured against live NWS data
  • Verification that reports its own limits
Project type
Weather dashboard, and a working example of agent-delivered product work under human direction
Stack
React 19, Vite 8, plain CSS, Leaflet, Lucide icons, Open-Meteo + NOAA/NWS APIs. JavaScript/JSX, no UI framework. An optional rain-alert feature uses Supabase.
My role
Product direction, agent operating rules, every design and scope decision, review and merge of each pull request
Status
Working portfolio project with real multi-API integration — not a full production weather platform.
Last verified
— claims re-checked against the Aura repository's merged pull requests; static, link, and route-readiness checks pass. No adoption or field-performance data exists yet.
Aura Weather current conditions and environmental exposure interface
Captured from the public live demo on May 12, 2026, before the September 2026 interface redesign. A current capture is pending.

Agent delivery lens

The work was getting a safety-critical feature built by coding agents without letting them decide what the product should say.

An agent can normalise a field, write the tests, and open a pull request. It should not choose the word a person reads during a tornado warning. Aura's repository separates those two kinds of work in writing, and the alerts milestone shows the separation holding under real tickets.

Rules before code

A repository instruction file sets the data-trust contract, the risky files, and a hard stop at the open pull request.

Human decision gates

Wording, severity labels, and visual treatment are mine. Agents report the question and stop.

Evidence over assertion

Each fix is checked against recorded live payloads, then reverted by hand to see which tests actually fail.

Evidence Snapshot

What shipped

  • Drills and test messages no longer render as live hazards
  • The NWS protective-action text appears on the alert card
  • Alerted area and a recommended-response chip
  • Alerts stay until the hazard ends, not until the message expires
  • "Starts within…" and "In effect now" phrasing
  • A warning's own outline drawn on the radar map

How the work was run

  • Competitive audit, then a remediation plan, then one ticket per pull request
  • Each risky file changed alone in its own commit
  • Every pull request states scope, checks run, checks not run, and what was left out
  • Agents stop at the open pull request; I review and merge

Decisions I made

  • A five-rung severity ladder: Minimal, Low, Moderate, High, Severe
  • The exact wording of every response chip
  • Lead time shown in buckets, never as a countdown
  • Fixing the hazard-end bug in its own pull request before building on it
  • Removing visual-regression baselines, and saying what that cost

What the live feed showed

  • 243 active NWS alerts sampled on September 18, 2026
  • Hazard end differed from message expiry on 184
  • 99 had a message expiry earlier than their own onset
  • 8 upcoming alerts were being dropped from the card
  • Onset differed from issue time on 169

What to verify in 90 seconds

Honest limits

  • No adoption, usage, or user-research data exists for this project
  • The feed figures come from a single-day sample, reported in the pull requests
  • A screenshot of a live warning in production has not been captured yet
  • NWS alerts cover the United States only
  • This is a portfolio project, not a production weather platform

Weather Data Trust Problem

This is the backstory the alerts work stands on. Weather providers return null for individual readings more often than demos suggest, and JavaScript turns Number(null) into 0. Aura was rendering missing humidity as "0%" and missing pressure as "0 hPa". The fix became a data-trust contract: a missing reading stays null from the API boundary to the screen, renders as unavailable, and is announced to assistive technology as "no data available".

A later audit found the contract still leaking in three places, because individual modules had wrapped the shared helper in local versions with looser behavior. The full write-up, including the claims I got wrong along the way, is in the repository's trust contract case study.

That contract is now the first section of the agents' operating rules. Alerts raise the stakes: a dropped warning is a false all-clear.

The Alert Problem

A competitive audit against The Weather Channel, AccuWeather, and MyRadar produced one finding that outranked the rest. Aura's alert card showed the event name, a headline, a priority, and an expiry. The NWS description was normalised and read by no component. The NWS instruction, the text that says what to do, was never normalised at all. A Severe Thunderstorm Warning rendered without "seek shelter".

The same narrow projection caused a second defect. The CAP status field was not read, so a drill published on the same endpoint as real alerts rendered as an active hazard. A test message was live in the national feed when this was measured.

The milestone that followed added no new provider, no new dependency, and no layout redesign. It made the card use what NWS already sends.

My Role

I set the product direction, wrote the agent operating rules, made every design and scope decision, and reviewed and merged each pull request. Coding agents implemented under those rules.

In practice that meant commissioning the audit, approving the remediation plan and its ticket order, answering each question the agents escalated, reading every pull request including the parts flagged as going beyond the ticket, and deciding what merged.

Operating Rules

The rules live in the repository, so every agent session starts from the same contract.

  • Data trust is a product contract. Missing provider data renders as unavailable. It never becomes 0, dry, calm, or coordinates (0,0).
  • Architecture has a direction. Components, then hooks, then API and services, then pure domain code. Components never fetch.
  • Risky files are named. The provider normaliser, the request-lifecycle hook, and the large layout surfaces get extra caution and their own commits.
  • Design decisions are not delegated. Agents may audit the interface and document what they find. If a change needs design judgment, they report it and stop.
  • Old documents are history until they match the code. Claims are re-checked against current source before they are repeated.
  • Hard stop at the open pull request. No merging, no auto-merge, no merging on green checks.

Decision Gates

The rules were tested by the first implementation ticket. The milestone spec proposed a response chip. The agent shipped the protective-action text and left the chip out, because mapping CAP response codes to words a person reads is a vocabulary decision the rules reserve for me. I approved the wording, and the next pull request transcribed it rather than choosing it: "Take shelter", "Evacuate", "Avoid the area", "Monitor conditions", and four more. Unknown codes render no chip.

  • Constraint

    An alert's onset on a long-fuse product is a forecaster's judgment that gets revised as the event approaches. In the sample, no upcoming onset was under an hour away.

  • Decision

    Show lead time in buckets such as "Starts within 12 hours". Never print a countdown.

  • Tradeoff

    The card is less precise than competitors that show minutes. It also never displays two significant figures of confidence on a number that will be reissued before it arrives.

Scannability Decisions

  • The protective instruction renders inline and the longer description sits behind a disclosure. Sampled instructions had a 163-character median against 548 for descriptions, and the actionable one should not need a click.
  • A missing instruction renders nothing. It was absent on about 40% of sampled alerts, but present on all 14 with an urgency of "Immediate", so an "unavailable" line would have fired almost only on small-craft advisories.
  • The response chip is deliberately not styled as a severity badge. One says how bad, the other says what to do, and a test asserts they never share a class.
  • County-level alerts that NWS ships without an outline get a caption on the map, so an alert on the card never sits beside an empty map with no reason given.
  • The alert outline draws beneath the location marker in its own map pane, so "does it cover me?" stays answerable.

What the Live Feed Showed

NWS publishes two end times that answer different questions. expires is when the message must be reissued. ends is when the hazard is forecast to be over. Aura had normalised the first under a name that promised the second, and both the card and the offline-restore path believed the name.

The agent found this while scouting the next ticket against live data, and I split it into its own pull request ahead of that ticket. One recorded Beach Hazards Statement had an onset of 05:00, a message expiry of 05:30, and a hazard end of 15:00. Under the old rule it vanished from the card for nine and a half hours, the hours it mattered most.

The fix resolves one shared rule for both paths, so the card and the restore logic cannot drift apart again. Where NWS sends no hazard end, the old behavior is kept exactly, so no alert gets a shorter window than it had.

Source of these figures: measurements taken by the implementing agent against api.weather.gov/alerts/active on September 18, 2026 and reported in the pull requests. They are a single-day sample, not a monitored metric.

Verification That Reports Its Limits

  • Recorded live payloads. Real NWS alert responses are committed as fixtures, and the domain tests run against them rather than against invented shapes.
  • Negative controls by hand. After each fix, the change is reverted to see which new tests fail. The hazard-end pull request reports that reverting the card fails only 1 of its 4 new render tests, and names which tests prove the fix and which only pin the contract.
  • Checks not run are named. When the agent's container could not run the pinned browser build, the pull request said so and deferred to CI instead of reporting a local pass.
  • No existing test modified. Pull requests 234 through 237 each report zero deletions across tracked test files. New behavior arrived as new tests.
  • Suite size at the last milestone pull request: 1,156 unit checks and 371 render checks passing on September 18, 2026.

Where the Process Caught Itself

  • A spec corrected by data. The spec said each newline in NWS text should become a paragraph. NWS hard-wraps at about 70 columns, so that rule cut a live warning after "stay away from" and stranded "windows." on its own line. 116 of 122 sampled instructions carried wrap newlines. The rule changed.
  • Stale claims dropped. Before recording my severity-ladder decision, the agent re-read the source and found two claims in the spec that were no longer true. Both were corrected in place with citations instead of being repeated.
  • An adversarial review of the agent's own commits found two defects before merge, including a cached alert that would have read "In effect now" for a hazard still hours away.
  • A mutation that broke nothing. Removing a geometry type guard failed no tests, which meant a multi-part alert could have reached the map as if its first piece were the whole alert. A test now pins the guard.

Accessibility Considerations

  • Automated in CI: Playwright runs axe against the live dashboard and the missing-data state as part of the end-to-end suite.
  • Captured evidence: one exported run of that gate — app demo state, 0 violations, WCAG A/AA, August 16, 2026 — plus an axe export for an earlier version of this case-study page.
  • The alert additions introduce no new live regions. The response chip carries an aria-label naming the field, such as "Recommended response: Avoid the area".
  • Missing values are announced as "no data available" instead of relying on an em dash.
  • What automation missed: axe passed clean while every row of the seven-day forecast hid its data from screen readers, because an aria-label on the row button replaced its contents. Automated checks prove the absence of known violations. They do not prove the page makes sense read aloud.

Performance Considerations

  • Lighthouse budgets run in CI alongside the test suite.
  • One lab run reported during the milestone scored performance 99 against a budget of 85 and accessibility 100 against a budget of 95. That is a single lab result, not field data.
  • The milestone was scoped to add no dependency. The alert outline reuses the existing Leaflet map, which loads lazily.
  • Changing °F/°C is handled client-side and does not refetch forecast or archive data.

Current Status

The alerts milestone is merged to main. Its last open item is a screenshot of a real warning rendered in production, which has to wait for a real warning. The interface has since been redesigned, and follow-up fixes from a later audit are in review. Aura remains a portfolio project and should not be described as a production weather platform.

What I Learned

Agents are most useful when the rules say what they may not decide. The pull request I value most from this milestone is the one where the agent declined to ship a feature the spec asked for, because the wording was mine to choose.

I also learned to read a verification section for what it admits. "Four new tests pass" tells me little. "Reverting the fix fails one of the four, and here is why" tells me what is actually protected.

What I Would Improve Next

  • Capture a live warning in production and replace the hero screenshot with the current interface.
  • Re-measure the feed figures on more than one day, across different weather regimes.
  • Add basic product analytics or user testing, so the outcome section can report behavior instead of test counts.
  • Draw county-level alerts from NWS zone geometry, which the milestone deliberately deferred.
  • Split the request-lifecycle hook, the largest file in the project and one that grew during the resilience work.