fix(webui): load mobile-menu.js; prune orphaned task queue entries

Both found in a user's console log.

1. ReferenceError: setupMobileMenu is not defined (dashboard.js:98)

   core/topbar/js/mobile-menu.js defines that global, and index.html
   never loaded it. dashboard.js called it unguarded as the FIRST line
   of setupEventListeners, so dashboard init threw every page load and
   took loadInstalledApps() with it — and the burger menu was dead on
   mobile. system-loader already guarded its own call with a typeof
   check, which is why this survived unnoticed.

   Loads the script (before dashboard.js) and guards the call, so
   optional nav chrome can never take down the page below it again.

2. Endless 404s on /api/tasks/<id> for tasks that no longer exist

   queue.json is append-only from the enqueue side and nothing ever
   pruned it, so any task file removed afterwards left an id the WebUI
   re-fetched forever, one 404 per poll per orphan. Adds
   cleanupOrphanQueueEntries to the idle housekeeping pass: entries with
   no task file are dropped and logged. Self-heals existing strays.

   (Provoked by my own clean-up of two test tasks earlier in this
   session, but the gap is real and predates it.)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-12 22:48:55 +01:00
parent 5e06e77d7f
commit 07daa7555a
3 changed files with 42 additions and 1 deletions

View File

@ -95,7 +95,14 @@ async function populateDashboardServiceButtons(installedApps) {
// Setup event listeners
function setupEventListeners() {
setupMobileMenu();
// Guarded like system-loader's mobile-menu component: this is a nav nicety,
// and an unguarded call meant a missing/reordered script took the dashboard's
// app list down with it. Never let optional chrome break the page below it.
if (typeof setupMobileMenu === 'function') {
setupMobileMenu();
} else {
console.warn('setupMobileMenu not available — mobile drawer disabled');
}
loadInstalledApps();
}

View File

@ -100,6 +100,12 @@
<script src="/core/ui-state/js/dismissible.js"></script>
<script src="/core/overlays/js/eo-modal.js"></script>
<script src="/core/tasks/js/task-refresh-coordinator.js"></script>
<!-- Defines the global setupMobileMenu(), which dashboard.js calls from
setupEventListeners() and system-loader registers as a component. It was
never loaded here, so dashboard init died on a ReferenceError at that
call — taking loadInstalledApps() with it — and the burger menu was dead
on mobile. Must come before dashboard.js. -->
<script src="/core/topbar/js/mobile-menu.js"></script>
<script src="/components/dashboard/js/dashboard.js"></script>
<script src="/core/boot/js/system-loader.js"></script>
<script src="/core/loading/js/loading-ui.js"></script>

View File

@ -492,6 +492,33 @@ dispatchSpecific() {
# HOUSEKEEPING
# ============================================================================
# Drop queue entries whose task file no longer exists. queue.json is append-only
# from the enqueue side, so any task file removed afterwards — by housekeeping, a
# manual clean-up, a restore — leaves an id behind that the WebUI keeps fetching
# forever, one 404 per poll per orphan. Nothing else prunes it, so it only ever
# grows. Cheap: a no-op unless an id has no file.
cleanupOrphanQueueEntries() {
local queue="$TASK_DIR/queue.json"
[[ -f "$queue" ]] || return 0
command -v jq >/dev/null 2>&1 || return 0
local ids orphans=() id
ids=$(jq -r '.[]?' "$queue" 2>/dev/null) || return 0
[[ -z "$ids" ]] && return 0
while IFS= read -r id; do
[[ -z "$id" ]] && continue
[[ -f "$TASK_DIR/${id}.json" ]] || orphans+=("$id")
done <<< "$ids"
(( ${#orphans[@]} == 0 )) && return 0
local filtered
filtered=$(jq -c --argjson drop "$(printf '%s\n' "${orphans[@]}" | jq -R . | jq -cs .)" \
'[ .[] | select(. as $i | $drop | index($i) | not) ]' "$queue" 2>/dev/null) || return 0
[[ -n "$filtered" ]] || return 0
printf '%s' "$filtered" | runFileWrite "$queue"
logInfo "Pruned ${#orphans[@]} orphaned queue entr$( (( ${#orphans[@]} == 1 )) && echo y || echo ies ): ${orphans[*]}"
}
cleanupZeroByteFiles() {
# Use the bash builtin `-s` (file size > 0) instead of forking `stat` per
# file — at 100+ task files the stat fork was a measurable share of the
@ -596,6 +623,7 @@ mainLoop() {
recoverOrphans
dispatchPending
cleanupZeroByteFiles
cleanupOrphanQueueEntries
maybeRegenPoll
fi
done