Research tree UI:
- The level pips shared the header row with the upgrade name and fought
for width; long names like "Festungsmauern" (5 pips) pushed the pips
out of the upgrade card. Pips now sit in the bottom row next to the
buy button (space-between), where width is plentiful, and carry a
"level / max" tooltip
- Tighter modal padding on phones (max-width 560px), scrollable at 92vh
PWA (installable as app in mobile browsers):
- public/manifest.webmanifest: standalone display, any orientation,
TRXTD theme colors, German lang, start_url "/"
- public/sw.js: network-first service worker with runtime caching.
index.html is always fetched fresh so new deploys are picked up
immediately; the cache only serves as offline fallback. API calls
and cross-origin requests are never intercepted
- Icon set generated by scripts/generate-icons.mjs (pure Node PNG
encoder, zero image dependencies): gold tower on the game-themed
dark gradient in 192px, 512px, maskable 512px (motif inside the safe
zone) and 180px apple-touch-icon
- index.html: manifest link, favicon, apple-touch-icon, theme-color,
mobile-web-app-capable, apple-mobile-web-app meta tags
- src/main.ts: service worker registration in production builds only
(dev HMR stays untouched)
- server.mjs: serve .webmanifest as application/manifest+json (Chrome
requires the correct MIME type for installability)
Verified against the production server: manifest (application/
manifest+json), icons (image/png) and sw.js (text/javascript) respond
with 200, index.html references all PWA tags; npm test 48/48 green.
- Add CSP, frame and referrer headers for served static files
- Enforce WebSocket origin check to prevent cross-site hijacking
- Trust X-Forwarded-For only when the peer is from a private proxy network
- Limit concurrent connections (500 total / 20 per IP) and rooms (300)
- Add ALLOWED_ORIGINS env var for additional WebSocket origins
- Document reverse proxy setup (NPM/NPMplus) in README
- Add scripts/security-test.mjs to verify origin and limit behavior