Skip to content

Commit a715c2d

Browse files
committed
fix: dismiss startup splash after first paint
1 parent 1b228d7 commit a715c2d

4 files changed

Lines changed: 25 additions & 22 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"lovcode": patch
3+
---
4+
5+
启动遮罩改为在 React 首帧后关闭,不再等待历史记录读取完成。

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
<body>
8989
<div id="splash">
9090
<img class="logo" src="/logo.svg" alt="" />
91-
<div class="label">Reading chat history</div>
91+
<div class="label">Opening Lovcode</div>
9292
<div class="bar" role="progressbar" aria-label="Loading"></div>
9393
</div>
9494
<script>

src/main.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,21 @@ const persister = createSyncStoragePersister({
5151
key: "lovcode:rq-cache",
5252
});
5353

54+
const notifyAppReady = () => window.dispatchEvent(new Event("app:ready"));
55+
56+
// Splash is a first-paint guard only. Chat history, sessions, and search index
57+
// work should render progressive loading states inside the app shell.
58+
window.addEventListener("app:ready", () => {
59+
const splash = document.getElementById("splash");
60+
if (!splash) return;
61+
splash.classList.add("fade");
62+
splash.addEventListener("transitionend", () => splash.remove(), { once: true });
63+
}, { once: true });
64+
65+
// Safety net: if React render is delayed or a route never reports readiness,
66+
// drop the splash quickly so startup cannot get stuck behind data loading.
67+
setTimeout(notifyAppReady, 1500);
68+
5469
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
5570
<I18nProvider>
5671
<PersistQueryClientProvider
@@ -81,16 +96,4 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
8196
</I18nProvider>,
8297
);
8398

84-
// Splash fade-out is triggered explicitly by the first layout that mounts
85-
// (RootLayout / standalone pages dispatch "app:ready"). This avoids the
86-
// flash of "Loading app…" between the splash and the real shell.
87-
window.addEventListener("app:ready", () => {
88-
const splash = document.getElementById("splash");
89-
if (!splash) return;
90-
splash.classList.add("fade");
91-
splash.addEventListener("transitionend", () => splash.remove(), { once: true });
92-
}, { once: true });
93-
94-
// Safety net: if no layout reports ready within 8s (e.g. unexpected error
95-
// boundary), drop the splash anyway so the user isn't stuck staring at it.
96-
setTimeout(() => window.dispatchEvent(new Event("app:ready")), 8000);
99+
requestAnimationFrame(() => requestAnimationFrame(notifyAppReady));

src/pages/_layout.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,9 @@ export default function RootLayout() {
105105
} catch {}
106106
}, []);
107107

108-
// Splash dismissal:
109-
// - On /workbench (and "/" which redirects via HomePage), the workbench
110-
// controls the splash — wait until the session list is
111-
// actually ready, no jarring "empty shell" gap.
112-
// - On every other route, RootLayout dismisses immediately — those
113-
// pages don't have a multi-second initial query.
114-
// The lastPath resume target may be anything, so we also skip dispatch
115-
// on "/" because HomePage will redirect within a microtask.
108+
// Non-workbench routes can still report readiness explicitly. The global
109+
// splash is dismissed after React's first paint, so workbench data loading is
110+
// shown inside the app shell instead of blocking startup.
116111
useEffect(() => {
117112
const p = location.pathname;
118113
if (p === "/" || p.startsWith("/workbench")) return;

0 commit comments

Comments
 (0)