ReviewOS

also looking at this

stacks/wildloop

feat: native trail maps and complete native mobile screens

#1017
Draft chris wants to merge codex/trail-experience into main
60 files +2505 -823

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

.maestro/native-trail/maps.yamlmodified+20-0
Changes to .maestro/native-trail/maps.yaml
@@ -30,6 +30,17 @@ name: Native Android trail maps and sticky save
3030- extendedWaitUntil:
3131 visible: '^Save$'
3232 timeout: 30000
33- scrollUntilVisible:
34 element:
35 id: trail-elevation-slider
36 direction: DOWN
37- tapOn:
38 id: trail-elevation-slider
39 point: '99.9%,50%'
40- extendedWaitUntil:
41 visible: ${INLINE_PROFILE_END_TEXT}
42 timeout: 15000
43- takeScreenshot: android-native-inline-elevation-selection
3344- scrollUntilVisible:
3445 element:
3546 id: trail-map-fullscreen
@@ -61,6 +72,15 @@ name: Native Android trail maps and sticky save
6172- tapOn:
6273 id: map-profile-toggle
6374- assertVisible: 'Toggle elevation profile, Elevation profile expanded'
75- tapOn:
76 id: map-elevation-slider
77 point: '99.9%,50%'
78- extendedWaitUntil:
79 visible: ${PROFILE_END_TEXT}
80 timeout: 15000
81- takeScreenshot: android-native-fullscreen-elevation-selection
82- tapOn:
83 id: map-fit
6484- tapOn:
6585 id: map-layer
6686- tapOn: '^Satellite$'
scripts/run-native-trail-android-e2e.tsmodified+9-0
Changes to scripts/run-native-trail-android-e2e.ts
@@ -85,6 +85,13 @@ export async function runNativeTrailAndroidE2e(): Promise<void> {
8585 if (detail.trail?.hasGeometry) { trail = detail.trail; break }
8686 }
8787 if (!trail) throw new Error('Native Android QA requires its seeded recorded route.')
88 const profileResponse = await fetch(`${origin}/api/trails/${trail.id}/profile`)
89 if (!profileResponse.ok) throw new Error('Native Android QA requires the trail profile endpoint to be available.')
90 const profile = (await profileResponse.json()) as { points: Array<{ mi: number, ft: number | null }> }
91 const endpoint = profile.points?.at(-1)
92 if (!endpoint || profile.points.length < 2 || !Number.isFinite(endpoint.mi) || (endpoint.ft !== null && !Number.isFinite(endpoint.ft)))
93 throw new Error('Native Android QA requires a selectable recorded route profile.')
94 const endElevation = endpoint.ft === null ? 'elevation unavailable' : `${endpoint.ft} ft`
8895 const first = primaryRoutePart([...decodeRouteParts(trail.geometry), ...(Array.isArray(trail.pieceRoutes) ? trail.pieceRoutes : [])])[0]
8996 if (!first) throw new Error('The seeded trail has no recorded route start.')
9097 await adb(['emu', 'geo', 'fix', String(first[1]), String(first[0])], 'seed-location')
@@ -102,6 +109,8 @@ export async function runNativeTrailAndroidE2e(): Promise<void> {
102109 APP_ID: mobile.android.packageName, TRAIL_ID: String(trail.id), TRAIL_NAME: trail.name,
103110 E2E_EMAIL: QA_SOCIAL_ACCOUNT.email, E2E_PASSWORD: QA_SOCIAL_ACCOUNT.sessionSecret,
104111 REVIEW_TEXT: review, EDITED_REVIEW_TEXT: edit,
112 PROFILE_END_TEXT: `${endpoint.mi.toFixed(2)} mi · ${endElevation}`,
113 INLINE_PROFILE_END_TEXT: `${endpoint.mi.toFixed(2)} mi, ${endElevation}`,
105114 GPX_NAME: `${trail.name.replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '').slice(0, 80)}.gpx`,
106115 }
107116 let reviewId: number | undefined