Skip to content

[Epic] Training plans: calendar editing, AI assistance, and Garmin/Suunto/COROS/Wahoo sync #583

Description

@jimmykane

Outcome

Give users a first-class training plan in Quantified Self: show planned sessions on the existing calendar, let users create and modify the plan, let the Assistant propose plan changes in natural language, and reliably deliver the plan's individual structured workouts to Garmin, COROS, Wahoo, and Suunto.

Quantified Self should be the source of truth in v1. Provider sync is outbound. Provider-side edits and inbound plan import are intentionally deferred.

Research snapshot: 2026-08-15.

Product principles

  • Planned workouts are a new domain, not future completed activity events. The calendar overlays planned sessions with imported/completed activities.
  • A plan edit is one logical change regardless of whether it was made in the editor or proposed by AI.
  • AI is proposal-first: show a validated diff, require explicit user acceptance, then apply with an expected-revision check. The model never writes Firestore or calls provider APIs directly.
  • Provider capabilities are not identical. Every workout must be classified as exact, degraded with an explicit warning, or unsupported for each selected provider. Never silently discard steps or targets.
  • Users opt into provider targets and can see delivery state for every scheduled workout.
  • The portable v1 baseline should be structured running and cycling. Provider-specific sports such as swimming, strength, and trail running can be enabled only where the provider contract and adapter tests prove support.

Provider investigation

Provider Capability found v1 interpretation Access/contract gate
Garmin Garmin's Training API publishes workouts and training plans to the Garmin Connect calendar and compatible devices. The app already models the optional WORKOUT_IMPORT permission in #333. Native plan/workout delivery, subject to the exact private contract. The repository does not contain the detailed Training API specification. Obtain the current schema, endpoints, update/delete behavior, limits, test access, and enablement. Garmin's program FAQ also makes partner approval a delivery dependency.
COROS The local COROS API Reference V2.0.6 (February 2026) has a Training Plan API: POST /coros/tp/list/push and POST /coros/tp/workout/deleteById. It accepts today through one year ahead, at most 30 workouts per push, and documents run, bike, swim, strength, and trail run structures. Completed workout data can include planWorkoutId for correlation. Native dated schedule delivery with stable partner IDs, batching, update semantics, and future/unexecuted deletion. Confirm Training Plan entitlement and test credentials; error 30009 means the application lacks access. Clarify replacement semantics for repeated IDs and overlapping push windows before implementation.
Wahoo The public Cloud API separates reusable plan content from scheduling: create a plan through /v1/plans, then create/update a dated workout through /v1/workouts with plan_id. The plan JSON format supports structured running and cycling. ELEMNT's planned-workout behavior exposes the current day plus six days. Create app-owned plan artifacts, attach them to scheduled workouts, and maintain a tested rolling delivery horizon. Use external_id and workout_token for idempotency. Add plans_read and plans_write to the existing workouts scopes, confirm partner approval and same-app ownership behavior, and require reconnect/consent when scopes are missing.
Suunto The official SuuntoPlus Guides API supports create/list/update/delete of app-owned guides. A guide can use usage=workout, a user-local localDate, and a stable externalId; that ID is returned in exported FIT data when the guide was used (FIT documentation). Deliver each scheduled workout as a dated SuuntoPlus Guide. Represent a whole plan as a series of guides, but describe this honestly as individual workout-guide delivery rather than a native provider calendar plan. Confirm Guides entitlement/subscription key, supported watches, storage limits, pin/select behavior, and whether any newer scheduling surface is available. No separate native training-plan/calendar API was found in the current official portal.

Consequences of the provider review

  • “Sync plan” is an orchestration concept in Quantified Self. It maps to a native plan where supported and fans out scheduled workouts everywhere.
  • Suunto does not have feature parity with Garmin/COROS. Its acceptance criteria must be based on dated guide delivery and the real user selection flow.
  • Wahoo constrains the cross-provider baseline to running and cycling and requires a rolling near-term delivery test.
  • Garmin is feasible at the product level, but implementation must not begin against guessed endpoints.
  • Provider access and certification are epic dependencies, not late release tasks.

Proposed v1 user experience

  1. A user creates a plan with a name, date range, time zone, goals/notes, and scheduled workouts.
  2. The /calendar views show planned workouts distinctly from completed activities, including moved, skipped, delivered, failed, and completed states.
  3. A user can create, edit, move, copy, skip, or delete a workout and can make bulk/date-shift changes at plan level.
  4. A user selects one or more connected provider accounts at plan level, with a per-workout override.
  5. Before saving, the editor shows provider compatibility and any loss of fidelity.
  6. Saving creates a new plan revision and asynchronously reconciles provider deliveries.
  7. The Assistant can propose a new plan or changes such as “move Tuesday's intervals to Wednesday and reduce the final repeat.” It returns a structured proposal and human-readable diff; nothing changes until the user accepts it.
  8. Each workout exposes per-provider state, last attempt, useful error/reconnect action, and retry/cancel controls.
  9. A completed imported activity may link back to its scheduled workout when a provider marker or an unambiguous deterministic match exists. Ambiguous matches require user confirmation.

Canonical domain

Add a domain separate from events and Training-derived metrics:

  • TrainingPlan — owner, title, goal/notes, active date range, source time zone, status, selected provider accounts, current revision.
  • WorkoutTemplate — reusable intent and structured-workout definition.
  • ScheduledWorkout — local calendar date/time, sport, title/notes, template snapshot, duration/distance estimates, planned/moved/skipped/completed status, and optional completed event link.
  • WorkoutStructure — ordered warm-up/work/recovery/cool-down steps, repeats, end conditions, and typed targets. The schema must be deterministic and independently validated.
  • ProviderDelivery — provider/account, scheduled workout and revision, external IDs, capability result, desired/actual state, attempts, timestamps, and sanitized error metadata.
  • PlanChangeProposal — base revision, typed operations, validation output, diff, creator (user/AI), approval/apply audit fields.

Date-only workouts must retain the user's IANA time zone and remain stable across travel and DST. Timed workouts need an explicit local instant/offset policy.

Work plan

Phase 0 — contracts, access, and product semantics

  • Acquire the current Garmin Training API specification and sandbox/test access.
  • Confirm COROS Training Plan entitlement, update semantics, limits, and test account behavior.
  • Request Wahoo plan scopes and verify ownership, scheduling horizon, updates, and deletes.
  • Confirm SuuntoPlus Guides access, device workflow, capacity, and supported target/step behavior.
  • Produce a versioned provider capability matrix covering sports, step/end types, targets, repeats, notes, scheduling, update/delete, horizon, size/rate limits, and completion correlation.
  • Approve the v1 source-of-truth, conflict, time-zone, deletion, and unsupported-workout policies.
  • Create redacted provider request/response fixtures for contract tests.

Exit: all four integrations have confirmed access paths and no adapter depends on guessed behavior.

Phase 1 — plan domain and safe mutation boundary

  • Define canonical models, strict validators, limits, revisioning, and ownership rules.
  • Add Firestore rules and write sanitization for plan/workout/proposal/delivery records.
  • Add authenticated callable/service boundaries for CRUD, proposal apply, and sync commands.
  • Make apply atomic with expected-plan-revision conflict detection and an audit trail.
  • Build a dedicated plan-delivery queue/ledger with idempotent keys, retry/backoff, stale-revision suppression, cancellation, and per-account isolation.
  • Handle provider disconnect, reauthorization, account deletion, plan deletion, and data retention without orphaned remote workouts.
  • Add structured logs and metrics for delivery latency, failures, retries, missing scopes, stale jobs, and capability rejections.

Phase 2 — calendar and plan editor

  • Overlay planned workouts on Week/Month/Year calendar queries without weakening the existing completed-activity path.
  • Add plan and workout create/edit/move/copy/skip/delete flows with accessible keyboard and mobile behavior.
  • Add structured step/repeat/target editing plus a provider compatibility preview.
  • Add plan-level provider selection and per-workout override.
  • Add per-provider delivery badges, error explanations, reconnect actions, retry/cancel, and last-synced details.
  • Preserve history of applied revisions so the user can understand what changed.

Phase 3 — AI-assisted creation and editing

  • Define a bounded proposal schema whose operations can only affect the selected user's plan.
  • Give the Assistant the minimum plan/calendar/training context needed for the request; do not expose tokens or provider payloads.
  • Validate proposals deterministically for schema, dates, limits, provider capability, stale revision, and unsafe/unsupported operations.
  • Render a reviewable before/after diff and require explicit acceptance.
  • Apply through the same mutation service as manual edits, then enqueue normal reconciliation.
  • Cover prompt injection, cross-user access, excessive-volume, duplicate-operation, stale-proposal, and partial-failure cases.
  • Make clear that plan suggestions are not medical advice and do not silently override user constraints.

Phase 4 — provider adapters

  • COROS: serialize the canonical structure, batch push windows safely, preserve stable IDs, delete only eligible future workouts, and correlate planWorkoutId on completion.
  • Wahoo: generate/version the plan JSON file, create/update app-owned plans and scheduled workouts, maintain idempotent external IDs/tokens, and honor the tested current-day-plus-six display behavior.
  • Suunto: package valid Guide ZIPs, create/update/delete dated guides with stable externalId, surface device pin/select limitations, and correlate used guides from FIT metadata.
  • Garmin: after Phase 0, implement the documented workout/plan lifecycle, permission/reconnect flow, and device delivery without inferring private contract behavior.
  • For every adapter, reject unsupported semantics or require an explicit user-approved degradation; never silently flatten or drop targets.
  • Add fixture-backed serializer/validator tests and sandbox/manual certification evidence for create, move/update, delete, retry, reconnect, and duplicate delivery.

Phase 5 — completion reconciliation, rollout, and documentation

  • Link completed activities by provider marker when available; otherwise use a bounded deterministic matcher and require confirmation when ambiguous.
  • Decide what happens to missed workouts, late completions, duplicates, and completed workouts after a plan edit.
  • Roll out behind provider-specific feature flags with kill switches and delivery dashboards.
  • Add unit, rules/emulator, queue/idempotency, adapter contract, calendar, AI proposal, accessibility, and end-to-end tests.
  • Update Training workspace, Activity calendar, Provider integration guide, provider integration docs/pages, help content, privacy/deletion guidance, and architecture links.
  • If a public MCP tool/field or bundled skill changes, follow the repository's strict MCP schema, fixture, contract, documentation, and plugin publication lifecycle.
  • Complete provider review/certification and manual production enablement as a separate, explicitly approved release operation.

Acceptance criteria

  • A user can create and maintain a revisioned training plan whose sessions appear correctly on the calendar alongside completed activities.
  • Manual edits and accepted AI proposals use the same validated, authorized, auditable mutation path.
  • AI never applies a plan change without a visible diff and explicit acceptance.
  • Every scheduled workout exposes compatibility and delivery state for every selected provider/account.
  • Create, reschedule/update, skip/delete, retry, reconnect, and plan deletion are idempotent and do not leave duplicate or stale remote workouts.
  • Garmin workouts/plans appear in Garmin Connect/calendar and reach a compatible test device under the documented contract.
  • COROS workouts appear on the intended dates; batching, updates, eligible deletes, and completion correlation are verified.
  • Wahoo running/cycling workouts appear through app-owned plans and scheduled workouts within the supported ELEMNT horizon.
  • Suunto workouts appear as dated SuuntoPlus Guides and can be selected/synced on supported devices; the UI does not claim native plan/calendar parity.
  • Unsupported structures are blocked or explicitly degraded with user approval and test coverage.
  • Time-zone/DST behavior, multi-account isolation, auth refresh/missing scopes, rate limits, retries, disconnects, deletions, and partial provider outages are tested.
  • Help, developer docs, provider pages, privacy/deletion behavior, observability, and rollout controls are complete.

Recommended decisions for v1

  • Quantified Self is authoritative; provider-side plan import and conflict merging are deferred.
  • Provider sync is explicit opt-in, with automatic reconciliation after accepted plan edits.
  • Running and cycling are the portable cross-provider baseline; enable other sports per adapter capability.
  • AI can create or edit proposals, but the user always reviews and accepts.
  • Provider-specific extensions are allowed, but portability loss must be visible before saving/syncing.
  • The epic is complete only when all four providers meet their truthful provider-specific acceptance criteria, even if adapters ship behind separate flags.

Non-goals

  • Importing or merging provider-authored plans in v1.
  • Fully autonomous/adaptive coaching that changes the calendar without user review.
  • Medical diagnosis or a claim that generated training prescriptions are safe for every athlete.
  • Reworking completed activity ingestion, route sync, or Training-derived metric calculations except where needed for explicit linking/context.
  • Pretending all providers support the same sports, targets, schedule horizon, or device UX.

Open product decisions

  • Which account receives a workout when a user connects multiple accounts for one provider?
  • Is provider targeting inherited only from a plan, or can a workout opt into additional providers?
  • What plan revision/history retention and undo window do we promise?
  • Which provider-specific sports are enabled in the first release after the run/bike baseline?
  • Which subscription tier, quotas, and abuse limits apply to AI planning and provider delivery?
  • What exact warnings/confirmation are required for lossy provider mappings?

Related work

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions