Cue-sheet review screen before riding (issue #55) #118

Merged
robert merged 1 commit from area/cue-sheet-review into main 2026-09-05 23:32:26 +02:00
Owner

Closes #55.

Adds CueSheetReviewScreen (companion/route/.../route/enrich/CueSheetReviewScreen.kt): a scrollable list of every cue with direction icon, distance-along-route and street name; which tier/enricher produced the sheet (visibly less trustworthy when it's the geometric guess, tier 3); a per-cue "Guess" badge for NAV_CUE_CONFIDENCE = 0 cues; hand delete and direction-correct, persisted via a new CueSheetCache.updateCues; and total cue count plus route distance as a sanity check against the planner. Reached from a new "Review cues" button on RouteDetailScreen, following RouteLibraryScreen's existing local-state screen-toggle pattern (this project has no navigation library, per #46's audit).

Two new detection heuristics (companion/core/.../route/enrich/CueSheetReviewAnalysis.kt)

  • findDenseCueClusters: 3+ cues within 40 m of route distance are flagged as a likely curve-mis-read-as-turns cluster. The 40 m window deliberately mirrors D27's own ±20 m heading-delta window for the (still unbuilt) geometric fallback — the same physical scale a real turn occupies. Three, not two, because two close cues is unremarkable (a mini-roundabout followed by a side turn); three real junctions that close together would be a safety hazard no planner produces.
  • findGeometricMismatches: compares a cue's claimed direction against an independently computed heading-delta turn (±20 m window, matching D27 again) via two new RouteGeodesy helpers (bearingDegrees, pointAtDistanceMeters). The straight/turn threshold is 20°, intentionally more sensitive than D27's own old 25° figure — this check's false positives cost a rider one extra glance, not a wrong instruction, so it leans toward flagging.

Both are unit-tested in :companion:core against real GpxCueEnricher (tier 0) output, including a real fixture (bikerouter-style-cues.gpx) where the mismatch check correctly flags both real cues once RDP simplification is accounted for (neither cue's snapped point sits at the polyline's actual corner) — a genuine finding, not a fabricated one.

Honest scope limits

  • BRouterEnricher (#27), MatchingEnricher (#28) and GeometryEnricher (#29) are all still open, unimplemented issues — only tier 0 is wired into any build today (see RouteStore.kt). Every display in this screen dispatches generically on EnrichmentTier/Direction (exhaustive when, no hardcoded tier-0-vs-tier-3 special case), so it needs no changes once they land — but the "entirely tier 3" banner and the geometric-guess badge are tested only against hand-built Cue data tagged EnrichmentTier.GEOMETRY, since there is no real GeometryEnricher yet to produce that data for real. This PR does not fabricate one.
  • The issue's 2026-09-01 update asks for "BRouter's measured divergence from the source GPX... shown for the route as a whole." That number is BRouterEnricher (#27)'s own job to compute (D25), and nothing in this codebase carries a divergence figure today for this screen to read — building UI for a number nothing produces would mean inventing a fake one, so this PR omits it rather than do that. Added a dependency edge onto #27 in the tracker for this reason.

RoutePreview gains an optional highlightPoint parameter (a dot drawn on the existing schematic polyline sketch) for "tapping a cue shows it on a map preview" — reusing the #25/#40 schematic preview rather than building a second polyline renderer, since a real georeferenced map doesn't exist yet (#42).

Verification

Real builds only, on Robert's local Android SDK/JDK 21:

  • ./gradlew :companion:core:test — 342 tests, all green (including the new CueSheetReviewAnalysisTest and the new CueSheetCache.updateCues coverage in CueSheetCacheTest).
  • ./gradlew :companion:route:assembleDebug and ./gradlew :companion:assembleDebug — both green.

de/en string parity kept from the start, per docs/TEAM.md's Kestrel convention.

Closes #55. Adds `CueSheetReviewScreen` (`companion/route/.../route/enrich/CueSheetReviewScreen.kt`): a scrollable list of every cue with direction icon, distance-along-route and street name; which tier/enricher produced the sheet (visibly less trustworthy when it's the geometric guess, tier 3); a per-cue "Guess" badge for `NAV_CUE_CONFIDENCE = 0` cues; hand delete and direction-correct, persisted via a new `CueSheetCache.updateCues`; and total cue count plus route distance as a sanity check against the planner. Reached from a new "Review cues" button on `RouteDetailScreen`, following `RouteLibraryScreen`'s existing local-state screen-toggle pattern (this project has no navigation library, per #46's audit). ### Two new detection heuristics (`companion/core/.../route/enrich/CueSheetReviewAnalysis.kt`) - **`findDenseCueClusters`**: 3+ cues within 40 m of route distance are flagged as a likely curve-mis-read-as-turns cluster. The 40 m window deliberately mirrors D27's own ±20 m heading-delta window for the (still unbuilt) geometric fallback — the same physical scale a real turn occupies. Three, not two, because two close cues is unremarkable (a mini-roundabout followed by a side turn); three real junctions that close together would be a safety hazard no planner produces. - **`findGeometricMismatches`**: compares a cue's claimed direction against an independently computed heading-delta turn (±20 m window, matching D27 again) via two new `RouteGeodesy` helpers (`bearingDegrees`, `pointAtDistanceMeters`). The straight/turn threshold is 20°, intentionally *more* sensitive than D27's own old 25° figure — this check's false positives cost a rider one extra glance, not a wrong instruction, so it leans toward flagging. Both are unit-tested in `:companion:core` against real `GpxCueEnricher` (tier 0) output, including a real fixture (`bikerouter-style-cues.gpx`) where the mismatch check correctly flags *both* real cues once RDP simplification is accounted for (neither cue's snapped point sits at the polyline's actual corner) — a genuine finding, not a fabricated one. ### Honest scope limits - `BRouterEnricher` (#27), `MatchingEnricher` (#28) and `GeometryEnricher` (#29) are all still open, unimplemented issues — only tier 0 is wired into any build today (see `RouteStore.kt`). Every display in this screen dispatches generically on `EnrichmentTier`/`Direction` (exhaustive `when`, no hardcoded tier-0-vs-tier-3 special case), so it needs no changes once they land — but the "entirely tier 3" banner and the geometric-guess badge are tested only against hand-built `Cue` data tagged `EnrichmentTier.GEOMETRY`, since there is no real `GeometryEnricher` yet to produce that data for real. This PR does not fabricate one. - The issue's 2026-09-01 update asks for "BRouter's measured divergence from the source GPX... shown for the route as a whole." That number is `BRouterEnricher` (#27)'s own job to compute (D25), and nothing in this codebase carries a divergence figure today for this screen to read — building UI for a number nothing produces would mean inventing a fake one, so this PR omits it rather than do that. Added a dependency edge onto #27 in the tracker for this reason. `RoutePreview` gains an optional `highlightPoint` parameter (a dot drawn on the existing schematic polyline sketch) for "tapping a cue shows it on a map preview" — reusing the #25/#40 schematic preview rather than building a second polyline renderer, since a real georeferenced map doesn't exist yet (#42). ### Verification Real builds only, on Robert's local Android SDK/JDK 21: - `./gradlew :companion:core:test` — 342 tests, all green (including the new `CueSheetReviewAnalysisTest` and the new `CueSheetCache.updateCues` coverage in `CueSheetCacheTest`). - `./gradlew :companion:route:assembleDebug` and `./gradlew :companion:assembleDebug` — both green. de/en string parity kept from the start, per docs/TEAM.md's Kestrel convention.
Cue-sheet review screen before riding (issue #55)
Some checks failed
dev-artifact / build-pbw (push) Failing after 0s
dev-artifact / build-apk (push) Failing after 0s
dev-artifact / publish (push) Has been skipped
fast-lane / host-c-tests (push) Failing after 0s
fast-lane / jvm-tests (push) Failing after 0s
fast-lane / pebble-build (push) Failing after 0s
fast-lane / lint-and-secrets (push) Failing after 0s
fast-lane / meta-declares-required-jobs (push) Failing after 0s
fast-lane / host-c-tests (pull_request) Failing after 0s
fast-lane / jvm-tests (pull_request) Failing after 0s
fast-lane / pebble-build (pull_request) Failing after 0s
fast-lane / lint-and-secrets (pull_request) Failing after 0s
fast-lane / meta-declares-required-jobs (pull_request) Failing after 0s
1be7e6eb48
Adds CueSheetReviewScreen: a scrollable list of every cue with direction
icon, distance-along-route and street name; which tier/enricher produced
the sheet (visibly less trustworthy when it's the geometric guess, tier 3);
a per-cue "Guess" badge for NAV_CUE_CONFIDENCE = 0 cues; hand delete and
direction-correct, persisted via a new CueSheetCache.updateCues that writes
back through the same cached-row mechanism CueSheetCache already owns
(#30), not a second edit-storage path; and total cue count plus route
distance as a sanity check against the planner. Reached from a new "Review
cues" button on RouteDetailScreen, following RouteLibraryScreen's existing
local-state screen-toggle pattern (this project has no navigation library).

Two new detection heuristics in :companion:core (route/enrich/
CueSheetReviewAnalysis.kt), both with documented, reasoned thresholds:

- findDenseCueClusters: 3+ cues within 40 m of route distance (the same
  +/-20 m window D27 defines for the geometric fallback's own heading-delta
  check) are flagged as a likely curve-mis-read-as-turns cluster.
- findGeometricMismatches: compares a cue's claimed direction against an
  independently computed heading-delta turn (+/-20 m window, 20 degree
  straight/turn threshold - intentionally more sensitive than D27's old
  25 degree figure, since a false positive here only costs a rider one
  extra glance) built on two new RouteGeodesy helpers, bearingDegrees and
  pointAtDistanceMeters. Both are exercised against real GpxCueEnricher
  (tier 0) output, including a real fixture where the check correctly
  flags both cues in bikerouter-style-cues.gpx once RDP simplification is
  taken into account.

Honest scope limits, stated in code and here:

- BRouterEnricher (#27), MatchingEnricher (#28) and GeometryEnricher (#29)
  remain open, unimplemented issues. Every display in this screen dispatches
  generically on EnrichmentTier/Direction (exhaustive `when`, no hardcoded
  tier-0-vs-tier-3 special case) so it needs no changes once they land, but
  the "entirely tier 3" banner and the geometric-guess badge are tested only
  against hand-built Cue data tagged EnrichmentTier.GEOMETRY - there is no
  real GeometryEnricher yet to produce that data. This PR does not fabricate
  one.
- "BRouter's measured divergence from the source GPX, shown for the route
  as a whole" (the issue's 2026-09-01 update) is not implemented. That
  number is BRouterEnricher's (#27) own job to compute, and nothing in this
  codebase carries a divergence figure today for this screen to display -
  building UI for a number nothing produces would mean inventing a fake
  one. Issue #55 now depends on #27 in the tracker for this reason.

RoutePreview gains an optional highlightPoint parameter (a dot drawn on the
existing schematic polyline sketch) for "tapping a cue shows it on a map
preview" - reusing the #25/#40 schematic preview rather than building a
second polyline renderer, since a real georeferenced map doesn't exist yet
(#42).

Verified with real builds: ./gradlew :companion:core:test (342 tests,
including CueSheetReviewAnalysisTest and the new CueSheetCache.updateCues
coverage) and ./gradlew :companion:route:assembleDebug /
:companion:assembleDebug, all green.

Claude-Session: https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt
robert merged commit ea4f6d24ff into main 2026-09-05 23:32:26 +02:00
Sign in to join this conversation.
No description provided.