Settings → Sync → Upload format: "Original file" (default) / "Extracted JSON".
- raw (default): reads original_path as base64, POSTs to /api/upload/raw; after
success, overwrites local detail_json/timeseries_json/geojson/source_hash with
the server's DEM-corrected extraction (Option A). Falls back to bas if the file
is missing.
- bas: POSTs pre-extracted JSON to /api/upload/bas, faster, no DEM correction.
Switching modes is safe — the server deduplicates by activity id so a previous
raw upload will return status:"duplicate" on a subsequent bas attempt.
Settings: watch directory field is hidden behind a warning if no instance
URL is saved yet, making the dependency explicit before the user sets a
path.
Import: runAutoScan silently skips (no errors) when instance URL is
missing; manualScan shows a single clear message instead of one failure
per file.
The Save button is anchored to the Instance section near the top. Moving
the auto_import_path write to onBlur on the field itself means the value
is persisted as soon as the user leaves the input — no need to scroll up
and hit Save.
- useFocusEffect re-reads auto_import_path from DB every time the Import
tab is focused, so the scan button appears immediately after saving in
Settings (was broken by conditional hook call which violated Rules of
Hooks and never re-fired on setting changes)
- Nested inner try/catch isolates DocumentPicker.getDocumentAsync so
ActivityNotFoundException (Karoo has no DocumentsUI) shows a friendly
message instead of crashing the tab
- Settings watch-directory placeholder updated to /sdcard/FitFiles
Replace useSetting()-based useTheme() with a React context (ThemeProvider
+ useTheme/usePaletteControl). The context holds palette key in state so
pressing a palette button in Settings re-renders all screens instantly.
Persists to SQLite and reloads the stored value on mount.
- theme.ts: useTheme() hook with race calendar (May–Sep windows),
auto-detects Giro/Tour/Vuelta by date; stores override in SQLite
- All screens (feed, import, activity, tab bar) now use accent/dim
from useTheme() instead of hardcoded #60a5fa
- Settings: Palette section with Auto/Default/Giro/Tour/Vuelta buttons
to override the auto-detected palette for testing
- Server: POST /api/upload/bas accepts pre-extracted BAS JSON (activity + optional timeseries/geojson), writes files and triggers merge_all
- sync.ts: uploadLocalActivities reads unsynced local activities by original_path, POSTs to /api/upload/bas, marks synced_at on success
- Settings: Upload toggle (Off / Upload local activities) in Sync section with subLabel dividers for Download / Upload groups
- Feed: sync message includes uploaded count when activities are pushed
Server (bincio/serve/server.py):
- Add _require_auth: accepts session cookie OR Authorization: Bearer token
- POST /api/auth/token: same as /api/auth/login but returns token in body
(password used once, not stored; mobile stores only the session token)
- GET /api/feed: auth-gated; reads _merged/index.json for the user and
returns the activities array as JSON
Mobile:
- db/sync.ts: syncFeed(db) fetches /api/feed, upserts each summary into
local SQLite as origin='remote'; skips locally-imported activities
- db/queries.ts: add upsertRemoteActivity (INSERT ... ON CONFLICT DO UPDATE
WHERE origin='remote' — never overwrites local imports); fix feed sort
order to started_at DESC instead of insertion order
- settings.tsx: Connect section — password field (not persisted) + Connect
button calls POST /api/auth/token and stores token; Disconnect clears it
- index.tsx: ↓ Sync button + pull-to-refresh both trigger syncFeed; cloud
badge on remote activities; empty state updated