Der Office-Container entstand nie, ohne Fehlermeldung: profiles: [office] startet nur mit --profile office, und in Portainer/Dockge/Komodo gibt es diesen Schalter gar nicht. COMPOSE_PROFILES=office in der .env wirkt genauso und braucht keine Kommandozeile – Compose liest die Variable fuer sich selbst. Gemessen mit den echten Dateien: ohne die Zeile 'app postgres', mit ihr 'app office postgres'. Steht jetzt in beiden .env.example, beiden Compose-Kopfzeilen, README und TESTING. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1242 lines
60 KiB
Markdown
1242 lines
60 KiB
Markdown
# 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.
|
||
|
||
---
|
||
|
||
## Phase 3 – Admin, Gruppen, Theming, Profil
|
||
|
||
### Admin-Panel: Zugriff & Sicherheit
|
||
- [X] Admin-Link erscheint **nur** für Admin-User in der Sidebar
|
||
- [X] Normaler User sieht den Admin-Link **nicht**
|
||
- [X] Normaler User, der `/api/v1/admin/users` direkt aufruft, bekommt `403` (serverseitiger Check)
|
||
- [X] Nach Logout ist kein Admin-Endpunkt erreichbar (`401`)
|
||
|
||
### Admin: Benutzerverwaltung
|
||
- [x] Benutzerliste zeigt alle User mit verwendetem Speicher + Quota + Rolle
|
||
- [x] Neuen User anlegen (Username, Anzeigename, Passwort, Quota, Admin-Flag)
|
||
- [x] Anlegen mit existierendem Username → Fehler „bereits vergeben" (`409`)
|
||
- [x] User bearbeiten (Anzeigename, Quota, Admin-Flag ändern)
|
||
- [x] **Selbst-Schutz:** eigenen Admin-Status kann man **nicht** entfernen (Fehlermeldung)
|
||
- [x] *Selbst-Schutz:** eigenes Konto kann man **nicht** löschen
|
||
- [x] Passwort-Reset generiert neues Passwort und zeigt es **einmalig** an
|
||
- [x] Neuer User kann sich mit dem zurückgesetzten Passwort einloggen
|
||
- [ ] User löschen entfernt den User (und seine Dateien) 🔎
|
||
|
||
### Admin: Gruppen
|
||
- [x] Gruppen-Tab lädt ohne schwarzen Bildschirm (war der Bug – jetzt gefixt)
|
||
- [x] Gruppe anlegen (Name + Beschreibung)
|
||
- [x] Gruppe aufklappen zeigt Mitglieder
|
||
- [x] Mitglied über Dropdown hinzufügen → Zähler steigt
|
||
- [x] Mitglied entfernen → Zähler sinkt
|
||
- [x] Gruppe mit doppeltem Namen → Fehler (`409`)
|
||
- [x] Gruppe löschen (auch mit Mitgliedern – Cascade)
|
||
- [ ] ⚠️ *Gruppen haben noch keine Funktion (keine Gruppenordner) – Phase 4.5/C, offen*
|
||
|
||
### Admin: Theming
|
||
- [x] Cloud-Name ändern → erscheint nach Reload überall (Login-Seite, Sidebar)
|
||
- [x] Primärfarbe ändern → Buttons/Akzente übernehmen die Farbe sofort
|
||
- [x] Akzentfarbe ändern → Vorschau aktualisiert live
|
||
- [x] Nach Reload sind die Farben persistent
|
||
- [ ] ⚠️ *Logo-Upload noch nicht vorhanden – Phase 4.5/D, offen*
|
||
|
||
### Profil-Seite (jeder User)
|
||
- [x] Avatar/Name unten in der Sidebar öffnet die Profil-Seite
|
||
- [x] Speicheranzeige (Balken) stimmt mit tatsächlicher Nutzung
|
||
- [ ] Anzeigename ändern → wird gespeichert (✓-Bestätigung) 🔎
|
||
- [x] Passwort ändern: falsches aktuelles Passwort → Fehler
|
||
- [x] Passwort ändern: neue Passwörter stimmen nicht überein → Fehler
|
||
- [x] Passwort ändern: < 8 Zeichen → Fehler
|
||
- [x] Passwort erfolgreich ändern → Logout → Login mit neuem Passwort klappt
|
||
- [x] Persönliche Farben ändern → werden angewendet
|
||
- [x] 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 `401` hin – und beginnt dabei sofort mit dem Rumpf, ohne
|
||
> `Expect: 100-continue` abzuwarten. 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 die `401` überhaupt zu sehen bekommt.
|
||
|
||
> Alle Punkte hier wurden am 19.08.2026 per `curl` gegen 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.
|
||
|
||
- [x] Login mit Username + **App-Passwort** → Verbindung steht 🔎
|
||
- [x] Login mit falschem Passwort → abgelehnt (`401`) 🔎
|
||
- [x] Login mit dem **Kontopasswort** → abgelehnt (`401`), auch wenn es stimmt 🔎
|
||
- [x] Ordnerstruktur entspricht der in der Web-App (gleiche Ordner sichtbar) 🔎
|
||
|
||
### WebDAV – Lesen
|
||
- [x] Ordner auflisten (PROPFIND) zeigt Dateien + Unterordner 🔎
|
||
- [x] Datei herunterladen → Inhalt korrekt 🔎
|
||
- [x] Datei, die in der Web-App hochgeladen wurde, ist via WebDAV sichtbar 🔎
|
||
|
||
### WebDAV – Schreiben
|
||
- [x] Datei hochladen (PUT) → erscheint danach in der Web-App 🔎
|
||
- [x] Neuen Ordner anlegen (MKCOL) → erscheint in der Web-App 🔎
|
||
- [x] Datei umbenennen (MOVE im selben Ordner) 🔎
|
||
- [x] Datei in anderen Ordner verschieben (MOVE) 🔎
|
||
- [x] Ordner umbenennen 🔎
|
||
- [x] Datei löschen (DELETE) → landet im Papierkorb der Web-App (nicht hart gelöscht!) 🔎
|
||
- [x] 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 🔎
|
||
- [x] **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/audit` ein `403`
|
||
|
||
### 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 <Gruppe>"
|
||
- [ ] 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` (nicht `403` –
|
||
sonst verriete die Antwort, dass die ID existiert)
|
||
- [ ] Freigaben tauchen im Audit-Log als `share.grant` / `share.revoke` auf
|
||
|
||
> Ü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` + Gruppenordner `Projekt`
|
||
→ eigener behält den Namen, Gruppenordner heißt `Projekt (<Gruppe>)`,
|
||
beide erreichbar, Schreibvorgänge landen jeweils richtig 🔎
|
||
- [ ] Nach Austritt aus der Gruppe verschwindet er aus der Wurzel 🔎
|
||
- [ ] `Shared` erscheint 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` (nicht `404`/`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
|
||
- [x] Logo hochladen → erscheint sofort in der Vorschau, Seitenleiste und
|
||
auf der Anmeldeseite (ohne Speichern-Klick) 🔎
|
||
- [x] 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 → `413` mit 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 → `422` mit 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=false` in der `.env` → Cloud läuft normal, nur ohne
|
||
Inhaltstreffer; `/api/v1/admin/ocr` meldet `enabled:false`
|
||
|
||
**Platte** (OCR ist das Einzige, was von allein Platz verbraucht):
|
||
|
||
- [ ] Während der Erkennung eines großen Scans bleibt `/data/tmp` fast leer –
|
||
`docker compose exec app du -sh /data/tmp` sollte im Kilobyte-Bereich
|
||
liegen, nicht im Megabyte-Bereich 🔎
|
||
- [ ] Nach der Erkennung liegen dort keine `ocr-*`-Dateien mehr 🔎
|
||
- [ ] `docker kill` mitten 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 (`%e` findet **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=1`
|
||
als Download ausgeliefert, nicht angezeigt 🔎
|
||
- [ ] Fremde Datei über `?inline=1` → `404` wie beim normalen Download 🔎
|
||
|
||
## Kachel-Cover nach Dateityp
|
||
|
||
- [x] Ton, Video, Archiv, Tabelle, **Präsentation**, Code, PDF und Dokument
|
||
haben je eigenes Symbol und eigene Farbe
|
||
- [x] Word (blau), Excel (grün), PowerPoint (orangerot) sind auf einen Blick
|
||
auseinanderzuhalten – auch die OpenDocument-Entsprechungen
|
||
(`.odt`, `.ods`, `.odp`) 🔎
|
||
- [x] PDF und Word-Dokument haben **verschiedene** Symbole, nicht nur
|
||
verschiedene Farben
|
||
- [x] `.md` und `.txt` zählen als Text (grau), nicht als Office-Dokument
|
||
- [x] Unten links steht die Endung als Aufkleber (`MP3`, `TAR.GZ`, `CSV`)
|
||
- [ ] Ton und Video tragen zusätzlich ein Abspielzeichen
|
||
- [ ] `.tar.gz` wird als Archiv erkannt, obwohl die Endung zweiteilig ist 🔎
|
||
- [x] 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 `.mp3` erscheint als `audio/mpeg`, nicht als
|
||
`application/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 `/admin` auch 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/ocr` ohne 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.gz` doppelt -> "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 `onClick` wurde vom danach
|
||
> notierten `onClick` stillschweigend ü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_PASSWORD` mit `$$` → `password_length` stimmt, 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
|
||
ist `profiles: ["office"]`: Ein Dienst in einem Profil startet nur, wenn das
|
||
Profil ausdrücklich verlangt wird.
|
||
|
||
Auf der Kommandozeile mit `--profile office`. **In einer Oberfläche
|
||
(Portainer, Dockge, Komodo) gibt es diesen Schalter nicht** – dort führt kein
|
||
Weg über die Bedienung, und genau da fällt man herein.
|
||
|
||
Lösung: eine Zeile in der `.env`, die Compose für sich selbst liest.
|
||
|
||
```env
|
||
COMPOSE_PROFILES=office
|
||
```
|
||
|
||
Gemessen mit den echten Dateien:
|
||
|
||
```
|
||
ohne COMPOSE_PROFILES: app postgres
|
||
mit COMPOSE_PROFILES: app office postgres
|
||
```
|
||
|
||
- [ ] Ohne die Zeile entsteht kein Office-Container 🔎
|
||
- [ ] Mit der Zeile bringt ein einfaches `docker compose up -d` ihn mit 🔎
|
||
- [ ] Auskommentiert lassen, solange kein Office gewollt ist – der Container
|
||
kostet rund 1 GB
|
||
|
||
---
|
||
|
||
## 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:
|
||
|
||
```bash
|
||
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_BIND` bindet der Port auf `127.0.0.1` 🔎
|
||
- [ ] `APP_BIND=0.0.0.0` bindet auf alle Adressen 🔎
|
||
- [ ] Mit `0.0.0.0` von einem zweiten Gerät im Netz erreichbar
|
||
- [ ] Hinter dem Reverse Proxy wieder auf `127.0.0.1` zurü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` 🔎
|
||
- [ ] `/health` 200, Anmeldung 200, `data/tmp` liegt 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_PASSWORD` mit `%`, `@`, `:`, `/` und Leerzeichen → Server startet,
|
||
Migrationen laufen, Anmeldung geht 🔎 (gemessen mit `8UoNj%CoWJ7ze@qy`
|
||
in einer eigenen Umgebung: `migrations up to date`, `/health` 200,
|
||
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.example`
|
||
startet ohne Nacharbeit
|
||
- [ ] `APP_ENV`, `DB_HOST` und `DB_PORT` stehen 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_URL` gesetzt; Redirect-URL zeigt auf
|
||
`https://<domain>/api/v1/auth/oidc/callback` und ist bei PocketID
|
||
**identisch** hinterlegt
|
||
- [ ] `OIDC_LINK_BY_USERNAME=true` **vor** 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 `name2` in 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-password` als OIDC-Konto → `400` mit klarer Meldung,
|
||
nicht `401` „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** – kein `sudo` nötig 🔎
|
||
- [ ] Eine hochgeladene Datei taucht dort sofort mit ihrem Namen auf 🔎
|
||
- [ ] Gelöschte Datei liegt unter `.trash/`, wiederhergestellte wieder in
|
||
`home/`
|
||
- [ ] Per SFTP von einem anderen Rechner: `get -r …/home` holt 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 (24.08.2026)
|
||
|
||
Aether ist WOPI-Host; der Office-Server holt die Datei selbst ab und schreibt
|
||
sie zurück. Getestet mit Collabora im Compose-Profil `office`.
|
||
|
||
### Ö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
|
||
- [ ] Ohne `--profile office` startet der Container nicht 🔎
|
||
- [ ] `OFFICE_ALLOWED_HOSTS` greift: eine fremde Adresse in `OFFICE_URL` wird
|
||
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` (nicht `403`) 🔎
|
||
|
||
### 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: `scrollTop`
|
||
bleibt 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 = 1`
|
||
und 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
|
||
- [ ] `/admin` oder `/profile` direkt 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
|
||
|
||
---
|
||
|
||
## 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 → `429` mit `Retry-After: 900`; ein Erfolg
|
||
setzt den Zähler zurück.
|
||
- **Passwort-Trennung:** Kontopasswort → WebDAV `401`, App-Passwort → Web-Login
|
||
`401`, App-Passwort eines fremden Kontos → `401`.
|
||
- **Isolation:** fremdes Konto bekommt auf 9 Endpunkte plus WebDAV durchgängig
|
||
`404`, `shared-with-me` leer.
|
||
- **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_id` wird `NULL`, Name bleibt); Filter, Blättern und die Obergrenze
|
||
von 500 greifen; Nicht-Admin `403`, ohne Anmeldung `401`.
|
||
- **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)
|