74cf56ce815dc2b7a2ba7baa5dd828811bceb5fd
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
74cf56ce81 |
feat: eine Startseite, und die Sicherung geht in die Nextcloud
STARTSEITE. Wer das Dashboard oeffnet, landete bisher in der Galerie -- mit 228 Kacheln und ohne Antwort auf die erste Frage, die man beim Oeffnen hat: laeuft alles? Jetzt zuerst eine Uebersicht: Zahlen, was heute dazukam, ein Bilderstreifen der letzten Uploads, die letzten acht Schritte aus dem Verlauf, die groessten Ordner, die Zugaenge -- und die ADRESSVORLAGE zum Kopieren, weil das die haeufigste Frage ueberhaupt ist. Die Galerie zieht auf /galerie um. Alles in EINER Antwort (/api/dash/uebersicht, gemessen 14 ms). Vier Einzelabfragen waeren vier Runden ueber den Proxy und koennten auseinander- laufen: Dateizahl aus der einen und letzte Ereignisse aus der naechsten Antwort sind zwei verschiedene Zeitpunkte. Der Router zog dabei nach src/router.ts um. Der Grund ist mehr als Geschmack: die Startseite braucht eine Navigationsfunktion, die anderen Seiten nicht -- und sobald EINE Seite eine Eigenschaft mehr hat, passt die gemeinsame Liste in App.tsx nicht mehr. Genau daran ist der erste Versuch gescheitert (TypeScript hat es gefangen). Als Modulfunktion hat wieder jede Seite dieselbe Signatur. SICHERUNG IN DIE NEXTCLOUD. Bisher lagen die Archive auf derselben Platte wie die Daten -- gegen einen Plattenausfall half das gar nicht. Jetzt gehen sie per WebDAV hinauf, mit curl statt rclone: Nextcloud spricht WebDAV, curl spricht WebDAV, und curl ist ohnehin da. Auch dort wird NACHGEZAEHLT: die Kopie wird zurueckgeholt und die Pruefsumme verglichen. Ein PUT, der 201 sagt, hat nichts bewiesen -- abgeschnittene Uebertragungen sehen genauso aus. Und ausgeduennt wird ebenfalls, sonst waechst die Nextcloud still voll. Die Zugangsdaten liegen NEBEN den Sicherungen (C:\backup\d4rk_media\ nextcloud.txt), nicht im Repo: sie gehoeren zur Maschine, nicht zum Code. Fehlt die Datei, sagt das Skript das einmal und sichert lokal weiter -- eine Sicherung, die wegen eines fehlenden Passworts GAR NICHT stattfindet, waere der schlechtere Tausch. Dabei einmal reingefallen: die Pruefung brach mit "sh: set: Illegal option -" ab, nachdem ein Werkzeug die Datei mit CRLF gespeichert hatte -- die Shell im Container liest dann "set -e\r". Sieht aus wie ein Tippfehler und ist keiner. Das Skript entfernt die Wagenruecklaeufe jetzt selbst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
84cb3ee200 |
feat: Galerie fuer 900 Dateien, und ein Finder fuer Verwaiste
SORTIERUNG. Nach Pfad, Groesse, Anlage- oder Aenderungsdatum, auf- und absteigend. Bisher gab es genau eine Reihenfolge (zuletzt geaendert), was bei 228 Dateien schon nicht mehr reicht. Die Sortierung kommt vom Aufrufer und geht in SQL, also wird sie NIEMALS durchgereicht, sondern gegen eine feste Liste getauscht. Nachgemessen mit "sort=; DROP TABLE media--": faellt auf den Standard zurueck, Datenbank unveraendert. Nach dem Pfad wird ohne Ruecksicht auf Gross- und Kleinschreibung sortiert, sonst stuende "Adder" vor "adder" und dazwischen nichts. ALLE TREFFER WAEHLEN. GET /media/ids liefert nur die IDs zur aktuellen Auswahl -- ohne das braucht das Leeren eines Ordners mit 900 Bildern fuenfzehn Seiten Klicken. Gedeckelt auf 5000, und wer daran stoesst, bekommt es GESAGT (gekappt: true) statt still eine halbe Auswahl. VERWAISTE. Ein Knopf zaehlt drei Arten von Abweichung: Dateien ohne Datensatz, Datensaetze ohne Datei, Vorschauen ohne Bild. ERST ZAEHLEN, DANN FRAGEN -- die Suche veraendert nichts. Und das Aufraeumen geht in ZWEI Richtungen, wobei die schonende die Voreinstellung ist: "aufnehmen" gibt Dateien ohne Datensatz einen, sie sind ja da und werden ausgeliefert -- sie fehlen nur in der Liste. Erst "aufraeumen" entfernt etwas, und auch dann nur Verweise auf Dateien, die es nicht mehr gibt, und abgeleitete Vorschauen. Beides landet im Verlauf. Am laufenden System durchgespielt: Datei am Dienst vorbei ins Volume gelegt -> gefunden; aufgenommen -> steht in der Galerie; Datei hinter dem Ruecken geloescht -> Datensatz ohne Datei gefunden; aufgeraeumt -> weg. Verlauf hat beide Schritte mit user:admin. (Der erste Versuch dieses Tests hat gelogen: Git Bash schreibt einen absoluten Pfad in `docker exec` zu einem Windows-Pfad um -- rm suchte nach "C:/Program Files/Git/data/...". `rm -f` schweigt dabei. Mit `sh -c` drumherum bleibt der Pfad heil.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4f149e365f |
feat: Discord-Bild und -Name im Dashboard
Wer ueber Discord hereinkommt, sieht sein Bild in der Kopfzeile, auf der
Kontoseite und neben seinen Eintraegen im Verlauf.
DIE ADRESSE BAUT DER DIENST, nicht die Oberflaeche. Gespeichert wird nur der
Bildstempel; das URL-Schema von Discord steht an EINER Stelle (avatarUrl in
db.ts) und nicht in jeder Ansicht, die ein Bild zeigt. Es hat drei Faelle, und
alle drei stehen dort:
- Stempel faengt mit "a_" an -> bewegtes Bild, muss als .gif angefragt werden
- Stempel vorhanden -> .png mit gewuenschter Groesse
- kein Stempel -> Discords Ersatzbild, dessen Nummer sich aus
der ID ergibt: (id >> 22) % 6. Das ist die
NEUE Rechnung; die alte ueber den
Diskriminator gilt seit den Pseudonymen
nicht mehr. Gegengerechnet an drei IDs.
Bild und Anzeigename werden BEI JEDER ANMELDUNG nachgezogen: wer sein Bild
wechselt, saehe sonst monatelang das alte und fragte sich, ob er im richtigen
Konto sitzt. Der Benutzername im Dashboard bleibt dagegen, wie er ist -- an ihm
haengen die Eintraege im Verlauf.
Der Avatar-Baustein kennt drei Faelle und keinen kaputten: Bild, sonst die
Initialen auf einer Farbe, die sich aus dem Namen ergibt (damit sie beim
Neuladen nicht springt), und fuer einen Token einen Schluessel -- ein Token hat
kein Gesicht, ein Platzhalterbild daneben waere eine Behauptung. Laedt das Bild
nicht (CDN nicht erreichbar, Stempel veraltet), faellt es auf die Initialen
zurueck statt ein kaputtes Bildsymbol zu zeigen.
Nachgemessen: cdn.discordapp.com liefert das Ersatzbild (200, image/png), die
Form der Bildadresse stimmt, und ein Passwort-Konto bekommt sauber avatar:null.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
23405d68d3 |
feat: mehrere Rollen duerfen herein, nicht nur eine
Wer EINE der eingetragenen Rollen hat, kommt ins Dashboard. Mehrere sind der Normalfall, sobald mehr als eine Person Zugang haben soll -- Admin und Fotograf sind selten dieselbe Rolle. Die bisherige einzelne Rolle geht NICHT verloren: der alte Schluessel (discord.role_id) wird weitergelesen, bis die Liste einmal gepflegt wird; dann raeumt das Speichern ihn weg. Zwei Quellen fuer dieselbe Frage laufen sonst auseinander. Nachgemessen: die vorhandene Rolle stand nach dem Umbau unveraendert drin, bereit blieb true. Getrennt wird an Komma, Semikolon, Leerzeichen und Zeilenumbruch -- wer drei IDs untereinander aus Discord kopiert, soll sie nicht erst zu einer Zeile zusammenschieben. Doppelte fallen zusammen. UND DIE SCHLECHTE WIRD BEIM NAMEN GENANNT: "123 ist keine Rollen-ID (17 bis 20 Ziffern)" statt "Rollen-ID ungueltig". Bei fuenf Eintraegen ist die zweite Meldung eine Suchaufgabe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1c5bf20eb0 |
feat: Medien im Fenster ansehen, und eine Auffrischung, die nichts wegzieht
ANSEHEN OHNE NEUEN TAB. Klick auf eine Kachel oeffnet den Betrachter: Bilder, Videos, Ton und PDF direkt, alles andere mit ehrlichem Hinweis und Herunterladen-Knopf -- ein Betrachter, der bei einer .zip ein schwarzes Feld zeigt, sieht aus wie ein kaputter Betrachter. Pfeiltasten blaettern, Escape schliesst; Adresse kopieren, neuer Tab und Loeschen sitzen in der Kopfzeile. Gezeigt wird IMMER die Vollfassung, nie die Vorschau: 320 Pixel reichen fuer eine Kachel, aber nicht dafuer, ein Bild zu beurteilen -- und genau dazu klickt man darauf. Damit musste die Auswahl umziehen: der Klick auf die Kachel oeffnet jetzt, das Kaestchen oben links waehlt aus. Es erscheint beim Darueberfahren und bleibt sichtbar, solange etwas gewaehlt ist. AUTOMATISCH AUFFRISCHEN, aber nicht stumpf. Ein billiger Fuehler (/api/dash/stand: drei Zahlen, GEMESSEN 6 ms je Abfrage ueber TLS und Proxy) wird alle fuenf Sekunden gefragt; erst wenn er sich aendert, wird richtig nachgeladen. Ein Tab im Hintergrund fragt gar nicht. Die dritte Zahl ist kein Zierrat: nach einem LOESCHEN gehen Anzahl und neuester Zeitstempel auf die alten Werte zurueck -- nur die Ereignis-ID zaehlt weiter. Ohne sie bliebe eine geloeschte Datei in der Galerie stehen. Nachgemessen. UND ES ZIEHT NICHTS WEG: wer etwas ausgewaehlt hat, ein Bild offen hat oder geblaettert ist, bekommt keinen Neuaufbau unter den Haenden, sondern einen Knopf "Neues da -- laden". Eine Liste, die sich beim Klicken selbst neu sortiert, ist der Grund, warum Leute Auto-Refresh abschalten. Der Schalter merkt sich seinen Zustand. Kein SSE, und das mit Absicht: dafuer braeuchte NPM zusaetzlich `proxy_buffering off` -- ein weiterer Handgriff im Proxy, der vergessen werden kann und dann zu einer Verbindung fuehrt, die scheinbar haengt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dffec7714c |
feat: Discord-Anmeldung -- im Panel einrichtbar, nicht in der .env
Auf Wunsch ueber die Oberflaeche statt ueber Umgebungsvariablen. Der Gewinn ist
nicht nur Bequemlichkeit: das Panel legt die REDIRECT-URI zum Kopieren hin, und
genau daran scheitert ein OAuth-Anlauf fast immer.
settings.ts Schluessel/Wert in SQLite, mit Umgebung als Rueckfall. Wer
schon Stack-Variablen gesetzt hat, behaelt sie; das Panel
uebersteuert. In der Umgebung bleibt, was den Dienst ueberhaupt
erreichbar macht (PUBLIC_URL, PORT, DATA_DIR) -- wer das per
Formular verstellen koennte, sperrt sich mit einem Klick aus.
discord.ts Der Weg in drei Schritten, ohne Bibliothek und OHNE BOT-TOKEN:
die Rolle wird mit dem Merkmal des BENUTZERS geholt
(guilds.members.read). Kein Bot, keine Bot-Rechte, kein zweites
Geheimnis, das ablaufen kann.
DAS GEHEIMNIS GEHT NIE HERAUS -- auch nicht an eine angemeldete Sitzung. Das
Panel bekommt nur "hatGeheimnis: true". Ein Formular, das den Client-Schluessel
im Klartext zurueckschickt, streut ihn in jeden Browser-Cache. Ein leeres Feld
heisst "unveraendert", nicht "loeschen"; zum Entfernen gibt es "-".
JEDER ABBRUCH IST SICHTBAR: kein weisser Bildschirm, sondern zurueck aufs
Formular mit dem Grund im Adressfeld -- "Du bist nicht auf diesem Server",
"Dir fehlt die noetige Rolle", oder bei 4xx vom Tausch der Hinweis auf die
Redirect-URI. Die IDs werden schon beim Speichern geprueft (17-20 Ziffern),
damit ein halber Link nicht erst beim Anmelden auffaellt.
Das Passwort bleibt als Notausgang. Ein ueber Discord angelegtes Konto bekommt
KEIN brauchbares Passwort ("nur-ueber-discord"): verifyPassword scheitert an
allem, was nicht dem scrypt-Format entspricht -- nachgesehen, nicht vermutet.
Dazu der Verlauf, wie gewuenscht: Suche nach Pfad (mit derselben
LIKE-Maskierung wie in der Galerie -- Suche nach "_" findet 0 statt allem),
waehlbare Seitengroesse (25/50/100/200, Standard 50) und mehr Luft zwischen
"Groesse" und "Von wem".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
0b295a2302 |
feat: Verlauf -- wer hat wann was abgelegt, ersetzt oder geloescht
Neuer Reiter, und dahinter eine eigene Tabelle. WARUM NICHT EINFACH
media.created_at -- drei Gruende, jeder allein wuerde reichen:
1. media kennt nur den JETZIGEN Stand. Was geloescht wurde, ist weg, samt
der Frage, wer es geloescht hat.
2. Ein Ueberschreiben hebt updated_at an und ueberschreibt damit die
Auskunft, wann die Datei urspruenglich kam.
3. media.token_id ist beim Upload aus dem Dashboard NULL -- dort hat niemand
einen Token, sondern eine Sitzung. Das "von wem" stand also nirgends.
Das ist NICHT das Logging, das die ROADMAP ablehnt: dort geht es um
Anfrage-Protokolle samt ClickHouse. Hier sind es ein paar Zeilen je Upload in
derselben SQLite-Datei, und nach einem halben Jahr raeumt pruneEvents auf.
DER VERLAUF STARTET NICHT LEER. Beim ersten Start wird er aus dem Bestand
nachgetragen (68 Eintraege) -- ein Tab, der am ersten Tag leer ist, obwohl 68
Bilder in der Ablage liegen, sieht aus wie ein kaputter Tab. Die nachgetragenen
Eintraege sind als solche gekennzeichnet: Zeitpunkt stimmt, Urheber steht als
"vor der Aufzeichnung", weil ihn niemand mehr kennt. Die Oberflaeche sagt das
unter der Tabelle, statt es zu verschweigen.
logEvent wirft nie: ein Verlauf, der einen Upload scheitern laesst, waere die
Buchhaltung, die das Geschaeft verhindert.
Nachgemessen: Upload, Ersetzen und Loeschen aus dem Dashboard erscheinen als
user:admin, die Uploads des Fotostudios als token:d4rk_photostudio; beim
Loeschen wird die Groesse VOR dem Loeschen geholt, sonst stuende dort nichts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
3ddab937e7 |
feat: hochladen aus dem Dashboard -- Bilder, Videos, Ton, PDF
POST /api/dash/upload, eigener Weg neben /api/upload. Der dortige haengt an
einem Bearer-Token; wer im Dashboard sitzt, hat eine SITZUNG und keinen Token,
und einen Token nur zum Hochladen anzulegen waere ein Geheimnis mehr, das
jemand verlieren kann.
Mehrere Dateien auf einmal, und JEDE bekommt ihr eigenes Ergebnis mit Pfad,
Groesse und Grund im Fehlerfall. Ein "3 von 5 hochgeladen" ohne die Namen der
zwei anderen ist genau die Sorte Rueckmeldung, wegen der wir hier neu bauen.
Eine kaputte Datei bricht die anderen nicht ab.
parseBody({ all: true }) ist noetig: ohne das liefert Hono bei mehreren Feldern
gleichen Namens nur das LETZTE, und aus fuenf Dateien wuerde eine.
DATEINAMEN WERDEN ZURECHTGEBOGEN, und zwar sichtbar. checkPath laesst
"Mein Video (final) 2.MP4" mit Recht nicht durch, also macht safeFilename
daraus "mein-video-final-2.mp4" -- Umlaute werden umschrieben und nicht
weggeworfen ("Grüße" -> "gruesse", nicht "gr-e"). Die Ergebnisliste zeigt den
neuen Pfad neben dem alten Namen.
BEIM MESSEN GEFUNDEN: bleibt vom Namen nichts uebrig -- "ЖУРНАЛ.txt",
" .png", "★★★.txt" --, hiessen bisher ALLE "datei.txt", und die zweite
Datei ueberschrieb die erste stillschweigend. Jetzt haengen die ersten acht
Stellen des SHA-256 daran: gleicher Inhalt ergibt denselben Namen,
verschiedener Inhalt verschiedene. Nachgemessen mit drei solchen Namen.
Der Fortschrittsbalken laeuft ueber XMLHttpRequest und nicht fetch: nur der
kennt upload.onprogress, und bei einem 40-MB-Video ist ein Balken der
Unterschied zwischen "es tut sich was" und "ist es abgestuerzt?".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f7086c231c |
feat: Ordneruebersicht in der Galerie -- und der Proxy haelt keine Bilder mehr fest
ORDNERUEBERSICHT. Eine Spalte neben der Galerie mit allen Ordnern, auf allen Ebenen, mit Anzahl und Groesse; ein Klick filtert. Neu dafuer: - GET /api/dash/folders -- die Ordner werden in JavaScript zusammengezaehlt und nicht in SQL: fuer eine Baumansicht braucht es JEDE Ebene, und das waere in SQLite eine rekursive Abfrage, die niemand mehr liest. - GET /api/dash/media?folder=... als EIGENER Filter neben der Suche. Die Suche findet mit LIKE %...% auch "alt/vehicles/adder.webp"; ein Ordner, der Dinge zeigt, die nicht darin liegen, ist keiner. Hier ist der Anfang verankert. - Die LIKE-Sonderzeichen werden maskiert: ein Ordner "fotos_alt" wuerde sonst auch "fotosXalt" treffen, weil _ in LIKE ein beliebiges Zeichen ist. Die Zahlen sind kumulativ -- "vehicles" enthaelt, was in "vehicles/showroom" liegt. Alles andere ueberrascht in einer Baumansicht. Nachgemessen ueber drei Ebenen: vehicles (24) > showroom (2) > tief (1), Filter und Zaehlung stimmen, und nach dem Aufraeumen steht wieder vehicles (22). Flache Liste mit Einrueckung statt aufklappbarem Baum: bei einer Handvoll Ordner ist ein Aufklappmechanismus mehr Bedienung als Nutzen -- und was man nicht aufklappen muss, kann man auch nicht zugeklappt uebersehen. Dazu: der NPM-Zwischenspeicher ist zu. Der Schnipsel steht im Feld "Custom Nginx Configuration"; NPM setzt ihn innerhalb des server-Blocks ein, eine location ist dort also gueltig. Nachgemessen: cache-control kommt wieder von uns, und ein ueberschriebenes Bild ist sofort das neue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
aa2575bdf4 |
feat: Sicherung, Anmeldebremse, Vorschaubilder -- und Schritt 5
SICHERUNG (tools/sichern.ps1, taeglich 04:30 als geplante Aufgabe). Die Datenbank wird nicht kopiert, sondern ueber SQLites eigene Sicherungsschnittstelle herausgeholt (server/src/backup.ts): im WAL-Modus liegt das Zuletzte noch nicht in media.db, und selbst alle drei Dateien zu kopieren ist nicht sicher, wenn waehrenddessen geschrieben wird. Die Bilder kommen aus einem NUR LESEND eingehaengten Volume dazu. Und sie prueft sich selbst: auspacken, Datenbank oeffnen, Medien/Token/ Benutzer zaehlen, mit dem laufenden Dienst vergleichen, sonst Fehler. Einmal wirklich zurueckgespielt -- leeres Volume, zweiter Dienst, Anmeldung mit dem echten Passwort, Bild abgerufen, Byte fuer Byte identisch. Eine Sicherung, die nie zurueckgespielt wurde, ist eine Hoffnung. ANMELDEBREMSE. Das Formular steht oeffentlich, und scrypt macht einen Versuch teuer -- aber teuer ist nicht selten. Fuenf freie Versuche je Adresse, dann Sperre ab 30 s mit Verdopplung bis 15 min, 429 samt Retry-After und einem Text, der sagt wie lange. Erfolg setzt zurueck. Nur im Speicher: wer sich aussperrt, startet den Container neu. Durchgemessen bis zur Erholung. VORSCHAUBILDER. sharp erzeugt beim Upload eine 320er WebP-Fassung unter /data/thumbs, ausgeliefert unter /t/<pfad>. Gemessen: 131502 -> 13078 Bytes, Faktor 10; eine Galerieseite faellt von 7,5 MB auf 766 KB. KEINE Spalte in der Datenbank -- ob es eine Vorschau gibt, sagt das Dateisystem, und die Galerie faellt bei 404 aufs Vollbild zurueck. Eine zweite Wahrheit, die auseinander- laufen kann, gibt es damit gar nicht erst. Ein Knopf zieht Fehlendes nach und nennt Zahlen statt "fertig". SCHRITT 5. Die Fivemanage-Dateien liegen in legacy/ mit Erklaerung; unsere docker-compose.media.yml heisst jetzt docker-compose.yml. Drei Volumes und drei Abbilder geloescht, rund 670 MB -- nachgezaehlt war vorher, dass kein Bild darin lag. Nebenbefund: `npm i sharp` scheitert auf diesem Rechner nicht an sharp, sondern an better-sqlite3 -- der Host laeuft auf Node 24 (ABI 137), node-gyp uebernimmt und findet keine Bauwerkzeuge. Genau die Falle aus dem Dockerfile. Eingetragen wurde mit --package-lock-only, gebaut wird im Abbild auf Node 22. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6c8ead8e15 |
feat: das Dashboard -- und die eine Regel steht im Code, nicht im Vorsatz
Schritt 4. React 18, TypeScript, Vite 5, Tailwind 4 (CSS-first), Lucide, Zustand; kein Konsta. Quelle in server/ui, Build nach server/web (gitignored), im Abbild von einer eigenen Docker-Stufe gebaut. JEDER KNOPF GIBT RUECKMELDUNG -- und zwar erzwungen, nicht vorgenommen: - api.ts haelt den EINZIGEN fetch und GENAU EINE Fehlerklasse. Der Fivemanage-Fehler war, zwei zu haben: geworfen wurde `new Error`, geprueft auf `instanceof ApiError`, und damit verschwand jeder Fehlschlag lautlos. - store.ts haelt run(): Aktion rein, Erfolgstext rein, und es meldet bei Erfolg diesen und bei Fehlschlag den Text DES DIENSTES. Wer darueber geht, kann keinen stillen Knopf bauen. - Jeder wartende Knopf ist gesperrt und zeigt einen Kreisel. Der zweite Klick eine Sekunde spaeter war der Weg zu neunzehn gleichnamigen Organisationen. - Fehlermeldungen bleiben stehen, bis jemand sie wegklickt. Ein Fehler, der von allein verschwindet, ist einer, den niemand gelesen hat. @vitejs/plugin-react ist auf ^4 festgenagelt: die 6 verlangt vite ^8. In den peerDependencies nachgesehen -- Vite 5.4.21 liegt in der Schnittmenge von plugin-react@4 und @tailwindcss/vite@4. Am laufenden Container nachgemessen: / kommt als text/html, assets/*.js als text/javascript, *.css als text/css (die MIME-Falle aus Schritt 1 traegt), und /tokens, /speicher, /konto liefern dieselbe index.html -- die SPA-Rueckfall- route tut, wofuer sie gebaut wurde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |