bool returns on the five record_* surface inputs collapsed nine real
outcomes into one bit. The cascade we found in this round — silent
click drop because viewport_bounds wasn't measured yet, then
keyboard_focus stays None, then typing also "fails" — looked like
three independent symptoms but was one root cause hiding inside
that bit. Replace the bool with a #[must_use] WebSurfaceInputOutcome
enum so each rejection names itself at the call site.
Variants map 1:1 to real return points in web_surface.rs:
Applied / NoChange / Buffered — three distinct success-ish states
the controller already needed to disambiguate (only Applied notifies)
DroppedInvalidBounds — geometry rejected zero/NaN viewport
DroppedNoViewportBounds — input arrived before the viewport tracker
DroppedOutOfBounds — window position outside the viewport rect
DroppedZeroDelta — wheel rounded to zero device px
DroppedEmptyText — empty record_typed_text
DroppedNoKeyboardFocus — type without a prior click
DroppedFocusMismatch — focus belongs to another tab/url
Behavior preserved: Applied is the only notify trigger, matching the
old `true` semantics. Three new negative-path tests (no_viewport_bounds,
zero_delta, no_keyboard_focus) lock the named drops so a future
regression surfaces as a wrong variant in tests instead of a missing
repaint. cargo test ely_app --bin ely_app web_surface: 17 passed.