Cut along concerns rather than line counts, so each file answers one question:
_scrollfit.py (180) — does the content fit, and what layout follows?
_scrollkeys.py (176) — key/wheel bindings and the six scroll commands
_userintent.py (106) — was this focus move the user's doing, or the app's?
_scrollable.py (243) — the widget wiring and the public surface
_scrollfit takes an explicit FitTarget bundle rather than the surface, matching
the ArmingCollaborators pattern; UserIntentTracker owns the two fields that
answer "did the user just act?", which is the guard that keeps the viewport
from scrolling itself under someone who touched nothing.
The 45-line module docstring — the three Tk defaults this viewport exists to
prevent, and the failure behind each — moved to docs/scrollable-viewport.md
rather than being trimmed. It is load-bearing rationale, so it is referenced,
not deleted.
389 passed, 100% branch coverage — unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG