From 7c9fe7214d1ad4ad31d2c7a3aaa333f9a26a31f9 Mon Sep 17 00:00:00 2001 From: Gagan Date: Sun, 5 Jul 2026 00:45:30 +0530 Subject: [PATCH] fix(apps): stop task.yaml rewrite races + blank-load watchdog + loop-lag monitor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Investigating 'apps blank while a bg task runs': measured serving latency through a real scheduled bg-task run — 1-3ms on both loopbacks throughout, so the server is not the bottleneck. Found and fixed the likely renderer-era culprit plus made the failure observable: - agent sync (apps:list, polled every 4s) rewrote each bundled agent's task.yaml unconditionally — racing the bg runner's own patches mid-run and spamming watcher events; now writes only when the definition changed - AppFrame load watchdog: if the iframe hasn't loaded in 6s, show a visible 'taking too long' state with Retry instead of a silent blank pane - main event-loop lag monitor: stalls >300ms are logged so future blank reports can be tied to the offending work --- .../src/components/apps/app-frame.tsx | 33 ++++++++++++++++++- apps/x/packages/core/src/apps/agents.ts | 12 ++++++- apps/x/packages/core/src/apps/server.ts | 22 +++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/apps/x/apps/renderer/src/components/apps/app-frame.tsx b/apps/x/apps/renderer/src/components/apps/app-frame.tsx index 3492e67d..c406b903 100644 --- a/apps/x/apps/renderer/src/components/apps/app-frame.tsx +++ b/apps/x/apps/renderer/src/components/apps/app-frame.tsx @@ -11,12 +11,29 @@ import { AppDetail } from '@/components/apps/app-detail' export function AppFrame({ app, onBack }: { app: rowboatApp.AppSummary; onBack: () => void }) { const [reloadNonce, setReloadNonce] = useState(0) const [showDetail, setShowDetail] = useState(false) + // Load watchdog: if the iframe hasn't fired `load` within the deadline, + // surface a visible retry state instead of a silent blank pane. + const [loadState, setLoadState] = useState<'loading' | 'ok' | 'stuck'>('loading') const title = app.manifest?.name ?? app.folder useEffect(() => { appOpened(app.folder) }, [app.folder]) + // Reset the watchdog when the target changes (adjust-during-render pattern). + const [watchKey, setWatchKey] = useState(`${app.folder}:${reloadNonce}`) + if (watchKey !== `${app.folder}:${reloadNonce}`) { + setWatchKey(`${app.folder}:${reloadNonce}`) + setLoadState('loading') + } + + useEffect(() => { + const timer = window.setTimeout(() => { + setLoadState((s) => (s === 'loading' ? 'stuck' : s)) + }, 6000) + return () => window.clearTimeout(timer) + }, [watchKey]) + return (
@@ -55,13 +72,27 @@ export function AppFrame({ app, onBack }: { app: rowboatApp.AppSummary; onBack:
-
+