What problem does this solve?
Currently, openGym enforces a strict architectural dichotomy between built-in (default) exercises and custom (user-created) exercises:
-
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.
-
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
What problem does this solve?
Currently, openGym enforces a strict architectural dichotomy between built-in (default) exercises and custom (user-created) exercises:
Default exercises are immutable and permanent:
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.Custom exercises are second-class citizens with missing parameter parity:
S.customExlack several parameters that built-in exercises possess:st): Built-in exercises have an array of step strings displayed cleanly under the "How to" section. Custom exercises only offer an unstructured single-textdescfield.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.tg(target),mg(muscle group),sm(secondaries), as well as overlaidprimariesandsecondaries. In custom exercises, these are synthesized ad-hoc (primaries,secondaries,muscleGroups), leading to schema inconsistencies.{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
EXDBas the base layer and store only the user's modifications in user state:exOverrides: {}to stateS(DEFinstore/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..."] } } }CATALOGUEandEXIDXinsrc/lib/exercises.js, applyS.exOverrides[id]over the base catalog entry: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:
deletedEx: [](array of hidden/deleted built-in exercise IDs) to stateS.S.deletedEx.deletedExinallExercises(S),EXIDX, Library search, and exercise selection pickers.deleteCustomExlogic): clean up future routine references while preserving historical logs inworkoutsusingexerciseMuscleSnapshot.S.customEx.4. Upgrade the Exercise Editor (
CustomExForm→ UnifiedExerciseEditorSheet)Consolidate exercise creation and editing into a unified form component:
exOverrides) and custom exercises (saving tocustomEx).st): add, reorder, edit, and delete individual instruction steps.desc).5. UI and Experience Parity
ExerciseDetail):st) for custom exercises if steps have been provided, maintaining visual and informational equivalence with built-in exercises.allExercises(S)returns all non-deleted built-ins (with overrides applied) plus all custom exercises.6. Persistence, Sync, and Testing
DEFfields inuseStore.js:exOverrides: {},deletedEx: [].localStorage(gym_state_v1), sync viapushState/restoredStateFor, and export/import cleanly with backup JSON files.src/lib/exercises.test.jscovering:exOverrideson built-in exercises.deletedExacross catalogue queries and search.Dependencies