feat: implement startup splash screen and loading behavior - #1974
feat: implement startup splash screen and loading behavior#1974ChrisTouo wants to merge 3 commits into
Conversation
johan-bell
left a comment
There was a problem hiding this comment.
Verified this hands-on — the core is excellent: the pre-paint theme script exactly mirrors globalConfig's resolution (dark / light / else system + matchMedia), /logo.svg exists in public/ so the root-absolute path works at any route depth, the strip in rewriteWebEntry is correct, and bootSplash.spec.ts tying the three drift-prone pieces together is a really nice touch. Three things before it can go in:
- Error path:
Startup().catchcallsmarkAppError()but never removes the splash, and the splash has no error state — a failed boot now shows an infinitely animating progress bar. The catch should swap the splash to an error message (or at least remove it). #1972 solves this with adata-render-state="error"panel + Reload button worth borrowing. ?nosplashregression:isAppLoadinghonours it but the static splash doesn't, so consumers of the opt-out now get a splash that stays up until startup finishes. A one-line guard in the pre-paint script fixes it.- Collision with #1972: that PR also injects a static boot splash (inside
#app, via a Vite plugin, with error/reload, nosplash and prefers-reduced-motion handling). Both cannot merge as-is — please converge on one implementation with Dirk. This PR's placement (outside#app, removed after full startup with the nextTick trick) covers the boot more completely; #1972's error panel and opt-outs are the missing half.
Minor: the hardcoded English "Loading..." is new user-visible text (the old splash was logo-only), and deployments configuring VITE_LOGO will show the default logo during boot.
fa0d1fe to
85782b6
Compare
Both this branch and #1974 add a static boot splash, so only one can land. #1974 keeps the splash and has absorbed this branch's error panel, ?nosplash guard and reduced-motion handling; this branch keeps the recovery work the splash reports through — the bounded database opens, the initLanguage timeout and the boot watchdog. The placement assertion goes with it: #1974 renders the splash as a sibling after #app rather than inside it, so that test can no longer hold here. The remaining cases assert the ids, classes and data-render-state contract #1974 preserved, and need its splash deployed to run green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5fbe93d to
5ed5d71
Compare
|
Remove the "Loading..." text and make sure the graphics is intuitive enough to indicate to the user that the app is loading |
|
Check why timers are used as guardrails. There must be a better way to detect when indexedDb is unblocked |
…ng and timeout removal
|
Why the change in database.ts A deploy that changes the docs index bumps the IndexedDB version. IndexedDB can't apply that upgrade while other tabs still hold the old version open, so the browser fires versionchange on those tabs and blocks the upgrading one. Dexie already handles this by closing the old connection so the upgrade can proceed, and we keep that — holding on would block the other tab indefinitely. What we add is what happens next: a background tab just closes and reopens lazily, but a visible tab is running code built for the old schema, so it reloads onto the new version instead of carrying on against a database it can no longer use. Separately, blocked is no longer treated as an error. It means "waiting for another connection to close," which resolves on its own, so the boot waits instead of failing. |
The splash lived in App.vue, so it could not appear until
app.mount()— thesecond-to-last step of Startup(). Everything slow (Dexie open, schema upgrades,
OIDC setup) ran before that against an empty #app, and the only work left after
it was
initLanguage(), a local IndexedDB read on a warm cache. That can resolvewithin a single animation frame, so the splash often never painted at all, while
the part of the boot that actually takes time showed a blank page.
Mounting earlier is not an option.
app.use(router)starts the router's initialnavigation, and isNotAuthenticatedGuard gates on
isAuthPluginInstalled, whichonly setupAuth() sets — running the guard first makes a logged-in user look
logged out on a cold load.
So the splash moves out of Vue. index.html carries it as plain HTML/CSS and
paints it on first parse, with a pre-paint script that resolves the theme the way
globalConfig does (darkMode is "class", so nothing sets that class until the
first module runs). It uses /logo.svg from public/ rather than VITE_LOGO, whose
configured value is page-relative and 404s below the root. main.ts removes it
once startup finishes, after a nextTick so the app is already painted underneath
and uncovering cannot expose a blank frame.
The web/SSG build strips it in the existing rewriteWebEntry transform: that build
ships already-rendered HTML with no blank window to cover, and it boots
main.web.ts instead of main.ts, so nothing there would ever remove the overlay.
With index.html owning the splash, App.vue's copy sat under an opaque overlay for
every frame it existed, so it is removed;
isAppLoadingstill gates the appcontent, the KeepAlive and the modals.