diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 58f3bd8..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "permissions": { - "allow": [ - "Read(//Users/brutsalvadi/src/bincio_data/_merged/**)", - "Read(//Users/brutsalvadi/src/bincio_data/**)", - "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(list\\(d.get\\(''''dependencies'''',{}\\).keys\\(\\)\\)\\)\")", - "Bash(uv run:*)", - "Bash(python3 -c \"import pyodide\")", - "Bash(uv build:*)", - "Bash(npm install:*)", - "Bash(npm run:*)", - "Bash(mkdir -p /tmp/bincio_ci/activities)", - "Bash(BINCIO_DATA_DIR=/tmp/bincio_ci npm run build)", - "Bash(ls /Users/brutsalvadi/src/bincio_activity/*.md)", - "Bash(python3:*)", - "Bash(wc -l /Users/brutsalvadi/src/bincio_activity/bincio/extract/*.py)", - "Bash(grep -E \"\\\\.py$\")", - "Bash([ -f ~/src/cycling_data_davide/activities.csv ])", - "Read(//Users/brutsalvadi/src/cycling_data_davide/**)", - "Bash(ls -la /tmp/bincio_dev_test/dave/_merged/activities/*.json)", - "Bash(ls -la /tmp/bincio_dev_test/dave/_merged/activities/*.geojson)", - "Bash(unzip -l ~/src/cycling_data_davide/export_18885842.zip)", - "Bash(unzip -p export_18885842.zip profile.csv)", - "Bash(unzip -p export_18885842.zip bikes.csv)", - "Bash(unzip -p export_18885842.zip routes.csv)", - "Bash(unzip -p export_18885842.zip privacy_zones.csv)", - "Bash(unzip -p export_18885842.zip activities.csv)", - "Bash(xargs -I{} python3 -c \"import json,sys; d=json.load\\(open\\('{}'\\)\\);print\\({k:d.get\\(k\\) for k in ['title','strava_id','started_at','privacy']}\\)\")" - ] - } -} diff --git a/.gitignore b/.gitignore index b80d778..f20ab04 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,14 @@ bincio_data/ .env extract_config.yaml +# Local working / scratch files +advice.md +issues.md +todo.md +site/public/data +site/public/*.whl +.claude/settings.local.json + # Capacitor native projects # Commit these if you want to track native code changes; # omit these lines if you regenerate them from `npx cap add` diff --git a/advice.md b/advice.md deleted file mode 100644 index 6454668..0000000 --- a/advice.md +++ /dev/null @@ -1,79 +0,0 @@ -# Advice on ARCHITECTURE.md - -Review of the mobile / offline plan in `ARCHITECTURE.md`. The two-stage extract/render pipeline, edit flow, and federation sections are accurate and clear — this document focuses on where the architectural risk lives: the mobile app and the path to fully-offline operation. - -## What's strong - -- **Workflow framing (1–4) is the right way to think about it.** Each workflow is a concrete user story, not an abstract capability list. Workflow 1 (record → convert → cloud) is achievable with what's already there; that's a good wedge to ship first. -- **Honest status table** at the bottom of "Fully offline — missing pieces". An architecture doc that admits what isn't done is more useful than one that hand-waves. -- **Incremental validation plan (§5).** Testing the service worker approach in the browser before touching native builds is the right call — cheap to run, and the failure mode is informative rather than expensive. - -## Decision: drop `capacitor-nodejs` - -The doc previously listed `capacitor-nodejs` as a fallback if service workers fail on iOS. We are dropping this option entirely. Reasons: - -- It embeds a full Node runtime, meaningfully increasing app size. -- iOS support relies on a fork of Node-mobile and has historically been spotty. -- It introduces a long-term maintenance burden (tracking upstream Node, plugin updates, two runtimes to debug). -- The "Hard" effort label in the original status table understated both the integration risk and the ongoing cost. - -If service workers turn out to be blocked on iOS, the fallback should be one of: - -1. **A data access abstraction** (see next section) that lets the app read from IndexedDB directly via a JS loader, with no `fetch('/data/*')` in the hot path at all. This sidesteps the WKWebView question entirely. -2. **A native Swift/Kotlin micro-server** if a local HTTP origin is genuinely required. -3. **Bundling data as static assets** and re-running `cap sync` on import — crude but boring and reliable. - -`capacitor-nodejs` should not appear in the doc, the test plan, or the status table. - -## Things to fix or add - -### 1. Add a data access abstraction step *before* the service worker work - -The doc frames the offline problem as "serve local data to the WebView at `/data/*`". That skips a prior question: does the Astro site actually need to fetch `/data/*` at runtime, or can the data layer be abstracted behind a thin loader (`loadIndex()`, `loadActivity(id)`) with two implementations? - -- **Cloud build:** loader uses `fetch('/data/...')` as today. -- **App build:** loader reads from IndexedDB directly. - -This is a much smaller change than service worker interception, avoids the iOS WKWebView question entirely, and is useful even if you stay cloud-only (testability, mocking, future federation transports). It should land *before* any service worker work — at which point the SW work may turn out to be unnecessary. - -### 2. Commit to JS for sidecar merge — do not use Pyodide - -The "missing pieces" section lists two options for re-running `merge_all` on save: reimplement in JS (~150 lines) or call into Pyodide. Pick JS. Reasons: - -- Pyodide is lazy-loaded by `/convert/`. It is **not** warm just because the app is open. -- A user tapping Edit → change title → Save would trigger a multi-second Pyodide cold start (~10MB) for a one-line edit. Terrible UX, and it repeats on every cold app launch. -- Porting `merge_all` to JS keeps the edit drawer decoupled from the convert page's machinery. The two subsystems stay independent. - -Pyodide should remain convert-page-only. - -### 3. The Workflow 4 diagram contradicts the test plan - -The "Fully offline on phone" workflow shows a "Local Node server" arrow as if it were the chosen path. The §5 test plan picks service workers first. The diagram should reflect that — show the SW path as primary, and drop the Node server box entirely (per the decision above). - -### 4. Missing: data lifecycle on device - -Nothing in the doc covers: - -- How much storage the app is allowed to use on iOS before the OS evicts it. -- What happens on uninstall / reinstall. -- Sync / conflict resolution if the same activity exists locally and on a cloud instance. - -These don't need solutions today, but they should be acknowledged as open questions. Otherwise Workflow 4 will hit all of them at once during implementation. - -### 5. Reconcile Pyodide payload size - -Line 162 says "~8MB", line 335 says "~10MB". Pick one and use it consistently. - -## Small stuff - -- The federation diagram uses a `Note1` node that won't render as a Mermaid note — it'll appear as a regular box. Use `%%` comments or restructure. -- The "iOS: App Store / TestFlight" cell in the PWA-vs-Capacitor table sits in the Capacitor column but reads like a downside. Clarify it's the distribution path, not a limitation relative to PWA. - -## Bottom line - -The plan is sound and the incremental validation approach is right. The two highest-leverage changes: - -1. **Add a data access abstraction layer** before the service worker work. It's small, useful regardless, and may make the SW work moot. -2. **Port `merge_all` to JS** so the edit drawer doesn't depend on Pyodide warm-up. - -With `capacitor-nodejs` removed, the offline path is: data access abstraction → IndexedDB-backed loader → JS merge → (optionally) service worker for any remaining `fetch('/data/*')` callsites that can't be migrated to the loader. diff --git a/issues.md b/issues.md deleted file mode 100644 index 1a7bb6b..0000000 --- a/issues.md +++ /dev/null @@ -1,99 +0,0 @@ -# Repository Audit — 2026-03-30 - -## CRITICAL - -| # | Area | File | Issue | Status | -|---|------|------|-------|--------| -| 1 | Security | `edit/server.py:351,386,431,588` | **Path traversal** — `activity_id` from URL is interpolated into filesystem paths without sanitization. Attacker can read/write/delete arbitrary files via `../../` | ✅ Fixed — `_check_id()` validates against `[a-zA-Z0-9\-]+` | -| 2 | Security | `edit/server.py:588` | **Path traversal in `delete_image`** — `filename` param is not `.name`-stripped (unlike `upload_image`), enabling deletion of arbitrary files | ✅ Fixed — `Path(filename).name` strip added | -| 3 | Security | `edit/server.py:542` | **Path traversal in `upload_activity`** — uploaded `file.filename` used directly for staged path | ✅ Fixed — `Path(file.filename).name` strip added | -| 4 | XSS | `ActivityDetail.svelte:67,189` | `marked()` output rendered with `{@html}` without sanitization. Sidecar markdown can inject `