Skip to content

[FEATURE REQUEST] Full parity between default and custom exercises: editable/deletable built-ins and unified exercise schema #199

Description

@giulioleuci

What problem does this solve?

Currently, openGym enforces a strict architectural dichotomy between built-in (default) exercises and custom (user-created) exercises:

  1. Default exercises are immutable and permanent:

    • The ~1,320 built-in exercises from the static catalogue (EXDB / CATALOGUE) cannot be edited. Users cannot alter exercise names to match their gym's equipment or regional naming conventions, adjust target or secondary muscle assignments according to their biomechanics, customize setup cues, or correct equipment designations.
    • Default exercises cannot be deleted or hidden. The vast built-in catalogue includes numerous variations and machine-specific movements that many users will never perform. This clutters the Library, routine exercise pickers, and search results with irrelevant entries that users cannot prune.
  2. Custom exercises are second-class citizens with missing parameter parity:

    • Custom exercises in S.customEx lack several parameters that built-in exercises possess:
      • Structured step-by-step instructions (st): Built-in exercises have an array of step strings displayed cleanly under the "How to" section. Custom exercises only offer an unstructured single-text desc field.
      • Visual media (img / gif): Built-in exercises link to demonstrations and animations. Custom exercises have no mechanism to attach, link, or display reference imagery, animations, or URLs, falling back to a static icon.
      • Muscle categorization discrepancies: Built-in exercises feature structured combinations of tg (target), mg (muscle group), sm (secondaries), as well as overlaid primaries and secondaries. In custom exercises, these are synthesized ad-hoc (primaries, secondaries, muscleGroups), leading to schema inconsistencies.
    • The UI enforces this asymmetry explicitly: the "Edit" and "Delete" actions in the exercise detail sheet are gated behind {ex.custom && ...}, making built-ins strictly read-only while custom exercises lack full feature richness.

Users need a consistent, flexible exercise catalogue where all exercises share the exact same parameter structure, custom exercises have access to every field that built-in exercises have, and default exercises can be customized, hidden/deleted, or reset by the user.

Proposed approach

1. Unified Exercise Data Schema

Standardize the data representation for both built-in and user-created exercises so that every exercise in the runtime index exposes identical properties:

  • id: Unique identifier (e.g., '0001' for built-ins, 'c...' or UUID for custom).
  • n: Name.
  • bp: Body part category (e.g., 'chest', 'back', 'upper legs').
  • eq: Equipment type.
  • tg / primaries: Primary target muscle(s).
  • sm / secondaries: Secondary muscle(s).
  • st: Step-by-step instructions (array of strings for the "How to" guide).
  • desc: Detailed notes, setup tips, or execution cues.
  • img / gif: Media reference (asset filename, custom image/GIF URL, or media identifier).
  • custom: Flag indicating whether the exercise was originally user-created.
  • isModified: Derived or stored flag indicating whether a built-in exercise has active user overrides.

2. User Overrides for Built-in Exercises (Overlay Pattern)

To keep the bundle lightweight and avoid copying the entire ~1,320-exercise catalogue into user state, preserve the pristine static EXDB as the base layer and store only the user's modifications in user state:

  • Add exOverrides: {} to state S (DEF in store/useStore.js), keyed by exercise ID:
    {
      "exOverrides": {
        "0025": {
          "n": "Barbell Bench Press (Powerlifting Grip)",
          "desc": "Arch back, retract scapulae, drive with legs.",
          "st": ["Set up under the bar...", "Unrack with straight arms..."]
        }
      }
    }
  • When building CATALOGUE and EXIDX in src/lib/exercises.js, apply S.exOverrides[id] over the base catalog entry:
    const effectiveExercise = { ...baseExercise, ...(st.exOverrides?.[baseExercise.id] || {}) }
  • Provide a "Reset to default" action in the UI whenever viewing or editing a built-in exercise that has active overrides.

3. Deleting and Hiding Built-in Exercises (Tombstone Pattern)

Because built-in exercises originate from the static codebase rather than mutable arrays, deleting a built-in exercise should act as a user-level deletion / exclusion:

  • Add deletedEx: [] (array of hidden/deleted built-in exercise IDs) to state S.
  • When a user deletes a default exercise:
    • Append its ID to S.deletedEx.
    • Filter out deletedEx in allExercises(S), EXIDX, Library search, and exercise selection pickers.
    • Apply the existing safe-deletion routine (deleteCustomEx logic): clean up future routine references while preserving historical logs in workouts using exerciseMuscleSnapshot.
  • In Settings (or a Library filter/overflow menu), provide a "Manage hidden exercises" or "Restore default exercises" screen where users can review deleted built-in exercises and restore them individually or in bulk.
  • Deleting a custom exercise continues to permanently remove it from S.customEx.

4. Upgrade the Exercise Editor (CustomExForm → Unified ExerciseEditorSheet)

Consolidate exercise creation and editing into a unified form component:

  • Available for all exercises: Accessible from the detail sheet for both built-in exercises (saving to exOverrides) and custom exercises (saving to customEx).
  • Complete parameter editing:
    • Name, body part, equipment.
    • Primary muscle groups and additional muscle groups (using the existing multi-select chip interface).
    • Step-by-step instructions editor (st): add, reorder, edit, and delete individual instruction steps.
    • Setup notes and cues (desc).
    • Media reference: optional URL or image picker for custom illustrations/GIFs.
  • Contextual actions:
    • For new custom exercises: "Create exercise".
    • For existing custom exercises: "Save changes" and "Delete exercise".
    • For built-in exercises: "Save changes", "Delete / Hide from library", and "Reset to default" (if modified).

5. UI and Experience Parity

  • Exercise Detail Sheet (ExerciseDetail):
    • Display "Edit" and "Delete" buttons for every exercise.
    • Render "How to" steps (st) for custom exercises if steps have been provided, maintaining visual and informational equivalence with built-in exercises.
    • Render media for custom exercises when a custom image/animation URL is configured, or graceful fallback styling when absent.
  • Library and Picker:
    • Unify list resolution: allExercises(S) returns all non-deleted built-ins (with overrides applied) plus all custom exercises.
    • Consistent search weighting across name, target, equipment, secondary muscles, and custom step text.

6. Persistence, Sync, and Testing

  • State migration & backward compatibility:
    • New DEF fields in useStore.js: exOverrides: {}, deletedEx: [].
    • Existing profiles load seamlessly without breaking historical workouts or custom exercise lists.
    • Changes naturally persist to localStorage (gym_state_v1), sync via pushState / restoredStateFor, and export/import cleanly with backup JSON files.
  • Unit test suite:
    • Add tests in src/lib/exercises.test.js covering:
      • Overlay resolution of exOverrides on built-in exercises.
      • Filtering of deletedEx across catalogue queries and search.
      • Resetting modified built-in exercises back to original catalogue data.
      • Parameter parity between custom exercise payloads and built-in catalogue objects.
      • Preservation of historical workout snapshots when a built-in or custom exercise is edited or deleted.

Dependencies

  • This doesn't require adding a new dependency

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions