# Admin Snappy performance baseline

## Task 1 baseline capture

Run Chromium remotely against existing authenticated development data. The driver does not create records.

## Measurement site

Captures MUST run against a site serving the exact tree under test. The dev site on port 8080 serves
`wordpress/wp-content` and is NOT that tree. Container `devzone-wordpress-snappy` publishes port 8081 and
bind-mounts the tree under test as `wp-content`; a PHP `auto_prepend_file` drop-in overrides `WP_HOME`/
`WP_SITEURL` to port 8081, so the shared database's 8080 site URL does not redirect the browser away.
Set `IPZ_TREE_UNDER_TEST` to that tree before running any command below. The E2E workspace pins `@playwright/test` to 1.62.0; resolve its package metadata from that workspace so the command cannot drift to a global or pnpm-store path:

```bash
export IPZ_PLAYWRIGHT_PACKAGE_JSON="$(node -e "console.log(require.resolve('@playwright/test/package.json', { paths: [process.cwd() + '/tests/e2e'] }))")"
```

```bash
cd "$IPZ_TREE_UNDER_TEST"
~/.claude/bin/e2e-remote \
  --env WP_BASE_URL=http://100.126.128.50:8081 \
  --env IPZ_PERF_SAMPLES=3 \
  --mkdir admin/performance \
  -- env IPZ_PLAYWRIGHT_PACKAGE_JSON=${IPZ_PLAYWRIGHT_PACKAGE_JSON} node admin/scripts/measure-admin-performance.mjs
```

The baseline capture writes `admin/performance/admin-snappy-baseline.json` with three cold and three warm samples for every required route. It replaces only the Task 1 baseline; it does not create comparison receipts.

`IPZ_PERF_SAMPLES` may increase the sample count. Do not turn a nonzero remote status into success. A nonzero status is a failed capture; never waive it.

## Comparison capture

Beachhead capture measures Languages and Content Translation, then appends one receipt to `comparisonReceipts` without changing the immutable Task 1 routes or budgets.

```bash
cd "$IPZ_TREE_UNDER_TEST"
~/.claude/bin/e2e-remote \
  --env WP_BASE_URL=http://100.126.128.50:8081 \
  --env IPZ_PERF_SAMPLES=3 \
  --mkdir admin/performance \
  -- env IPZ_PLAYWRIGHT_PACKAGE_JSON=${IPZ_PLAYWRIGHT_PACKAGE_JSON} node admin/scripts/measure-admin-performance.mjs --capture-beachhead admin/performance/admin-snappy-baseline.json admin/performance/admin-snappy-route-contract.json
```

Final capture measures all required routes and appends a final receipt in the same way.

```bash
cd "$IPZ_TREE_UNDER_TEST"
~/.claude/bin/e2e-remote \
  --env WP_BASE_URL=http://100.126.128.50:8081 \
  --env IPZ_PERF_SAMPLES=3 \
  --mkdir admin/performance \
  -- env IPZ_PLAYWRIGHT_PACKAGE_JSON=${IPZ_PLAYWRIGHT_PACKAGE_JSON} node admin/scripts/measure-admin-performance.mjs --capture-final admin/performance/admin-snappy-baseline.json admin/performance/admin-snappy-route-contract.json
```

## Browser projects on the remote runners

Run E2E gates with `--project=chromium --project=firefox`. The `webkit` project cannot launch on the
debian1/debian2 runners: the pinned Playwright 1.62.0 webkit build links `libicudata.so.76`, `libicui18n.so.76`,
`libicuuc.so.76`, `libxml2.so.2`, and `libevent-2.1.so.7`, and Debian forky offers no `libicu76` or
`libxml2` candidate (it ships `libicuuc.so.78` and `libxml2.so.16`). `playwright install-deps webkit`
fails at apt. Leave `webkit` in `playwright.config.js` so it still runs where those sonames exist.

Comparison captures require a valid Task 1 baseline, use the canonical `#/translations/posts` route, and fail if its `/translations/content?post_type=post` request is absent or the page lands on another hash. They never use the legacy redirect route or string-translation selectors.

## Validation

Validate checked-in Task 1 evidence without launching a browser.

```bash
cd "$IPZ_TREE_UNDER_TEST"
node admin/scripts/measure-admin-performance.mjs --validate admin/performance/admin-snappy-baseline.json admin/performance/admin-snappy-route-contract.json
```

Validate appended comparison receipts against the immutable Task 1 budgets and exact route sets.

```bash
node admin/scripts/measure-admin-performance.mjs --validate-beachhead admin/performance/admin-snappy-baseline.json admin/performance/admin-snappy-route-contract.json
node admin/scripts/measure-admin-performance.mjs --validate-final admin/performance/admin-snappy-baseline.json admin/performance/admin-snappy-route-contract.json
```

Both comparison validators fail closed when a receipt is missing, stale relative to `capturedAt`, incomplete, zero-valued, missing an expected API endpoint, or outside its Task 1 budgets. Persisted routes must also meet their warm first-content and improvement budgets. Live-only routes (`persist: false`) still run both phases and must issue their expected requests in each phase, but are exempt from cache-derived warm-improvement budgets. Normal `--validate` does not require comparison receipts, so the unchanged baseline can be checked before beachhead and final work exists; the two comparison commands must remain failing until their respective remote captures append fresh receipts.

## What is recorded

Each route has separate cold and warm samples with request count, per-endpoint TTFB and payload bytes, route-to-skeleton, route-to-first-content, route-to-settled, CDP scripting/layout/style time, long-task count/duration, and candidate localStorage JSON parse/write time. `routeToSkeletonMs: null` means the current route rendered no shaped skeleton; it is baseline evidence, not a zero-duration skeleton.

Endpoint timing is kept separate from synchronous DOM/chart work. `synchronousDomAndChartMs` is CDP scripting plus layout and style recalculation, while endpoint TTFB measures browser-observed request-to-response time.

Validation rejects empty route sets, missing required routes, duplicate/unknown routes, zero request counts, empty endpoint metrics, nonpositive timing/payload observations, malformed contract rows, missing expected endpoints, and observed API endpoints without a contract row. The Translate Posts receipt must end at `#/translations/posts`, observe `/translations/content`, and carry normalized query `post_type=post`, distinguishing it from the Pages tab.

## Contract and budgets

`admin-snappy-route-contract.json` is the inventory for observed page owners, store keys, GET routes, normalized query fields, browser/server cache policy, invalidation mutations, tags, and live fields. The `posts:page` belongs to the canonical Translate Posts route (`#/translations/posts`) and its measured `/translations/content?post_type=post` API inventory. The registered `/posts` collection is covered by the same runtime policy descriptor but is outside the immutable historical measurement subset.

For every route, acceptance derives from its immutable Task 1 cold baseline.

- cold settled time and synchronous DOM/chart time may not regress beyond `COLD_REGRESSION_TOLERANCE` (5%), the measurement-noise margin for cold metrics this feature does not target;
- persisted routes must improve warm first content by at least 50% (`warmFirstContentMs`); `warmFirstContentTargetMs` records the aspirational 200 ms target where the environment permits;
- live-only routes (`persist: false`) must fetch on both passes and never use cache-derived warm budgets;
- comparison receipts must carry the original baseline timestamp and budget snapshot.


## Frozen measurement crosswalk

The seven rows in `admin-snappy-route-contract.json` are the immutable measured Task 1 subset, not a second runtime registry. `AdminDataPolicy::ROUTES` is the complete current 15-descriptor policy. Validation maps every measured row to exactly one policy descriptor by key, endpoint, normalized query fields, and persistence safety; the remaining eight descriptors are policy-only coverage and must not be added to or rewrite the historical receipt. The latest final receipt remains the seven-route capture at `2026-08-09T19:23:36.336Z`.

`admin/src/utils/Cache.js` was deleted after source, test, Webpack-entry, dynamic-import, and built-runtime searches found zero references. Team, Workflow, and Assignments are routed; their complete runtime-policy coverage is verified separately from this historical measurement subset.
