fix architectural issues: SPA nav, error handling, CSS bugs, perf

- responsive.js: fix scroll race condition (scroll after innerHTML),
  add error handling for fetch failures, fix implicit global `eid`,
  dispatch `beforenavigate` event for cleanup hooks
- chessbed.js: fix implicit global `ratings` variable
- status.js: clear polling interval on SPA navigation via
  `beforenavigate` event to prevent leak
- App.css: add font-display:swap to all @font-face, fix broken
  media query (missing px unit), consolidate duplicate selectors
  (.concentratedHead, .relative, strong), fix hardcoded bookshelf
  background-image path to use relative URL
- header.html: defer chessbed.js, use p5.min.js instead of p5.js
- monitor.py: use ThreadPoolExecutor for concurrent service checks
- config.py: fix __import__('envs.py') → __import__('envs')
- app.py: rename misleading error handlers (page404→handle_http_error,
  page500→handle_generic_error), fix error info leakage by not passing
  raw exception to InternalServerError, fix hardcoded canonical "404"

https://claude.ai/code/session_01FUhPqQLahEoL6FMxhXkDKa
This commit is contained in:
Claude
2026-02-12 14:39:43 +00:00
parent 9b6e29a15c
commit c8b1f124f2
8 changed files with 77 additions and 55 deletions

View File

@@ -1,23 +1,27 @@
@font-face {
font-family: "neon-future";
src: url("../fonts/NeonFuture.ttf")
src: url("../fonts/NeonFuture.ttf");
font-display: swap;
}
@font-face {
font-family: "shuttlex";
src: url("../fonts/SHUTTLE-X.ttf");
font-display: swap;
}
@font-face {
font-family: "sunset-club";
src: url("../fonts/SunsetClub.otf")
src: url("../fonts/SunsetClub.otf");
font-display: swap;
}
@font-face {
font-family: "robotoreg";
src: url("../fonts/RobotoCondensed-Regular.ttf")
src: url("../fonts/RobotoCondensed-Regular.ttf");
font-display: swap;
}
html, body {
@@ -117,10 +121,6 @@ li {
color: #a8a8a8;
}
strong {
color: #ecebeb;
}
p, li, span {
color: rgb(212, 212, 212);
font-size: 1rem;
@@ -289,6 +289,9 @@ tr {
padding-right: 4rem;
border-bottom: #0f0f0f solid 5px;
color: white;
position: relative;
padding-bottom: 0.5em;
margin-bottom: 1em;
}
.foreground {
@@ -313,12 +316,6 @@ tr {
line-height: 1.5em;
}
.concentratedHead {
position: relative;
padding-bottom: 0.5em;
margin-bottom: 1em;
}
.concentratedHead::after {
content: '';
position: absolute;
@@ -442,10 +439,6 @@ tr {
height: 0;
}
.relative {
position: relative;
}
.onRight {
overflow: scroll;
margin: auto;
@@ -955,7 +948,7 @@ tr {
padding: 10px;
border: solid 2px #553;
border-radius: 0.5em;
background-image: url("/static/photos/wood.jpg");
background-image: url("../photos/wood.jpg");
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
@@ -1125,9 +1118,10 @@ tr {
@font-face {
font-family: Chess New;
src: url('../fonts/chessglyph-new.0cc8115c.woff2');
font-display: swap;
}
@media screen and (max-width: 600) {
@media screen and (max-width: 600px) {
#nametagContainer {
width: unset;
}

View File

@@ -10,7 +10,7 @@ async function addChessEmbed(username) {
if (user.status === 200) {
user = await user.json();
stats = await stats.json();
ratings = {
const ratings = {
rapid: stats.chess_rapid.last.rating,
blitz: stats.chess_blitz.last.rating,
bullet: stats.chess_bullet.last.rating,

View File

@@ -13,30 +13,46 @@ function toggleMenu(collapse=false) {
}
async function goto(location, { push = true } = {}) {
let a = await fetch("/api/goto/" + location, {
credentials: "include",
method: "GET",
mode: "cors",
});
let a;
try {
a = await fetch("/api/goto/" + location, {
credentials: "include",
method: "GET",
mode: "cors",
});
if (!a.ok) {
console.error(`Navigation failed: HTTP ${a.status}`);
return;
}
} catch (err) {
console.error("Navigation fetch failed:", err);
return;
}
document.dispatchEvent(new Event('beforenavigate'));
const response = await a.json();
const metadata = response[0];
const content = response[1];
const root = document.getElementById("root");
root.innerHTML = content;
root.querySelectorAll("script").forEach((oldScript) => {
const newScript = document.createElement("script");
Array.from(oldScript.attributes).forEach(attr => {
newScript.setAttribute(attr.name, attr.value);
});
newScript.textContent = oldScript.textContent;
oldScript.parentNode.replaceChild(newScript, oldScript);
});
if (!window.location.href.includes("#")) {
window.scrollTo({top: 0, left: 0, behavior:"instant"});
} else {
eid = decodeURIComponent(window.location.hash.substring(1))
document.getElementById(eid).scrollIntoView()
const eid = decodeURIComponent(window.location.hash.substring(1));
const el = document.getElementById(eid);
if (el) el.scrollIntoView();
}
const metadata = response[0];
const content = response[1];
let root = document.getElementById("root");
root.innerHTML = content;
root.querySelectorAll("script").forEach((oldScript) => {
const newScript = document.createElement("script");
Array.from(oldScript.attributes).forEach(attr => {
newScript.setAttribute(attr.name, attr.value);
});
newScript.textContent = oldScript.textContent;
oldScript.parentNode.replaceChild(newScript, oldScript);
});
toggleMenu(collapse=true);
document.querySelector("title").textContent = metadata["title"];
if (push) {

View File

@@ -248,6 +248,14 @@ function initStatusPage() {
statusIntervalId = setInterval(fetchStatus, 300000);
}
// Clean up interval when navigating away via SPA
document.addEventListener('beforenavigate', () => {
if (statusIntervalId !== null) {
clearInterval(statusIntervalId);
statusIntervalId = null;
}
});
// Start when page loads
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initStatusPage);