Commit be7d35a

Browse files
authored
perf(web): stop preview loading rerenders (#7561)
1 parent 549201f commit be7d35a

6 files changed

Lines changed: 135 additions & 63 deletions

File tree

‎apps/web/src/components/preview/PreviewChromeRow.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ describe("PreviewChromeRow", () => {
99
<PreviewChromeRow
1010
url="https://example.com/dashboard?mode=edit&tab=1#notes"
1111
loading={false}
12-
loadProgress={0}
1312
canGoBack={false}
1413
canGoForward={false}
1514
refreshDisabled={false}

‎apps/web/src/components/preview/PreviewChromeRow.tsx‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { cn } from "~/lib/utils";
2424
interfaceProps{
2525
url: string;
2626
loading: boolean;
27-
loadProgress: number;
2827
canGoBack: boolean;
2928
canGoForward: boolean;
3029
refreshDisabled: boolean;
@@ -65,7 +64,6 @@ const NOOP = () => {};
6564
exportfunctionPreviewChromeRow({
6665
url,
6766
loading,
68-
loadProgress,
6967
canGoBack,
7068
canGoForward,
7169
refreshDisabled,
@@ -308,16 +306,12 @@ export function PreviewChromeRow({
308306
) : null}
309307
{trailingActions}
310308
</form>
311-
{loadProgress>0 ? (
312-
<div
313-
aria-hidden
314-
className="pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 rounded-r-full bg-primary transition-all duration-150 ease-out"
315-
style={{
316-
width: `${loadProgress}%`,
317-
boxShadow: "0 0 6px 1px var(--color-ring)",
318-
}}
319-
/>
320-
) : null}
309+
<div
310+
aria-hidden
311+
data-loading={loading}
312+
className="preview-loading-progress pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 w-full origin-left rounded-r-full bg-primary"
313+
style={{boxShadow: "0 0 6px 1px var(--color-ring)"}}
314+
/>
321315
</div>
322316
);
323317
}

‎apps/web/src/components/preview/PreviewView.test.tsx‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FILL_PREVIEW_VIEWPORT,
66
ThreadId,
77
}from"@t3tools/contracts";
8+
import{act,Profiler}from"react";
89
import{renderToStaticMarkup}from"react-dom/server";
910
import{beforeEach,describe,expect,it,vi}from"vite-plus/test";
1011

@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
3031
toggleAnnotation: nullas(()=>void)|null,
3132
pictureInPicture: false,
3233
showEmptyState: false,
34+
loading: false,
3335
recordVisitForThread: vi.fn(),
3436
}));
3537

@@ -103,7 +105,7 @@ vi.mock("~/previewStateStore", () => ({
103105
hasWebContents: true,
104106
canGoBack: false,
105107
canGoForward: false,
106-
loading: false,
108+
loading: mocks.loading,
107109
zoomFactor: 1,
108110
pictureInPicture: mocks.pictureInPicture,
109111
colorScheme: "system",
@@ -244,7 +246,6 @@ vi.mock("./PreviewUnreachable", () => ({ PreviewUnreachable: () => null }));
244246
vi.mock("./ZoomIndicator",()=>({ZoomIndicator: ()=>null}));
245247
vi.mock("./AgentBrowserCursor",()=>({AgentBrowserCursor: ()=>null}));
246248
vi.mock("~/browser/BrowserSurfaceSlot",()=>({BrowserSurfaceSlot: ()=>null}));
247-
vi.mock("./useLoadingProgress",()=>({useLoadingProgress: ()=>0}));
248249
vi.mock("./usePreviewSession",()=>({usePreviewSession: vi.fn()}));
249250

250251
import{PreviewView}from"./PreviewView";
@@ -256,6 +257,68 @@ const TEST_THREAD_REF = {
256257
}asconst;
257258
constTEST_RUNTIME_TAB_ID=previewRuntimeTabId(TEST_THREAD_REF,null,"tab-1");
258259

260+
// ReactDOM needs a host, but this unit suite intentionally has no DOM dependency.
261+
classTestNode{
262+
parentNode: TestNode|null=null;
263+
childNodes: TestNode[]=[];
264+
readonlynodeName: string;
265+
readonlytagName: string;
266+
readonlynamespaceURI="http://www.w3.org/1999/xhtml";
267+
readonlystyle={};
268+
269+
constructor(
270+
name: string,
271+
readonlyownerDocument: TestNode|null=null,
272+
readonlynodeType=1,
273+
){
274+
this.nodeName=name.toUpperCase();
275+
this.tagName=this.nodeName;
276+
}
277+
278+
settextContent(_value: string){
279+
this.childNodes=[];
280+
}
281+
282+
appendChild(child: TestNode){
283+
child.parentNode=this;
284+
this.childNodes.push(child);
285+
returnchild;
286+
}
287+
288+
removeChild(child: TestNode){
289+
this.childNodes.splice(this.childNodes.indexOf(child),1);
290+
child.parentNode=null;
291+
returnchild;
292+
}
293+
294+
createElement(name: string){
295+
returnnewTestNode(name,this);
296+
}
297+
298+
addEventListener(){}
299+
removeEventListener(){}
300+
setAttribute(){}
301+
}
302+
303+
functioninstallTestDom(){
304+
constdocument=newTestNode("#document",null,9);
305+
constwindow={
306+
document,
307+
HTMLIFrameElement: TestNode,
308+
setInterval: globalThis.setInterval,
309+
clearInterval: globalThis.clearInterval,
310+
setTimeout: globalThis.setTimeout,
311+
clearTimeout: globalThis.clearTimeout,
312+
addEventListener(){},
313+
removeEventListener(){},
314+
};
315+
vi.stubGlobal("document",document);
316+
vi.stubGlobal("window",window);
317+
vi.stubGlobal("HTMLIFrameElement",window.HTMLIFrameElement);
318+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT",true);
319+
returndocument;
320+
}
321+
259322
describe("PreviewView navigation",()=>{
260323
beforeEach(()=>{
261324
mocks.navigate.mockClear();
@@ -279,9 +342,38 @@ describe("PreviewView navigation", () => {
279342
mocks.toggleAnnotation=null;
280343
mocks.pictureInPicture=false;
281344
mocks.showEmptyState=false;
345+
mocks.loading=false;
282346
mocks.recordVisitForThread.mockClear();
283347
});
284348

349+
it("does not rerender while loading time passes",async()=>{
350+
vi.useFakeTimers();
351+
mocks.loading=true;
352+
constdocument=installTestDom();
353+
const{ createRoot }=awaitimport("react-dom/client");
354+
constroot=createRoot(document.createElement("div")asunknownasElement);
355+
constonRender=vi.fn();
356+
357+
try{
358+
awaitact(()=>{
359+
root.render(
360+
<Profilerid="preview"onRender={onRender}>
361+
<PreviewViewthreadRef={TEST_THREAD_REF}tabId="tab-1"visible/>
362+
</Profiler>,
363+
);
364+
});
365+
constinitialRenderCount=onRender.mock.calls.length;
366+
367+
awaitact(()=>vi.advanceTimersByTimeAsync(1_000));
368+
369+
expect(onRender).toHaveBeenCalledTimes(initialRenderCount);
370+
}finally{
371+
awaitact(()=>root.unmount());
372+
vi.useRealTimers();
373+
vi.unstubAllGlobals();
374+
}
375+
});
376+
285377
it.each([
286378
[
287379
"https://localhost:8000/dashboard?mode=test#top",

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import{shouldShowPreviewEmptyState}from"./previewEmptyStateLogic";
5151
import{BrowserSurfaceSlot}from"~/browser/BrowserSurfaceSlot";
5252
import{useBrowserSurfaceStore}from"~/browser/browserSurfaceStore";
53-
import{useLoadingProgress}from"./useLoadingProgress";
5453
import{usePreviewSession}from"./usePreviewSession";
5554
import{ZoomIndicator}from"./ZoomIndicator";
5655
import{AgentBrowserCursor}from"./AgentBrowserCursor";
@@ -142,7 +141,6 @@ export function PreviewView({
142141
constisUnreachable=navStatus._tag==="LoadFailed";
143142
constshowEmptyState=shouldShowPreviewEmptyState(snapshot);
144143
constcontroller=desktopOverlay?.controller??"none";
145-
constloadProgress=useLoadingProgress(loading);
146144
constviewport=snapshot?.viewport??FILL_PREVIEW_VIEWPORT;
147145
constbrowserDefaults=useBrowserDefaults();
148146
constpanelRect=useBrowserSurfaceStore((state)=>
@@ -661,7 +659,6 @@ export function PreviewView({
661659
<PreviewChromeRow
662660
url={url}
663661
loading={loading}
664-
loadProgress={loadProgress}
665662
canGoBack={canGoBack}
666663
canGoForward={canGoForward}
667664
refreshDisabled={refreshDisabled}

‎apps/web/src/components/preview/useLoadingProgress.ts‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,3 +2357,38 @@ code {
23572357
.ultrathink-chroma {
23582358
animation: ultrathink-chroma-shift 10s linear infinite;
23592359
}
2360+
2361+
@keyframes preview-loading-progress {
2362+
from {
2363+
transform:scaleX(0.04);
2364+
}
2365+
2366+
to {
2367+
transform:scaleX(0.9);
2368+
}
2369+
}
2370+
2371+
.preview-loading-progress {
2372+
opacity:0;
2373+
transform:scaleX(1);
2374+
transition:
2375+
transform 150ms ease-out,
2376+
opacity 150ms ease-out 220ms;
2377+
}
2378+
2379+
.preview-loading-progress[data-loading="true"] {
2380+
opacity:1;
2381+
animation: preview-loading-progress 5.3scubic-bezier(0.1,0.5,0.2,1) forwards;
2382+
}
2383+
2384+
@media (prefers-reduced-motion: reduce) {
2385+
.preview-loading-progress,
2386+
.preview-loading-progress[data-loading="true"] {
2387+
transition: none;
2388+
animation: none;
2389+
}
2390+
2391+
.preview-loading-progress[data-loading="true"] {
2392+
transform:scaleX(0.9);
2393+
}
2394+
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit be7d35a

Browse files
authored
perf(web): stop preview loading rerenders (#7561)
1 parent 549201f commit be7d35a

6 files changed

Lines changed: 135 additions & 63 deletions

File tree

‎apps/web/src/components/preview/PreviewChromeRow.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ describe("PreviewChromeRow", () => {
99
<PreviewChromeRow
1010
url="https://example.com/dashboard?mode=edit&tab=1#notes"
1111
loading={false}
12-
loadProgress={0}
1312
canGoBack={false}
1413
canGoForward={false}
1514
refreshDisabled={false}

‎apps/web/src/components/preview/PreviewChromeRow.tsx‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { cn } from "~/lib/utils";
2424
interfaceProps{
2525
url: string;
2626
loading: boolean;
27-
loadProgress: number;
2827
canGoBack: boolean;
2928
canGoForward: boolean;
3029
refreshDisabled: boolean;
@@ -65,7 +64,6 @@ const NOOP = () => {};
6564
exportfunctionPreviewChromeRow({
6665
url,
6766
loading,
68-
loadProgress,
6967
canGoBack,
7068
canGoForward,
7169
refreshDisabled,
@@ -308,16 +306,12 @@ export function PreviewChromeRow({
308306
) : null}
309307
{trailingActions}
310308
</form>
311-
{loadProgress>0 ? (
312-
<div
313-
aria-hidden
314-
className="pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 rounded-r-full bg-primary transition-all duration-150 ease-out"
315-
style={{
316-
width: `${loadProgress}%`,
317-
boxShadow: "0 0 6px 1px var(--color-ring)",
318-
}}
319-
/>
320-
) : null}
309+
<div
310+
aria-hidden
311+
data-loading={loading}
312+
className="preview-loading-progress pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 w-full origin-left rounded-r-full bg-primary"
313+
style={{boxShadow: "0 0 6px 1px var(--color-ring)"}}
314+
/>
321315
</div>
322316
);
323317
}

‎apps/web/src/components/preview/PreviewView.test.tsx‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FILL_PREVIEW_VIEWPORT,
66
ThreadId,
77
}from"@t3tools/contracts";
8+
import{act,Profiler}from"react";
89
import{renderToStaticMarkup}from"react-dom/server";
910
import{beforeEach,describe,expect,it,vi}from"vite-plus/test";
1011

@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
3031
toggleAnnotation: nullas(()=>void)|null,
3132
pictureInPicture: false,
3233
showEmptyState: false,
34+
loading: false,
3335
recordVisitForThread: vi.fn(),
3436
}));
3537

@@ -103,7 +105,7 @@ vi.mock("~/previewStateStore", () => ({
103105
hasWebContents: true,
104106
canGoBack: false,
105107
canGoForward: false,
106-
loading: false,
108+
loading: mocks.loading,
107109
zoomFactor: 1,
108110
pictureInPicture: mocks.pictureInPicture,
109111
colorScheme: "system",
@@ -244,7 +246,6 @@ vi.mock("./PreviewUnreachable", () => ({ PreviewUnreachable: () => null }));
244246
vi.mock("./ZoomIndicator",()=>({ZoomIndicator: ()=>null}));
245247
vi.mock("./AgentBrowserCursor",()=>({AgentBrowserCursor: ()=>null}));
246248
vi.mock("~/browser/BrowserSurfaceSlot",()=>({BrowserSurfaceSlot: ()=>null}));
247-
vi.mock("./useLoadingProgress",()=>({useLoadingProgress: ()=>0}));
248249
vi.mock("./usePreviewSession",()=>({usePreviewSession: vi.fn()}));
249250

250251
import{PreviewView}from"./PreviewView";
@@ -256,6 +257,68 @@ const TEST_THREAD_REF = {
256257
}asconst;
257258
constTEST_RUNTIME_TAB_ID=previewRuntimeTabId(TEST_THREAD_REF,null,"tab-1");
258259

260+
// ReactDOM needs a host, but this unit suite intentionally has no DOM dependency.
261+
classTestNode{
262+
parentNode: TestNode|null=null;
263+
childNodes: TestNode[]=[];
264+
readonlynodeName: string;
265+
readonlytagName: string;
266+
readonlynamespaceURI="http://www.w3.org/1999/xhtml";
267+
readonlystyle={};
268+
269+
constructor(
270+
name: string,
271+
readonlyownerDocument: TestNode|null=null,
272+
readonlynodeType=1,
273+
){
274+
this.nodeName=name.toUpperCase();
275+
this.tagName=this.nodeName;
276+
}
277+
278+
settextContent(_value: string){
279+
this.childNodes=[];
280+
}
281+
282+
appendChild(child: TestNode){
283+
child.parentNode=this;
284+
this.childNodes.push(child);
285+
returnchild;
286+
}
287+
288+
removeChild(child: TestNode){
289+
this.childNodes.splice(this.childNodes.indexOf(child),1);
290+
child.parentNode=null;
291+
returnchild;
292+
}
293+
294+
createElement(name: string){
295+
returnnewTestNode(name,this);
296+
}
297+
298+
addEventListener(){}
299+
removeEventListener(){}
300+
setAttribute(){}
301+
}
302+
303+
functioninstallTestDom(){
304+
constdocument=newTestNode("#document",null,9);
305+
constwindow={
306+
document,
307+
HTMLIFrameElement: TestNode,
308+
setInterval: globalThis.setInterval,
309+
clearInterval: globalThis.clearInterval,
310+
setTimeout: globalThis.setTimeout,
311+
clearTimeout: globalThis.clearTimeout,
312+
addEventListener(){},
313+
removeEventListener(){},
314+
};
315+
vi.stubGlobal("document",document);
316+
vi.stubGlobal("window",window);
317+
vi.stubGlobal("HTMLIFrameElement",window.HTMLIFrameElement);
318+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT",true);
319+
returndocument;
320+
}
321+
259322
describe("PreviewView navigation",()=>{
260323
beforeEach(()=>{
261324
mocks.navigate.mockClear();
@@ -279,9 +342,38 @@ describe("PreviewView navigation", () => {
279342
mocks.toggleAnnotation=null;
280343
mocks.pictureInPicture=false;
281344
mocks.showEmptyState=false;
345+
mocks.loading=false;
282346
mocks.recordVisitForThread.mockClear();
283347
});
284348

349+
it("does not rerender while loading time passes",async()=>{
350+
vi.useFakeTimers();
351+
mocks.loading=true;
352+
constdocument=installTestDom();
353+
const{ createRoot }=awaitimport("react-dom/client");
354+
constroot=createRoot(document.createElement("div")asunknownasElement);
355+
constonRender=vi.fn();
356+
357+
try{
358+
awaitact(()=>{
359+
root.render(
360+
<Profilerid="preview"onRender={onRender}>
361+
<PreviewViewthreadRef={TEST_THREAD_REF}tabId="tab-1"visible/>
362+
</Profiler>,
363+
);
364+
});
365+
constinitialRenderCount=onRender.mock.calls.length;
366+
367+
awaitact(()=>vi.advanceTimersByTimeAsync(1_000));
368+
369+
expect(onRender).toHaveBeenCalledTimes(initialRenderCount);
370+
}finally{
371+
awaitact(()=>root.unmount());
372+
vi.useRealTimers();
373+
vi.unstubAllGlobals();
374+
}
375+
});
376+
285377
it.each([
286378
[
287379
"https://localhost:8000/dashboard?mode=test#top",

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import{shouldShowPreviewEmptyState}from"./previewEmptyStateLogic";
5151
import{BrowserSurfaceSlot}from"~/browser/BrowserSurfaceSlot";
5252
import{useBrowserSurfaceStore}from"~/browser/browserSurfaceStore";
53-
import{useLoadingProgress}from"./useLoadingProgress";
5453
import{usePreviewSession}from"./usePreviewSession";
5554
import{ZoomIndicator}from"./ZoomIndicator";
5655
import{AgentBrowserCursor}from"./AgentBrowserCursor";
@@ -142,7 +141,6 @@ export function PreviewView({
142141
constisUnreachable=navStatus._tag==="LoadFailed";
143142
constshowEmptyState=shouldShowPreviewEmptyState(snapshot);
144143
constcontroller=desktopOverlay?.controller??"none";
145-
constloadProgress=useLoadingProgress(loading);
146144
constviewport=snapshot?.viewport??FILL_PREVIEW_VIEWPORT;
147145
constbrowserDefaults=useBrowserDefaults();
148146
constpanelRect=useBrowserSurfaceStore((state)=>
@@ -661,7 +659,6 @@ export function PreviewView({
661659
<PreviewChromeRow
662660
url={url}
663661
loading={loading}
664-
loadProgress={loadProgress}
665662
canGoBack={canGoBack}
666663
canGoForward={canGoForward}
667664
refreshDisabled={refreshDisabled}

‎apps/web/src/components/preview/useLoadingProgress.ts‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,3 +2357,38 @@ code {
23572357
.ultrathink-chroma {
23582358
animation: ultrathink-chroma-shift 10s linear infinite;
23592359
}
2360+
2361+
@keyframes preview-loading-progress {
2362+
from {
2363+
transform:scaleX(0.04);
2364+
}
2365+
2366+
to {
2367+
transform:scaleX(0.9);
2368+
}
2369+
}
2370+
2371+
.preview-loading-progress {
2372+
opacity:0;
2373+
transform:scaleX(1);
2374+
transition:
2375+
transform 150ms ease-out,
2376+
opacity 150ms ease-out 220ms;
2377+
}
2378+
2379+
.preview-loading-progress[data-loading="true"] {
2380+
opacity:1;
2381+
animation: preview-loading-progress 5.3scubic-bezier(0.1,0.5,0.2,1) forwards;
2382+
}
2383+
2384+
@media (prefers-reduced-motion: reduce) {
2385+
.preview-loading-progress,
2386+
.preview-loading-progress[data-loading="true"] {
2387+
transition: none;
2388+
animation: none;
2389+
}
2390+
2391+
.preview-loading-progress[data-loading="true"] {
2392+
transform:scaleX(0.9);
2393+
}
2394+
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit be7d35a

Browse files
authored
perf(web): stop preview loading rerenders (#7561)
1 parent 549201f commit be7d35a

6 files changed

Lines changed: 135 additions & 63 deletions

File tree

‎apps/web/src/components/preview/PreviewChromeRow.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ describe("PreviewChromeRow", () => {
99
<PreviewChromeRow
1010
url="https://example.com/dashboard?mode=edit&tab=1#notes"
1111
loading={false}
12-
loadProgress={0}
1312
canGoBack={false}
1413
canGoForward={false}
1514
refreshDisabled={false}

‎apps/web/src/components/preview/PreviewChromeRow.tsx‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { cn } from "~/lib/utils";
2424
interfaceProps{
2525
url: string;
2626
loading: boolean;
27-
loadProgress: number;
2827
canGoBack: boolean;
2928
canGoForward: boolean;
3029
refreshDisabled: boolean;
@@ -65,7 +64,6 @@ const NOOP = () => {};
6564
exportfunctionPreviewChromeRow({
6665
url,
6766
loading,
68-
loadProgress,
6967
canGoBack,
7068
canGoForward,
7169
refreshDisabled,
@@ -308,16 +306,12 @@ export function PreviewChromeRow({
308306
) : null}
309307
{trailingActions}
310308
</form>
311-
{loadProgress>0 ? (
312-
<div
313-
aria-hidden
314-
className="pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 rounded-r-full bg-primary transition-all duration-150 ease-out"
315-
style={{
316-
width: `${loadProgress}%`,
317-
boxShadow: "0 0 6px 1px var(--color-ring)",
318-
}}
319-
/>
320-
) : null}
309+
<div
310+
aria-hidden
311+
data-loading={loading}
312+
className="preview-loading-progress pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 w-full origin-left rounded-r-full bg-primary"
313+
style={{boxShadow: "0 0 6px 1px var(--color-ring)"}}
314+
/>
321315
</div>
322316
);
323317
}

‎apps/web/src/components/preview/PreviewView.test.tsx‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FILL_PREVIEW_VIEWPORT,
66
ThreadId,
77
}from"@t3tools/contracts";
8+
import{act,Profiler}from"react";
89
import{renderToStaticMarkup}from"react-dom/server";
910
import{beforeEach,describe,expect,it,vi}from"vite-plus/test";
1011

@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
3031
toggleAnnotation: nullas(()=>void)|null,
3132
pictureInPicture: false,
3233
showEmptyState: false,
34+
loading: false,
3335
recordVisitForThread: vi.fn(),
3436
}));
3537

@@ -103,7 +105,7 @@ vi.mock("~/previewStateStore", () => ({
103105
hasWebContents: true,
104106
canGoBack: false,
105107
canGoForward: false,
106-
loading: false,
108+
loading: mocks.loading,
107109
zoomFactor: 1,
108110
pictureInPicture: mocks.pictureInPicture,
109111
colorScheme: "system",
@@ -244,7 +246,6 @@ vi.mock("./PreviewUnreachable", () => ({ PreviewUnreachable: () => null }));
244246
vi.mock("./ZoomIndicator",()=>({ZoomIndicator: ()=>null}));
245247
vi.mock("./AgentBrowserCursor",()=>({AgentBrowserCursor: ()=>null}));
246248
vi.mock("~/browser/BrowserSurfaceSlot",()=>({BrowserSurfaceSlot: ()=>null}));
247-
vi.mock("./useLoadingProgress",()=>({useLoadingProgress: ()=>0}));
248249
vi.mock("./usePreviewSession",()=>({usePreviewSession: vi.fn()}));
249250

250251
import{PreviewView}from"./PreviewView";
@@ -256,6 +257,68 @@ const TEST_THREAD_REF = {
256257
}asconst;
257258
constTEST_RUNTIME_TAB_ID=previewRuntimeTabId(TEST_THREAD_REF,null,"tab-1");
258259

260+
// ReactDOM needs a host, but this unit suite intentionally has no DOM dependency.
261+
classTestNode{
262+
parentNode: TestNode|null=null;
263+
childNodes: TestNode[]=[];
264+
readonlynodeName: string;
265+
readonlytagName: string;
266+
readonlynamespaceURI="http://www.w3.org/1999/xhtml";
267+
readonlystyle={};
268+
269+
constructor(
270+
name: string,
271+
readonlyownerDocument: TestNode|null=null,
272+
readonlynodeType=1,
273+
){
274+
this.nodeName=name.toUpperCase();
275+
this.tagName=this.nodeName;
276+
}
277+
278+
settextContent(_value: string){
279+
this.childNodes=[];
280+
}
281+
282+
appendChild(child: TestNode){
283+
child.parentNode=this;
284+
this.childNodes.push(child);
285+
returnchild;
286+
}
287+
288+
removeChild(child: TestNode){
289+
this.childNodes.splice(this.childNodes.indexOf(child),1);
290+
child.parentNode=null;
291+
returnchild;
292+
}
293+
294+
createElement(name: string){
295+
returnnewTestNode(name,this);
296+
}
297+
298+
addEventListener(){}
299+
removeEventListener(){}
300+
setAttribute(){}
301+
}
302+
303+
functioninstallTestDom(){
304+
constdocument=newTestNode("#document",null,9);
305+
constwindow={
306+
document,
307+
HTMLIFrameElement: TestNode,
308+
setInterval: globalThis.setInterval,
309+
clearInterval: globalThis.clearInterval,
310+
setTimeout: globalThis.setTimeout,
311+
clearTimeout: globalThis.clearTimeout,
312+
addEventListener(){},
313+
removeEventListener(){},
314+
};
315+
vi.stubGlobal("document",document);
316+
vi.stubGlobal("window",window);
317+
vi.stubGlobal("HTMLIFrameElement",window.HTMLIFrameElement);
318+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT",true);
319+
returndocument;
320+
}
321+
259322
describe("PreviewView navigation",()=>{
260323
beforeEach(()=>{
261324
mocks.navigate.mockClear();
@@ -279,9 +342,38 @@ describe("PreviewView navigation", () => {
279342
mocks.toggleAnnotation=null;
280343
mocks.pictureInPicture=false;
281344
mocks.showEmptyState=false;
345+
mocks.loading=false;
282346
mocks.recordVisitForThread.mockClear();
283347
});
284348

349+
it("does not rerender while loading time passes",async()=>{
350+
vi.useFakeTimers();
351+
mocks.loading=true;
352+
constdocument=installTestDom();
353+
const{ createRoot }=awaitimport("react-dom/client");
354+
constroot=createRoot(document.createElement("div")asunknownasElement);
355+
constonRender=vi.fn();
356+
357+
try{
358+
awaitact(()=>{
359+
root.render(
360+
<Profilerid="preview"onRender={onRender}>
361+
<PreviewViewthreadRef={TEST_THREAD_REF}tabId="tab-1"visible/>
362+
</Profiler>,
363+
);
364+
});
365+
constinitialRenderCount=onRender.mock.calls.length;
366+
367+
awaitact(()=>vi.advanceTimersByTimeAsync(1_000));
368+
369+
expect(onRender).toHaveBeenCalledTimes(initialRenderCount);
370+
}finally{
371+
awaitact(()=>root.unmount());
372+
vi.useRealTimers();
373+
vi.unstubAllGlobals();
374+
}
375+
});
376+
285377
it.each([
286378
[
287379
"https://localhost:8000/dashboard?mode=test#top",

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import{shouldShowPreviewEmptyState}from"./previewEmptyStateLogic";
5151
import{BrowserSurfaceSlot}from"~/browser/BrowserSurfaceSlot";
5252
import{useBrowserSurfaceStore}from"~/browser/browserSurfaceStore";
53-
import{useLoadingProgress}from"./useLoadingProgress";
5453
import{usePreviewSession}from"./usePreviewSession";
5554
import{ZoomIndicator}from"./ZoomIndicator";
5655
import{AgentBrowserCursor}from"./AgentBrowserCursor";
@@ -142,7 +141,6 @@ export function PreviewView({
142141
constisUnreachable=navStatus._tag==="LoadFailed";
143142
constshowEmptyState=shouldShowPreviewEmptyState(snapshot);
144143
constcontroller=desktopOverlay?.controller??"none";
145-
constloadProgress=useLoadingProgress(loading);
146144
constviewport=snapshot?.viewport??FILL_PREVIEW_VIEWPORT;
147145
constbrowserDefaults=useBrowserDefaults();
148146
constpanelRect=useBrowserSurfaceStore((state)=>
@@ -661,7 +659,6 @@ export function PreviewView({
661659
<PreviewChromeRow
662660
url={url}
663661
loading={loading}
664-
loadProgress={loadProgress}
665662
canGoBack={canGoBack}
666663
canGoForward={canGoForward}
667664
refreshDisabled={refreshDisabled}

‎apps/web/src/components/preview/useLoadingProgress.ts‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,3 +2357,38 @@ code {
23572357
.ultrathink-chroma {
23582358
animation: ultrathink-chroma-shift 10s linear infinite;
23592359
}
2360+
2361+
@keyframes preview-loading-progress {
2362+
from {
2363+
transform:scaleX(0.04);
2364+
}
2365+
2366+
to {
2367+
transform:scaleX(0.9);
2368+
}
2369+
}
2370+
2371+
.preview-loading-progress {
2372+
opacity:0;
2373+
transform:scaleX(1);
2374+
transition:
2375+
transform 150ms ease-out,
2376+
opacity 150ms ease-out 220ms;
2377+
}
2378+
2379+
.preview-loading-progress[data-loading="true"] {
2380+
opacity:1;
2381+
animation: preview-loading-progress 5.3scubic-bezier(0.1,0.5,0.2,1) forwards;
2382+
}
2383+
2384+
@media (prefers-reduced-motion: reduce) {
2385+
.preview-loading-progress,
2386+
.preview-loading-progress[data-loading="true"] {
2387+
transition: none;
2388+
animation: none;
2389+
}
2390+
2391+
.preview-loading-progress[data-loading="true"] {
2392+
transform:scaleX(0.9);
2393+
}
2394+
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit be7d35a

Browse files
authored
perf(web): stop preview loading rerenders (#7561)
1 parent 549201f commit be7d35a

6 files changed

Lines changed: 135 additions & 63 deletions

File tree

‎apps/web/src/components/preview/PreviewChromeRow.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ describe("PreviewChromeRow", () => {
99
<PreviewChromeRow
1010
url="https://example.com/dashboard?mode=edit&tab=1#notes"
1111
loading={false}
12-
loadProgress={0}
1312
canGoBack={false}
1413
canGoForward={false}
1514
refreshDisabled={false}

‎apps/web/src/components/preview/PreviewChromeRow.tsx‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { cn } from "~/lib/utils";
2424
interfaceProps{
2525
url: string;
2626
loading: boolean;
27-
loadProgress: number;
2827
canGoBack: boolean;
2928
canGoForward: boolean;
3029
refreshDisabled: boolean;
@@ -65,7 +64,6 @@ const NOOP = () => {};
6564
exportfunctionPreviewChromeRow({
6665
url,
6766
loading,
68-
loadProgress,
6967
canGoBack,
7068
canGoForward,
7169
refreshDisabled,
@@ -308,16 +306,12 @@ export function PreviewChromeRow({
308306
) : null}
309307
{trailingActions}
310308
</form>
311-
{loadProgress>0 ? (
312-
<div
313-
aria-hidden
314-
className="pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 rounded-r-full bg-primary transition-all duration-150 ease-out"
315-
style={{
316-
width: `${loadProgress}%`,
317-
boxShadow: "0 0 6px 1px var(--color-ring)",
318-
}}
319-
/>
320-
) : null}
309+
<div
310+
aria-hidden
311+
data-loading={loading}
312+
className="preview-loading-progress pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 w-full origin-left rounded-r-full bg-primary"
313+
style={{boxShadow: "0 0 6px 1px var(--color-ring)"}}
314+
/>
321315
</div>
322316
);
323317
}

‎apps/web/src/components/preview/PreviewView.test.tsx‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FILL_PREVIEW_VIEWPORT,
66
ThreadId,
77
}from"@t3tools/contracts";
8+
import{act,Profiler}from"react";
89
import{renderToStaticMarkup}from"react-dom/server";
910
import{beforeEach,describe,expect,it,vi}from"vite-plus/test";
1011

@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
3031
toggleAnnotation: nullas(()=>void)|null,
3132
pictureInPicture: false,
3233
showEmptyState: false,
34+
loading: false,
3335
recordVisitForThread: vi.fn(),
3436
}));
3537

@@ -103,7 +105,7 @@ vi.mock("~/previewStateStore", () => ({
103105
hasWebContents: true,
104106
canGoBack: false,
105107
canGoForward: false,
106-
loading: false,
108+
loading: mocks.loading,
107109
zoomFactor: 1,
108110
pictureInPicture: mocks.pictureInPicture,
109111
colorScheme: "system",
@@ -244,7 +246,6 @@ vi.mock("./PreviewUnreachable", () => ({ PreviewUnreachable: () => null }));
244246
vi.mock("./ZoomIndicator",()=>({ZoomIndicator: ()=>null}));
245247
vi.mock("./AgentBrowserCursor",()=>({AgentBrowserCursor: ()=>null}));
246248
vi.mock("~/browser/BrowserSurfaceSlot",()=>({BrowserSurfaceSlot: ()=>null}));
247-
vi.mock("./useLoadingProgress",()=>({useLoadingProgress: ()=>0}));
248249
vi.mock("./usePreviewSession",()=>({usePreviewSession: vi.fn()}));
249250

250251
import{PreviewView}from"./PreviewView";
@@ -256,6 +257,68 @@ const TEST_THREAD_REF = {
256257
}asconst;
257258
constTEST_RUNTIME_TAB_ID=previewRuntimeTabId(TEST_THREAD_REF,null,"tab-1");
258259

260+
// ReactDOM needs a host, but this unit suite intentionally has no DOM dependency.
261+
classTestNode{
262+
parentNode: TestNode|null=null;
263+
childNodes: TestNode[]=[];
264+
readonlynodeName: string;
265+
readonlytagName: string;
266+
readonlynamespaceURI="http://www.w3.org/1999/xhtml";
267+
readonlystyle={};
268+
269+
constructor(
270+
name: string,
271+
readonlyownerDocument: TestNode|null=null,
272+
readonlynodeType=1,
273+
){
274+
this.nodeName=name.toUpperCase();
275+
this.tagName=this.nodeName;
276+
}
277+
278+
settextContent(_value: string){
279+
this.childNodes=[];
280+
}
281+
282+
appendChild(child: TestNode){
283+
child.parentNode=this;
284+
this.childNodes.push(child);
285+
returnchild;
286+
}
287+
288+
removeChild(child: TestNode){
289+
this.childNodes.splice(this.childNodes.indexOf(child),1);
290+
child.parentNode=null;
291+
returnchild;
292+
}
293+
294+
createElement(name: string){
295+
returnnewTestNode(name,this);
296+
}
297+
298+
addEventListener(){}
299+
removeEventListener(){}
300+
setAttribute(){}
301+
}
302+
303+
functioninstallTestDom(){
304+
constdocument=newTestNode("#document",null,9);
305+
constwindow={
306+
document,
307+
HTMLIFrameElement: TestNode,
308+
setInterval: globalThis.setInterval,
309+
clearInterval: globalThis.clearInterval,
310+
setTimeout: globalThis.setTimeout,
311+
clearTimeout: globalThis.clearTimeout,
312+
addEventListener(){},
313+
removeEventListener(){},
314+
};
315+
vi.stubGlobal("document",document);
316+
vi.stubGlobal("window",window);
317+
vi.stubGlobal("HTMLIFrameElement",window.HTMLIFrameElement);
318+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT",true);
319+
returndocument;
320+
}
321+
259322
describe("PreviewView navigation",()=>{
260323
beforeEach(()=>{
261324
mocks.navigate.mockClear();
@@ -279,9 +342,38 @@ describe("PreviewView navigation", () => {
279342
mocks.toggleAnnotation=null;
280343
mocks.pictureInPicture=false;
281344
mocks.showEmptyState=false;
345+
mocks.loading=false;
282346
mocks.recordVisitForThread.mockClear();
283347
});
284348

349+
it("does not rerender while loading time passes",async()=>{
350+
vi.useFakeTimers();
351+
mocks.loading=true;
352+
constdocument=installTestDom();
353+
const{ createRoot }=awaitimport("react-dom/client");
354+
constroot=createRoot(document.createElement("div")asunknownasElement);
355+
constonRender=vi.fn();
356+
357+
try{
358+
awaitact(()=>{
359+
root.render(
360+
<Profilerid="preview"onRender={onRender}>
361+
<PreviewViewthreadRef={TEST_THREAD_REF}tabId="tab-1"visible/>
362+
</Profiler>,
363+
);
364+
});
365+
constinitialRenderCount=onRender.mock.calls.length;
366+
367+
awaitact(()=>vi.advanceTimersByTimeAsync(1_000));
368+
369+
expect(onRender).toHaveBeenCalledTimes(initialRenderCount);
370+
}finally{
371+
awaitact(()=>root.unmount());
372+
vi.useRealTimers();
373+
vi.unstubAllGlobals();
374+
}
375+
});
376+
285377
it.each([
286378
[
287379
"https://localhost:8000/dashboard?mode=test#top",

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import{shouldShowPreviewEmptyState}from"./previewEmptyStateLogic";
5151
import{BrowserSurfaceSlot}from"~/browser/BrowserSurfaceSlot";
5252
import{useBrowserSurfaceStore}from"~/browser/browserSurfaceStore";
53-
import{useLoadingProgress}from"./useLoadingProgress";
5453
import{usePreviewSession}from"./usePreviewSession";
5554
import{ZoomIndicator}from"./ZoomIndicator";
5655
import{AgentBrowserCursor}from"./AgentBrowserCursor";
@@ -142,7 +141,6 @@ export function PreviewView({
142141
constisUnreachable=navStatus._tag==="LoadFailed";
143142
constshowEmptyState=shouldShowPreviewEmptyState(snapshot);
144143
constcontroller=desktopOverlay?.controller??"none";
145-
constloadProgress=useLoadingProgress(loading);
146144
constviewport=snapshot?.viewport??FILL_PREVIEW_VIEWPORT;
147145
constbrowserDefaults=useBrowserDefaults();
148146
constpanelRect=useBrowserSurfaceStore((state)=>
@@ -661,7 +659,6 @@ export function PreviewView({
661659
<PreviewChromeRow
662660
url={url}
663661
loading={loading}
664-
loadProgress={loadProgress}
665662
canGoBack={canGoBack}
666663
canGoForward={canGoForward}
667664
refreshDisabled={refreshDisabled}

‎apps/web/src/components/preview/useLoadingProgress.ts‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,3 +2357,38 @@ code {
23572357
.ultrathink-chroma {
23582358
animation: ultrathink-chroma-shift 10s linear infinite;
23592359
}
2360+
2361+
@keyframes preview-loading-progress {
2362+
from {
2363+
transform:scaleX(0.04);
2364+
}
2365+
2366+
to {
2367+
transform:scaleX(0.9);
2368+
}
2369+
}
2370+
2371+
.preview-loading-progress {
2372+
opacity:0;
2373+
transform:scaleX(1);
2374+
transition:
2375+
transform 150ms ease-out,
2376+
opacity 150ms ease-out 220ms;
2377+
}
2378+
2379+
.preview-loading-progress[data-loading="true"] {
2380+
opacity:1;
2381+
animation: preview-loading-progress 5.3scubic-bezier(0.1,0.5,0.2,1) forwards;
2382+
}
2383+
2384+
@media (prefers-reduced-motion: reduce) {
2385+
.preview-loading-progress,
2386+
.preview-loading-progress[data-loading="true"] {
2387+
transition: none;
2388+
animation: none;
2389+
}
2390+
2391+
.preview-loading-progress[data-loading="true"] {
2392+
transform:scaleX(0.9);
2393+
}
2394+
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit be7d35a

Browse files
authored
perf(web): stop preview loading rerenders (#7561)
1 parent 549201f commit be7d35a

6 files changed

Lines changed: 135 additions & 63 deletions

File tree

‎apps/web/src/components/preview/PreviewChromeRow.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ describe("PreviewChromeRow", () => {
99
<PreviewChromeRow
1010
url="https://example.com/dashboard?mode=edit&tab=1#notes"
1111
loading={false}
12-
loadProgress={0}
1312
canGoBack={false}
1413
canGoForward={false}
1514
refreshDisabled={false}

‎apps/web/src/components/preview/PreviewChromeRow.tsx‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { cn } from "~/lib/utils";
2424
interfaceProps{
2525
url: string;
2626
loading: boolean;
27-
loadProgress: number;
2827
canGoBack: boolean;
2928
canGoForward: boolean;
3029
refreshDisabled: boolean;
@@ -65,7 +64,6 @@ const NOOP = () => {};
6564
exportfunctionPreviewChromeRow({
6665
url,
6766
loading,
68-
loadProgress,
6967
canGoBack,
7068
canGoForward,
7169
refreshDisabled,
@@ -308,16 +306,12 @@ export function PreviewChromeRow({
308306
) : null}
309307
{trailingActions}
310308
</form>
311-
{loadProgress>0 ? (
312-
<div
313-
aria-hidden
314-
className="pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 rounded-r-full bg-primary transition-all duration-150 ease-out"
315-
style={{
316-
width: `${loadProgress}%`,
317-
boxShadow: "0 0 6px 1px var(--color-ring)",
318-
}}
319-
/>
320-
) : null}
309+
<div
310+
aria-hidden
311+
data-loading={loading}
312+
className="preview-loading-progress pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 w-full origin-left rounded-r-full bg-primary"
313+
style={{boxShadow: "0 0 6px 1px var(--color-ring)"}}
314+
/>
321315
</div>
322316
);
323317
}

‎apps/web/src/components/preview/PreviewView.test.tsx‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FILL_PREVIEW_VIEWPORT,
66
ThreadId,
77
}from"@t3tools/contracts";
8+
import{act,Profiler}from"react";
89
import{renderToStaticMarkup}from"react-dom/server";
910
import{beforeEach,describe,expect,it,vi}from"vite-plus/test";
1011

@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
3031
toggleAnnotation: nullas(()=>void)|null,
3132
pictureInPicture: false,
3233
showEmptyState: false,
34+
loading: false,
3335
recordVisitForThread: vi.fn(),
3436
}));
3537

@@ -103,7 +105,7 @@ vi.mock("~/previewStateStore", () => ({
103105
hasWebContents: true,
104106
canGoBack: false,
105107
canGoForward: false,
106-
loading: false,
108+
loading: mocks.loading,
107109
zoomFactor: 1,
108110
pictureInPicture: mocks.pictureInPicture,
109111
colorScheme: "system",
@@ -244,7 +246,6 @@ vi.mock("./PreviewUnreachable", () => ({ PreviewUnreachable: () => null }));
244246
vi.mock("./ZoomIndicator",()=>({ZoomIndicator: ()=>null}));
245247
vi.mock("./AgentBrowserCursor",()=>({AgentBrowserCursor: ()=>null}));
246248
vi.mock("~/browser/BrowserSurfaceSlot",()=>({BrowserSurfaceSlot: ()=>null}));
247-
vi.mock("./useLoadingProgress",()=>({useLoadingProgress: ()=>0}));
248249
vi.mock("./usePreviewSession",()=>({usePreviewSession: vi.fn()}));
249250

250251
import{PreviewView}from"./PreviewView";
@@ -256,6 +257,68 @@ const TEST_THREAD_REF = {
256257
}asconst;
257258
constTEST_RUNTIME_TAB_ID=previewRuntimeTabId(TEST_THREAD_REF,null,"tab-1");
258259

260+
// ReactDOM needs a host, but this unit suite intentionally has no DOM dependency.
261+
classTestNode{
262+
parentNode: TestNode|null=null;
263+
childNodes: TestNode[]=[];
264+
readonlynodeName: string;
265+
readonlytagName: string;
266+
readonlynamespaceURI="http://www.w3.org/1999/xhtml";
267+
readonlystyle={};
268+
269+
constructor(
270+
name: string,
271+
readonlyownerDocument: TestNode|null=null,
272+
readonlynodeType=1,
273+
){
274+
this.nodeName=name.toUpperCase();
275+
this.tagName=this.nodeName;
276+
}
277+
278+
settextContent(_value: string){
279+
this.childNodes=[];
280+
}
281+
282+
appendChild(child: TestNode){
283+
child.parentNode=this;
284+
this.childNodes.push(child);
285+
returnchild;
286+
}
287+
288+
removeChild(child: TestNode){
289+
this.childNodes.splice(this.childNodes.indexOf(child),1);
290+
child.parentNode=null;
291+
returnchild;
292+
}
293+
294+
createElement(name: string){
295+
returnnewTestNode(name,this);
296+
}
297+
298+
addEventListener(){}
299+
removeEventListener(){}
300+
setAttribute(){}
301+
}
302+
303+
functioninstallTestDom(){
304+
constdocument=newTestNode("#document",null,9);
305+
constwindow={
306+
document,
307+
HTMLIFrameElement: TestNode,
308+
setInterval: globalThis.setInterval,
309+
clearInterval: globalThis.clearInterval,
310+
setTimeout: globalThis.setTimeout,
311+
clearTimeout: globalThis.clearTimeout,
312+
addEventListener(){},
313+
removeEventListener(){},
314+
};
315+
vi.stubGlobal("document",document);
316+
vi.stubGlobal("window",window);
317+
vi.stubGlobal("HTMLIFrameElement",window.HTMLIFrameElement);
318+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT",true);
319+
returndocument;
320+
}
321+
259322
describe("PreviewView navigation",()=>{
260323
beforeEach(()=>{
261324
mocks.navigate.mockClear();
@@ -279,9 +342,38 @@ describe("PreviewView navigation", () => {
279342
mocks.toggleAnnotation=null;
280343
mocks.pictureInPicture=false;
281344
mocks.showEmptyState=false;
345+
mocks.loading=false;
282346
mocks.recordVisitForThread.mockClear();
283347
});
284348

349+
it("does not rerender while loading time passes",async()=>{
350+
vi.useFakeTimers();
351+
mocks.loading=true;
352+
constdocument=installTestDom();
353+
const{ createRoot }=awaitimport("react-dom/client");
354+
constroot=createRoot(document.createElement("div")asunknownasElement);
355+
constonRender=vi.fn();
356+
357+
try{
358+
awaitact(()=>{
359+
root.render(
360+
<Profilerid="preview"onRender={onRender}>
361+
<PreviewViewthreadRef={TEST_THREAD_REF}tabId="tab-1"visible/>
362+
</Profiler>,
363+
);
364+
});
365+
constinitialRenderCount=onRender.mock.calls.length;
366+
367+
awaitact(()=>vi.advanceTimersByTimeAsync(1_000));
368+
369+
expect(onRender).toHaveBeenCalledTimes(initialRenderCount);
370+
}finally{
371+
awaitact(()=>root.unmount());
372+
vi.useRealTimers();
373+
vi.unstubAllGlobals();
374+
}
375+
});
376+
285377
it.each([
286378
[
287379
"https://localhost:8000/dashboard?mode=test#top",

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import{shouldShowPreviewEmptyState}from"./previewEmptyStateLogic";
5151
import{BrowserSurfaceSlot}from"~/browser/BrowserSurfaceSlot";
5252
import{useBrowserSurfaceStore}from"~/browser/browserSurfaceStore";
53-
import{useLoadingProgress}from"./useLoadingProgress";
5453
import{usePreviewSession}from"./usePreviewSession";
5554
import{ZoomIndicator}from"./ZoomIndicator";
5655
import{AgentBrowserCursor}from"./AgentBrowserCursor";
@@ -142,7 +141,6 @@ export function PreviewView({
142141
constisUnreachable=navStatus._tag==="LoadFailed";
143142
constshowEmptyState=shouldShowPreviewEmptyState(snapshot);
144143
constcontroller=desktopOverlay?.controller??"none";
145-
constloadProgress=useLoadingProgress(loading);
146144
constviewport=snapshot?.viewport??FILL_PREVIEW_VIEWPORT;
147145
constbrowserDefaults=useBrowserDefaults();
148146
constpanelRect=useBrowserSurfaceStore((state)=>
@@ -661,7 +659,6 @@ export function PreviewView({
661659
<PreviewChromeRow
662660
url={url}
663661
loading={loading}
664-
loadProgress={loadProgress}
665662
canGoBack={canGoBack}
666663
canGoForward={canGoForward}
667664
refreshDisabled={refreshDisabled}

‎apps/web/src/components/preview/useLoadingProgress.ts‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,3 +2357,38 @@ code {
23572357
.ultrathink-chroma {
23582358
animation: ultrathink-chroma-shift 10s linear infinite;
23592359
}
2360+
2361+
@keyframes preview-loading-progress {
2362+
from {
2363+
transform:scaleX(0.04);
2364+
}
2365+
2366+
to {
2367+
transform:scaleX(0.9);
2368+
}
2369+
}
2370+
2371+
.preview-loading-progress {
2372+
opacity:0;
2373+
transform:scaleX(1);
2374+
transition:
2375+
transform 150ms ease-out,
2376+
opacity 150ms ease-out 220ms;
2377+
}
2378+
2379+
.preview-loading-progress[data-loading="true"] {
2380+
opacity:1;
2381+
animation: preview-loading-progress 5.3scubic-bezier(0.1,0.5,0.2,1) forwards;
2382+
}
2383+
2384+
@media (prefers-reduced-motion: reduce) {
2385+
.preview-loading-progress,
2386+
.preview-loading-progress[data-loading="true"] {
2387+
transition: none;
2388+
animation: none;
2389+
}
2390+
2391+
.preview-loading-progress[data-loading="true"] {
2392+
transform:scaleX(0.9);
2393+
}
2394+
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit be7d35a

Browse files
authored
perf(web): stop preview loading rerenders (#7561)
1 parent 549201f commit be7d35a

6 files changed

Lines changed: 135 additions & 63 deletions

File tree

‎apps/web/src/components/preview/PreviewChromeRow.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ describe("PreviewChromeRow", () => {
99
<PreviewChromeRow
1010
url="https://example.com/dashboard?mode=edit&tab=1#notes"
1111
loading={false}
12-
loadProgress={0}
1312
canGoBack={false}
1413
canGoForward={false}
1514
refreshDisabled={false}

‎apps/web/src/components/preview/PreviewChromeRow.tsx‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { cn } from "~/lib/utils";
2424
interfaceProps{
2525
url: string;
2626
loading: boolean;
27-
loadProgress: number;
2827
canGoBack: boolean;
2928
canGoForward: boolean;
3029
refreshDisabled: boolean;
@@ -65,7 +64,6 @@ const NOOP = () => {};
6564
exportfunctionPreviewChromeRow({
6665
url,
6766
loading,
68-
loadProgress,
6967
canGoBack,
7068
canGoForward,
7169
refreshDisabled,
@@ -308,16 +306,12 @@ export function PreviewChromeRow({
308306
) : null}
309307
{trailingActions}
310308
</form>
311-
{loadProgress>0 ? (
312-
<div
313-
aria-hidden
314-
className="pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 rounded-r-full bg-primary transition-all duration-150 ease-out"
315-
style={{
316-
width: `${loadProgress}%`,
317-
boxShadow: "0 0 6px 1px var(--color-ring)",
318-
}}
319-
/>
320-
) : null}
309+
<div
310+
aria-hidden
311+
data-loading={loading}
312+
className="preview-loading-progress pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 w-full origin-left rounded-r-full bg-primary"
313+
style={{boxShadow: "0 0 6px 1px var(--color-ring)"}}
314+
/>
321315
</div>
322316
);
323317
}

‎apps/web/src/components/preview/PreviewView.test.tsx‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FILL_PREVIEW_VIEWPORT,
66
ThreadId,
77
}from"@t3tools/contracts";
8+
import{act,Profiler}from"react";
89
import{renderToStaticMarkup}from"react-dom/server";
910
import{beforeEach,describe,expect,it,vi}from"vite-plus/test";
1011

@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
3031
toggleAnnotation: nullas(()=>void)|null,
3132
pictureInPicture: false,
3233
showEmptyState: false,
34+
loading: false,
3335
recordVisitForThread: vi.fn(),
3436
}));
3537

@@ -103,7 +105,7 @@ vi.mock("~/previewStateStore", () => ({
103105
hasWebContents: true,
104106
canGoBack: false,
105107
canGoForward: false,
106-
loading: false,
108+
loading: mocks.loading,
107109
zoomFactor: 1,
108110
pictureInPicture: mocks.pictureInPicture,
109111
colorScheme: "system",
@@ -244,7 +246,6 @@ vi.mock("./PreviewUnreachable", () => ({ PreviewUnreachable: () => null }));
244246
vi.mock("./ZoomIndicator",()=>({ZoomIndicator: ()=>null}));
245247
vi.mock("./AgentBrowserCursor",()=>({AgentBrowserCursor: ()=>null}));
246248
vi.mock("~/browser/BrowserSurfaceSlot",()=>({BrowserSurfaceSlot: ()=>null}));
247-
vi.mock("./useLoadingProgress",()=>({useLoadingProgress: ()=>0}));
248249
vi.mock("./usePreviewSession",()=>({usePreviewSession: vi.fn()}));
249250

250251
import{PreviewView}from"./PreviewView";
@@ -256,6 +257,68 @@ const TEST_THREAD_REF = {
256257
}asconst;
257258
constTEST_RUNTIME_TAB_ID=previewRuntimeTabId(TEST_THREAD_REF,null,"tab-1");
258259

260+
// ReactDOM needs a host, but this unit suite intentionally has no DOM dependency.
261+
classTestNode{
262+
parentNode: TestNode|null=null;
263+
childNodes: TestNode[]=[];
264+
readonlynodeName: string;
265+
readonlytagName: string;
266+
readonlynamespaceURI="http://www.w3.org/1999/xhtml";
267+
readonlystyle={};
268+
269+
constructor(
270+
name: string,
271+
readonlyownerDocument: TestNode|null=null,
272+
readonlynodeType=1,
273+
){
274+
this.nodeName=name.toUpperCase();
275+
this.tagName=this.nodeName;
276+
}
277+
278+
settextContent(_value: string){
279+
this.childNodes=[];
280+
}
281+
282+
appendChild(child: TestNode){
283+
child.parentNode=this;
284+
this.childNodes.push(child);
285+
returnchild;
286+
}
287+
288+
removeChild(child: TestNode){
289+
this.childNodes.splice(this.childNodes.indexOf(child),1);
290+
child.parentNode=null;
291+
returnchild;
292+
}
293+
294+
createElement(name: string){
295+
returnnewTestNode(name,this);
296+
}
297+
298+
addEventListener(){}
299+
removeEventListener(){}
300+
setAttribute(){}
301+
}
302+
303+
functioninstallTestDom(){
304+
constdocument=newTestNode("#document",null,9);
305+
constwindow={
306+
document,
307+
HTMLIFrameElement: TestNode,
308+
setInterval: globalThis.setInterval,
309+
clearInterval: globalThis.clearInterval,
310+
setTimeout: globalThis.setTimeout,
311+
clearTimeout: globalThis.clearTimeout,
312+
addEventListener(){},
313+
removeEventListener(){},
314+
};
315+
vi.stubGlobal("document",document);
316+
vi.stubGlobal("window",window);
317+
vi.stubGlobal("HTMLIFrameElement",window.HTMLIFrameElement);
318+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT",true);
319+
returndocument;
320+
}
321+
259322
describe("PreviewView navigation",()=>{
260323
beforeEach(()=>{
261324
mocks.navigate.mockClear();
@@ -279,9 +342,38 @@ describe("PreviewView navigation", () => {
279342
mocks.toggleAnnotation=null;
280343
mocks.pictureInPicture=false;
281344
mocks.showEmptyState=false;
345+
mocks.loading=false;
282346
mocks.recordVisitForThread.mockClear();
283347
});
284348

349+
it("does not rerender while loading time passes",async()=>{
350+
vi.useFakeTimers();
351+
mocks.loading=true;
352+
constdocument=installTestDom();
353+
const{ createRoot }=awaitimport("react-dom/client");
354+
constroot=createRoot(document.createElement("div")asunknownasElement);
355+
constonRender=vi.fn();
356+
357+
try{
358+
awaitact(()=>{
359+
root.render(
360+
<Profilerid="preview"onRender={onRender}>
361+
<PreviewViewthreadRef={TEST_THREAD_REF}tabId="tab-1"visible/>
362+
</Profiler>,
363+
);
364+
});
365+
constinitialRenderCount=onRender.mock.calls.length;
366+
367+
awaitact(()=>vi.advanceTimersByTimeAsync(1_000));
368+
369+
expect(onRender).toHaveBeenCalledTimes(initialRenderCount);
370+
}finally{
371+
awaitact(()=>root.unmount());
372+
vi.useRealTimers();
373+
vi.unstubAllGlobals();
374+
}
375+
});
376+
285377
it.each([
286378
[
287379
"https://localhost:8000/dashboard?mode=test#top",

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import{shouldShowPreviewEmptyState}from"./previewEmptyStateLogic";
5151
import{BrowserSurfaceSlot}from"~/browser/BrowserSurfaceSlot";
5252
import{useBrowserSurfaceStore}from"~/browser/browserSurfaceStore";
53-
import{useLoadingProgress}from"./useLoadingProgress";
5453
import{usePreviewSession}from"./usePreviewSession";
5554
import{ZoomIndicator}from"./ZoomIndicator";
5655
import{AgentBrowserCursor}from"./AgentBrowserCursor";
@@ -142,7 +141,6 @@ export function PreviewView({
142141
constisUnreachable=navStatus._tag==="LoadFailed";
143142
constshowEmptyState=shouldShowPreviewEmptyState(snapshot);
144143
constcontroller=desktopOverlay?.controller??"none";
145-
constloadProgress=useLoadingProgress(loading);
146144
constviewport=snapshot?.viewport??FILL_PREVIEW_VIEWPORT;
147145
constbrowserDefaults=useBrowserDefaults();
148146
constpanelRect=useBrowserSurfaceStore((state)=>
@@ -661,7 +659,6 @@ export function PreviewView({
661659
<PreviewChromeRow
662660
url={url}
663661
loading={loading}
664-
loadProgress={loadProgress}
665662
canGoBack={canGoBack}
666663
canGoForward={canGoForward}
667664
refreshDisabled={refreshDisabled}

‎apps/web/src/components/preview/useLoadingProgress.ts‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,3 +2357,38 @@ code {
23572357
.ultrathink-chroma {
23582358
animation: ultrathink-chroma-shift 10s linear infinite;
23592359
}
2360+
2361+
@keyframes preview-loading-progress {
2362+
from {
2363+
transform:scaleX(0.04);
2364+
}
2365+
2366+
to {
2367+
transform:scaleX(0.9);
2368+
}
2369+
}
2370+
2371+
.preview-loading-progress {
2372+
opacity:0;
2373+
transform:scaleX(1);
2374+
transition:
2375+
transform 150ms ease-out,
2376+
opacity 150ms ease-out 220ms;
2377+
}
2378+
2379+
.preview-loading-progress[data-loading="true"] {
2380+
opacity:1;
2381+
animation: preview-loading-progress 5.3scubic-bezier(0.1,0.5,0.2,1) forwards;
2382+
}
2383+
2384+
@media (prefers-reduced-motion: reduce) {
2385+
.preview-loading-progress,
2386+
.preview-loading-progress[data-loading="true"] {
2387+
transition: none;
2388+
animation: none;
2389+
}
2390+
2391+
.preview-loading-progress[data-loading="true"] {
2392+
transform:scaleX(0.9);
2393+
}
2394+
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit be7d35a

Browse files
authored
perf(web): stop preview loading rerenders (#7561)
1 parent 549201f commit be7d35a

6 files changed

Lines changed: 135 additions & 63 deletions

File tree

‎apps/web/src/components/preview/PreviewChromeRow.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ describe("PreviewChromeRow", () => {
99
<PreviewChromeRow
1010
url="https://example.com/dashboard?mode=edit&tab=1#notes"
1111
loading={false}
12-
loadProgress={0}
1312
canGoBack={false}
1413
canGoForward={false}
1514
refreshDisabled={false}

‎apps/web/src/components/preview/PreviewChromeRow.tsx‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { cn } from "~/lib/utils";
2424
interfaceProps{
2525
url: string;
2626
loading: boolean;
27-
loadProgress: number;
2827
canGoBack: boolean;
2928
canGoForward: boolean;
3029
refreshDisabled: boolean;
@@ -65,7 +64,6 @@ const NOOP = () => {};
6564
exportfunctionPreviewChromeRow({
6665
url,
6766
loading,
68-
loadProgress,
6967
canGoBack,
7068
canGoForward,
7169
refreshDisabled,
@@ -308,16 +306,12 @@ export function PreviewChromeRow({
308306
) : null}
309307
{trailingActions}
310308
</form>
311-
{loadProgress>0 ? (
312-
<div
313-
aria-hidden
314-
className="pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 rounded-r-full bg-primary transition-all duration-150 ease-out"
315-
style={{
316-
width: `${loadProgress}%`,
317-
boxShadow: "0 0 6px 1px var(--color-ring)",
318-
}}
319-
/>
320-
) : null}
309+
<div
310+
aria-hidden
311+
data-loading={loading}
312+
className="preview-loading-progress pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 w-full origin-left rounded-r-full bg-primary"
313+
style={{boxShadow: "0 0 6px 1px var(--color-ring)"}}
314+
/>
321315
</div>
322316
);
323317
}

‎apps/web/src/components/preview/PreviewView.test.tsx‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FILL_PREVIEW_VIEWPORT,
66
ThreadId,
77
}from"@t3tools/contracts";
8+
import{act,Profiler}from"react";
89
import{renderToStaticMarkup}from"react-dom/server";
910
import{beforeEach,describe,expect,it,vi}from"vite-plus/test";
1011

@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
3031
toggleAnnotation: nullas(()=>void)|null,
3132
pictureInPicture: false,
3233
showEmptyState: false,
34+
loading: false,
3335
recordVisitForThread: vi.fn(),
3436
}));
3537

@@ -103,7 +105,7 @@ vi.mock("~/previewStateStore", () => ({
103105
hasWebContents: true,
104106
canGoBack: false,
105107
canGoForward: false,
106-
loading: false,
108+
loading: mocks.loading,
107109
zoomFactor: 1,
108110
pictureInPicture: mocks.pictureInPicture,
109111
colorScheme: "system",
@@ -244,7 +246,6 @@ vi.mock("./PreviewUnreachable", () => ({ PreviewUnreachable: () => null }));
244246
vi.mock("./ZoomIndicator",()=>({ZoomIndicator: ()=>null}));
245247
vi.mock("./AgentBrowserCursor",()=>({AgentBrowserCursor: ()=>null}));
246248
vi.mock("~/browser/BrowserSurfaceSlot",()=>({BrowserSurfaceSlot: ()=>null}));
247-
vi.mock("./useLoadingProgress",()=>({useLoadingProgress: ()=>0}));
248249
vi.mock("./usePreviewSession",()=>({usePreviewSession: vi.fn()}));
249250

250251
import{PreviewView}from"./PreviewView";
@@ -256,6 +257,68 @@ const TEST_THREAD_REF = {
256257
}asconst;
257258
constTEST_RUNTIME_TAB_ID=previewRuntimeTabId(TEST_THREAD_REF,null,"tab-1");
258259

260+
// ReactDOM needs a host, but this unit suite intentionally has no DOM dependency.
261+
classTestNode{
262+
parentNode: TestNode|null=null;
263+
childNodes: TestNode[]=[];
264+
readonlynodeName: string;
265+
readonlytagName: string;
266+
readonlynamespaceURI="http://www.w3.org/1999/xhtml";
267+
readonlystyle={};
268+
269+
constructor(
270+
name: string,
271+
readonlyownerDocument: TestNode|null=null,
272+
readonlynodeType=1,
273+
){
274+
this.nodeName=name.toUpperCase();
275+
this.tagName=this.nodeName;
276+
}
277+
278+
settextContent(_value: string){
279+
this.childNodes=[];
280+
}
281+
282+
appendChild(child: TestNode){
283+
child.parentNode=this;
284+
this.childNodes.push(child);
285+
returnchild;
286+
}
287+
288+
removeChild(child: TestNode){
289+
this.childNodes.splice(this.childNodes.indexOf(child),1);
290+
child.parentNode=null;
291+
returnchild;
292+
}
293+
294+
createElement(name: string){
295+
returnnewTestNode(name,this);
296+
}
297+
298+
addEventListener(){}
299+
removeEventListener(){}
300+
setAttribute(){}
301+
}
302+
303+
functioninstallTestDom(){
304+
constdocument=newTestNode("#document",null,9);
305+
constwindow={
306+
document,
307+
HTMLIFrameElement: TestNode,
308+
setInterval: globalThis.setInterval,
309+
clearInterval: globalThis.clearInterval,
310+
setTimeout: globalThis.setTimeout,
311+
clearTimeout: globalThis.clearTimeout,
312+
addEventListener(){},
313+
removeEventListener(){},
314+
};
315+
vi.stubGlobal("document",document);
316+
vi.stubGlobal("window",window);
317+
vi.stubGlobal("HTMLIFrameElement",window.HTMLIFrameElement);
318+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT",true);
319+
returndocument;
320+
}
321+
259322
describe("PreviewView navigation",()=>{
260323
beforeEach(()=>{
261324
mocks.navigate.mockClear();
@@ -279,9 +342,38 @@ describe("PreviewView navigation", () => {
279342
mocks.toggleAnnotation=null;
280343
mocks.pictureInPicture=false;
281344
mocks.showEmptyState=false;
345+
mocks.loading=false;
282346
mocks.recordVisitForThread.mockClear();
283347
});
284348

349+
it("does not rerender while loading time passes",async()=>{
350+
vi.useFakeTimers();
351+
mocks.loading=true;
352+
constdocument=installTestDom();
353+
const{ createRoot }=awaitimport("react-dom/client");
354+
constroot=createRoot(document.createElement("div")asunknownasElement);
355+
constonRender=vi.fn();
356+
357+
try{
358+
awaitact(()=>{
359+
root.render(
360+
<Profilerid="preview"onRender={onRender}>
361+
<PreviewViewthreadRef={TEST_THREAD_REF}tabId="tab-1"visible/>
362+
</Profiler>,
363+
);
364+
});
365+
constinitialRenderCount=onRender.mock.calls.length;
366+
367+
awaitact(()=>vi.advanceTimersByTimeAsync(1_000));
368+
369+
expect(onRender).toHaveBeenCalledTimes(initialRenderCount);
370+
}finally{
371+
awaitact(()=>root.unmount());
372+
vi.useRealTimers();
373+
vi.unstubAllGlobals();
374+
}
375+
});
376+
285377
it.each([
286378
[
287379
"https://localhost:8000/dashboard?mode=test#top",

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import{shouldShowPreviewEmptyState}from"./previewEmptyStateLogic";
5151
import{BrowserSurfaceSlot}from"~/browser/BrowserSurfaceSlot";
5252
import{useBrowserSurfaceStore}from"~/browser/browserSurfaceStore";
53-
import{useLoadingProgress}from"./useLoadingProgress";
5453
import{usePreviewSession}from"./usePreviewSession";
5554
import{ZoomIndicator}from"./ZoomIndicator";
5655
import{AgentBrowserCursor}from"./AgentBrowserCursor";
@@ -142,7 +141,6 @@ export function PreviewView({
142141
constisUnreachable=navStatus._tag==="LoadFailed";
143142
constshowEmptyState=shouldShowPreviewEmptyState(snapshot);
144143
constcontroller=desktopOverlay?.controller??"none";
145-
constloadProgress=useLoadingProgress(loading);
146144
constviewport=snapshot?.viewport??FILL_PREVIEW_VIEWPORT;
147145
constbrowserDefaults=useBrowserDefaults();
148146
constpanelRect=useBrowserSurfaceStore((state)=>
@@ -661,7 +659,6 @@ export function PreviewView({
661659
<PreviewChromeRow
662660
url={url}
663661
loading={loading}
664-
loadProgress={loadProgress}
665662
canGoBack={canGoBack}
666663
canGoForward={canGoForward}
667664
refreshDisabled={refreshDisabled}

‎apps/web/src/components/preview/useLoadingProgress.ts‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,3 +2357,38 @@ code {
23572357
.ultrathink-chroma {
23582358
animation: ultrathink-chroma-shift 10s linear infinite;
23592359
}
2360+
2361+
@keyframes preview-loading-progress {
2362+
from {
2363+
transform:scaleX(0.04);
2364+
}
2365+
2366+
to {
2367+
transform:scaleX(0.9);
2368+
}
2369+
}
2370+
2371+
.preview-loading-progress {
2372+
opacity:0;
2373+
transform:scaleX(1);
2374+
transition:
2375+
transform 150ms ease-out,
2376+
opacity 150ms ease-out 220ms;
2377+
}
2378+
2379+
.preview-loading-progress[data-loading="true"] {
2380+
opacity:1;
2381+
animation: preview-loading-progress 5.3scubic-bezier(0.1,0.5,0.2,1) forwards;
2382+
}
2383+
2384+
@media (prefers-reduced-motion: reduce) {
2385+
.preview-loading-progress,
2386+
.preview-loading-progress[data-loading="true"] {
2387+
transition: none;
2388+
animation: none;
2389+
}
2390+
2391+
.preview-loading-progress[data-loading="true"] {
2392+
transform:scaleX(0.9);
2393+
}
2394+
}

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit be7d35a

Browse files
authored
perf(web): stop preview loading rerenders (#7561)
1 parent 549201f commit be7d35a

6 files changed

Lines changed: 135 additions & 63 deletions

File tree

‎apps/web/src/components/preview/PreviewChromeRow.test.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ describe("PreviewChromeRow", () => {
99
<PreviewChromeRow
1010
url="https://example.com/dashboard?mode=edit&tab=1#notes"
1111
loading={false}
12-
loadProgress={0}
1312
canGoBack={false}
1413
canGoForward={false}
1514
refreshDisabled={false}

‎apps/web/src/components/preview/PreviewChromeRow.tsx‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { cn } from "~/lib/utils";
2424
interfaceProps{
2525
url: string;
2626
loading: boolean;
27-
loadProgress: number;
2827
canGoBack: boolean;
2928
canGoForward: boolean;
3029
refreshDisabled: boolean;
@@ -65,7 +64,6 @@ const NOOP = () => {};
6564
exportfunctionPreviewChromeRow({
6665
url,
6766
loading,
68-
loadProgress,
6967
canGoBack,
7068
canGoForward,
7169
refreshDisabled,
@@ -308,16 +306,12 @@ export function PreviewChromeRow({
308306
) : null}
309307
{trailingActions}
310308
</form>
311-
{loadProgress>0 ? (
312-
<div
313-
aria-hidden
314-
className="pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 rounded-r-full bg-primary transition-all duration-150 ease-out"
315-
style={{
316-
width: `${loadProgress}%`,
317-
boxShadow: "0 0 6px 1px var(--color-ring)",
318-
}}
319-
/>
320-
) : null}
309+
<div
310+
aria-hidden
311+
data-loading={loading}
312+
className="preview-loading-progress pointer-events-none absolute bottom-0 left-0 z-10 h-0.5 w-full origin-left rounded-r-full bg-primary"
313+
style={{boxShadow: "0 0 6px 1px var(--color-ring)"}}
314+
/>
321315
</div>
322316
);
323317
}

‎apps/web/src/components/preview/PreviewView.test.tsx‎

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FILL_PREVIEW_VIEWPORT,
66
ThreadId,
77
}from"@t3tools/contracts";
8+
import{act,Profiler}from"react";
89
import{renderToStaticMarkup}from"react-dom/server";
910
import{beforeEach,describe,expect,it,vi}from"vite-plus/test";
1011

@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
3031
toggleAnnotation: nullas(()=>void)|null,
3132
pictureInPicture: false,
3233
showEmptyState: false,
34+
loading: false,
3335
recordVisitForThread: vi.fn(),
3436
}));
3537

@@ -103,7 +105,7 @@ vi.mock("~/previewStateStore", () => ({
103105
hasWebContents: true,
104106
canGoBack: false,
105107
canGoForward: false,
106-
loading: false,
108+
loading: mocks.loading,
107109
zoomFactor: 1,
108110
pictureInPicture: mocks.pictureInPicture,
109111
colorScheme: "system",
@@ -244,7 +246,6 @@ vi.mock("./PreviewUnreachable", () => ({ PreviewUnreachable: () => null }));
244246
vi.mock("./ZoomIndicator",()=>({ZoomIndicator: ()=>null}));
245247
vi.mock("./AgentBrowserCursor",()=>({AgentBrowserCursor: ()=>null}));
246248
vi.mock("~/browser/BrowserSurfaceSlot",()=>({BrowserSurfaceSlot: ()=>null}));
247-
vi.mock("./useLoadingProgress",()=>({useLoadingProgress: ()=>0}));
248249
vi.mock("./usePreviewSession",()=>({usePreviewSession: vi.fn()}));
249250

250251
import{PreviewView}from"./PreviewView";
@@ -256,6 +257,68 @@ const TEST_THREAD_REF = {
256257
}asconst;
257258
constTEST_RUNTIME_TAB_ID=previewRuntimeTabId(TEST_THREAD_REF,null,"tab-1");
258259

260+
// ReactDOM needs a host, but this unit suite intentionally has no DOM dependency.
261+
classTestNode{
262+
parentNode: TestNode|null=null;
263+
childNodes: TestNode[]=[];
264+
readonlynodeName: string;
265+
readonlytagName: string;
266+
readonlynamespaceURI="http://www.w3.org/1999/xhtml";
267+
readonlystyle={};
268+
269+
constructor(
270+
name: string,
271+
readonlyownerDocument: TestNode|null=null,
272+
readonlynodeType=1,
273+
){
274+
this.nodeName=name.toUpperCase();
275+
this.tagName=this.nodeName;
276+
}
277+
278+
settextContent(_value: string){
279+
this.childNodes=[];
280+
}
281+
282+
appendChild(child: TestNode){
283+
child.parentNode=this;
284+
this.childNodes.push(child);
285+
returnchild;
286+
}
287+
288+
removeChild(child: TestNode){
289+
this.childNodes.splice(this.childNodes.indexOf(child),1);
290+
child.parentNode=null;
291+
returnchild;
292+
}
293+
294+
createElement(name: string){
295+
returnnewTestNode(name,this);
296+
}
297+
298+
addEventListener(){}
299+
removeEventListener(){}
300+
setAttribute(){}
301+
}
302+
303+
functioninstallTestDom(){
304+
constdocument=newTestNode("#document",null,9);
305+
constwindow={
306+
document,
307+
HTMLIFrameElement: TestNode,
308+
setInterval: globalThis.setInterval,
309+
clearInterval: globalThis.clearInterval,
310+
setTimeout: globalThis.setTimeout,
311+
clearTimeout: globalThis.clearTimeout,
312+
addEventListener(){},
313+
removeEventListener(){},
314+
};
315+
vi.stubGlobal("document",document);
316+
vi.stubGlobal("window",window);
317+
vi.stubGlobal("HTMLIFrameElement",window.HTMLIFrameElement);
318+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT",true);
319+
returndocument;
320+
}
321+
259322
describe("PreviewView navigation",()=>{
260323
beforeEach(()=>{
261324
mocks.navigate.mockClear();
@@ -279,9 +342,38 @@ describe("PreviewView navigation", () => {
279342
mocks.toggleAnnotation=null;
280343
mocks.pictureInPicture=false;
281344
mocks.showEmptyState=false;
345+
mocks.loading=false;
282346
mocks.recordVisitForThread.mockClear();
283347
});
284348

349+
it("does not rerender while loading time passes",async()=>{
350+
vi.useFakeTimers();
351+
mocks.loading=true;
352+
constdocument=installTestDom();
353+
const{ createRoot }=awaitimport("react-dom/client");
354+
constroot=createRoot(document.createElement("div")asunknownasElement);
355+
constonRender=vi.fn();
356+
357+
try{
358+
awaitact(()=>{
359+
root.render(
360+
<Profilerid="preview"onRender={onRender}>
361+
<PreviewViewthreadRef={TEST_THREAD_REF}tabId="tab-1"visible/>
362+
</Profiler>,
363+
);
364+
});
365+
constinitialRenderCount=onRender.mock.calls.length;
366+
367+
awaitact(()=>vi.advanceTimersByTimeAsync(1_000));
368+
369+
expect(onRender).toHaveBeenCalledTimes(initialRenderCount);
370+
}finally{
371+
awaitact(()=>root.unmount());
372+
vi.useRealTimers();
373+
vi.unstubAllGlobals();
374+
}
375+
});
376+
285377
it.each([
286378
[
287379
"https://localhost:8000/dashboard?mode=test#top",

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { revealInFileExplorerLabel } from "./fileExplorerLabel";
5050
import{shouldShowPreviewEmptyState}from"./previewEmptyStateLogic";
5151
import{BrowserSurfaceSlot}from"~/browser/BrowserSurfaceSlot";
5252
import{useBrowserSurfaceStore}from"~/browser/browserSurfaceStore";
53-
import{useLoadingProgress}from"./useLoadingProgress";
5453
import{usePreviewSession}from"./usePreviewSession";
5554
import{ZoomIndicator}from"./ZoomIndicator";
5655
import{AgentBrowserCursor}from"./AgentBrowserCursor";
@@ -142,7 +141,6 @@ export function PreviewView({
142141
constisUnreachable=navStatus._tag==="LoadFailed";
143142
constshowEmptyState=shouldShowPreviewEmptyState(snapshot);
144143
constcontroller=desktopOverlay?.controller??"none";
145-
constloadProgress=useLoadingProgress(loading);
146144
constviewport=snapshot?.viewport??FILL_PREVIEW_VIEWPORT;
147145
constbrowserDefaults=useBrowserDefaults();
148146
constpanelRect=useBrowserSurfaceStore((state)=>
@@ -661,7 +659,6 @@ export function PreviewView({
661659
<PreviewChromeRow
662660
url={url}
663661
loading={loading}
664-
loadProgress={loadProgress}
665662
canGoBack={canGoBack}
666663
canGoForward={canGoForward}
667664
refreshDisabled={refreshDisabled}

‎apps/web/src/components/preview/useLoadingProgress.ts‎

Lines changed: 0 additions & 45 deletions
This file was deleted.

‎apps/web/src/index.css‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,3 +2357,38 @@ code {
23572357
.ultrathink-chroma {
23582358
animation: ultrathink-chroma-shift 10s linear infinite;
23592359
}
2360+
2361+
@keyframes preview-loading-progress {
2362+
from {
2363+
transform:scaleX(0.04);
2364+
}
2365+
2366+
to {
2367+
transform:scaleX(0.9);
2368+
}
2369+
}
2370+
2371+
.preview-loading-progress {
2372+
opacity:0;
2373+
transform:scaleX(1);
2374+
transition:
2375+
transform 150ms ease-out,
2376+
opacity 150ms ease-out 220ms;
2377+
}
2378+
2379+
.preview-loading-progress[data-loading="true"] {
2380+
opacity:1;
2381+
animation: preview-loading-progress 5.3scubic-bezier(0.1,0.5,0.2,1) forwards;
2382+
}
2383+
2384+
@media (prefers-reduced-motion: reduce) {
2385+
.preview-loading-progress,
2386+
.preview-loading-progress[data-loading="true"] {
2387+
transition: none;
2388+
animation: none;
2389+
}
2390+
2391+
.preview-loading-progress[data-loading="true"] {
2392+
transform:scaleX(0.9);
2393+
}
2394+
}

0 commit comments

Comments
 (0)