Skip to content

Commit a6f17df

Browse files
authored
Merge pull request #1276 from RicoUHD/white-screen-of-death
Resolve WSOD by restructuring asset management and cache strategies
2 parents 5dcb4ab + 365d1d5 commit a6f17df

13 files changed

Lines changed: 164 additions & 44 deletions

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ app/static/css/main.css
4646
app/static/js/sortable.min.js
4747
app/static/js/tiny-mde.min.js
4848
app/static/css/tiny-mde.min.css
49+
app/static/js/vendor/
50+
app/static/css/vendor/
4951

5052
# Lock files generated by CI before container build
5153

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
# compiled tailwindcss file
77
app/static/css/main.css
88

9+
# Portable development tools
10+
.tools/
11+
12+
# Compiled static vendor assets
13+
app/static/js/vendor/
14+
app/static/css/vendor/
15+
916
CLAUDE.md
1017
.python-version
1118

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
4141
RUN uv run --frozen --no-dev pybabel compile --use-fuzzy -d app/translations
4242

4343
# Ensure static directories exist and build static assets
44-
RUN mkdir -p app/static/js app/static/css && npm --prefix app/static/ run build
44+
RUN mkdir -p app/static/js app/static/css && DOCKER_BUILD=true npm --prefix app/static/ run build
4545

4646
# ─── Stage 3: Runtime ─────────────────────────────────────────────────────
4747
FROM ghcr.io/astral-sh/uv:python3.13-alpine

app/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ def create_app(config_object=DevelopmentConfig):
6868
# Step 5: Setup context processors and filters
6969
if show_startup:
7070
logger.step("Configuring request processing", "⚙️")
71-
from .context_processors import inject_plus_features, inject_server_name
71+
from .context_processors import inject_plus_features, inject_server_name, inject_app_version
7272

7373
app.context_processor(inject_server_name)
7474
app.context_processor(inject_plus_features)
75+
app.context_processor(inject_app_version)
7576
register_error_handlers(app)
7677

7778
# Register custom Jinja filters

app/context_processors.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
from app.extensions import db
23
from app.models import Settings
34

@@ -30,3 +31,8 @@ def inject_plus_features():
3031
is_plus_enabled = False
3132

3233
return {"is_plus_enabled": is_plus_enabled}
34+
35+
36+
def inject_app_version():
37+
"""Inject current app version into template context for cache busting."""
38+
return {"app_version": os.getenv("APP_VERSION", "dev")}

app/static/copy-assets.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const assets = [
5+
// Original assets
6+
{ src: 'node_modules/sortablejs/Sortable.min.js', dest: 'js/sortable.min.js' },
7+
{ src: 'node_modules/tiny-markdown-editor/dist/tiny-mde.min.js', dest: 'js/tiny-mde.min.js' },
8+
{ src: 'node_modules/tiny-markdown-editor/dist/tiny-mde.min.css', dest: 'css/tiny-mde.min.css' },
9+
10+
// Safe Vendor assets (to fix the White Screen of Death / WAF node_modules blocks)
11+
{ src: 'node_modules/alpinejs/dist/cdn.min.js', dest: 'js/vendor/alpine.min.js' },
12+
{ src: 'node_modules/@alpinejs/collapse/dist/cdn.min.js', dest: 'js/vendor/alpine-collapse.min.js' },
13+
{ src: 'node_modules/flowbite/dist/flowbite.min.js', dest: 'js/vendor/flowbite.min.js' },
14+
{ src: 'node_modules/htmx.org/dist/htmx.min.js', dest: 'js/vendor/htmx.min.js' },
15+
{ src: 'node_modules/htmx-ext-preload/dist/preload.min.js', dest: 'js/vendor/htmx-preload.min.js' },
16+
{ src: 'node_modules/animate.css/animate.min.css', dest: 'css/vendor/animate.min.css' },
17+
{ src: 'node_modules/bowser/bundled.js', dest: 'js/vendor/bowser.min.js' },
18+
{ src: 'node_modules/inapp-spy/dist/index.global.js', dest: 'js/vendor/inapp-spy.min.js' }
19+
];
20+
21+
const isProduction = process.env.NODE_ENV === 'production' || process.env.DOCKER_BUILD === 'true';
22+
23+
console.log('📦 Starting cross-platform asset copy...');
24+
25+
assets.forEach(asset => {
26+
const srcPath = path.resolve(__dirname, asset.src);
27+
const destPath = path.resolve(__dirname, asset.dest);
28+
29+
if (!fs.existsSync(srcPath)) {
30+
if (isProduction) {
31+
console.error(`❌ CRITICAL: Source file missing for production build: ${asset.src}`);
32+
process.exit(1); // Fails the build pipeline safely
33+
} else {
34+
console.warn(`⚠️ Warning: Source file not found: ${asset.src}. Skipping in dev mode.`);
35+
return;
36+
}
37+
}
38+
39+
// Ensure destination directory exists
40+
const destDir = path.dirname(destPath);
41+
if (!fs.existsSync(destDir)) {
42+
fs.mkdirSync(destDir, { recursive: true });
43+
}
44+
45+
try {
46+
fs.copyFileSync(srcPath, destPath);
47+
console.log(`✓ Copied ${asset.src} -> ${asset.dest}`);
48+
} catch (err) {
49+
console.error(`❌ Failed to copy ${asset.src}:`, err);
50+
process.exit(1);
51+
}
52+
});
53+
54+
console.log('✨ All assets copied successfully!');

app/static/js/in-app-escape.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
window.wizarrInAppEscapeLoaded = true;
1313

1414
// Import dependencies from node_modules
15-
const Bowser = window.Bowser;
15+
const Bowser = window.Bowser || window.bowser;
1616
const InAppSpy = window.InAppSpy;
1717

1818
if (!Bowser || !InAppSpy) {

app/static/js/pwa-registration.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,25 @@
1111
// Service Worker Registration
1212
if ('serviceWorker' in navigator) {
1313
window.addEventListener('load', function() {
14-
navigator.serviceWorker.register('/static/sw.js')
14+
// updateViaCache: 'none' ensures the browser always fetches sw.js from the
15+
// server instead of the HTTP cache, so service worker updates are detected
16+
// immediately after deployment.
17+
navigator.serviceWorker.register('/static/sw.js', { updateViaCache: 'none' })
1518
.then(function(registration) {
1619
console.log('ServiceWorker registration successful with scope: ', registration.scope);
20+
21+
// Check for service worker updates every hour
22+
setInterval(function() { registration.update(); }, 60 * 60 * 1000);
23+
24+
// Listen for a new service worker becoming available
25+
registration.addEventListener('updatefound', function() {
26+
var newWorker = registration.installing;
27+
newWorker.addEventListener('statechange', function() {
28+
if (newWorker.state === 'activated') {
29+
console.log('New Wizarr version activated');
30+
}
31+
});
32+
});
1733
})
1834
.catch(function(err) {
1935
console.log('ServiceWorker registration failed: ', err);

app/static/js/tiny-mde.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/static/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"scripts": {
66
"build:css": "npx @tailwindcss/cli -i src/style.css -o css/main.css",
77
"watch:css": "npx @tailwindcss/cli -i src/style.css -o css/main.css --watch",
8-
"build:js": "cp node_modules/sortablejs/Sortable.min.js js/sortable.min.js && cp node_modules/tiny-markdown-editor/dist/tiny-mde.min.js js/tiny-mde.min.js && cp node_modules/tiny-markdown-editor/dist/tiny-mde.min.css css/tiny-mde.min.css",
8+
"build:js": "node copy-assets.js",
99
"build": "npm run build:css && npm run build:js"
1010
},
1111
"author": "",

0 commit comments

Comments
 (0)