EPIC: Trim recorded route — start/end handles + suggested trim (backend/iOS/Android/web) #992

Closed
opened 2026-08-07 03:50:31 +00:00 by spikerj · 1 comment
Owner

Recorded activities often carry garbage points at the ends (forgot to stop the tracker and drove off; walked from the truck to the trailhead before starting). Let the user open a completed activity and trim points from either end, with a one-tap "suggested trim" that detects the sustained speed jump of driving.

Locked decisions:

  • New POST api/recorded-routes/{id}/trim — body is the range to KEEP as inclusive upload-order indices (matches the analysis-contract index space): {keepStartIndex, keepEndIndex, expectedPointCount}. 200 → full detail DTO; 400 out-of-range vs actual points-doc length / kept<2; 404 foreign id; 409 expectedPointCount mismatch (retry/idempotency guard). No-op → 200 no writes.
  • Semantics everywhere: inclusive slice; segment rebase (p.segment -= kept[0].segment); stats recomputed via the pinned RouteStatsCalculator formulas (untouched).
  • Write order (no transactions): delete recorded-route-analysis doc first (stale inclusive indices are the hazard; doc self-describes as safe-to-delete and lazily rematerializes) → replace PointsJson → recompute summary from the points doc (retry self-heals).
  • Suggested trim is a client-side pure function ported 3× like TrackStats (works offline on unsynced routes): arc speeds (segment-boundary arcs = 0; speedMps else haversine/dt), tail+leading scans, threshold 6.0 m/s (11.0 bike), sustain ≥20 s, tolerating ≤30 s slow breaks (traffic lights). Slow walk-to-trailhead deliberately not auto-detected — manual handles cover it.
  • UI on all clients: dual-thumb range slider bound to point indices + live map preview (kept polyline normal, trimmed ends dimmed, boundary handles), before/after stats, confirm dialog with exact delta. MapLibre Native has no draggable-annotation story, so no on-map dragging.
  • Synced routes must trim via the endpoint — re-flipping to pending-upload would duplicate server-side. Unsynced routes trim locally. Web is the trim surface for routes recorded on any device.
  • One canonical fixture trace; expectations duplicated verbatim into all four test suites.
  • No server-side undo v1 (snapshot-original is a possible follow-up).

Children: backend endpoint (lands first), then iOS / Android / Angular in parallel.


Child issues (per-repo, auto-close on merge)

This epic is complete when every box below is checked. Each child lives in the
repo that holds its fix, so fixes #N in a PR closes it automatically.

iOS

  • spikerj/spikersoft-ios#16 — iOS: route trim UI + RouteTrimmer (epic #992) (was #994)

Checklist generated by the umbrella-tracker migration, 2026-08-07 — Opus 5 Agent

Recorded activities often carry garbage points at the ends (forgot to stop the tracker and drove off; walked from the truck to the trailhead before starting). Let the user open a completed activity and trim points from either end, with a one-tap "suggested trim" that detects the sustained speed jump of driving. **Locked decisions:** - New `POST api/recorded-routes/{id}/trim` — body is the range to **KEEP** as inclusive upload-order indices (matches the analysis-contract index space): `{keepStartIndex, keepEndIndex, expectedPointCount}`. 200 → full detail DTO; 400 out-of-range vs actual points-doc length / kept<2; 404 foreign id; 409 `expectedPointCount` mismatch (retry/idempotency guard). No-op → 200 no writes. - Semantics everywhere: inclusive slice; segment rebase (`p.segment -= kept[0].segment`); stats recomputed via the pinned `RouteStatsCalculator` formulas (untouched). - Write order (no transactions): delete `recorded-route-analysis` doc **first** (stale inclusive indices are the hazard; doc self-describes as safe-to-delete and lazily rematerializes) → replace `PointsJson` → recompute summary from the points doc (retry self-heals). - Suggested trim is a **client-side pure function** ported 3× like TrackStats (works offline on unsynced routes): arc speeds (segment-boundary arcs = 0; `speedMps` else haversine/dt), tail+leading scans, threshold 6.0 m/s (11.0 bike), sustain ≥20 s, tolerating ≤30 s slow breaks (traffic lights). Slow walk-to-trailhead deliberately not auto-detected — manual handles cover it. - UI on all clients: dual-thumb range slider bound to point indices + live map preview (kept polyline normal, trimmed ends dimmed, boundary handles), before/after stats, confirm dialog with exact delta. MapLibre Native has no draggable-annotation story, so no on-map dragging. - Synced routes must trim via the endpoint — re-flipping to pending-upload would duplicate server-side. Unsynced routes trim locally. Web is the trim surface for routes recorded on any device. - One canonical fixture trace; expectations duplicated verbatim into all four test suites. - No server-side undo v1 (snapshot-original is a possible follow-up). Children: backend endpoint (lands first), then iOS / Android / Angular in parallel. <!-- BEGIN MIGRATED-CHILDREN --> --- ## Child issues (per-repo, auto-close on merge) This epic is complete when every box below is checked. Each child lives in the repo that holds its fix, so `fixes #N` in a PR closes it automatically. **iOS** - [ ] spikerj/spikersoft-ios#16 — iOS: route trim UI + RouteTrimmer (epic #992) <sub>(was #994)</sub> <sub>Checklist generated by the umbrella-tracker migration, 2026-08-07 — Opus 5 Agent</sub> <!-- END MIGRATED-CHILDREN -->
spikerj added the epic label 2026-08-07 13:43:04 +00:00
Author
Owner

Dissolved into per-repo issues as part of the umbrella-tracker breakup.

This epic could never auto-close from a merge, because Gitea only resolves
fixes #N within a single repo. Its work now lives where the code is:

iOS

Already shipped — verified against master and live prod during the migration, no issue filed:

The unit is tracked by the shared [Route trim] title prefix and by sibling
cross-links in each issue body. Closing here — the umbrella tracker is being
emptied.

— Opus 5 Agent

Dissolved into per-repo issues as part of the umbrella-tracker breakup. This epic could never auto-close from a merge, because Gitea only resolves `fixes #N` within a single repo. Its work now lives where the code is: **iOS** - spikerj/spikersoft-ios#16 — iOS: route trim UI + RouteTrimmer **Already shipped** — verified against master and live prod during the migration, no issue filed: - spikerj/spikersoft-issues#993 (complete) - spikerj/spikersoft-issues#995 (complete) - spikerj/spikersoft-issues#996 (complete) The unit is tracked by the shared `[Route trim]` title prefix and by sibling cross-links in each issue body. Closing here — the umbrella tracker is being emptied. — Opus 5 Agent
Sign in to join this conversation.