feat(app): Kompakt ↔ Cockpit — Ansichts-Umschalter im Header (0.2.1.9)

Nach dem 0.2.1.8-Deploy sah die App "unveraendert" aus — korrekt, weil der
Hauptchat nur eine Kachel hat (= Vollbild-Chat). Jetzt explizit umschaltbar:
- services/viewMode.ts: 'compact' | 'cockpit', persistiert (aria_view_mode),
  Default 'compact' (nichts aendert sich fuer normale Nutzung).
- ViewModeToggle im Navigations-Header (rechts, kollisionsfrei): "⧉ Kompakt" /
  "⧉ Cockpit".
- WorkspaceScreen: compact → klassische ChatScreen direkt; cockpit → Canvas.
- Canvas: Uebersicht/Gesten/Back jetzt auch bei einer Kachel erreichbar (der
  single-Force-Fokus entfaellt), damit sich Cockpit auch im Hauptchat wie ein
  Desktop anfuehlt. "⤢ Uebersicht"-Button nach unten rechts verschoben (weg von
  ChatScreens Kopf-Icons).

Changelog: Workspace-Release als 0.2.1.8 gefuehrt, Umschalter als 0.2.1.9.
tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 00:32:33 +02:00
co-authored by Claude Opus 4.8
parent bbfd544013
commit 7329257830
7 changed files with 142 additions and 19 deletions
+14 -17
View File
@@ -49,7 +49,6 @@ const WorkspaceCanvas: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ projectId, visibleTiles, subtitles }
</View>
{/* Steuerung */}
{focusedTileId && !single && (
{focusedTileId && (
<TouchableOpacity style={styles.overviewBtn} onPress={() => setFocusedTileId(null)} activeOpacity={0.8}>
<Text style={styles.overviewBtnText}> Übersicht</Text>
</TouchableOpacity>
@@ -211,16 +206,18 @@ const WorkspaceCanvas: React.FC<Props> = ({ 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: {