Skip to content

cicistream/swimming-page

Repository files navigation

Deep Water Swimming Page

Deep Water Swimming Page is a swim-native personal performance homepage built around a small publish pipeline:

raw swim sessions -> canonical private data -> public generated data -> homepage

Live site: cicistream.github.io/swimming-page

The goal is to make a swimmer's training archive feel deliberate and publishable, instead of looking like a generic fitness export or admin dashboard.

Deep Water Swimming Page preview

What Ships Today

  • Vite + React + TypeScript front end
  • build-data pipeline that normalizes source sessions into private and public artifacts
  • a publish-friendly homepage with summary metrics, yearly consistency heatmap, stroke mix, and activity archive
  • sample config and sample swim sessions so the project runs on first clone
  • a design system in DESIGN.md that defines the Deep Pool editorial look

Product Shape

The current app is intentionally narrow:

  • one swimmer profile
  • one generated public homepage
  • sample JSON as the initial input source
  • generated data files checked into neither public/generated/ nor data/private/

This is the first working spine, not the final platform. The current repo proves the contract between import, normalization, publish artifacts, and UI.

Quick Start

  1. Install dependencies.
npm install
  1. Generate the private and public artifacts.
npm run build:data
  1. Start the local app.
npm run dev
  1. Build the production bundle.
npm run build

Available Scripts

  • npm run build:data: normalize sample/provider input into generated artifacts
  • npm run probe:fit:swim-fields: scan exported .fit files for swimming-related fields such as swolf, stroke, pool length, and laps
  • npm run dev: rebuild data first, then start the Vite dev server
  • npm run build: rebuild data, type-check, and create the production bundle
  • npm run preview: serve the production build locally

Repository Layout

sample-data/          demo input sessions
scripts/build-data.mjs
data/private/         canonical private swim records (generated)
public/generated/     public publish artifacts consumed by the app (generated)
src/                  React app shell and Deep Water UI
swim.config.json      swimmer profile + display + provider config
DESIGN.md             visual system source of truth

Data Flow

scripts/build-data.mjs currently reads:

  • provider input selected in swim.config.json
  • swim.config.json

It then generates:

  • data/private/canonical-swims.json
  • public/generated/summary.json
  • public/generated/activities.json
  • public/generated/latest.json
  • public/generated/heatmap.json
  • public/generated/sync-report.json
  • public/generated/config.json

The split is deliberate:

  • data/private/ is the fuller canonical layer for future merge/conflict workflows
  • public/generated/ is the publish-safe layer consumed by the homepage

Configuration

The first-run configuration lives in swim.config.json.

Current sections:

  • profile: public-facing swimmer identity and summary copy
  • display: privacy and presentation toggles
  • provider: selected source plus a lightweight capability matrix

Notable display behavior:

  • startedAt remains a date key in public activity data for stable charting and archive rendering
  • precise timestamps can still be emitted separately when enabled
  • exact location and notes stay out of public artifacts unless explicitly turned on

Sample Data Contract

Each canonical swim record is expected to include:

  • id
  • source
  • sourceActivityId
  • startedAt
  • timezone
  • distanceMeters
  • durationSeconds
  • pacePer100mSeconds
  • poolLengthMeters when the provider exposes pool detail
  • laps when the provider exposes pool detail

Optional fields currently supported:

  • stroke
  • swolf
  • calories
  • notes
  • location

Some providers may not expose every swim-specific field. Missing optional fields, and provider-specific gaps such as pool metadata, are preserved as partial-data markers so the UI can expose completeness honestly.

Design Direction

The visual system is intentionally not a generic sports dashboard. The app follows a dark, editorial "Deep Pool" language:

  • tonal depth over visible borders
  • large typographic anchors over card grids
  • restrained aquatic highlights over loud accent colors
  • technical swim data presented as calm, premium publishing

See DESIGN.md for the current source of truth.

Current Scope

Implemented now:

  • sample JSON import path
  • provider adapter scaffold for Huawei Health
  • canonical/private to public/publish split
  • partial-data labeling
  • sync provenance scaffolding
  • Deep Water homepage shell

Planned next:

  • real Huawei Health auth + sync implementation
  • provider selection rubric
  • manual override and conflict resolution flow
  • stronger onboarding and diagnostics
  • richer publish controls

Provider Scaffold

The build pipeline now loads sessions through provider adapters in scripts/providers/.

Available providers:

  • sample_json: reads the file configured at provider.sampleJson.path
  • huawei_health: reads a raw JSON export configured at provider.huaweiHealth.rawDataPath and maps it into the canonical swim contract
  • keep_swim_probe: reads a canonical draft generated from Keep probe responses and is displayed as Keep in the site UI

Huawei Health scaffold notes:

  • current mode is a file-based scaffold, not a completed live API sync
  • local import helper lives at npm run sync:huawei
  • import source path can be passed with --from or stored in provider.huaweiHealth.importPath
  • expected raw file path defaults to data/sources/huawei-health/export.json
  • example import payload lives at sample-data/huawei-health-export.sample.json
  • env var names for a future auth flow live under provider.huaweiHealth.credentialsEnv

Suggested Huawei Health bootstrap:

  1. Copy sample-data/huawei-health-export.sample.json to data/sources/huawei-health/export.json.
  2. Set provider.selected to huawei_health in swim.config.json.
  3. Replace the sample payload with your real exported or synced Huawei Health JSON.
  4. Run npm run build:data.

Automated local import flow:

  1. Export your Huawei Health JSON somewhere on your computer.
  2. Run npm run sync:huawei -- --from /absolute/path/to/export.json.
  3. The script validates the JSON, copies it into data/sources/huawei-health/export.json, stores import metadata next to it, and runs build:data.
  4. If provider.selected is still sample_json, switch it to huawei_health before the next build.

Keep Probe

There is also a narrow validation script for checking whether Keep's private API exposes swimming records for your account.

Run it with:

npm run probe:keep:swimming -- --phone 13800000000 --password 'your-password'

Or probe a batch of likely swimming type names:

npm run probe:keep:swimming -- --phone 13800000000 --password 'your-password' --default-types

To also brute-force several likely detail endpoints derived from the returned traininglogs schema:

npm run probe:keep:swimming -- --phone 13800000000 --password 'your-password' --default-types --detail-candidates

What it does:

  • logs into Keep using your provided credentials
  • requests one or more candidate activity types such as swimming, swim, poolSwimming, and indoorSwimming
  • saves the raw list response under data/sources/keep-probe/
  • if a first record exists, requests its detail payload and saves that too
  • saves an aggregate summary file so we can compare candidate types in one place

This is only for feasibility checking. It does not change your website data or import anything into the swim provider pipeline.

If the probe confirms that Keep exposes swim list data, you can draft canonical sessions from the saved probe files:

npm run map:keep:swim-probe

That script reads the latest probe summary, picks the best matching swim sport type, and writes a canonical draft JSON to data/sources/keep-probe/canonical-swims.draft.json.

To run the site directly from that draft, set provider.selected to keep_swim_probe. The checked-in default config still points at sample_json so a fresh clone can build without private probe artifacts.

To refresh the Keep-based swim data in one command:

KEEP_PHONE=13800000000 KEEP_PASSWORD='your-password' npm run sync:keep

That runs the full local chain:

  • probe Keep swim list data
  • map probe output into canonical draft swims
  • rebuild public/generated/*

Keep sync metadata is persisted under data/sources/keep-probe/sync-meta.json, so the site can show the real last successful sync time, the most recent failed attempt, and whether the current archive has gone stale.

Online sync trigger

The deployed GitHub Pages site cannot safely hold GitHub credentials, so online sync uses a small relay service.

This repo includes a Cloudflare Worker template at infra/sync-trigger-worker.js. Wrangler deployment config lives in wrangler.toml.

Current deployed URLs:

Worker env vars:

  • ALLOWED_ORIGIN: your deployed site origin, for example https://cicistream.github.io
  • GITHUB_OWNER: GitHub owner or org, for example cicistream
  • GITHUB_REPO: repo name, for example swimming-page
  • GITHUB_TOKEN: GitHub token with permission to dispatch repository events

Front-end env var:

  • VITE_SYNC_TRIGGER_URL: the deployed Worker URL

The relay validates the browser origin, then sends a repository_dispatch event with type sync_keep, which is handled by .github/workflows/keep-sync-pages.yml.

Quick deploy:

npx wrangler login
npx wrangler secret put GITHUB_TOKEN
npm run worker:deploy

Then set VITE_SYNC_TRIGGER_URL to the deployed Worker URL before building and deploying the site.

Local import support

The local import modal in development currently supports:

  • .json: imports directly into the matching JSON-backed provider and rebuilds the page
  • .csv: maps common swim export columns into canonical swim sessions and rebuilds the page
  • .tcx, .xml: parses swim TCX-style activity data into canonical swim sessions and rebuilds the page
  • .gpx: parses swim tracks into approximate canonical swim sessions and rebuilds the page
  • .fit: stores the file under data/sources/fit-probe/imports/ and refreshes the FIT swim field probe report
  • .zip: stores the file under data/sources/manual-imports/ for future mapping work

CSV imports currently recognize common columns such as:

  • time: date, startedAt, startTime, datetime
  • distance: distance, distanceMeters, distanceKm
  • duration: duration, durationSeconds, durationMinutes, time
  • optional metadata: poolLength, laps, stroke, swolf, calories, notes, location

There is a starter template at sample-data/swims.import-template.csv.

GitHub Actions Automation

There is a scheduled GitHub Actions workflow for Keep-based refresh and GitHub Pages deploy in .github/workflows/keep-sync-pages.yml.

Repository secrets required:

  • KEEP_PHONE
  • KEEP_PASSWORD

The workflow:

  • runs npm run sync:keep with SWIM_PROVIDER_OVERRIDE=keep_swim_probe
  • builds the site with a GitHub Pages base path
  • deploys the updated static site to GitHub Pages

Before relying on it, enable GitHub Pages for the repository and allow GitHub Actions to deploy Pages artifacts.

Keep swimming notes

What is confirmed so far:

  • Keep swim list probing works for candidate types such as swim, poolSwimming, and indoorSwimming
  • the current local site integration uses that list-level data successfully
  • the saved Keep list payload includes useful summary fields such as start time, duration, calories, average/max heart rate, vendor info, and a text title that usually contains the distance

Current limits:

  • the list payload does not currently expose poolLengthMeters, laps, swolf, or stroke
  • the guessed Keep detail endpoints we tried, including /{sportType}log/{id} and several traininglogs variants, did not return a usable swim detail payload
  • Keep's swim distance field can be 0, so the current mapper falls back to parsing distance from titles like 游泳池游泳 1025 米

Network debugging notes:

  • Charles with plain phone proxy works, so proxying itself is not the problem
  • Charles SSL Proxying causes Keep to fail while normal browser HTTPS still works
  • in practice, that means ordinary Charles interception is not a reliable path to the Keep swim detail JSON for this app

Recommendation:

  • use npm run sync:keep for the current stable, list-based swim sync
  • if you want poolLengthMeters, laps, or swolf, plan for a stronger reverse-engineering path such as emulator + system certs, rooted device, or Frida, instead of repeating ordinary Charles attempts

Contributing

This repo is still at the first public spine stage, so the most valuable contributions are:

  • provider adapters
  • data contract hardening
  • visual polish that preserves the Deep Water design language
  • publishing and privacy controls
  • testing around normalization and generated artifact integrity

If you are changing the UI, keep DESIGN.md in sync with the implementation direction.

About

游泳数据个人看板

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors