mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-08-13 02:35:04 +00:00
* fix(pages): inline critical CSS to remove the initial white flash (#601) Every rule in pages/src/styles/index.css — including `body { background-color: #000000 }` — is imported from JS and injected at runtime by style-loader, and the bundle is split into four deferred chunks. So no stylesheet exists at all until the last chunk executes, and index.html had no inline CSS: the browser painted its default white canvas for several hundred milliseconds before the page turned black. Inline a minimal critical block in the HtmlWebpackPlugin template, ahead of the two render-blocking CDN stylesheet links, with the same #000000/#ffffff as index.css so nothing shifts when the runtime CSS arrives. It applies to `html` as well as `body`, since `html` is what the browser has resolved at first paint. Also add `<meta name="color-scheme" content="dark">`. The Google Fonts and Font Awesome links are render-blocking, so first paint waits on them and the inline style cannot help until they load; the meta is what gives the browser a dark canvas in the meantime. The boot indicator hangs off `#root:empty::after`, so React's first render removes it with no JS teardown, and its animation is disabled under prefers-reduced-motion. One template feeds both HtmlWebpackPlugin instances, so dist/404.html (the SPA deep-link fallback) gets the same treatment. Tested: 7 table-driven vitest assertions over index.html, each verified to fail when the block is removed. lint / typecheck / build / size-limit clean (86.6 kB brotlied against the 150 kB gate), and both dist/index.html and dist/404.html confirmed to carry the block ahead of the first stylesheet and script tag. * fix(pages): load CDN stylesheets async instead of declaring color-scheme Review feedback: `<meta name="color-scheme" content="dark">` reaches further than this fix needs. It switches UA-rendered form controls, scrollbars and <dialog> backdrops to their dark variants for the whole site, permanently — not just during the loading window it was added to cover. Take the root cause instead. The meta was only needed because the Google Fonts and Font Awesome stylesheets are render-blocking, so first paint waited on two CDN round-trips and the inline critical block could not apply until they resolved. Loading them with media="print" + an onload swap drops them out of the critical path entirely, so the inline block paints immediately and the meta has nothing left to do. Nothing above the fold regresses: the font URL already carries display=swap, so text renders in a fallback and swaps, and the only Font Awesome icons are the three in WhySection, well below the initial viewport. <noscript> copies keep both stylesheets applied when scripting is off, since the onload swap cannot run there. Also close the gap flagged on the inline colours: they are duplicated from index.css out of necessity, and nothing enforced that they stay equal. The test now parses the `body` rule out of index.css and compares, so editing one without the other fails rather than shipping a visible shift. Structural assertions now run against the markup with HTML comments stripped. The comment explaining this block names the tags it discusses, and an indexOf('<noscript>') was matching the prose rather than the element. Tested: 12 vitest assertions, each of the three new ones mutation-checked in isolation (make the links blocking / desync index.css / drop the noscript — each fails exactly its own assertion). lint / typecheck / build / size clean, 86.6 kB brotlied against the 150 kB gate. Both dist/index.html and dist/404.html verified to carry the inline block ahead of any stylesheet or script, two async links, and the noscript fallback.
78 lines
3.9 KiB
HTML
78 lines
3.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Open Code Review</title>
|
|
<meta name="description" content="The open source code review agent." />
|
|
|
|
<!-- Open Graph / social preview (iMessage, WeChat, Slack, Facebook, ...) -->
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:site_name" content="Open Code Review" />
|
|
<meta property="og:url" content="https://open-codereview.ai/" />
|
|
<meta property="og:title" content="Open Code Review" />
|
|
<meta property="og:description" content="The open source code review agent." />
|
|
<meta property="og:image" content="https://open-codereview.ai/og-image.png" />
|
|
|
|
<!-- Twitter / X -->
|
|
<meta name="twitter:card" content="summary" />
|
|
<meta name="twitter:title" content="Open Code Review" />
|
|
<meta name="twitter:description" content="The open source code review agent." />
|
|
<meta name="twitter:image" content="https://open-codereview.ai/og-image.png" />
|
|
|
|
<!-- Critical paint state, inlined because every rule in src/styles/index.css
|
|
ships inside the deferred JS chunks (style-loader, no MiniCssExtractPlugin).
|
|
Until the last chunk executes there is no stylesheet at all, so the browser
|
|
paints its default white canvas. Colours must stay in sync with the `body`
|
|
rule in src/styles/index.css so nothing shifts when the runtime CSS lands;
|
|
src/index.html.test.ts asserts they still match. Keep this block ahead of
|
|
the links below — that ordering is what makes it critical CSS. -->
|
|
<style>
|
|
html, body { margin: 0; background-color: #000000; color: #ffffff; }
|
|
|
|
/* Boot indicator. Present only while #root has no children, so React mounting
|
|
removes it with no JS teardown of our own. */
|
|
#root:empty::after {
|
|
content: "";
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 28px;
|
|
height: 28px;
|
|
margin: -14px 0 0 -14px;
|
|
border: 2px solid rgba(255, 255, 255, 0.15);
|
|
border-top-color: rgba(255, 255, 255, 0.6);
|
|
border-radius: 50%;
|
|
animation: ocr-boot-spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes ocr-boot-spin { to { transform: rotate(360deg); } }
|
|
|
|
/* Still shows the ring, just without the motion. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
#root:empty::after { animation: none; }
|
|
}
|
|
</style>
|
|
|
|
<link rel="icon" type="image/svg+xml" href="<%= require('./logo.svg') %>" />
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<!-- Loaded without blocking first paint: `media="print"` does not match the
|
|
screen, so the browser fetches these without holding up rendering, and the
|
|
onload handler switches them on once they arrive. Otherwise the two CDN
|
|
round-trips gate first paint and the inline block above cannot apply until
|
|
they finish. Text swaps in via the font URL's own display=swap, and the
|
|
Font Awesome icons only appear well below the fold (WhySection).
|
|
The <noscript> copies keep both stylesheets render-blocking-but-applied
|
|
when scripting is off, since the onload swap cannot run there. -->
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" media="print" onload="this.media='all'" />
|
|
<noscript>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
|
|
</noscript>
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
</body>
|
|
</html>
|