diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6221057 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,43 @@ +# Secrets and local-only files — never bake these into the image. +# The app reads all config via os.environ, populated by docker-compose's +# env_file directive at container start; it never reads a file from disk, +# so excluding .env here doesn't change runtime behavior at all. +.env +.env.* +!.env.example + +# Git metadata — large, irrelevant to the running app, and would otherwise +# ship the full commit history inside every built image. +.git/ +.gitignore +.gitattributes + +# Runtime data — volume-mounted at /app/data and /app/csv-data; baking in +# whatever happens to be on the build host's disk at build time would be +# both useless (shadowed by the volume mount) and a data leak. +data/ +csv-data/ + +# Python cache / virtual envs +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ +.venv/ +venv/ + +# Docs — only needed for GitHub's README rendering, not by the running app +docs/ + +# Local design references — not part of the app +designidea.webp + +# OS cruft +.DS_Store +Thumbs.db +desktop.ini + +# Editor/tooling +.worktrees/ +.superpowers/ diff --git a/.env.example b/.env.example index 6db3b38..3dc3ba1 100644 --- a/.env.example +++ b/.env.example @@ -4,14 +4,34 @@ # Flask SECRET_KEY=change-me-to-a-long-random-string +# Timezone the container's clock (and therefore the 3:00 AM nightly scheduler) +# runs on. Without this, the container defaults to UTC regardless of where +# it's hosted, so "3:00 AM" silently means 3:00 AM UTC, not your local time. +# Use an IANA name, e.g. America/Toronto, America/New_York, Europe/London. +# See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +TZ=America/Toronto + # Feature flags USE_AI_WOD=false -# Anthropic API key — only needed if USE_AI_WOD=true. When set, the daily -# WOD's warm-up/cool-down/coaching notes are written by Claude instead of -# the static templates. Get a key at https://console.anthropic.com/ +# Optional AI "coach's read" on the Insights page. When true (and a key is set +# below), Claude writes a short first-person synthesis of your computed +# insights. The insight cards themselves are always rule-based and show either +# way. Off by default. +USE_AI_INSIGHTS=false + +# Anthropic API key — needed only if USE_AI_WOD=true or USE_AI_INSIGHTS=true. +# When set, the daily WOD's warm-up/cool-down/coaching notes and/or the +# Insights coach's read are written by Claude instead of static text. +# Get a key at https://console.anthropic.com/ ANTHROPIC_API_KEY= +# Background scheduler — nightly Concept2 sync, PB recalc, badge eval, and DB +# backup. On by default. Set to false on a secondary/dev instance so it doesn't +# run the nightly jobs (and send duplicate notification emails) alongside the +# instance that owns your live data. +RUN_SCHEDULER=true + # Concept2 API C2_CLIENT_ID= C2_CLIENT_SECRET= diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4961c5b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +version: 2 +updates: + # Python dependencies (requirements.txt, requirements-dev.txt) + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + labels: + - "dependencies" + + # Docker base image (Dockerfile) + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + labels: + - "dependencies" + + # GitHub Actions used in .github/workflows/ + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + labels: + - "dependencies" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..12bdd26 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,26 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + pytest: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v7 + with: + python-version: "3.11" + cache: "pip" + cache-dependency-path: requirements-dev.txt + + - name: Install dependencies + run: pip install -r requirements-dev.txt + + - name: Run tests + run: pytest -v diff --git a/.gitignore b/.gitignore index 395fb91..1c1dc32 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,14 @@ csv-data/ # Project spec files row-tracker-spec.md docs/redesign-spec.md +docs/superpowers/ +docs/marketing-plan.md + +# Local design references +designidea.webp # Local git worktrees .worktrees/ + +# Local-only deploy trigger (references personal homelab; not for the public repo) +deploy.sh diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bfc2ce8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,349 @@ +# Changelog + +All notable changes to Row Tracker are documented here. + +Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows +[Semantic Versioning](https://semver.org/) — while the major version is `0`, minor bumps may +include breaking changes (`.env` keys, schema, etc.), same as any other pre-1.0 project. + +History below `0.9.0` is backfilled from commit history at the point versioning was introduced — +these releases weren't tagged contemporaneously, but the groupings and dates reflect what actually shipped. + +## [0.10.4] — 2026-08-16 + +### Added +- **Dependabot** (`.github/dependabot.yml`) — weekly automated PRs for outdated pip dependencies + (`requirements.txt`/`requirements-dev.txt`), the Docker base image, and the GitHub Actions added + in 0.10.2. Every Dependabot PR gets checked by the same CI workflow as any other PR, so a bump + that breaks something fails the check instead of merging silently. + +## [0.10.3] — 2026-08-16 + +### Fixed +- CI workflow pinned `actions/checkout@v4` and `actions/setup-python@v5` — the first run flagged + both as being forced onto a deprecated Node.js runtime. Bumped to the current majors + (`@v7`/`@v7`). + +## [0.10.2] — 2026-08-16 + +### Added +- **CI: GitHub Actions now runs the full test suite on every push to `main` and every PR** + (`.github/workflows/tests.yml`) — 228 tests, Python 3.11 to match the Docker base image, pip + dependency caching. The test suite has existed for a while but nothing ran it automatically; + now a red check on a PR means something needs a look before merge. Status badge added to the + top of the README, alongside the license badge. +- `CONTRIBUTING.md` updated to mention the automated check. + +## [0.10.1] — 2026-08-15 + +### Fixed +- **"Insights" was missing from the mobile nav drawer** — it was added to the desktop nav when + the Insights page shipped (0.9.5) but never added to the hamburger menu, so it was invisible on + phones/tablets ever since. Added, and covered by a new regression test that diffs the desktop + and mobile nav link sets so a future addition can't silently repeat this. + +## [0.10.0] — 2026-08-15 + +### Fixed +- **The v0.9.12 timezone fix didn't actually fix the root cause.** `scheduler.py` has always + passed `timezone="America/Toronto"` to `BackgroundScheduler`, which looks correct — but that + setting does **not** propagate to a job's `CronTrigger` unless the trigger is *also* given an + explicit timezone. Every nightly `CronTrigger(hour=3, ...)` call was silently falling back to + the container's OS clock instead, which is exactly the bug v0.9.12's `TZ` env var papered over + by making the OS clock coincidentally correct. Every `CronTrigger` now gets the timezone + explicitly (sourced from the `TZ` env var, defaulting to `America/Toronto`), so the schedule is + correct regardless of the container's OS timezone. +- **A bad or expired C2 API token was indistinguishable from "nothing new to sync."** `get_results()` + caught 401s and network failures internally and just returned an empty list — identical to a + genuinely successful call that found zero new workouts. `C2ApiClient` now tracks the actual + failure reason (`last_error`) and `sync_workouts()` surfaces it as a real error instead of a + silent no-op. +- The manual Sync button's frontend only treated `status: "error"` as a failure, missing the + `"partial"` state the `/sync` route already returns when a sync completes with errors — a + partially-failed sync showed as a plain success in the UI. Now shown as a failure with the + actual error message. + +### Added +- **"Last synced" indicator on the Dashboard**, next to the Sync button — shows how long ago the + last successful sync ran, or a clear warning if the most recent attempt failed. Backed by a new + `SyncStatus` table, updated by both the nightly scheduler and manual syncs. +- **Email alerts on scheduled-job failure.** The nightly sync, PB recalc, badge evaluation, and + backup jobs previously only logged their own failures — now they also email `NOTIFY_EMAIL` + (same address badge/milestone notifications already use), so a broken job doesn't sit unnoticed + until someone happens to check container logs. + +## [0.9.12] — 2026-08-15 + +### Fixed +- **The nightly 3:00 AM scheduler (sync, PB recalc, badge eval, backup) was actually running at + 3:00 AM UTC**, not 3:00 AM local time as the FAQ/Quick Start have always documented ("3:00 AM + Toronto time"). The container had no timezone configured, so it silently defaulted to UTC — + for anyone east of Greenwich in winter or west of it generally, that's several hours off from + the documented time, and any workout logged in that gap wouldn't sync until the *following* + night. Added a `TZ` variable to `.env.example` (defaults to `America/Toronto`, matching the + docs) — set it to your own [IANA zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) + if you're elsewhere. Existing deployments need to add `TZ=` to their own `.env` and restart — + see the updated README. + +## [0.9.11] — 2026-08-14 + +### Security +- **Chart.js now loads from a CDN with Subresource Integrity**, closing the one blind spot left + from the earlier security review: the vendored copy had no version pinned anywhere and no way + to know if it went stale. It's now pinned to Chart.js 4.4.1 with a `sha384` SRI hash verified + against the actual bytes jsDelivr serves — a tampered or compromised CDN response would simply + fail to execute rather than run silently. +- **The local copy stays as an automatic fallback** (`window.Chart || document.write(...)`) so + Row Tracker's "no external dependencies required" promise holds even fully offline — an + air-gapped homelab or a CDN outage falls back to the same file the service worker already + pre-caches for offline PWA use. The fallback file is now byte-identical to the pinned CDN + version (previously a different, unverified build had been hand-vendored). +- All five chart-rendering templates now share one partial (`_chart_cdn.html`) instead of + duplicating the script tag, with the exact commands to regenerate the hash and fallback file + documented inline for the next version bump. + +## [0.9.10] — 2026-08-14 + +### Security +- **Upgraded the Docker image's build toolchain and OS packages.** A vulnerability scan of the + built image found known CVEs in the base image's bundled `pip`/`setuptools`/`wheel` (and + `jaraco-context`, a pip dependency) — none had ever been upgraded past whatever version shipped + with the `python:3.11-slim` base image. The Dockerfile now explicitly upgrades them before + installing app dependencies, and runs `apt-get upgrade` for OS-level packages so future rebuilds + pick up Debian's security patches automatically. This resolved every fixable finding from the + scan (1 HIGH, 1 HIGH, 5 MEDIUM/LOW). The remaining findings are in Debian OS utilities (chiefly + `perl`, present in every Debian-based image) with no upstream fix published yet, and are not + reachable through the application's own code — Row Tracker never shells out to any OS binary. + +## [0.9.9] — 2026-08-11 + +### Fixed +- **Insights volume figures now read in consistent kilometres.** The year-over-year and + weekly-volume cards rendered distances as an ambiguous "k m" hybrid (e.g. `1,996k m`, + `56.0k m/week`) that read like a typo. They now show clean kilometres (`1,996 km`, + `56.0 km/week`), including the pill and sparkline labels. + +## [0.9.8] — 2026-08-10 + +### Added +- **`RUN_SCHEDULER` env flag** (default `true`). Set it to `false` on a secondary or + development instance so it doesn't run the nightly Concept2 sync, PB recalc, badge + evaluation, and backup — and doesn't fire duplicate notification emails — alongside the + instance that owns your live data. Documented in `.env.example`. + +## [0.9.7] — 2026-08-10 + +### Added +- **Brand logo throughout the app.** The circular rower emblem now sits in the nav bar + (desktop and mobile) in place of the placeholder emoji, and the full "ROW TRACKER" + lockup anchors the top of the Dashboard as a theme-switched hero — the dark-mode + artwork in dark mode, the light-mode artwork in light mode. Logos were processed to + transparent backgrounds so they sit cleanly on any surface, and both versions share an + identical frame so switching themes causes no size shift. + +## [0.9.6] — 2026-08-10 + +### Added +- **Milestones section on the Insights page** — all-time-highlight cards rendered as a + big-number treatment: years rowing (with session count), biggest single day, total + hours on the erg, and longest unbroken streak. These are facts rather than patterns, + so they carry no confidence tag and appear once there's a real history behind them. +- **Year-over-year volume insight** — compares meters logged Jan 1 → today against the + identical span of last year, so progress (or a lull) shows up as it happens. + +### Changed +- **Pace-trend insight now measures steady pieces only** (20 min+). Trending pace across + all workout types was confounded by changes in workout mix — more sprints or more easy + volume could masquerade as a pace change. Restricting to steady work makes the trend + mean what it says. +- Tuned insight surfacing against real data: day-of-week, rest-gap, and seasonal pace + effects were left gated (the underlying signal is genuinely flat at the median, so + loosening thresholds would have manufactured noise) while the new milestone and + year-over-year rules add substance that the data actually supports. + +## [0.9.5] — 2026-08-10 + +### Added +- **Insights page** — a new nav section that reads your whole history and surfaces + patterns in plain language (best day of the week, rest-day effect, pace and volume + trends, fastest stroke rate in steady pieces, session-length clusters, consistency, + PB clustering). Each insight clears a minimum-sample and significance check before it + appears and is tagged **Strong pattern** or **Early signal**; the strongest carry a + recommendation, some linking into the WOD generator. Implemented as a deterministic, + rule-based engine (`insights_engine.py`) that runs entirely on your server. +- **Optional AI "coach's read"** (`insights_ai.py`, gated by `USE_AI_INSIGHTS=true` + + `ANTHROPIC_API_KEY`, off by default) — a short first-person synthesis at the top of the + Insights page. It only rephrases the facts the engine already computed and never + invents a number; the cards render identically without it. + +## [0.9.4] — 2026-08-10 + +### Fixed +- **Manual Sync (and any other POST) no longer fails after the page has been open a while.** + CSRF tokens carried a default 1-hour time limit, so clicking "Sync workouts" on a + long-open dashboard returned `400 request failed — check logs` with a + `CSRF token has expired` log line. The time limit is now disabled; tokens stay + session-bound, which is the actual CSRF protection. + +## [0.9.3] — 2026-08-07 + +Closes out the pre-public-release audit started in 0.9.2. + +### Security +- **Rewrote git history to remove a leaked Gmail App Password** that had been present in + `.env.example` from 2026-06-14 to 2026-07-23 (the value itself was already revoked before + this fix). Every commit SHA from that point forward changed as a result — this repo's history + was force-pushed once as part of this fix. No other secrets were found anywhere in history. + +### Added +- `CONTRIBUTING.md` — how to report bugs/features, the fork→branch→PR flow, dev setup, and the + doc-sync convention this codebase follows. Linked from the README. +- GitHub topics for discoverability: `self-hosted`, `concept2`, `rowing`, `ergometer`, `flask`, + `docker`, `python`, `fitness-tracker`, `homelab`. + +### Fixed +- README claimed AI coaching was "the only feature that talks to a third party" — inaccurate, + since the Feedback button also emails the developer directly (recipient is hardcoded, not + `.env`-configurable). Added a dedicated Feedback section spelling out exactly what it does + and doesn't send. +- Removed dead code in `c2_api.py`: `_persist_refresh_token()` had no callers (Concept2 issues a + non-expiring bearer token, so nothing ever rotates it) and wouldn't have worked reliably even + if called — it wrote to `.env` inside the container, which isn't a mounted file and is now + correctly excluded from the image entirely. Also fixed the module docstring, which still + described an OAuth token-exchange flow the code never actually implements. + +### Changed +- Untracked `designidea.webp` (unreferenced design-reference image) and two internal + `docs/superpowers/` AI-agent planning docs — kept locally, gitignored, consistent with the + existing `row-tracker-spec.md` / `docs/redesign-spec.md` convention. + +## [0.9.2] — 2026-08-07 + +Fixes from a pre-public-release audit. The remaining items from that audit (a leaked +credential in git history, a couple of untracked-file cleanup questions) required user +decisions and are closed out in 0.9.3 above. + +### Fixed +- **`.env` was being baked directly into the built Docker image** — no `.dockerignore` existed, + so `COPY . .` copied the real, secret-filled `.env` file (and the entire `.git` history) into + every image layer. The app never actually reads that in-image copy (all config comes from + `os.environ`, populated by Compose's `env_file` at container start), so excluding it is purely + a fix, not a behavior change. Added `.dockerignore` excluding `.env`, `.git/`, `data/`, + `csv-data/`, caches, and other build-irrelevant paths. +- README's clone command still had the placeholder `yourusername` instead of the real + `dsubtle1` — first-time visitors couldn't copy-paste it correctly. +- `LICENSE.md` and the README's embedded license text had mismatched copyright-name casing + (`dSubtle1` vs `dsubtle1`). + +### Changed +- Removed `python-dotenv` from `requirements.txt` — never actually imported anywhere; the app + reads config exclusively via `os.environ`. +- Minor doc-sync polish: added `VERSION`/`CHANGELOG.md` to the README's Project Structure tree, + normalized a wording mismatch between `QUICKSTART.md` and its in-app twin ("Start a Journey" → + "Start a Virtual Journey"). + +## [0.9.1] — 2026-08-07 + +### Changed +- The version display moved from a centered line at the bottom of page content to a small, + low-opacity `vX.Y.Z` badge fixed to the bottom-right corner of the viewport on every page + +### Fixed +- The service worker's static-asset cache (`CACHE_NAME`) was a fixed string that never changed + across deploys, so any CSS/JS update was invisible to a browser that had already loaded the app + once — cache-first meant it just kept serving the old file forever. `sw.js` is now rendered from + a Jinja template with `CACHE_NAME` tied to `app_version`, so every version bump automatically + invalidates the old cache instead of silently serving stale static assets + +## [0.9.0] — 2026-08-07 + +### Added +- AI-assisted WOD coaching narrative — optional Claude Haiku-generated warm-up/cool-down/coaching + notes, feature-flagged via `USE_AI_WOD` + `ANTHROPIC_API_KEY`; falls back to the static rule-based + text automatically if disabled or unavailable +- Versioning: `VERSION` file, this changelog, and the version now shown in the site footer and FAQ page +- Explicit self-hosting/privacy statement in the README — no Row Tracker backend, your own Concept2 + and Anthropic credentials, your own data + +### Changed +- Removed the Support/sponsorship section from the README for now + +## [0.8.0] — 2026-08-07 + +### Added +- Email notifications for newly earned badges, lifetime-metres milestones, and virtual journey completions + (`NOTIFY_EMAIL`, defaults to `MAIL_USERNAME`) + +### Changed +- Refreshed README screenshots and fixed stale documentation + +## [0.7.0] — 2026-08-06 + +### Added +- PWA install support — manifest, app icons, service worker for offline-capable static asset caching +- Nightly automated SQLite database backups (30-day retention) via APScheduler +- Data export — workout history and personal bests as CSV or JSON +- Route test coverage across all four blueprints +- Distinct icon + progress bar per badge +- Clickable journey waypoints with a details popup and Wikipedia photo banner + +### Fixed +- Badge `earned_date` defaulting to today instead of the actual earned date +- Journey map label overlap and mobile scaling +- Grainy waypoint banner images (now uses the original file, not a rescaled thumbnail) +- Dependency bump to close known CVEs + +### Changed +- Vendored Chart.js locally instead of loading from a CDN + +## [0.6.0] — 2026-08-05 + +### Added +- WOD History rewritten as a month-by-month calendar (`/wod/history`, `/api/wod/day`) +- Journey Map teaser card on the dashboard + +## [0.5.0] — 2026-08-04 + +### Added +- Dashboard redesign (Phase R2) — circular lifetime-metres gauge, pace/volume sparklines, + "Your Progress" journey checklist + +### Fixed +- Badges never being earnable — `seed_badges()` was never actually called on startup + +## [0.4.0] — 2026-08-04 + +### Added +- Full pytest suite for engine modules (PBs, badges, WOD generation) +- Real planned-session tracking for the Iron Month badge +- Per-stroke pace/stroke-rate visualization on the workout detail page +- UI-driven CSV import for pre-API-access seasons +- CSRF protection on every state-changing route + +### Fixed +- Personal best delta (improvement-vs-previous) tracking bug + +### Changed +- Redesign polish (Phase R1) — light mode and teal accent consistency +- `SECRET_KEY` fallback behavior + +## [0.3.0] — 2026-06-28 to 2026-07-23 + +### Changed +- README screenshots, license, and formatting refresh + +### Security +- Replaced a leaked Gmail app password in `.env.example` with a placeholder + +## [0.2.0] — 2026-06-17 to 2026-06-18 + +### Added +- Mobile-responsive layout +- Heart rate data on workout detail (min/avg/max, zone classification) +- Chart improvements + +## [0.1.0] — 2026-06-14 + +### Added +- Initial release: dashboard, Concept2 Logbook sync, personal bests, in-app feedback form, + FAQ and Quick Start guide diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c8d4a50 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing to Row Tracker + +Thanks for considering it — this is a personal hobby project, but it's public because other Concept2 rowers might find it useful, and because improvements from people who actually use it are welcome. + +This is early-stage (alpha), single-maintainer software, so keep expectations calibrated: response times may be slow, and conventions here may still shift. That said, real contributions — bug fixes, small features, doc corrections — are genuinely appreciated. + +## Reporting bugs or suggesting features + +Open a [GitHub Issue](https://github.com/dsubtle1/row-tracker/issues). Include: +- What you expected vs. what happened +- Steps to reproduce, if it's a bug +- Your setup if relevant (self-hosted, so environment details sometimes matter) + +If you're running the app and just want to flag something quickly, the in-app **💬 Feedback** button also reaches the maintainer directly — that one's better for quick notes than for anything you want tracked publicly. + +## Proposing a code change + +1. Fork the repo and create a branch off `main`. +2. Follow the [Self-Hosting](README.md#self-hosting) steps to get a local dev instance running. +3. Make your change. A few conventions this codebase follows: + - Engine logic (PB calculation, badges, WOD generation) lives in top-level modules (`pb_engine.py`, `badge_engine.py`, `wod_engine.py`, ...) and is unit-tested directly — Flask routes in `blueprints/` stay thin. + - No frontend build step — plain Jinja2 templates, vanilla JS, and hand-written CSS in `static/`. + - If your change affects user-facing behavior, update `README.md`, `FAQ.md`, and/or `QUICKSTART.md` — and their in-app template twins (`templates/tracker/faq.html` + `faq_template.html`, `templates/tracker/quickstart.html` + `quickstart_template.html`, which should stay identical to each other) — as part of the same PR, not a follow-up. +4. Run the test suite before opening a PR: + ```bash + docker compose exec row-tracker pip install -r requirements-dev.txt + docker compose exec row-tracker python -m pytest + ``` +5. Open a PR against `main` with a short description of what changed and why. GitHub Actions runs + the full test suite automatically on every PR (see the badge at the top of the README) — a red + check means something needs a look before merge. + +## What's especially useful + +- Bug fixes with a clear repro +- Fixes for anything in [FAQ.md's Known Issues](FAQ.md#known-issues) +- Small, focused features rather than large speculative ones — easier to review, easier to merge +- Doc corrections, typo fixes, clarity improvements — always welcome, no need to ask first + +## License + +By contributing, you agree your contribution is licensed under the project's [MIT License](LICENSE.md). diff --git a/Dockerfile b/Dockerfile index a0e6d9e..572965d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,20 @@ FROM python:3.11-slim WORKDIR /app +# Apply any OS-level security patches Debian has published since this base +# image tag was built. A vulnerability scan found several CVEs in OS packages +# shipped with the base image; most had no fix available at scan time, but +# this keeps every future rebuild current with whatever Debian does publish, +# without needing another manual Dockerfile change each time. +RUN apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/* + # Install dependencies COPY requirements.txt . +# The base image bundles whatever pip/setuptools/wheel version was current +# when that image tag was published. A vulnerability scan found known CVEs in +# those bundled versions (and jaraco-context, a pip dependency) — upgrade the +# build toolchain itself before installing anything else. +RUN pip install --no-cache-dir --upgrade pip setuptools wheel jaraco.context RUN pip install --no-cache-dir -r requirements.txt # Copy application code diff --git a/FAQ.md b/FAQ.md index 2760b2f..724eb8d 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,6 +1,6 @@ # Row Tracker — FAQ & Known Issues -*Alpha release · June 2026* +*Alpha release · v0.9.0* --- @@ -28,7 +28,10 @@ Yes. On iPhone/iPad, open Row Tracker in Safari, tap the Share icon, then **Add ### Data & Sync **How often does my data sync?** -Automatically every night at 3:00 AM Toronto time. You can also click the Sync button on the Dashboard at any time to pull in your latest workouts immediately. +Automatically every night at 3:00 AM (see `TZ` in your `.env` — defaults to Toronto time). You can also click the Sync button on the Dashboard at any time to pull in your latest workouts immediately. + +**How do I know if the nightly sync is actually working?** +The Dashboard shows a "Last synced" indicator next to the Sync button, so you don't have to take it on faith. If a nightly sync, PB recalc, badge evaluation, or backup job fails, Row Tracker also emails whatever address `NOTIFY_EMAIL` (or `MAIL_USERNAME`) points to — the same address badge and milestone notifications use — so a broken sync doesn't go unnoticed. **My latest workout isn't showing — what should I do?** First, make sure your workout has synced to the Concept2 Online Logbook via ErgData. Then click the Sync button on the Dashboard. If it still doesn't appear, wait a few minutes and try again — occasionally the Concept2 API has a short delay. @@ -76,6 +79,19 @@ Optionally. Set `USE_AI_WOD=true` and add an `ANTHROPIC_API_KEY` in your `.env` --- +### Insights + +**What is the Insights page?** +It reads your whole workout history and surfaces patterns in plain language — things like which day you tend to row fastest, whether a rest day sharpens your next session, how your pace is trending, which stroke rate your steady pieces fly at, and how this year compares to last. It also closes with a Milestones section of all-time highlights (years rowing, biggest single day, total hours on the erg, longest streak). Each card may carry a suggested next step. + +**Why don't I see many insights yet?** +Every insight has to clear a minimum-sample and significance check before it appears — a pattern won't show up on three data points. Cards are tagged **Strong pattern** or **Early signal** so you can tell how much weight to give each one. As you log more sessions, more cards unlock. + +**Are the insights AI-generated?** +No — the cards are computed entirely on your own server by a rule-based engine, and work fully offline. There's an *optional* extra: set `USE_AI_INSIGHTS=true` with an `ANTHROPIC_API_KEY` and Claude Haiku adds a short first-person "coach's read" at the top that ties the cards together. It only ever rephrases the numbers the engine already computed — it never invents a figure — and everything works the same without it. + +--- + ### Achievements & Badges **How do I earn badges?** diff --git a/LICENSE.md b/LICENSE.md index 6c83d00..26a42db 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -Copyright (c) 2026 dSubtle1 +Copyright (c) 2026 dsubtle1 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/QUICKSTART.md b/QUICKSTART.md index 31df00b..a9d7638 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -1,6 +1,6 @@ # Row Tracker — Quick Start Guide -*Alpha release · June 2026* +*Alpha release · v0.9.0* --- @@ -75,7 +75,7 @@ Click **View all →** under History to open the WOD calendar — a month-by-mon --- -## Step 6 — Start a Journey +## Step 6 — Start a Virtual Journey Click **Journeys** in the navigation bar. @@ -102,6 +102,16 @@ Click **Achievements** in the navigation bar to see: --- +## Step 8 — See Your Insights + +Click **Insights** in the navigation bar. Row Tracker reads your whole history and surfaces patterns in plain language — which day you row fastest, whether a rest day sharpens your next session, how your pace is trending, which stroke rate your steady pieces fly at, and more. Each pattern shows as a card, tagged **Strong pattern** or **Early signal** so you know how much to trust it, and the strongest ones suggest a next step. + +Don't worry if the page looks sparse at first — insights only appear once there's enough data behind them to trust, so more cards unlock as you log sessions. + +> Want a short "coach's read" that ties the cards together in a sentence or two? Set `USE_AI_INSIGHTS=true` and add an `ANTHROPIC_API_KEY` in your `.env`. It only ever rephrases the numbers already on the page, and the cards work exactly the same without it. + +--- + ## A Few Tips - **Dark/light mode** — use the ☀️ toggle in the top-right corner to switch themes diff --git a/README.md b/README.md index 9a6b2b5..5e09ae6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,22 @@ -# 🚣 Row Tracker +
+
+