Hunter Water Management
An irrigation console for one water manager running ten sites — where the design problem is an eight-hour watering window, not a dashboard.
- Engagement
- Self-initiated spec project
- Role
- Product Design & Front-End
- Platform
- Web console, 360px → desktop
- Domain
- Commercial & municipal irrigation
- Scope
- 10 sites, 290 stations, 4 programs
- Build
- Next.js, prerendered static

A municipal water manager runs ten sites — 290 stations across parks, sports complexes, medians and streetscapes — from one console, and every one of them has to water inside the same overnight band. That single constraint generates the whole product. The night either fits in the window or it does not. The concurrent draw either stays under the mainline ceiling or it does not. The month's consumption either paces against a district allotment or it overruns it, and by the time the meter is read it is too late to do anything about it. I designed the console around the window rather than around the controller, and built it as a working prototype so the scheduling claims could be tested rather than drawn.
A night that has to fit in eight hours.
The operator's first question every day is whether last night's work completed, and the second is whether tonight's will. Both are answered by the same picture: every station's cycle laid into a lane across the window, with the concurrent draw plotted underneath it against the mainline ceiling. A timeline that merely scatters bars across eight hours looks plausible and is wrong — so this one is generated by a packer, not by a layout.
- Window
- 21:00 – 05:00
- Constraints
- 3, all binding
- Peak draw
- 692.3 GPM
The Window Is The Spine, Not A Panel
Nearly every figure in the product is downstream of the watering window, so it is drawn once, large, and everything else refers back to it. A site is in trouble when its night no longer fits — that is a single reading, available before any number is parsed.
The Night Plane Is The Only Dark Surface
In the default light theme the timeline is the one dark panel in the product, because it depicts the night. That gives the console two planes: a day plane for the workspace and a night plane reserved for the window. A night colour anywhere else is a bug, not a style choice, and it stays that way in dark mode — where the timeline separates from the page by saturation rather than by luminance.
The Packer Honours Three Real Constraints
A station cannot open before its program does; the concurrent draw cannot exceed the mainline's GPM; and the number of stations open at once is capped electrically, separately from the hydraulic limit and differently on ACC2 and ICC2 hardware. Drop any one of them and the night collapses into the first ninety minutes.
Cycle And Soak, Because Slopes Run Off
A station's nightly runtime splits into passes with a mandatory soak between them — turf on a slope cannot absorb ninety unbroken minutes. It is also what makes the arithmetic work: without it, the nightly volumes stop reconciling with the monthly meter reads.
The Timeline's Height Is Itself A Reading
Runs are laid into non-overlapping lanes, so the lane count equals peak concurrency. A tall night is a busy night, legible before the axis is read.
A queue that says what to do about it.
An irrigation fault is a physical event on the ground — a broken lateral, a stuck valve, a controller that has stopped reporting — and the person reading the alert has to decide whether to send a truck this morning. The queue is written for that decision rather than for the log.
- Open
- 5 alerts
- Crew today
- 2
- Fault types
- Flow, comms, budget
Every Alert Quotes Its Own Numbers
High-flow and low-flow alerts name the station's design flow and its measured flow, and those figures come from the same station record the rest of the console reads. The alert cannot describe a fault the data does not contain.
Faults Stay Physically Possible
A measured flow is pinned within the band its nozzle type can actually draw — an MP Rotator cannot pull a rotor's 34 GPM. A prototype that ignores that reads as convincing to everyone except the person who would be dispatched.
Open Counts Are Derived, Never Stored
A site's open-alert count is computed from the alert list rather than kept alongside it, so the badge on a site card and the rows on the alert page cannot disagree.
The Copy Says What Happened And What To Do
Errors do not apologise and empty states invite an action. "Check the solenoid and the decoder address before the next window" is a dispatch instruction; "an error occurred" is not.
Consumption that reconciles with the meter.
The district issues an allotment per billing cycle, and the manager's exposure is the gap between the pace they are on and the ceiling they were given. That figure is only useful if it agrees with the schedule that produced it — and in an early draft it did not.
- Cycle to date
- 3.96M of 5.47M gal
- Pacing
- 72% used, 9 days left
- Hand-set
- The allotment only
One Direction Of Flow, No Values Written Back
Sites are pure config; stations are generated from them; the packer produces the schedule; consumption is derived from the schedule. Nothing downstream writes a value back upstream, which is what makes the whole set consistent by construction.
The Bug That Set The Rule
An early draft stored month-to-date consumption on each site. The nightly volumes disagreed with the monthly figures by a factor of seven. Deriving them does not fix that error — it makes that class of error impossible, which is a different and better outcome.
Only The Allotment Is Entered
It is a policy figure issued by the water district, not something the controller knows. Everything else in the reporting view is computed, and the one hand-set number is the one the system genuinely cannot infer.
Overage Is Framed As A Percentage
Seasonal adjust scales every runtime in a program at once, and it is the control an operator actually reaches for when a site runs hot. So the reporting page states overage in the same units as the fix, rather than handing back a list of stations to edit by hand.
Reference ET Explains The Curve
Twelve cycles of consumption are shown against the evapotranspiration curve that drives them, so a rising month reads as weather before it reads as waste.
A report that names who owns the failure.
California's Model Water Efficient Landscape Ordinance sets a Maximum Applied Water Allowance per landscape, and a district can pass in aggregate while individual landscapes fail. Producing a number is the easy half; the report's job is to say which lever moves it.
- Standard
- CCR Title 23, Div. 2, Ch. 2.7
- District
- 93% of allowance
- Failing
- 3 landscapes
Two Causes, Two Owners
Where estimated total water use already exceeds the allowance, the planting design cannot comply at any schedule and the fix is horticultural. Where it does not, the schedule is simply applying too much. The report separates them, because sending both to the same person wastes the finding.
Areas Are Recovered, Not Surveyed
Hydrozone area is derived from station design flow — area = 96.3 × GPM ÷ PR — so the report needs no second set of hand-entered landscape areas that could disagree with the stations they describe. Precipitation rates are per nozzle type, with drip deliberately low, because point-source emitters wet a large gross area slowly.
The Basis Is Printed, Not Assumed
Two forms of the Special Landscape Area term are in circulation. The coefficient is a named constant and appears on the face of the report, so a reader can see which one was applied rather than having to reverse-engineer it.
Print-To-PDF Is The Export Format
The browser's own print path keeps the output as selectable vector text that paginates properly and carries the heading structure — all of which a canvas-drawn PDF loses — at no bundle cost. The print stylesheet forces paper colours regardless of theme, because a dark PDF is the wrong artefact.
It States That It Is Not A Filing
The page is computed from prototype data and reads like a submittable document. The provenance banner is load-bearing for exactly that reason.
Two planes, and colour that only means state.
Every colour was either sampled from Hunter's own assets or derived from one and then measured against the surface it sits on. The standard renders in-product and audits itself: nothing on it is a transcribed hex, and nothing on it is an estimated ratio.
- Type
- Inter Tight + IBM Plex Mono
- Themes
- Light default, dark opt-in
- Series colours
- One
There Is Deliberately No Categorical Palette
The brand set fails the separation checks — its two blues sit 7.8 ΔE apart, and green collides with orange under deuteranopia. So every chart is a single series against a single-hue ramp, which is the honest form for monitoring data anyway. A second series gets a different mark type, not a second hue.
Status Colour Never Travels Alone
Four status colours are reserved for state and never used as a chart series, and each one appears with a word beside it. Each also ships three steps — a mark that must clear 3:1, ink that must clear 4.5:1, and a tinted chip — because one value cannot clear both bars.
The Standard Measures Itself As You Read It
The brand page holds intent only: what a token is for, where it came from, and what bar it must clear. Two hidden probes resolve every token in both themes and the contrast figures are computed in the browser, so the documentation cannot drift from the product. The test suite fails the build if any graded pairing on that page misses its stated bar, in either theme.
It Caught A Real Failure Immediately
The original light dim-text token measured 3.18:1 on the alternate panel — below AA for the small text it carries. That was found by the measurement, not by eye, which is the whole argument for measuring.
Chip Separation Is Reported, Never Graded
There is no standard bar for how distinct a status chip must be from its panel, and a luminance ratio measures it badly — a pale tint on white differs mostly in hue. The light chips sit at 1.14–1.20 and the dark ones at 1.42–1.47, and that asymmetry is shown on the page rather than hidden. What carries the status is the ink and the word, and both of those are graded.
The Faces Are Named Placeholders
Inter Tight and IBM Plex Mono stand in for Hunter's own faces, and swapping them is a two-line change because nothing downstream names a family. The supplied reverse wordmark may sit on the deep brand colour or the night plane and nowhere else — a light-surface placement needs the positive lockup, not a CSS filter.
Checked on four devices, not asserted in a mockup.
The console is a working prototype rather than a set of screens, so the claims it makes are testable — and several of them are tested on every run, across Chromium, WebKit, a Pixel 5 and an iPhone 13.
- Specs
- 6 files, ~150 assertions
- Profiles
- 4 devices
- Routes
- 13, every one covered
The Schedule Is Asserted, Not Eyeballed
The suite checks that the watering window never overruns its bounds and that peak draw never exceeds the mainline capacity or the concurrency limit. A packer that quietly breaks its own constraints would still render a convincing picture.
No Page Scrolls Sideways On A Phone
Wide content — timelines, dense tables — scrolls inside its own container, and that is asserted on every route at every device size. Full density survives to the phone rather than being cut down to a summary.
The Check Found A Bug Nobody Would Have Reported
Screen-reader-only text is absolutely positioned, so inside an unpositioned table cell its containing block resolved to the viewport and it escaped the table's scroll clip — pushing the permissions matrix sideways. Invisible text, visible symptom, and only found because the rule was mechanical.
Both Themes Are Re-Measured In The Browser
The design-system spec reads the live custom properties and re-measures every threshold in each theme, so an unmeasured value fails in CI rather than in someone's eyes.
The Admin Forms Admit They Persist Nothing
There is no backend and no controller to write to. The forms are live to the touch, and the save bar says plainly that nothing is stored — a control that looks like it saved when it did not is worse than one that says so.
Process
I started from the constraint rather than the feature list: everything a commercial irrigation manager cares about is downstream of an eight-hour overnight window, so the window became the spine and every other view was designed as a consequence of it. Writing the rules first — two planes, status colour never travels alone, no categorical palette, derived values are never stored — meant later layout questions had answers that were not a matter of taste. Then I built it as a working responsive prototype, because a timeline drawn by hand can claim a night fits in the window, while one produced by a packer either does or does not.
Research methods
- Commercial and municipal irrigation workflow — watering windows, ordinance hours, and billing cycles
- Controller behaviour on ACC2 and ICC2 hardware: programs, seasonal adjust, and concurrency limits
- Hydraulics and horticulture — mainline capacity, nozzle precipitation rates, cycle & soak on slopes
- California MWELO (CCR Title 23, Div. 2, Ch. 2.7) — MAWA, ETWU, ETAF and the Special Landscape Area term
- Colour audit of the supplied brand assets: ΔE separation, deuteranopia simulation, measured contrast per surface
Deliverables
- Product rationale and a written design standard, rendered in-product and self-auditing
- Design system: two surface planes, reserved status language, measured token set in both themes
- 13 routes covering operations, reporting, and administration
- A constraint-honouring nightly scheduler, not a mocked timeline
- A MWELO compliance report with a print-native PDF export
- Working responsive front-end prototype, tested on four device profiles
The console
13The dark theme
4Responsive & phone
14Next project
Events by Ness