{
  "schema": "leumas.docs.page/1",
  "id": "skill:leumas-studio#domains/gym",
  "slug": "skills/leumas-studio/domains/gym",
  "kind": "tools",
  "bucket": "skill",
  "title": "Gym — what you can do",
  "name": "Gym",
  "eyebrow": "what you can do",
  "chip": null,
  "summary": "<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run pnpm skills:sync. -->",
  "keywords": [
    "leumas-studio",
    "fetch remember",
    "skill-sync",
    "pnpm",
    "skills",
    "edit",
    "hand",
    "generated"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run `pnpm skills:sync`. -->\n\n# Gym — what you can do\n\n`/admin/d/gym` · pillar `apps`\n\nRun a self-hosted gym — 1,324 exercises, weekly plans, guided sessions, 4 progression systems, AI coach.\n\n## Fetch this, never remember it\n\nThese change per install and per release. Call for them - a list written here would be a future lie.\n\n| What | Call | Why |\n|---|---|---|\n| this gym's exercise library, including its own custom exercises | `GET /api/gym/exercises` | the 1,324 shipped rows are the same everywhere, but a gym's custom exercises are not — and the facets tell you which filter combinations would return anything at all |\n| whether a session is already running | `GET /api/gym/overview` | `running` is what makes Today resume rather than restart; starting a second session without `force` silently returns the first |\n| what to lift next for one exercise, and why | `GET /api/gym/next/:exerciseId` | the prescription is DERIVED from the log every time it is asked for — never stored — so it is always current and there is no counter to go stale |\n| whether this install can show exercise animations at all | `GET /api/gym/settings` | `mediaConfigured` is false by default and that is not a fault: the media belongs to Gym visual and Leumas ships none of it |\n\n## How to actually do things here\n\nWritten by hand, because the order and the traps are the part no file can derive.\n\n### Give somebody a program to start on\n\n1. `GET /api/gym/starters` for the three shipped programs and what each would cost this catalog.\n2. `POST /api/gym/routines` with `starterId` to build it, or `POST /api/gym/ai/program` to have one written.\n3. Check `unfilled` (starter) or `warnings` (AI) before telling anyone it is ready.\n\n**Trap:** A non-empty `unfilled` means the catalog could not supply those slots — the routine EXISTS and is missing exercises. Say so. The AI lane refuses the whole document rather than dropping an exercise it invented, so a 422 there means nothing was saved, not that something partial was.\n\n### Run a workout\n\n1. `POST /api/gym/sessions` with `{ routineId, day }` — it returns the sets already prescribed and stamped.\n2. `PATCH /api/gym/sessions/:id/sets/:setId` with `{ reps, weight, completed: true }` per set as it happens.\n3. `POST /api/gym/sessions/:id/finish` to close it and get the records it set.\n\n**Trap:** An unticked set is a MISS, not an absence — that is what stops a session that fell apart from advancing the load next time. Finishing DELETES the unticked rows, so tick as you go rather than at the end. Sending `completed: false` un-ticks and changes the judgement.\n\n### Work out why a weight went down\n\n1. `GET /api/gym/next/:exerciseId` and read `prescription.reason.text` — it names the rule and the count.\n2. `GET /api/gym/stats/exercise/:exerciseId` for the curve and the records behind it.\n\n**Trap:** A deload is the policy working, not a bug. Greyskull resets after ONE failure by design where linear gives three attempts. A field the policy has no opinion on comes back `undefined`, not 0 — reading it as 0 prescribes an empty bar.\n\n### Bring history over from another tracker\n\n1. `POST /api/gym/import` with the file text and NO `commit` — this previews and returns `unresolved`.\n2. Map each unresolved name to an exerciseId and re-post with `mapping` and `commit: true`.\n\n**Trap:** A commit with unresolved names is REFUSED (409) unless you pass `ignoreUnresolved`, which DROPS those rows. Inventing an exercise is the one irreversible mistake an importer can make, so it will not guess. Weights convert per row, not per file.\n\n### Show exercise animations\n\n1. Obtain a licence from Gym visual, or host a copy you are entitled to.\n2. `PATCH /api/gym/settings` with `mediaBase` set to that absolute https origin.\n\n**Trap:** Leumas ships NO exercise media — it belongs to Gym visual and is excluded from the dataset's MIT grant. There is deliberately no default base, a relative path is refused, and the attribution must render wherever the media does. An empty `mediaBase` is the correct state, not a broken install.\n\n## Tabs\n\nEvery one is a real URL. A tab with its own section below carries a real job.\n\n| Tab | Path | Group | What it is for |\n|---|---|---|---|\n| `desk` | `/admin/gym` | - | - |\n| `today` | `/admin/gym/today` | - | - |\n| `plan` | `/admin/gym/plan` | - | - |\n| `classes` | `/admin/gym/classes` | - | - |\n| `library` | `/admin/gym/library` | - | - |\n| `history` | `/admin/gym/history` | - | - |\n| `stats` | `/admin/gym/stats` | - | - |\n| `members` | `/admin/gym/members` | - | - |\n| `settings` | `/admin/gym/settings` | - | - |\n\n## API\n\nMounted prefixes that serve this domain: `/api/gym`\n\nThe full route table is `leumas-capabilities/reference/api-routes.md`.\n\n",
  "source": {
    "path": ".claude/skills/leumas-studio/reference/domains/gym.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 4812,
    "hash": "93530b3378ba699483370a8bf4efba4b6b19ce45"
  },
  "urls": {
    "html": "/p/skills/leumas-studio/domains/gym",
    "json": "/docs/skills/leumas-studio/domains/gym.json",
    "md": "/docs/skills/leumas-studio/domains/gym.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  }
}
