fix(webui): View Task navigates instead of reloading the page

The button assigned window.location.href, which is a full page load: the
app re-boots, every panel re-fetches and open state is lost — for a
control whose whole job is "show me that task".

It fired more often than it looks. The two branches above it only handle
being ALREADY on an app page with an app name, or ALREADY on /tasks. A
system-wide task carries no app name, and its /tasks/<category>/<id>
target keeps the id in the path rather than a ?task= query, so from the
dashboard or the App Center every guard missed and the reload was the
outcome.

The router handles that URL fine — tasks-manager reads the category and
task id out of the path on mount, so /tasks/all/<id> arrives with the
task already highlighted. Both fallbacks now offer it to spaClean first
and keep location.href only for when the router genuinely is absent.
The URL is normalised to a same-origin path, since navigate() expects a
path and the incoming value may be absolute; anything off-origin is
refused rather than routed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-20 22:52:47 +01:00
parent 67970a84e9
commit 2964688fc7

View File

@ -356,6 +356,36 @@ window.removeNotification = (notificationId) => {
}
};
// Navigate without dropping the SPA. Returns true when the router took it.
//
// The fallbacks below used to assign window.location.href, which is a FULL page
// load: the app re-boots, every panel re-fetches, and any open state is lost —
// for a button whose whole job is "show me that task". It fired more often than
// it looks, because the branches above only handle two situations (already on an
// app page WITH an app name, or already on /tasks). A "View Task" for a
// system-wide task carries no app name, and its /tasks/<cat>/<id> target keeps
// the id in the PATH rather than a ?task= query, so from the dashboard or the
// App Center every one of those guards missed and the reload was the outcome.
//
// The router handles the same URL perfectly well: tasks-manager reads the
// category and task id straight out of the path on mount, so /tasks/all/<id>
// arrives with that task already highlighted. location.href stays as the last
// resort for when the router genuinely is not there.
const _notifyNavigate = (url) => {
try {
if (window.spaClean && typeof window.spaClean.navigate === 'function') {
// navigate() wants a same-origin PATH; the incoming value may be absolute.
const u = new URL(url, window.location.origin);
if (u.origin !== window.location.origin) return false; // never route off-site
window.spaClean.navigate(u.pathname + u.search);
return true;
}
} catch (e) {
console.warn('SPA navigation failed, falling back to a page load:', e);
}
return false;
};
window.handleNotificationNavigation = (url) => {
try {
console.log('🔗 handleNotificationNavigation called with URL:', url);
@ -472,14 +502,16 @@ window.handleNotificationNavigation = (url) => {
}
}
// If we get here and no managers were available, fallback
console.warn('⚠️ Falling back to page reload for URL:', url);
// No manager matched this URL — hand it to the router rather than reloading.
if (_notifyNavigate(url)) return true;
console.warn('⚠️ No SPA router available; falling back to a page load for URL:', url);
window.location.href = url;
return false;
} catch (error) {
console.error('❌ Error handling notification navigation:', error);
// Fallback to direct navigation if parsing fails
console.warn('⚠️ Falling back to page reload due to error for URL:', url);
// Parsing failed, but the router can still take the raw URL.
if (_notifyNavigate(url)) return true;
console.warn('⚠️ Falling back to a page load due to error for URL:', url);
window.location.href = url;
return false;
}