diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7a7bc2..1a5b017 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,17 @@ Alle Änderungen am Projekt. Format: [Keep a Changelog](https://keepachangelog.c
---
-## [0.2.2.0] — 2026-07-17 — Desktop-Workspace: zoombarer Canvas, Live-Code-Editor, QEMU/VNC
+## [0.2.1.9] — 2026-07-17 — Kompakt ↔ Cockpit: Umschalter für den Kachel-Desktop
+
+### Hinzugefügt
+
+- **Ansichts-Umschalter im Header** („⧉ Kompakt" / „⧉ Cockpit"): Die App startet in **Kompakt** — der klassische Vollbild-Chat, **exakt wie vor dem Umbau** (Default, Mama-tauglich). Ein Tap auf den Button oben rechts schaltet auf **Cockpit** — den zoom-/verschiebbaren Kachel-Desktop. Persistiert über Neustart (`aria_view_mode`).
+- **Warum:** Nach dem 0.2.1.8-Deploy sah die App „unverändert" aus — korrekt, denn im normalen Chat gibt es nur eine Kachel (= Vollbild-Chat). Der Umschalter macht den Cockpit-Modus jetzt **explizit sichtbar/steuerbar**, statt nur bei Code-Projekten aufzutauchen.
+- Im Cockpit ist die **Übersicht jetzt immer erreichbar** (auch im Hauptchat mit nur einer Kachel): „⤢ Übersicht"-Button (unten rechts, weg von den Chat-Kopf-Icons), 2-Finger-Pinch/Pan, Hardware-Back.
+
+---
+
+## [0.2.1.8] — 2026-07-17 — Desktop-Workspace: zoombarer Canvas, Live-Code-Editor, QEMU/VNC
### Hinzugefügt
diff --git a/android/App.tsx b/android/App.tsx
index 8b4a64b..2baf3f8 100644
--- a/android/App.tsx
+++ b/android/App.tsx
@@ -14,6 +14,7 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import WorkspaceScreen from './src/workspace/WorkspaceScreen';
import SettingsScreen from './src/screens/SettingsScreen';
+import ViewModeToggle from './src/components/ViewModeToggle';
import rvs from './src/services/rvs';
import { initLogger, installGlobalCrashReporter } from './src/services/logger';
import { acquireBackgroundAudio } from './src/services/backgroundAudio';
@@ -170,6 +171,7 @@ const App: React.FC = () => {
options={{
title: 'ARIA Chat',
headerTitle: 'ARIA Cockpit',
+ headerRight: () => ,
}}
/>
{
+ const [mode, setMode] = useState(viewMode.get());
+ useEffect(() => viewMode.subscribe(setMode), []);
+
+ const isCockpit = mode === 'cockpit';
+ return (
+ viewMode.toggle()}
+ style={[styles.pill, isCockpit && styles.pillActive]}
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
+ activeOpacity={0.75}
+ >
+
+ {isCockpit ? '⧉ Cockpit' : '⧉ Kompakt'}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ pill: {
+ marginRight: 12,
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ borderRadius: 16,
+ borderWidth: 1,
+ borderColor: '#1E1E2E',
+ backgroundColor: '#12122A',
+ },
+ pillActive: { borderColor: '#0096FF', backgroundColor: '#0A1F33' },
+ text: { color: '#9090B0', fontSize: 13, fontWeight: '700' },
+ textActive: { color: '#0096FF' },
+});
+
+export default ViewModeToggle;
diff --git a/android/src/services/viewMode.ts b/android/src/services/viewMode.ts
new file mode 100644
index 0000000..cb5f46c
--- /dev/null
+++ b/android/src/services/viewMode.ts
@@ -0,0 +1,57 @@
+/**
+ * viewMode — App-Ansicht: 'compact' (klassischer Vollbild-Chat wie vor 0.2.2.0)
+ * oder 'cockpit' (zoombarer Kachel-Desktop).
+ *
+ * Default 'compact' → fuer normale Nutzung aendert sich nichts (Mama-tauglich).
+ * Umschaltbar ueber den Header-Button; persistiert in AsyncStorage. Muster wie
+ * services/rvs.ts (Singleton mit Listener-Liste).
+ */
+
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+export type ViewModeValue = 'compact' | 'cockpit';
+
+const KEY = 'aria_view_mode';
+type Sub = (mode: ViewModeValue) => void;
+
+class ViewMode {
+ private mode: ViewModeValue = 'compact';
+ private subs: Sub[] = [];
+ private loaded = false;
+
+ constructor() {
+ AsyncStorage.getItem(KEY).then((v) => {
+ if (v === 'cockpit' || v === 'compact') this.mode = v;
+ this.loaded = true;
+ this.emit();
+ }).catch(() => { this.loaded = true; });
+ }
+
+ get(): ViewModeValue { return this.mode; }
+ isLoaded(): boolean { return this.loaded; }
+
+ set(mode: ViewModeValue): void {
+ if (this.mode === mode) return;
+ this.mode = mode;
+ AsyncStorage.setItem(KEY, mode).catch(() => {});
+ this.emit();
+ }
+
+ toggle(): void {
+ this.set(this.mode === 'compact' ? 'cockpit' : 'compact');
+ }
+
+ subscribe(cb: Sub): () => void {
+ this.subs.push(cb);
+ cb(this.mode);
+ return () => { this.subs = this.subs.filter((s) => s !== cb); };
+ }
+
+ private emit(): void {
+ const m = this.mode;
+ this.subs.forEach((cb) => cb(m));
+ }
+}
+
+const viewMode = new ViewMode();
+export default viewMode;
diff --git a/android/src/workspace/WorkspaceCanvas.tsx b/android/src/workspace/WorkspaceCanvas.tsx
index 47b72d0..04394f0 100644
--- a/android/src/workspace/WorkspaceCanvas.tsx
+++ b/android/src/workspace/WorkspaceCanvas.tsx
@@ -49,7 +49,6 @@ const WorkspaceCanvas: React.FC = ({ projectId, visibleTiles, subtitles }
const bounds = useMemo(() => boundsOf(visibleTiles), [visibleKey]);
const worldW = bounds.w;
const worldH = bounds.h;
- const single = visibleTiles.length <= 1;
const { loaded: layoutLoaded, getFocus, saveFocus } = useWorkspaceLayout(projectId);
// Kamera (shared values fuer 60fps auf dem UI-Thread).
@@ -60,21 +59,17 @@ const WorkspaceCanvas: React.FC = ({ projectId, visibleTiles, subtitles }
const savedTx = useSharedValue(0);
const savedTy = useSharedValue(0);
- // Bei nur einer Kachel: immer fokussiert. Verschwindet die fokussierte
- // Kachel aus der Sichtbarkeit, auf Chat zurueckfallen.
+ // Verschwindet die fokussierte Kachel aus der Sichtbarkeit → auf Chat zurueck.
useEffect(() => {
- if (single) {
- setFocusedTileId(visibleTiles[0] || 'chat');
- } else if (focusedTileId && !visibleTiles.includes(focusedTileId)) {
+ if (focusedTileId && !visibleTiles.includes(focusedTileId)) {
setFocusedTileId('chat');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visibleKey]);
- // Zuletzt fokussierte Kachel pro Projekt wiederherstellen (nur Mehr-Kachel-
- // Projekte; laeuft NACH dem single-Effekt oben, gewinnt also fuer Code-Projekte).
+ // Zuletzt fokussierte Kachel pro Projekt wiederherstellen.
useEffect(() => {
- if (!layoutLoaded || single) return;
+ if (!layoutLoaded) return;
const stored = getFocus();
if (stored === undefined) return;
if (stored === null || visibleTiles.includes(stored)) setFocusedTileId(stored);
@@ -101,10 +96,10 @@ const WorkspaceCanvas: React.FC = ({ projectId, visibleTiles, subtitles }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [focusedTileId, visibleKey, vw, vh]);
- // Hardware-Back: im Fokus (und mehr als eine Kachel) → zurueck zur Uebersicht.
+ // Hardware-Back: im Fokus → zurueck zur Uebersicht.
useEffect(() => {
const onBack = () => {
- if (focusedTileId && !single) {
+ if (focusedTileId) {
setFocusedTileId(null);
return true;
}
@@ -112,10 +107,10 @@ const WorkspaceCanvas: React.FC = ({ projectId, visibleTiles, subtitles }
};
const sub = BackHandler.addEventListener('hardwareBackPress', onBack);
return () => sub.remove();
- }, [focusedTileId, single]);
+ }, [focusedTileId]);
// Gesten nur in der Uebersicht (Fokus-Modus: Touches fallen an die Kachel).
- const gesturesEnabled = !focusedTileId && !single;
+ const gesturesEnabled = !focusedTileId;
const canvasGesture = useMemo(() => {
const pinch = Gesture.Pinch()
.enabled(gesturesEnabled)
@@ -194,7 +189,7 @@ const WorkspaceCanvas: React.FC = ({ projectId, visibleTiles, subtitles }
{/* Steuerung */}
- {focusedTileId && !single && (
+ {focusedTileId && (
setFocusedTileId(null)} activeOpacity={0.8}>
⤢ Übersicht
@@ -211,16 +206,18 @@ const WorkspaceCanvas: React.FC = ({ projectId, visibleTiles, subtitles }
const styles = StyleSheet.create({
root: { flex: 1, backgroundColor: '#0D0D1A' },
world: { position: 'absolute', left: 0, top: 0 },
+ // Unten rechts (ueber der Chat-Eingabezeile) — weg von ChatScreens Kopf-Icons.
overviewBtn: {
position: 'absolute',
- top: 10,
- right: 12,
+ bottom: 88,
+ right: 14,
backgroundColor: 'rgba(18,18,42,0.92)',
- borderColor: '#1E1E2E',
+ borderColor: '#0096FF',
borderWidth: 1,
borderRadius: 18,
paddingHorizontal: 14,
paddingVertical: 8,
+ elevation: 4,
},
overviewBtnText: { color: '#0096FF', fontSize: 14, fontWeight: '700' },
hintBar: {
diff --git a/android/src/workspace/WorkspaceScreen.tsx b/android/src/workspace/WorkspaceScreen.tsx
index e03c4b9..dae99dc 100644
--- a/android/src/workspace/WorkspaceScreen.tsx
+++ b/android/src/workspace/WorkspaceScreen.tsx
@@ -14,14 +14,18 @@ import React, { useEffect, useMemo, useState } from 'react';
import projectFocus, { FocusSnapshot } from '../services/projectFocus';
import codeFile from '../services/codeFile';
import desktop from '../services/desktop';
+import viewMode, { ViewModeValue } from '../services/viewMode';
+import ChatScreen from '../screens/ChatScreen';
import { TileId, visibleTilesFor } from './layout';
import WorkspaceCanvas from './WorkspaceCanvas';
const WorkspaceScreen: React.FC = () => {
+ const [mode, setMode] = useState(viewMode.get());
const [focus, setFocus] = useState(projectFocus.get());
const [hasCode, setHasCode] = useState(false);
const [hasDesktop, setHasDesktop] = useState(false);
+ useEffect(() => viewMode.subscribe(setMode), []);
useEffect(() => projectFocus.subscribe(setFocus), []);
const pid = focus.focusedProjectId;
@@ -53,6 +57,12 @@ const WorkspaceScreen: React.FC = () => {
[pid, focus.projectNameById],
);
+ // Kompakt-Ansicht: klassischer Vollbild-Chat, exakt wie vor dem Umbau.
+ if (mode === 'compact') {
+ return ;
+ }
+
+ // Cockpit: zoombarer Kachel-Desktop.
return ;
};