Repository navigation
perf: analysis, Bot or Not and puzzles show their layout from the first paint - #327
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…the first paint EmptyBoard draws chessground's board elements and coordinates without a chessground instance, so page shells can show it in the server's HTML. _document applies the visitor's saved board theme to <body> before the first paint (SettingsContext did it only after hydration), so such a board doesn't change colour when the app loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Until its game arrived the page showed nothing for 1 s, then an animated loading board, then faded in. It now renders a shell, on the server too: the panels, an empty board and the guess form where the page draws them, with the phone or desktop layout chosen by CSS at the width isMobile uses. The game then fills it in without fading in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd puzzle load AuthenticatedWrapper takes a fallback to render until the visitor's session has loaded; the puzzles page renders its shell there and while the puzzle loads (between puzzles too) instead of a blank page and the animated loading board. The shell sizes the desktop board as the page first does, in CSS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and game load The analysis page rendered nothing until the visitor's session and the game had loaded, then the animated loading board, then faded in; /analysis showed the loading board while it found a game to open. Both now render a shell, on the server too: the panels, the board with its win-rate and eval bars, the arrow legend, the compact blunder meter and the board controls in their empty starting states, where the page draws them. CSS picks the phone-style or desktop layout at the width the page uses (1121 px), and the desktop board's size, which the page computes in JavaScript, is the same formula in CSS. The game then fills it in without fading in. The left panel's tabs move to src/constants/analysis.ts, shared with the shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…app starts The first analysis shell cost the emulated phone time: on first visits the game went in ~0.5 s later and the main thread did ~0.7 s more. React built the shell as the session loaded (AuthenticatedWrapper's fallback), built a second one inside the page while the game loaded, and both carried framer-motion bars, tabs and board controls in the phone and desktop layouts. PageShell now holds each shell. One rendered while the app hydrates keeps the server's HTML as it is (an empty dangerouslySetInnerHTML, whose children React neither renders nor hydrates); one rendered later, as after a link within the app, renders only the layout for the window. The analysis and puzzles pages keep one shell, beside AuthenticatedWrapper, until the game or puzzle is on screen: the page reports it in a layout effect, so the two are never painted together, and is memoized so that removing the shell doesn't render it again. AuthenticatedWrapper's fallback is gone. With this, the game is fully visible sooner than with round 4's fade-in, on the phone and on desktops (LOADING.md, Round 5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al build Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, and the game appears without fading in After choosing a side, the router first fetched /play/maia's code (on the emulated phone it waited behind the prefetches of the header's links), and the game then faded in over 0.4 s. The dialog now prefetches the game page as it opens, and /play/maia shows the game as soon as it is ready. From choosing a side until the game is fully visible: emulated phone 1.98-2.35 -> 1.42-1.57 s, desktop 1.32-1.70 -> 0.99-1.14 s (4 runs each); ~0.4 s of it is the fade, ~0.2 s on the phone the prefetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…visit's time goes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ashtonanderson
force-pushed
the
claude/perf-round-4
branch
from
November 3, 2026 06:22
4fe9b5b to
eff77bb
Compare
ashtonanderson
force-pushed
the
claude/perf-round-5
branch
from
November 3, 2026 06:22
694083b to
c3d493e
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # LOADING.md
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fifth round: the analysis pages, Bot or Not and the puzzles page show their layout from the first paint, instead of a blank page, an animated loading board and a fade-in. Stacked on #326 (
claude/perf-round-4); this PR's base is that branch. Details and all numbers are inLOADING.md("Round 5").The problem
These pages sent only the header and footer, and drew nothing until:
AuthenticatedWrapper);Then
Loadingshowed nothing for 1 s, an animated loading board, and faded the page in. They pick their phone or desktop layout in JavaScript from the window's width, which the server doesn't know.Changes
An empty board drawn with CSS alone (e4f9e79).
EmptyBoarduses chessground's own elements, styles and coordinates._documentapplies the visitor's saved board theme before the first paint, so the empty board is in their colours.Shells for three pages. Each renders its panels, an empty board and its static parts where the loaded page draws them, on the server too, in their empty starting states:
/analysis/<id>/...and/analysiswhile it finds a game. It draws the panels, the win-rate and eval bars, the arrow legend, the compact blunder meter and the board controls.How the shells match the page. CSS picks the phone or desktop layout at the width the page uses (1025 px; 1121 px on analysis). Board sizes the pages compute in JavaScript are the same formulas in CSS. The game then fills the shell in, without fading in.
The shell costs nothing at start-up (e92f4a5).
PageShellkeeps the server's HTML untouched while the app starts (an emptydangerouslySetInnerHTML, so React neither renders nor hydrates it)./play, after choosing a side (3c00156). The setup dialog now prefetches the game page's code. Before, the code waited behind the prefetches of the header's links. /play/maia then shows the game as soon as it's ready, instead of fading it in over 0.4 s.
Results (round-4 branch → this branch)
Emulated phone (4G, every thread at a quarter speed), medians of 5 loads, builds alternating load by load. ada was heavily loaded by other users during these runs, so times vary.
Desktop, game fully visible:
The game was fully visible sooner in 19 of 20 paired loads. Main-thread work was the same or lower.
Lighthouse (phone settings, local builds, medians of 3):
Analysis's speed index is unchanged (3.16 → 3.14 s). Bot or Not's blocking time fell from 0.95 to 0.67 s.
/play, from choosing a side until the game is fully visible (4 runs each, against the rest of this branch):
The fade is ~0.4 s of the saving; on the phone the prefetch adds ~0.2 s.
play/start_gameitself takes ~0.57 s on the backend.Stockfish
Checked with
scripts/perf/chart-probe.mjson the analysis page, final build, 8 steps per run (2 runs per game on desktop, 3 on the phone):Not settled on the phone.
_document's theme line.Testing
npm test(84, with main's),npx tsc --noEmitandnpm run lintpass, rebased on main after Capture PostHog web vitals again #330 and Track what visitors do, and stop losing events #332.Not done
main).🤖 Generated with Claude Code