import { useEffect, useState } from 'react' import { LayoutDashboard, BookOpen, FileText, Settings as SettingsIcon } from 'lucide-react' import Dashboard from './pages/Dashboard' import SettingsPage from './pages/Settings' import LogsPage from './pages/Logs' import AnleitungPage from './pages/Anleitung' import FirstRunWizard from './components/FirstRunWizard' import { api } from './lib/api' import { EventStreamProvider, useStrom } from './lib/useEventStream' import { BetriebProvider } from './lib/useBetrieb' import { Fehlergrenze } from './components/Fehlergrenze' type Page = 'dashboard' | 'anleitung' | 'logs' | 'settings' /** * Zeigt, ob der Live-Strom steht. * * Hier stand bis V2-3 ein fest verdrahtetes „ONLINE" mit pulsierendem Punkt — * es leuchtete auch dann gruen, wenn die API tot war. Genau die Sorte * Placebo-Anzeige, die in Etappe 19 schon einmal aufgeraeumt wurde * („Fortschritt log, Auswurf tat nichts, Encoder wurden behauptet statt * gemessen"). Jetzt sagt sie die Wahrheit. */ function VerbindungsAnzeige() { const { verbunden, zuletzt } = useStrom() if (verbunden) { return (
LIVE
) } return (
VERBINDUNG WEG
) } function AppInhalt() { const [currentPage, setCurrentPage] = useState('dashboard') // First-Run: solange der Einrichtungs-Assistent nicht abgeschlossen ist, zeigen wir // ihn statt des Dashboards. null = /setup noch nicht geprüft (kein Aufblitzen). const [setupDone, setSetupDone] = useState(null) /* * ⚠️ Ein fehlgeschlagener Abruf ist KEINE Aussage über die Einrichtung. * * Hier stand `.catch(() => setSetupDone(true))` mit der Begründung * „API/Setup nicht erreichbar → nicht blockieren". Die Folge hat der * Commander am 28.08.2026 gemeldet: **Nach einer frischen Installation * fehlte der Ersteinrichtungs-Assistent.** * * Und zwar zwangsläufig: Das Fenster geht unmittelbar nach dem Setup auf, * der Dienst braucht ein bis zwei Sekunden bis zur ersten Antwort — der * erste Abruf scheitert also fast immer. Aus „ich konnte nicht nachsehen" * wurde „ist schon erledigt", und der Assistent war für immer weg. * * Dasselbe Prinzip steht nebenan in `useEventStream.tsx`: Wer nichts Neues * weiß, behält, was er wusste — und behauptet nichts. * * Deshalb: nachfragen, bis der Server ANTWORTET. Erst eine Antwort * entscheidet. */ useEffect(() => { let abgemeldet = false let versuche = 0 const fragen = () => { api.get('/setup') .then((r) => { if (!abgemeldet) setSetupDone(Boolean(r.data?.done)) }) .catch(() => { if (abgemeldet) return versuche += 1 // Zwei Minuten lang alle zwei Sekunden. Antwortet der Server bis // dahin nicht, hat der Nutzer ein anderes Problem als den // Assistenten — dann zeigen wir die Oberfläche, damit er die Logs // sehen kann. if (versuche < 60) setTimeout(fragen, 2000) else setSetupDone(true) }) } fragen() return () => { abgemeldet = true } }, []) const navItems: { id: Page; label: string; icon: any }[] = [ { id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard }, { id: 'anleitung', label: 'Anleitung', icon: BookOpen }, { id: 'logs', label: 'Logs', icon: FileText }, { id: 'settings', label: 'Einstellungen', icon: SettingsIcon }, ] // Solange /setup noch nicht geantwortet hat: sagen, dass gewartet wird. // Ein leeres Fenster sieht aus wie ein Absturz — und nach einer frischen // Installation ist genau das der erste Eindruck. if (setupDone === null) { return (

Rippy startet …

warte auf den Dienst

) } if (!setupDone) return setSetupDone(true)} /> return (
{/* Top Header Navigation Bar */}
{/* Logo */}
setCurrentPage('dashboard')}> RIPPY CINEMA OS
{/* Central Nav Tabs */}
{/* Main Content Viewport */}
{/* Je Seite eine eigene Grenze: Faellt eine aus, bleibt die Navigation stehen und man kommt woanders hin. Ohne das war am 29.08.2026 nach einem Klick auf „Rippen starten" der ganze Bildschirm leer — samt Kopfzeile. */} {currentPage === 'dashboard' && } {currentPage === 'anleitung' && } {currentPage === 'logs' && } {currentPage === 'settings' && }
) } export default function App() { // Der Provider haelt GENAU EINE SSE-Verbindung fuer die ganze Anwendung. // Ein Hook je Komponente waere eine Verbindung je Komponente — also das // alte Problem in neuer Form. // BetriebProvider ganz aussen: Was Rippy anzeigen DARF, entscheidet sich // vor allem anderen. Ohne ihn zeigte das Windows-Fenster "Pruefen: docker // compose ps" — einen Rat, den dort niemand befolgen kann. return ( ) }