# NEXT Athlete OS RC112 · Pass chrome reposition QA

## Scope

RC112 moves existing active-pass chrome without changing the sensitive player lifecycle.

Requested layout change:

- Progress bar moves from the sticky header to below the exercise media + exercise text cards.
- Topbar actions move to the upper right in this order:
  1. Back / `#prevBtn` when available
  2. Next / `#nextBtn`
  3. Menu / `#menuToggleBtn` furthest right

## Guardrails read first

- `docs/ARCHITECTURE_DECISIONS.md`
- `docs/DO_NOT_TOUCH_WITHOUT_DESIGN.md`
- `docs/RC110_PASS_PAGE_STANDARDIZATION_QA.md`
- `docs/RC111_PASS_PLAYER_STANDARDIZATION_QA.md`

## What changed

### `preview.html`

Existing pass controls were moved, not replaced:

- `#prevBtn`
- `#nextBtn`
- `#menuToggleBtn`
- `#settingsMenu`
- `#exerciseMeta`
- `#progressBar`

The pass controls are now inside the existing sticky pass topbar:

- `.next-pass-topbar`
- `.next-pass-controls`
- new alias `.pass-top-controls`

The progress block remains the same functional DOM elements, but is relocated below `.next-pass-layout`:

- `.next-pass-progress`
- new alias `.next-pass-progress-below`
- `#exerciseMeta`
- `#progressBar`

### `assets/css/next-system.css`

Added an RC112 override block only for chrome placement:

- `.next-pass-action-row`
- `.pass-top-controls`
- `.next-pass-progress-below`
- topbar button sizing/alignment
- menu dropdown position from the new top-right anchor

### Existing player lifecycle preserved

No lifecycle/player rewrite was made. These functions remain owned by `app.js` and were not duplicated:

- `startWorkout()`
- `renderStep()`
- `renderVideo()`
- `renderRest()`
- timer/autoplay/preload/wake-lock/iOS fallback flow

`syncPassControlsRow()` still controls whether back is visible. RC112 only changes where the existing controls live and how they are styled.

## What did NOT change

- No new player renderer.
- No new video source logic.
- No changed YouTube/OTA/server priority.
- No image generation.
- No foreground/background cutout image model.
- No new asset model.
- No server deploy test from this sandbox.

## Real-DOM QA method

The QA uses the RC108+ method:

- Load actual `preview.html`.
- Inline actual CSS and JS.
- Run the real app DOM with Playwright/Chromium using `page.set_content()`.
- Use the existing player lifecycle functions:
  - `startWorkout()`
  - `primeWorkoutStart()`
  - `renderRest()`
- Mock only local fetch endpoints:
  - `ota_youtube_proxy.php`
  - `video_backup_api.php`
- Abort external network/iframe loading in the sandbox.

External YouTube iframe rendering is still blocked in the sandbox, so the YouTube area appears grey. The DOM state and iframe path are still verified.

## Screenshots produced

Start state:

- `qa_screenshots/rc112_pass_player_start_mobile_390x844.png`
- `qa_screenshots/rc112_pass_player_start_ipad_820x1180.png`
- `qa_screenshots/rc112_pass_player_start_desktop_1440x1024.png`

YouTube state:

- `qa_screenshots/rc112_pass_player_youtube_mobile_390x844.png`
- `qa_screenshots/rc112_pass_player_youtube_ipad_820x1180.png`
- `qa_screenshots/rc112_pass_player_youtube_desktop_1440x1024.png`

Rest state, including visible Back button:

- `qa_screenshots/rc112_pass_player_rest_mobile_390x844.png`
- `qa_screenshots/rc112_pass_player_rest_ipad_820x1180.png`
- `qa_screenshots/rc112_pass_player_rest_desktop_1440x1024.png`

Metrics:

- `qa_screenshots/rc112_pass_player_dom_metrics.json`

## Metrics verified

For mobile, iPad and desktop:

- `.next-pass-controls` is inside `.next-pass-topbar`.
- `.next-pass-progress-below` is not inside `.next-pass-topbar`.
- Control row bottom is within the topbar bottom.
- Progress top is below the lower edge of both media card and info card.
- Menu button is to the right of Next.
- Back button is to the left of Next when visible.
- No broken local images.
- Existing RC111 pass-player helper state remains loaded.

## Commands run and passed

```bash
node tests/rc112_pass_chrome_reposition_smoke.js
node tests/rc111_pass_player_standardization_smoke.js
node tests/rc110_pass_page_standardization_smoke.js
node tests/rc109_asset_path_cleanup_smoke.js
node tests/rc107_asset_and_library_smoke.js
node tests/rc109_library_compactness_static_smoke.js
node tests/rc104_direct_composite_smoke.js
node tests/rc106_smoke.js assets/js/core/exercise-illustrations.js "$PWD"
find assets/js -name '*.js' -print0 | xargs -0 -n1 node --check
php -l ota_youtube_proxy.php
php -l video_backup_api.php
python3 tests/rc112_pass_chrome_real_dom_qa.py
python3 - <<'PY'
import json
p='qa_screenshots/rc112_pass_player_dom_metrics.json'
j=json.load(open(p))
assert len(j['start'])==3 and len(j['youtube'])==3 and len(j['rest'])==3
for stage in ['start','youtube','rest']:
  for m in j[stage]:
    assert m['helperLoaded']
    assert m['controlsInsideTopbar']
    assert not m['progressInsideTopbar']
    assert m['controlsRect']['bottom'] <= m['topbarRect']['bottom'] + 1
    assert m['progressRect']['top'] >= max(m['mediaRect']['bottom'],m['infoRect']['bottom']) - 1
    assert not m['brokenImages']
    prev=m['prevRect']; nxt=m['nextRect']; menu=m['menuRect']
    if prev['width']>1:
      assert prev['right'] <= nxt['left'] + 1
    assert nxt['right'] <= menu['left'] + 1
print('RC112 real DOM metrics OK')
PY
unzip -t next_v96_rc112_pass_chrome_reposition.zip
```

## Deploy caveat

The real deployed host was not tested from this sandbox. RC112 is verified with real preview DOM/CSS/JS using the local inlined Playwright method. Final validation should still be done on the deployed `preview.html` host, especially for real YouTube iframe loading and OTA/proxy behaviour.
