From 6929c460aa9255bfb5d1926141ddc2863f538aa7 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sun, 3 May 2026 12:01:28 +0200 Subject: [PATCH] feat(web): ping rendererReady from App.tsx and ErrorBoundary App.tsx useEffect signals boot-completion (success path). ErrorBoundary componentDidCatch signals it on the caught-error path so the in-app error UI isn't overridden by native recovery 20s later. --- packages/web/src/App.tsx | 13 ++++++++++++- packages/web/src/main.tsx | 6 ++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 6e87adc5..65b1faab 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Routes, Route, Navigate, useSearchParams } from 'react-router-dom'; import { LoginPage } from './components/auth/LoginPage'; import { RegisterPage } from './components/auth/RegisterPage'; @@ -29,6 +29,17 @@ function AuthRedirect({ children }: { children: React.ReactNode }) { } export function App() { + // Boot-completion ping — disarms the main-process boot timer. + // MUST be first hook on the unconditional render path. Single fire on + // first commit; main owns "armed once" gating so duplicate calls (e.g. + // ErrorBoundary path) are idempotent. Semantic: "renderer survived + // render," NOT "data loaded." A spinner on screen counts as boot success. + useEffect(() => { + if (typeof window.backspace?.rendererReady === 'function') { + window.backspace.rendererReady(); + } + }, []); + const showTitleBar = isElectron(); return ( diff --git a/packages/web/src/main.tsx b/packages/web/src/main.tsx index f8fdedf4..d0c0e7fb 100644 --- a/packages/web/src/main.tsx +++ b/packages/web/src/main.tsx @@ -20,6 +20,12 @@ class ErrorBoundary extends React.Component< componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error('[ErrorBoundary]', error, errorInfo.componentStack); + // Renderer is alive enough to show the fallback UI — disarm the boot timer. + // Without this, the timer fires 20s after a caught render error and + // overrides the in-app error UI with native recovery, which is wrong. + if (typeof window.backspace?.rendererReady === 'function') { + window.backspace.rendererReady(); + } } render() {