Commit Graph
25 Commits
Author SHA1 Message Date
marcandClaude Opus 5 ded1134f6a feat(pdf): Zoomstufen von 10 % auf 5 % verfeinert
Marc las am Telefon Sprünge von acht Prozentpunkten – die Stufe ist
relativ, nicht in Punkten: Bei 65 % sind zehn Prozent sieben Punkte, bei
200 % zwanzig. Fünf Prozent treffen das Gemeinte besser.

Die Schwelle der Geste folgt der Stufenweite (vorher 8 % / 7 %), sonst
ließe sich die feinere Weite mit den Fingern gar nicht treffen.

Kein Ruckeln dadurch: Die Zeichenschleife bricht eine laufende
Darstellung ab, sobald sich der Maßstab ändert – von zwanzig Stufen
einer Geste wird nur die letzte fertig gemalt.

Gemessen, dieselbe Geste:
  65 → 68 → 71 → 75 → 79 → 83 → 87 → 91 → 96 → 101 → 106 → 111 → 117 → 123 → 129 %

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 00:52:37 +02:00
marcandClaude Opus 5 9c5cda75d7 feat(office): Dokumente am Telefon als PDF ansehen statt bearbeiten
Am Finger öffnet ein Office-Dokument jetzt das gewandelte PDF im eigenen
Betrachter; "Edit" daneben führt trotzdem in den Editor. Mit Maus ändert
sich nichts.

Der Web-Editor ist eine vollständige Textverarbeitung – zwischen seinen
Leisten bleibt auf 400 Pixeln vom Dokument ein Rechteck. Collabora und
OnlyOffice verhalten sich gleich; deshalb kein besserer Bedienmodus,
sondern ein anderes Werkzeug. Gewandelt wird von dem Server, der auch
bearbeitet: Sonst sähe man unterwegs ein LibreOffice-Layout eines
Dokuments, für das man OnlyOffice gewählt hat.

Je Dokument gibt es GENAU EIN PDF, und das ist keine Frage des
Aufräumens, sondern des Schlüssels: previews/<id>.pdf trägt die Fassung
nicht im Namen, eine neue Fassung überschreibt. files.preview_version
sagt, ob das Abgelegte noch passt.

Sicherheit: Die Adresse, die OnlyOffice zurückgibt, wird von uns
abgerufen – der Wähler des Transports lässt nur OFFICE_ALLOWED_HOSTS zu,
zusätzlich eine ausdrückliche Prüfung mit Test. JWT-Signatur (HS256) für
die eigene API des Dokumentenservers. Fremde Kennung 404, ohne Anmeldung
401. Das Ergebnis muss mit %PDF- beginnen, sonst landete eine
Fehlerseite im Zwischenspeicher.

Gemessen gegen einen echten OnlyOffice-Server im Docker-Netz:
  erster Abruf 1,05 s -> zweiter 0,009 s
  erneut hochgeladen -> Fassung 2, preview_version 2, weiterhin 1 Datei
  GC: herrenloses PDF von vorgestern weg, echtes bleibt
  endgültig gelöscht -> PDF weg; Papierkorb -> PDF bleibt
  ohne Anmeldung 401, fremde Kennung 404, Nicht-Office 415

Dazu im Admin-Bereich neu: Platzbedarf der abgeleiteten Daten
(Vorschaubilder, PDFs, Profilbilder). Sie zählen gegen kein Kontingent –
ein Ordner, der wächst, ohne dass ihn jemand sieht, ist eine stille
Überraschung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 23:30:49 +02:00
marcandClaude Opus 5 84ae570305 docs(office): aliasgroup1 und OFFICE_WOPI_URL richtiggestellt
Der Collabora-Schnipsel empfahl `aliasgroup1: http://app:8080` und
`OFFICE_WOPI_URL=http://app:8080`. Beides kann nicht funktionieren,
sobald der Browser die Cloud unter einer anderen Adresse sieht als der
Docker-Dienstname – also immer:

  * Collabora baut aus WOPISrc sein frame-ancestors. Steht dort der
    interne Name, lehnt der Browser die Einbettung ab.
  * aliasgroup1 ist die Positivliste der WOPI-Hosts. Steht der Host
    nicht drin: "No authorized hosts found matching the target host".

Beide Zeilen nennen jetzt die öffentliche Adresse, aliasgroup1 als
regulären Ausdruck mit Port. Dazu die Begründung im Text (samt der
Folge, dass Collabora die Datei dann über den Reverse Proxy holt), zwei
Zeilen in der Fehlertabelle und derselbe Hinweis in der .env.example.

Außerdem der Kenntnisstand zu OnlyOffice nachgezogen: Start, Umwandlung
nach PDF und JWT sind hier inzwischen gemessen, der WOPI-Kreis nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 22:27:20 +02:00
marcandClaude Opus 5 0723d853f0 feat(pdf): Finger und Mausrad zoomen in Zehnteln statt in Vierteln
Die Leiter der Knöpfe (0,5 / 0,75 / 1 / 1,25 / …) ist für eine Geste zu
grob – man schießt über die Größe hinaus, die man gerade sucht. Finger
und Mausrad gehen jetzt zehn Prozent pro Schritt, die Knöpfe behalten
ihre Leiter: Ein Klick soll etwas bewegen, eine Geste ist ein Regler.

Multiplikativ, nicht in festen Prozentpunkten: Von 50 auf 60 % ist ein
Fünftel mehr, von 300 auf 310 % ein Dreißigstel. Zehn Prozent vom
aktuellen Wert fühlen sich überall gleich an. Auf ganze Prozent
gerundet, sonst stünde 71,50000000000001 % in der Leiste.

Gemessen, dieselbe Geste wie vorher: 65 % -> 116 % statt 65 % -> 125 %,
zurück auf 71 % statt 50 %.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 21:17:57 +02:00
marcandClaude Opus 5 574ba3e6c5 feat(mobile): Zurück-Geste schließt die Ansicht, zwei Finger zoomen das PDF
Zwei Dinge, die am Telefon fehlten – beide in der installierten App
besonders schmerzhaft, weil es dort keine Adresszeile als Rückweg gibt.

1. Zurück schloss bisher die Anwendung, nicht die Vorschau. Vorschau,
   Office-Editor und die aufgeschobene Seitenleiste kamen im Verlauf des
   Browsers gar nicht vor: Der einzige Eintrag war die Dateiliste. Jetzt
   legt jede bildschirmfüllende Ansicht beim Öffnen einen leeren Eintrag
   an (die Adresse bleibt gleich – eine Vorschau ist kein Ort für ein
   Lesezeichen) und verbraucht ihn beim Schließen wieder, egal ob über
   die Geste, Escape oder den Knopf. Ein Stapel sorgt dafür, dass bei
   übereinanderliegenden Ansichten nur die oberste reagiert.

   Collabora blendet auf kleinen Schirmen seinen eigenen Schließen-Knopf
   aus (closeButtonEnabled && !isSmallScreenDevice) – ohne diesen Weg
   käme man aus einem Dokument am Telefon nur über die Kopfzeile heraus.

2. Die Zusammenziehgeste vergrößerte die ganze Seite statt des PDFs:
   Leiste und Knöpfe wuchsen mit, das Dokument blieb grob. touch-action
   nimmt dem Browser die Geste, ein eigener Zuhörer setzt unsere
   Zoomstufen an ihre Stelle, verankert an der Mitte zwischen den
   Fingern – dieselbe Regel wie bei Strg+Mausrad.

Gemessen in Chromium mit Touch-Emulation, 412x900:
  Vorschau offen -> Verlauf 2->3, Zurück -> Vorschau zu, Anwendung bleibt
  Esc -> Eintrag wieder weg (ein weiteres Zurück verlässt die App)
  Aufziehen 65% -> 125%, Zuziehen -> 50%, visualViewport.scale bleibt 1

Ein Befund aus dieser Messung steckt im Code: Zuerst kam pro Geste nur
eine Stufe, weil sich der Zuhörer bei jeder Zoomänderung neu anmeldete
und der gemerkte Fingerabstand dabei verlorenging. Abstand und Maßstab
liegen deshalb in Referenzen, der Zuhörer meldet sich einmal an.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 20:25:59 +02:00
marcandClaude Opus 5 face318131 docs(plan): Idee notiert – Dateien im Papierkorb ansehen (vertagt)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 15:01:21 +02:00
marcandClaude Opus 5 105b1a0e74 fix(trash): Vorschaubilder auch im Papierkorb ausliefern
GetThumbnail schlug die Datei mit is_deleted = false nach – im
Papierkorb also 404, und dort stand für jedes Bild nur ein graues
Quadrat. Man entschied über "endgültig löschen", ohne zu sehen, was es
war.

Schlägt der erste Griff fehl, wird jetzt im Papierkorb nachgesehen. Das
Vorschaubild überlebt das weiche Löschen ohnehin: Entfernt wird es erst
beim endgültigen Löschen, und der GC zählt es bis dahin als benutzt.

Geprüft gegen die laufende Instanz: Bild hochladen, löschen,
Vorschaubild abrufen -> 200, image/jpeg, 64x64.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 14:21:55 +02:00
marcandClaude Opus 5 451f359734 feat(uploads): fertige Meldungen verschwinden von selbst
Nach einem Stapel-Upload blieb unten rechts für jede Datei eine Karte
stehen, bis man sie einzeln wegklickte oder die Seite neu lud. Bei
vielen Dateien war der Bildschirm zu.

Fertige Meldungen räumen sich jetzt nach vier Sekunden selbst ab.
Fehler bleiben: Sie tragen die einzige Auskunft, die es sonst nirgends
gibt. Zusätzlich stehen höchstens vier Karten gleichzeitig, der Rest
wird gezählt – der Bildschirm läuft ja schon während des Hochladens
voll, nicht erst danach. Gescheiterte Dateien stehen dabei oben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:14:13 +02:00
marcandClaude Opus 5 8d95088271 feat(mobile): Wischen vom linken Rand holt die Seitenleiste
Bisher gab es dafuer nur den Knopf oben links - am Telefon die Ecke,
die man einhaendig am schlechtesten erreicht.

Vier Bedingungen halten die Geste aus dem Weg: nur im 44-px-Streifen am
Rand (sonst waeren waagerecht scrollbare Bereiche nicht mehr zu
bedienen), mindestens 60 px Weg, doppelt so weit waagerecht wie
senkrecht (sonst oeffnet sie beim Blaettern), hoechstens 800 ms.

44 px und nicht 20: Androids Zurueck-Geste belegt die aeussersten ~20
px, was dort beginnt erreicht die Seite nie.

Aus einer bildschirmfuellenden Ansicht heraus tut sie nichts - dort
wischt man durch Seiten, und die Leiste laege ohnehin dahinter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 11:24:23 +02:00
marcandClaude Opus 5 5ea66ea884 fix(thumbnails): Fassung in der Adresse, Sofortanzeige, kein Flackern
Gleicher Dateiname erzeugt keine neue Datei, sondern eine neue Fassung -
gleiche ID, gleiche Vorschaubild-Adresse. Die darf einen Tag im Browser
liegen, also zeigte die Kachel nach dem erneuten Hochladen bis zu 24
Stunden das alte Bild. Das erklaert, warum eine Drehung erst nach
mehreren Versuchen "ankam". Die Adresse traegt jetzt ?v=<Fassung>.

Vorschaubilder entstehen im Hintergrund; die Kachel fragt jetzt bis zu
fuenfmal nach, statt bis zum naechsten Neuladen ihr Ersatzsymbol zu
zeigen. Nur bei Bildern juenger als zwei Minuten - sonst fragte jede
dauerhaft fehlgeschlagene Datei bei jedem Oeffnen erneut. Gemessen: 608
ms bis zur Anzeige, ohne Neuladen.

Das Anheben der Kachel beim Ueberfahren ist raus: Steht der Zeiger auf
der Unterkante, rutscht sie unter ihm weg, mouseleave, sie faellt
zurueck, mouseenter - im Kreis, sichtbar als Flackern.

Dazu liest die EXIF-Auswertung jetzt 256 statt 64 KB und gibt bei einem
abgeschnittenen Block nicht mehr auf: Die Drehung steht vorn, das
eingebettete Vorschaubild dahinter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:29:09 +02:00
marcandClaude Opus 5 5b3658640e revert(admin): Knopf zum Neuerzeugen der Vorschaubilder wieder raus
130 Zeilen fuer einen einmaligen Bedarf - die Haelfte davon eine Karte
im Admin-Bereich, die sonst dauerhaft dort steht. Die EXIF-Behebung
selbst bleibt: Neu hochgeladene Bilder stehen richtig herum.

Bestehende Vorschaubilder werden damit nicht mehr nachgezogen. Wer sie
gerade ruecken will, laedt die Datei erneut hoch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:15:00 +02:00
marcandClaude Opus 5 4bd9a6ca56 fix(thumbnails): EXIF-Drehung auswerten, hochkant blieb quer
Kameras speichern die Bildpunkte fast immer quer und legen die Drehung
als EXIF-Notiz daneben. Browser befolgen sie von selbst - Go nicht.
Deshalb stand dasselbe Foto in der grossen Ansicht aufrecht und lag im
Vorschaubild auf der Seite.

Neues Paket internal/imageutil liest genau ein Feld (0x0112) aus dem
Anfang der Datei und dreht danach. Von Hand statt mit einer Bibliothek:
Der Weg dorthin ist kurz und liegt fest, und gebraucht wird ein Feld.
Gelesen werden nur die ersten 64 KB, damit ein 40-MB-Foto nicht zweimal
im Speicher liegt.

Gedreht wird vor dem Verkleinern - andersherum bekaeme ein hochkantes
Foto den Zuschnitt eines Querformats.

Bestehende Vorschaubilder aendern sich davon nicht. Dafuer neu:
POST /admin/thumbnails/rebuild samt Knopf im Admin-Bereich.

Auch Profilbilder aus OIDC laufen jetzt durch dieselbe Auswertung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:08:10 +02:00
marcandClaude Opus 5 edefce3572 feat(pdf): Strg + Mausrad zoomt, Zoomgruppe rechts aussen
Die Zoomknoepfe sitzen jetzt rechts in der Leiste statt links neben der
Seitenzahl - dieselbe Anordnung wie in InkNote. Die dritte Rasterspalte
war ohnehin da, damit die Suche wirklich mittig sitzt; jetzt traegt sie
auch etwas. Schmal bleibt der Zoom in der ersten Zeile, sonst waere die
Leiste am Telefon dreizeilig geworden.

Strg (oder Cmd) + Mausrad zoomt das Dokument. Der Zuhoerer wird von Hand
angemeldet: React meldet Rad-Ereignisse passiv an, und passiv darf man
preventDefault nicht rufen - ohne das zoomt der Browser die ganze Seite
statt des Dokuments.

Beim Rad wird der Punkt unter dem Zeiger festgehalten statt der Seite.
Gemessen: Seite 2 bleibt ueber zwei Zoomstufen unter dem Zeiger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:02:06 +02:00
marcandClaude Opus 5 3d9a4832f9 fix(pwa): geaenderte Kopfzeilen erreichen den Browser wieder
Ein Eintrag im Cache ist eine ganze Antwort, mitsamt ihren Kopfzeilen. Weil
index.html vorab im Cache lag und von dort auch „/" beantwortete, bekam der
Browser dauerhaft die CSP von damals: eine Aenderung an HONEYBAR_ORIGIN kam
nie an, denn der Inhalt der Datei aendert sich dabei nicht, und der
Vorab-Cache holt nur Geaendertes nach. Sichtbar als Widerspruch, an dem
nichts falsch aussah — das Skript laedt den neuen Ursprung, die CSP erlaubt
den alten.

Navigationen laufen jetzt Netz zuerst, Cache als Rueckfall; index.html bleibt
vorab abgelegt, aber nur noch als letzte Reihe (precacheFallback). Dass sie
nicht mehr als Verzeichnisseite fuer „/" einspringt, ist der eigentliche
Punkt: sonst kaeme die neue Regel nie zum Zug.

Nachgemessen: Wechsel des Ursprungs kommt beim zweiten Aufruf an, und
offline laden „/", „/admin" und „/s/<token>" weiterhin die Anwendung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 23:54:25 +02:00
marcandClaude Opus 5 d6b9d6a1ce fix(ui): die Seite rechnet die Höhe der HoneyOS-Leiste ab
Die Leiste liegt fest am oberen Rand und schiebt den Inhalt herunter. Wer
danach noch mit 100vh rechnete, war genau um ihre Höhe zu hoch — am Rechner
eine Bildlaufleiste, die es vorher nicht gab. Und wer selbst fest am oberen
Rand klebte (Seitenleiste am Handy, Vorschau, Office), lag darunter und war
verdeckt.

--honeybar-h setzt die Leiste selbst auf <html>; ohne sie ist der Wert 0 und
alles bleibt wie zuvor. Drei Hilfsklassen in index.css halten das an einer
Stelle zusammen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 23:40:34 +02:00
marcandClaude Opus 5 57156a6394 fix(honeybar): Ursprung zur Laufzeit statt beim Bauen
Die Adresse im <script>-Tag stand fest in der index.html, waehrend die CSP
aus HONEYBAR_ORIGIN kam. Wer die Leiste woanders betreibt, bekam genau den
Widerspruch: die CSP erlaubte den eigenen Ursprung, geladen wurde der alte.

Der erste Versuch, das ueber ein Bau-Argument zu loesen, half beim fertigen
Abbild nicht — dort ist der Bau vorbei, und ein Umzug haette einen Neubau
verlangt. Ursprung und Schluessel stehen jetzt in /api/v1/public/config, die
Oberflaeche haengt das Skript selbst ein. Beides speist sich aus derselben
Variablen wie die CSP, sie koennen also nicht auseinanderlaufen, und eine
Aenderung wirkt beim Neustart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 23:07:35 +02:00
marcandClaude Opus 5 6c7eb348b0 fix(frontend): Ursprung der HoneyOS-Leiste kommt aus der .env
Die Adresse im <script>-Tag stand fest im Quelltext, waehrend die CSP aus
HONEYBAR_ORIGIN kam. Wer die Leiste woanders betreibt, bekam genau den
Widerspruch: die CSP erlaubte den eigenen Ursprung, geladen wurde der alte.

Beides speist sich jetzt aus derselben Variablen. Ist sie leer, nimmt
vite.config.ts den Tag ganz heraus — sonst bliebe src="/honeybar/honeybar.js"
stehen und der Service Worker beantwortete das mit index.html.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:58:19 +02:00
marcandClaude Opus 5 1d933c5c73 feat(files): Sammel-Loeschen und Ziehen auf den Papierkorb
Zwei Wege, weil sie verschiedene Momente treffen: Der Knopf in der
Auswahlleiste raeumt eine geplante Auswahl weg, das Ziehen auf den
Papierkorb in der Seitenleiste die eine Datei, die einem gerade
auffaellt.

Zieht man eine markierte Datei, meint man die Markierung - deshalb
traegt der Ziehvorgang jetzt zusaetzlich aether-file-ids mit allen
markierten IDs. Eine unmarkierte Datei zieht nur sich selbst.

Der Knopf fragt nach, das Ziehen nicht: Ziehen ist eine bewusste
Bewegung mit gedrueckter Taste, und der Papierkorb ist umkehrbar. Eine
Rueckfrage nach jedem Zug klickt man nach dem dritten Mal blind weg.

filesApi.deleteMany schickt vier Anfragen gleichzeitig und meldet, was
nicht ging; fehlgeschlagene Dateien bleiben markiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:43:36 +02:00
marcandClaude Opus 5 3b12d2e943 feat(honeybar): Adresse auf honeyos.duckdns.org, Schlüssel aus der Bauzeit
HoneyOS liegt auf einer anderen Site als Aether – duckdns.org steht auf der
Public-Suffix-Liste, die gemeinsame Endung zaehlt fuer den Browser also
nicht. Folge: bei der Anfrage der Leiste schickt er die HoneyOS-Sitzung
nicht mit, und ohne zweiten Ausweis bekaeme sie nie eine App-Liste.

- data-key kommt aus HONEYBAR_KEY in der .env und wird beim Bauen von Vite
  in die index.html eingesetzt. Bewusst nicht im Quelltext: er kaeme sonst
  ins Repo und liesse sich nur mit einer Code-Aenderung wechseln.
- Leer gebaut heisst data-key="" – die Leiste bekommt dann keine Liste,
  Aether bleibt davon unberuehrt.

Nach einer Aenderung an HONEYBAR_KEY ist ein `build app` noetig, ein
Neustart genuegt nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:11:19 +02:00
marcandClaude Opus 5 9b8e82cdb2 feat(honeybar): HoneyOS-Leiste einbinden, CSP-Ausnahme dafür
Die Leiste kommt als Skript von der HoneyOS-Domain und holt sich von dort
ihre App-Liste. Beides blockte die CSP bisher (script-src / connect-src
fielen auf 'self' zurueck) – ohne Ausnahme erschien sie gar nicht.

- HONEYBAR_ORIGIN nimmt genau diesen einen Ursprung in script-src und
  connect-src auf. Leer heisst: keine Leiste, und die Regel bleibt so eng
  wie bisher. Frame-Freigabe erbt sie ausdruecklich nicht.
- SecurityHeaders nimmt die Ausnahmen jetzt als CSP-Struct statt als
  variadische frameSrc-Liste – mit drei Richtungen wurde das sonst unklar.
- Das <script>-Tag steht in der index.html und nicht serverseitig
  eingespritzt: der Service Worker legt die index.html in seinen Vorrat
  (navigateFallback), was zur Bauzeit fehlt, kaeme im Browser nie an.
- defer, damit ein langsames oder abgeschaltetes HoneyOS den Start von
  Aether nicht ausbremst.

Tests fuer die neue Regel: eng ohne Ausnahme, beide Richtungen mit,
Pfad wird auf den Ursprung gekuerzt, Adresse ohne Schema faellt raus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 21:03:01 +02:00
marcandClaude Opus 5 b5a83e4057 fix(upload): gezogenes Vorschaubild wurde erneut hochgeladen
Zieht man das angezeigte Bild ein Stueck zur Seite, legt Chrome es als
echte Datei in den Ziehvorgang. dataTransfer.types enthaelt dann
"Files" - nicht zu unterscheiden von einer Datei aus dem Dateimanager.
Die Ablageflaeche lud das eigene Bild daraufhin ein zweites Mal hoch.

Zwei Riegel: draggable={false} an allen Bildern, die kein eigenes
Ziehen brauchen (die Kachel bleibt ziehbar und verschiebt die Datei),
und useInternalDrag - was in dieser Seite losgezogen ist, wird nie als
Upload behandelt. dragstart feuert nur bei eigener Quelle.

Gemessen: vorher 1 Upload, nachher 0. Ein Zug von aussen laedt
unveraendert hoch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 17:24:22 +02:00
marcandClaude Opus 5 e9e07f9bed fix(ui): schmale Geraete zeigten nur eine Kachelspalte
Auf 360px (Galaxy Z Flip 7 aufgeklappt) brauchten zwei Spalten
2x160 + 12 = 332px, verfuegbar waren nach 20px Innenrand aber nur
320px - also fiel das Raster auf eine Spalte zurueck.

Schmal jetzt 132px Mindestbreite, 10px Abstand, 12px Innenrand. Damit
stehen zwei Spalten ab 300px Fensterbreite, ohne dass breite Schirme
sich aendern (dort weiter 160px und 20px).

Raster und Innenrand stehen als Klassen in index.css: inline gesetzt
haette die Media-Query sie nicht erreicht - derselbe Fehler wie zuletzt
in der Kopfleiste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:11:27 +02:00
marcandClaude Opus 5 0ef7bd67ab feat(webdav): Anleitung zum Einhaengen neben den App-Passwoertern
Ein Info-Knopf unter Einstellungen -> Access oeffnet drei Wege:
Windows (Netzlaufwerk), Linux ueber davfs2 samt fstab und secrets, und
Linux ueber den Dateimanager. Dazu WebDAV-Adresse, Benutzername und
Cloud-Adresse zum Kopieren.

Die WebDAV-Adresse stand bisher nirgends in der Oberflaeche - sie ist
zwar ableitbar, aber nur wenn man weiss, dass es sie gibt. Sie entsteht
aus window.location statt aus PUBLIC_URL: Was im Browser traegt, traegt
auch im Dateimanager.

Mitgenommen sind die drei Stellen, an denen es sonst scheitert: Windows
bricht ohne HTTPS ab und laedt nichts ueber 50 MB, die Konsole braucht
davfs2, und Dateimanager wollen davs:// statt https://.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 14:49:57 +02:00
marcandClaude Opus 5 9a2f90c89b fix(ui): Kopfleiste auf dem Handy hatte 0px Abstand nach oben
Die Regeln der schmalen Ansicht griffen nie: Ein Inline-Stil schlägt
jede Klassenregel, und `padding: '0 14px'` samt `paddingTop:
var(--safe-top)` stand genau dort. Gemessen kam padding-top als 0px an
statt als 8px - Titel und Nutzerbereich klebten am oberen Fensterrand.
Alle Abstaende stehen jetzt im CSS-Block, schmal 14px oben und 10px
unten.

Dazu das mitgelieferte App-Symbol neu gezeichnet: Die drei PNG lagen
als Graustufen vor, der indigo Verlauf war zu Schwarz eingeebnet. Die
Wolke besteht jetzt aus drei Kreisen und einem Rechteck dazwischen -
gerade Unterkante, keine Kerben in der Silhouette.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 14:26:49 +02:00
marcandClaude Opus 5 9f44215698 feat(oidc): Profilbild vom Identitätsanbieter übernehmen
Schickt der Anbieter ein `picture`, wird es bei der Anmeldung geholt,
mittig auf 256x256 zugeschnitten und oben rechts angezeigt. Fehlt es
oder scheitert das Holen, bleibt es beim Buchstabenkreis - die
Anmeldung selbst hängt nicht davon ab.

Die Bildadresse kommt aus einem Token, also von außen. Erlaubt ist
deshalb ausschließlich der Ursprung aus OIDC_ISSUER, Weiterleitungen
eingeschlossen; dazu 4 MB und 5 Sekunden als Deckel. Ohne das wäre es
eine Aufforderung an den Server, für einen Fremden beliebige Adressen
im internen Netz anzufragen.

Neu: Migration 000019 (avatar_key/-source/-updated_at), Paket
internal/avatar, Route GET /api/v1/users/{id}/avatar. Der GC durchsucht
"avatars/" mit und kennt die Schlüssel - sonst räumte er die Bilder
nach einem Tag weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 13:43:22 +02:00