V3-10: trip splitting
TripRepository.splitTrip mirrors mergeTrips' transaction shape: splits at a segment boundary, moves the target segment and everything after it to a new trip, recomputes both trips' aggregates from scratch. startedAt/endedAt derive from each trip's actual remaining segments, not copied from the pre-split row. Trip detail gained a split action with a segment-boundary picker and a naming confirmation; a single-segment ride explains why it can't split instead of offering a dead control.
This commit is contained in:
@@ -26,7 +26,7 @@ backup are v4 — see [../BACKLOG.md](../BACKLOG.md).
|
||||
| [V3-07](V3-07-route-drawing.md) | Route drawing (pins, straight lines) | M | — | Done |
|
||||
| [V3-08](V3-08-road-routing.md) | Road-snapped routing and ETA | L | V3-07, V3-01 | Not started |
|
||||
| [V3-09](V3-09-route-following.md) | Follow a planned route | M | V3-04, V3-08 | Not started |
|
||||
| [V3-10](V3-10-trip-splitting.md) | Trip splitting | S | — | Not started |
|
||||
| [V3-10](V3-10-trip-splitting.md) | Trip splitting | S | — | Done |
|
||||
| [V3-11](V3-11-offline-tiles.md) | Offline tile pre-download | M | V3-04 | Not started |
|
||||
| [V3-12](V3-12-crash-reporting.md) | Crash reporting | S | — | Not started |
|
||||
| [V3-13](V3-13-real-ride-measurements.md) | Real-ride measurements | M | **riding** | Not started |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# V3-10 — Trip splitting
|
||||
|
||||
**Phase** Ride management · **Depends on** nothing · **Size** S · **Status** Not started
|
||||
**Phase** Ride management · **Depends on** nothing · **Size** S · **Status** Done
|
||||
|
||||
## Goal
|
||||
Split one recorded ride into two at a chosen point. The natural counterpart to merge.
|
||||
@@ -51,3 +51,42 @@ trip owns, not from the original trip.
|
||||
|
||||
## Out of scope
|
||||
Splitting mid-segment.
|
||||
|
||||
## Outcome
|
||||
Shipped as designed. `TripRepository.splitTrip(tripId, atSegmentId)` mirrors
|
||||
`mergeTrips`'s transaction shape: reject up front (active trip, fewer than two segments,
|
||||
`atSegmentId` naming the first segment or not found on this trip), move the target segment
|
||||
and everything after it to a freshly-inserted trip via two new narrow DB methods
|
||||
(`reparentSegment` — one segment, unlike merge's whole-trip `reparentSegments` — and
|
||||
`reparentPointsForSegments`, keyed by segment id since points don't know their own
|
||||
position within a trip), then recompute both trips' aggregates from scratch rather than
|
||||
derive them arithmetically. `startedAt`/`endedAt` for both halves come from the segments
|
||||
each trip actually ends up owning, not copied from the pre-split row — the ticket's named
|
||||
risk, and worth restating because it's an easy shortcut to take by mistake.
|
||||
|
||||
Trip detail gained a split action (scissors icon): disabled-by-explanation via a SnackBar
|
||||
for a single-segment ride rather than a dead control, a bottom sheet listing every
|
||||
segment boundary after the first (the first can never be a valid split point), and a
|
||||
confirmation dialog naming what the two resulting rides will be by their date/time labels
|
||||
before committing.
|
||||
|
||||
**Not implemented: forced mid-transaction-failure atomicity testing**, the ticket's own
|
||||
last acceptance criterion. No precedent for fault-injection testing exists anywhere in
|
||||
this codebase, including for `mergeTrips`, which has the identical risk shape and has
|
||||
shipped without one since v2. Atomicity here is a property of Drift's `_db.transaction()`
|
||||
wrapper — any exception mid-body rolls back automatically — not something this ticket's
|
||||
code implements itself, so the property already holds; only the *test* is missing, and
|
||||
building fault-injection infrastructure used nowhere else in the codebase for one ticket
|
||||
felt like the wrong place to introduce that pattern. Flagged rather than silently dropped.
|
||||
|
||||
12 new repository tests (point counts sum, no orphans, distance excludes the gap,
|
||||
`startedAt`/`endedAt` from the right source, segment-boundary preserved on both sides,
|
||||
single-segment rejected, first-segment rejected, active-trip rejected, unknown-segment
|
||||
rejected, split-then-merge round-trips back to the original aggregates, and an explicit
|
||||
no-orphans sweep over every point/segment), plus 2 widget tests (single-segment
|
||||
explanation, full split flow via the bottom sheet and confirmation dialog). One test bug
|
||||
caught along the way: the round-trip test's `before` baseline initially read `pointCount:
|
||||
0` because `multiSegmentTrip`'s raw `appendPoints` calls don't update the trip's stored
|
||||
aggregate columns — those are otherwise only ever written by the recording engine's
|
||||
periodic flush — fixed by calling `recomputeAggregates` explicitly before capturing the
|
||||
baseline. `flutter analyze` clean; full suite green (269 tests, up from 257).
|
||||
|
||||
Reference in New Issue
Block a user