Fix native white screen: relative asset base + boot crash guard
Two changes targeting the post-update white screen on the Zapstore APK: 1. vite.config: set base './' when CAPACITOR_BUILD is set. In a native WebView the bundle is served from the local filesystem, so absolute '/' asset paths are the classic cause of a silent white screen. The web deploy keeps '/'. This is the documented Capacitor requirement and is zero-risk to the working web build. 2. index.html: add a dependency-free "boot crash guard" that runs before the app bundle and paints any uncaught error / unhandled rejection — plus a 12s "did not render" timeout — directly to the screen using inline styles only. A boot failure that would otherwise be an info-less white screen now shows the actual error ON DEVICE, with no USB/remote-debugging needed. main.tsx signals a clean boot to cancel the timeout guard. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
9a8609354e
commit
5334144d27
3 changed files with 65 additions and 0 deletions
52
index.html
52
index.html
|
|
@ -46,6 +46,58 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
BOOT CRASH GUARD (native-safe).
|
||||||
|
Runs before the app bundle and paints any uncaught boot error directly to
|
||||||
|
the screen using inline styles only — so a failure that would otherwise be
|
||||||
|
a silent white screen (bundle load error, module-level throw, missing CSS)
|
||||||
|
becomes a readable message on device, with no cable/remote-debug needed.
|
||||||
|
If the app boots normally it clears itself and costs nothing.
|
||||||
|
-->
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var shown = false;
|
||||||
|
function paint(kind, err) {
|
||||||
|
if (shown) return;
|
||||||
|
shown = true;
|
||||||
|
try {
|
||||||
|
var msg = (err && (err.stack || err.message)) ? (err.stack || err.message) : String(err);
|
||||||
|
var el = document.createElement('div');
|
||||||
|
el.setAttribute('style', [
|
||||||
|
'position:fixed', 'inset:0', 'z-index:2147483647',
|
||||||
|
'background:#000', 'color:#fff', 'padding:20px',
|
||||||
|
'font:13px/1.5 -apple-system,system-ui,monospace',
|
||||||
|
'overflow:auto', '-webkit-overflow-scrolling:touch'
|
||||||
|
].join(';'));
|
||||||
|
el.innerHTML =
|
||||||
|
'<div style="color:#ff0040;font-weight:700;font-size:16px;margin-bottom:8px">SkateSpots failed to start</div>' +
|
||||||
|
'<div style="color:#9ca3af;margin-bottom:12px">' + kind + '</div>' +
|
||||||
|
'<pre style="white-space:pre-wrap;word-break:break-word;color:#f3f4f6;margin:0">' +
|
||||||
|
(msg ? msg.replace(/[<>&]/g, function (c) { return { '<': '<', '>': '>', '&': '&' }[c]; }) : '(no message)') +
|
||||||
|
'</pre>' +
|
||||||
|
'<button onclick="location.reload()" style="margin-top:16px;padding:10px 16px;background:#ff0040;color:#000;border:0;border-radius:8px;font-weight:700">Reload</button>';
|
||||||
|
document.body.appendChild(el);
|
||||||
|
} catch (e) { /* last resort: nothing we can do */ }
|
||||||
|
}
|
||||||
|
window.addEventListener('error', function (e) {
|
||||||
|
paint('Uncaught error', e.error || e.message);
|
||||||
|
});
|
||||||
|
window.addEventListener('unhandledrejection', function (e) {
|
||||||
|
paint('Unhandled promise rejection', e.reason);
|
||||||
|
});
|
||||||
|
// If nothing has rendered into #root after 12s, surface a hint.
|
||||||
|
setTimeout(function () {
|
||||||
|
var root = document.getElementById('root');
|
||||||
|
if (root && root.childElementCount === 0) {
|
||||||
|
paint('App did not render (timeout)', 'The bundle loaded but nothing mounted within 12s. This usually means a boot-time exception was swallowed.');
|
||||||
|
}
|
||||||
|
}, 12000);
|
||||||
|
// Let the app signal a clean boot to cancel the timeout guard.
|
||||||
|
window.__SKATESPOTS_BOOTED__ = function () { shown = true; };
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -81,3 +81,11 @@ createRoot(document.getElementById("root")!).render(
|
||||||
<App />
|
<App />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Tell the boot crash guard (in index.html) that the app mounted successfully,
|
||||||
|
// so its "did not render" timeout doesn't fire.
|
||||||
|
try {
|
||||||
|
(window as unknown as { __SKATESPOTS_BOOTED__?: () => void }).__SKATESPOTS_BOOTED__?.();
|
||||||
|
} catch {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(() => ({
|
export default defineConfig(() => ({
|
||||||
|
// In a native Capacitor WebView the bundle is served from the local
|
||||||
|
// filesystem, so assets must be referenced with RELATIVE paths ('./…').
|
||||||
|
// Absolute '/…' paths are the classic cause of a white screen on device.
|
||||||
|
// The regular web deploy keeps '/' (served from a domain root).
|
||||||
|
base: process.env.CAPACITOR_BUILD ? './' : '/',
|
||||||
server: {
|
server: {
|
||||||
host: "::",
|
host: "::",
|
||||||
port: 8080,
|
port: 8080,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue