diff --git a/CLAUDE.md b/CLAUDE.md index fe2b17d..88eca60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,6 +144,123 @@ vite: { }, ``` +## Activity sidecar edits — design spec + +Users edit activities via **sidecar markdown files** that live alongside BAS JSON in the data dir. +No database, no server — consistent with the project's static-files-only philosophy. + +### File naming + +``` +~/bincio_data/ + 2024-05-15T10:30:00Z_cycling.json ← immutable extract output (never touched) + 2024-05-15T10:30:00Z_cycling.md ← user edits (sidecar) +``` + +Same stem as the JSON, `.md` extension. `bincio extract` never writes `.md` files, +so re-running extract is always safe and will never clobber user edits. + +### Sidecar format + +YAML frontmatter + optional Markdown body: + +```markdown +--- +title: "Epic climb up Monte Grappa" +sport: cycling # override detected sport +hide_stats: [cadence] # suppress specific stat panels in detail view +highlight: true # pin/feature in feed (shown first, maybe badged) +private: false # exclude from public feed +gear: "Trek Domane" # freeform gear note +--- + +Rode with Marco and Giulia. Legs felt great after the rest week... +``` + +- All frontmatter keys are optional; omit means "keep extracted value" +- The Markdown body becomes the activity's `description`, rendered as HTML in the detail page +- `hide_stats` takes stat panel names: `elevation`, `speed`, `heart_rate`, `cadence`, `power` + +### Where overrides are applied: the render stage + +The **render stage** (`bincio render`) is the right place — not extract, not the browser. + +- Extract → clean BAS JSON (immutable) +- Render → merges sidecars → Astro build consumes enriched data + +A `bincio.render.merge` module walks the data dir, finds `*.md` sidecars, +and produces either enriched JSON files or a separate `overrides/index.json` +that Astro reads at build time. The site never needs to fetch a `.md` file +at runtime — all merging is build-time, keeping the static-first guarantee. + +### Federation angle + +Sidecars work for *remote* activities too: if you include someone else's BAS feed, +you can write local `.md` sidecars for their activity IDs. Your render stage applies +your overrides on top of their data. This is a natural extension of the local case. + +### Editing UX: `bincio edit --serve` + +A separate FastAPI server (`bincio edit --serve`, default port 4041) handles all writes. +The static site and Astro are untouched — no hybrid mode, no dead-code API routes in prod. + +**How it works:** + +``` +bincio edit --serve --data ~/bincio_data # starts on :4041 +``` + +- Serves a bundled Svelte UI (single compiled HTML, reuses existing Svelte investment) +- `GET /api/activity/{id}` — returns merged BAS JSON + existing sidecar fields +- `POST /api/activity/{id}` — writes `edits/{id}.md` to the data dir +- `POST /api/activity/{id}/images` — multipart upload → `edits/images/{id}/{filename}` +- The Astro dev server's file watcher picks up `.md` writes → incremental rebuild + +**Edit UI features:** +- Title text input (pre-filled from BAS JSON) +- Sport dropdown (pre-filled, shows all known sport types) +- Markdown textarea for description, with minimal toolbar (bold, italic, link, image insert) +- Live markdown preview panel +- `hide_stats` checkbox group: elevation, speed, heart_rate, cadence, power +- `highlight` toggle (feature in feed) +- `private` toggle (suppress from feed at render time) +- Image drag-and-drop zone → uploads to `edits/images/{id}/`, inserts `![]()` into textarea +- Save button → POST to API → success toast + +**Workflow (typical):** +1. User browses the Astro dev server on :4040 +2. Activity detail page has an "Edit" button (rendered only when `PUBLIC_EDIT_URL` env var is set) +3. Button links to `:4041/edit/{id}` — opens the FastAPI-served edit UI +4. User fills in form, saves → sidecar written → Astro rebuilds → refreshing :4040 shows changes + +The `PUBLIC_EDIT_URL` env var in `.env` controls whether the Edit button appears; +leave it unset for production builds, set to `http://localhost:4041` for local dev. + +### Image storage + +``` +~/bincio_data/ + edits/ + 2024-05-15T10:30:00Z_cycling.md + images/ + 2024-05-15T10:30:00Z_cycling/ + col-summit.jpg + group-photo.jpg +``` + +Images are referenced in the markdown body with relative paths: `![Summit](col-summit.jpg)`. +The render stage resolves relative image paths against `edits/images/{id}/` and copies them +to `site/public/images/activities/{id}/` so they're served from the static site. + +### Decided + +- **Sidecar location**: `edits/` subdirectory (not co-located with JSON) — cleaner, easier to + backup/sync just your customisations independently of the extracted data +- **`private: true`**: suppresses from `index.json` at render time (not client-side hide) — + safer for public hosting +- **`highlight`**: visual badge in feed + sorted before non-highlighted activities +- **Edit UI**: `bincio edit --serve` FastAPI server (Option B) — not integrated into Astro + ## Known issues / next steps - `bincio render` Python CLI is a stub — site is built via `npm run build` directly @@ -165,3 +282,9 @@ vite: { - [ ] Map thumbnail in activity cards (SVG path from GeoJSON) - [ ] GitHub Actions template for auto-publish - [ ] Karoo/Garmin Connect importers beyond Strava +- [ ] `bincio.render.merge` module: walk `edits/`, parse sidecars, produce enriched data for Astro +- [ ] `bincio render --watch` incremental rebuild on sidecar changes +- [ ] Sidecar `.md` format: title, sport, description, hide_stats, highlight, private, images +- [ ] `bincio edit --serve` FastAPI server with Svelte edit UI (port 4041) +- [ ] Edit button on activity detail pages (visible when `PUBLIC_EDIT_URL` env var set) +- [ ] Image upload → `edits/images/{id}/`, render stage copies to `public/images/activities/{id}/`