Skip to content

feat(ui): set time settings on a two-column minutes-and-seconds roller - #150

Merged
PurpleSentinel merged 1 commit into
mainfrom
feature/time-roller-selector
Aug 19, 2026
Merged

PurpleSentinel merged 1 commit into
mainfrom
feature/time-roller-selector

Conversation

@PurpleSentinel

Copy link
Copy Markdown
Contributor

Closes #149.

The pickers could not reach their own fields' values

Not merely clumsy — incomplete. Every one of these stores and validates far more than
the picker offered:

Field Validated Picker reached
average_lap_seconds 0-3599 (59:59) 180 s (3:00)
session_duration 1-1440 min 60 min
rest_duration 0-1440 min 60 min

A circuit with a lap over three minutes could not be configured at all. The lists could not
just be extended either: kValueChoiceCapacity is twelve.

Interaction

Both columns live at once, the vertical gesture acting on whichever one it starts over, so
minutes and seconds need no focus switched between them. Columns wrap within themselves and
never carry — a carry is unpredictable when the two are targeted independently, and a
flick rolling seconds several times round would otherwise drag minutes with it.

Why the input layer had to grow

A discrete swipe per step needs up to 59 gestures to cross a column, which is worse than
the single press it replaces. So the screen tracks the pointer continuously and a flick
coasts with exponential decay. InputAction deliberately reduces touch to named actions and
discards position; attach_drag_input adds a parallel continuous path. The discrete contract
is untouched and every other screen still uses it.

Saving needed a longer hold, found on the device

Built against LVGL's LV_EVENT_LONG_PRESSED — 400 ms — saving fired while the driver was
still taking hold of the unit. Captured before the fix, with no drag samples at all first:

open AVERAGE LAP at 0 s
set  AVERAGE LAP to 600 s      <- 2.2 s after opening, nothing rolled
set  AVERAGE LAP to 1020 s     <- 1.4 s after opening, nothing rolled

Now a measured 1200 ms hold, cancelled if the finger wanders more than 14 px, with a bar
filling as it counts — a longer hold without feedback would read as an unresponsive screen,
a worse defect than the one being fixed. HoldToSave is separate from TimeRoller because
it concerns the finger rather than the value, and carries no LVGL so it is host-tested.

Cancelling is a horizontal swipe: down drives the digits and can no longer mean back.

Settings v5, and a log format bump

Durations are stored in seconds now, which the old uint16 cannot hold — 1439 minutes of
seconds is 86,399. They widen to uint32 with a v5 format and a migration; everything
before v5 held whole minutes, so migrating is exact. Tested against a real v4 blob.

The log record mirrors those fields, so kLogFormatVersion goes to 2 and older logs are
rejected by version rather than silently misread as very short sessions. That consumes the
bump #137 was waiting for.

Verification

  • 35 host suites (15 new) and 70 simulator tests; ranges, wrap without carry, drag
    accumulation, flick decay and settling, catching a coasting column, commit clamping, the
    zero-means-unset coupling, and the hold threshold and wander cancel
  • On the panel, a 175-sample editing session:
drags=25  col=sec value=17:03
drags=50  col=min value=28:05
drags=75  col=min value=0:05
drags=150 col=min value=2:00
set AVERAGE LAP to 115 s

Column targeting follows the touch with no focus switch, and wrap-without-carry is visible
in real data
— seconds hold at :05 while minutes roll 28 -> 0 -> 5 -> 3. One deliberate
save, no accidental commits, no watchdog triggers. Probe removed and its absence confirmed on
the final image.

Also

.gitignore now covers core.*: a failing host test binary drops core dumps in the
repository root, and seven of them were sitting there from this branch's own debugging.

The preset pickers could not reach most of the values their own fields hold. Average
lap offered eleven choices stopping at 3:00 against a field validated to 59:59, so a
circuit with a longer lap could not be configured at all; session and rest duration
stopped at 60 minutes against a validated 24 hours. The lists could not simply be
extended either, since kValueChoiceCapacity is twelve.

Both columns are live at once. The vertical gesture acts on whichever column it starts
over, so minutes and seconds are adjusted without a focus to switch between them.
Columns wrap within themselves and never carry: a carry is unpredictable when the two
are targeted independently, and a flick that rolls seconds several times round would
otherwise drag minutes with it.

A discrete swipe per step would have needed 59 gestures to cross a column, which is
worse than the single press it replaces, so the screen tracks the pointer continuously
and a flick coasts with exponential decay. That needed a continuous path through the
input layer, which until now deliberately reduced touch to named actions. The discrete
contract is untouched and everything else still uses it.

Saving is a measured 1200 ms hold rather than LVGL's 400 ms long press. On the device
that threshold committed a value 1.4 seconds after the screen opened, before anything
had been rolled. The hold is cancelled if the finger wanders more than 14 px, and a bar
fills as it counts, because a longer hold with no feedback reads as an unresponsive
screen rather than a deliberate one. Cancelling is a horizontal swipe: down now drives
the digits and can no longer mean back.

Durations are stored in seconds, which needed a settings v5 and a migration, since 1439
minutes of seconds does not fit the uint16 they used to occupy. Everything before v5
held whole minutes, so migrating is exact. The log record mirrors those fields, so
kLogFormatVersion goes to 2 as well and older logs are rejected by version rather than
silently misread as very short sessions; that consumes the bump #137 was waiting for.

Fifteen host tests cover the ranges, wrap without carry, drag accumulation, flick decay
and settling, catching a coasting column, clamping to a field's floor on commit, the
zero-means-unset coupling to lower display, and the hold threshold and its wander
cancel. Verified on the panel across a 175-sample editing session: column targeting
follows the touch, minutes wrapped 28 to 0 while seconds held at :05, and the session
ended in exactly one deliberate save with no accidental commits.

Closes #149

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@PurpleSentinel
PurpleSentinel merged commit 358f50b into main Aug 19, 2026
3 checks passed
@PurpleSentinel
PurpleSentinel deleted the feature/time-roller-selector branch August 19, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace preset time pickers with a two-column rolling selector for average lap, session and rest

2 participants