Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions LOADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,100 @@ Not done: Hand and Brain (/play/hb) still waits for its game as before, and
the dialog that buttons elsewhere open still loads its code on the first
click.

### Round 8: the analysis chart after the page

Measured against round 7 at 9781b24 (before the rebase on #330 and #332): local
production builds, the test PostHog key, the phone being Chrome's own 4x CPU
throttling with 4G, 5 runs each, the builds alternating, ada at load ~40–50.

recharts and its d3 helpers (~320 KB of JavaScript, uncompressed) were the
largest part of the analysis page's start-up code, for one chart: Moves by
Rating, in the sidebar on desktops and below the board on phones. The chart
(`MovesByRatingArea`) now loads with `next/dynamic` once `MovesByRating`
renders; the panel and its heading draw at once, and a placeholder of the
chart's height holds its place. Puzzles, drills, broadcasts and the home
page's sections use the same component.

| analysis, round 7 → round 8 | phone (first visit) | desktop |
| ------------------------------------ | ------------------- | ------------- |
| JavaScript at start-up (build, gzip) | 417 → 311 kB | 417 → 311 kB |
| game fully visible | 2.65 → 2.56 s | 0.65 → 0.59 s |
| Moves by Rating chart drawn | 2.98 → 3.13 s | 0.77 → 0.79 s |
| main-thread work, first 10 s | 6.51 → 6.66 s | |

The game was fully visible sooner in 4 of 5 phone pairs and 5 of 5 desktop
ones. The saving is mostly the download: recharts' code ran only when the
chart was drawn, so its script time doesn't change, and on this 4G (9 Mbps)
the 106 kB take ~0.1 s; on slower connections they take longer. The chart's
panel and chart are at the same place and size as before (a Pixel 7 and 1440
× 900).

Lighthouse (phone settings, local builds, 3 runs each): analysis 57–66 →
59–67 (medians 66 → 64), blocking time 2.72 → 2.31 s, speed index 3.02 →
3.07 s, main content (the board, in both) 2.45–3.91 → 2.60–3.44 s; /puzzles
42 → 42.

**Stockfish** (`scripts/perf/chart-probe.mjs`, the analysis page, 8 steps
per run, each pair run in both orders): on a desktop depth 12 came 0.03 s
sooner after a step (± 0.02 s; sooner on 19 of 32 steps) and the depth
reached before the next step went from 17.03 to 16.94; on the emulated phone
0.23 s sooner (± 0.24 s; sooner on 38 of 63 steps), 63 of 64 steps reached
it against 64, depth before the next step 14.69 → 14.61. As in round 6, the
build run first in a pair came out slower on the phone (with round 7 first
the branch was 0.93 s sooner, with the branch first 0.48 s later), which is
why the orders are balanced.

### Rounds 4–8 together

Main where the rounds start (ef23af3, after #324) against round 8's tip
before the rebase on #330 and #332 (56a1767): local production builds
behind `scripts/perf/stub-proxy.mjs`, a test PostHog key, 5 loads each, the
builds alternating, ada at load 50–75. The phone is Chrome's own 4x CPU
throttling with 4G; returning visits reuse each build's own warmed profile.
/play opens the page, chooses "Play as white" as soon as the dialog takes
taps, and waits for the game.

| main → round 8 | phone (first visit) | desktop | phone (returning) |
| ---------------------------------------------- | ------------------- | ------------- | ----------------- |
| /play: setup dialog on screen | 1.94 → 0.49 s | 0.37 → 0.16 s | 0.98 → 0.38 s |
| /play: board with its pieces after choosing | 1.89 → 0.59 s | 1.23 → 0.30 s | 1.54 → 0.94 s |
| /play: game takes moves, from the request | 3.29 → 2.44 s | 1.26 → 1.11 s | 2.01 → 2.07 s |
| analysis game fully visible | 3.29 → 2.27 s | 0.93 → 0.54 s | 2.01 → 1.52 s |
| /analysis: board fully visible | 5.90 → 5.24 s | 1.38 → 1.14 s | |
| /puzzles: puzzle fully visible | 3.99 → 3.39 s | 2.42 → 2.04 s | |
| Bot or Not: game fully visible | 3.13 → 2.59 s | 1.78 → 1.44 s | |
| home: main-thread work in the first 10 s (5 s) | 7.20 → 4.95 s | 1.99 → 1.41 s | 5.85 → 3.87 s |
| home: JavaScript transferred | 535 → 384 KB | 535 → 489 KB | |

Each "fully visible" and /play row except returning /play's last came out
sooner in 5 of 5 pairs. JavaScript at start-up (build output, gzip): shared
by all pages 308 → 181 kB, analysis game 468 → 311 kB, /play 283 → 169 kB,
home 295 → 183 kB, /puzzles 453 → 293 kB, Bot or Not 323 → 208 kB.

What is later:

- Returning /play, choosing at once: the game takes moves 0.06 s later (4
of 5 pairs), while the analytics start up (round 6).
- First paint on the desktop app pages, 0.03–0.06 s later (e.g. analysis
game 0.22 → 0.27 s), and on the phone for /puzzles and Bot or Not (0.51 →
0.56 s, 0.50 → 0.56 s). It now draws the page's layout.
- Largest contentful paint where the layout now comes first and the largest
element later: Bot or Not on a desktop 0.20 → 1.49 s, /puzzles 1.72 →
2.17 s (desktop) and 3.29 → 3.46 s (phone), the analysis game on the phone
0.53 → 0.63 s (returning 0.52 → 0.68 s). The game or puzzle is on screen
sooner in each, but PostHog's web vitals, on since #330, report LCP.

**Stockfish** (`scripts/perf/chart-probe.mjs`, the analysis page, 8 steps
per run, each pair run in both orders): every step reached depth 12 on both
builds. With the tip run first, after the page-load runs above had finished,
depth 12 came at the same time on a desktop (0.01 s later, ± 0.01 s) and
0.14 s sooner on the phone (± 0.08 s; sooner on 21 of 32 steps). With main
run first, during those runs, main was 1–2 s slower on several desktop steps
and the tip came out 0.85 s sooner (± 0.20 s); that half isn't counted. Depth 18,
reached on fewer than half the steps, is too noisy to separate (phone 0.00 s
± 0.05 s; desktop, tip first, 0.73 s later ± 0.62 s). Depth reached before
the next step: desktop 16.97 → 16.91, phone 16.48 → 16.50.

## What changed

| commit | change | effect |
Expand Down Expand Up @@ -1086,6 +1180,7 @@ click.
| b04ed0c | /play's setup dialog is in the server's HTML, without its entrance animation; it ignores taps until the app has started | Phone: dialog on screen 1.64 → 0.58 s; desktop 0.45 → 0.30 s |
| 30d4a55 | /play/maia shows the game, its board at the starting position, while `start_game` answers; moves and their logging wait for the game's id | After choosing, the board with its pieces: phone 1.99 → 1.29 s, desktop 1.02 → 0.46 s |
| f3d1b7a | The setup dialog sends `start_game` as the visitor chooses; /play/maia takes that request | After choosing, the board takes moves: phone 1.60 → 1.29 s, desktop 0.90 → 0.83 s (with 30d4a55) |
| c16c4fa | The Moves by Rating chart (recharts, ~320 KB) loads with `next/dynamic` once its panel renders | Analysis start-up JavaScript 417 → 311 kB; game fully visible: phone 2.65 → 2.56 s, desktop 0.65 → 0.59 s; chart 0.02–0.15 s later |

## Things you will notice

Expand Down Expand Up @@ -1128,6 +1223,8 @@ click.
- **/play shows its setup dialog at once**, without its slide-in; it takes
taps once the app has started. After choosing a side, the game appears at
once and takes moves once the backend has created it.
- **The Moves by Rating chart fills in a moment after its panel** (0.02–0.15
s later than before).
- **PostHog and Google Analytics start a second or more later**: PostHog's
page view is stamped when it starts, and visitors who leave within the
first seconds may not be counted.
Expand Down
12 changes: 12 additions & 0 deletions src/components/Analysis/MovesByRating.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ jest.mock('recharts', () => {
}
})

// MovesByRating loads the chart (MovesByRatingArea) with next/dynamic; here
// it renders at once.
jest.mock(
'next/dynamic',
() => () =>
function MovesByRatingArea(props: Record<string, unknown>) {
const { createElement } = jest.requireActual('react')
const chart = jest.requireActual('./MovesByRatingArea')
return createElement(chart.MovesByRatingArea, props)
},
)

const moves = (e2e4: number) => [
{ rating: 600, e2e4, d2d4: 20 },
{ rating: 700, e2e4: e2e4 + 1, d2d4: 19 },
Expand Down
Loading
Loading