Skip to content

feat: Withings weight sync + dedicated Biometrics category UX#96

Closed
marian001 wants to merge 1 commit intoelkimek:mainfrom
marian001:feature/withings-weight-biometrics-ux
Closed

feat: Withings weight sync + dedicated Biometrics category UX#96
marian001 wants to merge 1 commit intoelkimek:mainfrom
marian001:feature/withings-weight-biometrics-ux

Conversation

@marian001
Copy link
Copy Markdown

@marian001 marian001 commented Mar 29, 2026

Summary

This PR adds a first wearable integration via Withings and introduces a dedicated Biometrics category UX for time-series biometrics.

It implements OAuth connection + sync from Withings, imports weight data into the existing biometrics structure, and surfaces biometrics as a first-class category in the sidebar with standard chart/detail interactions.

Closes the immediate use case discussed in #5 around wearables integration by shipping an initial, practical scope.

What was added/changed

1) Withings integration (initial scope: weight only)

  • New module: js/withings-weight.js
    • OAuth authorize URL creation (user.metrics scope)
    • OAuth callback handling in-app
    • Token exchange + refresh flow
    • Weight fetch from Withings measure/getmeas
    • Mapping of Withings weight measurements into importedData.biometrics.weight
    • Incremental sync via lastupdate
    • Remove/disconnect flow for imported Withings weight rows
  • js/main.js
    • OAuth callback routing updated so Withings callback is handled first when applicable

2) Settings UI for Withings

  • js/settings.js
    • New Withings Integration section in Settings
    • Fields: Client ID, Client Secret, Redirect URI
    • Actions:
      • Save credentials
      • Authorize
      • Sync weight
      • Delete Withings data (and disconnect)
    • Connection status + last sync display
    • Note in UI that this initial version imports only weight

3) Biometrics as dedicated category + UX parity improvements

  • New module: js/biometrics-view.js
    • Dedicated Biometrics category view rendering
    • 3 cards/charts: Weight, Blood Pressure, Pulse
    • Uses shared createLineChart(...) (axes/tooltips/grid), not mini custom sparkline charts
    • Category-level time range filter (1M / 3M / 9M / All) applied across all biometrics charts
    • Detail modal per biometrics type with:
      • chart + modal-level range filter
      • measurement list
      • add new measurement form
      • delete measurement
      • inline value editing (same interaction style as marker modal: click value -> editable input, Enter/blur save, Esc cancel)
    • Editing enabled for Withings-imported weight rows as user override

4) Navigation and routing updates

  • js/nav.js
    • Biometrics appears as its own item under Categories (when biometrics data exists)
    • Genetics dashboard-scroll behavior made more robust
  • js/views.js
    • New route: navigate('biometrics') -> showBiometrics()
    • Dashboard no longer renders biometrics block directly (biometrics live in dedicated screen)
    • Empty-state gating updated so biometrics-only profiles still count as having data

How it works

Data model

  • Reuses existing state.importedData.biometrics structure:
    • weight[] with { date, value, unit, source }
    • bp[] with { date, sys, dia, source }
    • pulse[] with { date, value, source }
  • Withings sync writes source: 'withings' rows for weight
  • Manual edits/additions write source: 'manual'

Sync behavior

  • OAuth state is stored in session storage and validated on callback
  • Access token is refreshed when near expiry
  • Sync can run incrementally based on last sync timestamp (lastupdate)
  • For each day, latest measurement in day is used

How to use Withings integration

  1. Create/configure a Withings API app in the Withings developer dashboard: https://developer.withings.com/dashboard/
  2. In your Withings app settings, get Client ID and Client Secret and set the Callback URL.
  3. Open Settings in getbased.
  4. In Withings Integration, enter:
    • Withings Client ID
    • Withings Client Secret
    • Redirect URI (defaults to current app URL)
  5. Ensure the Withings Callback URL exactly matches the Redirect URI configured in getbased.
  6. Click Save credentials.
  7. Click Authorize and complete Withings OAuth consent.
  8. Back in app, click Sync weight.
  9. Open Categories → Biometrics to view charts and manage data.

Optional:

  • Delete Withings data removes imported Withings weight rows and disconnects.

Scope note / future extension

This PR intentionally ships weight-only import for now, even though Withings API exposes additional measurements. The integration path and UI scaffolding are now in place, so additional types (e.g., blood pressure, heart rate, body composition metrics) can be added incrementally in follow-up PRs.

Related issue

Files changed

  • js/main.js
  • js/nav.js
  • js/settings.js
  • js/views.js
  • js/withings-weight.js (new)
  • js/biometrics-view.js (new)

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 29, 2026

@marian001 is attempting to deploy a commit to the elkimek's projects Team on Vercel.

A member of the Team first needs to authorize it.

@elkimek
Copy link
Copy Markdown
Owner

elkimek commented Mar 30, 2026

Thanks for the PR Marian, I can't merge it right now as I'm in process of rewriting getbased to V2 (Tauri/TypeScript/Svetle) and these integration will need a proper system, can't be just glued into it. Will look into this ASAP once V2 is stable and released.

Copy link
Copy Markdown
Owner

@elkimek elkimek left a comment

Choose a reason for hiding this comment

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

Thanks for this Marian — solid work, especially the biometrics-view module and the OAuth flow structure. Sorry for the delayed review (V2 rewrite was abandoned, back to v1).

I've done a thorough code review. The biometrics category view is great UX and something I want to ship. However, there are some issues with the Withings integration that need addressing, plus an architectural consideration.

Security issues (must fix)

  1. Client secret + tokens stored in plaintext localStorage — needs encryption via encryptedSetItem / SENSITIVE_PATTERNS in crypto.js, consistent with how all other API keys are stored
  2. Access token in URL query string (line 166) — leaks to browser history/logs. Use Authorization: Bearer header instead
  3. OAuth state uses Math.random() — not cryptographically secure. Use crypto.getRandomValues() for CSRF protection
  4. No meastype filter on API call — fetches all measurement types, filters client-side. Should request only weight (meastype=1) from the server

Functional bugs

  1. Delete filter logic is inverted (line 252) — keeps Withings rows and deletes manual ones. The condition needs to be flipped
  2. Weight stored in display units — should always store raw kg with unit: 'kg', convert at display time. Currently bakes the unit system at sync time

Architecture: wearable adapter system

Per the discussion in #5, I want to build a proper wearable adapter system (similar to adapters.js for specialty labs) before merging individual integrations. The core issue is that wearable APIs need server-side OAuth token exchange — putting the client secret in the browser isn't safe.

The plan is:

  • js/wearables.js — shared OAuth flow, credential storage (encrypted), sync scheduling, settings UI generation
  • js/wearables/withings.js — adapter config (endpoints, scopes, data mapping)
  • OAuth token exchange handled via the relay (users who want wearables likely already self-host one for sync)

Your Withings code would become the first adapter in this system. The biometrics-view.js is mostly ready to merge as-is — it's the rendering layer that any wearable adapter feeds into.

What I'd suggest

  1. I'll merge biometrics-view.js (the category view) separately — it's good and needed
  2. I'll build the wearable adapter framework based on your Withings implementation as the reference
  3. Your OAuth + sync code becomes the Withings adapter, with the security fixes above

Would you be open to that approach? Happy to collaborate on the adapter system.

@elkimek
Copy link
Copy Markdown
Owner

elkimek commented Apr 6, 2026

To clarify my review above — I'm not merging wearable integrations right now. I need to build a proper wearable adapter system first (per #5), which will take time. Your code is a useful reference for when I get to it. Thanks again for the contribution.

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.

2 participants