Terminfarbe: Sie kam auf dem Telefon nie an. RFC 7986 schreibt für COLOR
einen CSS3-*Namen* vor, wir schrieben Hex – DAVx5 liest die Eigenschaft
über eine Namenstabelle und verwirft alles andere, also blieb der Termin
im Samsung-Kalender farblos. Jetzt beides nebeneinander:
COLOR:tomato für fremde Clients (damit die Farbe ankommt)
X-AETHER-COLOR:#ea4335 für die eigene Palette (damit sie genau bleibt)
Beim Lesen gewinnt der genaue Wert; ein von außen eingefärbter Termin
wird über die Namenstabelle übersetzt. Nächstgelegener Ton per Abstand im
RGB-Würfel (color.go).
Wiederholungen können jetzt mehr als "alle N":
- wöchentlich an ausgewählten Wochentagen (Mo–So, Mehrfachauswahl). Der
Wochentag des Starts bleibt fest angehakt – eine Reihe, die ihren
eigenen ersten Termin auslässt, ist keine.
- monatlich und jährlich wahlweise am festen Kalendertag (am 4.) oder am
n-ten Wochentag (am 1. Freitag). Beide Angaben leitet der Server aus
dem Startdatum ab, statt sie getrennt entgegenzunehmen – so kann die
Regel gar nicht erst auf einen anderen Tag zeigen als der Termin.
- Der fünfte Wochentag eines Monats wird bewusst zu "letzter": Einen
fünften Freitag hat nicht jeder Monat, BYDAY=5FR ließe die Reihe sonst
in etwa jedem zweiten Monat stillschweigend ausfallen.
Regeln, die das Formular weiterhin nicht abbildet, bleiben lesbar, aber
nicht bearbeitbar (409) – unverändert.
Dazu scripts/kalender-selbsttest.py, 19 Prüfungen gegen die laufende
Instanz, in TESTING.md verlinkt. Alle grün, unter anderem:
am 1. Freitag monatlich -> 04.09. 02.10. 06.11. 04.12.
am 1. Freitag im September -> 2026-09-04, 2027-09-03
Mo+Fr wöchentlich -> 07./11./14./18./21./25.09.
ganztägig am 3. -> bleibt der 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
76 KiB
Aether – Test-Checkliste
Manuelle Test-Checkliste für die fertigen Phasen. Abhaken beim Durchtesten.
- ⚠️ = bekannte Lücke, muss nicht getestet werden
- 🔎 = von Claude per
curl/API gegen ein Wegwerf-Konto geprüft und grün. Bewusst nicht abgehakt: eine API-Prüfung ersetzt nicht den Test in der Oberfläche bzw. mit einem echten Client. Beim Durchgehen selbst abhaken.
Kalender: Selbsttest per Skript
Die Kalender-Schnittstelle hat einen automatischen Selbsttest – schneller und gründlicher als Klicken, und er prüft genau die Stellen, an denen schon einmal etwas schiefging:
python3 scripts/kalender-selbsttest.py
19 Prüfungen: Ganztags-Datum (der 3. muss der 3. bleiben – der Fehler, bei dem ein Termin östlich von UTC einen Tag zurücksprang), mehrtägige Termine, Terminfarbe in beiden Schreibweisen, alle Wiederholungsmuster (wöchentlich an bestimmten Tagen, monatlich/jährlich am festen Tag bzw. am n-ten Wochentag), Bearbeiten einer Reihe, und dass das Zeitfenster wirklich begrenzt.
Legt Termine an und löscht am Ende alle Termine im ersten Kalender des Kontos. Also nur gegen eine Entwicklungsinstanz laufen lassen.
Notfall: Migration hängt fest ("Dirty database version")
Betrifft jede künftige Migration, nicht nur eine bestimmte. Nachgestellt und
bestätigt (siehe kalender-plan.md, Abschnitt „Migrationen"): Scheitert eine
Migration, bleibt schema_migrations.dirty auf true stehen, und jeder
weitere Start von Aether bricht ab – die ganze Cloud, nicht nur der neue Teil.
Kein Datenverlust dabei: Die Migration lief in einer Transaktion und wurde
zurückgerollt, bevor der Fehler kam. dirty=true ist nur eine Sperre, keine
Aussage über beschädigte Daten.
Beheben:
docker exec aether-db psql -U aether -d aether \
-c "UPDATE schema_migrations SET dirty=false;"
Danach Aether neu starten. Lief die Migrationsdatei nur teilweise (z. B. zwei
CREATE TABLE in einer Datei, das erste gelang), vorher prüfen, was bereits
existiert, bevor man einen erneuten Lauf zulässt – sonst scheitert der zweite
Versuch am selben Fehler wie der erste.
Phase 3 – Admin, Gruppen, Theming, Profil
Admin-Panel: Zugriff & Sicherheit
- Admin-Link erscheint nur für Admin-User in der Sidebar
- Normaler User sieht den Admin-Link nicht
- Normaler User, der
/api/v1/admin/usersdirekt aufruft, bekommt403(serverseitiger Check) - Nach Logout ist kein Admin-Endpunkt erreichbar (
401)
Admin: Benutzerverwaltung
- Benutzerliste zeigt alle User mit verwendetem Speicher + Quota + Rolle
- Neuen User anlegen (Username, Anzeigename, Passwort, Quota, Admin-Flag)
- Anlegen mit existierendem Username → Fehler „bereits vergeben" (
409) - User bearbeiten (Anzeigename, Quota, Admin-Flag ändern)
- Selbst-Schutz: eigenen Admin-Status kann man nicht entfernen (Fehlermeldung)
- Selbst-Schutz:* eigenes Konto kann man nicht löschen
- Passwort-Reset generiert neues Passwort und zeigt es einmalig an
- Neuer User kann sich mit dem zurückgesetzten Passwort einloggen
- User löschen entfernt den User (und seine Dateien) 🔎
Admin: Gruppen
- Gruppen-Tab lädt ohne schwarzen Bildschirm (war der Bug – jetzt gefixt)
- Gruppe anlegen (Name + Beschreibung)
- Gruppe aufklappen zeigt Mitglieder
- Mitglied über Dropdown hinzufügen → Zähler steigt
- Mitglied entfernen → Zähler sinkt
- Gruppe mit doppeltem Namen → Fehler (
409) - Gruppe löschen (auch mit Mitgliedern – Cascade)
- ⚠️ Gruppen haben noch keine Funktion (keine Gruppenordner) – Phase 4.5/C, offen
Admin: Theming
- Cloud-Name ändern → erscheint nach Reload überall (Login-Seite, Sidebar)
- Primärfarbe ändern → Buttons/Akzente übernehmen die Farbe sofort
- Akzentfarbe ändern → Vorschau aktualisiert live
- Nach Reload sind die Farben persistent
- ⚠️ Logo-Upload noch nicht vorhanden – Phase 4.5/D, offen
Profil-Seite (jeder User)
- Avatar/Name unten in der Sidebar öffnet die Profil-Seite
- Speicheranzeige (Balken) stimmt mit tatsächlicher Nutzung
- Anzeigename ändern → wird gespeichert (✓-Bestätigung) 🔎
- Passwort ändern: falsches aktuelles Passwort → Fehler
- Passwort ändern: neue Passwörter stimmen nicht überein → Fehler
- Passwort ändern: < 8 Zeichen → Fehler
- Passwort erfolgreich ändern → Logout → Login mit neuem Passwort klappt
- Persönliche Farben ändern → werden angewendet
- Logout-Button im Profil-Bereich funktioniert (ohne Profil zu öffnen)
Phase 4 – WebDAV & OIDC
WebDAV – Verbindung
Verbinden mit https://<deine-domain>/dav/ oder lokal direkt mit
http://127.0.0.1:8090/dav/. Achtung Port: die App lauscht im Container auf
8080, Compose bildet das auf dem Host aber auf 8090 ab (127.0.0.1:8090->8080).
Vom Host aus gilt immer 8090 – und nur vom Host, die Bindung ist auf localhost.
Anmeldung mit dem Benutzernamen und einem App-Passwort (Profil → App passwords). Das Kontopasswort wird hier abgelehnt – es ist zugleich der Admin-Zugang und hat auf Geräten nichts verloren.
Clients: KDE/Dolphin, macOS Finder, Windows Explorer, cadaver, rclone, Cyberduck.
Hintergrund zu Dolphin/KIO: KIO schickt Zugangsdaten nie vorab, sondern immer erst auf den
401hin – und beginnt dabei sofort mit dem Rumpf, ohneExpect: 100-continueabzuwarten. Große Uploads scheiterten deshalb mit „unerwarteter Fehler 0", während kleine Dateien durchliefen. Behoben am 22.08.2026: Der Server liest den Rumpf einer unauthentifizierten Anfrage weg, bevor er die Anmeldeaufforderung schickt, damit die Verbindung stehen bleibt und KIO die401überhaupt zu sehen bekommt.
Alle Punkte hier wurden am 19.08.2026 per
curlgegen ein Wegwerf-Konto geprüft und waren grün (🔎). Was das nicht abdeckt: ein echter Client (Finder, Explorer, Cyberduck, rclone) verlangt andere PROPFIND-Properties und nutzt Locking – deshalb stehen die Haken offen.
- Login mit Username + App-Passwort → Verbindung steht 🔎
- Login mit falschem Passwort → abgelehnt (
401) 🔎 - Login mit dem Kontopasswort → abgelehnt (
401), auch wenn es stimmt 🔎 - Ordnerstruktur entspricht der in der Web-App (gleiche Ordner sichtbar) 🔎
WebDAV – Lesen
- Ordner auflisten (PROPFIND) zeigt Dateien + Unterordner 🔎
- Datei herunterladen → Inhalt korrekt 🔎
- Datei, die in der Web-App hochgeladen wurde, ist via WebDAV sichtbar 🔎
WebDAV – Schreiben
- Datei hochladen (PUT) → erscheint danach in der Web-App 🔎
- Neuen Ordner anlegen (MKCOL) → erscheint in der Web-App 🔎
- Datei umbenennen (MOVE im selben Ordner) 🔎
- Datei in anderen Ordner verschieben (MOVE) 🔎
- Ordner umbenennen 🔎
- Datei löschen (DELETE) → landet im Papierkorb der Web-App (nicht hart gelöscht!) 🔎
- Bestehende Datei überschreiben → bei aktivierter Versionierung landet die alte Fassung in der Historie; bei deaktivierter wird ersetzt (so gewollt, Versionierung ist pro Datei opt-in) 🔎
- Checksumme/Integrität: hochgeladene Datei wieder runterladen → identisch 🔎
- Große Datei (100 MB+) per Dolphin hochladen – war der Fehler „unerwarteter Fehler 0" 🔎
- Quota wird bei WebDAV-Upload geprüft →
507 Insufficient Storage🔎 - Abgebrochener Upload (Kontingent voll oder Verbindung weg) hinterlässt nichts – keine abgeschnittene Datei, kein verwaistes Blob 🔎
- Rate-Limit/Lockout am WebDAV-Login (10 Fehlversuche / 15 min →
429) 🔎 - App-Passwort ist der einzige WebDAV-Zugang, gilt nicht fürs Web-Login, und ist nach dem Widerruf sofort tot 🔎
App-Passwörter (Profil → App passwords)
- App-Passwort anlegen → Klartext erscheint einmalig, „Copy" kopiert es
- Damit einen WebDAV-Client verbinden (Username = normaler Benutzername)
- Liste zeigt „Last used" nach der ersten Nutzung
- Widerrufen → Client kommt sofort nicht mehr rein
- Kontopasswort im WebDAV-Client → abgelehnt; der Anmeldedialog nennt den Grund im Realm-Text 🔎
- Kontopasswort funktioniert weiterhin fürs Web-Login 🔎
- App-Passwort funktioniert nicht fürs Web-Login 🔎
Audit-Log (Admin → Activity)
- Tab zeigt Einträge, neueste zuerst 🔎
- Fehlgeschlagene Anmeldung erscheint rot als „Sign-in failed" 🔎
- Anmeldung, Passwortwechsel, User-/Gruppen-Änderungen, Freigaben und Löschungen tauchen auf 🔎
- Filter-Dropdown schränkt auf eine Aktion ein
- Blättern funktioniert (ab 50 Einträgen)
- Gelöschtes Konto: Eintrag bleibt lesbar, Name mit „(deleted account)"
- Passwort-Reset steht im Protokoll – ohne das Passwort selbst
- Normaler User bekommt auf
/api/v1/admin/auditein403
Speicher-Bereinigung (Admin → Activity → „Run now")
- „Run now" meldet „Nothing to clean up", solange alles in Ordnung ist 🔎
- Verwaiste Datei älter als 24 h wird entfernt, Ergebnis wird angezeigt 🔎
- Frisch angelegte verwaiste Datei bleibt liegen (Schonfrist) 🔎
- Echte Dateien bleiben nach dem Lauf unangetastet 🔎
- Der Lauf erscheint anschließend selbst im Audit-Log
Phase 4.5/B – Interne Freigaben
Freigeben (Rechtsklick → „Share with people…")
- Ordner an eine Person freigeben, Recht „Can view"
- Empfänger sieht ihn unter Shared with me, mit „View only"
- Empfänger kann hineinsehen und herunterladen
- Empfänger kann nicht umbenennen, löschen, hochladen (Meldung statt Fehler)
- Recht auf „Can edit" ändern → wird ersetzt, kein zweiter Eintrag
- Empfänger kann jetzt umbenennen und hochladen
- Einzelne Datei freigeben → Empfänger sieht nur sie, nicht den Ordner drumherum
- An eine Gruppe freigeben → alle Mitglieder sehen es, mit Hinweis „via "
- Mitglied aus der Gruppe entfernen → Zugriff sofort weg
- „Remove" im Dialog → Zugriff sofort weg
Grenzen (das Wichtige)
- Empfänger kann die Freigabe nicht weitergeben (nur der Eigentümer teilt)
- Dateien bleiben beim Eigentümer: Speicherbalken des Eigentümers wächst, wenn der Empfänger etwas hochlädt – nicht der des Empfängers
- Unterordner, den ein Empfänger anlegt, gehört dem Eigentümer
- Empfänger kann eine geteilte Datei nicht in seinen eigenen Ordner verschieben
- Ohne Freigabe liefert ein direkter Aufruf mit bekannter ID
404(nicht403– sonst verriete die Antwort, dass die ID existiert) - Freigaben tauchen im Audit-Log als
share.grant/share.revokeauf
Über WebDAV erscheinen Freigaben unter
/dav/Shared/<Eigentümer>/<Freigabe>/– siehe eigener Abschnitt weiter unten.
Phase 4.5/C – Gruppenordner
Anlegen (Admin → Groups)
- Gruppe wählen, Namen eingeben, „Create" → Ordner erscheint in der Liste
- Zweiter Ordner für dieselbe Gruppe → Fehler „already has a folder" (
409) 🔎 - Übersicht zeigt Gruppe, Mitgliederzahl und belegten Platz
Beim Nutzer
- Kein Mitglied: Ordner ist nicht sichtbar 🔎
- Mitglied hinzufügen → Ordner erscheint in dessen Wurzel, beschriftet als „Group folder" 🔎
- Zwei Mitglieder arbeiten darin: hochladen, herunterladen, umbenennen, Unterordner anlegen 🔎
- Mitglied entfernen → Ordner verschwindet sofort 🔎
Der Ordner selbst ist geschützt
- Rechtsklick auf den Gruppenordner: kein Löschen, Umbenennen, Verschieben, Teilen – nur Öffnen und Favorit
- Er lässt sich nicht per Drag & Drop verschieben
- Direkter API-Aufruf (
DELETE /folders/{id}) →403🔎 - Innerhalb des Ordners ist alles normal löschbar 🔎
Speicher und Papierkorb
- Dateien liegen unter
groups/<id>/home/…, nicht bei einem Nutzer 🔎 - Kontingent zählt gegen die Gruppe, bei keinem Mitglied 🔎
- Gruppenkontingent überschritten →
413🔎 - Gelöschte Gruppendatei erscheint im Papierkorb jedes Mitglieds und lässt sich wiederherstellen 🔎
- Admin löscht den Gruppenordner → Daten und Verzeichnis restlos weg 🔎
Geteiltes und Gruppenordner über WebDAV
Der Aufbau bildet die Weboberfläche nach: Gruppenordner liegen in der Wurzel
neben den eigenen, Personen-Freigaben gebündelt unter Shared/.
- Gruppenordner erscheint in der WebDAV-Wurzel, gleiche Liste wie in der Weboberfläche 🔎
- Hineinnavigieren, hochladen, Unterordner anlegen 🔎
- Namensgleichheit: eigener Ordner
Projekt+ GruppenordnerProjekt→ eigener behält den Namen, Gruppenordner heißtProjekt (<Gruppe>), beide erreichbar, Schreibvorgänge landen jeweils richtig 🔎 - Nach Austritt aus der Gruppe verschwindet er aus der Wurzel 🔎
Sharederscheint nur, wenn eine Person etwas geteilt hat 🔎/Shared/listet die Personen,/Shared/<Person>/deren Freigaben 🔎- Datei im geteilten Ordner lesen und schreiben (bei „Can edit") 🔎
- Einzeln geteilte Datei erscheint als Datei, nicht als Ordner 🔎
- Nur-Lesen: Schreiben und Löschen →
403(nicht404/405) 🔎 - In
/Shared/selbst lässt sich nichts anlegen oder löschen (403) 🔎 - Verschieben über Eigentumsgrenzen →
403🔎 - Nicht Vorhandenes bleibt
404– verrät nichts 🔎 - Entzug der Freigabe / Austritt aus der Gruppe → sofort
404🔎 - Bens Upload landet im Bereich des Eigentümers und zählt gegen dessen Kontingent 🔎
- Prüfsumme über den geliehenen Pfad: Upload → WebDAV-GET → Web-Download, alles identisch 🔎
- Mit einem echten Client (Dolphin) durchgehen
OIDC (nur falls konfiguriert)
OIDC ist optional. Test nur, wenn OIDC_ISSUER etc. in .env gesetzt sind.
Wichtig: OIDC_REDIRECT_URL muss auf <PUBLIC_URL>/api/v1/auth/oidc/callback zeigen
und exakt so beim IdP registriert sein.
- Ohne OIDC-Config: kein SSO-Button auf der Login-Seite (Standardfall)
- Mit OIDC-Config: SSO-Button erscheint
- Klick auf SSO → Weiterleitung zum IdP
- Nach erfolgreichem IdP-Login → zurück in Aether, eingeloggt
- Beim ersten OIDC-Login wird automatisch ein User angelegt (aus
preferred_username) - Zweiter OIDC-Login desselben Accounts → gleicher User (kein Duplikat)
- Username-Kollision → automatischer Suffix (
name2,name3, …) - Manipulierter/abgebrochener Callback → Redirect mit Fehler, kein Login
- CSRF/State: Login mit falschem
state-Cookie → abgelehnt
Phase 4.5/D – Logo & App-Symbol
Admin → Theming: Logo
- Logo hochladen → erscheint sofort in der Vorschau, Seitenleiste und auf der Anmeldeseite (ohne Speichern-Klick) 🔎
- Logo austauschen → neues Bild sofort sichtbar, altes ist von der Platte verschwunden 🔎
- „Remove" → Standard-Wolke ist zurück 🔎
- SVG, Textdatei, getarnte EXE → abgelehnt (
415) 🔎 - Datei über 2 MB →
413mit klarer Meldung 🔎 - Normaler Nutzer kann weder hochladen noch löschen (
403) 🔎 - Logo ist ohne Anmeldung abrufbar (die Anmeldeseite braucht es) 🔎
Admin → Theming: App-Symbol
Ein Upload für beides – Tab-Symbol und installierte App. Der Server rechnet 64, 192, 512 und die maskable-Fassung daraus.
- Quadratisches PNG (am besten 512×512) hochladen → Symbol im Tab wechselt ohne Neuladen 🔎
- Anderer, schon offener Tab zieht beim nächsten Laden nach
- Austauschen → neues Symbol sofort da, altes Verzeichnis von der Platte verschwunden 🔎
- „Remove" → Standard-Symbol ist zurück, Verzeichnis gelöscht 🔎
- Vorlage kleiner als 192×192 →
422mit klarer Meldung 🔎 - Vorlage mit 256×256 → es entsteht kein 512er, das Manifest nennt ihn auch nicht (nie hochskalieren) 🔎
- Nicht quadratisches Bild → wird gerahmt, nicht beschnitten 🔎
- SVG und ICO → abgelehnt (
415); Datei über 2 MB →413🔎 - Ohne Anmeldung abrufbar (der Browser fragt das Tab-Symbol vor dem Anmelden ab) 🔎
- Speicher-Bereinigung („Run now") löscht den Symbol-Satz nicht 🔎
Manifest (/manifest.webmanifest)
- Direkt aufrufen → nennt Cloud-Namen und Primärfarbe aus dem Admin-Bereich, nicht die fest eingebauten Werte 🔎
- Ohne eigenes Symbol fällt es auf die mitgelieferten Bilder zurück 🔎
- Auf dem Handy „Zum Startbildschirm hinzufügen" → Name und Symbol stimmen
- Bereits installierte App: Symbol wechselt erst nach ein, zwei Tagen – Chrome fragt das Manifest von selbst nach, aber nicht sofort. Sicher sofort nur nach Deinstallieren und neu Installieren.
Vor und Zurück im Verlauf
- In mehrere Ordner nacheinander wechseln → Zurück (Browser-Taste, Seitentaste der Maus oder Wischgeste) geht die Reihenfolge rückwärts, Vor wieder nach vorn
- In der Werkzeugleiste stehen keine eigenen Vor/Zurück-Pfeile mehr – das macht allein der Browser
- Über die Seitenleiste die Ansicht wechseln (Papierkorb, Freigaben, Admin, Profil) → Zurück führt zur vorigen Ansicht
- Aus einer anderen Ansicht zurück zu „Files" → der zuletzt geöffnete Ordner ist wieder da, nicht die Wurzel
- Über die Brotkrumen nach oben springen → zählt als eigener Schritt, Zurück führt wieder nach unten
- Auf einen Favoriten in der Seitenleiste klicken → eigener Schritt im Verlauf
- Zweimal denselben Ordner öffnen, in dem man schon steht → erzeugt keinen zweiten Schritt (Zurück springt nicht ins Leere)
- Seite neu laden (
F5) → man landet wieder im selben Ordner - Adresse zeigt die Ansicht (
/admin,/trash); direkt eingegeben lädt sie auch. Der Ordner steht nicht in der Adresse – ein Link auf einen tiefen Ordner ist also nicht teilbar (bewusst, siehe Projektplan)
Suche
Feld in der Werkzeugleiste, rechts neben den Brotkrumen. Sucht in Namen von Dateien und Ordnern – und seit dem 23.08.2026 zusätzlich im Inhalt von PDFs und Bildern (siehe „Volltext über den Inhalt" weiter unten).
- Ab zwei Zeichen erscheinen Treffer, ein Zeichen tut nichts
- Unter jedem Treffer steht der Ordner, in dem er liegt
- Ordner-Treffer anklicken → Ordner wird geöffnet
- Datei-Treffer anklicken → springt nur in den Ordner. Es öffnet sich keine Vorschau 🔎
- Die gesuchte Datei ist dort kurz umrandet und wird in den sichtbaren Bereich geholt; die Markierung verschwindet nach knapp drei Sekunden von selbst 🔎
- Geöffnet wird weiterhin per Doppelklick auf die Datei
- Das gilt in Kachel- und Listenansicht
- Pfeiltasten wählen aus, Enter öffnet, Esc schließt
- Schnell tippen → keine veralteten Treffer (jede Anfrage bricht die vorige ab)
- Papierkorb bleibt außen vor: gelöschte Datei erscheint nicht 🔎
Volltext über den Inhalt (OCR)
Der Punkt, für den ein Rechnungsarchiv überhaupt gebaut wird: Ein Beleg namens
scan_0042.pdf muss über das zu finden sein, was drin steht.
Wichtig zum Verständnis beim Prüfen: Die Erkennung läuft im Hintergrund.
Direkt nach dem Hochladen findet die Suche den Inhalt noch nicht – bei einem
PDF mit Textebene nach etwa einer Sekunde, bei einem Scan nach rund einer
Minute je Seite. Der Stand steht in GET /api/v1/admin/ocr (noch ohne
Oberfläche) und im Log (docker compose logs app | grep component=ocr).
- PDF mit Textebene hochladen → nach ein paar Sekunden findet ein Wort
aus dem Text die Datei; im Log steht
source=layer🔎 - Eingescannte oder abfotografierte Rechnung hochladen → nach etwa einer
Minute je Seite findet ein Wort daraus die Datei; im Log
source=ocr🔎 - Unter dem Treffer steht die Fundstelle mit hervorgehobenem Suchwort, und der Name allein hätte nicht gepasst 🔎
- Die Fundstelle läuft über zwei Zeilen und bricht am Ende der zweiten mit „…" ab – nicht mitten in der ersten 🔎
- Links daneben eine dünne Linie in derselben Farbe wie das hervorgehobene Wort; sie trennt das Zitat vom Ordnernamen darüber 🔎
- Ein Dokument mit
<script>im Text zeigt das als sichtbaren Text, führt es nicht aus (die Marken kommen als[[hl]], nicht als HTML) - Ordner- und Dateinamen beginnen in der Trefferliste auf derselben linken Kante (gemessen: alle bei x=616) 🔎
- Datei, deren Name passt, steht vor den Inhaltstreffern 🔎
- Eine Datei, bei der Name und Inhalt passen, erscheint einmal, nicht zweimal 🔎
- Das Original ist unverändert: Größe und Prüfsumme nach der Erkennung identisch, keine neue Version in der Historie, WebDAV lädt die Datei nicht erneut 🔎
- Bestandsdateien von vor dem Einbau sind ebenfalls durchsuchbar (werden beim ersten Start nachgetragen) 🔎
- Gelöschte Datei erscheint auch bei Inhaltstreffern nicht
- Ein anderer Nutzer findet nichts über den Inhalt fremder Dokumente – der Umfang ist derselbe wie bei der Namenssuche
- Word-/Excel-Datei liefert keinen Inhaltstreffer (bewusst nicht gebaut), wird aber weiterhin über den Namen gefunden
- Erstes Dokument nach einem frischen Container-Start gelingt beim ersten Versuch (kein „Tesseract failed to report available languages" im Log) 🔎
OCR_ENABLED=falsein der.env→ Cloud läuft normal, nur ohne Inhaltstreffer;/api/v1/admin/ocrmeldetenabled:false
Platte (OCR ist das Einzige, was von allein Platz verbraucht):
- Während der Erkennung eines großen Scans bleibt
/data/tmpfast leer –docker compose exec app du -sh /data/tmpsollte im Kilobyte-Bereich liegen, nicht im Megabyte-Bereich 🔎 - Nach der Erkennung liegen dort keine
ocr-*-Dateien mehr 🔎 docker killmitten in einer Erkennung → beim nächsten Start meldet das Log „Zwischendateien eines früheren Laufs entfernt"- Bei weniger als 2 GiB freiem Platz pausiert die Erkennung mit einer Warnung im Log, statt weiterzuarbeiten; die Cloud bleibt bedienbar
Suche und Berechtigungen (das Wichtige)
Am 23.08.2026 mit zwei Wegwerf-Konten durchgespielt – die Punkte sind grün, aber ein zweites Augenpaar auf der Oberfläche schadet nicht.
- Fremde Dateien tauchen nie auf, auch nicht mit passendem Namen 🔎
- Nach einer Freigabe erscheinen die Dateien des anderen – auch die in Unterordnern der Freigabe (Vererbung nach unten) 🔎
- Nach Entzug der Freigabe sind sie sofort wieder weg 🔎
- Gruppenordner erscheinen bei Mitgliedern, bei anderen nicht 🔎
- Nach Austritt aus der Gruppe sofort weg 🔎
- Suchwort mit
%oder_sucht diese Zeichen wörtlich, nicht als Platzhalter (%efindet nicht alles mit „e") 🔎
Vorschau
Doppelklick auf eine Datei zeigt sie, statt sie herunterzuladen. Der Download-Knopf und das Kontextmenü laden weiter herunter.
- Bild, PDF, Textdatei, Video, Tondatei → werden angezeigt
- PDF öffnet den eingebauten Betrachter mit Seitenleiste, Zoom und Drucken 🔎
- Speicherbalken zeigt direkt nach der Anmeldung den echten Wert, nicht 0 B 🔎
- ZIP, EXE und anderes → Doppelklick lädt weiter herunter
- Pfeile links/rechts blättern durch die anzeigbaren Dateien des Ordners, Pfeiltasten ebenso
- Esc schließt, Klick auf den dunklen Rand ebenso
- „Download" in der Vorschau lädt die Datei
- Textdatei über 2 MB → Hinweis statt Anzeige
- Video/Ton: in der Abspielleiste vor- und zurückspringen, auch an eine Stelle, die noch nicht geladen war 🔎
- Bild behält sein Seitenverhältnis, egal wie das Fenster steht
- Textansicht ist ein Blatt in der Mitte, nicht die ganze Fläche
Textdatei bearbeiten
- „Edit" → Textfeld, „Save" schreibt zurück, Liste zeigt neue Größe
- Bei eingeschalteter Versionierung landet die alte Fassung in der Historie
- „Cancel" verwirft, Schließen mit ungesicherten Änderungen fragt nach
- Pfeiltasten schreiben im Textfeld, statt zu blättern
- Nur-Lese-Freigabe → „Save" meldet fehlenden Schreibzugriff
- Sicherheit: eine hochgeladene HTML-Datei wird auch mit
?inline=1als Download ausgeliefert, nicht angezeigt 🔎 - Fremde Datei über
?inline=1→404wie beim normalen Download 🔎
Kachel-Cover nach Dateityp
- Ton, Video, Archiv, Tabelle, Präsentation, Code, PDF und Dokument haben je eigenes Symbol und eigene Farbe
- Word (blau), Excel (grün), PowerPoint (orangerot) sind auf einen Blick
auseinanderzuhalten – auch die OpenDocument-Entsprechungen
(
.odt,.ods,.odp) 🔎 - PDF und Word-Dokument haben verschiedene Symbole, nicht nur verschiedene Farben
.mdund.txtzählen als Text (grau), nicht als Office-Dokument- Unten links steht die Endung als Aufkleber (
MP3,TAR.GZ,CSV) - Ton und Video tragen zusätzlich ein Abspielzeichen
.tar.gzwird als Archiv erkannt, obwohl die Endung zweiteilig ist 🔎- Bilder zeigen weiterhin ihr Vorschaubild – mit Aufkleber darüber, damit PNG, JPG, GIF und SVG unterscheidbar bleiben
- Ordner: zweilagige Form mit Reiter und heller Oberkante, nicht mehr
flachgedrückt; Gruppenordner in der Primärfarbe mit
GROUP-Aufkleber - Auch in „Verschieben nach…", „Shared with me" und im Admin-Bereich dieselbe Form – dort waren die Ordner vorher schwarz 🔎
- Ziel beim Ziehen hebt sich weiterhin ab
- Listenansicht und Suchtreffer zeigen denselben Ordner wie die Kacheln
Dateitypen (MIME)
- Neu hochgeladene
.mp3erscheint alsaudio/mpeg, nicht alsapplication/octet-stream🔎 - Bestandsdatei mit falschem Typ lässt sich trotzdem abspielen – der Typ wird beim Ausliefern aus dem Namen neu bestimmt 🔎
Kopfleiste in allen Ansichten
Alle sechs Ansichten teilen sich dieselbe Leiste (components/PageHeader.tsx),
feste Höhe 57 px.
- Zwischen Home, Papierkorb, „Shared with me", Freigabe-Links, Admin und Profil umschalten → die Leiste springt nicht, der Inhalt darunter beginnt überall auf derselben Kante
- Symbol (18 px) und Titel (15 px, Stärke 600) sitzen überall gleich – auch „Home" in den Brotkrumen. Beim Umschalten springt nichts 🔎 (nachgemessen: Symbol x=240, Titel x=268 in allen sechs Ansichten)
- Alle Kopfsymbole haben runde Strichenden
- Bedienelemente stehen überall rechts
- Schmales Fenster: die Leiste bleibt einzeilig, nichts bricht um
Einstellungen und Admin-Zugang (23.08.2026)
- „Admin" steht nicht mehr in der Seitenleiste – der Weg dorthin führt über das Nutzermenü oben rechts 🔎
- Die Seitenleiste zeigt nur noch Orte: Home, Trash, Shared with me, Shared links, Favoriten 🔎
- Die Einstellungen heißen „Settings" (nicht mehr „Profile") und sind in Reiter unterteilt wie der Admin-Bereich: Account → Access → Appearance → Storage 🔎
- Die Reiterleiste sieht in Einstellungen und Admin-Bereich gleich aus –
beide benutzen
components/Tabs.tsx. Gemessen: Höhe 39, Schrift 14 px, aktiver Reiter fett und unterstrichen 🔎 - Der Speicherbalken steht am Ende, nicht mehr ganz oben 🔎
- Bei „App passwords" steht der Hinweis dabei, dass das Kontopasswort an
/dav/bewusst abgelehnt wird - Ein Nutzer ohne Adminrechte sieht „Admin panel" im Menü nicht und
kommt über
/adminauch nicht hinein
Stand der Texterkennung im Admin-Bereich (23.08.2026)
Admin panel → Activity, oberhalb von „Storage cleanup".
- „Full-text indexing" zeigt die Zahl der gelesenen Dokumente 🔎
- Während die Warteschlange abgearbeitet wird: gelber Punkt, Drehrädchen und „… to go"; die Zahl aktualisiert sich von selbst alle fünf Sekunden
- Steht die Warteschlange still, hört das Nachfragen auf (im Netzwerk-Reiter der Entwicklerwerkzeuge zu sehen) – sonst liefe alle fünf Sekunden eine Anfrage ins Leere
- Grüner Punkt, wenn alles gelesen ist
- Bei
OCR_ENABLED=false: gedämpfter Punkt und ein Hinweis, was fehlt - Nicht lesbare Dateien werden mitgezählt („n could not be read")
Eigener Stand für jeden Nutzer
Settings → Storage → „Document search". Der Admin-Bereich zeigt den ganzen Server; hier sieht jeder Nutzer seine eigenen Dateien.
- Als normaler Nutzer (ohne Adminrechte) ist die Karte sichtbar und zeigt eine Zahl 🔎
- Die Zahl umfasst nur die eigenen Dateien – ein zweites Konto mit eigenen PDFs sieht seine eigene Zahl, nicht die des Servers 🔎
GET /api/v1/ocrohne Anmeldung →401🔎- Während der Erkennung: gelber Punkt und „… still to go", danach grün
- Kein Adminrecht nötig, um die eigene Zahl zu sehen
Etiketten und Belegdatum (23.08.2026)
Rechtsklick auf eine Datei → „Tags…".
- Neues Etikett im Dialog anlegen; es erscheint sofort gewählt 🔎
- Derselbe Name in anderer Schreibweise wird abgelehnt („STEUER-2026" neben „steuer-2026") 🔎
- Leerer Name wird abgelehnt 🔎
- Etiketten erscheinen als Aufkleber an der Kachel und in der Listenansicht 🔎
- Seitenleiste zeigt sie unter den Favoriten mit Farbpunkt und Zählstand 🔎
- Klick darauf zeigt alles mit diesem Etikett – über Ordnergrenzen hinweg 🔎
- Die Adresse
/tags/<id>ist verlinkbar und übersteht ein Neuladen 🔎 - Zurück-Taste des Browsers führt vom Etikett zurück
- Gelöschte Dateien zählen nicht mit und erscheinen nicht
Belegdatum
- Datum setzen, Dialog erneut öffnen → das Datum steht drin (nicht leer) 🔎
- Die Etikettenansicht sortiert nach Belegdatum, neueste zuerst; Dateien ohne Datum stehen am Ende 🔎
- Datum wieder leeren funktioniert
Was nicht schiefgehen darf
- Ungültiges Datum → Fehlermeldung, und die Etiketten bleiben unverändert (nicht halb gespeichert) 🔎
- Eine Datei in einem Gruppenordner kann nur Gruppen-Etiketten tragen; der Dialog zeigt dort auch nur diese 🔎
- Ein zweites Konto sieht die eigenen Etiketten nicht
- Wer eine Datei nur lesen darf, kann sie nicht etikettieren
- Etikett löschen entfernt nur die Ordnung, keine Dateien 🔎
Sammel-Download als ZIP (23.08.2026)
Drei Wege dorthin - Auswahl, Ordner, Etikett.
Auswahl
Strg-Klick (oder Klick aufs Kaestchen) waehlt eine Datei aus; das Kaestchen erscheint beim Ueberfahren 🔎- Ist etwas ausgewaehlt, genuegt ein einfacher Klick fuer die naechste
Umschalt-Klick waehlt den Bereich in der angezeigten Reihenfolge- Die Kopfleiste zeigt "n selected" mit "Download as ZIP" und "Clear" 🔎
- Ordnerwechsel verwirft die Auswahl (sonst laedt man ungesehen mit) 🔎
- Escape hebt die Auswahl auf 🔎
- Ist die Vorschau offen, schliesst das erste Escape nur die Vorschau – die Auswahl bleibt; erst das zweite hebt sie auf 🔎
- Waehrend man in ein Feld tippt, verwirft Escape die Eingabe und nicht die Auswahl
- Ein einfacher Klick waehlt nichts aus, auch nicht wenn schon etwas markiert ist – dafuer gibt es Kaestchen, Strg und Umschalt 🔎
- Ein Doppelklick oeffnet die Vorschau und aendert die Auswahl nicht 🔎
- Funktioniert in Kachel- und Listenansicht
Ordner und Etikett
- Rechtsklick auf einen Ordner -> "Download as ZIP" laedt den ganzen Inhalt 🔎
- Unterordner behalten ihre Struktur im Archiv 🔎
- Etikettenansicht -> "Download all as ZIP" laedt alles mit diesem Etikett
- Das Archiv heisst nach Ordner bzw. Etikett 🔎
Was nicht schiefgehen darf
- Ohne Anmeldung ->
401, ohne CSRF-Token ->403🔎 - Eine erfundene Datei-Kennung landet nicht im Archiv 🔎
- Fremde Dateien sind nicht enthalten, auch nicht bei geratener Kennung
- Zwei gleichnamige Dateien werden zu "name" und "name (2)" - nicht stillschweigend zu einer 🔎
paket.tar.gzdoppelt -> "paket (2).tar.gz", nicht "paket.tar (2).gz" 🔎- Umlaute im Dateinamen kommen im entpackten Archiv richtig an
- Ein grosser Ordner startet sofort (gestreamt) statt erst zu sammeln 🔎
- Ueber 2000 Dateien -> klare Meldung statt endlosem Warten
- Bereits gepackte Formate (.zip, .deb, .jpg, .pdf) werden nur gelagert, nicht erneut komprimiert 🔎
Bedienung am Finger (24.08.2026)
Auf dem Handy öffnete gar nichts. Ordner und Dateien hingen an
onDoubleClick – und den doppelten Klick gibt es auf einem Touchscreen
praktisch nicht: Man trifft nicht zweimal denselben Pixel, der Browser meldet
zwei einzelne Berührungen. Der Doppelklick ist eine Mausgeste.
Unterschieden wird jetzt nach pointer: coarse, also nach dem Zeigegerät,
nicht nach der Fensterbreite: Ein schmales Fenster am Schreibtisch bleibt eine
Maus, ein Tablet im Querformat bleibt ein Finger.
- Handy: ein Tipp öffnet Ordner 🔎 und Dateien 🔎 (Kachel und Liste)
- Läuft eine Auswahl, schaltet der Tipp die Auswahl um statt zu öffnen – ohne Strg/Umschalt wäre Mehrfachauswahl am Telefon sonst unbedienbar
- Langes Drücken öffnet das Kontextmenü (bitte am Gerät prüfen – die Kacheln sind auch ziehbar, und der Browser könnte stattdessen eine Ziehbewegung beginnen)
- Desktop unverändert: Doppelklick öffnet, einfacher Klick bleibt frei, Strg/Umschalt wählt aus 🔎
Falle, die zweimal zuschlug: In JSX gewinnt die zuletzt notierte Eigenschaft. Ein per Spread eingesetztes
onClickwurde vom danach notiertenonClickstillschweigend überschrieben – der Tipp auf ein PDF tat nichts, während der auf einen Ordner ging. Dateien schreiben ihren Handler deshalb aus, statt ihn zusammenzusetzen. Dieselbe Sorte Fehler wie Inline-Stil gegen Klasse: Es wird gesetzt und sofort überschrieben.
Zoom „ganze Breite" bei gemischten Formaten (24.08.2026)
Gerechnet wird gegen die häufigste Seitenbreite, nicht mehr gegen die breiteste.
- Zehnseitiges Dokument mit einer Querformat-Seite: die hochkanten Seiten füllen die Breite 🔎 (gemessen 384 von 412 px; vorher 46 % statt 65 %, weil eine einzige Seite das ganze Dokument bestimmte)
- Die Querformat-Seite ragt über den Rand und lässt sich seitlich schieben – das ist bei ihr ohnehin gewollt
- Reines Hochformat: unverändert
„invalid credentials" beim ersten Admin-Login (24.08.2026)
Symptom: frischer Start, admin account ready im Log, Volume mehrfach
gelöscht – und die Anmeldung mit den richtigen Daten gibt 401.
Ursache ist die .env, nicht der Code: Ein $ wird von Docker Compose als
Variable gelesen und schneidet den Wert ab. Gemessen mit
ADMIN_PASSWORD=Sicher$Passwort2026:
angekommen ist: Sicher (6 statt 19 Zeichen)
Compose meldet dazu: The "Passwort2026" variable is not set.
Die Meldung erscheint auf stderr beim up, nicht im Container-Log – wer die
Logs im Nachhinein ansieht, sieht sie nie. Die Datenbank funktioniert dabei,
weil Postgres und Anwendung denselben abgeschnittenen Wert bekommen.
Escapen:
in der .env |
kommt an |
|---|---|
abc$def |
abc |
abc$$def |
abc$def ✓ |
'abc$def' |
abc$def ✓ |
abc\$def |
abc\ |
ADMIN_PASSWORDmit$$→password_lengthstimmt, Anmeldung 200 🔎 (gemessen: 19 Zeichen, Login 200)- Ohne Escape → Warnung im Log „ADMIN_PASSWORD ist auffaellig kurz" 🔎
- Nachsehen, was wirklich ankam:
docker compose exec app printenv ADMIN_PASSWORD
Der Start nennt seit dem 24.08.2026 die Länge des Passworts (nicht den Wert) und warnt unter 12 Zeichen. Ohne diese Zahl war der Fehler von außen nicht zu sehen.
Office-Container entsteht nicht (24.08.2026)
Kein Fehler, keine Meldung – der Container existiert einfach nicht. Ursache
war profiles: ["office"] in den README-Schnipseln: Ein Dienst in einem
Profil startet nur mit --profile office.
In Portainer, Dockge oder Komodo gibt es diesen Schalter nicht. Dort war der Dienst über die Bedienung gar nicht erreichbar, und nichts wies darauf hin. Eine Voreinstellung, bei der das Einfügen des Schnipsels nichts bewirkt, ist die falsche – die Schnipsel haben deshalb kein Profil mehr.
- Schnipsel aus dem README anhängen,
docker compose up -d→ der Office-Container entsteht 🔎 (geprüft für alle drei:app office postgres) - Abschalten mit
docker compose stop office - Wer ein Profil will, hängt
profiles: ["office"]selbst an – dann entweder--profile officeoderCOMPOSE_PROFILES=officein der.env; Letzteres wirkt auch ohne Kommandozeile 🔎
Kommentar hinter leerem Wert in der .env (24.08.2026)
Beim Testen selbst gebaut und sofort wieder eingefangen. In einer .env gilt:
A=production # kommentar → production ✓
C=production#kommentar → production#kommentar
F= # kommentar → "# kommentar" ✗
Hinter einem gefüllten Wert ist ein Kommentar harmlos. Hinter einem
leeren wird er zum Wert. OIDC_ISSUER= # z. B. https://… startete damit
den OIDC-Aufbau mit dem Kommentartext als Adresse – und weil ein Fehlschlag
dort den Start abbricht, kam die Cloud nicht hoch.
In der (inzwischen zusammengeführten) .env.example steckten vier weitere
solche Zeilen, darunter
OFFICE_PROVIDER= # collabora | onlyoffice: Office wäre mit Unsinn als
Anbieter eingeschaltet worden. Alle vier umgestellt, der Kommentar steht jetzt
darüber.
grep -nE '^[A-Z_]+=[[:space:]]*#' .env.exampleliefert nichts 🔎- Frische Instanz allein aus den Beispieldateien startet 🔎
Beschriftung des SSO-Knopfes (24.08.2026)
OIDC_BUTTON_LABEL, Vorgabe „Sign in with SSO / OIDC".
- Gesetzt → der Knopf auf der Anmeldeseite trägt den Text
- Nicht gesetzt → die Vorgabe
- OIDC aus →
/public/configliefert einen leeren Wert 🔎 (die Einstellung geht niemanden etwas an, solange es keinen Knopf gibt)
Versionen: was beim Ausschalten passiert (24.08.2026)
Der Schalter „Keep versions" setzt nur ein Flag. Vorhandene Fassungen bleiben liegen – mitsamt ihrem Platz im Kontingent. Gemessen an einer Datei mit drei Fassungen:
| Schritt | Fassungen | Blobs |
|---|---|---|
| dreimal überschrieben | 3 | 4 |
| Versionierung ausgeschaltet | 3 | 4 |
| in den Papierkorb | 3 | 4 |
| Papierkorb geleert | – | 0 |
- Ausschalten löscht nichts 🔎
- Der Papierkorb genügt nicht – dort bleibt alles 🔎
- Leeren des Papierkorbs entfernt Fassungen und Blobs sofort 🔎 (nicht erst beim nächsten GC)
Anzahl aufgehobener Fassungen (24.08.2026)
MAX_VERSIONS_KEPT, Vorgabe 5. Vorher eine Konstante – und zwar zwei:
eine im Backend, eine im Verlaufs-Dialog, verbunden nur durch den Kommentar
„muss zusammenpassen". Ein Satz ist keine Zusicherung. Der Dialog holt die
Zahl jetzt aus /api/v1/public/config.
MAX_VERSIONS_KEPT=3→/public/configmeldet 3 🔎- Datei mit „Keep versions" siebenmal überschreiben → genau 3 Fassungen im Verlauf, und zwar die neuesten 🔎 (gemessen: v6, v5, v4 bei aktueller v7)
- Der Dialog nennt dieselbe Zahl wie der Server
MAX_VERSIONS_KEPT=0wird verworfen, die Vorgabe greift –OFFSET 0löschte sonst jede Fassung- Unbrauchbarer Wert (
abc) → Vorgabe, kein Startabbruch - Ohne „Keep versions" an der Datei entsteht weiterhin kein Verlauf
Erreichbarkeit von außen (24.08.2026)
ERR_CONNECTION_REFUSED beim Zugriff über die IP ist meist kein Fehler,
sondern die Vorgabe: Der Port ist auf 127.0.0.1 gebunden, davor gehört der
Reverse Proxy. Der Kernel lehnt ab, bevor irgendetwas die Anwendung erreicht –
daher „refused" und kein Zeitüberlauf.
Prüfen:
docker ps --format '{{.Names}}\t{{.Ports}}'
# 127.0.0.1:8090->8080/tcp = nur dieser Rechner
# 0.0.0.0:8090->8080/tcp = das ganze Netz
- Ohne
APP_BINDbindet der Port auf127.0.0.1🔎 APP_BIND=0.0.0.0bindet auf alle Adressen 🔎- Mit
0.0.0.0von einem zweiten Gerät im Netz erreichbar - Hinter dem Reverse Proxy wieder auf
127.0.0.1zurückstellen – sonst steht die Anmeldung unverschlüsselt im Netz
Frische Umgebung nur aus den Beispieldateien (24.08.2026)
Der einzige Test, der zählt: docker-compose.example.yml + .env.example
kopieren, Passwörter eintragen, starten. Nichts sonst.
Beim ersten Durchlauf brachte er sofort einen Fehler ans Licht, den keine Codeprüfung findet:
migrations up to date
FTL blob storage init failed error="mkdir /app/data: permission denied"
STORAGE_PATH hat die Voreinstellung ./data – richtig für ein Binary, das
man von Hand startet, falsch im Container, wo daraus /app/data wird statt
des Mounts unter /data. Die schlanke .env.example führte den Schlüssel
nicht, weil er „eine brauchbare Vorgabe" hat. Hatte er nicht.
Jetzt setzt das Abbild STORAGE_PATH und TMP_PATH selbst. Es kennt seine
Verzeichnisstruktur besser als jede Beispieldatei; eine .env darf weiterhin
überschreiben.
- Frische Umgebung nur aus den beiden Beispieldateien →
migrations up to date,admin account ready,listening🔎 /health200, Anmeldung 200,data/tmpliegt auf dem Wirt 🔎- Dabei ein Passwort mit
%und@verwendet 🔎
Datenbank-Passwort mit Sonderzeichen (24.08.2026)
Die Verbindungszeichenkette wurde mit fmt.Sprintf zusammengeklebt. Beim
ersten Aufbau einer frischen Umgebung fiel das sofort auf:
FTL migrations failed
error="parse \"postgres://aether:8UoNj%CoWJ7ze@qy@localhost:5432/aether\":
invalid URL escape \"%Co\""
@ und : beenden den Anmeldeteil einer URL, % leitet eine Maskierung ein.
Die key=value-Form hätte dasselbe Problem mit einem Leerzeichen gehabt,
nur stiller: Der Rest dahinter wäre als eigener Schlüssel gelesen worden.
Jetzt baut net/url die Adresse, und die Umrechnung von Hand in pkg/db ist
ersatzlos weg.
DB_PASSWORDmit%,@,:,/und Leerzeichen → Server startet, Migrationen laufen, Anmeldung geht 🔎 (gemessen mit8UoNj%CoWJ7ze@qyin einer eigenen Umgebung:migrations up to date,/health200, Login 200)go test ./pkg/config/...deckt die Fälle ab, ohne dass eine Datenbank laufen muss 🔎- IPv6 als
DB_HOST(::1) wird geklammert, der Port bleibt erhalten 🔎
Keine Regel „welche Zeichen erlaubt sind". Wer die erst nachschlagen muss, wählt am Ende ein schwächeres Passwort.
Dazu: DB_HOST fehlte im schlanken Beispiel
Die .env.example im Wurzelverzeichnis führte DB_NAME, DB_USER und
DB_PASSWORD, aber nicht DB_HOST. Die Voreinstellung ist localhost – und
aus dem Container heraus ist das er selbst. Im Log stand deshalb
@localhost:5432, obwohl Postgres als postgres im Compose-Netz lief.
- Frische Umgebung nur aus
docker-compose.example.yml+.env.examplestartet ohne Nacharbeit APP_ENV,DB_HOSTundDB_PORTstehen darin
Handy: eine Kopfzeile statt zwei (24.08.2026)
Die eigene Leiste über der Kopfzeile („☰ Aether") ist weg. Sie enthielt nur das Menü-Symbol und den Namen der Cloud – und der stand ohnehin in der Seitenleiste, die sie öffnete. Gemessen kostete sie 57 der 915 Pixel.
- Handy: eine Kopfzeile – ☰, Ort („Home"/Ordner), rechts das Konto 🔎 (gemessen 120 px statt 205 px)
- Das Konto steht in der Titelzeile, nicht in der Reihe mit „+ Folder" und „Upload" 🔎
- ☰ öffnet die Seitenleiste 🔎
- Desktop unverändert: Kopfzeile 56 px, kein ☰, Konto ganz rechts hinter der Trennlinie 🔎
Sichere Bereiche – der Abstand wirkte nirgends
.aether-safe-top und Geschwister waren Klassen; alle drei Stellen setzen
ihren Abstand aber als Inline-Stil, und der schlägt jede Klasse. Der
Abstand wurde berechnet und sofort überschrieben. Jetzt Variablen
(--safe-top, --safe-bottom, --safe-left).
Nur in der installierten App zu sehen (im Browser ist der Wert 0):
- Kopfzeile klebt nicht an der Statusleiste / Uhr
- Speicherbalken der Seitenleiste liegt nicht hinter dem Wischbalken
- Querformat mit Aussparung: Seitenleiste beginnt neben der Kamera
Handy und PWA (23.08.2026)
Die Kopfleiste war auf schmalen Schirmen kaputt: eine Zeile ohne Umbruch, die ihren Inhalt gequetscht statt umgebrochen hat. Gemessen bei 390 px: Brotkrume auf einen Strich zusammengeschoben, Suchfeld über der Sortierauswahl, Ansichts-Umschalter verschwunden, Nutzerbereich abgeschnitten.
- Auf dem Handy sind alle Bedienelemente sichtbar und erreichbar: Brotkrume, Suche, Sortierung, Umschalter, „+ Folder", Upload, Nutzer 🔎
- Nichts läuft seitlich aus dem Bild (kein waagerechtes Scrollen) 🔎
- Auf dem Desktop bleibt die Leiste einzeilig, 56 px wie vorher 🔎
- Die Trefferliste der Suche passt auf den Schirm, ohne über den Rand zu gehen
- Etikettendialog und „Edit tag" sind auf dem Handy vollständig bedienbar
- Als PWA installiert dasselbe prüfen – dort fehlt die Adressleiste, die Leiste sitzt also weiter oben
- Drehen ins Querformat und zurück
Installierte App
- Der Inhalt reicht bis an die Ränder, steht aber nicht hinter Uhr oder Wischbalken 🔎
- Der Speicherbalken unten links ist erreichbar, nicht hinter dem Wischbalken 🔎
- Die Statusleiste hat die eingestellte Primärfarbe (nicht mehr das voreingestellte Indigo) 🔎
- Das Symbol auf dem Startbildschirm ist das hochgeladene 🔎
- Die Seite lässt sich am Anschlag nicht gummiartig überziehen
- Offline: Die App startet und zeigt die Anmeldeseite – mehr geht nicht, weil jede Datei vom Server kommt. Kein Fehlerbildschirm 🔎
Etiketten anpassen (23.08.2026)
- Beim Anlegen erscheint eine Farbwahl, sobald man tippt 🔎
- Zwei nacheinander angelegte Etiketten bekommen verschiedene Farben
- Rechtsklick auf ein Etikett in der Seitenleiste öffnet ein kleines Menü mit „Edit…" und „Delete" – nicht sofort einen Dialog 🔎
- „Edit…" öffnet Umbenennen und Farbwahl (ohne Löschknopf daneben) 🔎
- „Delete" fragt zurück und nennt die Zahl der betroffenen Dateien 🔎
- Umbenennen wirkt sofort – auch an den Aufklebern der Kacheln, ohne Neuladen 🔎
- Farbe ändern wirkt sofort an Punkt und Aufklebern
- Die Rückfrage sagt ausdrücklich, dass die Dateien bleiben 🔎
- Nach dem Löschen verschwinden die Aufkleber an den Kacheln sofort 🔎
- Der Zählstand in der Seitenleiste geht direkt hoch, wenn man etikettiert 🔎
OIDC scharf schalten (23.08.2026)
Der Durchgang vor dem Produktivgang. Reihenfolge zählt hier: Zuerst
OIDC_LINK_BY_USERNAME=true setzen, dann zum ersten Mal über PocketID
anmelden. Andersherum entsteht ein zweites Konto, und die Dateien liegen danach
auf zwei Konten verteilt.
.env:OIDC_ISSUER,OIDC_CLIENT_ID,OIDC_CLIENT_SECRET,OIDC_REDIRECT_URLgesetzt; Redirect-URL zeigt aufhttps://<domain>/api/v1/auth/oidc/callbackund ist bei PocketID identisch hinterlegtOIDC_LINK_BY_USERNAME=truevor dem ersten Login- Der Benutzername bei PocketID (
preferred_username) ist derselbe wie in Aether – sonst greift die Verknüpfung nicht - Anmeldeseite zeigt den SSO-Knopf 🔎
- Erster OIDC-Login landet auf dem bestehenden Konto: gleiche Dateien, Adminrechte noch da 🔎
- Danach steht in Settings → Account der Hinweis statt des Passwortformulars 🔎
- Zweiter Login findet dasselbe Konto (kein
name2in der Nutzerliste) 🔎 - WebDAV funktioniert weiter – App-Passwörter sind von OIDC unberührt
- Das Kontopasswort funktioniert für den Web-Login nicht mehr (Konto hat keines mehr)
Passwort bei OIDC-Konten (23.08.2026)
Wer sich über PocketID anmeldet, hat in Aether kein Passwort (password
steht auf ''). Das Formular konnte also nur fehlschlagen – und zwar mit
„current password incorrect", was nach einem Tippfehler aussieht statt nach dem
wahren Grund.
- Als OIDC-Konto zeigt Settings → Account statt des Formulars den Hinweis „You sign in through your identity provider…" 🔎
- Als Konto mit Passwort ist das Formular unverändert da 🔎
POST /auth/change-passwordals OIDC-Konto →400mit klarer Meldung, nicht401„current password incorrect" 🔎- Ein OIDC-Konto kann sich nicht über das Passwortfeld anmelden
(
''ist kein gültiger Argon2-Hash – galt schon vorher, bitte trotzdem einmal probieren)
Nutzerbereich oben rechts (23.08.2026)
Der Block saß bis dahin unten links in der Seitenleiste – mit dem Abmelden- Symbol direkt neben dem Knopf, der zu den Einstellungen führt. Abmelden ist die einzige Handlung, die man nicht zurücknehmen kann, und war die am leichtesten erreichbare.
- Oben rechts steht Avatar + Anzeigename, in jeder Ansicht an derselben Stelle 🔎
- Nur der Anzeigename, nicht zusätzlich die Kennung darunter 🔎
- Klick öffnet das Menü: Name, Kennung (nur wenn sie sich unterscheidet), „Administrator", dann Settings / Admin panel / Sign out
- „Sign out" steht zuletzt, rot und durch eine Trennlinie abgesetzt
- Klick daneben schließt, Esc schließt
- Unten links steht nur noch der Speicherbalken – kein Nutzer, kein Abmelden 🔎
- „Admin panel" erscheint nur bei Administratoren
- Die Kopfleiste bleibt in allen Ansichten gleich hoch (der Nutzerbereich wächst sie nicht auf)
Dateien auf dem Wirt / SFTP (23.08.2026)
Die Daten liegen seit dem 23.08.2026 in einem gewöhnlichen Verzeichnis
(DATA_PATH), nicht mehr in einem Docker-Volume.
ls -la $DATA_PATH/users/<name>/home/zeigt echte Ordner und Namen, und die Dateien gehören dir – keinsudonötig 🔎- Eine hochgeladene Datei taucht dort sofort mit ihrem Namen auf 🔎
- Gelöschte Datei liegt unter
.trash/, wiederhergestellte wieder inhome/ - Per SFTP von einem anderen Rechner:
get -r …/homeholt alles - Upload, Vorschau, WebDAV und OCR funktionieren wie vorher (der Container läuft jetzt unter deiner Kennung) 🔎
- Beim Umzug: Prüfsummen von Volume und Zielverzeichnis stimmen überein, bevor umgeschaltet wird 🔎
Office – Collabora / OnlyOffice / Euro-Office (24.08.2026)
Aether ist WOPI-Host; der Office-Server holt die Datei selbst ab und schreibt sie zurück. Gemessen wurde mit Collabora; OnlyOffice und Euro-Office sprechen dieselbe Schnittstelle, sind hier aber nie gelaufen (Arbeitsspeicher).
Öffnen
- Rechtsklick auf .odt/.docx/.ods → „Edit in Office…" erscheint 🔎
- Doppelklick öffnet den Editor statt der Vorschau 🔎
- Der Inhalt ist da – nicht nur die Werkzeugleiste 🔎
- Ohne Office-Server: Der Menüpunkt erscheint gar nicht erst 🔎
- Bei einem PDF erscheint er ebenfalls nicht (eigener Betrachter)
Speichern
- Text tippen, Strg+S → Fassung und Größe der Datei ändern sich 🔎 (gemessen: v1 → v3, 81 → 94 Zeichen im Dokument)
- Der getippte Text steht wirklich in der heruntergeladenen Datei 🔎
- Bei eingeschalteter Versionierung entsteht ein Eintrag im Verlauf
- Kontingent voll → Office meldet einen Fehler, die alte Fassung bleibt
- Schließen und neu öffnen zeigt den gespeicherten Stand
Rechte
- Datei mit Leserecht geliehen → „read only" in der Kopfzeile, kein Speichern
- Freigabe während der Bearbeitung zurückziehen → das nächste Speichern scheitert (die Rechte werden bei jedem WOPI-Aufruf neu aufgelöst)
- Token einer Datei an einer anderen Datei-ID →
404 - Abgelaufenes Token →
401 /wopi/files/<id>ohne Token →401🔎
Aufbau
- Schnipsel aus dem README anhängen,
docker compose up -d→ der Container entsteht (die Schnipsel haben kein Profil, siehe unten) 🔎 OFFICE_PROVIDERleer → kein Menüpunkt, auch wenn der Container läuft- Falscher Port in
OFFICE_URL(9980 statt 80 bei Euro-Office) → die Ermittlung findet nichts, der Menüpunkt bleibt weg OFFICE_ALLOWED_HOSTSgreift: eine fremde Adresse inOFFICE_URLwird abgelehnt statt angefragt- Sicherheitsrichtlinie nennt den Office-Ursprung unter
frame-src🔎 - Ohne Office-Server bleibt die Richtlinie so eng wie zuvor
Absender / Korrespondent (23.08.2026)
Das zweite Ordnungsmerkmal neben den Etiketten. Unterschied: genau einer je Dokument. Gesetzt im selben Dialog (Rechtsklick → „Organise…").
Setzen
- Namen tippen und speichern → Absender erscheint an der Kachel, in der Seitenleiste unter FROM, Zähler auf 1 🔎
- Zweites Dokument, Vorschlagsliste zeigt den vorhandenen Namen 🔎
- Gleicher Name in anderer Schreibweise („stadtwerke münchen") landet beim vorhandenen Absender und legt keinen Zwilling an 🔎
- Feld leeren und speichern → Absender weg, Etiketten und Belegdatum bleiben stehen 🔎
- Nur Leseberechtigung an einer geliehenen Datei → kein Speichern möglich
Liste und Ansicht
- Klick auf einen Absender in der Seitenleiste → „alles von X", absteigend nach Belegdatum sortiert 🔎
- Adresse
/from/<id>überlebt das Neuladen (Lesezeichen) 🔎 - „Download all as ZIP" in dieser Ansicht
- Zurück-Taste des Browsers führt aus der Absenderansicht heraus
Umbenennen und Löschen
- Rechtsklick → „Rename…" → alle bisherigen Dokumente behalten ihn 🔎
- Umbenennen auf einen schon vergebenen Namen → klare Meldung 🔎
- Rechtsklick → „Delete" fragt nach und sagt dabei, dass die Dokumente bleiben 🔎
- Nach dem Löschen: Dokumente da, Belegdatum da, nur die Zuordnung weg 🔎
Fremdzugriff
- Erratene Absender-ID an einer eigenen Datei →
400🔎 - Fremden Absender umbenennen oder löschen →
404(nicht403) 🔎
Gruppenordner
- Ein Absender in einem Gruppenordner gehört der Gruppe, nicht dem Anlegenden – alle Mitglieder sehen ihn
- Die Vorschlagsliste zeigt in einem Gruppenordner nur Gruppen-Absender
PDF-Betrachter (23.08.2026)
Eigener Betrachter statt des eingebauten im <iframe>. Gründe: Chrome auf
Android zeigte im <iframe> gar kein PDF an, und einem fremden Betrachter
lässt sich kein Suchwort vorgeben.
Werkzeugleiste
- Suchfeld sitzt mittig in der Leiste (gemessen: 0 Pixel Abweichung) 🔎
- Beim ersten Treffer rutscht das Feld nicht zur Seite – der Platz für die Sprungknöpfe steht immer bereit 🔎
- Am Telefon zwei Zeilen: Steuerung oben, Suche darunter, beide Sprungpfeile sichtbar 🔎
- Zoomprozent stimmt beim Öffnen (nicht 10 %) – dreimal hintereinander geprüft, war vorher vom Zufall abhängig 🔎
Grundlegend
- PDF per Doppelklick öffnen → Seiten erscheinen, Seitenzahl stimmt 🔎
- Blättern über die Pfeile der Werkzeugleiste 🔎
- Zoom −/+ und der Umschalter „ganze Seite ↔ 100 %" 🔎
- Nach dem Zoomen bleibt man auf derselben Seite – nicht am Anfang eines fremden Blattes 🔎 (war ein Fehler: sprang zurück zur Fundstelle)
- Text im PDF lässt sich markieren und kopieren 🔎
- Drucken über den Browser (Strg+P) – der Betrachter bringt keinen eigenen Druckknopf mit
Suche im Dokument
- Aus der Volltextsuche geöffnet → Suchwort steht im Feld, der Betrachter steht auf der Seite mit dem Treffer, das Wort ist orange 🔎
- Weitere Treffer blass markiert, der aktuelle kräftiger und mit Kante 🔎
- Das markierte Wort bleibt lesbar – der Streifen liegt darüber, nicht darauf 🔎
- Der Streifen sitzt auf der Zeile: nicht schief, oben nicht angeschnitten 🔎 (kein Treffer ragt aus der Seite heraus)
- Markiert wird das ganze Wort, nicht der Schnipsel: „la" färbt
Display,last,labex– nicht zwei Buchstaben mittendrin 🔎 (gemessen: unser Kasten und die Auswahl des Browsers liegen auf demselben Pixel, 357,08 / 10,38 – der Versatz der Ersatzschrift ist nicht wegzurechnen, wohl aber zu umgehen) - Sehr lange „Wörter" (Adresse, Prüfsumme) werden nicht ganz eingefärbt – dort bleibt es beim Fundstück
- Suchfeld leeren → alle Markierungen verschwinden 🔎 (war ein Fehler: die Färbung blieb zu einem Wort stehen, das im Feld längst nicht mehr stand)
- Während Treffer markiert sind, lässt sich Text weiter mit der Maus auswählen – die Markierungsebene fängt keine Klicks ab
- „Nächster Treffer" / „Voriger Treffer" springt der Reihe nach 🔎
- Beim Tippen ruckt nichts. Der Rand über der ersten Seite bleibt
stehen, das Blatt klebt nicht am Fensterrand 🔎 (gemessen:
scrollTopbleibt 0, Rand bleibt 16 px, kein einziges Roll-Ereignis) - Ist der Treffer schon zu sehen, wird gar nicht gerollt; erst ein Treffer außerhalb holt die Anzeige nach 🔎 (gemessen: 6 × Enter → Treffer 7 von 54, genau ein Roll-Ereignis)
- Enter im Suchfeld = nächster Treffer, Umschalt+Enter = voriger
- Scan (nur Bild, per OCR erkannt): Suche findet die Seite, springt hin, markiert aber nichts – Anzeige „1 / 5 · page" 🔎
- Ein Treffer nur im Dateinamen (nicht im Inhalt) füllt das Suchfeld nicht vor – sonst meldete der Betrachter „No hits" ohne Grund
- Fremde Datei:
GET /api/v1/files/<id>/text→404
Test-PDF für die Suche
~/Dokumente/aether-suchtest.pdf – zehn Seiten, jede eine eigene Falle.
Suchwort ist überall aether; ein Durchlauf deckt alle Fälle ab.
| Seite | Fall | Erwartung |
|---|---|---|
| 1 | Schriftgrößen 48 bis 4 pt | Kasten sitzt bei jeder Größe auf dem Wort |
| 2 | Times / Helvetica / Courier, fett, kursiv | dito – hier fällt der Versatz der Ersatzschrift am ehesten auf |
| 3 | Wortgrenzen: Bindestrich, Klammern, Umlaute, Ziffern, Unterstriche | ganzes Wort gefärbt; die 68-stellige Prüfsumme nicht |
| 4 | Zwei Spalten, 8 pt, enge Zeilen | kein Überlappen zwischen den Zeilen |
| 5 | Wort am Zeilenende getrennt (Ae- / ther) |
wird nicht gefunden – bekannte Grenze |
| 6 | gedreht: 90°, 45°, 180° | Kasten dreht mit |
| 7 | Tabelle | Treffer in Zellen |
| 8 | 66 Treffer auf einer Seite | Zähler stimmt, Weiterspringen läuft durch |
| 9 | Querformat | Zoom stellt sich um |
| 10 | Scan (nur Bild, leicht schief) | erst nach OCR, dann seitengenau: „· page", ohne Färbung |
Gemessen beim Bauen: 390 Treffer über 10 Seiten, keine Fehler in der Konsole.
Randfälle
- 60-seitiges PDF: höchstens 6 Seiten gleichzeitig gezeichnet, Suche über alle 60 findet die richtige Seite 🔎
- Passwortgeschütztes PDF → „This PDF is password protected." 🔎
- Kaputte Datei mit Endung .pdf → „Could not open this PDF." 🔎
- Andere Vorschauen unverändert (Bild, Text, Ton, Video) 🔎
Am Telefon
- PDF wird angezeigt (mit dem
<iframe>kam dort nur ein Download-Hinweis) 🔎 - Kein waagerechter Überlauf, Seite füllt die Breite 🔎
- Die Pfeile zum Blättern zwischen Dateien sind bei einem PDF ausgeblendet – sie lägen mitten auf dem Text 🔎
- Zwei Finger zum Zoomen (das macht der Browser selbst)
Tastatur
- Escape im Suchfeld des Betrachters leert das Feld, schließt nicht die Vorschau
- Pfeiltasten blättern nicht zur nächsten Datei, während man im Suchfeld schreibt 🔎
Erkannter Text mit Seitenmarken
- Nach dem Aktualisieren stehen Bestandsdateien auf
extract_version = 1und tragen Seitenmarken 🔎 - Dateien im Papierkorb werden nicht neu erkannt 🔎
Eigenschaften einer Datei (24.08.2026)
Rechtsklick → Properties…. Das, was Dateimanager unter Windows und Linux anbieten. Anlass: Bei einem Bild stand nirgends, wie groß es in Pixeln ist.
- Bild → Zeile „Dimensions" mit Breite × Höhe und Megapixeln 🔎 (gemessen: 2000 × 1851 px, 3.7 MP)
- Nicht-Bild (Archiv, PDF, Office) → keine Zeile „Dimensions" 🔎
- Größe steht zweimal: gerundet und auf das Byte genau 🔎
- Belegdatum, Absender und Etiketten erscheinen nur, wenn gesetzt 🔎
- „Version 2 (history kept)" bzw. „(history off)" passt zum Schalter „Keep versions" im selben Menü 🔎
- Klick auf die Prüfsumme kopiert sie („copied" erscheint kurz)
- Ohne HTTPS (kein sicherer Kontext) bleibt der Wert stehen statt zu kopieren – keine Fehlermeldung
- Die Maße kommen aus dem Browser, nicht vom Server: Ein Bildformat, das der Browser nicht kennt, lässt die Zeile bei „measuring…" – kein Fehler
Kachelansicht: Absender statt Größe (24.08.2026)
- Kachel mit Absender: links Symbol + Name, rechts das Etikett, keine Dateigröße 🔎
- Kachel ohne Absender: links die Größe wie bisher 🔎
- Alle Kacheln einer Reihe sind gleich hoch, auch mit langem Absendernamen (er wird abgeschnitten, nicht umgebrochen) 🔎
- Die Größe steht weiterhin in der Listenansicht und unter „Properties…"
Wurzelort heißt „Home"
- Brotkrume ganz links zeigt ein Haus-Symbol und „Home", nicht „My Files"
- Seitenleiste ebenso
- Suchtreffer in der Wurzel stehen als „in Home"
Sortierung
- Auswahlfeld in der Werkzeugleiste: Name, Größe, Datum – je auf- und absteigend
- Ordner stehen immer vor Dateien und folgen nur der Namensrichtung
- Sortierung und Ansicht (Kacheln/Liste) überleben das Neuladen
Downloads & Adressen (PWA-Fallen)
- Rechtsklick → „Download" lädt die Datei – ohne dass ein leerer Tab aufgeht
- Download aus dem Versionsverlauf funktioniert
- Download aus „Shared with me" funktioniert
/adminoder/profiledirekt in der Adresszeile eingeben → Anwendung lädt, kein 404 🔎- Freigabelink
/s/<token>in einem frischen Browser öffnen (privates Fenster, kein Service Worker) → Seite lädt 🔎 - Nach dem Update einmal hart neu laden (
Strg+Shift+R), damit der alte Service Worker ersetzt wird
Profilbild vom Identitätsanbieter (24.08.2026)
Gemessen an der laufenden Instanz auf 127.0.0.1:8090:
| Fall | Erwartet | Gemessen |
|---|---|---|
Bild hinterlegt, /me |
avatar_ver > 0 |
1787571663 |
GET /users/<id>/avatar?v=… |
200, image/png, 256×256 |
✅, private, max-age=604800, immutable |
| ohne Anmeldung | 401 |
✅ |
| Konto ohne Bild | 404 |
✅ |
| erfundene Konto-ID | 404, nicht 403 |
✅ |
| Blob fehlt, Zeile steht | Buchstabenkreis, kein Loch | ✅ (Screenshot) |
| GC-Lauf | Bild bleibt, 0 Waisen | ✅ (122 gescannt) |
verwaiste Datei unter avatars/ |
wird gefunden, Schonfrist greift | ✅ (123 gescannt, 1 jung) |
Geprüft mit Go-Tests, nicht am laufenden Anbieter:
- Zuschnitt: 800×200, 200×800, 512×512 und 16×16 ergeben alle 256×256; bei einem breiten Bild bleibt die Mitte stehen (linke Kante rot, rechte blau).
- JPEG wird angenommen, Unsinn (
"",<svg/>, Text) abgelehnt. - SSRF:
169.254.169.254,localhost:8080,postgres:5432,id.example.com.angreifer.net,id.example.com:9200,file://,gopher://werden alle abgewiesen; eine Weiterleitung auf einen fremden Rechner wird nicht befolgt. - Ein Anbieter, der erst
401sagt, bekommt genau einen zweiten Versuch mit Token – nicht mehr. - Eine 8-MB-Antwort wird bei 4 MB abgeschnitten.
Noch offen (braucht deinen echten PocketID):
- Meldet PocketID
pictureim ID-Token oder erst über UserInfo? - Kommt das Bild ohne Token heraus, oder greift der zweite Versuch?
- Bild in PocketID austauschen → steht nach dem nächsten Anmelden hier
- Bild in PocketID entfernen → das alte bleibt hier stehen (so gebaut: gelöscht wird nur beim Austausch, nicht auf Verdacht)
Kopfleiste auf dem Handy (24.08.2026)
Gemessen bei 412×915, angemeldet:
| vorher | nachher | |
|---|---|---|
padding-top |
0px | 14px |
padding-bottom |
0px | 10px |
| Oberkante Titel | 8px | 22px |
Ursache war nicht der gewählte Wert, sondern dass die Regel nie griff: Der
Inline-Stil (padding: '0 14px', paddingTop: 'var(--safe-top)') schlug die
Klassenregel der schmalen Ansicht. Alle Abstände stehen jetzt im CSS-Block.
- Auf dem Handy gegenprüfen, installiert und im Browser-Tab – nur
installiert ist
--safe-topungleich 0.
Mitgeliefertes App-Symbol (24.08.2026)
Die drei PNG lagen als Graustufen vor: Der indigo Verlauf war zu Schwarz eingeebnet, die Unterkante der Wolke lief schief aus. Neu gezeichnet (drei Kreise plus Rechteck dazwischen, keine Freihandkurve) und in RGBA gerendert.
- Nach
docker compose pullerscheint das neue Symbol im Tab - Auf dem Startbildschirm frühestens nach Neu-Installieren (Chrome hält eine eigene Kopie)
Anleitung „Connect as a drive" (24.08.2026)
Neuer Info-Knopf unter Einstellungen → Access, neben den App-Passwörtern.
Die Adressen entstehen aus window.location, nicht aus PUBLIC_URL – was im
Browser trägt, trägt auch im Dateimanager.
Geprüft auf 1280×900 und 412×915:
-
Alle drei Reiter (Windows, Linux · Konsole, Linux · Dateimanager) öffnen.
-
Über HTTP erscheint der rote Hinweis und der Dateimanager-Reiter bietet
dav://an; über HTTPSdavs://und kein Hinweis. -
Die Kopierknöpfe stehen an WebDAV-Adresse, Benutzername, Cloud-Adresse und an jedem Befehlsblock.
-
Auf der produktiven Instanz gegenprüfen, dass dort
https://…/dav/steht und der HTTPS-Hinweis nicht erscheint -
Einen der Befehle wirklich ausführen (davfs2-Einhängen)
-
Windows-Weg auf einem echten Windows durchspielen – die 50-MB-Grenze und der Registry-Pfad stammen aus der Dokumentation, nicht aus einem Versuch
Kachelraster auf schmalen Geräten (24.08.2026)
Ein Galaxy Z Flip 7 (360px aufgeklappt) zeigte eine Spalte statt zwei: Bei 160px Mindestbreite und 20px Innenrand brauchen zwei Spalten 332px, da waren aber nur 320px. Schmal jetzt 132px Mindestbreite, 10px Abstand und 12px Innenrand.
| Breite | Spalten | Kachel | Innenrand | Querlauf |
|---|---|---|---|---|
| 316 (Deckelschirm) | 2 | 141px | 12px | nein |
| 360 (Flip 7) | 2 | 163px | 12px | nein |
| 412 (übliches Handy) | 2 | 189px | 12px | nein |
| 768 | 3 | 161px | 20px | nein |
| 1280 | 6 | 160px | 20px | nein |
Raster und Innenrand stehen jetzt als Klassen in index.css; inline gesetzt
hätte keine Media-Query sie erreicht.
- Auf dem Flip 7 selbst gegenprüfen, auf beiden Schirmen
Bild ziehen lud es erneut hoch (24.08.2026)
Zieht man das angezeigte Bild in der Vorschau ein Stück zur Seite, legt Chrome
es als echte Datei in den Ziehvorgang. dataTransfer.types enthält dann
Files – nicht zu unterscheiden von einer Datei aus dem Dateimanager. Die
Ablagefläche lud das Bild daraufhin ein zweites Mal hoch.
Nachgestellt und gemessen:
| Fall | vorher | nachher |
|---|---|---|
| Vorschaubild ziehen und ablegen | draggable=true, 1 Upload |
draggable=false, 0 Uploads |
| Datei von außen ablegen | 1 Upload, Overlay sichtbar | unverändert 1 Upload, Overlay sichtbar |
Zwei Riegel, weil einer allein zu leicht wieder aufgeht:
draggable={false}an allen Bildern, die kein eigenes Ziehen brauchen – Kachel-Vorschauen, Vorschaufenster, Suchtreffer, Papierkorb, Etiketten, Logo, Profilbild. Die Kachel bleibt ziehbar und verschiebt die Datei.hooks/useInternalDrag.ts: Was in dieser Seite losgezogen ist, wird nie als Upload behandelt.dragstartfeuert nur bei eigener Quelle; von außen gibt es keins.
- Auf dem Handy gegenprüfen: Ein Bild lässt sich mit dem Finger nicht mehr aus der Vorschau ziehen (dort war das Wischen ohnehin gemeint)
Sammel-Löschen und Ziehen auf den Papierkorb (24.08.2026)
Zwei Wege zum selben Ziel: der Knopf in der Auswahlleiste (mit Rückfrage) und das Ziehen auf den Papierkorb in der Seitenleiste (ohne).
Gemessen an einem Testordner mit fünf Dateien:
| Handlung | Erwartet | Gemessen |
|---|---|---|
| drei markieren, eine davon auf den Papierkorb ziehen | alle drei | aether-file-ids trägt 3 IDs, 3 DELETE, Kacheln 5 → 2 |
| über dem Papierkorb schweben | rot umrandet | rgb(239,68,68) + Rahmen; danach wieder grau |
| zwei markieren, „Move to trash" | Rückfrage, dann weg | Dialog „Move 2 files…", 2 DELETE, Kacheln 0, Leiste weg |
Der Sammel-Aufruf läuft mit vier gleichzeitigen Anfragen. Fehlgeschlagene Dateien bleiben markiert, statt die Auswahl zu verlieren.
- Am Finger gegenprüfen: Ziehen auf den Papierkorb setzt eine Maus voraus, auf dem Handy bleibt der Knopf der Weg
- Mit vielen Dateien (50+) auf der produktiven Instanz gegenprüfen
PDF: Strg + Mausrad, Zoom nach rechts (24.08.2026)
| Prüfung | Gemessen |
|---|---|
| Zoomgruppe breit | rechts außen, 1138–1262 px bei 1280 px Fenster |
| Suchfeld bleibt mittig | Mitte 640, Fenstermitte 640 |
| Strg + Rad hoch | 100 % → 125 % |
| Strg + Rad runter (2×) | → 75 % |
| Rad ohne Strg | unverändert 75 %, blättert nur |
preventDefault gerufen |
ja – sonst zoomte der Browser die ganze Seite |
| Punkt unter dem Zeiger | bleibt: Seite 2 vor und nach zwei Zoomstufen, Bildlauf 1400 → 2250 |
| Schmal (412 px) | Zoom in der ersten Zeile neben der Seitenzahl, zweizeilig wie vorher |
Beim Rad wird der Punkt unter dem Zeiger festgehalten, nicht die Seite: Man zeigt auf eine Stelle und will, dass sie dort bleibt. Die Knöpfe halten weiterhin die Seite – dort gibt es keinen Zeiger, auf den man sich beziehen könnte.
- Am Trackpad gegenprüfen (Zwei-Finger-Zoom meldet sich als Strg + Rad)
Hochkant-Fotos lagen im Vorschaubild quer (24.08.2026)
Kameras speichern die Bildpunkte fast immer quer und legen die Drehung als EXIF-Notiz daneben. Browser befolgen sie von selbst – Go nicht. Gemessen an einem Testbild mit 400×200 Bildpunkten und Drehung 6:
| vorher | nachher | |
|---|---|---|
| große Ansicht (Browser) | 200×400 hochkant ✅ | unverändert |
| Vorschaubild (Server) | 256×128 quer ❌ | 128×256 hochkant ✅ |
Alle acht Fälle, beide Byte-Reihenfolgen (II und MM):
- Drehung 1 und 3 bleiben quer, 6 und 8 werden hochkant – Seitenverhältnis allein genügt aber nicht als Beweis, deshalb ein gelber Balken als Marke: bei 6 landet er oben, bei 8 unten. Beide Bilder angesehen.
- Leere Datei, Text statt JPEG, abgeschnittener EXIF-Block, Drehung 0 und 99 ergeben alle 1 („nicht drehen") statt eines Fehlers.
- Bei Drehung 1 entsteht keine Kopie des Bildes.
Bestehende Vorschaubilder ändern sich davon nicht – sie liegen fertig im Speicher und werden nicht neu erzeugt. Ein Foto, das heute quer liegt, liegt auch morgen quer, bis es erneut hochgeladen wird.
Ein Knopf zum Neuerzeugen war kurz da und wurde wieder entfernt (130 Zeilen für einen einmaligen Bedarf).
- Auf der produktiven Instanz mit echten Handy-Fotos gegenprüfen – neu hochgeladene, nicht die bestehenden
Vorschaubilder: Zwischenspeicher, Sofortanzeige, Flackern (25.08.2026)
Gleicher Dateiname = gleiche Datei. Gemessen: dreimal dieselbe Datei
hochgeladen ergibt dreimal dieselbe ID mit den Fassungen 1, 2, 3. Die
Adresse des Vorschaubildes war damit unverändert, und es liegt einen Tag im
Browser (max-age=86400) – die Kachel zeigte bis zu 24 Stunden lang das alte
Bild. Die Adresse trägt jetzt ?v=<Fassung>.
Vorschaubild ohne Neuladen. Der Server erzeugt es im Hintergrund, die Antwort auf den Upload ist längst da. Die Kachel fragt jetzt bis zu fünfmal nach (0,4 s bis 5 s Abstand), aber nur bei Bildern, die jünger als zwei Minuten sind – sonst fragte jede dauerhaft fehlgeschlagene Datei bei jedem Öffnen des Ordners erneut.
Gemessen: Bild über das Datei-Feld hochgeladen, Vorschaubild 608 ms später
sichtbar, performance zählt genau eine Navigation – also kein Neuladen.
Flackern am Kachelrand. Beim Überfahren wurde die Kachel um einen Pixel
angehoben. Steht der Zeiger genau auf der Unterkante, rutscht sie unter ihm
weg → mouseleave → sie fällt zurück → mouseenter, im Kreis. Das Anheben
ist raus, der Rahmen in der Primärfarbe zeigt dasselbe an, ohne die Kachel zu
bewegen. Nachher gemessen: Zeiger 1,5 s auf der Unterkante gehalten, 0
Ereignisse, transform: none.
- Das Flackern selbst konnte ich nicht nachstellen: Ein 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
brauchen keinen manuellen Durchgang mehr, stehen hier aber als Beleg:
- Prüfsummen über 8 Größen (0 B, 1 B, 511/512/513 B, 64 KB, 1 MB+1, 20 MB): WebDAV rein → WebDAV raus → REST raus, jedes Mal identisch. Gespeicherte Prüfsumme = tatsächliche Prüfsumme auf der Platte, 0 Abweichungen.
- Pfad-Ausbruch: 5 WebDAV-Varianten (
../, URL-kodiert, absolut) und 5 REST-Dateinamen – nichts landet außerhalb des Nutzerbereichs. - Kontingent:
413über REST,507über WebDAV, keine Datei-Torsos, kein Rest im Staging-Verzeichnis. - Soft-Delete: gelöschte Datei liegt im Papierkorb, Download
404, nach Wiederherstellung Inhalt unverändert. - Sperre: 10 Fehlversuche →
429mitRetry-After: 900; ein Erfolg setzt den Zähler zurück. - Passwort-Trennung: Kontopasswort → WebDAV
401, App-Passwort → Web-Login401, App-Passwort eines fremden Kontos →401. - Isolation: fremdes Konto bekommt auf 9 Endpunkte plus WebDAV durchgängig
404,shared-with-meleer. - Audit-Log: jede sicherheitsrelevante Aktion erzeugt einen Eintrag;
zurückgesetztes Passwort, App-Passwort, Freigabe-Token und Share-Passwort
stehen nicht darin; Einträge überleben das Löschen des Kontos
(
actor_idwirdNULL, Name bleibt); Filter, Blättern und die Obergrenze von 500 greifen; Nicht-Admin403, ohne Anmeldung401. - Blob-Bereinigung: DB-Referenzen und Platte stimmen exakt überein (123 zu 123); ein künstlich gealtertes Waisen-Blob wird entfernt, das eingetragene Logo nicht.
Regressionstest (sollte weiter funktionieren)
- Normaler Datei-Upload (Web) mit Fortschrittsanzeige
- Papierkorb: löschen → wiederherstellen
- Share-Link erstellen (mit Passwort + Ablauf) und öffentlich öffnen
- Favoriten in Sidebar: hinzufügen, umsortieren, navigieren
- Ordner per Drag&Drop in anderen Ordner verschieben
- „Verschieben nach…"-Dialog
- Ordner-Upload per Drag & Drop (ganzer Ordner inkl. Unterordner wird angelegt)