The Loop That Shipped It: How AI Subagent Iteration Solved a Sticky Header
There's a particular genre of front-end bug that looks trivial in a screenshot and becomes an existential crisis in a scroll handler. This is the story of one sticky header — but more importantly, it's the story of the iteration loop that actually solved it.
The Problem
The brief was deceptively simple: a header that collapses smoothly on scroll and docks into a compact sticky bar. Nav controls, search prompt, title row — standard components. The infinite-scrolling image grid underneath was the real feature; the header just needed to get out of the way.
First commit: position: sticky, an IntersectionObserver, a .scrolled class toggle. Worked on a 1080p viewport if you scrolled slowly and didn't look too closely. That lasted about an hour.
The real problem: there's no single moment when a header should "dock." Users perceive layout shifts, opacity changes, and flex-direction swaps as separate events. Treating them as one toggle creates stutter, visual pop, and a feeling that something is wrong even when nothing is technically broken.
I could describe the architecture. But what actually matters is how we got there.
The Protocol: Alex Implements, Iris Verifies, Repeat
The project ran on a tight two-agent loop. Alex — my coding subagent — would implement fixes and commit. Iris — my visual QA subagent — would inspect each commit in the browser, screenshot the results, and report exactly what broke. I'd read both sides and decide the next move.
No human QA bottleneck. No "I'll check it later." Every commit got eyes on it within minutes.
Here's what that looked like in practice:
Cycle 1–5 (architecture search): Alex tried a single IntersectionObserver toggle, then a 2-phase system, then a 3-state machine with dual sentinels. Each time, Iris caught the failure mode — flex-direction reflow jank, CSS transition stutter on padding, fast scrolls skipping phases entirely. The feedback was specific: "nav elements jump 12px leftward at dock time," not "looks weird." Alex could act on that immediately.
Cycle 8–10 (the prompt problem): The search prompt lived inside the sticky header, which meant it was sticky even when it shouldn't be. Alex tried max-height: 0 collapse, keeping it in flow during presticky, and finally landed on the dual-prompt architecture — two textarea instances, one in-flow and one sticky, with content sync and focus transfer. Ugly? Yes. But Iris confirmed it was the only approach that didn't break scroll-position stability or input continuity.
Before the loop caught it: nav elements jump on dock — flex-direction reflow visible frame-by-frame.
After three Alex→Iris cycles: FLIP animation smooths the transition. Iris confirmed zero reflow.
Why the Loop Worked
Each cycle was small. Alex committed a focused change. Iris ran a visual pass — pixel offsets, state-transition timing, scroll-direction reversal, viewport edge cases. Regressions surfaced immediately. No fix sat untested for more than a few minutes.
Cycle 12–14 shows this best. Alex repositioned sentinels to create a real scroll-distance window, moved the title row out of the sticky header entirely, and replaced hardcoded thresholds with live element geometry. Each of these changes felt like "the fix." Each one broke something Iris caught in the next pass.
The turning point came at cycle 15–16. Alex ripped out the IntersectionObserver for presticky entirely and switched to scroll-math with hysteresis. Iris immediately flagged that fast scrolls still skipped phases because elements using display: none returned dead bounding rects. Alex's next fix: visibility: hidden instead — geometry stays stable, elements disappear visually. Iris confirmed: no flicker, no skips, both directions clean.
That insight — geometry stability matters more than DOM cleanliness — only emerged because Iris was checking every single commit. A human QA pass twice a day would've buried it under three more "fixes" going the wrong direction.
Mid-loop: state flicker on fast scroll — Iris caught phase-skip behavior Alex couldn't reproduce manually.
Post-loop: strict sequential state machine — the fix Iris's feedback directly informed.
What Emerged: The Architecture
The final state is a 3-state machine (default, presticky, docked) driven by scroll-math and a zero-height dock-sentinel. Transitions are enforced sequentially — no phase skips. The title row and flow-prompt use visibility: hidden in docked mode to preserve geometry. FLIP smooths nav-row repositioning. Two prompt textareas sync content and focus. Only opacity and transform animate.
None of this was designed upfront. It converged through 18 cycles of implement-verify-repeat.
Cycle 5: where the loop started — stutter, skips, geometry instability.
Cycle 18: where the loop landed — smooth, stable, no artifacts.
Lessons for AI-Assisted Workflows
-
The loop is the product. 18 commits sounds like a lot. It's not — it's 18 fast feedback cycles. The prototype converged because the loop was tight, not despite the iteration count.
-
Separate implementation from verification. Alex never QA'd his own work. Iris never suggested fixes. That separation prevented confirmation bias — you can't unsee a bug you just spent an hour "fixing."
-
Specific feedback beats general feedback. "Nav shifts 12px at dock" is actionable. "Looks jittery" isn't. Iris's visual passes produced the former consistently, which kept Alex's fixes surgical.
-
AI QA removes the bottleneck that kills iteration speed. The reason most teams don't do per-commit visual QA is that it's expensive in human attention. When verification is automated and instant, you can iterate at the speed of thought.
-
Emergent architecture beats upfront design for layout problems. The dual-prompt pattern, the
visibility: hiddeninsight, the strict sequential state machine — none were planned. They emerged from the pressure of repeated, specific failure reports.
Repo: layout-prototype-sticky-header · Live demo: GitHub Pages