Building in public
How we built WaterShortcut
A practical product for a boring-but-expensive problem: why did my water bill change? This is the honest timeline—not a polished startup fairy tale.
Educational estimates only—not legal, financial, or plumbing advice.
The problem we kept hearing
People open a water bill, see a number that feels wrong, and then face a wall of tiers, sewer multipliers, and irrigation unknowns. Forums are full of “call the utility” and “check for a toilet leak”—true, but incomplete. We wanted a tool that separates usage vs rates vs sewer vs leak signals and ends with a next step you can actually take this week.
How long it took (real calendar)
- Late March 2026 — foundationShared AI service binding, first landing that pointed into a real diagnosis flow instead of a brochure.
- April 2026 — design system“Aqueduct Warmth” pass: product UI had to feel calm under stress (bills are stressful). Hero, tools, and trust copy got rewritten for mobile first.
- June–July 2026 — publisher + measurementGuides with interactive steps + “Ask Droplet” coach, AdSense hybrid (manual shells + permanent home Auto Ads with CTA exclusions), dual-send analytics (GA4 + first-party portfolio events).
- July 2026 — promotion readinessRemoved undeliverable “email me the kit” promises; pro kit delivers on-page immediately. Vignette no longer hijacks primary CTAs. Deploy guards so stale
dist/cannot silently win.
In git terms this leaf is ~90+ commits of iteration on one domain—not a weekend template. Most time went into trust, consent, ads policy, and not lying in the UI.
Architecture (what runs in production)
- Cloudflare Workers
- Hono
- SSR HTML + light React SPA
- D1 + KV
- Portfolio AI service binding
- Stripe via billing binding
- GA4 + first-party events
Primary product surfaces are server-rendered for speed and SEO. Interactive funnels (analyze, results, account) stay ad-free by policy. Editorial pages can carry restrained manual units; home keeps page-level Auto Ads with CSS google-auto-ads: ignore on CTAs/nav so conversion chrome stays clean.
What we rewrote (on purpose)
1. Ads without wrecking trust
We tried “ads everywhere,” then the opposite (home ads off). Current model: permanent Auto Ads on / only as an owner standing order, manual display slots mid-content + footer with reserved shell heights (CLS), Multiplex autorelaxed only when the unit type matches, and intentional excludes on hero/CTAs/tab bar.
2. Measurement that isn’t vanity
GA4 alone left the portfolio blind. We dual-send allowlisted camelCase events to a shared first-party ingest (no bill text, no emails). Engagement uses scroll_50 and heartbeat_15s with a short non-PII session token.
3. Promises the product can keep
Anything that said “we’ll email you” without a working sender was deleted or replaced with instant on-page delivery. That single class of honesty fixes more conversion than another hero gradient.
Upload a bill or enter numbers manually → see usage vs rate framing → leave with a leak check or calculator. Sources linked where we cite EPA WaterSense and similar public guidance.
What still sucks (so you don’t have to discover it)
- Utility data quality varies by city; some “find my provider” results are directory + AI research, not a universal official API.
- Paid acquisition is intentionally locked until spend attribution is boringly correct.
- City help pages stay conservative (noindex until evidence gate) so we don’t ship thin location spam.
FAQ
No. Educational estimates with sources. Confirm rates and policies with your utility or a licensed pro.
No for core diagnosis tools (upload, manual entry, leak check, calculators). Credits power deeper AI features.
Consent-gated GA4 plus PII-safe first-party portfolio events (page_view, cta_click, engagement). No bill contents in analytics.
If you’re building in public too
Ship the diagnosis loop before the growth theater. Instrument consent-aware analytics early. Keep interactive money/trust flows free of accidental interstitial traps. And write the page you’re reading now before you post Show HN—so the link destination is the story, not a homepage that assumes context.