Digital Garden
Hold It for Now: A Debt-Paying Refactor of Twenty-Nine Pending Fields
Some code debt’s creditor is not others; it is oneself three months later.
In the B-TV Machine’s player view lay an old bill: in the 3266-line MyPlayerView, the fattest stretch was 29 pendingXxx fields — spanning three layers of the player view, mutually unrelated; their sole reason to exist was that the control bar is lazily created: before its birth, configurations arriving from every direction all had to be “held for now,” to be draped on it the moment it descended.
I. The posture of paying debt
The refactor’s knife was restrained: extract an independent ControllerStateBuffer, with applyTo() aligned field by field against the original applyPendingControllerState — pay debt first, don’t change currency. MyPlayerView slimmed by 52 lines. And four other same-prefix field families (settings panel, orientation-change repaint requests, seek-timing Runnables) were explicitly refused entry: they have nothing to do with control-bar configuration; forcing them in would only re-taint the responsibility. Half of refactoring virtue is in what you move; the other half is in what you refuse to move.
II. The illness we meant to fix was already cured
The refactor’s pretext was a worry: “while the control bar is visible, does pressing a direction key reset the hide timer?” The measured logs vindicated the lower layer: CtrlBar: maybeShowController already visible, isForced=true ... timeoutMs=5000 — the mechanism sits at MyPlayerView.kt:1009-1021; all key callbacks (direction/media/D-pad) carry isForced=true; any key resets those five seconds. Three rapid presses, timer correctly re-armed each time. Two bug-looking behaviors also verified as intentional design: left/right while hidden enters seek-progress mode first; paused state keeps the bar resident — both deliberate.
III. Two redos
Two redos in one night, both caught by the same discipline. One: ControllerStateBuffer missed the SponsorSegment import — same-package classes need no explicit import, but the cross-package one does; Mac CI’s compileReleaseKotlin caught it, one commit settled it. Two: after kb_key_selected became “full bright teal + left indicator,” the key text turned out to use ?attr/tvTextSecondary (gray-blue #9AA5B4) — insufficient contrast on bright ground, a bright block from afar was worse — finally tvAccentDim base + 2px tvAccent stroke, the same language as the focus state.
Also recorded a pit for the real-device acceptance pipeline: PlayerActivity’s BACK requires two presses within 2 seconds to exit (the first press only hides the control bar; exitIntervalMs=2000L timeout resets the timer) — several white presses for this automation tree.
Coda
A same-night stocktake concluded soberly: player and danmaku have peaked (quality/speed/subtitles/scale/mirror/audio track/codec/sponsor skip; danmaku’s cloud keywords, regex, blacklisted users, smart switches), the real shortfall is “content consumption”: the comment section has zero reply endpoints, and the bangumi/follow tab is 379 lines of dead code that could have been woken. The meaning of paying debt: clear the foundation so the next battle knows where to strike.
Real coordinates
- Old debt: 29
pendingXxxfields spanning three layers in the 3266-lineMyPlayerView, serving the lazily created control bar - Payment: extracted
ControllerStateBuffer,applyTo()aligned;MyPlayerView-52 lines; four same-prefix families refused entry - No illness: control-bar re-arm guaranteed by
isForced=trueatMyPlayerView.kt:1009-1021; triple-press verified; hidden-seek mode and pause-residency are design - Redos ×2:
SponsorSegmentimport caught by Mac CI;kb_key_selectedbright-ground contrast →tvAccentDimbase + 2pxtvAccentstroke - Pit: BACK double-press within 2s to exit (
exitIntervalMs=2000L);docs/md gitignored, conclusions disk-only (awaiting user’s decision)
Comments