fix(home): re-render after config loads to fix null homeCfg on direct load (#1194)

## Summary

- On direct page load to `#/home` (or a full refresh), `renderHome()`
runs before the async `/api/config/theme` fetch resolves, so
`window.SITE_CONFIG` is `undefined` and `homeCfg` is `null` — showing SF
defaults instead of the site's customisations.
- When navigating from another page the fetch has already completed,
which is why it works in that case.
- Fix: subscribe to `theme-refresh` (the event fired ~300 ms after the
config is fetched and applied) and re-render; clean up the listener in
`destroy()`.

This matches the existing pattern used by `analytics.js` and `map.js`.

Fixes #1193

## Test plan

- [x] Hard-refresh directly to `#/home` — customised `heroTitle`,
`heroSubtitle`, steps, footer links must render correctly
- [x] Navigate from another page to Home — still renders correctly (no
regression)
- [x] Site with no custom config — defaults render, no JS errors
- [x] Theme customiser changes while on Home page — page re-renders
(theme-refresh re-render still works)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
efiten
2026-05-21 05:56:41 +02:00
committed by GitHub
parent e078f4bbb6
commit bdbcb337ca
2 changed files with 17 additions and 3 deletions
+9 -2
View File
@@ -5,6 +5,7 @@
let searchTimeout = null;
let miniMap = null;
let searchAbort = null; // AbortController for document-level listeners
let _themeRefreshHandler = null;
const PREF_KEY = 'meshcore-user-level';
const MY_NODES_KEY = 'meshcore-my-nodes'; // [{pubkey, name, addedAt}]
@@ -31,9 +32,14 @@
function init(container) {
if (!localStorage.getItem(PREF_KEY)) {
showChooser(container);
return;
} else {
renderHome(container);
}
renderHome(container);
_themeRefreshHandler = function() {
if (!localStorage.getItem(PREF_KEY)) showChooser(container);
else renderHome(container);
};
window.addEventListener('theme-refresh', _themeRefreshHandler);
}
function showChooser(container) {
@@ -237,6 +243,7 @@
clearTimeout(searchTimeout);
if (searchAbort) { searchAbort.abort(); searchAbort = null; }
if (miniMap) { miniMap.remove(); miniMap = null; }
if (_themeRefreshHandler) { window.removeEventListener('theme-refresh', _themeRefreshHandler); _themeRefreshHandler = null; }
}
// ==================== MY NODES DASHBOARD ====================
+8 -1
View File
@@ -126,7 +126,14 @@ async function effectiveBgFor(page, selector) {
});
await step('Path link contrast (#pathsContent a) ≥ 4.5:1 in dark mode', async () => {
const linkColor = await page.$eval('#pathsContent a[href^="#/nodes/"]', (el) => getComputedStyle(el).color);
// Use page.evaluate (single CDP call) so querySelector and getComputedStyle
// are atomic — page.$eval splits them across two calls, leaving a window
// where a concurrent re-render can detach the element before getComputedStyle
// runs, causing Chromium to return '' for color.
const linkColor = await page.evaluate(() => {
const el = document.querySelector('#pathsContent a[href^="#/nodes/"]');
return el ? getComputedStyle(el).color : '';
});
const bgColor = await effectiveBgFor(page, '#pathsContent a[href^="#/nodes/"]');
const fg = parseRgb(linkColor);
const bg = parseRgb(bgColor);