Skip to content

Fix/trip display - #1058

Draft
jona159 wants to merge 8 commits into
devfrom
fix/trip-display
Draft

Fix/trip display#1058
jona159 wants to merge 8 commits into
devfrom
fix/trip-display

Conversation

@jona159

@jona159 jona159 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Type of Change

  • Dependency upgrade
  • Bug fix (non-breaking change)
  • Breaking change
    • e.g. a fixed bug or new feature that may break something else
  • New feature
  • Code quality improvements
    • e.g. refactoring, documentation, tests, tooling, ...

Implementation

Checklist

  • I gave this pull request a meaningful title
  • My pull request is targeting the dev branch
  • I have added documentation to my code
  • I have deleted code that I have commented out

Additional Information

  • This PR closes #

Summary by CodeRabbit

  • New Features
    • Mobile maps now highlight the latest trips and support trip-based coloring.
    • Interactive legends show trip time ranges, latest-trip badges, and accessible controls.
    • Sensor values are displayed with scrollable lists and clearer value ranges.
    • Mobile map views now use point-based sensor markers and localized timestamps.
  • Bug Fixes
    • Invalid, incomplete, or non-finite sensor measurements are excluded from map displays.
    • Empty or unavailable trip data is handled more reliably.
  • Localization
    • Added English and German translations for mobile map labels and explanations.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 639434b1-883f-4861-925c-3f91bbf5c2c7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Mobile routes now share latest-trip filtering and measurement normalization. Mobile map layers render validated sensor points and localized trip legends. Sensor and trip views now support improved empty states, scrolling, accessibility, and latest-trip indicators.

Changes

Mobile trip map flow

Layer / File(s) Summary
Latest-trip selection
app/lib/mobile-box-helper.ts
Trip grouping uses a 60-second gap default. New helpers return the latest trips or flattened points while preserving chronological order.
Route measurement preparation
app/routes/explore.$deviceId.tsx, app/routes/explore.$deviceId.$sensorId.$.tsx
Routes use shared measurement normalization and latest-trip filtering for mobile data without a start date. Mobile overview content remains visible when sensors are selected.
Trip overview and legend
app/components/map/layers/mobile/mobile-overview-layer.tsx, app/components/map/layers/mobile/mobile-overview-legend.tsx, public/locales/en/mobile-map.json, public/locales/de/mobile-map.json
Trip labels and timestamps use locale-aware formatting. Empty results clear map state. The legend displays translated controls, interactive items, and latest-trip badges.
Sensor point rendering
app/components/map/layers/mobile/mobile-box-layer.tsx, app/components/map/layers/mobile/mobile-box-view.tsx
Invalid sensor measurements are excluded. Valid data renders point features with ISO timestamps. The sensor list scrolls and displays validated value bounds.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DeviceIdRoute
  participant TripHelpers
  participant SensorRoute
  participant MobileOverview
  participant MapLegend
  DeviceIdRoute->>TripHelpers: select latest mobile trip points
  TripHelpers-->>DeviceIdRoute: return selected locations
  SensorRoute->>TripHelpers: filter normalized measurements
  TripHelpers-->>SensorRoute: return measurements from latest trips
  SensorRoute->>MobileOverview: provide mobile locations and sensor data
  MobileOverview->>MapLegend: provide localized trip items and color callback
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: fixing trip display behavior across the mobile map views and helpers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/trip-display

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/map/layers/mobile/mobile-box-layer.tsx`:
- Around line 42-46: Update the sensorData filter used to build mappableData so
it also requires both location coordinates to pass Number.isFinite, while
preserving the existing non-null measurement location/value and finite value
checks. Use the coordinate fields from measurement.location before creating
point features.

In `@app/components/map/layers/mobile/mobile-box-view.tsx`:
- Around line 103-118: Update the sensorData filtering used by minValue and
maxValue to exclude measurements whose location is null, matching
MobileBoxLayer’s rendered point set. Preserve the existing value-null and
finite-number checks so the legend bounds continue to use only valid rendered
measurements.

In `@app/components/map/layers/mobile/mobile-overview-layer.tsx`:
- Around line 207-212: Update the empty-data branch in the clusteredTrips update
flow to also reset highlightedTrip, hoveredCluster, and popupInfo alongside the
existing source and legend state resets. Ensure the next non-empty update cannot
reuse stale hover, highlight, or popup interaction state.

In `@app/routes/explore`.$deviceId.$sensorId.$.tsx:
- Around line 19-29: Update the RawMeasurement.time type to Date | null, then
exclude measurements with null times when constructing and applying
latestPointTimes in the measurement filtering flow. Preserve valid dated
measurements and prevent toISOString() from being called for null-time records.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d0a26c2d-d847-4201-a6ce-aa974c4aa13c

📥 Commits

Reviewing files that changed from the base of the PR and between 5635799 and d1028de.

📒 Files selected for processing (9)
  • app/components/map/layers/mobile/mobile-box-layer.tsx
  • app/components/map/layers/mobile/mobile-box-view.tsx
  • app/components/map/layers/mobile/mobile-overview-layer.tsx
  • app/components/map/layers/mobile/mobile-overview-legend.tsx
  • app/lib/mobile-box-helper.ts
  • app/routes/explore.$deviceId.$sensorId.$.tsx
  • app/routes/explore.$deviceId.tsx
  • public/locales/de/mobile-map.json
  • public/locales/en/mobile-map.json

Comment on lines +42 to +46
const mappableData = sensorData.filter(
(measurement) =>
measurement.location !== null &&
measurement.value !== null &&
Number.isFinite(Number(measurement.value)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate coordinates before creating point features.

The filter accepts a non-null location with NaN or infinite coordinates. Those values create invalid GeoJSON positions and can prevent MapLibre from rendering the source. Filter both coordinates with Number.isFinite.

Proposed fix
 		const mappableData = sensorData.filter(
 			(measurement) =>
 				measurement.location !== null &&
+				Number.isFinite(measurement.location.x) &&
+				Number.isFinite(measurement.location.y) &&
 				measurement.value !== null &&
 				Number.isFinite(Number(measurement.value)),
 		)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const mappableData = sensorData.filter(
(measurement) =>
measurement.location !== null &&
measurement.value !== null &&
Number.isFinite(Number(measurement.value)),
const mappableData = sensorData.filter(
(measurement) =>
measurement.location !== null &&
Number.isFinite(measurement.location.x) &&
Number.isFinite(measurement.location.y) &&
measurement.value !== null &&
Number.isFinite(Number(measurement.value)),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/map/layers/mobile/mobile-box-layer.tsx` around lines 42 - 46,
Update the sensorData filter used to build mappableData so it also requires both
location coordinates to pass Number.isFinite, while preserving the existing
non-null measurement location/value and finite value checks. Use the coordinate
fields from measurement.location before creating point features.

Comment on lines +103 to +118
const sensorData = Array.isArray(sensor.data)
? sensor.data.filter(
(measurement) =>
measurement.value !== null &&
Number.isFinite(Number(measurement.value)),
)
: []

const minValue = Math.min(...sensorData.map((d) => Number(d.value)))
const maxValue = Math.max(...sensorData.map((d) => Number(d.value)))
const minValue =
sensorData.length > 0
? Math.min(...sensorData.map((d) => Number(d.value)))
: 0
const maxValue =
sensorData.length > 0
? Math.max(...sensorData.map((d) => Number(d.value)))
: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Calculate legend bounds from the rendered point set.

MobileBoxLayer excludes measurements with location === null before it calculates point colors. Legend includes their values in minValue and maxValue. If such a measurement contains an endpoint value, the legend range does not match the range used for rendered points. Filter out measurements without a location here too.

Proposed fix
 		? sensor.data.filter(
 				(measurement) =>
+					measurement.location !== null &&
 					measurement.value !== null &&
 					Number.isFinite(Number(measurement.value)),
 			)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const sensorData = Array.isArray(sensor.data)
? sensor.data.filter(
(measurement) =>
measurement.value !== null &&
Number.isFinite(Number(measurement.value)),
)
: []
const minValue = Math.min(...sensorData.map((d) => Number(d.value)))
const maxValue = Math.max(...sensorData.map((d) => Number(d.value)))
const minValue =
sensorData.length > 0
? Math.min(...sensorData.map((d) => Number(d.value)))
: 0
const maxValue =
sensorData.length > 0
? Math.max(...sensorData.map((d) => Number(d.value)))
: 0
const sensorData = Array.isArray(sensor.data)
? sensor.data.filter(
(measurement) =>
measurement.location !== null &&
measurement.value !== null &&
Number.isFinite(Number(measurement.value)),
)
: []
const minValue =
sensorData.length > 0
? Math.min(...sensorData.map((d) => Number(d.value)))
: 0
const maxValue =
sensorData.length > 0
? Math.max(...sensorData.map((d) => Number(d.value)))
: 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/map/layers/mobile/mobile-box-view.tsx` around lines 103 - 118,
Update the sensorData filtering used by minValue and maxValue to exclude
measurements whose location is null, matching MobileBoxLayer’s rendered point
set. Preserve the existing value-null and finite-number checks so the legend
bounds continue to use only valid rendered measurements.

Comment on lines +207 to +212
if (clusteredTrips.length === 0) {
setSourceData(null)
setExpandedSourceData(null)
setLegendItems([])
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear interaction state with empty map data.

Line 207 clears the map sources but retains highlightedTrip, hoveredCluster, and popupInfo. If a user hovers a trip before the data becomes empty, the next non-empty update can render the old popup and highlight the wrong trip number. Reset these states in this branch.

Proposed fix
 		if (clusteredTrips.length === 0) {
 			setSourceData(null)
 			setExpandedSourceData(null)
 			setLegendItems([])
+			setHighlightedTrip(null)
+			setHoveredCluster(null)
+			setPopupInfo(null)
 			return
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (clusteredTrips.length === 0) {
setSourceData(null)
setExpandedSourceData(null)
setLegendItems([])
return
}
if (clusteredTrips.length === 0) {
setSourceData(null)
setExpandedSourceData(null)
setLegendItems([])
setHighlightedTrip(null)
setHoveredCluster(null)
setPopupInfo(null)
return
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/map/layers/mobile/mobile-overview-layer.tsx` around lines 207
- 212, Update the empty-data branch in the clusteredTrips update flow to also
reset highlightedTrip, hoveredCluster, and popupInfo alongside the existing
source and legend state resets. Ensure the next non-empty update cannot reuse
stale hover, highlight, or popup interaction state.

Comment on lines +19 to +29
type RawMeasurement = {
sensorId: string
locationId: bigint | null
time: Date
value: number | null
location: {
id: bigint
x: number
y: number
} | null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve nullable measurement times before trip filtering.

SensorWithMeasurementData['data'] permits time: null, but RawMeasurement narrows it to Date. The casts on Lines 103 and 121 do not make a null value valid. If a mobile measurement has a location and time: null, toISOString() throws and fails the route loader.

Set RawMeasurement.time to Date | null. Exclude null-time measurements when building and applying latestPointTimes.

Proposed fix
 type RawMeasurement = {
 	sensorId: string
 	locationId: bigint | null
-	time: Date
+	time: Date | null
 	value: number | null
 	location: {
 		id: bigint
 		x: number
 		y: number
 	} | null
 }

-	const locationPoints: LocationPoint[] = normalizedData
-		.filter((measurement) => measurement.location !== null)
+	const locationPoints: LocationPoint[] = normalizedData
+		.filter(
+			(measurement) =>
+				measurement.location !== null && measurement.time !== null,
+		)
 		.map((measurement) => ({
 			geometry: {
 				x: measurement.location!.x,
 				y: measurement.location!.y,
 			},
 			time: measurement.time.toISOString(),
 		}))
@@
 	return normalizedData.filter(
 		(measurement) =>
 			measurement.location !== null &&
+			measurement.time !== null &&
 			latestPointTimes.has(measurement.time.toISOString()),
 	)

Also applies to: 51-68

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/routes/explore`.$deviceId.$sensorId.$.tsx around lines 19 - 29, Update
the RawMeasurement.time type to Date | null, then exclude measurements with null
times when constructing and applying latestPointTimes in the measurement
filtering flow. Preserve valid dated measurements and prevent toISOString() from
being called for null-time records.

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 65.97% 2242 / 3398
🔵 Statements 64.51% 2316 / 3590
🔵 Functions 63.21% 433 / 685
🔵 Branches 50.79% 1080 / 2126
File CoverageNo changed files found.
Generated in workflow #2868 for commit f14234b by the Vitest Coverage Report Action

@scheidtdav scheidtdav linked an issue Aug 13, 2026 that may be closed by this pull request
3 tasks
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.

Mobile Tracks not showing correctly

1 participant