Files
ARIA-AGENT/android/App.tsx
T
duffyduck 21a315ca71 feat(debug): App-Crash-Reporting via RVS — Logs in der Diagnostic-UI
Stefan ist unterwegs, ADB-Zugriff nicht moeglich. Loesung: die App
loggt ihre eigenen Crashes via RVS, Bridge sammelt sie in
/shared/logs/app.log, Diagnostic-Server liefert sie als JSON.
Damit braucht's keinen ADB mehr — Crashes sind sofort vom Browser
(oder Claude per curl) lesbar.

Komponenten:

1. App components/ErrorBoundary.tsx
   - React-ErrorBoundary fuer kritische Sections
   - componentDidCatch → reportAppError (RVS-Send)
   - UI zeigt Error-Box statt White-Screen + Reset-Button

2. App services/logger.ts
   - reportAppError(scope, message, stack) → rvs.send('app_log', ...)
   - installGlobalCrashReporter() haengt sich an ErrorUtils.setGlobalHandler
     UND HermesInternal.enablePromiseRejectionTracker — fangt sowohl
     ungefangene Errors als auch unhandled Promise-Rejections
   - Konsole bleibt parallel aktiv (damit ADB im Dev-Build weiter
     was sieht)

3. App App.tsx: installGlobalCrashReporter() im useEffect zusammen
   mit initLogger.

4. App ChatScreen.tsx:
   - Inbox-Modal mit ErrorBoundary umschlossen (scope: InboxModal,
     onReset schliesst Modal)
   - MemoryDetailModal mit ErrorBoundary umschlossen
   - DetailModal wird nur noch konditional gerendert (memoryDetailId
     != null) statt immer visible-toggle — vermeidet potentielles
     Modal-Stacking-Problem

5. RVS server.js: ALLOWED_TYPES += "app_log"

6. Bridge aria_bridge.py:
   - elif msg_type == "app_log": haengt eine Zeile an
     /shared/logs/app.log (JSONL, jedes Item {ts, platform, level,
     scope, message, stack})
   - Plus log.info Hinweis fuer das normale Bridge-Log

7. Diagnostic server.js:
   - GET /api/app-log[?limit=N] → letzte N Eintraege als JSON
   - POST /api/app-log/clear → log-Datei loeschen

Workflow zum Debuggen des Inbox-Crashes:
  Stefan rebuilded App → drueckt Inbox → ErrorBoundary fangt den
  Crash (oder Global-Handler bei ungefangenem Error) → reportAppError
  → RVS → Bridge schreibt nach /shared/logs/app.log → Stefan
  oder Claude rufen GET /api/app-log auf → sehen Stacktrace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:42:55 +02:00

149 lines
4.1 KiB
TypeScript

/**
* ARIA Cockpit - Haupteinstiegspunkt
*
* Stefans primaere Schnittstelle zu ARIA.
* Bottom-Tab-Navigation mit Chat und Einstellungen.
*/
import React, { useEffect } from 'react';
import { StatusBar, StyleSheet } from 'react-native';
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import ChatScreen from './src/screens/ChatScreen';
import SettingsScreen from './src/screens/SettingsScreen';
import rvs from './src/services/rvs';
import { initLogger, installGlobalCrashReporter } from './src/services/logger';
// --- Navigation ---
const Tab = createBottomTabNavigator();
// Dunkles Theme fuer die gesamte App
const DarkTheme = {
...DefaultTheme,
dark: true,
colors: {
...DefaultTheme.colors,
primary: '#0096FF',
background: '#0D0D1A',
card: '#12122A',
text: '#FFFFFF',
border: '#1E1E2E',
notification: '#FF3B30',
},
};
// Tab-Icons (Text-basiert, kein Icon-Paket noetig)
const TAB_ICONS: Record<string, { active: string; inactive: string }> = {
Chat: { active: '\uD83D\uDCAC', inactive: '\uD83D\uDCAC' },
Einstellungen: { active: '\u2699\uFE0F', inactive: '\u2699\uFE0F' },
};
// --- App ---
const App: React.FC = () => {
// Beim Start: gespeicherte RVS-Konfiguration laden und verbinden
useEffect(() => {
// Verbose-Logging-Setting laden BEVOR andere Module loslegen.
// initLogger ist async aber blockt nichts — solange er noch laueft,
// loggen wir normal (Default an), danach respektiert console.log das Setting.
initLogger().catch(() => {});
// Crash-Reporter installieren — ungefangene JS-Errors landen via RVS
// bei der Bridge (sichtbar in /shared/logs/app.log + Diagnostic-API)
installGlobalCrashReporter();
const initConnection = async () => {
const config = await rvs.loadConfig();
if (config) {
rvs.setConfig(config);
rvs.connect();
}
};
initConnection();
// Beim Beenden: Verbindung sauber trennen
return () => {
rvs.disconnect();
};
}, []);
return (
<>
<StatusBar barStyle="light-content" backgroundColor="#0D0D1A" />
<NavigationContainer theme={DarkTheme}>
<Tab.Navigator
screenOptions={({ route }) => ({
headerStyle: styles.header,
headerTitleStyle: styles.headerTitle,
headerTintColor: '#FFFFFF',
tabBarStyle: styles.tabBar,
tabBarActiveTintColor: '#0096FF',
tabBarInactiveTintColor: '#555570',
tabBarIcon: ({ focused }) => {
const icons = TAB_ICONS[route.name];
return (
<React.Fragment>
{/* Emoji als Icon */}
{React.createElement(
require('react-native').Text,
{
style: {
fontSize: 22,
opacity: focused ? 1 : 0.5,
},
},
icons ? (focused ? icons.active : icons.inactive) : '?',
)}
</React.Fragment>
);
},
})}
>
<Tab.Screen
name="Chat"
component={ChatScreen}
options={{
title: 'ARIA Chat',
headerTitle: 'ARIA Cockpit',
}}
/>
<Tab.Screen
name="Einstellungen"
component={SettingsScreen}
options={{
title: 'Einstellungen',
}}
/>
</Tab.Navigator>
</NavigationContainer>
</>
);
};
// --- Styles ---
const styles = StyleSheet.create({
header: {
backgroundColor: '#12122A',
elevation: 0,
shadowOpacity: 0,
borderBottomWidth: 1,
borderBottomColor: '#1E1E2E',
},
headerTitle: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: '700',
},
tabBar: {
backgroundColor: '#12122A',
borderTopColor: '#1E1E2E',
borderTopWidth: 1,
height: 60,
paddingBottom: 6,
paddingTop: 4,
},
});
export default App;