# Building the app

The app is produced by a script, not by hand. You write one small file, the script does
the rest: it takes `assets/app-template.html`, fills in your config, library and
programme, derives the 302 entry swap catalogue from `references/exercise-index.md`,
inlines the artwork for this programme, packs the other artwork into a JSON tag, applies
the accent colour, copies the icons and checks that the result parses.

Four steps:

1. Write `build-input.js` in the user's IRON/OS folder (see `SKILL.md` for where that is).
2. Run `node <skill folder>/scripts/build.js build-input.js`.
3. Read the report. Fix anything it warns about and run it again.
4. Tell the user what they have and how to use it.

Never edit the built HTML by hand, and never try to write the catalogue or the artwork
yourself. The artwork alone is 10 MB.

---

## 1. Write `build-input.js`

Start from `scripts/build-input.example.js`, which is a complete three day programme.
The file exports five things:

```js
module.exports = { APP, LIB, PROGRAM, accent: 'brass', out: '<name>-iron-os.html' };
```

`out` is resolved relative to `build-input.js`, so a bare filename lands next to it.
A `.json` file with the same keys also works, but JS is easier to write and can carry
comments.

### APP: the config

```js
const APP = {
  name:        'IRON/OS',
  brandSplit:  ['IRON','OS'],
  defaultMode: 'maintenance',
  phaseStart:  '',
  watchLabel:  'Watch',
  watchPrompt: 'How does it feel today? The next-day response is the signal that matters.',
  showWatchBanner: true,
  excluded:    [],
  nutrition:   null,
  cloudSync:   false,
  configVersion: 1
};
```

Anything you leave out takes the template's default, so you only need to write the
fields you are setting.

- `name` and `brandSplit` come from intake section 9. The header renders
  `brandSplit[0]`, a slash in the accent colour, then `brandSplit[1]`. For a one word
  name use `['NAME','']`. The script also puts `name` in the browser tab and the home
  screen title.
- `defaultMode`: their current phase, one of `deficit`, `maintenance`, `surplus`. For a
  single fixed programme see the note under PROGRAM.
- `phaseStart`: `'YYYY-MM-DD'` if they run phases, otherwise `''`. The week counter hides
  itself when there is no start date.
- `watchLabel`: name the actual area, for example `'Shoulder watch'` or `'Knee watch'`.
  `watchPrompt` is the question the check in asks about it.
- `showWatchBanner`: **`false` if the intake found no injuries.** Do not prompt a healthy
  person about pain every morning.
- `excluded`: index slugs their injuries rule out. They never appear as swap options.
  This is what stops someone casually swapping into the exact movement that hurts them,
  so do not leave it empty when the intake found an injury. The script fails on a slug
  that is not in the index, so check spelling against `exercise-index.md`.
- `nutrition`: **leave `null` unless they asked for the food side.** A training only app
  then shows nothing about food. When they do have a meal plan:

  ```js
  nutrition: { kcal:2000, protein:155, plan:'ben-meal-plan.html', note:'Deficit until 30 Sep' }
  ```

  With `plan` set, the app adds a MEAL tab next to WEEK that opens the plan, and the
  workout page itself shows nothing about food. `plan` is a filename relative to the
  app, so keep both files in the same folder, and set the plan's `app` field to the app's
  filename so its "Back to workouts" bar works. If they wanted targets but no plan file,
  leave `plan` out and the targets show as a small strip instead. `note` is optional.
- `cloudSync`: **stays `false` unless the user asks for it.** When false the app hides
  the cloud sync menu and makes no network calls. See `references/cloud-sync.md`.
- `configVersion`: an integer. **Increase it by one on every rebuild.** The app keeps
  its own saved state, and only a higher `configVersion` makes it take a new
  `defaultMode` or `phaseStart` from the file. Forget this and "I'm starting a diet on
  Monday" rebuilds into an app that still says maintenance.

### accent

One of `'brass'`, `'red'`, `'ice'`, `'green'`, chosen in intake section 9:

| Preset | accent | highlight | on-accent |
|---|---|---|---|
| brass | `#e8a020` | `#ffc457` | `#211500` |
| red | `#e63b2e` | `#ff6a5e` | `#ffffff` |
| ice | `#38bdf8` | `#7dd3fc` | `#06202e` |
| green | `#4ade80` | `#86efac` | `#052e12` |

Or an explicit object, `{ accent:'#..', hi:'#..', on:'#..' }`, with an optional `theme`
for the browser chrome colour (defaults to the accent). The script rewrites the `:root`
variables, the accent tints and the `theme-color` meta for you.

### LIB: the exercise library

One entry per exercise, keyed by the id you use in `PROGRAM`:

```js
'bench-press':{name:'Bench Press', equip:'Barbell', muscle:'Chest',
  sec:['Triceps','Shoulders'], anchor:true, watch:false,
  sets:{deficit:3,maintenance:3,surplus:4}, reps:'4-6', rpe:'RPE 8', rest:180,
  start:0, step:2.5, log:'weight_reps',
  tech:['five short cues','...']},
```

- **The id.** Use the index slug as the id whenever the movement is in the index; the
  artwork and the log type are then found automatically. If you want a different id,
  add `art:'<slug>'` to point it at the artwork. A movement that is not in the index
  gets a sensible id of your own and no `art`; the card renders a neutral placeholder
  and everything else works.
- **`muscle`**: the primary muscle, and it **must be one of the index headings**:
  Chest, Back, Lats, Upper Back, Rear Delts, Shoulders, Biceps, Triceps, Forearms,
  Quads, Hamstrings, Glutes, Calves, Adductors, Hips, Legs, Posterior Chain, Lower Back,
  Core, Mobility. The swap sheet's "same muscle first" list and the weekly volume tab
  both match on this label. "Upper chest" or "Side delts" is not a muscle the app knows;
  write Chest or Shoulders and put the nuance in `name` or `tech`. The script warns on
  any other spelling.
- **`sec`**: the secondary muscles, copied from the "Also works" column of the index.
  **Required.** The weekly volume count scores a set as 1 for the primary muscle and 0.5
  for each secondary; leave `sec` out and the totals silently understate the work. Use
  `[]` only when the index shows none.
- **`anchor`**: true for the three to five lifts whose load is protected. Swapping one
  triggers a warning in the app.
- **`watch`**: true for anything adjacent to a reported injury. The card carries the
  `watchLabel` tag.
- **`sets`**: per mode, from the volume tables in `programming.md`. `0` hides the slot
  in that mode.
- **`reps`**, **`rpe`**, **`rest`** (seconds): from the rep range and rest tables in
  `programming.md`. For timed movements write the target in `reps`, for example `'45 s'`.
- **`start`**: their working weight from intake section 5. Leave `0` where unknown; the
  app shows an empty field they fill on the first session. **`step`**: the increment the
  app suggests when they beat the range.
- **`log`**: what the card records. One of:
  - `weight_reps` (default): weight and reps, for anything loaded.
  - `reps`: reps only, no weight box. Push ups, inverted rows, bodyweight squats.
  - `time`: seconds. Planks, holds, stretches, carries, cardio.
  - `assisted`: assistance weight and reps, for assisted pull ups, chin ups and dips.
    Less assistance is progress.

  When the id (or `art`) is an index slug and you leave `log` out, the script fills it
  from the index's Logs column and says so in the report. Set it yourself for anything
  the index does not have.
- **`tech`**: five cues maximum, short, imperative, specific to the movement.

### PROGRAM: the days

```js
const PROGRAM = [
  {id:1, name:'Upper A', focus:'Push bias · bench anchor',
   slots:['bench-press','incline-dumbbell-press','seated-row','cable-lateral-raise','tricep-pushdown','pec-deck']},
  ...
];
```

One entry per training day, ids `1, 2, 3 ...` in order. `slots` lists LIB ids in
session order: anchor first, then the second compound, then accessories. The app
rotates through however many days you give it.

For a **single fixed programme**, set all three mode values the same on every exercise.
The toggle then changes nothing, which is harmless.

## 2. Choosing the exercises

Read `references/exercise-index.md`. It lists the 302 illustrated exercises grouped by
muscle, with equipment, what the app logs, and the secondary muscles. There is also an
equipment cross reference, which is the fastest way to build for a home gym or for
someone with only a resistance band.

Pick with the intake in front of you:

- **Match the equipment they actually have.** 111 of the exercises are bodyweight, so a
  no equipment programme is entirely possible.
- **Delete anything their injuries exclude,** and put those slugs in `APP.excluded`. Do
  not leave a movement in "just in case".
- **Substitute by pattern**, changing the variable that hurt: the grip, the range, the
  implement, or the angle.

If a programme genuinely needs a movement the index does not have, use it anyway. A
right exercise without a picture beats a wrong one with a picture.

## 3. Run the script

From the user's IRON/OS folder:

```
node <skill folder>/scripts/build.js build-input.js
```

The skill folder is wherever the skill is installed, normally `~/.claude/skills/iron-os`.
The script locates the template, the artwork and the index relative to itself, so the
working directory does not matter.

It writes the app to `out`, copies `icon.png` and `apple-touch-icon.png` next to it, and
prints one line: file size (about 11 MB), exercise count, catalogue count and the number
of warnings, followed by the warnings themselves.

**It stops** when a `PROGRAM` slot is not in `LIB`, an `APP.excluded` slug is not in the
index, the ids are not `1..n`, a `log` value is unknown, the catalogue does not come to
302 entries, or the generated script does not parse. Read the message, fix the input,
run it again.

**It warns**, and still builds, when a LIB entry has no artwork, a `muscle` label is
outside the index vocabulary, `sec` is missing, a LIB entry is unused, or
`configVersion` is not set. Treat a vocabulary or `sec` warning as something to fix
before handing the app over; the other warnings are for you to judge.

Do not touch anything else in the output. The timer, logging, swapping, backup and
check in all work already, and the attribution comment above `ART` and the credit line
in the footer must stay: the artwork is CC BY-SA 4.0 (Everkinetic, expanded by Bryl
Lim) and the script carries that credit through unchanged.

## 4. Rebuilding

When the programme changes, edit `build-input.js`, **increase `configVersion`**, and
run the script again. Their history, swaps and settings live in the browser, not the
file, so they survive as long as the new file replaces the old one at the same address.

---

## How swapping behaves

Worth understanding, because people will use it.

- A slot keeps its **programming** (sets, reps, rest, anchor status) and changes only the
  **movement**. Swapping bench for dumbbell bench keeps 3 x 4-6 at 3 minutes rest.
- The swap sheet lists exercises with the **same primary muscle first**, then the rest,
  minus anything in `APP.excluded`. This is why `muscle` must use the index vocabulary.
- A swapped exercise takes its **muscles and log type from the catalogue**, so the weekly
  volume tab keeps counting correctly.
- **History follows the movement, not the slot.** Swap away and back, and the original's
  numbers are still there. This is deliberate: showing barbell bench weights on a
  dumbbell press would be misleading.
- A swapped exercise has **no technique cues**, since you wrote them for the original.
  The panel says so and invites them to ask their coach. If they settle on the swap, add
  proper cues at the next rebuild.
- Swaps live in the browser, so they survive a rebuild of the app file.

---

## After building

Tell them, in plain terms:

1. Where the file is, and to follow the install guide's phone step to get it onto their
   home screen so it behaves like an app.
2. Tap any exercise picture to see the movement play.
3. Use **Swap** on any exercise to change it for another. There are 302 to choose from.
4. Everything saves on that device. No account, no cloud by default.
5. Use **Export backup** in the menu now and again, and before changing phones.
6. After a session, **Copy session summary** in the menu puts a short text on the
   clipboard. Pasting that into the conversation is how they get coached.

Do not oversell it. It is a good log, a good timer and a clear demonstration of each
movement; the coaching comes from the conversation, not the file.
