Long-document editing measurements
This report is for maintainers investigating typing latency and local recovery costs. Use the benchmark to measure both costs before changing recovery behavior.
Run the benchmark
After other build and browser checks finish, run these commands from the repository root:
pnpm build
AGMD_BENCH_OUTPUT=/tmp/agentsmarkdown-editing.json \
node scripts/benchmark-editing.mjsThe benchmark starts an ephemeral local Worker with isolated database, media, and room state. It creates a separate document for each size and surface, then verifies the exact saved text. It does not read or edit production documents.
Each surface receives five warmup inputs and 30 measured inputs. To change the sample count, set AGMD_BENCH_ITERATIONS to an integer from 10 through 200. The benchmark records the Git commit in sourceCommit. For an uncommitted build, set AGMD_BENCH_LABEL to identify the working tree. The runner fingerprints built JavaScript, JSON, and CSS files in builtAppSha256. Build the app again after changing source files.
The performance configuration runs separately from pnpm test:browser. It has no timing pass criteria; environment-dependent timings do not gate normal tests.
Baseline on October 2, 2026
The app measurements use commit 4c77063c4d800401c163ffc84ad53a96f0dbecde. The recovery helper matches that commit. The raw baseline report records every percentile and environment field.
Environment: Apple M3 Max, ARM64, macOS kernel 25.6.0, Chromium 153.0.8010.12, Node.js 26.0.0, and a 1280 × 720 viewport. Each document contains ASCII prose with the caret at its end. Document sizes are decimal bytes: 10 KB, 100 KB, and 1 MB. The benchmark uses one active editor and the local Worker.
Typing latency
The browser measurement starts at beforeinput and ends after two animation-frame callbacks. This includes a roughly 33 ms frame interval in this run. The driver measurement also includes Playwright calls and transport overhead. The percentiles use 30 samples per surface:
| Document size | Surface | Browser p50 | Browser p95 | Driver p50 | Driver p95 |
|---|---|---|---|---|---|
| 10 KB | Live | 32.2 ms | 32.8 ms | 33.4 ms | 34.5 ms |
| 10 KB | Source | 32.3 ms | 32.9 ms | 33.3 ms | 33.9 ms |
| 100 KB | Live | 31.9 ms | 33.2 ms | 33.4 ms | 34.4 ms |
| 100 KB | Source | 31.2 ms | 32.6 ms | 33.3 ms | 39.3 ms |
| 1 MB | Live | 32.2 ms | 33.2 ms | 33.3 ms | 34.1 ms |
| 1 MB | Source | 32.2 ms | 33.7 ms | 33.2 ms | 34.5 ms |
This workload shows no increase in browser input-to-frame latency with document size on this machine. The frame interval limits what these measurements can distinguish. Animation-frame callbacks are a proxy for visual readiness; they do not verify when the display presents pixels.
Local recovery costs
The isolated browser measurement uses the app's recovery functions and 30 samples after five warmup inputs. It creates a Yjs document, adds one character, and measures these phases separately:
- Read the document text.
- Encode the full Yjs state.
- Convert the encoded state to base64.
- Serialize and retain the draft through
createRecoveryController.
The following table records p50 and p95 in milliseconds:
| Document size | Full recovery p50 / p95 | Yjs encoding p50 / p95 | Base64 p50 / p95 | Retention p50 / p95 |
|---|---|---|---|---|
| 10 KB | 0.1 / 0.2 | 0.0 / 0.1 | 0.1 / 0.2 | 0.0 / 0.1 |
| 100 KB | 1.5 / 1.9 | 0.1 / 0.2 | 1.0 / 1.5 | 0.3 / 0.5 |
| 1 MB | 21.5 / 25.3 | 1.1 / 1.4 | 16.8 / 20.0 | 3.7 / 4.0 |
Text serialization is below the browser timer resolution in these isolated samples. During actual editing, localStorage.setItem alone has a 1.7 ms p95 for 1 MB documents on both surfaces. This storage-only measurement excludes Yjs encoding, base64 conversion, and JSON serialization.
Base64 conversion accounts for most measured recovery work at 1 MB. Preserve synchronous recovery until measurements on slower hardware justify a change. If that work causes missed frame intervals, profile base64 conversion first and preserve the exact recovery bytes when optimizing it.
Review workflow build on October 2, 2026
After the verification checks finished, the benchmark ran twice against the final working tree on codex/review-workflows. Both runs use the same environment, workloads, and sample counts as the baseline. The source base remains 4c77063c4d800401c163ffc84ad53a96f0dbecde; the app includes uncommitted review workflow changes. The labels are final-review-workflows-working-tree and final-review-workflows-working-tree-repeat. Both builds have this SHA-256 fingerprint:
942d7fe90a1fd38872c2fabc18df63fe80fde0d13c9887912311037d08ec3021The first report and repeat report preserve every measurement. The baseline predates build fingerprinting and records its source commit without a build hash.
Typing comparison
The following table compares browser input-to-frame timings in milliseconds:
| Document size | Surface | Baseline p95 | First run p50 / p95 | Repeat p50 / p95 |
|---|---|---|---|---|
| 10 KB | Live | 32.8 | 30.4 / 32.5 | 30.3 / 31.8 |
| 10 KB | Source | 32.9 | 31.2 / 32.4 | 31.5 / 32.4 |
| 100 KB | Live | 33.2 | 30.9 / 32.2 | 32.4 / 33.1 |
| 100 KB | Source | 32.6 | 31.0 / 33.0 | 32.4 / 32.6 |
| 1 MB | Live | 33.2 | 25.2 / 32.2 | 32.3 / 33.5 |
| 1 MB | Source | 33.7 | 26.8 / 32.3 | 32.4 / 32.6 |
Browser p95 remains near the animation-frame interval at every size in both runs. The lower p50 values in the first run do not establish an improvement: frame scheduling limits this measurement, and the repeat differs. Driver p95 ranges from 42.9 through 56.5 ms in the first run and from 33.4 through 94.4 ms in the repeat. The repeat's largest driver p95 occurs in the 10 KB Source document while its browser p95 remains 32.4 ms. Driver timing includes automation and transport overhead, so it cannot isolate app processing time.
Recovery comparison
The recovery encoding and controller functions are unchanged from the baseline commit. Full recovery timings in milliseconds are:
| Document size | Baseline p50 / p95 | First run p50 / p95 | Repeat p50 / p95 |
|---|---|---|---|
| 10 KB | 0.1 / 0.2 | 0.2 / 0.4 | 0.2 / 0.3 |
| 100 KB | 1.5 / 1.9 | 1.4 / 2.3 | 1.3 / 2.2 |
| 1 MB | 21.5 / 25.3 | 25.4 / 42.6 | 23.0 / 28.1 |
At 1 MB, the phase timings in milliseconds are:
| Phase | Baseline p50 / p95 | First run p50 / p95 | Repeat p50 / p95 |
|---|---|---|---|
| Full Yjs encoding | 1.1 / 1.4 | 1.4 / 3.5 | 1.2 / 1.4 |
| Base64 conversion | 16.8 / 20.0 | 19.0 / 21.2 | 17.9 / 22.8 |
| Retention | 3.7 / 4.0 | 4.5 / 11.0 | 4.0 / 4.2 |
Actual editor storage-write p95 at 1 MB is 2.6 ms in Live and 2.2 ms in Source in the first run, then 1.5 ms on both surfaces in the repeat. The higher first-run recovery tail is not reproduced at the same magnitude with the same build. These runs show variability and do not identify its cause or establish a regression from the review workflow changes. Base64 conversion remains the largest measured recovery phase. Keep the current recovery behavior, and use this benchmark with slower hardware and fragmented Yjs histories before choosing an optimization.
Limits
These results do not represent production networking, mobile hardware, concurrent edits, large tables, component rendering, or documents with fragmented Yjs histories. The isolated recovery measurement separates recovery cost from the app's full editing path; it does not attribute every editing delay to recovery. Browser timer precision and animation-frame scheduling affect small timings. Use the same browser, hardware, source revision, workload, and sample count when comparing runs.