[P1/high] feat(web): terms-clean health-capture foundation (Capacitor + HealthSource seam) — vires-ops#37#71
Draft
cipher813 wants to merge 3 commits into
Draft
[P1/high] feat(web): terms-clean health-capture foundation (Capacitor + HealthSource seam) — vires-ops#37#71cipher813 wants to merge 3 commits into
cipher813 wants to merge 3 commits into
Conversation
…pluggable HealthSource + Import-from-Health (vires-ops#37) Lays the terms-clean automatic-capture architecture that replaces the Strava path (vires-ops#36, closed for its AI-use prohibition): read the user's own on-device hike/walk workouts from Apple HealthKit / Android Health Connect, so there is no third-party terms restriction on feeding the coach (unblocks load-accounting). Architecture (built at the correct layer — health capture is just another route-derivation mode, reusing the existing SI RouteStats pipeline): - web/capacitor.config.ts: wraps the existing Vite PWA (webDir=dist); the PWA still builds/runs standalone. Adds @capacitor/core (dep) + cli (dev). - web/src/lib/health.ts: the TypeScript seam — HealthSource interface, an SI normalizer (untrusted native records -> HealthWorkout), a workout->RouteStats projection, a Capacitor-native source (bridges the `Health` plugin) and an unavailable web fallback selected by platform. - web/src/components/RouteCapture.tsx: a native-only "Health" route mode that lists recent workouts and prefills distance/elevation/duration via the SAME applyStats path; tab hidden unless a health source isAvailable(), so the standalone PWA is unaffected. - routeMode/api: `health` RouteSource; backend RouteSource literal widened to accept it. The load path (SI conversion + metabolic cost) is identical regardless of source — no change to POST /api/workouts/activity behavior. Validated locally: web build + oxlint + vitest (131 pass, +7 new); backend ruff + pytest (365 pass, +1 new). The native ios/android projects and the HealthKit / Health Connect plugin native code require Xcode / the Android SDK + a device and are the remaining out-of-CI work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Priority: P1 · Complexity: high
Part of the route-tracking arc (vires-ops EPIC #34); implements the buildable, terms-clean foundation of vires-ops#37 (automatic ruck capture). Not
Closes— the native capture core is out-of-CI (see Remaining below).What this delivers
Health capture is built at the correct architectural layer: just another route-derivation mode, reusing the existing SI
RouteStats→applyStatspipeline rather than a parallel path.web/capacitor.config.ts— wraps the existing Vite PWA (webDir: dist); the PWA still builds/runs standalone. Adds@capacitor/core(dep) +@capacitor/cli(dev).web/src/lib/health.ts— the TypeScript seam:HealthSourceinterface, an SI normalizer for untrusted native records (normalizeWorkout), aworkout → RouteStatsprojection, a Capacitor-native source that bridges aHealthplugin, and an unavailable web fallback selected byCapacitor.isNativePlatform().web/src/components/RouteCapture.tsx— a native-only Health route mode listing recent hike/walk workouts; on selection it prefills distance/elevation/duration through the sameapplyStatsused by trail/draw/GPX. The tab is hidden unlesshealthSource.isAvailable(), so the standalone web PWA is byte-unaffected.routeMode.ts/api.ts/api/schemas/workout.py— newhealthRouteSource; the backendRouteSourceliteral is widened to accept it (a 1-value enum change, not a load-path change — SI conversion +metabolic_cost_kjare identical regardless of source, soPOST /api/workouts/activitybehavior is unchanged).Note on issue anchors
The issue references
RuckForm.tsx/logRuck/POST /api/workouts/ruck; those were refactored into the genericActivityForm.tsx/RouteCapture.tsx/logActivity→POST /api/workouts/activityseam. This PR targets the real code. The issue also asks for read-only prefill; the established route-capture UX prefills editable fields ("edit anything that looks off") — kept consistent here.Validated locally (green)
npm run build✓,npm run lint(oxlint) ✓,npm run test(vitest) ✓ — 131 pass, +7 new (health.test.ts).ruff check✓,pytest✓ — 365 pass, +1 new (test_activity_accepts_health_route_source).Remaining — why DRAFT (out of this environment)
The actual on-device capture cannot be built or validated on a headless Linux box; it needs Apple/Android tooling + a device:
npx cap add ios/npx cap add androidnative project generation.Healthplugin native code — a HealthKit workout query (iOS) + a Health Connect read (Android), mapping to the SI{ distanceM, elevationGainM, durationS }this seam already consumes.To validate the buildable part:
cd web && npm ci && npm run build && npm run lint && npm run test(Node ≥20.19) and, from the repo root,pytest.