diff --git a/TESTING.md b/TESTING.md
index 57723f3..74a49ac 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -1500,6 +1500,31 @@ Ereignisse, `transform: none`.
programmgesteuerter Zeiger prüft die Überfahrung nicht bei jedem Bild neu,
eine echte Maus schon. Bitte mit der Maus gegenprüfen.
+## Wischen holt die Seitenleiste (25.08.2026)
+
+Vom linken Rand nach rechts öffnet, nach links schließt – nur an Geräten mit
+grobem Zeiger (`pointer: coarse`), mit der Maus passiert nichts.
+
+Gemessen bei 412×900 mit Touch:
+
+| Geste | Erwartet | Gemessen |
+|---|---|---|
+| vom Rand (x=10) 150 px nach rechts | öffnet | ✅ |
+| offen, 130 px nach links | schließt | ✅ |
+| aus der Mitte (x=200) nach rechts | nichts | ✅ |
+| vom Rand, nur 30 px | nichts | ✅ |
+| vom Rand, 60 px rechts bei 100 px runter | nichts | ✅ |
+| vom Rand, 150 px in 1,2 s | nichts | ✅ |
+| in der Vollbild-Vorschau vom Rand | nichts | ✅ |
+
+Die Grenzen: 44 px Randstreifen, mindestens 60 px Weg, doppelt so weit
+waagerecht wie senkrecht, höchstens 800 ms.
+
+- [ ] **Auf Android gegenprüfen.** Die Zurück-Geste des Systems belegt die
+ äußersten ~20 px; was dort beginnt, erreicht die Seite nie. Deshalb 44 px
+ statt 20 – ob das in der Praxis reicht, kann nur ein echtes Gerät sagen.
+- [ ] Im Querformat gegenprüfen (Aussparung links)
+
## Automatisiert geprüft (22.08.2026)
Diese Punkte hat Claude per API/`curl` gegen Wegwerf-Konten durchgespielt; sie
diff --git a/frontend/src/components/FilePreview.tsx b/frontend/src/components/FilePreview.tsx
index 5ed740e..5298cb2 100644
--- a/frontend/src/components/FilePreview.tsx
+++ b/frontend/src/components/FilePreview.tsx
@@ -171,6 +171,10 @@ export default function FilePreview({ file, onPrev, onNext, onClose, onSaved, se
return (
diff --git a/frontend/src/components/Shell.tsx b/frontend/src/components/Shell.tsx
index 2a6f89b..bed1d68 100644
--- a/frontend/src/components/Shell.tsx
+++ b/frontend/src/components/Shell.tsx
@@ -1,7 +1,8 @@
-import { useState } from 'react'
+import { useCallback, useState } from 'react'
import { filesApi } from '../api/client'
import type { FolderItem, User } from '../api/client'
import { useNavHistory, type View } from '../hooks/useNavHistory'
+import { useEdgeSwipe } from '../hooks/useEdgeSwipe'
import Sidebar from './Sidebar'
import FileBrowser from '../pages/FileBrowser'
import TrashPage from '../pages/TrashPage'
@@ -25,6 +26,14 @@ export default function Shell({ user, onLogout, onUserChanged }: Props) {
const nav = useNavHistory()
const { view, folderPath } = nav
const [sidebarOpen, setSidebarOpen] = useState(false)
+
+ // Am Finger: vom linken Rand nach rechts wischen holt die Leiste, nach
+ // links wischen schickt sie zurück. Mit Maus passiert nichts.
+ useEdgeSwipe({
+ open: sidebarOpen,
+ onOpen: useCallback(() => setSidebarOpen(true), []),
+ onClose: useCallback(() => setSidebarOpen(false), []),
+ })
// Hochgezählt, wenn etwas auf den Papierkorb gezogen wurde – die Dateiliste
// hängt daran und holt sich daraufhin neu.
const [trashBump, setTrashBump] = useState(0)
diff --git a/frontend/src/hooks/useEdgeSwipe.ts b/frontend/src/hooks/useEdgeSwipe.ts
new file mode 100644
index 0000000..37ba418
--- /dev/null
+++ b/frontend/src/hooks/useEdgeSwipe.ts
@@ -0,0 +1,97 @@
+import { useEffect } from 'react'
+
+/**
+ * Wischen vom linken Rand öffnet die Seitenleiste, Wischen nach links
+ * schließt sie wieder.
+ *
+ * Bisher gab es dafür nur den Knopf oben links. Auf einem Telefon ist das die
+ * Ecke, die man einhändig am schlechtesten erreicht – gerade auf einem
+ * aufgeklappten Flip.
+ *
+ * Drei Bedingungen, damit die Geste nicht in die Quere kommt:
+ *
+ * * **Nur am Rand.** Sonst öffnete jedes Wischen über eine Kachel die
+ * Leiste, und waagerecht scrollbare Bereiche (Reiterleisten, breite
+ * Tabellen, der PDF-Betrachter im Zoom) wären nicht mehr zu bedienen.
+ * * **Deutlich waagerecht.** Beim Blättern durch eine lange Liste wandert
+ * der Finger fast nie exakt senkrecht; ohne diese Bedingung öffnete sich
+ * die Leiste beim Scrollen.
+ * * **Zügig.** Ein langsames Ziehen ist meistens etwas anderes – ein
+ * Auswählen, ein Verschieben, ein Halten.
+ *
+ * **Auf Android hat das System das erste Wort:** Die Zurück-Geste belegt die
+ * äußersten ~20 Pixel beider Ränder, und was dort beginnt, erreicht die Seite
+ * gar nicht. Die Zone ist deshalb 44 Pixel breit – ein Wisch, der knapp hinter
+ * dem Systembereich ansetzt, wird noch erkannt.
+ */
+
+/** Breite des Streifens am linken Rand, in dem eine Geste beginnen darf. */
+const edgeZone = 44
+/** So weit muss der Finger waagerecht wandern. */
+const minDistance = 60
+/** Länger gilt als „gezogen", nicht als „gewischt". */
+const maxDuration = 800
+
+interface Options {
+ open: boolean
+ onOpen: () => void
+ onClose: () => void
+}
+
+export function useEdgeSwipe({ open, onOpen, onClose }: Options) {
+ useEffect(() => {
+ if (typeof matchMedia !== 'function' || !matchMedia('(pointer: coarse)').matches) return
+
+ let startX = 0
+ let startY = 0
+ let startedAt = 0
+ let valid = false
+
+ function onStart(e: TouchEvent) {
+ valid = false
+ if (e.touches.length !== 1) return // zwei Finger heißt zoomen
+ const t = e.touches[0]
+
+ // Nicht aus einer bildschirmfüllenden Ansicht heraus: Dort läge die
+ // Leiste hinter dem Betrachter, und man wischt dort durch Seiten.
+ const el = e.target instanceof Element ? e.target : null
+ if (el?.closest('[data-fullscreen]')) return
+
+ // Geschlossen: nur am Rand. Offen: überall dort, wo die Leiste und ihr
+ // Schatten liegen – weiter rechts hat ein Wisch nichts mit ihr zu tun.
+ if (!open && t.clientX > edgeZone) return
+ if (open && t.clientX > 320) return
+
+ startX = t.clientX
+ startY = t.clientY
+ startedAt = Date.now()
+ valid = true
+ }
+
+ function onEnd(e: TouchEvent) {
+ if (!valid) return
+ valid = false
+ const t = e.changedTouches[0]
+ if (!t) return
+ if (Date.now() - startedAt > maxDuration) return
+
+ const dx = t.clientX - startX
+ const dy = t.clientY - startY
+ // Doppelt so weit zur Seite wie nach oben oder unten: Das trennt ein
+ // Wischen sauber vom Blättern, ohne einen genauen Winkel zu verlangen.
+ if (Math.abs(dx) < minDistance || Math.abs(dx) < Math.abs(dy) * 2) return
+
+ if (!open && dx > 0) onOpen()
+ else if (open && dx < 0) onClose()
+ }
+
+ // Passiv: Die Geste verhindert nichts, sie sieht nur zu. Ein nicht-passiver
+ // Zuhörer würde das Scrollen der ganzen Anwendung ausbremsen.
+ document.addEventListener('touchstart', onStart, { passive: true })
+ document.addEventListener('touchend', onEnd, { passive: true })
+ return () => {
+ document.removeEventListener('touchstart', onStart)
+ document.removeEventListener('touchend', onEnd)
+ }
+ }, [open, onOpen, onClose])
+}