Commit Graph
99 Commits
Author SHA1 Message Date
D4rkst3randClaude Opus 5 93156e5205 backup: gegenpruefen, nachholen, und endlich Bescheid sagen
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Befund 2, 3 und 4 aus docs/befunde-2026-08-12.md. Befund 1 bleibt offen, Befund
5 war falsch und ist zurueckgezogen.

DER BOT SAGT JETZT BESCHEID. src/melden.js ist neu und enthaelt, was vorher in
watchdog.js eingeschlossen war: dmAdmin. Genau deshalb hat alles andere im Bot
geschwiegen oder in die Konsole geschrieben -- und eine Konsolenzeile in einem
Container liest niemand.

Dazu dmAdminEinmalig: meldet nur bei ZUSTANDSWECHSEL. Sonst wuerde eine
Pruefung, die alle zehn Minuten laeuft, denselben Ausfall alle zehn Minuten
melden, und nach der dritten DM liest man sie nicht mehr. Der Merker steht in
den Einstellungen und nicht im Arbeitsspeicher -- ein Bot, der nach jedem
Update neu startet, haette sonst nach jedem Update wieder eine frische Meinung.
Gemessen gegen eine KOPIE der Datenbank, sechs Schritte, alle wie beabsichtigt.

DER WACHHUND AUF last_backup. pruefeSicherung() meldet, wenn die letzte
Sicherung aelter als 26 Stunden ist, wenn sie fehlgeschlagen ist, oder wenn es
nie eine gab. Im Fehlerfall steht NICHT last_backup als Zeitpunkt im Embed: der
wird nur bei Erfolg gesetzt, dort staende also der letzte GUTE Lauf, und das
liesse die Sicherung frischer aussehen als sie ist.

DER TAG FAELLT NICHT MEHR AUS. Vorher "if (now.getHours() !== 3) return" -- wer
waehrend dieser einen Stunde unten war, hatte den Tag verloren, und ein Bot,
der nach jedem Update neu startet, ist genau dieser Fall. Jetzt zaehlt nur: es
ist nach 03:00 und heute war noch keine.

DAS ARCHIV WIRD AUSGEPACKT UND GEZAEHLT. integrity_check plus Tabellenzahl
gegen die laufende Datenbank. Faellt das durch, wird das Archiv GELOESCHT (im
Sicherungsordner saehe es sonst aus wie eine Sicherung), die Rotation laeuft
nicht, und last_backup bleibt stehen.

An echten Archiven gemessen, mit zwei Gegenproben, damit die Pruefung nicht nur
"ja" sagen kann:

    2026-08-10  817 KB  54 Tabellen  86 ms
    2026-08-11  886 KB  54 Tabellen  64 ms
    2026-08-12  970 KB  55 Tabellen  78 ms   (laufend: 55)
    halbes gzip -> "unexpected end of file"
    Muell       -> "incorrect header check"

Die 54 gegen 55 sind kein Fehler, sondern eine gewachsene Tabelle zwischen dem
11. und dem 12. Verglichen wird zeitgleich, also stoert das nicht -- wissen
sollte man es, bevor jemand alte Archive gegen die heutige Zahl haelt.

BEFUND 5 WAR FALSCH. pruneMessageCache wird sehr wohl aufgerufen, taeglich, in
mod-tools.js:239 -- und stand schon zum Zeitpunkt der Durchsicht dort. Gezaehlt
wurden "Aufrufe ausserhalb von db.js", und die Sammel-Importzeile ist als
Import durchgegangen, waehrend der Aufruf zwanzig Zeilen tiefer nicht mitkam.
Das ist genau der Fehler, den zu vermeiden dieser Bericht dasteht; die anderen
vier sind deshalb einzeln nachgemessen worden und stimmen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:47:09 +02:00
D4rkst3randClaude Opus 5 5ac079699c watchdog: sagen WARUM, nebeneinander pruefen, Gespenster aufraeumen
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
DER GRUND STEHT JETZT DABEI. Bisher landete im Ausfall-Embed nur "HTTP 503".
Das sagt, DASS etwas ist, nicht WAS -- und wer nachts eine DM bekommt, will
genau das wissen, bevor er sich einloggt.

Viele Dienste sagen es naemlich selbst. d4rk_media antwortet auf /status mit
einer 503 UND einer Begruendung, und die geht jetzt mit in die Meldung UND in
die Stoerung (startIncident nimmt einen Grund entgegen -- damit steht er in der
Historie der oeffentlichen Statusseite und nicht nur einmalig in einer DM):

    vorher   HTTP 503
    jetzt    HTTP 503 — sicherung: letzte vor 40 Stunden

Gemessen am laufenden Dienst, nicht ausgedacht.

Gelesen wird nur bei content-type JSON und hoechstens 64 KB; der Satz wird bei
240 Zeichen gekappt. Erkannt werden erst unsere eigene Form (eine Liste von
Pruefungen, die durchgefallenen werden genannt), dann die ueblichen Felder
error/message/reason/grund/detail, zuletzt ein blosser Zustand wie "degraded".
Wer nichts davon liefert, bekommt weiterhin "HTTP nnn" -- kein Fehler, nur
keine Zusatzauskunft. Zehn Faelle durchgeprueft, darunter kaputtes JSON, kein
Objekt und null.

DAS URTEIL BLEIBT DER STATUSCODE. Ein Dienst, der 200 mit "ok": false
antwortet, gilt weiterhin als erreichbar -- alles andere waere eine stille
Verhaltensaenderung fuer jede andere ueberwachte Adresse.

Nebenbei bekommt der Rumpf damit endlich eine Behandlung: er wird gelesen oder
verworfen. Ein fetch, dessen Antwort niemand anfasst, haelt die Verbindung, bis
der Aufraeumer sie holt.

NEBENEINANDER STATT NACHEINANDER. Die Schleife lief der Reihe nach, und jede
Adresse durfte bis zum Zeitlimit brauchen: bei fuenf Diensten und zehn Sekunden
Grenze konnte ein Durchlauf fast eine Minute dauern. Bei zwei Minuten Intervall
ist das knapp, bei zehn Diensten ueberholt sich der Waechter selbst. Geprueft
wird jetzt gleichzeitig, AUSGEWERTET weiter der Reihe nach -- die DMs sollen in
nachvollziehbarer Ordnung ankommen und nicht in der, in der zufaellig
geantwortet wurde.

GESPENSTER AUFRAEUMEN. `state` wurde nie aufgeraeumt: ein aus der Dienst-Tabelle
geloeschter Eintrag blieb bis zum Neustart im Speicher und damit in der
Uebersicht im Panel stehen. Was nicht mehr ueberwacht wird, fliegt jetzt am Ende
jedes Durchlaufs raus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:19:58 +02:00
D4rkst3randClaude Opus 5 1ef06f747c Modliste: melden was sich geaendert hat, einzeln laden
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Bisher gab es fuer Mods nur einen Link auf das Sammelpaket des Hosters — vier
Gigabyte, auch wenn sich zwei Dateien geaendert haben. Jetzt liest der Bot die
Modliste aus der Statusabfrage, die er ohnehin holt, und meldet im Kanal, was
neu, aktualisiert oder entfernt wurde. Beim ersten Durchlauf bleibt es still,
sonst kaeme eine Meldung ueber 110 "neue" Mods.

Geaendert heisst: andere Version oder anderer Hash. Beides einzeln reicht
nicht — manche Modder bessern nach, ohne die Version zu erhoehen.

Dazu die Seite /mods/<server>: jeder Mod mit Version und Groesse, Suche ueber
Titel, Dateiname und Autor, und ein Download je Zeile. Der laeuft durch den
Bot, damit der Zugangs-Code des Spielservers nicht in einem Link landet — mit
dem Code liesse sich auch der ganze Spielstand lesen. Angefragt wird nur, was
wirklich in der Modliste steht; ueber den Dateinamen kommt man an nichts
anderes heran.

Groessen lernt der Bot beim Weiterleiten. Der Spielserver beantwortet kein
HEAD (501) und ignoriert Range, es gibt also keinen billigen Weg, sie vorher
zu erfahren — und 4 GB nur fuers Anzeigen zu holen waere keiner.

Nebenbei gefunden und behoben: 'server.mods' und 'commands.tag' gab es je
zweimal in den Sprachdateien. Der spaetere Eintrag gewinnt still, deshalb
stand auf dem neuen Knopf woertlich "%s Mods (110)" und auf der Befehlsseite
die Beschreibung von /tag statt der Kopfzeile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:22:55 +02:00
D4rkst3randClaude Opus 5 9b681754fa Ein Embed je Hof, das sich selbst aktualisiert
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Fuhrpark stand bisher nur als Gesamtzahl im Karten-Embed. Jetzt bekommt
jeder Hof eine eigene Nachricht im selben Kanal, die bei jedem Durchlauf
bearbeitet statt neu gepostet wird: Land, Fahrzeuge mit Wert und
Betriebsstunden, was in die Werkstatt muss, was gewaschen gehoert und was
geladen ist.

Die Fahrzeuge kommen aus vehicles.xml, blockweise gelesen — Verschleiss und
Schmutz stehen in Kindelementen und gehoeren sonst dem falschen Fahrzeug.
Gemietete Missionsfahrzeuge bleiben draussen, sie verfaelschen Anzahl und
Wert. Faellt die Datei einmal aus, bleiben die Embeds stehen, statt dass ein
Hof verschwindet und gleich darauf doppelt gepostet wird.

Damit tauchen auch Hoefe auf, die noch kein Land gekauft haben: die Liste der
Hoefe kommt aus Parzellen und Fuhrpark zusammen.

Dazu raus, was nur den Kanal vollgeschrieben hat: die Meldung zum
Monatswechsel und die zum Landkauf. Der Monat wird still weitergestellt — er
steht ohnehin im Embed. Damit faellt der Ereignis-Kanal weg und mit ihm die
gespeicherte Besitzliste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:53:02 +02:00
D4rkst3randClaude Opus 5 0875dff127 Server-Embed aufgeraeumt: volle Reihen statt halber
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Discord setzt drei Inline-Felder in eine Reihe. Es waren fuenf — Status,
Spieler, Map, Version, Mods —, also drei plus zwei, und die zweite Reihe stand
halb leer. Bei einem Server, der weniger meldet, stand ein einzelnes Feld
allein herum.

Neu sortiert nach dem, was man wissen will:

Der Zustand ist die Ueberschrift, kein Datenpunkt. Er steht jetzt in der
Beschreibung, eine Zeile mit Trennpunkten: online, Schloss, Ping. Das spart
eine ganze Feldreihe und liest sich als Satz. Der Ping ist damit aus der
Fusszeile raus, wo er zwischen Marke und Intervall stand.

Die Auslastung ist die Zahl, wegen der man hinschaut — sie bekommt die volle
Breite statt eines Drittels, und der Balken wurde von zehn auf sechzehn
Zeichen breiter.

Map, Version und Mods bilden genau eine Reihe. Fehlt eins davon, fuellen
unsichtbare Felder auf, damit die Reihe nicht schief steht.

Adresse und alles, was Discord nicht klickbar macht — fivem://, steam://,
ts3server:// —, standen als je eigener Codeblock untereinander. Das war eine
Wand aus Kaesten. Jetzt ein Block "Zum Kopieren" mit allem drin.

Nebenbei: fuer den Namen im Spiel hatte ich Discords Kleintext-Auszeichnung
genommen. Die rendert in Embeds nicht verlaesslich, und dann staende das
Steuerzeichen woertlich da. Kursiv tut es auch.

Geprueft: drei Zusammenstellungen im Klartext durchgespielt (LS mit allem,
FiveM mit wenig, offline), und die Auffuellung mit einem, zwei und drei
Feldern sowie ohne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:25:58 +02:00
D4rkst3randClaude Opus 5 ea523b76b3 Servername aus dem Spiel und ein Schloss fuer Passwort oder Whitelist
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Name im Panel ist der, den DU vergibst — wie der Server sich im Spiel
nennt, stand nirgends. Steht jetzt darunter, aber nur wenn es etwas anderes
ist als der eingetragene Name; sonst haette dieselbe Zeile zweimal dagestanden.

Beim Schloss war die Frage, wie viel die Abfrage ueberhaupt hergibt. Nachgesehen
statt geraten: 31 der 358 gamedig-Protokolle melden ein Passwort. valve
gehoert dazu, deckt also CS2, Rust, ARK, Squad, 7DTD, Zomboid, Gmod und DayZ
ab. Minecraft nicht, Farming Simulator auch nicht — im LS-XML kommt das Wort
Passwort kein einziges Mal vor. Und eine Whitelist meldet grundsaetzlich kein
Spiel, die steht in einer Datei auf dem Server.

Also beides: automatisch, wo es geht, und eine Einstellung, die es schlaegt.
Wer "Whitelist" eintraegt, bekommt sie angezeigt, auch wenn der Server
schweigt.

Wichtig dabei ist der dritte Zustand. "Sagt nichts" ist nicht "offen" —
deshalb `null` und kein `false`. Ein Schloss, das bei jedem schweigenden
Server fehlt, waere eine Aussage, die wir nicht belegen koennen; umgekehrt
waere ein Schloss ohne Grundlage eine Luege. Bei unbekannt steht schlicht
nichts da.

Geprueft: Einstellung schlaegt Messung in allen vier Faellen, automatisch mit
true/false/null, fehlender Server, unbekannter Wert faellt auf automatisch
zurueck statt zu sperren, Schloss im Embed-Titel nur wenn belegt, Spielname
nur bei echtem Unterschied (auch mit Leerzeichen drumherum). Panel und
/server-Seite im laufenden Frontend angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:12:33 +02:00
D4rkst3randClaude Opus 5 4ce3c10d76 Kein Monatswechsel mehr aus einer kaputten Antwort
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Bot rief einen neuen Monat aus, obwohl im Spiel keiner war. Zwei Fehler,
die zusammen genau das ergeben:

statsLesen las die Uhrzeit als `Number(server.dayTime) || 0`. Eine Antwort
ohne dayTime — Server startet neu, Proxy schiebt eine Fehlerseite dazwischen,
XML halb geschrieben — wurde damit zu 0. Und tagUmgeschlagen prueft auf
`jetzt < vorher`. Null ist kleiner als jeder Vortagswert, also sah jede
kaputte Antwort wie Mitternacht aus.

Beides behoben: fehlt die Angabe, ist sie jetzt null und nicht 0 — "weiss
nicht" ist kein Tageswechsel. Und ein Wechsel muss die Form eines echten
Mitternachtssprungs haben: vom spaeten Abend in den fruehen Morgen, beide
Werte innerhalb eines Tages. Ein Zappeln um ein paar Minuten faellt jetzt
durch. Eine Antwort ohne Uhrzeit ueberschreibt ausserdem den letzten guten
Stand nicht mehr, sonst ginge der echte Sprung danach verloren.

Beim Nachmessen zeigte sich, dass meine Doku an der Stelle falsch war: dayTime
ist nicht live, sondern ein Schnappschuss. Fuenf Abrufe ueber zwei Minuten mit
einem Spieler online ergaben denselben Wert (37028620) — der Server schreibt
das XML im "Web API Interval" neu, hier alle 360 Sekunden. Steht jetzt richtig
in docs/ls-feed.md, samt dem verbleibenden Restrisiko: ein Serverneustart
setzt die Uhr auf den gespeicherten Stand zurueck und kann in seltenen Faellen
einen Monat zu viel zaehlen.

Geprueft: echter Sprung, normaler Tagesverlauf, Erstlauf, fehlende Angabe als
0/null/NaN, kleiner Ruecksprung, Ruecksprung am Vormittag, unsinnige Groessen,
und dass eine fehlende dayTime im XML als null ankommt statt als 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:05:21 +02:00
D4rkst3randClaude Opus 5 b69cb6c5e4 Freie Links je Server statt fester Felder
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
connect_url und mods_url waren zwei Spalten fuer zwei Zwecke — und beim
naechsten Zweck waere eine dritte dazugekommen. Jetzt gibt es eine Liste:
Beschriftung plus Adresse, so viele wie noetig. Regeln, TeamSpeak, Shop,
Bewerbung.

Als JSON-Spalte, nicht als eigene Tabelle: es haengt an genau einem Server,
wird immer komplett gelesen und immer komplett geschrieben. Gelesen wird es als
Liste, geschrieben darf es als Liste hereinkommen — better-sqlite3 kann kein
Array binden und wuerde sonst werfen. Kaputtes JSON ergibt eine leere Liste
statt eines Absturzes im Monitor.

Discord nimmt fuenf Knoepfe je Reihe; ab jetzt werden sie umgebrochen, bei
zwei Reihen ist Schluss. Die Grenze steht im Code und nicht im Fehler beim
Senden, den niemand sieht. Zu lange Beschriftungen werden auf 80 Zeichen
gekuerzt, halbe Zeilen fliegen raus statt als leerer Knopf zu erscheinen. Was
nicht http(s) ist — ts3server://, steam:// —, wird wie bisher ein Codeblock im
Embed zum Kopieren.

Dabei umbenannt: das Embed-Feld fuer einen nicht klickbaren Connect-Link hiess
"Connect", der Knopf daneben "Verbinden". Jetzt heisst beides gleich.

Geprueft: Umbruch bei zwoelf Links, Reihenfolge mit Connect und Mods vorn,
halbe Zeilen, ueberlange Beschriftung, nicht klickbare Schemata, fehlendes und
unbrauchbares links-Feld, dazu Speichern, Aendern und kaputtes JSON in der
Datenbank. Panel und /server-Seite im laufenden Frontend angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:39:10 +02:00
D4rkst3randClaude Opus 5 5de172f862 LS bekommt einen festen Platz unter Gaming
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Das Modul stand nur unter "Meine Funktionen" — also solange, wie du es
angeheftet laesst. Loest man die Nadel, ist es nur noch uebers Modul-Raster
erreichbar. Fuer die groesste Seite im Panel zu wenig.

Jetzt kann ein Modul `nav: true` setzen und bekommt damit einen Eintrag in der
Seitenleiste, unter seiner eigenen Gruppe. Kein Sonderfall fuer LS: jedes
Modul, das eine ganze Seite fuellt, kann sich so einsortieren.

Dabei aufgefallen und mitgenommen: eine halbe Antwort von /api/modules/:id hat
die ganze Config weiss gemacht. Der Ladepfad ist zwar sauber abgesichert —
`fehler` und `!detail` fangen ab —, aber ein Objekt OHNE die Listen kommt
durch beide Wachen und laesst dann `detail.werte.some(...)` werfen, was React
mit dem gesamten Baum quittiert. Vier Listen mit `?? []` kosten nichts und
machen aus dem Weissbild schlimmstenfalls eine leere Sektion.

Zwei Werkzeug-Fallen nebenbei: `npx vite build` zieht mal das lokale Vite 6,
mal ein gecachtes Vite 8 aus dem npx-Ordner — ab jetzt nur noch
`npm --prefix frontend run build`. Und der laufende Preview-Server haelt
dist/assets fest, was den Build mit EPERM abbricht; vorher stoppen.

Geprueft: Seitenleiste zeigt Gaming mit Game-Server und LS, Klick oeffnet die
Modulseite und markiert den Eintrag, kein Fehler in der Konsole.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 08:38:52 +02:00
D4rkst3randClaude Opus 5 9f929fe0e4 Eigene Kategorie Gaming fuer Game-Server und LS
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Game-Server-Monitor stand unter "Server & Technik" zwischen DB-Sicherung,
Repo-Sicherung und Member-Gate. Technisch nicht falsch — dort laeuft alles,
was der Bot an Infrastruktur anfasst. Nur sucht niemand seinen LS-Server neben
dem Datenbank-Backup. Mit dem LS-Modul waren es zwei Eintraege in der falschen
Schublade, und das reicht fuer eine eigene.

Getrennt wird nach dem, worum es geht: "Server & Technik" ist der Bot selbst,
"Gaming" sind die Spiele-Server drumherum. Beide Ebenen ziehen mit — die
Modul-Kacheln ueber MODULE_GROUPS, die Seitenleiste ueber TAB_SECTIONS —, und
der Tab heisst jetzt "Game-Server" statt "Server", weil "Server" im selben
Panel schon zweierlei bedeutete.

Nebenbei: das Modul hiess "Server-Monitor" und ueberwacht Game-Server; jetzt
steht das auch dran. Die Suche im Panel findet den Tab zusaetzlich ueber "ls",
"landwirtschaft", "farming", "hof", "karte" und "preise".

Geprueft: kein Modul ohne Gruppe, keine leere Gruppe, Seitenleiste und
Modul-Kacheln im laufenden Panel angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 08:24:34 +02:00
D4rkst3randClaude Opus 5 1075b91121 Kanaele werden auch angezeigt, Monate statt Schluessel, Sorten auf Deutsch
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die Kanalauswahl blieb leer, obwohl "einsatzbereit" gruen daneben stand — ein
Widerspruch, der die Ursache schon verraet: der Wert lag in der Datenbank, das
Formular bekam ihn nur nie zu sehen. Ich hatte beim letzten Mal das Speichern
repariert und uebersehen, dass currentSettings() dieselbe von Hand gepflegte
Liste fuehrt. Jetzt holt auch sie die Schluessel aus dem Modul-Register.
Geheimes bleibt aussen vor: gitea_api_token steht unter requires, faellt aber
durch die Namenspruefung und wird weiterhin nur als "ist gesetzt" gemeldet.

Dazu die Uebersetzung. Im Embed standen wheat, clover_windrow und seeds, weil
der Feed nun mal Englisch spricht. Jetzt Weizen, Rotklee (Schwad) und Saatgut,
mit lesbarem Rueckfall fuer alles, was nicht in der Liste steht — 371 Sorten
uebersetzt niemand von Hand.

Und der Monat. Die zwoelf Perioden sind schlicht Monate, EARLY_SPRING ist
Maerz. Also nimmt das Feld jetzt "September", "sept" oder "9" statt
EARLY_AUTUMN; Mehrdeutiges wie "Ju" wird abgelehnt statt geraten.

Weiterzaehlen kann der Bot selbst, seit klar ist, dass ein Monat ueber einen
Tag laeuft. Nicht ueber playTime — die steht im Savegame und wird nur beim
Autosave geschrieben: ueber zwei Stunden gemessen stieg sie um 50, waehrend
die Spieluhr 18,7 Stunden weiterlief. dayTime in der Statusabfrage ist dagegen
live, und jeder Ruecksprung um Mitternacht ist ein Monatswechsel. Steht der
Server leer, steht die Spielzeit — und der Monat bleibt richtigerweise stehen.

Geprueft: alle zwoelf Monate hin und zurueck, Abkuerzungen, Monatszahlen,
Mehrdeutiges, acht Sorten Unsinn, der Jahreswechsel von Februar auf Maerz, der
Ruecksprung um Mitternacht samt Erstlauf, und dass jede Sorte der
Standardauswahl einen deutschen Namen hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 02:31:20 +02:00
D4rkst3randClaude Opus 5 c5e13182c0 Modul-Felder speichern wieder, und die Feed-Adresse ist einstellbar
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die LS-Kanaele liessen sich im Panel waehlen und waren nach dem Speichern
wieder leer. Der Grund: PUT /api/settings fuehrt fuer jede Feldart eine von
Hand gepflegte Liste erlaubter Schluessel, und ein Schluessel, der nirgends
steht, faellt still durch. Keine Fehlermeldung, kein Log — das Feld sieht
funktionsfaehig aus und vergisst.

Das ist keine Einzelheit, sondern eine Falle fuer jedes kuenftige Modul: wer
eins ergaenzt und diese Listen nicht mitpflegt, baut denselben Fehler nach.
Die Modulliste weiss laengst, welche Felder es gibt und welcher Art sie sind —
also holt der Handler sie jetzt von dort, statt sie ein zweites Mal
aufzuzaehlen.

Beim Bauen des Helfers ware ich fast in die naechste Falle gelaufen: ich hatte
alle requires-Eintraege als Kanaele eingestuft. Dort stehen aber auch
gitea_api_token und watchdog_urls, und die haetten dann die Kanal-Pruefung
durchlaufen — Speichern waere mit "Kanal nicht gefunden" gescheitert, sobald
jemand seinen Gitea-Token aendert. Uebernommen wird jetzt nur, was der
Namenskonvention folgt.

Dazu die fest verdrahtete Feed-Adresse. http://host:port/feed war eine Wette
auf drei Annahmen gleichzeitig: dass jeder Hoster http nimmt, den Feed unter
/feed ablegt und keinen Pfad davorsetzt. Aendern konnte das niemand. Jetzt gibt
es ein Feld dafuer, und wer die ganze Abfrage-URL ins Host-Feld einfuegt,
bekommt es mitsamt Schema und Pfad automatisch ausgefuellt. Was dahinter kommt
(dedicated-server-stats.xml und Geschwister) gibt Giants vor und bleibt im
Code.

Geprueft: jedes Feld jedes Moduls landet in einem Eimer — der Test faellt
kuenftig aus, sobald jemand ein Feld ergaenzt, das nicht speicherbar waere.
Dazu, dass nichts faelschlich in die Kanal- oder Rollenpruefung geraet, keine
Dubletten, und die Feed-Basis mit eigenem Pfad, https, ueberzaehligen
Schraegstrichen sowie sechs Sorten Unbrauchbarem. Das Einfuegen einer
https-URL mit Pfad im laufenden Panel angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 02:13:32 +02:00
D4rkst3randClaude Opus 5 768fcda666 Karte: die Welt ist doppelt so breit wie mapSize
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die Parzellen lagen im Wald. Ich hatte -mapSize/2 bis +mapSize/2 angenommen —
naheliegend beim Namen, aber falsch: die Koordinaten laufen von -mapSize bis
+mapSize. Damit wurde jede Parzelle um Faktor zwei vom Mittelpunkt weggezogen.

Schlimmer als der Fehler war meine Pruefung. Der Zonentest hat die Koordinaten
auf ihre eigene Bounding-Box normiert und danach geschaut, ob die Felder in der
richtigen Ecke liegen. Das besteht bei JEDEM Massstab, weil die Anordnung
zueinander ja stimmt. 14 von 15 Treffern klangen ueberzeugend und sagten
ueber den Massstab genau nichts aus.

Gemessen wurde es dann so: die eingebrannte Legende steht sowohl im Feed-Bild
als auch im Ingame-Screenshot, damit laesst sich der eine aufs andere
umrechnen. Vier Parzellen an den vier Ecken ergaben eine Weltbreite von 7822
bei mapSize 4096 — Faktor 1,91 bei rund 5 Prozent Ablesegenauigkeit.

Dabei ist noch etwas herausgekommen: die Marken H1..H8 auf dem Luftbild sind
als Passpunkte hervorragend, nicht wertlos, wie ich in der Doku behauptet
hatte. Mit richtigem Massstab liegen alle acht Hofparzellen unter 13 px von
ihrer Marke; mit falschem rund 200. Das ist jetzt der Test — acht Passpunkte,
die jede kuenftige Verschiebung sofort auffliegen lassen, statt eines
Zonenvergleichs, der alles durchwinkt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:59:21 +02:00
D4rkst3randClaude Opus 5 e215cd4c7c LS-Modul: Besitzkarte auf dem Luftbild und Preiskurven
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Feed eines LS-Servers gibt viel mehr her als die Spielerzahl, die der
Monitor bisher davon benutzt hat: in denselben 49 KB stehen 163 Parzellen mit
Besitzer und Hektar, 130 Felder, jedes Fahrzeug samt Ladung und die Position
jedes Spielers. Und das Luftbild liefert der Server auch, in jeder Groesse bis
4096.

Also eine Karte: gekaufte Parzellen nach Hof eingefaerbt, Kreisflaeche gleich
Hektar, Fahrzeuge und Spieler live, dazu eine Hof-Uebersicht mit Flaeche und
Wert. Wechselt Land den Besitzer, kommt eine Meldung — beim allerersten
Durchlauf bewusst nicht, sonst haette der Botstart eine Meldung pro bereits
gekaufter Parzelle abgesetzt.

Dazu die Preiskurven aus der economy.xml: je Fruchtart, was sie in welcher der
zwoelf Perioden bringt, als Balkenzeile mit "jetzt verkaufen" oder "+43 Prozent
im Hochwinter".

Was NICHT geht, und zwar grundsaetzlich: welche Frucht auf welchem Feld steht
und wie weit sie ist. Das liegt in einer binaeren Density-Map, die kein
Endpunkt herausgibt. Ich habe 18 Dateinamen durchprobiert, es gibt genau drei.
Die Mods, die das anzeigen, sind reine HUD-Overlays ohne Ausgang. Und keiner
der fuenf anderen LS-Bots kann es, obwohl einer davon Geld kostet. Das steht
mit allen Belegen in docs/ls-feed.md, damit die Frage nicht dreimal kommt.

Beim Bauen aufgefallen:

- istLsServer() gab den Zugangs-Code zurueck statt true — die &&-Kette liefert
  den letzten wahren Wert. Beim ersten Log-Aufruf haette der Code im Klartext
  im Protokoll gestanden.
- Die Karte als PNG war 2,5 MB, als JPEG 268 KB. Es ist ein Luftbild.
- Nach Preisschwankung sortiert gewinnt Spargel: 26 bis 7540 Euro, und Weizen
  taucht gar nicht mehr auf. Die Liste arbeitet jetzt mit einer Auswahl.
- Die Jahreszeit steht in keinem Endpunkt. Schaetzbar waere sie, aber nur
  solange niemand die Zeitskala aendert — also eine Einstellung.

Geprueft gegen echte Serverdaten: Zerlegen der Statusabfrage inklusive
Vollstaendigkeit aller 163 Parzellen, Ladung ohne Diesel und leere Slots,
Hof-Uebersicht ohne verlorene Parzellen, Preiskurve mit vollem und leerem
Balken an der richtigen Stelle, flache Kurve ohne Division durch Null,
Besitzwechsel inklusive Erstlauf und neu hinzugekommener Parzelle,
Hof-Namen aus der Einstellung, SVG ohne NaN und mit maskierten Namen, und der
Merker in der Datenbank inklusive kaputtem JSON.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:52:25 +02:00
D4rkst3randClaude Opus 5 eec170ff41 Connect-Links im Discord anklickbar, und Mods bekommen einen eigenen Knopf
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Im Kanal stand woertlich das da:

    [https://cfx.re/join/4odv68](https://cfx.re/join/4odv68)

Der Link war sein eigener Linktext. Discord macht aus der nackten URL zwischen
den eckigen Klammern zuerst selbst einen Link und kommt mit der
Masked-Link-Schreibweise dann nicht mehr durch — heraus kommen die Klammern im
Klartext. Der Kommentar an der Stelle behauptete ausserdem, so seien steam://
und Konsorten klickbar. Waren sie nie.

Jetzt sind es Link-Knoepfe unter dem Embed. Die Frage nach der Schreibweise
stellt sich damit gar nicht mehr, und ein Knopf sieht auch nach einem aus.
Discord nimmt dort nur http(s) an; fivem:// und steam:// stehen deshalb weiter
im Embed, aber als Codeblock zum Kopieren statt als toter Link.

Dazu ein eigenes Feld fuer den Mod-Download. Vorher landete der beim
Connect-Link, weil es kein anderes gab — und dann fuehrte "Verbinden" zu einer
ZIP-Datei. Farming Simulator liefert die Modliste ohnehin mit, also steht die
Anzahl jetzt auch im Embed und als Chip auf der Seite.

Beim Testen aufgefallen: eine neue Spalte hat jeden Aufrufer umgebracht, der
sie noch nicht kannte — better-sqlite3 wirft bei einem fehlenden benannten
Parameter. createGameserver und updateGameserver fuellen die Felder jetzt
einmal auf, statt das an jeder Aufrufstelle nachzuziehen.

Geprueft: was in einen Knopf darf und was nicht (https, http, fivem://,
steam://, leer, null, Leerzeichen, Satz mit Link drin), beide Knoepfe in einer
Reihe, nur einer, gar keiner — eine leere Reihe weist Discord ab —, https
landet im Knopf und nicht doppelt auch im Embed, fivem:// umgekehrt, Mods bei
47/0/null, und der Aufruf ohne die neue Spalte. Seite im laufenden Frontend
angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:52:08 +02:00
D4rkst3randClaude Opus 5 4e7fc99999 Server-Seite ueberarbeitet, und die Spielauswahl kommt jetzt aus gamedig
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
LS25 fehlte, aber nicht nur LS25: die Auswahl im Panel war eine von Hand
gepflegte Liste mit neunzehn Eintraegen, und fuenf davon gab es bei gamedig
gar nicht — "arkse", "ark", "sevendaystodie", "minecraftbe" und "terraria".
Wer eines davon auswaehlte, bekam einen Server, der dauerhaft offline stand,
ohne dass irgendwo ein Grund stand. Die echten IDs heissen ase, sdtd, mbe und
terrariatshock.

Also nicht LS25 nachtragen, sondern die Liste abschaffen: sie kommt jetzt aus
gamedig selbst, 360 Eintraege, sortiert, mit Suche. Jedes Update bringt neue
Spiele automatisch mit. Dazu der Standard-Query-Port als Vorschlag, sobald man
ein Spiel waehlt. Bestehende Eintraege mit den toten IDs werden beim Start
einmalig umgebogen.

Farming Simulator hat noch eine zweite Huerde: der Server antwortet nur mit
Zugangs-Code, und den hat queryServer nie durchgereicht. LS25 waere also auch
mit richtiger ID offline geblieben. Jetzt gibt es eine Spalte dafuer, das Feld
erscheint nur bei Spielen, die einen brauchen (Farming Simulator, Terraria —
bei Satisfactory optional), und Speichern ohne Code wird abgelehnt statt
stillschweigend hingenommen. Der Code steht nicht in der oeffentlichen API.

Nebenbei aufgefallen: input[type=password] war im CSS nirgends erfasst. Das
erste solche Feld kam voellig ungestylt daher.

Die /server-Seite dazu:

- Auslastungs-Balken mit eigener Farbe statt des XP-Balkens vom Level-System.
  Gruen frei, gelb ab 80 Prozent, rot voll — die Zahl steht rechtsbuendig,
  mittig laege sie bei 50 Prozent genau auf der Fuellkante.
- Map, Version und Ping als Chips. Vorher standen sie mit Punkten getrennt in
  der Statuszeile und wurden auf dem Handy zu Brei.
- Wer gerade drauf ist. Die Namen holt der Monitor ohnehin schon fuers Embed,
  auf der Seite standen sie nur nirgends.
- Welches Spiel es ueberhaupt ist. Das stand nirgends ausser im Emoji.
- Offline-Karten waren bisher leer bis auf das Wort. Jetzt steht dort, seit
  wann — soweit der Verlauf reicht, sonst gar nichts statt einer Schaetzung.
- Zeitachse und Peak-Linie am Verlauf. Eine Flaeche ohne Bezugspunkt sagt
  nicht, wie hoch hoch ist.
- Plaetze insgesamt in der Uebersicht oben.

Geprueft: Token speichern und aendern, Spielnamen inklusive unbekannter ID,
Token-Bedarf pro Protokoll, Liste ohne Doppler, alle Icon-Schluessel gegen
gamedig, "zuletzt online" ignoriert Offline-Proben, und die Migration mit einer
Datenbank im alten Stand. Beide Seiten am laufenden Frontend angesehen, auch
auf Handybreite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 16:52:09 +02:00
D4rkst3randClaude Opus 5 22054c109e Mod-Log: eigenes Gedaechtnis, damit beim Loeschen nicht "unbekannt" dasteht
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Dein Eintrag war der Normalfall, nicht die Ausnahme:

    User: unbekannt
    Inhalt: [kein Text / nicht im Cache]

Discord schickt beim Loeschen nur eine Huelle, sobald die Nachricht nicht mehr
im Cache des Bots liegt. Der reicht ein paar hundert Nachrichten weit und faengt
erst beim Start des Bots an — bei einem Post von gestern ist sie also nie drin.
Ein Protokoll, das genau dann nichts weiss, wenn es interessant wird, ist keins.

Also merkt sich der Bot Nachrichten jetzt kurz selbst: Verfasser, Wortlaut,
Namen der Anhaenge. Beim Loeschen steht das im Eintrag, danach wird der Merker
weggeworfen. Wie lange aufbewahrt wird, steht unter Werte — sieben Tage
voreingestellt, 0 heisst gar nicht mitschreiben. Bewusst knapp und einstellbar:
das ist ein Werkzeug zum Nachvollziehen von Loeschungen, kein Archiv.

Dazu drei Sachen, die vorher fehlten:

- Wer geloescht hat. Steht in Discords Audit-Log, aber nur wenn es jemand
  anders war als der Verfasser. Gemeldet wird nur, was frisch ist und zu Kanal
  und Verfasser passt — Discord zaehlt bei wiederholtem Loeschen denselben
  Eintrag hoch statt neue anzulegen, da ist eine falsche Angabe schnell
  gemacht. Lieber keine als eine falsche.
- Sammel-Loeschungen. /purge und Discords eigenes Aufraeumen standen gar nicht
  im Log: eine geloeschte Nachricht fiel auf, hundert nicht. Jetzt mit Anzahl,
  Kanal und den ersten fuenfzehn Zeilen.
- Anhaenge. Dass ein Bild dranhing, war vorher nirgends zu sehen — und das
  Bild selbst ist nach dem Loeschen ohnehin weg.

Nebenbei: "[kein Text / nicht im Cache]" waren zwei Aussagen in einer und beide
unklar. Jetzt steht dort entweder der Wortlaut, oder dass die Nachricht keinen
hatte, oder dass sie aelter ist als die Aufbewahrung reicht.

Beim Testen gefunden: pruneMessageCache(0) hat nichts geloescht. "-0 days" ist
genau jetzt, und `created_at < jetzt` laesst die eben geschriebene Zeile stehen.
Bei 0 wird jetzt alles geleert, wie es gemeint war.

Geprueft: Merken, Wiederfinden, Anhaenge durch JSON, Bearbeiten zieht mit,
Vergessen nach dem Loggen, unbekannte ID, doppelte ID, Aufbewahrung mit einem
zu alten und einem frischen Eintrag, und der 0-Fall. Ereignisnamen und
AuditLogEvent gegen die installierte discord.js geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 09:25:37 +02:00
D4rkst3randClaude Opus 5 b6b767acb7 Bis fuenf Eintraege Knoepfe statt Auswahlmenue
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Du hattest recht. Bei vier Anliegen ist ein Auswahlmenue der schlechtere Weg:
zwei Klicks statt einem, und die Moeglichkeiten sieht man erst nach dem
Aufklappen. Ein Knopf ist beides — sichtbar und sofort.

Umgekehrt gilt es nicht immer. Ab sechs wird die Knopfreihe zur Wand und
Discord bricht sie um; dann ist das Menue das ruhigere Bild. Also nicht
"Knoepfe statt Menue", sondern der Uebergang: bis fuenf Knoepfe, darueber
Menue. Fuenf, weil genau so viele in eine Reihe passen.

Betroffen waren nur zwei Stellen — Ticket-Anliegen und Wunsch-Bereiche. Alles
andere im Bot waren ohnehin schon Knoepfe.

Eine Sache verliert man dabei: die graue Beschreibungszeile, die ein Menue
unter jedem Eintrag anzeigt. Die steht jetzt im Text des Aufruf-Posts, und da
ist sie ehrlich gesagt besser aufgehoben — man liest sie, bevor man klickt,
statt danach.

Geprueft gegen discord.js selbst, weil Ueberlaengen und ungueltige Emojis sonst
erst beim Posten auffallen: null Eintraege (der eine alte Knopf), eins bis
fuenf (Knoepfe mit Emoji und der ID in der Kennung), sechs (zurueck zum Menue),
Murks-Emoji (faellt weg, Knopf bleibt), 200 Zeichen langer Name (auf 80
gekuerzt), und die Beschreibungszeile in allen drei Faellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 09:14:59 +02:00
D4rkst3randClaude Opus 5 b3758f1f76 Screenshots zu Ideen, und der Bot legt die Forum-Tags selbst an
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Zwei Fragen, zwei Antworten — eine davon ein Nein mit Umweg.

## Bilder im Eingabefenster: geht nicht

Discord-Modals koennen nur Textfelder. Kein Datei-Anhang, keine Umgehung — das
ist eine Grenze der Plattform, nicht unseres Codes.

Der Umweg ist besser als er klingt: Wer nach dem Einreichen einen Screenshot in
den Thread haengt, sieht ihn auf der Ideen-Seite. Die Bestaetigung nach dem
Absenden sagt das jetzt auch. Damit kann man auch nachtraeglich noch Bilder
dazulegen, was im Eingabefenster gar nicht ginge.

## Warum nicht einfach der Discord-Link

Discord haengt seit einiger Zeit eine Signatur an die Anhang-Links
(?ex=&is=&hm=), die nach etwa einem Tag ablaeuft. Ein gespeicherter Link zeigt
morgen ein kaputtes Bild. Deshalb laden Devlogs und Galerie seit dem ersten Tag
herunter — im Kopf von devlog-archive.js steht der Grund. Wuensche machen es
jetzt genauso, ausgeliefert unter /wish-assets/.

Beim Loeschen eines Beitrags oder eines ganzen Wunsches gehen die Dateien mit;
sonst waechst der Ordner mit Bildern, auf die nichts mehr zeigt.

Das Herunterladen stand vorher zweimal fast wortgleich im Code. Mit den
Wuenschen waeren es drei Kopien geworden — also einmal nach bot/bilder.js, und
Devlogs und Galerie ziehen mit um. Ein Fehler steckt jetzt an einer Stelle
statt an dreien.

## Forum-Tags per Knopf

Bisher musstest du sie von Hand anlegen. Jetzt macht es das Panel: die Bereiche
von dort plus die fuenf Staende, mit ihren Emojis. Vorhandene bleiben, auch
fremde — die Liste wird ergaenzt und nicht ersetzt. Discords Grenze von 20 Tags
wird eingehalten, was nicht mehr reinpasst, wird gemeldet statt still
verschluckt.

Geprueft: forumTagsAnlegen gegen eine Kanal-Attrappe — leeres Forum, teilweise
vorhandene, Gross/Kleinschreibung, fremde Tags, nichts zu tun (dann wird auch
nicht geschrieben), volles Forum, kein Forum, kein Kanal. Im Browser die
Ideen-Seite mit Bildern in einem Beitrag, einem Beitrag nur aus Bild ohne Text,
und der Lightbox darueber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 00:17:01 +02:00
D4rkst3randClaude Opus 5 5073e7bcf4 Jede Idee bekommt ihre eigene Seite mit der Unterhaltung aus dem Thread
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Vorbild windrose.support: Klick auf eine Idee, und man sieht sie ganz — Stand,
Begruendung, wer sie eingereicht hat, und darunter, was dazu geschrieben wurde.

Der Unterschied bleibt, wo geschrieben wird. Die Beitraege sind aus dem
Discord-Thread gespiegelt, nicht hier getippt. Deshalb fuehrt der einzige
Knopf am Ende in den Thread und nicht in ein Formular. Ein zweiter Ort fuer
dieselbe Unterhaltung waere genau das, was ich beim Konzept als das Falsche
bezeichnet habe.

Bisher wurde nur mitgezaehlt. Jetzt landen die Beitraege in einer eigenen
Tabelle — mit Bearbeiten und Loeschen, sonst stuende auf der Webseite fuer
immer, was in Discord laengst zurueckgenommen wurde. Reine Bild-Posts ohne Text
bleiben draussen, die haetten hier nichts zu sagen.

Der Kommentar-Zaehler wird jetzt aus der Tabelle gezaehlt statt in einer Spalte
mitgefuehrt. Eine Wahrheit statt zwei, die auseinanderlaufen koennen — dieselbe
Ueberlegung wie beim Zuspruch.

Nebenbei geprueft, weil es haette schiefgehen koennen: /api/wishes/suche und
/api/wishes/:id liegen auf derselben Ebene. Der Router bevorzugt die feste
Route vor der mit Platzhalter — nachgestellt mit find-my-way, nicht aus dem
Gedaechtnis behauptet.

Geprueft: im Browser die Seite mit drei Beitraegen und die ohne (kein Thread,
keine Begruendung, Hinweis statt Liste), der Weg von der Roadmap dorthin ohne
Neuladen, Migration erneut durchgespielt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 00:05:27 +02:00
D4rkst3randClaude Opus 5 1856a1ed83 Foren waehlbar, Emoji-Tafel repariert, Balken sagt seit wann er misst
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Drei Dinge, die beim Ausprobieren aufgefallen sind.

## Foren standen nicht zur Auswahl

Ein Forum-Kanal ist fuer discord.js nicht "textBased" — er hat keine
Nachrichten, nur Beitraege. Die Kanal-Liste im Panel hat genau danach
gefiltert, also tauchte das Forum nirgends auf, und beim Speichern haette
dieselbe Pruefung es abgelehnt. Beides kennt jetzt Foren und Medien-Kanaele;
in der Auswahl stehen sie mit 📋 statt # und dem Hinweis "Forum".

Das erklaert auch das "Unknown Channel" beim Testen: eingestellt war noch der
alte Kanal, und der neue liess sich gar nicht erst waehlen.

## Emoji-Tafel landete in der Nachbarspalte

Die Config laeuft ab genug Breite zweispaltig — CSS `columns: 2`. In einem
Mehrspalten-Layout zerreisst es absolut positionierte Kaesten: die Tafel wurde
fragmentiert und ihr Inhalt in der anderen Spalte gezeichnet, waehrend beim
Feld nur der leere Rahmen stehen blieb. Genau so sah es auch aus.

Die Tafel haengt jetzt an <body> und wird per JS an den Knopf gerechnet, wie
der Dialog auch. Sie klappt nach oben, wenn unten kein Platz ist, wandert beim
Scrollen mit und bleibt beim Fenstergroesse-Aendern an ihrem Feld.

## Balken sagt jetzt, seit wann er misst

Die Waechter-Balken sahen leer aus. Sind sie nicht: 545 Messpunkte, alle
gruen — aber eben nur von heute, weil die Dienste gestern angelegt wurden.
Sechs von sieben Tagen sind zu Recht grau, das sieht nur aus wie kaputt.

Steht links jetzt "misst seit 01.08." statt stur "vor 7 Tagen", sobald die
erste Messung deutlich spaeter liegt als der Anfang des Fensters. Bei vollem
Verlauf bleibt es bei "vor 7 Tagen".

Geprueft im Browser: Tafel an <body> und `position: fixed`, richtig
positioniert und nach oben geklappt, alle sieben Gruppen und 127 Symbole in
einem Stueck; Forum in der Auswahlliste mit Kennzeichnung; beide
Beschriftungen des Balkens (angebrochener und voller Verlauf).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:59:19 +02:00
D4rkst3randClaude Opus 5 cc7a51f02e Emojis zum Anklicken, und Wuensche als Forums-Beitraege mit Tags
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Ueberall, wo ein Emoji nach Discord geht — Rollen-Menues, Ticket-Anliegen,
Wunsch-Bereiche, Portal-Kacheln — stand ein leeres Textfeld, in das man mit
Win+. hineintippen musste. Jetzt sitzt daneben ein Knopf mit einer Tafel:
sieben Gruppen, deutsche Suchbegriffe ("auto", "geld", "warnung"), ein Klick
schreibt ins Feld. Das Feld bleibt daneben stehen, denn Server-Emojis
(<:name:123>) kennt nur Discord.

Bewusst eine eigene Liste statt einer Bibliothek: die vollstaendige
Unicode-Tabelle waere ein halbes Megabyte fuer eine Handvoll Symbole, und die
Suchbegriffe duerfen deutsch sein.

## Forum-Tags

Ist der Voting-Kanal ein Forum, wird jeder Wunsch jetzt ein Forums-Beitrag
statt Nachricht-plus-Thread — und bekommt Tags fuer Bereich und Stand. Damit
laesst sich auch in Discord filtern, wofuer Tags ja da sind. Beim Wechsel des
Stands zieht das Tag mit: der alte fliegt raus, der neue kommt rein, der
Bereich bleibt stehen.

Zugeordnet wird ueber den Namen. Wer im Forum ein Tag "Fahrzeuge" anlegt,
bekommt es automatisch gesetzt; wer keines anlegt, verliert nichts. Bewusst
keine ID-Zuordnung im Panel — das waere eine zweite Liste, die man pflegen
muss und die beim ersten Umbenennen auseinanderlaeuft.

Dabei aufgefallen und mitgefixt: im Forum liegt die Startnachricht im Beitrag
selbst und nicht im Kanal. Der Status-Aktualisierer hat sie vorher im Kanal
gesucht und nicht gefunden — die Begruendung waere im Forum also nie im Post
gelandet. Und der Titel wird jetzt nur um den Stand ergaenzt statt
ueberschrieben, damit "🚗 Fahrzeuge" stehen bleibt.

Geprueft: tagsFuer gegen 20 Faelle — kein Forum, Medien-Kanal, fehlende Tags,
Gross/Kleinschreibung, Deckel bei fuenf (mehr nimmt Discord nicht), und alle
fuenf Staende einzeln. Im Browser die Emoji-Tafel an allen vier Stellen:
Suche, Escape, Klick daneben, Leeren, Uebernehmen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:45:18 +02:00
D4rkst3randClaude Opus 5 edf8fa14c5 Ideen bekommen Bereiche und einen Thread zum Reden
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Nach dem Vorbild von windrose.support: Bereiche zum Einsortieren, Sortierung
nach Top / Bewegung / Neu, und eine Zahl daneben, wie viel darueber geredet
wird.

Der Unterschied zu so einem Dienst ist, wo geredet wird. Zu jedem Wunsch macht
der Bot jetzt einen Thread unter dem Post auf. Der Kommentar-Zaehler auf der
Roadmap zaehlt die Beitraege darin und verlinkt hinein — es gibt also einen
Zaehler wie bei einem Ideen-Board, aber ohne ein zweites Kommentarsystem neben
Discord zu stellen. Genau das hatte ich beim letzten Mal als das Falsche
bezeichnet, und dabei bleibt es.

Eingereicht wird jetzt auch ueber einen Aufruf-Post: /wunsch-setup postet ihn,
und mit Bereichen wird daraus ein Auswahlmenue — erst wohin, dann das
Formular. Ohne Bereiche bleibt es beim einen Knopf. Derselbe Aufbau wie bei den
Ticket-Anliegen, bis hin zur Emoji-Pruefung, die ich mir dort schon geschrieben
hatte.

Das Anlegen selbst ist nach bot/wishes.js gewandert. /wunsch, der Knopf und die
Webseite gehen jetzt denselben Weg — vorher hatte die Webseite ihr eigenes
Embed zusammengebaut, das dem aus dem Befehl nur aehnlich sah und keinen Thread
bekam.

"Bewegung" sind die Stimmen der letzten sieben Tage. Die Rangliste allein
zementiert alte Wuensche: was einmal oben steht, bleibt oben, egal ob noch
jemand hinschaut. Sortiert wird auf dem Server, weil die Zeitstempel der
Stimmen im Browser gar nicht ankommen.

Bewusst nicht uebernommen: Gegenstimmen. Windrose hat sie, aber bei einer
Community dieser Groesse laden sie zum Nachtreten ein und schrecken vom
Einreichen ab — und eine Zahl, die aus zwei Richtungen kommt, sagt am Ende
weniger als eine, die nur zaehlt, wer etwas will.

Geprueft: Migration erneut durchgespielt (das Schema hat drei Spalten
dazubekommen), Routen, SQL gegen das Schema, und im Browser alle drei
Filterreihen einzeln und kombiniert, der Sortierwechsel, die Thread-Links und
die Bereichs-Pflege im Panel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:31:55 +02:00
D4rkst3randClaude Opus 5 fb61e7279d Wuensche bekommen einen Ausgang statt nur einer Punktzahl
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Voting hatten wir. Was fehlte, war alles danach: ein Wunsch sammelte zwoelf
Stimmen und dann passierte sichtbar nie wieder etwas damit. Genau das nennt der
Artikel, der die Idee angestossen hat, "letting the board become a graveyard" —
und das war der Zustand.

Jeder Wunsch hat jetzt einen Stand: wird geprueft, geplant, in Arbeit,
umgesetzt, nicht geplant — mit Begruendung. Die steht oeffentlich unter dem
Wunsch auf der Roadmap und wird in den Discord-Post zurueckgeschrieben, wo
abgestimmt wurde. Ein abgelehnter Wunsch mit einem Satz Begruendung ist mehr
wert als einer, der ewig oben schwebt.

Springt ein Wunsch auf "umgesetzt", bekommt jeder eine Direktnachricht, der
dafuer gestimmt hat. Das ist der Punkt, an dem unser Aufbau einem fertigen
Voting-Dienst ueberlegen ist: der Draht zu jedem Einzelnen ist ohnehin offen.

## Zwei Sachen, die kaputt waren

Doppelt abstimmen ging. Der Zuspruch wurde von zwei Wegen hochgezaehlt — 👍 in
Discord und der Web-Knopf — und nur der Web-Weg merkte sich, wer geklickt hat.
Dieselbe Person zaehlte zweimal. Jetzt wird nicht mehr hochgezaehlt, sondern
gezaehlt: eine Zeile je Person, egal woher der Klick kam. Der zusammengesetzte
Schluessel schliesst den Fall aus, statt ihn nachtraeglich zu korrigieren.

Die Discord-Nachrichten-ID war der Primaerschluessel. Damit konnte ein Wunsch
nur existieren, solange seine Nachricht existiert, das Team konnte keinen von
Hand eintragen, und zwei Doppler liessen sich nicht zusammenfuehren. Wuensche
haben jetzt eine eigene ID; die Nachrichten-ID ist nur noch ein Verweis.

Die Migration erhaelt den Zuspruch: was vom alten Punktestand nicht auf
gespeicherte Web-Stimmen zurueckgeht, waren Reaktionen ohne Namen — die Zahl
bleibt als Sockel stehen, weil rueckwirkend niemand mehr feststellen kann, wer
das war. Durchgespielt gegen eine Datenbank im alten Aufbau: Punktestand
erhalten, verwaiste Stimmen fallen raus, ein zweiter Start migriert nicht
nochmal, und der Fall, in dem der alte Zaehler hinter den echten Stimmen
zurueckhing, zaehlt jetzt richtig.

## Dazu

Doppler zusammenfuehren: die Stimmen wandern zum Original, wer fuer beide
gestimmt hat, zaehlt dort weiterhin einmal. Beim Tippen im Wunsch-Feld zeigt die
Seite, was es schon gibt — ein Klick darauf stimmt mit, statt einen zweiten
gleichen Wunsch anzulegen. Und die Liste laesst sich nach Stand filtern.

Was ich bewusst nicht gebaut habe: Kommentare auf der Webseite. Jeder Wunsch
ist schon eine Discord-Nachricht — die Diskussion gehoert in den Thread
darunter und nicht in ein zweites System.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:18:29 +02:00
D4rkst3randClaude Opus 5 1c5c7d2470 Playtester werden jetzt beworben, nicht angeklickt
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Knopf unter dem Playtester-Aufruf hat jeden aufgenommen, der ihn gedrueckt
hat — ohne Frage, ohne Pruefung. Fuer ein Programm, dessen Teilnehmer Zugang
und Alpha-Keys bekommen, ist das die falsche Tuer.

Statt einen zweiten Pruef-Ablauf danebenzustellen, laeuft die Aufnahme jetzt
ueber die Bewerbungs-Formulare, die es laengst gibt: Modal ausfuellen,
Review-Embed im Staff-Kanal,  oder , Rolle und Antwort-DM bei Zusage. Das
ist dieselbe Maschinerie, nur ein anderer Aufhaenger — und damit auch nur eine
Stelle, an der spaeter etwas kaputtgehen kann.

Ein Formular laesst sich als Playtester-Formular markieren ("Annahme traegt als
Playtester ein"). Wer dort angenommen wird, landet zusaetzlich in der
Playtester-Liste, damit die Schluessel-Verteilung ihn kennt. Nur eines kann es
sein — sonst wuesste /playtester-setup nicht, welches es posten soll.

/playtester-setup postet jetzt den Knopf dieses Formulars. Fehlt das Formular,
fehlen Fragen oder fehlt der Review-Kanal, sagt der Befehl das, statt einen
Aufruf zu posten, der ins Leere fuehrt.

Der alte Knopf in bereits geposteten Nachrichten nimmt niemanden mehr auf: er
verweist auf den Aufruf. Austreten bleibt Selbstbedienung — dafuer braucht es
keine Freigabe.

Im Panel steht im Playtester-Bereich, ueber welches Formular die Aufnahme
laeuft, ob es schon gepostet ist, und ein Weg dorthin. Ohne markiertes Formular
steht dort, dass gerade niemand hereinkommt — das ist sonst der Grund, warum
sich tagelang niemand bewirbt.

Geprueft: Routen, SQL gegen das Schema, und im Browser beide Zustaende des
Hinweises sowie der neue Schalter — beim Wechsel zwischen zwei Formularen
folgt er dem richtigen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:04:16 +02:00
D4rkst3randClaude Opus 5 ddd7610d6c Playtester bekommen einen eigenen Bereich, Module ziehen um
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Playtester und Alpha-Keys standen als zwei Kaesten mitten im Community-Bereich
zwischen Geburtstagen und Starboard. Die Liste war reine Anzeige: Namen und
Datum, sonst nichts. Wer einem Einzelnen einen Schluessel geben wollte, konnte
nur "an alle verteilen" druecken, und wer jemanden wieder rausnehmen wollte,
musste an die Datenbank.

Jetzt ist es ein eigener Bereich mit drei Zahlen oben und einer Liste, in der
neben jedem Namen sein Schluessel steht. Daneben, je nach Zustand, "Schluessel
geben" oder "Zurueckziehen", und ein ✕ zum Entfernen — die Rolle geht dabei
mit, denn nur aus der Liste zu streichen und die Rolle stehen zu lassen waere
eine Halbwahrheit.

Der Vorrat ist jetzt sichtbar, nicht nur seine Groesse: einen vertippten
Schluessel findet man sonst nie wieder. Nachlegen und "wer bekommt einen?"
laufen ueber Dialoge, damit der Bereich nicht zumuellt — dafuer gibt es jetzt
eine kleine Dialog-Komponente, gebaut wie die Lightbox (Portal, Escape,
Hintergrund festhalten).

Zurueckziehen legt den Schluessel zurueck in den Vorrat. Die Person hat ihn per
DM natuerlich weiterhin — das ist eine Buchhaltung, keine Sperre. Loeschen geht
absichtlich nur bei freien Schluesseln.

## Module dorthin, wo man sie sucht

Ein paar Module standen im falschen Bereich, teils seit Langem:

- Willkommens-Karte: Support → Brand. Sie ist Aussehen, kein Support-Fall, und
  im Brand-Bereich steht der Rest vom Auftritt. Der Block ist woertlich
  umgezogen, nur in eine eigene Konstante — so kann sich beim Umzug nichts am
  Inhalt geaendert haben.
- Twitch & YouTube: System → Feeds. Es ist ein Feed.
- Events: System → Community.
- Auto-Rolle und Rollen merken: Ihre Felder standen laengst im Rollen-Bereich,
  nur das Register sagte "Community" — dadurch sprang "Einrichten" woanders hin
  als die Einstellung liegt.
- Playtester und Alpha-Keys: Community → eigener Bereich.

Damit ist System kein Sammelbecken mehr, sondern nur noch Technik: Waechter,
Sprachkanaele, Erinnerungen, Sicherungen, Member-Gate.

Geprueft: Routen (alle vier neuen bewacht), SQL gegen das Schema, und im
Browser beide Dialoge (Escape schliesst, Hintergrund wird wieder freigegeben,
Hinzufuegen bleibt bei leerem Feld aus), die Liste in allen drei Zustaenden,
und die Suche — "willkommen" fuehrt jetzt nach Brand, "twitch" zu Feeds,
"sticky" zu Rollen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:49:27 +02:00
D4rkst3randClaude Opus 5 bb2aee7818 Tickets: Anliegen zur Auswahl, und das Team kann uebernehmen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Bisher sah jedes Ticket gleich aus: ein Knopf, ein Thread, und im Thread stand
als Erstes die Frage, worum es ueberhaupt geht. Und wer sich darum kuemmert,
stand nirgends — man sah hinterher nur, wer zufaellig auf "Schliessen" geklickt
hat.

Anliegen sind Kategorien, die man in der Config anlegt: Name, Emoji, eine
Zeile Beschreibung, ein Einstiegstext und optional eine Rolle, die beim
Oeffnen erfaehrt, dass etwas anliegt. Der Ticket-Post wird dadurch vom Knopf
zum Auswahlmenue; das gewaehlte Anliegen steht im Thread-Namen und der
Einstiegstext gleich im ersten Beitrag — gute Stelle fuer "was wir wissen
muessen".

Wer keine anlegt, merkt nichts: ohne Eintrag bleibt es beim einen Knopf. Wer
nur eine Sorte Tickets hat, soll nichts einrichten muessen.

Uebernehmen ist ein Knopf im Thread, sichtbar fuers Team (wer aufraeumen darf,
darf auch uebernehmen). Danach steht im Thread, wer sich kuemmert, und der
Knopf wird zu "Freigeben". Die Bedingung "nur wenn noch niemand dran ist"
steckt im UPDATE und nicht davor — zwei gleichzeitige Klicks wuerden sonst
beide gewinnen. Beim Schliessen landen Anliegen und Bearbeiter im Transcript
und im Mod-Log-Eintrag.

Die Erstellung ist dabei aus client.js nach tickets.js gewandert. Knopf und
Menue gehen jetzt denselben Weg, und in client.js sind drei Importzeilen
weggefallen, die nur noch dort standen.

Geprueft: alsEmoji gegen 20 Eingaben (Unicode, Hautfarbe, ZWJ-Ketten,
Server-Emoji, Murks) und das fertige Panel gegen discord.js selbst — ein
ungueltiges Emoji oder ein zu langes Label laesst Discord sonst die ganze
Nachricht fallen, und das faellt erst beim Senden auf. Im Browser: Liste,
Bearbeiten-Formular und der leere Zustand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:14:49 +02:00
D4rkst3randClaude Opus 5 e32152b5ac Eigener Herzschlag: der Bot kann jetzt auch ueber sich selbst Auskunft geben
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Waechter prueft alles ausser sich selbst — ist der Bot weg, prueft
niemand mehr, und hinterher sieht der Verlauf aus, als waere nie etwas
gewesen. Im Dashboard stand deshalb nur "seit 12d 4h", also wie lange es
diesmal gutgegangen ist. Was davor war, wusste niemand.

Jetzt schreibt der Bot alle zwei Minuten eine Zeile: laeuft der Prozess, und
steht die Verbindung zu Discord? Ein Bot, der laeuft aber nicht verbunden
ist, ist fuer alle draussen genauso weg — deshalb zaehlt beides.

Der Takt ist absichtlich nicht einstellbar. Ein spaeter geaenderter Abstand
wuerde den alten Verlauf falsch aussehen lassen: wie viele Proben eine
Stunde haette haben muessen, liesse sich rueckwirkend nicht mehr sagen.

Ausgewertet wird vor allem die Abwesenheit. Fuer einen fremden Dienst heisst
eine fehlende Stunde "niemand hat gemessen" und wird grau; beim Bot selbst
ist genau das die Aussage — fehlende Stunden werden rot. Grau bleibt nur,
was vor der allerersten Aufzeichnung liegt. Die angebrochene erste und die
laufende letzte Stunde rechnen anteilig, sonst waeren sie dauerhaft gelb.

Nebenbei mitgenommen:

- Statusseite und die frei angelegten Seiten stehen jetzt in der Fusszeile
  beider Seiten — dort sucht man Rechtliches. Die Navigationsleiste zeigt
  weiterhin nur, was "im Menü" gesetzt hat; wer die Nutzungsbedingungen dort
  raus nimmt, hat sie trotzdem noch in der Fusszeile.
- Die Config-Spalte hatte keine Obergrenze. min-width:0 allein reicht nicht,
  wenn ein Kind eine grosse Mindestbreite mitbringt — mit dem Balken wuchs
  sie auf 724 statt 335 px, und weil der Body waagerecht abschneidet, war
  der Ueberstand nicht scrollbar, sondern weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:10:53 +02:00
D4rkst3randClaude Opus 5 3080f52e71 AutoMod sichtbar machen: Treffer ins Protokoll, Regeln ins Panel
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Discords eigener Filter arbeitet serverseitig und ohne Latenz — daran ist
nichts zu verbessern. Was fehlte, war die Sicht darauf: Treffer landeten nur
in Discords eigenem Kanal, und die Regeln pflegte man in einer ganz anderen
Oberflaeche als alles andere.

Jetzt spiegelt der Bot jeden Treffer als Embed ins Mod-Log — mit Nutzer,
Regel, Ausloeser, Massnahme, getroffenem Wort und gekuerztem Wortlaut, ohne
Erwaehnungen, die den Kanal anpingen. Im Panel steht unter Support jede Regel
mit Ausloeser, Massnahmen und der Zahl ausgenommener Rollen und Kanaele —
letzteres oft der Grund, warum eine Regel scheinbar nicht greift. Schalten
geht dort direkt.

Das Bearbeiten der Wortlisten bleibt bewusst in Discord; dort ist es gut
geloest, und ein zweiter Ort dafuer waere nur eine weitere Stelle, an der
etwas auseinanderlaeuft.

Dazu noetig: der Gateway-Intent AutoModerationExecution. Nicht privilegiert,
aber Discord schickt die Ereignisse nur an Bots mit "Server verwalten" —
fehlt das Recht, sagt das Panel es statt still leer zu bleiben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 06:53:33 +02:00
D4rkst3randClaude Fable 5 c8b1f604f0 Umfragen: /umfrage als native Discord-Abstimmung
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Bewusst Discords eigenes Poll-Objekt statt einer Nachbildung mit Knoepfen —
Oberflaeche, Auszaehlen und Ergebnisanzeige macht Discord dann selbst, auch
auf dem Handy. Wir bauen nur die Frage zusammen.

Bis zu vier Antworten (Discord erlaubt zehn, vier halten den Befehl
uebersichtlich), Laufzeit von einer Stunde bis einer Woche, Mehrfachauswahl
optional. Rechte: ManageMessages.

Zwei gleiche Antworten werden abgefangen. Discord stoert das nicht, aber
abstimmen kann darauf niemand sinnvoll.

Nicht zu verwechseln mit /wunsch: das sammelt Feature-Wuensche dauerhaft und
zeigt sie auf der Roadmap. /umfrage ist die schnelle Frage zwischendurch und
laeuft von selbst ab. Steht so auch als Kommentar im Befehl.

Geprueft gegen die installierte discord.js-Fassung (14.27) statt gegen die
Dokumentation: Feldnamen aus den Typdefinitionen gelesen, und die fertige
Nutzlast durch discord.js' eigene Serialisierung gejagt. Kommt als
poll_media / allow_multiselect / layout_type 1 raus, genau wie Discords API
es erwartet.

Dazu: 18 Befehle laden ohne Namenskollision, Befehl steht auf der
oeffentlichen Liste in beiden Sprachen, Zahl auf der Produktseite von 17
auf 18.

Nutzungsbedingungen als Entwurf unter docs/ — Text fuer den Seiten-Editor,
keine Rechtsberatung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 06:34:04 +02:00
D4rkst3randClaude Fable 5 52a735f83a SPA-Fallback beantwortet auch HEAD
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Discord prueft die im Portal hinterlegte Datenschutz- und AGB-Adresse mit
einer HEAD-Anfrage. Der Fallback behandelte nur GET, alles andere lief in
reply.code(404) — Discord bekam eine 404 und lehnte die Adresse mit
"URL ist nicht erlaubt" ab.

Im Cloudflare-Log nachgewiesen: 35.190.130.193 (Google LLC, dort laeuft
Discord), python-requests, Methode HEAD, Pfad /datenschutz, und ausdruecklich
"nicht eingedaemmt" — Cloudflare hat also durchgelassen, die 404 kam von uns.

Damit sind auch zwei Verdaechtigungen vom Tisch: weder Bot Fight Mode (war
ohnehin aus) noch AI Crawl Control. Letzteres hatte nur meine eigenen Abrufe
geblockt, was mich faelschlich glauben liess, der RSS-Feed sei fuer alle tot.
Er ist es nicht.

Geprueft gegen den echten Fastify-Aufbau, ueber eine echte TCP-Verbindung
statt app.inject(): HEAD /datenschutz und /impressum liefern 200, text/html,
korrekte Content-Length und keinen Rumpf. API-Pfade bleiben unangetastet
(HEAD /api/settings weiter 401).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 06:26:36 +02:00
D4rkst3randClaude Fable 5 137452da11 Linked Roles: Rollen, die an geprueften Werten haengen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Neues Modul, standardmaessig aus. Ein Mitglied klickt in Discord auf "Rolle
holen", landet auf /linked-roles, bestaetigt den OAuth-Dialog — und der Bot
schiebt ihm drei Kennzahlen zu, die Discord dann gegen die Rollen-Einstellung
prueft:

  level        Zahl >= eingestelltem Wert
  dabei_seit   Datum <= "vor X Tagen", also Mitglied seit mindestens X Tagen
  playtester   Wahrheitswert

Der Bot vergibt dabei keine Rollen. Er liefert nur die Zahlen; was daraus
wird, entscheidet die Rollen-Einstellung im Server. Die Daten kommen aus dem,
was ohnehin da ist: Level-Tabelle, Playtester-Liste, Discords Beitrittsdatum.

Eigener OAuth-Weg statt /auth/login, weil die Berechtigungen andere sind —
role_connections.write hat der normale Login nicht.

Die Tokens werden gespeichert, und das ist keine Bequemlichkeit: ohne
Refresh-Token bliebe jede Rolle auf dem Stand des Verknuepfungs-Tages stehen,
ein Level-Up kaeme nie an. Discords eigene Anleitung sagt dasselbe. Der
Umfang ist eng (nur identify + role_connections.write), beim Widerruf oder
Entkoppeln fliegt der Datensatz sofort raus. Steht so auch als Kommentar an
der Tabelle.

Abgeglichen wird alle 6 Stunden, einstellbar. Dafuer kann everyTuned jetzt
auch Stunden — und wirft bei einer unbekannten Einheit, statt den Abstand
still um Faktor 60 oder 3600 danebenzulegen.

Beim Bauen gefunden: moduleEnabled fehlte im Import von client.js. Der Bot
waere beim ClientReady mit ReferenceError gestorben.

Geprueft: alle drei Kennzahlen gegen Discords Formatgrenzen (Schluessel-Regex,
Laengen, gueltige Typ-Codes), Modul- und Stellwert-Register, die
Stunden-Umrechnung (6 h = 21600000 ms), Speichern und Loeschen einer
Verknuepfung, dazu 186 SQL-Abfragen und alle Routen auf Rechtepruefung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 05:57:57 +02:00
D4rkst3randClaude Fable 5 55e9ef57e2 Oeffentliche Statusseite nach Statuspage-Vorbild
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Neue Seite /status auf dem Hub: Gesamtlage als Banner, darunter die
freigegebenen Dienste mit 90-Tage-Balken, die Game-Server, und die
Stoerungs-Historie der letzten 90 Tage.

Die Adressliste ist von einer kommagetrennten Einstellung in eine Tabelle
gewandert. Jeder Dienst hat jetzt Namen, Gruppe und einen Schalter
"oeffentlich" — sonst stuende auf einer oeffentlichen Seite die nackte URL,
auch von internen Diensten. Die alte Liste wird beim ersten Start uebernommen,
bewusst als nicht oeffentlich: was dort auftaucht, soll eine Entscheidung sein
und nicht durch eine Migration passieren.

/api/status liefert nur freigegebene Dienste und dabei keine Adressen, nur
Namen. Auch die Stoerungs-Historie ist gefiltert — sonst verriete sie, was es
sonst noch gibt. Im Browser gegengeprueft: auf der ganzen Seite steht kein
einziges https://.

Stoerungen werden jetzt aufgezeichnet, nicht nur gemeldet: pro Adresse ein
offener Eintrag, bis es wieder laeuft. Beim Wiederkommen wird er geschlossen,
bevor die DM rausgeht — sonst bliebe er bei einem Sendefehler ewig offen.

Aufbewahrung von 7 auf 90 Tage. Der Balken kann jetzt zwei Aufloesungen:
168 Stunden fuers Panel ("was war letzte Nacht?") und 90 Tage fuer die
Statusseite ("wie zuverlaessig?"). Stuendlich ueber 90 Tage waeren ueber 2000
Striche und unlesbar.

Im Panel sind aus der reinen Anzeige Verwaltung geworden: anlegen, bearbeiten,
entfernen, freigeben — dazu die Stoerungsliste.

Beim Bauen gefunden: die Uebernahme der alten Liste stand mitten in db.js und
rief getSetting(), dessen prepared statement erst 300 Zeilen weiter unten
entsteht. Der Bot starb beim Start mit "Cannot access getSettingStmt before
initialization" — nachgewiesen, dann ans Dateiende verschoben.

Geprueft: 182 SQL-Abfragen gegen das Schema, alle schreibenden Routen bewacht,
Stoerungs-Logik durchgespielt (doppeltes Eroeffnen wird abgelehnt, Schliessen
ohne offene gibt null), und beide Seiten im Browser mit drei Diensten in allen
drei Zustaenden.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 05:45:50 +02:00
D4rkst3randClaude Fable 5 fd2d31f07f Watchdog: Embeds statt Textzeilen, dazu eine Uebersicht im Panel
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die DM-Alarme waren als einzige Meldung noch reiner Text — direkt neben dem
Raid-Alarm, der ein Embed ist. Jetzt beide im selben Look, mit Ausfalldauer
bzw. Fehlergrund und Antwortzeit als Felder.

Uebersicht im Panel (System, unter den Watchdog-URLs): je Adresse ein
Erreichbarkeits-Balken ueber 7 Tage, gruen/rot/grau wie bei den Game-Servern,
dazu Status, Antwortzeit und seit wann etwas weg ist.

Dafuer schreibt der Waechter jetzt mit — vorher lebte sein Zustand nur im
Speicher und war nach jedem Neustart weg. Neue Tabelle watchdog_history,
gleiche Bauart und gleiche 7-Tage-Aufbewahrung wie player_history, stundenweise
zusammengefasst statt roh ausgeliefert.

Wichtig dabei: aufgezeichnet wird jeder Check, nicht nur die Wechsel. Sonst
saehen ruhige Stunden im Balken aus wie Messluecken.

Der Balken ist aus Server.jsx in components/UptimeTrack.jsx gewandert, damit
oeffentliche Seite und Panel denselben benutzen. Er zeigt die Antwortzeit im
Tooltip nur, wo sie erhoben wird — bei Game-Servern gibt es keine.

Der Endpunkt /api/watchdog liegt hinter der Rechtepruefung, nicht oeffentlich:
in der Adressliste koennen interne Dienste stehen.

Zwei fehlende Importe in api.js gefunden und ergaenzt (moduleEnabled, tuning) —
der erste Aufruf der Route waere sonst mit ReferenceError gestorben.

Geprueft: alle 171 SQL-Abfragen gegen das Schema, die drei neuen inklusive.
Im Browser mit drei Faellen — laeuft, ist weg, noch nie geprueft: Punktfarbe,
Quote, Ausfallstunden und Messluecken stimmen, die ungepruefte Adresse zeigt
korrekt gar keinen Balken. Oeffentliche Server-Seite nach dem Umbau
gegengeprueft.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 05:28:22 +02:00
D4rkst3randClaude Fable 5 2d4afad3cb Level-Embeds aufgewertet, Server-Footer korrigiert
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Server-Monitor: Der Footer behauptete fest "alle 2 min", obwohl das Intervall
seit den einstellbaren Werten aus monitor_interval kommt. Wer im Panel etwas
anderes setzt, bekam eine falsche Angabe. Liest jetzt den echten Wert.

Level-Aufstieg: War als einziges im Community-Bereich reiner Text, waehrend
Geburtstage, Verlosungen und Bewerbungen laengst Embeds sind. Jetzt ueber
brandEmbed mit Avatar, Gesamt-XP und Rest bis zum naechsten Level. Der Text
bleibt die Vorlage aus dem Panel — nur der Rahmen ist neu.

Dazu zeigt das Embed die Belohnungs-Rolle: entweder die gerade vergebene oder
die naechste, auf die man hinspielt. grantRewards() gibt dafuer zurueck, was
wirklich dazukam.

Ist die Vorlage im Panel leer, wirft setDescription('') — der Aufstieg waere
still ausgefallen. Faellt jetzt auf eine schlichte Zeile zurueck.

/rank: Avatar als Thumbnail, Podest-Zeichen fuer die ersten drei, Rest-XP und
naechste Belohnung als eigene Felder. Das Feld erscheint nur, wenn es
Belohnungs-Rollen gibt — sonst stuende dort ein leeres Feld.

Nebenbei zwei veraltete ephemeral: true auf flags: MessageFlags.Ephemeral
umgestellt, wie es der Rest der Befehle schon macht.

Geprueft: alle vier Embeds wirklich gebaut und ausgegeben, discord.js
validiert dabei. Das Intervall im Footer zog den auf 7 gestellten Wert.
Die negative XP-Differenz im ersten Testlauf war ein inkonsistenter
Testdatensatz — mit 4200 XP ist man Level 9, nicht 7; ueber 0..60000 XP
durchgespielt wird die Differenz nie negativ.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 05:21:56 +02:00
D4rkst3randClaude Fable 5 8fa43a3e47 Code-Review: drei Fehler behoben
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Session-Cookie ohne secure-Flag (src/web/auth.js)
    Das Anmelde-Cookie war httpOnly, sameSite und signiert — aber ohne secure.
    Beim ersten Aufruf ueber http, bevor der Proxy auf https umleitet, geht es
    damit im Klartext mit. Auf einem fremden WLAN ist das die Sitzung.

    Das Flag laesst sich nicht aus request.protocol ableiten: hinter dem
    Reverse-Proxy kommt jede Anfrage als http an, trustProxy ist nicht gesetzt.
    Stattdessen aus der konfigurierten Adresse des Hosts, mit derselben
    Zuordnung, die der OAuth-Redirect schon benutzt. Lokal ueber
    http://localhost bleibt es aus, sonst kaeme man beim Entwickeln nicht rein.

    Geprueft gegen sechs Hosts: hub und bot ergeben secure, localhost nicht.
    Gilt auch fuer das OAuth-State- und das SSO-Ruecksprung-Cookie.

Unbehandelte Rejection beim Start (src/bot/client.js)
    registerCommands() faengt nichts ab, der Aufrufer im ClientReady-Handler
    auch nicht. Seit Node 15 beendet eine unbehandelte Rejection den Prozess —
    ein Rate-Limit von Discord beim Start haette den Bot also abgeschossen und
    mit restart: unless-stopped in eine Neustart-Schleife geschickt. Dabei
    stehen die zuvor registrierten Befehle bei Discord weiter, und alles
    andere koennte laufen. Jetzt wird der Fehler geloggt und der Bot bleibt an.

Number('') ist 0 (src/bot/anti-raid.js)
    Beim Aufheben der Raid-Sperre wird die gemerkte Verifizierungsstufe
    zurueckgesetzt. Fehlt die Notiz, ergab Number('') die 0 — und 0 ist eine
    gueltige Stufe, naemlich "keine". Der Server waere also auf offen gestellt
    worden statt so gelassen, wie er war. Jetzt wird nur zurueckgesetzt, was
    auch wirklich als Zahl notiert ist.

Ohne Befund geprueft: alle 168 SQL-Abfragen gegen das echte Schema inklusive
Migrationen, alle 109 Routen auf Rechtepruefung (53 von 54 schreibenden haben
eine, die 54. haengt am timing-safe verglichenen Secret), Farbwerte vor dem
Einbetten in ausgeliefertes CSS, das Escaping der OG-Meta-Tags, Timer-Cleanup
im Frontend und die uebrigen async-Handler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 04:48:52 +02:00
D4rkst3randClaude Fable 5 91397d8a44 Raid-Sperre von Hand setzen, aufheben oder nachsehen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der fehlende Befehl zum Raid-Schutz. Drei Unterbefehle:

  status  ist gerade gesperrt, und wie lange noch
  an      jetzt sperren — fuer die Welle, die man kommen sieht
  aus     sofort aufheben — fuer den Fehlalarm um drei Uhr nachts

Rechte: ManageGuild, weil die Sperre genau das anfasst — Verifizierung und
Invites. Antworten sind ephemeral.

Die Sperr-Massnahmen sind in dichtmachen() herausgezogen, damit automatisch
und von Hand denselben Weg nehmen und sich nicht auseinanderentwickeln. Die
Meldung sagt jeweils dazu, wodurch sie ausgeloest wurde und wer es war.

Ist der Raid-Schutz ausgeschaltet, verweist der Befehl aufs Panel statt
stillschweigend nichts zu tun.

Dazu: der Befehl steht auf der oeffentlichen Befehls-Seite, in beiden
Sprachen, und die Zahl auf der Produktseite geht von 16 auf 17.

Geprueft: 17 Befehle laden, keine doppelten Namen, Rechte-Bitmaske 32
(ManageGuild), alle neuen Exporte vorhanden, kein Sprachschluessel fehlt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 04:32:39 +02:00
D4rkst3randClaude Fable 5 aef708c405 Raid-Schutz: erkennt Beitritts-Wellen und sperrt voruebergehend
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Neues Modul anti_raid, standardmaessig AUS. Zaehlt Beitritte in einem
Zeitfenster; reisst es, geht der Server dicht: Verifizierung auf Hoch,
Einladungen gesperrt, DM an den Owner und Eintrag ins Protokoll.

Bewusst zurueckhaltend — es wird niemand gekickt oder gebannt. Bei einem
Fehlalarm, etwa nach einem Stream-Shoutout, waere ein Massenkick schlimmer
als der Raid. Beide Massnahmen sind reversibel und treffen niemanden, der
schon drin ist.

Der Sperrzustand liegt in den Einstellungen, nicht nur im Speicher: startet
der Bot mitten in einer Sperre neu, waere der Timer sonst weg und der Server
bliebe fuer immer dicht. Beim Start wird die Restzeit abgesessen oder, wenn
laengst abgelaufen, sofort aufgehoben.

Drei Werte im Panel: Schwelle (8), Fenster (20 s), Sperrdauer (15 min).

disableInvites() gibt es erst ab discord.js 14.4 und nur mit ManageGuild —
beides wird geprueft, statt im Ernstfall einen Fehler zu werfen.

Die Fenster-Logik ist als reine Funktion herausgezogen und mit neun Faellen
geprueft: langsame Beitritte loesen nicht aus, die Welle schon, der Rand des
Zeitfensters zaehlt nicht mehr mit, und nach Ruhe faengt das Fenster von vorn.

Offen: ein Befehl zum sofortigen Aufheben. Bis dahin laeuft die Sperre aus
oder man setzt die Verifizierung in Discord selbst zurueck.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 04:25:25 +02:00
D4rkst3randClaude Fable 5 fb7ee78548 Status-Seite wird ein Dashboard
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Statt sieben flacher Kacheln jetzt: Kopfzeile mit Bot-Identitaet und Laufzeit,
vier Kennzahlen mit Nebenzeile, Aktivitaetsverlauf und Modul-Zustand pro
Bereich. Die Bereichs-Zeilen sind klickbar und springen in den Modul-Ueberblick.

Fast alles war schon da und musste nur zusammengestellt werden. Neu am Server
sind zwei Zeitstempel in archiveStats(): lastDevlog und lastCommit. Reine
Zaehler sagen nicht, ob noch etwas passiert — "142 Devlogs, zuletzt vor zwei
Tagen" schon.

Der Mitglieder-Zuwachs kommt aus joins/leaves der Tagesdaten, der Verlauf
fuellt fehlende Tage mit 0 statt sie wegzulassen, sonst staucht er sich.

Beim Bauen gefunden: die Spalte in commits heisst committed_at, nicht ts. Da
better-sqlite3 beim Vorbereiten sofort auswertet, waere der Bot beim Start
abgestuerzt. Beide neuen Abfragen gegen das echte Schema geprueft.

Dazu ein Dev-Server-Eintrag in launch.json: der Build ist minifiziert, im
Fehlerfall steht dort nur "at bp". Mit dem Dev-Server kommt der Komponenten-
und Zeilenverweis — genau das hat den Weg zur Ursache abgekuerzt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 04:19:51 +02:00
D4rkst3randClaude Fable 5 1e2d295665 Erreichbarkeits-Balken auf der Server-Seite
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Ein Strich pro Stunde ueber 7 Tage, gruen/gelb/rot je nachdem, ob in der Stunde
alle, ein Teil oder keine Messung erreichbar war.

Die Daten lagen laengst da: player_history haelt 7 Tage vor, die API schnitt
aber auf 24 Stunden ab. Statt Rohdaten (ueber eine Woche rund 5000 Zeilen pro
Server) fasst uptimeBuckets() in SQL stundenweise zusammen — hoechstens 168
Zeilen.

- Stunden ohne Messung werden als Luecke gezeichnet, nicht als "online". Sonst
  saehe eine Bot-Auszeit aus wie ein einwandfrei laufender Server.
- Die alte 24h-Uptime-Zahl neben dem Peak ist raus: zweimal dieselbe Groesse
  ueber verschiedene Zeitraeume verwirrt nur.
- Tooltip zeigt Ortszeit. Die Eimer werden in UTC abgeglichen (SQLite rechnet
  so), angezeigt wird lokal — sonst stuende dort eine Stunde, die niemand
  wiedererkennt.

Zwei Fehler beim Bauen gefunden und behoben:
- .srv-card ist ein Grid-Item mit min-width:auto und wuchs durch die 168
  Striche auf 724 statt 335 px, die Karte sprengte auf dem Handy das Raster.
- "Uhr" stand hart deutsch im Tooltip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 04:09:19 +02:00
D4rkst3randClaude Fable 5 c0a56c1eb5 Logo auf der Willkommens-Karte und in geteilten Links
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Zwei Stellen, an denen die Marke bisher fehlte.

Willkommens-Karte: Das Wasserzeichen war der Markenname als Text mit 5 %
Deckkraft — jetzt der Katzenkopf. Die Datei wird einmal beim Start gelesen
statt bei jedem Beitritt, und fehlt sie, rendert die Karte wie bisher ohne
Wasserzeichen. Der Schalter heisst entsprechend "Logo im Hintergrund".

Link-Vorschau: og:image war nur gesetzt, wenn ein Devlog einen Screenshot
hatte. Startseite, Roadmap, Funktionen und jedes Devlog ohne Bild kamen in
Discord als graue Textzeile. Jetzt faellt alles ohne eigenes Bild auf
og-default.png zurueck (1200x630, Lockup auf Schwarz mit Akzentstreifen),
und twitter:card ist durchgehend summary_large_image.

Die Laufzeit-Datei liegt unter src/bot/assets/, nicht in docs/ — das
Dockerfile kopiert nur src/ und frontend/dist/.

Geprueft: librsvg zeichnet das eingebettete <image> wirklich (42726 Pixel
unterscheiden sich gegen eine Fassung ohne), das OG-Bild landet im dist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 03:55:28 +02:00
D4rkst3randClaude Fable 5 570e45fe60 Team-Bereich aufgehuebscht: Avatare, Rechte-Chips, Protokoll mit relativer Zeit
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Team-Reiter war funktional, aber roh: Rechte standen als "content,community"
da, es gab keine Gesichter, und das Protokoll war eine Wand aus Zeitstempeln.

- /api/webadmins und /api/auditlog liefern jetzt Avatar und Anzeigename aus dem
  Member-Cache — kostet keinen zusaetzlichen Discord-Aufruf
- Owner-Zeile mit eigenem Chip "Alle Bereiche", damit klar ist, wer alles darf
- Bereiche als lesbare Chips statt roher IDs; die Auswahl beim Anlegen sind
  klickbare Karten mit Beschreibung
- Protokoll zeigt relative Zeit ("vor 37 Min"), erst 12 Zeilen, Rest auf Klick
- Toenungen ueber color-mix aus --neon: stimmt im Hub (gold) wie auf der
  Bot-Seite (orange)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 22:25:51 +02:00
D4rkst3randClaude Fable 5 c026b2a603 Wichtige Funktionen direkt in der Seitenleiste
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Nach dem Umbau lagen alle Funktionen hinter dem Raster — für die vier, fünf,
die man dauernd anfasst, ist das ein Umweg zu viel.

Oben in der Seitenleiste steht jetzt „Meine Funktionen": ein Klick öffnet
die Seite direkt. Welche dort stehen, entscheidet der Stern auf der
Modul-Karte oder auf der Seite selbst.

Bewusst nicht alle 35: Deckel bei acht, sonst ist der Gewinn gegenüber dem
Raster wieder weg. Der Standard ist eine Vermutung (Devlogs, Willkommen,
Tickets, Starboard, Galerie) und ausdrücklich zum Ändern gedacht.

Die Auswahl liegt als Einstellung in der Datenbank, nicht im Browser — sie
gilt damit auf jedem Gerät und fürs ganze Team. Eine geleerte Auswahl wird
als solche gespeichert, sonst käme beim nächsten Laden der Standard zurück.

Der Stern ist ungeheftet nur angedeutet und wird erst beim Überfahren
deutlich — 35 Karten mit vollen Sternen sähen aus wie ein Sternenhimmel.

Nebenbei: bei geöffneter Modul-Seite war sowohl „Module" als auch die
Funktion in der Leiste hervorgehoben. Jetzt nur noch die Funktion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 22:08:42 +02:00
D4rkst3randClaude Fable 5 de4e812896 Werte für mehrere Funktionen, und Anlegen frischt die Auswahl auf
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Zwei Nachzügler zu den Modul-Seiten.

Die Thread-Archivdauer gilt für Tickets und Modmail, hing aber nur an
Tickets — auf der Modmail-Seite fehlte sie also. `module` darf jetzt eine
Liste sein; nach außen kommt immer eine, damit niemand zwischen einem und
mehreren unterscheiden muss.

Und beim Anlegen aus dem Panel wurde nur die Einstellung gesetzt, nicht die
Kanalliste nachgeladen. Der frische Kanal stand damit zwar in der
Einstellung, fehlte aber in der Auswahl — das Feld sah leer aus, obwohl es
gesetzt war. Aufgefallen im Browser-Test: „Einsatzbereit" stand da, die
Auswahl daneben war leer. Jetzt holt reloadSettings Kanäle, Rollen und
Einstellungen frisch; das gilt für den Einzelknopf, den Assistenten und die
Modul-Seiten gleichermaßen.

Nachgeprüft: Kanal über die Status-Liste anlegen, dann ohne Neuladen nach
Feeds wechseln — dort steht #dev-log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 22:00:36 +02:00
D4rkst3randClaude Fable 5 c88926aba0 Modul-Seiten: alles zu einer Funktion an einem Ort
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Bisher war eine Funktion über vier Bereiche verteilt: Schalter unter Module,
Kanal unter Support, Texte unter Texte, Werte unter Werte. Wer die
Begrüßung ändern wollte, brauchte drei Bereiche.

Jetzt öffnet ein Klick auf die Karte die Seite dieser Funktion — mit
Schalter, ihren Kanälen und Feldern, ihren Texten, ihren Werten und dem Weg
zum passenden Werkzeug. Für „Willkommen" also Kanal, Karte, Farben,
Vorschau und beide Texte untereinander.

Bewusst keine 35 Einträge in der Seitenleiste: das Modul-Raster ist die
Navigation, wie bei MEE6. Die Seitenleiste bleibt so lang wie vorher.

Die Seite wird aus den Registern gebaut, nicht von Hand: modules.js kennt
die Felder (neu: `fields` für alles jenseits der Pflichtkanäle, `tool` für
den Verweis auf einen Editor), templates.js hatte die Zuordnung schon,
tuning.js hat sie bekommen. Eine neue Funktion bekommt ihre Seite dadurch
geschenkt — es gibt keinen Ort, an dem man sie vergessen könnte.

Was ein eigenes Werkzeug hat — Rollen-Menüs, Bewerbungen, Composer,
Server-Monitor —, bleibt in seinem Bereich; die Modul-Seite verlinkt nur
dorthin. Dort ist mehr Platz, und die Editoren lassen sich nicht sinnvoll
aus Registern erzeugen.

Texte und Werte bleiben zusätzlich als flache Übersicht: wer alle Texte am
Stück durchgehen will, soll dafür nicht 27 Seiten öffnen müssen.

Beim Umbau ist mir ein Skript in die Quelldatei gelaufen — der Schreibvorgang
scheiterte an einem Emoji und hinterließ modules.js leer. Wiederhergestellt
aus dem letzten Commit; die Skripte schreiben jetzt erst in eine
Zwischendatei und benennen danach um.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 21:54:31 +02:00
D4rkst3randClaude Fable 5 bea35234c6 „Einrichten" zeigt jetzt, was fehlt — und Env-Werte gelten als gesetzt
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Zwei Fehler, die zusammen für Ratlosigkeit gesorgt haben.

In der Liste stand „Fehlermeldungen — Gitea-Token", obwohl das Token über
GITEA_API_TOKEN längst gesetzt war und /bug lief. Grund: die
Voraussetzungs-Prüfung las die Einstellung roh statt den wirksamen Wert,
sah also nur in die Datenbank und nicht in die Umgebung. Betraf außerdem
Devlog-Kanal, Commit-Kanal und Guild-ID. runtime-settings hat dafür jetzt
effectiveSetting(), das beides zusammenführt.

Und wer dann auf „Einrichten" drückte, landete im System-Bereich — dort
gibt es gar kein Token-Feld, das steht bei Brand. Das Modul zeigte auf den
falschen Bereich.

Jetzt springt „Einrichten" auf das konkrete Feld und hebt es kurz hervor,
statt nur den Bereich zu wechseln. Der Bereich kommt dabei aus dem
Einstellungs-Register, das ohnehin weiß, wo jedes Feld liegt — damit kann
die Zuordnung am Modul nicht mehr auseinanderlaufen. Dieselbe Logik hängt
an „Einstellungen →" auf den Modul-Karten.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 21:43:34 +02:00
D4rkst3randClaude Fable 5 01fadadcd8 Grundgerüst-Assistent: alle fehlenden Kanäle auf einmal
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Einzelknopf war der Anfang; wer frisch startet, klickt sich damit
sechzehnmal durch. Der Assistent macht das in einem Durchlauf.

Erst eine Vorschau, bevor irgendetwas passiert: was neu entsteht, was schon
existiert und nur verknüpft wird, welche Kanäle privat werden und in welche
Kategorie alles kommt. Die Kategorie heißt wie die Marke und wird
wiederverwendet, wenn es sie schon gibt.

Der Plan ist nach Einstellung entdoppelt — Devlogs und Wochen-Rückblick
brauchen denselben Kanal, der soll nicht zweimal auftauchen. Rollen kommen
ohne Kategorie, die gibt es dort nicht.

Zwischen zwei Anlagen liegen 350 ms. Discord verträgt ein Dutzend
Kanal-Erstellungen am Stück schlecht, und ein Rate-Limit mitten im Lauf
wäre die unangenehmste Art zu scheitern.

Scheitert ein Schritt, laufen die übrigen weiter und das Ergebnis zeigt pro
Zeile, was passiert ist. Vorher hätte ein einzelner Fehler den Rest
verschluckt.

Im Test: 19 offene Punkte, Vorschau zeigt 16, danach bleiben 2 — die beiden
ohne Vorlage (Gitea-Token und Watchdog-Adressen), die sich nicht anlegen
lassen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 21:17:36 +02:00
D4rkst3randClaude Fable 5 684573b88c Fehlende Kanäle und Rollen aus dem Panel anlegen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Die Status-Seite listet Module, denen ein Kanal oder eine Rolle fehlt.
Bisher hieß das: rüber zu Discord, Kanal anlegen, Rechte setzen, zurück ins
Panel, auswählen, speichern. Jetzt steht daneben ein Knopf, der genau das
in einem Schritt macht — inklusive Eintragen der Einstellung.

Die Vorlagen hängen bei den Modulen (modules.js): Name, Thema und ob der
Kanal privat sein soll. Private Kanäle bekommen @everyone entzogen und
Bot plus alle Rollen mit Moderationsrecht eingetragen — sonst legt man
einen Modmail-Kanal an, den das Team nicht sieht.

Gibt es Kanal oder Rolle schon (bei Rollen unabhängig von Groß- und
Kleinschreibung), werden sie verknüpft statt doppelt angelegt. Beim
Temp-Voice-Hub entsteht ein Sprachkanal statt eines Textkanals.

Fehlen dem Bot die Rechte, steht das im Panel im Klartext („Dem Bot fehlt
das Recht Kanäle verwalten") statt einer Discord-Fehlernummer.

Bewusst nur Anlegen: kein Löschen, kein Umbenennen. Ein Fehlklick beim
Anlegen kostet einen überflüssigen Kanal, einer beim Löschen dessen
Verlauf. Umbenennen wäre zusätzlich eine Falle, weil Discord es auf zwei
Änderungen pro zehn Minuten und Kanal begrenzt.

moduleMissing liefert dafür Objekte statt Beschriftungen, damit das Panel
weiß, was sich anlegen lässt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 18:29:54 +02:00
D4rkst3randClaude Fable 5 1c3fda8d64 Config: Speichern-Leiste und durchsuchbare Kanal-Auswahl
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Der Speichern-Knopf stand am Seitenende und war bei den zweispaltigen
Bereichen oft gar nicht zu sehen. Vor allem gab es keinen Hinweis, dass
etwas offen ist — wer einen Kanal umstellte und neu lud, verlor die
Änderung kommentarlos.

Jetzt erscheint unten eine Leiste, sobald sich etwas vom gespeicherten
Stand unterscheidet: Anzahl der Änderungen, Verwerfen, Speichern. Beim
Verlassen mit offenen Änderungen fragt der Browser nach. Der Vergleich
zieht Bot-Name und -Beschreibung mit ein, die außerhalb des Formulars
gepflegt werden.

Die 27 Kanal- und Rollen-Auswahlen sind keine nativen Dropdowns mehr,
sondern dieselbe durchsuchbare Liste wie bei den gamedig-IDs: tippen
filtert, Enter nimmt den ersten Treffer. Bei zwölf Kanälen merkt man das
schon, bei fünfzig ist es der Unterschied.

Beim Testen zwei echte Fehler gefunden, beide älter als diese Änderung:

Nicht gesetzte Kanäle kommen als null aus der API. String(null) ist
"null" — also eine Kanal-ID, die es nie gibt. Auf einer frischen
Installation scheiterte damit das Speichern *jeder* Einstellung, solange
Devlog- oder Commit-Kanal leer waren. Sichtbar wurde das erst, weil die
Leiste jetzt überall auftaucht statt nur auf vier Bereichen.

Außerdem wurde jeder Kanal im Formular geprüft, auch die unveränderten.
Ein inzwischen gelöschter Kanal hätte damit das Speichern aller anderen
Einstellungen dauerhaft blockiert. Geprüft wird jetzt nur, was sich
gegenüber dem ausgelieferten Stand wirklich ändert.

Nachtrag zum Selbsttest: der erste Versuch, die Auswahlen per Regex zu
ersetzen, lief mit DOTALL und griff dadurch über <select>-Grenzen hinweg —
zwei fremde Dropdowns wurden zerlegt. Der Build hat es gemeldet, die Datei
wurde zurückgesetzt und der Ersatz zeilenweise wiederholt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 18:11:31 +02:00
D4rkst3randClaude Fable 5 2e58268f8d Bestenliste, Devlog-Filter, Galerie und leere Bereiche
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Vier Stellen, die beim Durchsehen der Seiten aufgefallen sind.

Die Bestenliste zeigte nur Namen — und für Platz 1 bis 3 denselben Pokal
ohne Nummer, wodurch die Reihenfolge nur an der Position erkennbar war.
Jetzt: Rangnummer, Avatar und die ersten drei Plätze farblich abgesetzt.
Die Avatare kommen aus dem Member-Cache, der durch den GuildMembers-Intent
ohnehin gefüllt ist — das kostet keinen einzigen Discord-Aufruf. Wer nicht
im Cache liegt, bekommt seinen Anfangsbuchstaben statt eines leeren Kreises.

Die Projekt-Chips im Devlog-Archiv waren bloß Beschriftung. Jetzt filtern
Chips mit Anzahl nach Projekt (ECOGAME / D4RKBOT) — man will meist nur
eins von beiden lesen. Serverseitig über LIKE auf die erste Zeile, wo das
Projekt ohnehin steht; das spart eine Spalte und eine Migration. Eine
aktive Suche schlägt den Filter, sonst müsste die Volltextsuche beides
verbinden, ohne dass jemand danach fragt.

Galerie: Vorschaubilder von 240 auf 340 Pixel, und Name plus Datum stehen
dauerhaft am Bild statt nur beim Überfahren — am Handy gab es kein Hover
und damit nie eine Zuordnung.

Events, Changelog, Galerie und Level endeten bei wenig Inhalt mit einem
einzelnen Satz in einer sonst leeren Seite; das sah nach Fehler aus statt
nach „noch nichts da". Jetzt steht dort ein Block mit Symbol, Erklärung
was hier erscheinen wird, und Verweisen auf belebte Bereiche.

Dazu ein Sprung-nach-oben-Knopf im Devlog-Archiv und im Changelog, die
beide sehr lang werden.

Beim Testen gefunden: ESCAPE '\' in einem JS-String kommt als leeres
Zeichen in SQLite an ("ESCAPE expression must be a single character") —
der Backslash müsste doppelt entwertet werden. Jetzt ist das Escape-Zeichen
ein Ausrufezeichen, das die Falle gar nicht erst aufmacht.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 17:44:57 +02:00