Commit Graph

90 Commits

Author SHA1 Message Date
a96a22aabc Shrink HUD stat tiles, control bar buttons, and nav bar
Direct feedback: HUD stat tiles filled nearly the whole screen above the
map, and the pause/stop/discard control bar and bottom nav bar were both
oversized. Caps the HUD grid to a fixed compact band instead of filling
down to the control bar, shrinks the handheld control bar from 72dp to
56dp (mounted mode stays 96dp for glove use), shrinks its icons from 32
to 24 outside mounted mode, and trims the nav bar from Material 3's
default 80dp to 64dp.
2026-08-25 13:02:51 -05:00
3b6aed2fa2 Rebuild release APK with FB-10/FB-11 map pan and Route Planner tile fixes 2026-08-25 11:56:23 -05:00
799471e5ff Document Phase 6 on-device verification for FB-10/FB-11 2026-08-25 11:51:45 -05:00
c1d326a52d Flip FB-11 to Done in the feedback tickets index 2026-08-25 11:38:03 -05:00
6d944cc85d Merge FB-11: fix Route Planner tile rendering (tile cache concurrency + logging) 2026-08-25 11:37:00 -05:00
0efc1700ff Flip FB-10 to Done in the feedback tickets index 2026-08-25 11:36:27 -05:00
dfd3e24062 FB-11: serialize FileTileCache mutations and log tile fetch failures
Fixes a real race in FileTileCache where two overlapping put() calls
(the persistent background map and a freshly-opened Route Planner map
both fetching tiles at once) could interleave at the _saveManifest
await point and silently lose a tile from the on-disk manifest. All
mutating and reading operations now go through a single serialization
queue. Also logs the URL and cause of tile fetch failures in
CachedTileProvider before rethrowing, and adds a concurrency
regression test. On-device verification was not performed (no
adb/emulator access in this environment); see the ticket's Outcome
section.
2026-08-25 11:36:21 -05:00
e44371ce9b Merge FB-10: fix map pan-recenter race 2026-08-25 11:34:54 -05:00
dde6ec833a FB-10: close the ambient-tick-vs-drag race that snapped the map back
A pointer down/up Listener around the FlutterMap now tracks whether a real
gesture is in progress, and didUpdateWidget's follow-recenter callbacks skip
the camera move while one is -- closing the timing window where a once-per-
second ambient GPS tick's postFrameCallback could land after a finger touched
the map but before flutter_map reported hasGesture: true, snapping the camera
back out from under an in-progress pan.
2026-08-25 11:34:05 -05:00
3810dd5a26 Write FB-10/FB-11 tickets: map pan-recenter race, Route Planner tile rendering 2026-08-25 11:10:23 -05:00
e06e410ad6 Add feedback: map still not pannable, Route Planner tiles still not rendering 2026-08-25 10:54:23 -05:00
4051416add Rebuild release APK with FB-06..FB-09 post-launch feedback fixes 2026-08-25 08:32:48 -05:00
c67072135b Document Phase 6 on-device verification for FB-06..FB-09 2026-08-25 08:32:32 -05:00
2abbaf5564 Flip FB-09 to Done in the feedback tickets index 2026-08-25 00:29:55 -05:00
8bc6351a6e Merge FB-09: HUD default sizing follows the title 2026-08-25 00:29:07 -05:00
7b452b7b81 FB-09: size default HUD widgets by title, not a fixed uniform cell
Adds minColSpanForLabel as a character-count proxy for how many grid
columns a metric's title needs, reworks HudWidgetLayout.defaultFor into
a left-to-right row-packing layout using it, enforces the same minimum
as a floor on manual resize, and splits _HudMetricValue's FittedBox so
the title renders at a fixed reference size while only the value
shrinks/grows to fill the remaining space.

Confirms a real, documented interaction with FB-08's _reflowRow: its
even column split has no awareness of a member's title-driven minimum
and can push one below it. Left unfixed per this ticket's own
Out-of-scope section -- _reflowRow's algorithm is FB-08's, not this
ticket's, to change.

Test count 426 -> 434, all green. flutter analyze unchanged (same 4
pre-existing, unrelated info-level issues).
2026-08-25 00:28:40 -05:00
aff59bfcf7 Flip FB-06/FB-07/FB-08 to Done in the feedback tickets index 2026-08-25 00:15:51 -05:00
de011cf9cf Merge FB-07: fix Route Planner opening on Null Island
# Conflicts:
#	docs/feedback/FB-07-route-planner-null-island.md
2026-08-25 00:15:10 -05:00
4b0029e463 Merge FB-06: always-interactive map with recenter control
# Conflicts:
#	docs/feedback/FB-06-always-interactive-map-recenter.md
2026-08-25 00:14:11 -05:00
c29a780d7c Merge FB-08: reflow HUD row widgets on hide/show
# Conflicts:
#	docs/feedback/FB-08-hud-reflow-on-hide.md
2026-08-25 00:13:10 -05:00
38ad2c75c3 FB-06: always-interactive map with a recenter control
Remove the hasPoints gate on RideMap's InteractionOptions so pan/zoom
work in both the idle and recording states. Add a recenter button
(GlassPanel + IconButton, matching the existing FloatingPill/
_OverflowMenu circular-chrome pattern) shown only when follow is true
and the rider has panned away; tapping it resumes following and jumps
the camera back to the latest point or ambient position without
changing zoom.

Adds 7 widget tests covering the always-on interaction flags and the
recenter button's visibility/behavior. flutter analyze stays clean;
flutter test goes from 412 to 419, all passing.

On-device marker-pulse verification was skipped: no Android tooling
(adb/emulator) is available in this environment.
2026-08-25 00:11:58 -05:00
97e3aaa5a0 FB-07: fix Route Planner opening on Null Island
A brand-new route (or one with fewer than two pins) now falls back to the
ambient GPS position for its initial map center instead of (0, 0) in the
middle of the ocean, reusing FB-01's ambientPositionProvider. Routes with
one or more real waypoints are unaffected. Falls back to (0, 0) as before
when no ambient fix is available.

Adds three widget tests covering the fixed-fix, no-fix, and
real-waypoint-wins cases. Emulator verification was attempted but the
Android emulator became unresponsive partway through and was abandoned
per the ticket's conservative-use guidance; details in the ticket's
Outcome section.
2026-08-25 00:10:32 -05:00
1a2e69cbf3 FB-08: reflow HUD row widgets when a metric is hidden or shown
setVisible now calls a new _reflowRow helper on the toggled metric's row,
spreading the remaining (or newly added) visible widgets evenly across the
4-column grid instead of leaving a blank cell where a hidden metric used to
be. Reflow is scoped to setVisible only; drag/resize behavior is unchanged.
2026-08-24 23:33:05 -05:00
d9aa412e15 Write FB-06..FB-09 feedback tickets (round two)
Turns the newly-added issues in docs/FEEDBACK.md into four more
self-contained tickets: always-interactive map with a recenter
button, Route Planner's Null Island centering bug, HUD reflow-on-hide,
and title-driven HUD sizing. Sequenced FB-08 before FB-09 since both
rework the same HUD layout files.
2026-08-24 23:26:49 -05:00
08f2cfbf02 Rebuild release APK with post-launch feedback fixes (FB-01..FB-05)
Ambient GPS drives the shared background map at street-level zoom
while idle; the idle Speed panel is gone until a ride starts; HUD
widgets are grid-snapped with auto-fit text; the Route Planner's map
render bug is fixed; routes can now be closed into a loop. All five
verified on the Android emulator, including one real bug (the first
ambient GPS fix not correcting the initial zoom) found and fixed
during that pass.
2026-08-24 20:02:18 -05:00
e06b371e2d Add autopilot-build skill: requirements-to-shipped-APK workflow
Codifies the ticket-writing, human-validation, wave-dispatch,
worktree-merge, emulator-verification, and APK-commit loop this repo's
UI-redesign and feedback batches were built with, written in ASD-STE100
so ticket files and subagent prompts stay unambiguous for a fresh
subagent with no conversation history.
2026-08-24 16:50:58 -05:00
f30f0063d0 FB-01: fix ambient GPS's first fix not correcting the initial zoom-2
Emulator verification caught a real bug the widget tests missed: the
shell background map builds before any location fix has resolved, so
ambientPosition starts null and the initial camera locks at zoom 2.
The first fix to arrive only re-centered (preserving that stale zoom
forever) instead of also jumping to ambientZoom.
2026-08-24 16:44:09 -05:00
6394f3bdd5 Flip FB-05 to Done in the feedback tickets index 2026-08-24 16:24:32 -05:00
07098991ef Merge FB-05: closed-loop routes 2026-08-24 16:23:16 -05:00
9ac0be6ee5 FB-05: closed-loop routes in Route Planner
Add an isClosedLoop toggle to route plans (schema v3 -> v4 migration),
threaded through the domain model, repository distance recomputation,
the overflow menu, and the drawn polyline, so a route can loop back to
its first waypoint.
2026-08-24 16:22:51 -05:00
57bc3c9233 Flip FB-04 to Done in the feedback tickets index 2026-08-24 16:13:45 -05:00
9faa9530d7 Merge FB-04: fix Route Planner map render + zoom 2026-08-24 16:12:30 -05:00
e491c9ee65 FB-04: fix Route Planner map failing to render on cold navigation
routePlanProvider's AsyncLoading state collapsed with "route doesn't exist"
via .valueOrNull, so a brand-new route's screen briefly rendered "This route
no longer exists." with no FlutterMap in the tree before the DB stream's
first emission arrived. Add a hasValue guard mirroring waypointsAsync's
existing pattern, and replace the route planner's hardcoded initialZoom of
14 with FB-01's ambientZoom constant for street-level parity with the rest
of the app.
2026-08-24 16:12:06 -05:00
9cc1cc779f Flip FB-01/FB-02/FB-03 to Done in the feedback tickets index 2026-08-24 16:05:56 -05:00
0ed6d2bebd Merge FB-03: grid-snapped HUD widgets with auto-fit text
# Conflicts:
#	docs/feedback/FB-03-grid-snapped-hud-widgets.md
#	lib/src/ui/record/record_screen.dart
2026-08-24 16:04:33 -05:00
9455574fbf Merge FB-02: hide idle Speed panel
# Conflicts:
#	docs/feedback/FB-02-hide-idle-speed-panel.md
2026-08-24 16:01:45 -05:00
8fabf4866e Merge FB-01: live street-level map
# Conflicts:
#	docs/feedback/FB-01-live-street-level-map.md
2026-08-24 16:00:06 -05:00
b75e0d7c38 Ignore .claude/ (agent worktrees) 2026-08-24 15:59:39 -05:00
756e3a9f53 FB-01: ambient GPS position drives the shared background map while idle
Adds ambientPositionProvider (never calls LocationSource.stop(), only the
idempotent start()) and wires it into ShellScaffold whenever no trip is
active, so the persistent background map centers and follows the device's
live GPS position at street-level zoom instead of sitting at (0,0)/zoom 2
until a recording starts. RideMap gains an ambientPosition param that the
existing chase-camera/pan-cancel/location-marker logic falls back to
whenever there are no recorded points, with recorded points always taking
priority. Also brings in the docs/feedback ticket set (FB-01..FB-05, README,
FEEDBACK.md) that this worktree's branch point predated.

Adds 10 tests (374 -> 384): RideMap-level ambient centering/chase/pan-cancel/
marker coverage in test/ride_map_test.dart, plus shell-wiring and
provider-level (never-calls-stop, mapEnabledProvider-off) coverage in
test/widget_test.dart. flutter analyze remains clean.
2026-08-24 15:58:45 -05:00
094e42a12f FB-02: hide the idle Speed panel until recording starts
Removes the idle GlassPanel/BigStat Speed-Status-Last ride block from
RecordScreen; the live map plus START control is now the entire idle
screen. HudEditOverlay is gated on !ui.isIdle. Migrates the five
affected widget tests to seed a recording trip and assert against the
HUD's own ValueKey(HudMetric.speed) widget instead of the old idle
Key('speed').

Also fixes a pre-existing gap this migration exposed: _HudMetricValue
had no mounted-mode awareness at all (no text scaling, and its
per-metric accent colours fall just under AA contrast against the
mounted theme's GlassPanel surface). Adds scale and mountedMode
params, reusing BigStat's own mechanisms (scale multiplier, explicit
onSurface ink) to fix both.
2026-08-24 15:53:14 -05:00
2d118b46a2 FB-03: grid-snapped HUD widgets with auto-fit, centered text
Replace free-form fractional HUD widget positioning with a 4x8 int grid
(col/row/colSpan/rowSpan), reject drag/resize moves that would overlap
another visible widget, and re-place a re-enabled metric into the next
free grid slot instead of stacking it on an occupier. Drag/resize now
track a local pixel offset during the gesture and only snap/commit once
at gesture end. HUD widget text is wrapped in FittedBox(scaleDown) so it
never overflows and stays centered at any grid size.
2026-08-24 15:53:00 -05:00
cf6b9d1d73 Write FB-01..FB-05 feedback tickets
Turns docs/FEEDBACK.md into five self-contained tickets, each grounded in
exact current code and a concrete implementation approach, so they can be
handed to independent subagents with no prior context.
2026-08-24 15:38:44 -05:00
fe4476596d Add post-launch feedback notes 2026-08-24 15:26:53 -05:00
04127d7237 Rebuild release APK with the UI redesign (UI-01 through UI-09)
All nine ui-redesign tickets are now complete: persistent tab shell,
Modern Professional Dark theme, monochrome dark map tiles, offline skeleton
map, the GlassPanel/FloatingPill component kit, customizable HUD widgets,
and the Map HUD / Plan / Rides History screen rebuilds. app-release.apk
force-added past .gitignore's build/ exclusion, same as the prior release
commit, so it can be pushed once a remote exists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
2026-08-24 14:35:17 -05:00
7c1d6570a2 UI-07: rebuild Rides History with search, activity filter, and real ride thumbnails
Restyles TripsScreen toward Map HUD's GlassPanel language: a search bar and
activity-filter dropdown, four distinct summary stat cards (replacing the
mockup's four-identical-cards artifact), and per-ride RideMap thumbnails
showing each ride's own recorded path instead of stock photos.

RideHistorySummary is a pure function rather than a provider, since folding
over an already-fetched trip list is cheap regardless of history length, and
it makes the aggregation directly unit-testable. Average speed is
distance-weighted across the whole history rather than averaging each ride's
own average.

Adds a showAttribution flag to RideMap (default true) to suppress the
TileAttribution control on tiny list thumbnails, where it was both a UX
problem and a test collision with each card's own activity icon.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
2026-08-24 14:32:00 -05:00
dccccb68b6 UI-06: rebuild Plan & Route Planning as a fullscreen map canvas
Replaces RoutePlannerScreen's AppBar-driven layout with a headerless,
full-opacity map canvas restyled toward Map HUD rather than copied from
this screen's own (drifted) Stitch export: a floating "tap to drop a pin"
tooltip before any pins exist, FloatingPill (Distance/Est. Time/Pins) once
they do, a "Start Route" floating button, a GlassPanel overflow menu
replacing the old app-bar actions (rename/delete/download), and a
pin-drop ripple micro-interaction. RoutesListScreen keeps its existing
list-with-+-button entry point (the IA question in the ticket's Risk
section, deferred as explicitly permitted) but restyles its rows as
GlassPanel cards.

"Start Route" gets real behavior instead of shipping as a dead button:
turn-by-turn route following doesn't exist yet (V3-09), so tapping it
starts an ordinary recording and switches to the Map tab -- useful today,
not a placeholder for a feature that isn't there. Est. Time honestly
shows "--" rather than fabricating an estimate the data model doesn't
back. Marching-ants route-line animation was dropped from scope: it isn't
in the acceptance criteria, and animating a dash phase under live map
pan/zoom is real complexity for a purely decorative effect.

Fixes a real, pre-existing bug found while working in this file: the
offline-tile download dialog still fetched from tile.openstreetmap.org
directly, a leftover from before UI-09 switched the live map to CARTO's
dark tiles -- a "successful" download would have cached tiles under keys
the dark-tile cache never reads from.

Also fixes a live Flutter framework warning the new GlassPanel-wrapped
ListTile surfaced (ink splashes need a Material ancestor, which
GlassPanel's DecoratedBox was blocking) and a real test bug (two pins
placed one directly under the new floating Start Route button in a
fixed test camera, causing a tap meant for the pin to hit the button
instead).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
2026-08-24 10:18:39 -05:00
fa6b1b8ef2 UI-05: rebuild Record screen as the Map HUD (design north star)
Replaces the scrolling stats-card layout with the full-bleed Map HUD:
UI-04's draggable/resizable HUD widgets float over UI-01's shared
background map (full opacity on this tab), and the old Pause/Stop/Resume/
Discard buttons become full-bleed, icon-only segments matching the
mockup's tertiary-container/error-container colors exactly. The idle
state keeps its prior compact layout deliberately -- HUD widgets only
appear once a ride exists, preserving the existing "a resting screen
must not look like a ride going nowhere" guarantee rather than
reinterpreting it.

State-machine logic (ticker, wakelock, speed subscription, error
handling) is untouched; every pre-existing record-screen test passed
unchanged against the rebuilt screen. Added tests that actually tap
Start/Pause/Stop and verify engine state changes, confirm HUD widgets
render over the map rather than replacing it, and verify the
mounted-mode speed digit's real contrast ratio against GlassPanel's
translucent surface specifically (the ticket's own named risk).

Corrects a UI-04 mistake found while implementing this ticket: the HUD's
default four metrics were ordered Speed/Distance/Elapsed/Max Speed, a
guess made before reading the actual Map HUD mockup HTML closely. The
real fixed row is Speed/Avg Speed/Dist/Time -- reordered HudMetric to
match and updated every test that asserted the old order.

Adds PulsingLocationMarker (UI-03) to RideMap's live usage via a new
showLocationMarker flag, and explicit tertiaryContainer/errorContainer
tokens to ripprColors so the control bar matches the design system's
literal values rather than an auto-derived tonal palette.

Verified end-to-end on a real emulator: Start, Pause, Resume, and Stop
all correctly drive the trip state machine with the full live map behind
everything. A lengthy false alarm during this verification (taps
appearing to do nothing) turned out to be a screenshot-scale
mis-measurement on the verification side, not an app defect -- resolved
by sampling pixel colors directly from the raw screenshot to find the
control bar's true on-screen position.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
2026-08-24 08:22:51 -05:00
bcface3529 UI-04: customizable, persisted HUD telemetry widget layout
Adds the full drag/resize/visibility infrastructure for a rider-owned HUD
layout: HudMetric (the stable 8-metric set), HudWidgetLayout (fractional
x/y/width/height + visible, with clamping to a legibility floor/ceiling
and JSON round-trip that degrades to sane defaults rather than crashing),
Config.hudLayout persistence, and a HudLayoutController that stays
in-memory-authoritative during an edit session and only writes through on
persist() -- never per drag frame.

DraggableResizableHudWidget and HudEditOverlay assemble the interaction:
edit mode is entered by a long-press on empty HUD space (not a specific
widget) and exited via Done or a tap on empty space; only while editing
does a widget attach any drag/resize gesture at all, so a normal tap can
never move one mid-ride by construction, not by an internal flag.

Fixes a real gesture-arena bug found during testing: outside edit mode, a
long-press landing on a widget was free to bubble to the overlay's
background long-press handler and wrongly enter edit mode. An inner no-op
GestureDetector of the same gesture type now absorbs it.

Adds a "Live HUD stats" Settings section, one switch per metric, that
persists immediately (unlike drag frames). Placed at the end of the
Settings list rather than in the middle -- inserting mid-list pushed
every later section below several existing tests' viewport assumptions.

Verified end-to-end on a real emulator including a full process restart:
resized a widget, force-stopped the app, relaunched, and the resize held.
No consuming screen exists yet (UI-05's job) -- verified via a throwaway
preview entry point, deleted after use.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
2026-08-23 20:45:02 -05:00
24f4e17e81 UI-03: shared floating-glass component kit
Adds GlassPanel (BackdropFilter + translucent fill + subtle border, values
lifted directly from the Stitch exports' own bg-surface/90 backdrop-blur-xl
border-outline-variant/30 treatment), FloatingPill (a GlassPanel shaped as
a capsule around an arbitrary number of labeled stat columns with
dividers), and PulsingLocationMarker (expanding-ring-plus-glow-dot,
matching the exports' pulse-ring keyframes exactly, with a static-dot
fallback when reduced motion is requested). Pure presentation -- every
UI-04/05/06/07 screen composes these rather than reimplementing blur/
border styling per screen.

Adds outlineVariant to ripprColors, needed for the panel border and pill
dividers and not previously wired up by UI-08.

Verified with a throwaway preview entry point (deleted after use, no
consuming screen exists yet) plus 8 new widget tests covering blur/
translucency/border structure, contrast in both themes, arbitrary stat
counts, and the pulsing marker's animation lifecycle and reduced-motion
fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
2026-08-23 20:22:19 -05:00
10040e6985 UI-02: animated skeleton map when tiles can't be fetched
Adds MapConnectivityState, a shared tracker of tile-fetch outcomes (cache
miss + network failure) that flips every map into an animated skeleton
after 3 consecutive failures and recovers on a single success -- either an
ordinary fetch succeeding, or (once TileLayer has been fully unmounted in
skeleton mode) a periodic single-tile probe every 15s. Detected at the
fetch level rather than via an OS connectivity API, since a captive portal
or degraded connection can report "online" while every real fetch times
out.

SkeletonMapLayer reuses the Stitch exports' 40px grid-overlay treatment
with a shimmer sweep, replacing TileLayer entirely (never fetching
underneath its own placeholder) while markers/polylines keep rendering
since they come from local data. RideMap and the route planner's
independent FlutterMap both wire this in via a plain skeletonMode bool.

Moved the tile-source constants into a new tiles/tile_config.dart so the
connectivity probe (in the app-layer composition root) doesn't need to
import from ui/ to build its request URL.

Verified end-to-end on a real emulator: cut network, cleared the tile
cache, confirmed the skeleton renders after real fetch failures, then
confirmed automatic recovery within one probe interval once network
returned -- not just via the widget/unit tests that also cover this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
2026-08-23 20:07:47 -05:00