/moodle — Moodle

Hinweis: Rahmen: 00-rahmen.md. Code: packages/plugins/src/moodle/ · Nachweis npm run verify:moodle.

Wofür

Die Pforte: wo Post von außen ankommt. Jede Ansicht ist ein Aufruf nach Moodle. Das ist langsamer als eine Kopie und dafür nie falsch — eine zwischengespeicherte Frist wäre eine zweite Wahrheit über einen Abgabetermin, und die falsche wäre die, die man sieht.

Der Schirm schreibt nichts. Eine Abgabe hochladen oder einen Forenbeitrag verfassen sind Handlungen mit Folgen an einer Hochschule; die sollen dort passieren, wo man sieht, was man tut. Jede Zeile, die in Moodle ein Gegenstück hat, verweist dorthin.

Weg und Rahmen

Route/moodle, Abschnitt über ?a=kurse|fristen|abgaben
MerkmalMOODLE — abschaltbar, seit dem 03.10.2026 für jedes Konto vorgesehen (DEFAULT_FEATURES, N480); bis dahin nur mit der Fähigkeit feature.moodle, die Wanderung 215 entfernt. Braucht einen hinterlegten Zugang
ZimmerPforte · Lautstärke 2 · #9aa5b8

Was man sieht

Drei Abschnitte: Kurse · Termine · Abgaben. Der zweite heißt am Reiter „Termine" und in der Adresse fristen — ?a=fristen, nicht ?a=termine. Die Reiterbeschriftung ist Oberfläche und darf sich ändern; der Schlüssel steht in drei Rückfallschlüsseln und in jedem Lesezeichen, den jemand gesetzt hat.

Der Kurs ist der Einstieg, nicht die Liste: wer auf einen Kurs klickt, bekommt dessen Abschnitte und Ankündigungen — nicht eine zweite Liste zum Weiterklicken.

Die drei Reiter tragen seit dem 23.09.2026 ein Zeichen (N134, Style-Guide §7.3 — eine Reihe trägt es oder keins): Kurse die Kappe des Studiums (kappe), Termine die Uhr der Fälligkeit (faellig), Abgaben das Blatt der Prüfung (pruefung, §7.1: Prüfungen und Abgaben tragen dieses Zeichen — erledigt heißt bereichsübergreifend nur noch „fertig"). Das Wort bleibt stehen, das Zeichen steht still links davon (aria-hidden) und trägt die Farbe; 16 px, wie jeder Reiter im Haus.

WasAnzeige
Verbindungsstandoben: ob der Zugang steht und welcher Server antwortet
KurseName, Kürzel, verknüpftes Denotato-Modul — ohne Modul dazu Kalenderkategorie und Semester (N149, ab 23.09.2026)
KursinhaltAbschnitte mit Dateien (Größe in Bytes unter 1 KB, sonst KB, ab 1 MB in MB mit einer Nachkommastelle) · daneben Datum und Uhrzeit der letzten Änderung
AnkündigungenForenbeiträge des Kurses
Termine/Fristenvier Stufen statt zwei (seit 21.09.2026, Style-Guide §7.6) — seit dem 22.09.2026 mit Schiene, Rand und Grund am Block; seit dem 23.09.2026 hinter dem Kurs, wohin die Frist gehört: „Modul …", ohne Modul „Kategorie …" und das Semester (N149)
AbgabenStatus je Aufgabe — mit derselben Stufe wie die Termine. Darüber der Verweis „Abgehakt wird unter To-Dos › Abgabe“ auf /tasks?art=submission: Die Liste zeigt, was Moodle meldet, und hat keinen eigenen Haken (seit dem 15.10.2026, KN-261015-abgaben-ort)
Nächste Frist am Kursan jeder Kurszeile, aus denselben 30 Tagen wie der Reiter „Termine"
Neu seit dem letzten Besucheine Marke „neu" an Ankündigungen und Dateien, darüber ein Satz

Texte für das Modell (KI-261007-gemeinsamer-wandler, 07.10.2026). Abschnittsbeschreibungen, Aufgaben-, Foren- und Beitragstexte gibt der Server als Markdown mit GFM-Tabellen aus (gemeinsamer Wandler in @shared/utils/htmlMarkdown), Adressen ohne Merkmal (D28). Der Schirm zeigt Beitragstexte weiter als Klartext: nurText liest das Markdown zurück. Beim Kursinhalt sind lange Texte in der Übersicht auf 400 Zeichen gekürzt und nennen den Weg zum ganzen; mit sectionId, sectionNumber oder cmId kommt nur dieser Teil, ungekürzt. Der Schirm zeigt Beschreibungen nicht und bleibt gleich.

Kurse ohne Modul (N149, BE47, 23.09.2026). Bis dahin konnte ein Kurs nur an einem Modul hängen — ein Vorkurs oder der Kurs für die Erstsemester gehört aber zu keinem, und seine Fristen hatten damit keinen Ort. Seitdem stehen an der Kurskarte neben „kein Modul" zwei weitere Auswahlfelder: Kalenderkategorie („keine Kategorie") und Semester („kein Semester"). Wann sie dastehen:

KursKategorie und Semester an der Karte
ohne Modulimmer
mit Modul, nichts eingetragennicht — das Modul bringt sein Semester mit, und zwei leere Felder mehr an jeder verknüpften Karte wären Lärm
mit Modul, Kategorie oder Semester schon eingetragenja — sonst hinge der Wert unsichtbar am Kurs und ließe sich nicht mehr leeren

Gibt es noch keine Kalenderkategorie oder kein Semester, ist das Feld gesperrt, und sein Hinweis sagt, warum („Noch keine Kalenderkategorie angelegt") — ein Feld, das nur „keine" anbietet, sähe aus wie eine Wahl, die es nicht gibt. Im Reiter „Termine" steht hinter dem Kurs, wohin die Frist gehört; die Suche findet sie auch darüber. Gelesen wird das aus derselben Übersicht wie die Kurskarte, damit eine eben gewählte Kategorie sofort an den Terminen steht — die Terminliste selbst wird dafür nicht neu geholt. Den Kalender erreicht die Kategorie noch nicht; wie sie dorthin kommt, ist offen („Was fehlt", Punkt 28).

Die vier Stufen einer Frist (21.09.2026). Bis dahin kannte der Schirm genau eine Frage — fällt es in den nächsten drei Tagen an? —, und eine Abgabe, deren Termin im März war, sah aus wie eine im Dezember: beide grau. Gerade die Abgabenliste ist voll davon, denn Moodle gibt dort alle Aufgaben eines Kurses heraus, auch die längst vergangenen.

StufeWie es aussieht
überfälligrot und halbfett, daneben „überfällig seit 2 Tagen"
in 24 Stundengelb und halbfett
in drei Tagengelb, Gewicht 500
weiter weg / ohne DatumNebentext, grau

Gerechnet wird in moodle/shared/faelligkeit.ts — rein, mit der Gegenwart als Parameter, damit sich die Grenzen durchspielen lassen.

Und seit dem 22.09.2026 kippt die ganze Zeile, nicht ein Wort darin (Style-Guide §7.6). Bis dahin färbte sich die Zeitangabe am rechten Rand und sonst nichts: Eine Abgabe, die in zwölf Stunden fällig ist, unterschied sich von einer im Dezember um sechs gelbe Zeichen. Wer eine Liste überfliegt, liest links.

StufeSchiene linksRandGrund
überfällig (bis 30 Tage)6 px rotrotdangerSoft
in 24 Stunden6 px gelbgelb—
in drei Tagen5 px gelb——
weiter weg / ohne Datum———
länger als 30 Tage vorbei———

Seit dem 23.09.2026 steht zusätzlich ein Zeichen vor dem Datum — aber nur bei zwei der vier Stufen (N134, Style-Guide §7.6: „Zeichen nur für vorbei und heute, nie an jedem Datum"). Überfällig bekommt die überfällige Uhr (ueberfaellig), heute fällig die fällige Uhr (faellig); „in drei Tagen", „weiter weg" und „ohne Datum" bleiben ohne Zeichen — sonst zöge sich eine Uhr durch die ganze Liste und wäre kein Hinweis mehr. Das Zeichen trägt currentColor: dieselbe Zeile, die schon rot oder gelb ist, bekommt kein zweites, eigenes Rot. Farbe stand hier bis dahin allein (eine Abweichung von Style-Guide §7.1); jetzt trägt auch ein Zeichen die Dringlichkeit.

Zwei Dinge daran sind Absicht:

Die letzte Zeile. mod_assign_get_assignments gibt alle Aufgaben eines Kurses heraus, auch die aus dem ersten Semesterdrittel. Jede davon mit rotem Rand und rotem Grund zu malen ergäbe eine Wand aus Rot, in der die Abgabe von gestern untergeht — die Hervorhebung höbe nichts mehr hervor. Nach dreißig Tagen trägt der Block deshalb nichts mehr; die Zeitangabe bleibt rot und sagt weiter „überfällig seit 197 Tagen". Die Auskunft geht nicht verloren, nur der Alarm. Gerechnet wird das in blockstufe(), getrennt von faelligkeit(), weil es eine andere Frage ist: „wann ist das fällig" gegen „muss ich das jetzt ansehen".

Die Farbe ist eine Abweichung, und sie ist entschieden. §7.6 schreibt für die beiden mittleren Stufen die Bereichsfarbe vor. Die ist hier #9aa5b8, ein Grau, das sich neben dem Nebentext nicht als Dringlichkeit liest — eine Schiene darin sähe aus wie ein Trennstrich. Genommen wird status.warning. Die Schiene liegt außerdem innen (inset box-shadow) und nicht als breiterer Rand: Ein border-left: 6px verschöbe den Text, sobald eine Frist näher rückt, und eine Liste, in der die Zeilen beim Neuladen wandern, liest sich unruhig.

Was seit dem letzten Besuch dazukam (21.09.2026). Moodle führt einen Lesezustand je Forenbeitrag, gibt ihn aber über keinen Web-Service heraus, den Denotato fragt — und ihn dort zu setzen wäre ein Schreibzugriff auf die Hochschule, den dieser Bereich mit Absicht nicht hat. Übrig bleibt der eigene Zeitpunkt: wann habe ich diesen Kurs zuletzt aufgemacht. Er steht im Browser (localStorage, Schlüssel denotato.moodle.besuche), wird beim Öffnen des Kurses fortgeschrieben, und daraus fällt beides ab — die Marke „neu" an der einzelnen Ankündigung und an der einzelnen Datei, und der Satz darüber: „2 neue Ankündigungen und 1 geänderte Datei seit Mo., 14.09., 21:47." Die Marke selbst trägt seit dem 23.09.2026 zusätzlich das Zeichen neu vor dem Wort (N134) — der grüne Hintergrund blieb, das Wort ist seitdem neutral (Style-Guide §7.3, Regel F) und das Zeichen trägt die Farbe.

Gezählt wird der ganze Kurs, nicht der gefilterte (22.09.2026). Bis dahin zählte der Satz nur, was die Suche übrig ließ — wer nach „Blatt 7" suchte, las „1 neue Ankündigung seit …" und hielt das für die Auskunft über den Kurs. Der Satz steht über dem Kurs und nicht über der Trefferliste. Die Marken an den einzelnen Zeilen zeigen weiterhin nur, was dasteht; sie beantworten die andere Frage.

Der erste Besuch markiert nichts. Wer einen Kurs zum ersten Mal öffnet, sähe sonst jede Ankündigung des Semesters als „neu" — eine Aussage, die stimmt und nichts sagt. Stattdessen steht dort „Erster Besuch in diesem Browser — ab jetzt merkt Denotato sich, was dazukommt."

Ohne Zugang sagt der Schirm das, statt ein leeres Semester zu melden (seit 21.09.2026). Bis dahin stand in allen drei Abschnitten ein Satz über Moodle — „Keine eingeschriebenen Kurse.", „In den nächsten 30 Tagen steht nichts an.", „Keine Abgaben in den eingeschriebenen Kursen." Alle drei stimmten nicht: Denotato hatte Moodle nie gefragt. Ist kein Zugang hinterlegt, bleibt der Verbindungsstand leer und alle drei Listen kommen leer zurück — für jemanden, der Denotato neu hat, sah das aus wie ein Studium ohne Kurse.

Jetzt unterscheidet der Leerzustand drei Gründe:

GrundWas dasteht
kein Zugang„Denotato kennt deinen Moodle-Zugang noch nicht. Es braucht den Web-Service-Schlüssel deiner Hochschule." — dazu der Hauptknopf „Moodle verbinden" (seit RL02, 28.09.2026; vorher ein kleiner Nebenknopf „Zu den Zugangsdaten" unter vier Zeilen Erklärung). Suchfeld steht dann keines (N318)
gefragt, nichts dawas der Abschnitt zeigen würde und wer ihn füllt (einschreiben, hochladen: in Moodle, nicht hier)
Suche filtert alles weg„Kein Kurs / Termin / keine Abgabe passt zur Suche."

Ohne Zugang stehen seit dem 15.10.2026 keine Reiter da (KN-261015-abgaben-ort). Alle drei zeigten denselben Satz und denselben Knopf; drei Reiter mit einem Inhalt sahen aus wie drei leere Listen. Es bleibt der eine Leerzustand mit „Moodle verbinden".

Erkannt wird „kein Zugang" daran, dass MOODLE_STATUS nichts zurückgibt — dieselbe Angabe, aus der oben der Verbindungsstand kommt.

„Kein Zugang" malt seit dem 21.09.2026 keinen roten Streifen mehr. Bis dahin stand über dem Leerzustand zusätzlich eine Fehlerzeile: „Der Moodle-Zugang fehlt auf dem Server." Zwei Dinge daran waren falsch. Es ist keine Störung, sondern der Zustand vor dem ersten Eintragen — und seit BE08 gehört der Zugang dem Konto und nicht dem Server, der Satz beschrieb also einen Ort, an dem seitdem nichts mehr steht. Der Leerzustand sagt dasselbe in ganzen Sätzen und mit einem Knopf daneben; alles andere wird weiterhin als Fehlerzeile gezeigt, mit Moodles eigener Kennung dahinter, wenn es eine gibt (B04) — „Moodle war nicht erreichbar (Kennung: accessexception)" ist der Unterschied zwischen „warte" und „hol dir ein neues Merkmal".

Wenn Moodle nicht antwortet

Es bleibt nichts still. Bis zum 21.09.2026 schwiegen vier Wege: die Modulliste für das Auswahlfeld, die Ankündigungen eines geöffneten Kurses samt ihren Beiträgen, und — der schlimmste — die Fristen hinter der Kursliste. Scheiterte dort etwas, verschwand die Angabe einfach: Das Auswahlfeld zeigte nur „kein Modul", der Kurs sah aus wie einer ohne Ankündigungen, und an jeder Kurszeile fehlte die nächste Frist, als hätte der Kurs keine. Ein leerer Ankündigungsblock ist ein gültiger Zustand — genau deswegen sah der Ausfall aus wie ein Ergebnis. Alle vier melden jetzt.

Was passiertWas der Leser sieht
Moodle antwortet gar nicht (Netz, HTTP ≠ 200, Zeitgrenze)Fehlerzeile „Moodle war unter … nicht erreichbar." · die Listen und der offene Kurs bleiben auf dem letzten Stand stehen · die Standmarke sagt „Moodle antwortet gerade nicht — Stand von Mo., 14.09., 21:47"
Moodle lehnt das Merkmal ab (invalidtoken, accessexception, Wartung …)Fehlerzeile mit dem Hinweis, dass ein neues Merkmal fällig ist — und die tägliche Gegenprobe (BE23) meldet es getrennt davon
Die Hochschule gibt den Web-Service nicht frei (enablewsdescription, servicenotavailable) (VA06, ab 30.09.2026)Fehlerzeile „Dein Moodle gibt seinen Web-Service nicht frei — ein neuer Sicherheitsschlüssel hilft nicht, freischalten kann ihn nur deine Hochschule“ mit Moodles Kennung in Klammern · Code moodle_service_disabled_error (502); vorher zählte der Fall als abgelehntes Merkmal
Moodle lehnt den Aufruf ab (alles andere)Fehlerzeile plus Moodles errorcode in Klammern — ein Fehler auf Denotatos Seite, kein Grund, ein Merkmal zu erneuern
kein Zugang hinterlegtkeine Fehlerzeile, sondern der Leerzustand mit „Moodle verbinden" · auch keine rote Meldung und keine automatische Rückmeldung: Der Server antwortet mit 409 moodle_not_configured, einem Zustand (F68, ab 24.09.2026) — vorher mit 500, und jeder Besuch zählte als Serverfehler
Der Host ist nicht freigegeben (VA14, N480, ab 03.10.2026)Fehlerzeile mit dem Satz „Denotato ruft … erst ab, wenn ein Admin diesen Host in der Verwaltung freigegeben hat — bis dahin schickt Denotato dein Merkmal nicht dorthin.“ · gefragt wird bei jedem Abruf und bei jeder Datei (hostIstFrei), eine Sperre in /admin gilt also sofort · Code moodle_forbidden_error (502); die Moodle-Hosts der Hochschulen sind freigegeben (N488)
Denotato fragt gar nicht erst (Adresse nicht https:// oder nicht öffentlich, Moodle leitet weiter, Datei liegt auf einem anderen Rechner) (D28, ab 23.09.2026)Fehlerzeile „Denotato hat die Moodle-Anfrage abgebrochen — das Ziel ist nicht erlaubt.", dahinter als zweiter Satz der Grund, der Rechner und Schritt nennt — etwa „Denotato spricht diese Moodle-Adresse nicht an: … Sie steht unter Einstellungen → Zugangsdaten." · kein Merkmal hat Denotato verlassen · Code moodle_forbidden_error (502), seit dem Abend des 23.09.2026; vorher hieß es „Moodle war nicht erreichbar" mit dem Satz als „(Kennung: …)"

Auch der geöffnete Kurs fällt zurück (22.09.2026, vorher Punkt 19). Abschnittsplan und Ankündigungen liegen unter moodle.kursinhalt und moodle.kursankuendigungen — zwei Schlüssel, damit die Hälfte, die geantwortet hat, frisch bleibt.

Und zwar für mehrere Kurse (22.09.2026, vorher Punkt 20). Unter jedem der beiden Schlüssel liegt eine Ablage mit Fächern, je Kurs eines; die Mehrzahl steht im Wert und nicht im Schlüssel, weil useStand den Schlüssel im Klartext verlangt (verify:rueckfall, Regel 4) und ein moodle.kursinhalt.<id> ein zusammengesetzter wäre. Wer während eines Ausfalls zwischen zwei Kursen wechselt, sieht bei beiden den Stand von gestern. Bis zum 22.09.2026 hielt der Rückfall genau einen: beim anderen stand der Satz, dass Denotato es nicht weiß — ehrlich und trotzdem die halbe Antwort, denn derselbe Browser hatte beide gestern noch vollständig da.

Die Ablage gibt von hinten her, bevor sie verworfen wird. Jeder Rückfallschlüssel hat 256 KB, und was darüber liegt, wird stillschweigend gelöscht — also auch das Fach des Kurses, den man gerade ansieht. Es bleiben deshalb höchstens acht Kurse und höchstens 96 KB liegen; darüber fällt weg, was am längsten nicht geholt wurde, und zwar immer von hinten. Eines bleibt in jedem Fall: das gerade abgelegte. Passt es allein nicht, verhält sich der Schirm wie vor dem 17.09.2026 und zeigt bei einem Ausfall nichts.

Jedes Fach weiß, wann es geholt wurde, und die Standmarke über einem Kurs nennt dessen Zeitpunkt, nicht den des Schlüssels. Sonst stünde über einem Kurs von vorgestern eine Marke von heute, sobald ein anderer Kurs geschrieben wurde — die Marke darf nie frischer klingen als das, was unter ihr steht (Rahmen 7). Im offenen Kurs gilt außerdem dessen Standmarke und nicht die der Kursliste.

Und der Ankündigungsblock verschwindet nicht mehr (22.09.2026, vorher Punkt 21). Warum an einer Stelle nichts steht, entscheidet eine Rechnung mit vier Ausgängen, und beide Blöcke — Inhalt wie Ankündigungen — benutzen dieselbe:

AusgangWannWas dasteht
datenes ist etwas zu zeichnendie Liste
ausgefallenMoodle hat nicht geliefert und von diesem Kurs ist nichts aufgehoben„Moodle hat die Ankündigungen nicht geliefert, und von diesem Kurs liegt hier kein früherer Stand …"
gefiltertes ist etwas da, die Suche lässt nichts übrig„Keine Ankündigung in diesem Kurs passt zur Suche."
leersonstbeim Inhalt „Dieser Kurs hat keine Abschnitte."; bei den Ankündigungen nichts, auch keine Überschrift — ein Kurs ohne Ankündigungsforum ist der Normalfall und kein Ereignis

Die Reihenfolge ist die Aussage. Der Ausfall steht vor der Suche: Wer nichts bekommen hat, weiß nicht, ob die Suche etwas weggefiltert hätte. Und gefiltert heißt, dass etwas da ist — bis zum 22.09.2026 las man in einem leeren Kurs „Nichts in diesem Kurs passt zur Suche.", einen Satz über die Suche, wo einer über den Kurs fällig war.

Moodle meldet Fehler mit HTTP 200. Der Statuscode sagt nichts; im Körper steht ein Feld exception. Wer nur auf den Status schaut, hält eine Absage für ein Ergebnis — geprüft wird deshalb der Körper.

Wenn Moodle langsam antwortet

Nach 15 Sekunden bricht jeder Aufruf ab und zählt als „nicht erreichbar" — dieselbe Zeile, derselbe Rückfall. Die Zahl steht seit dem 21.09.2026 an einer Stelle und lässt sich über MOODLE_TIMEOUT_SECONDS in der Serverumgebung ändern; ein Wert, der keine Zahl oder nicht positiv ist, fällt stillschweigend auf die 15 Sekunden zurück. Vorher las nur der Umgebungs-Rückfall die Einstellung, den seit BE08 kein angemeldeter Nutzer mehr geht — die drei Wege, die tatsächlich laufen (Schirm, tägliche Gegenprobe, Abgleichskript), trugen die Fünfzehn je einzeln im Quelltext.

Ausgenommen ist das Herunterladen einer Datei. Ein Vorlesungsskript mit zwölf Megabyte braucht länger als jede Abfrage: dort gelten zwei Minuten für den Download und drei für das Auslesen des Textes. Beides an dieselbe Zahl zu binden hieße, entweder die Abfrage zu geduldig oder den Download zu ungeduldig zu machen.

Was man tun kann

Reiter ordnen (OE10, N500): Wer Vorgaben für alle setzen darf, ordnet und blendet die Reiter über „Bearbeiten“ im Kopf; die Folge steht in oberflaeche.reiter unter moodle, Ansichten je Abschnitt moodle/<abschnitt> (Rahmen §3).

ElementWas passiert
Kurs anklickenöffnet Inhalt und Ankündigungen
Moodle verbinden (F09, ab 21.09.2026; bis RL02 „Zu den Zugangsdaten")nur im Leerzustand ohne Zugang, als Hauptknopf: der Weg zum Web-Service-Schlüssel. Zeigt auf /settings?a=zugaenge und damit auf den Abschnitt, nicht auf den Schirm — vorher nannte der Satz daneben den Abschnitt beim Namen, weil die Einstellungen ?a= nicht lasen
Vorschlag übernehmen („Vorschlag: <Modul> · Übernehmen") (KN-261013-anschluss-einrichten, ab 14.10.2026)steht an einem Kurs ohne Modul, wenn eine Regel ein Modul findet: Modulkürzel oder ganzer Modulname als Wortfolge in Kursname oder Kurzname, Kürzel vor Name, bei Gleichstand kein Vorschlag; ein Modul, das schon an einem anderen Kurs hängt, wird nicht vorgeschlagen (moodle/server/kursvorschlag.ts). Das Semester kommt vom Modul oder aus dem Kursbeginn. Ein Tippen setzt Modul und Semester über /moodle/assign-course; danach verschwindet die Zeile. Ein Kurs mit Vorschlag bekommt keine Karte „Modul anlegen“ im Begleiter
Modul zuordnen („Modul wählen …")verknüpft einen Moodle-Kurs mit einem Denotato-Modul
Kategorie zuordnen („keine Kategorie") (N149, ab 23.09.2026)an einem Kurs ohne Modul — oder mit Modul, wenn schon eine eingetragen ist: ordnet ihn einer Kalenderkategorie zu; „keine Kategorie" leert sie. Gesperrt, solange es keine Kategorie gibt
Semester zuordnen („kein Semester") (N149, ab 23.09.2026)dasselbe für ein Semester
Übernehmenimportiert den Kurs als Modul nach Denotato
Hierher holen (an der einzelnen Datei)lädt sie auf Denotatos Server, legt sie als Quelle im verknüpften Modul ab und gewinnt ihren Text. Steht nur an PDF-Dateien (seit dem 07.10.2026 nicht mehr an PS, DjVu und EPUB, die nie Text lieferten), und nur, wenn der Kurs an einem Modul hängt — der Knopf sagt das vorher, statt es hinterher als Fehler zu melden. Er lädt nicht in den Download-Ordner; wer die Datei selbst will, klickt daneben auf ihren Namen und landet in Moodle
Suchenfiltert die drei Listen und im Kurs: Ankündigungen samt Verfasser und deren Text, Abschnitte samt Aktivitäten und Dateinamen
Alle Kursezurück zur Liste
In Moodle öffnenVerweis nach außen

„Übernehmen" und „Hierher holen" melden ihren Erfolg mit einer Zeile, und die trägt seit dem 23.09.2026 den Haken davor (haken, N134) — zuvor nur grüner Hintergrund und grüner Text; das Wort ist seitdem neutral (Style-Guide §7.3, Regel F), die Farbe trägt jetzt das Zeichen.

Woher die Daten kommen

MOODLE_STATUS · MOODLE_COURSES · MOODLE_CONTENTS · MOODLE_FORUMS · MOODLE_DISCUSSIONS · MOODLE_POSTS · MOODLE_DEADLINES · MOODLE_ASSIGNMENTS · MOODLE_LINK · MOODLE_LINKS · MOODLE_ASSIGN · MOODLE_IMPORT.

Die Zuordnung eines Kurses (N149, BE47, 23.09.2026). MOODLE_LINKS liefert seitdem vier Listen: modules wie bisher (jedes Modul, mit seinem Kurs oder ohne), dazu courses — jeder Kurs, an dem ein Modul, eine Kategorie oder ein Semester hängt —, categories und semesters für die beiden Auswahlfelder. Geschrieben wird über MOODLE_ASSIGN (/api/moodle/assign-course): Ein weggelassenes Feld bleibt, null leert es, und eine fremde oder unbekannte Kennung wird abgewiesen, bevor etwas geändert ist. Kategorie und Semester stehen in study.moodle_courses (Wanderung 99). Das Modul steht bis zum Umzug weiter an study.modules.moodle_course_id — die Spalte ist die Quelle, aus der Übernahme und Materialabgleich lesen, und die Tabelle spiegelt sie mit. Ein Dienst, der BE47 noch nicht kennt, schickt nur modules, auch wenn die Tabelle in seiner Datenbank schon steht. Der Schirm steht dann da wie vorher und zeigt weder Kategorie noch Semester — genauso, solange die Übersicht noch nicht geladen ist. Zwei gesperrte Felder mit dem Hinweis „Noch keine Kalenderkategorie angelegt" wären in beiden Fällen eine falsche Aussage.

Aufgehoben wird unter moodle.kurse, moodle.fristen, moodle.abgaben sowie — je Kurs ein Fach — moodle.kursinhalt und moodle.kursankuendigungen. Der Schirm war der erste, der einen Rückfall hatte, und seit dem 21.09.2026 ist er es über dasselbe Muster wie alle anderen (G03, Rahmen 7). Zwei Dinge sind dabei besser geworden: Die Marke nennt jetzt den ältesten Stand dessen, was der offene Abschnitt zeigt — und sie blitzt nicht mehr bei jedem Neuladen auf, bevor überhaupt gefragt wurde.

Der Unterschied betrifft genau den Kursabschnitt, und er ist der Grund für die Regel: Dort stehen zwei Stände untereinander, die Kursliste und an jeder Zeile die nächste Frist aus dem Kalender. Antwortete Moodle auf die Kurse und nicht auf den Kalender, stand über einer frisch geholten Liste nichts, während an jeder Zeile eine Frist von vorgestern hing — der schlimmste der drei Fälle, denn er sieht aus wie der beste. In den Abschnitten „Termine" und „Abgaben" steht je ein Stand, und der ist dann auch der älteste.

Was von Moodle in Denotatos Datenbank landet, landet dort nur durch zwei Handgriffe: „Übernehmen" legt den Kurs als study.modules an und seine Aufgaben als study.exams; „Hierher holen" legt eine Datei als study.sources ab, samt Seitentext. Beide sind wiederholbar, ohne zu verdoppeln: Wiedererkannt wird an Moodles eigenen Kennungen — moodle_activity_id (Moodles cmid, nicht die Aufgabennummer), moodle_module_id zusammen mit dem Dateinamen, moodle_course_id — und nicht am Namen, weil zwei Semester derselben Vorlesung gleich heißen und ein Ordner mit sechs Dateien sich sonst selbst überschriebe. Was von Hand nachgetragen wurde, bleibt dabei stehen; ein zweiter Lauf zieht nur nach, was Moodle geändert hat, und lädt eine unveränderte Datei nicht noch einmal.

Der Web-Service-Schlüssel steht verschlüsselt in auth.service_credentials (bytea, pgp_sym_encrypt) — mitsamt der Adresse. Er verlässt den Server nie: in einer gespeicherten Datei-Adresse steht er nicht, weil Moodle dieselbe Datei unter zwei Pfaden anbietet und Denotato den ohne Schlüssel aufhebt.

Die Eule: „Einen Kurs zusammenfassen“ (KI21, N372)

Der geöffnete Kurs steht seit dem 28.09.2026 in der Adresse (/moodle?id=<Kursnummer>); „Zurück“ nimmt die Nummer wieder heraus, und beim Neuladen öffnet das Menü den Kurs wieder. Daraus liest die Eule ihren Gegenstand (client/gegenstand.ts).

Der Leser moodle/server/angebote.ts gibt nur mit, was Denotato zu dem Kurs verzeichnet hat: Modul (Name und Kürzel), Semester und Kalenderkategorie der Zuordnung, bis zu acht anstehende und vier zuletzt fällige Abgaben und Prüfungen des Moduls mit Stand und Punkten und bis zu dreißig aus Moodle übernommene Materialien. Moodle selbst wird nicht gefragt — der Leser läuft in der Transaktion des Kontos, und ein Abruf über das Netz hielte sie offen. Ein Kurs, dem im Konto weder Modul noch Semester noch Kategorie zugeordnet ist, wird abgewiesen wie ein fremder; das Menü der Eule sagt deshalb, einen zugeordneten Kurs zu öffnen.

Der Begleiter sucht Kurse (KI71, N519)

Ist Moodle für das Konto an, bekommt der Begleiter in Moodle und in seinem eigenen Schirm das Werkzeug moodle_kurse_suchen (moodle/server/nachlesen.ts, Abgleich in moodle/server/kurssuche.ts). Es liest wie moodle_kurse: erst der Bereich, dann der eigene Zugang ohne Rückfall, dann die Kurse aus Moodle; das Modul je Kurs kommt aus den gespeicherten Zuordnungen (modules.moodle_course_id). Die Kursadresse geht nicht mit, weil sie die Adresse des Moodle trägt. Sagt Moodle ab, liest das Modell den Satz mit dem nächsten Schritt und, falls vorhanden, die zugeordneten Kurse ohne Titel.

Dateien und Seiten lesen (KI-261007-moodle-seiten-dateien)

Das Werkzeug moodle_datei_lesen (POST /api/moodle/file, Kern in moodle/server/dateilesen.ts) liest eine Datei oder eine Moodle-Seite eines Kurses als Markdown, ohne sie zu übernehmen und ohne dass der Kurs an einem Modul hängt. Gewählt wird mit der Kennung des Moduls oder mit einer Datei-Adresse aus dem Kursinhalt; ob sie zum Kurs gehört, prüft derselbe Abgleich der Browser-Adressen wie beim Holen, und eine fremde Adresse wird gar nicht erst abgerufen. Eine Seite kommt aus mod_page_get_pages_by_courses; gibt die Hochschule die Funktion nicht frei, liest das Werkzeug die Datei index.html der Seite. HTML läuft durch den gemeinsamen Wandler (@shared/utils/htmlMarkdown), PDF durch pdftotext je Seite, Word, Excel und PowerPoint durch den eigenen Leser moodle/server/bueroformate.ts (Zip über node:zlib mit festen Grenzen gegen Zip-Bomben, schmaler XML-Leser, dann derselbe Wandler). Die Ausgabe ist in Teile von höchstens 8192 Zeichen geschnitten; eine Tabelle, die über eine Teilgrenze reicht, trägt ihren Kopf im nächsten Teil noch einmal. Webservice-Pfade und Merkmale werden aus dem Text entfernt, bevor er das Werkzeug verlässt (D28). Auf dem Schirm gibt es dazu nichts; das Werkzeug ist für den Begleiter und MCP-Clients.

Vorschläge aus Moodle (KI-261007-luecken-vorschlaege)

Ein Kurs ohne Modul bringt beim Lesen der Kursliste eine Modul-Karte in den Begleiter (moodle/server/kartenluecken.ts). Der Klick auf „Anlegen“ legt das Modul an und ordnet den Kurs in derselben Handlung zu: Moodle hängt dafür an die Karte eine Folge (moodle/server/kartenfolgen.ts), die moodle_kurs_zuordnen mit der neuen Modulkennung ruft. „Zurücknehmen“ geht vom letzten Schritt her — erst die Zuordnung (Gegenzug in moodle/server/gegenzuege.ts: Modul, Kategorie und Semester des Kurses wie davor), dann das Modul. Ist Moodle für das Konto aus, legt der Klick nur das Modul an.

Ein neues Übungsblatt oder Skript bringt Karten, sobald der Materialabgleich seinen Text frisch gelesen hat — eine neue Datei oder eine, die Moodle geändert hat (moodle/server/materialkarten.ts, die Regeln in @shared/handlers/study/kartenentwuerfe.ts). Aus einem Blatt wird je Aufgabe eine Karte „Aufgabe anlegen?“, dem Thema zugeordnet, dessen Nummer das Modell aus der Themenliste des Moduls nennt. Ohne Modell kommt eine Karte mit dem Namen des Blatts und ohne Aufgabentext; sie bietet nur „Bearbeiten“ (Grundsatz 40). Aus einem Skript eines Moduls ohne Themen werden Karten „Thema anlegen?“ mit Aspekten — nur mit Modell, denn den Namen eines Themas erfindet keine Regel. Hinaus gehen nur der Text der Datei und die nummerierten Themennamen, nie eine Kennung (N519); der Text gilt als Daten, nie als Anweisung (KI37). Gezählt wird gegen den Anlass „Vorschläge aus neuem Material“ mit eigenem Tagesdeckel. Eine weggeklickte Karte kommt erst mit einer neuen Fassung des Blatts wieder. Der Schalter heißt „Wenn in Moodle ein Übungsblatt oder Skript neu ist“ (Herkunft moodle-material).

Eine Frist ohne To-do (KI-261007-planen-vorschlaege) bringt eine Karte „To-do anlegen?“ mit Titel, Kurs, Fälligkeitstag und der Adresse der Aktivität in der Notiz. Moodle meldet zwei Wege über den Anschluss fristen (moodle/server/fristkarten.ts): beim Materialabgleich die Abgaben des Plans, beim Lesen der Fristen jeden Termin des Moodle-Kalenders. Beide nennen eine Abgabe mit derselben Kennung, sie bleibt eine Karte. Ob schon ein To-do steht und wie die Karte aussieht, entscheidet die To-do-Verwaltung (docs/seiten/tasks.md). Der Schalter heißt „Wenn in Moodle eine Frist ohne To-do steht“ (Herkunft moodle-frist).

Abgaben aus jedem Kurs ins Studium (F-261015-hausaufgabenfristen, 15.10.2026). Nach den Fristen fragt der Abgleich Moodle einmal nach allen Abgaben aller eingeschriebenen Kurse und schreibt die kommenden als Blatt nach „Studium › Prüfungen“, mit dem Modul des Kurses oder ohne (moodle/server/abgabenabgleich.ts, Einzelheiten in docs/seiten/study.md). Der Abgleich läuft dabei für jedes nicht gesperrte Konto mit hinterlegtem Zugang; bis zum 15.10.2026 stand ein Anmeldename als Vorgabe im Skript, und der Timer glich nur dieses eine Konto ab.

Einreichungen setzen den Abgabestand (W-261019-einreichungsstatus, 20.10.2026). Danach fragt der Abgleich je Blatt mit Moodle-Aktivität den eigenen Einreichungsstand (mod_assign_get_submission_status, bei Gruppenabgaben zählt die der Gruppe). Eingereicht stellt ein offenes Blatt auf „abgegeben“; eine neue Einreichung nach der Antwort auf „Genügt die aktuelle Abgabe?“ macht die Abgabe wieder vorläufig. Nach Abschneidezeit oder Frist fragt er je Blatt nur noch einmal (study.exams.moodle_einreichung_gelesen_am, Wanderung 382). Eine Abgabe, zu der Moodle nichts sagt, hält die anderen nicht auf; die Zeile im Bericht zählt sie als „ohne Einreichungsstand“. Einzelheiten in docs/seiten/study.md.

Lernkarten aus einem Moodle-Skript (KI-261007-lernen-vorschlaege). Steht in einem Skript eines zugeordneten Kurses eine Definition, ein Satz oder eine Formel, die im Fach des Moduls noch keine Karte hat, kommt „N Lernkarten anlegen“ mit Vorschau dazu; angelegt wird ins Fach des Moduls, das dem Kurs zugeordnet ist (Moodle-Vorrang). Liegt dieselbe Datei zusätzlich im eigenen Materialordner, ist sie dort eine eigene Quelle mit eigenem Anlass; doppelte Paare fallen erst beim Vergleich mit den Karten im Fach weg. Nachweis: verify:moodle, „Moodle-Skript mit Sätzen“.

Aufgaben aus Ankündigungen und Skripten (KI-261007-moodle-ankuendigungen). Der Materialabgleich holt je zugeordnetem Kurs die Ankündigungsforen und meldet neue Ankündigungen der letzten 14 Tage über den Anschluss aufgabentexte (Quelle moodle-ankuendigung, höchstens drei je Kurs und Lauf); steht darin eine Aufgabe mit Frist, schlägt die To-do-Verwaltung ein To-do vor (docs/seiten/tasks.md). Welche Ankündigung gemeldet ist, steht in study.moodle_announcements (Wanderung 264); eine in Moodle geänderte meldet sich wieder, unter derselben Zeile. Ist der Schalter „Wenn in einer Moodle-Ankündigung eine Aufgabe mit Frist steht“ (Herkunft moodle-ankuendigung) aus, fragt der Abgleich Moodle gar nicht nach den Foren. Ein neu gelesenes Skript meldet seinen Text ebenso (Quelle skript), aus Moodle wie aus dem eigenen Ordner. Nachweis: verify:moodle, „Neue Ankuendigungen melden sich bei den To-dos“.

Eine neue Datei im Kurs (KI-261014-moodle-dateien). Liest jemand — Mensch oder Begleiter — den Inhalt eines Kurses, der an einem Modul hängt, kommt zu jeder lesbaren Datei, die dort noch nicht als Material steht, die Karte „Datei ins Material übernehmen?“ mit Titel und Modul (moodle/server/dateikarten.ts). Welche Datei als Material zählt, entscheidet dieselbe Regel wie beim Übernehmen (planeUebernahme); bekannt ist eine Datei an Aktivität und Dateiname, auch wenn ihre Quelle im Papierkorb liegt. „Übernehmen“ ruft moodle_uebernehmen mit genau dieser Datei und führt ins Material. „Nicht jetzt“ hält die Karte für diese Datei fern. Ein Kurs ohne Modul bringt keine Datei-Karte; dafür gibt es die Modul-Karte oben. Der Schalter heißt „Wenn in einem zugeordneten Moodle-Kurs eine Datei neu ist“ (Herkunft moodle-datei).

Die nächtliche Fristsuche (KI-261015-naechtliche-fristsuche, 15.10.2026). Der Timer denotato-fristsuche.timer läuft jede Nacht um 03:40 (moodle/server/runFristsuche.ts, von Hand npm run fristsuche --prefix packages/backend, mit -- --plan nur die Liste, wer dran wäre). Dran ist ein Konto mit hinterlegtem Zugang, wenn seine Stufe es in dieser Nacht vorsieht: Stufe frei alle fristsuche.tageFrei Tage (Vorgabe 7), premium, admin und dev alle fristsuche.tagePremium Tage (Vorgabe 1), 0 heißt aus; beides stellt ein Admin in den Instanzwerten. Welche Nacht der Woche ein wöchentliches Konto hat, rechnet sich aus seiner Kennung. Gelesen werden die laufenden Kurse: jeder Abschnitt mit Zusammenfassung und den Beschreibungen seiner Aktivitäten (gekürzte Abschnitte ganz) und die Diskussionen der Foren, die keine Ankündigungen sind und seit der letzten Suche geschrieben wurden (moodle/server/fristsuche.ts). Was ein Wort der Frist und eines der Zeit trägt, geht über den Anschluss aufgabentexte an die To-do-Verwaltung (Quellen moodle-kurstext und moodle-forumtext); steht darin eine Aufgabe mit Frist ohne To-do, kommt die Karte „To-do anlegen?“. Gefragt werden höchstens fristsuche.texte Texte je Konto und Nacht (Vorgabe 12), jeder Ruf aus dem Kontingent des Kontos; ein unveränderter Text wird erst nach fristsuche.wiederTage Tagen (Vorgabe 30) wieder gefragt, denn sein Gegenstand im Modellprotokoll ist aus Ort und Inhalt gerechnet. Der Schalter heißt „Wenn in einem Moodle-Kurs eine Frist nur im Text steht“ (Herkunft moodle-fristsuche); ist er aus, fragt die Suche Moodle gar nicht erst. Nachweis: plugins/src/moodle/server/fristsuche.test.ts.

Was fehlt

  1. Kein Rückfall bei Ausfall — gebaut am 17.09.2026. Kurse, Fristen und Abgaben bleiben stehen, wenn Moodle nicht antwortet, und darüber steht „Moodle antwortet gerade nicht — Stand von Mo., 14.09., 21:47". Und seit dem 21.09.2026 wartet der Baustein nicht mehr (G03): useStand zog das Muster aus ihm heraus, elf Bereichsschirme benutzen es, und verify:rueckfall hält fest, dass es dabei bleibt. Was noch fehlt: Kursinhalt und Ankündigungen fallen nicht zurück, nur die drei Listen — und der Kalender und das Briefing, die hier als die Nächsten genannt waren, sind bis heute nicht dabei.
  2. Keine Frist in der Übersicht — zweimal halb gebaut. Im Schirm steht sie seit dem 21.09.2026 an jeder Kurszeile: die nächste der nächsten 30 Tage, mit Namen und Termin, in vier Stufen gefärbt (§7.6); zugeordnet wird über Moodles Kurskennung und nicht über den Namen, weil zwei Semester derselben Vorlesung gleich heißen. In der Übersicht stehen nur übernommene Abgaben: die Übernahme legt sie als study.exams mit kind = 'abgabe' an, und briefingController.ts holt die nächsten fünf noch offenen. Was fehlt: ein Kurs, den man nicht übernommen hat, ist auf der Startseite unsichtbar. Seine Fristen stehen nur hier, und dass man den Schritt noch nicht getan hat, sagt einem die Startseite nicht — sie schweigt genauso wie bei einem Kurs ohne Abgaben.
  3. Kein Herunterladen — gebaut am 21.09.2026. moodle/server/dateien.ts lädt eine Moodle-Datei mit dem Web-Service-Schlüssel, erkennt ein PDF am Inhalt statt am Namen und gewinnt Text daraus; seit dem 21.09.2026 steht der Knopf „Hierher holen" an der einzelnen Datei und benutzt genau das. Was fehlt: ein Herunterladen in den Browser gibt es weiterhin nicht — wer die Datei selbst will, klickt auf ihren Namen und landet in Moodle. Und es geht eine Datei nach der anderen: einen Abschnitt oder eine Auswahl auf einmal zu holen, ist nicht vorgesehen.
  4. Keine Benachrichtigung. Eine neue Ankündigung merkt man, wenn man nachsieht — die Marke „neu" (Punkt 7) steht erst da, wenn man den Kurs schon geöffnet hat. Etwas, das von sich aus meldet, bräuchte einen Lauf auf dem Server und eine Stelle, an der er meldet; beides gibt es für diesen Bereich nicht.
  5. Keine Frist → To-Do. Der offensichtlichste Handgriff fehlt.
  6. Keine Suche über Kurse und Beiträge — halb gebaut. Das Suchfeld filtert die drei Listen und seit dem 21.09.2026 auch im Kurs: Ankündigungen samt Verfasser, Abschnitte samt Aktivitäten und Dateinamen. Seit dem 22.09.2026 auch den Text der Ankündigung selbst — Moodle liefert den Eröffnungsbeitrag in mod_forum_get_forum_discussions mit, und in der Betreffzeile steht „Organisatorisches" und nicht „Klausur verschoben". Was fehlt: die Antworten unter einer Ankündigung. Die holt MOODLE_POSTS, und zwar erst beim Aufklappen; sie für jede Diskussion im Voraus zu holen wäre ein Aufruf je Diskussion in den Web-Service der Hochschule. Und gesucht wird immer nur in dem, was schon geladen ist; Moodles eigene Suche (core_search_*) wird nie gefragt.
  7. Kein Ungelesen-Zustand — gebaut am 21.09.2026. Eine Marke „neu" steht an jeder Ankündigung und an jeder Datei, die sich seit dem letzten Besuch des Kurses geändert hat. Was fehlt: der Besuch steht im localStorage dieses einen Browsers. Wer am Rechner liest und abends am Telefon nachsieht, bekommt dort wieder „erster Besuch" — und in der Kursliste steht gar nichts, weil dafür jeder Kurs einzeln bei Moodle angefragt werden müsste.
  8. Der Kalender kennt Moodle nicht — übernommene Abgaben werden zu Terminen: sie stehen als study.exams, und calendarCollector.ts sammelt daraus Ganztagstermine. Was fehlt: Moodles eigener Kalender. Gefragt wird genau ein Kalenderaufruf, core_calendar_get_action_events_by_timesort — und der gibt nur Handlungstermine heraus, also solche, die an einer Aktivität hängen. Ein Klausurtermin, eine Veranstaltung, ein Eintrag, den ein Dozent von Hand in den Kurskalender setzt, hat keine Aktivität und kommt damit nirgends an; core_calendar_get_calendar_events und die Monatsansicht werden nicht gefragt. Der Deckel von 50 ist seit dem 22.09.2026 weg. Moodle gibt je Aufruf höchstens fünfzig Termine heraus (limitnum ist dort gedeckelt, 100 lehnt den ganzen Aufruf ab); bis dahin fragte Denotato genau einmal und nahm, was kam — in einem Semester mit acht Kursen fiel der Rest still weg, und an einer Kurszeile stand „keine Frist in den nächsten 30 Tagen" für einen Kurs, der eine hatte. Jetzt wird über Moodles eigenes lastid/aftereventid geblättert, bis zu vier Seiten weit. Dabei entscheidet die erste Seite über den Erfolg und jede weitere nur über die Vollständigkeit: Scheitert die erste, ist das ein Ausfall mit Fehlerzeile und Rückfall; scheitert eine spätere, bleibt stehen, was schon da ist. Blättern kann damit nie weniger liefern als der eine Aufruf davor. Was fehlt: mehr als 200 Termine in dreißig Tagen fallen weiterhin still weg, und das Abbrechen sagt es nur dem Server — MOODLE_DEADLINES gibt die Terminliste ohne Vermerk heraus, weil die Antwortform (und mit ihr endpointPaths.ts, der Client und packages/mcp) sonst mitwandern müsste.
  9. Keine Dozentenzuordnung. Moodle weiß, wer den Kurs hält; /people erfährt es nicht. Denotato fragt bis heute niemanden danach: core_enrol_get_enrolled_users steht in keinem Aufruf, und der einzige Name, der am Schirm auftaucht, ist der Verfasser einer Ankündigung.
  10. Kein Merken einer Datei als Material — gebaut am 21.09.2026 (siehe Punkt 3). Jede Datei des Kurses wird zur Quelle in study.sources, mit Datei und Seitentext, und der Knopf an der einzelnen Datei holt genau eine. Was fehlt: der Knopf steht nur an dem, was Denotato lesen kann — ein Video, ein Archiv, eine Tabelle bleiben außen vor; und er verlangt, dass der Kurs an einem Modul hängt, weil eine Quelle ohne Modul zu nichts gehörte.
  11. Kein Verlauf — gebaut am 21.09.2026. Über dem Kurs steht, was seit dem letzten Besuch dazukam: Ankündigungen und geänderte Dateien, jede Zahl mit ihrem Wort. Seit dem 22.09.2026 zählt er den ganzen Kurs und nicht den gefilterten — der Satz steht über dem Kurs, nicht über der Trefferliste. Was fehlt: was verschwunden ist, sagt niemand. Eine gelöschte Ankündigung fehlt einfach; dafür müsste der vorige Bestand mit im Besuchsbuch liegen und nicht nur sein Datum.
  12. Nur ein Moodle. Ein zweiter Server (anderes Institut) geht nicht.
  13. Dateigrößen runden großzügig — behoben am 21.09.2026. Unter einem Kilobyte steht jetzt die Zahl selbst („40 Bytes"); vorher hob ein Math.max(1, …) alles Kleinere auf „1 KB". Was bleibt: die Einheiten „Bytes"/„KB"/„MB" stehen fest im Quelltext und werden nicht übersetzt — nur das Trennzeichen der Zahl kommt aus der Oberflächensprache (I05).
  14. Keine Angabe, wann eine Datei zuletzt geändert wurde — gebaut am 21.09.2026, ergänzt am 22.09.2026. Neben Name und Größe steht das Datum mit Uhrzeit, das Moodle im selben Aufruf mitliefert; ein Übungsblatt, das am Tag der Ausgabe zweimal hochgeladen wird, ist damit unterscheidbar. Was fehlt: die Aktivitäten selbst (ein Test, ein Forum, eine Aufgabe) tragen kein Datum, obwohl sich auch die ändern.
  15. Die Fälligkeit färbt nur die Zeitangabe — gebaut am 22.09.2026. Kurszeile, Termin und Abgabe tragen Schiene, Rand und Grund nach §7.6; ab dreißig Tagen Säumnis trägt der Block nichts mehr, damit die Abgabenliste keine Wand aus Rot wird. Was bleibt: die Farbe ist status.warning und nicht die Bereichsfarbe, die §7.6 verlangt — das Moodle-Grau #9aa5b8 liest sich nicht als Dringlichkeit. Die Abweichung steht hier und damit im Dokument; der lebende Style-Guide unter /stil weiß nichts von ihr, und verify:stil prüft Zimmerfarben, nicht Abweichungen davon.
  16. „Überfällig seit" rechnet in Vierundzwanzigstundenschritten — behoben am 21.09.2026. Gerechnet wird in Kalendertagen, in der Zone des Lesers: Eine Abgabe von gestern 23:00 ist um 01:00 „überfällig seit einem Tag" und nicht mehr „seit heute". Die Zone kostet keine neue Abhängigkeit — sie kommt aus dem Zeitgeber, den der Schirm ohnehin führt, und die Rechnung selbst bleibt rein: Sie bekommt zwei Tagesschlüssel und keine Uhr. Und seit dem 22.09.2026 sagt der Rückfall, dass er einer ist. Lässt sich kein Tagesschlüssel bilden — ein unlesbarer Zeitpunkt, eine Zone, die Intl nicht kennt —, steht wieder die Rechnung in Vierundzwanzigstundenschritten da, und zwar angeschrieben: „überfällig seit etwa einem Tag", „überfällig seit weniger als einem Tag". Vorher schrieben beide Wege denselben Satz, und „seit einem Tag" hieß einmal „seit gestern" und einmal „seit vierundzwanzig Stunden".
  17. Die Zeitgrenze steht in der Serverumgebung, nicht in den Einstellungen. MOODLE_TIMEOUT_SECONDS gilt für alle Konten gemeinsam und lässt sich nur auf dem Server ändern — während der Zugang selbst seit BE08 dem einzelnen Konto gehört. Wer ein langsames Institut hat, kann nichts tun; wer ein schnelles hat, wartet trotzdem fünfzehn Sekunden. Und die zwei Minuten fürs Herunterladen einer Datei sind überhaupt nicht einstellbar.
  18. Der Abgleich von Hand nimmt noch den alten Datenbankport an — halb, und der Vorwurf war falsch (22.09.2026). 5431 ist nicht die Nummer des ausgemusterten Pi, sondern die des Cloud-Servers: betrieb/hetzner/20-postgres.sh setzt PG_PORT=5431, und betrieb/hetzner/README.md nennt sie an einem Dutzend Stellen. Die Zahl zu ändern wäre eine Entscheidung über den Betrieb und nicht über diesen Bereich. Geändert wurde ihre Stummheit: Sie stand an drei Stellen in drei verschiedenen Schreibweisen (Number(env('DB_PORT')) || 5431 nimmt eine gesetzte Null nicht, env('DB_PORT') === '' ? '5431' : … schon), liegt jetzt in server/datenbank.ts, und wer sie ohne DB_PORT benutzt, liest das vor dem Verbindungsversuch statt danach ein „connection refused". verify:moodle besteht darauf, dass DB_PORT im Bereich genau einmal gelesen wird. Was fehlt: dieselbe Zahl steht weiterhin in betrieb/ und in anderen Bereichen; ein Nachweis dafür gibt es nur hier. Ganz erledigt am 24.09.2026 (B57, N243): Geraten wird nirgends mehr. server/datenbank.ts reicht nur noch databaseConfig() aus @shared weiter, das ohne DB_PORT oder DB_NAME abbricht und die erwartete .env nennt. verify:moodle verlangt jetzt, dass der Bereich DB_PORT gar nicht selbst liest; für ganz Denotato hält verify:umgebung fest, dass keine Datei einen Ersatzwert setzt.
  19. Ein Ausfall mitten im Kurs sieht aus wie ein Ende — gebaut am 22.09.2026. Kursinhalt und Ankündigungen liegen unter moodle.kursinhalt und moodle.kursankuendigungen; wer den Kurs gestern offen hatte, sieht heute den Stand von gestern, markiert mit der Standmarke des Kurses. Seit demselben Tag sind auch die beiden Reste weg, die hier standen: aufgehoben wird nicht mehr genau ein Kurs (siehe 20), und der Ankündigungsblock sagt beim Ausfall, dass er einer ist (siehe 21). Was fehlt: das Aufgehobene sammelt sich erst innerhalb einer Sitzung an (siehe 23).
  20. Der Rückfall des Kurses hält genau einen — gebaut am 22.09.2026. Unter beiden Kursschlüsseln liegt jetzt eine Ablage mit einem Fach je Kurs; die Mehrzahl steht im Wert, weil useStand den Schlüssel im Klartext verlangt. Die Größenpflege, die dafür nötig war, ist eine reine Rechnung in shared/kursablage.ts: höchstens acht Fächer und 96 KB, abgeräumt wird von hinten, das gerade abgelegte Fach bleibt immer. Jedes Fach trägt seinen eigenen Holzeitpunkt, damit die Standmarke den Kurs meint und nicht den Schlüssel. Was fehlt: das Zusammentragen beginnt bei jedem Neuladen von vorn (siehe 23), und wie viele Kurse gerade aufgehoben sind, sagt der Schirm nirgends — wer nach acht Kursen den neunten öffnet, verliert den ältesten, ohne es zu erfahren.
  21. Der Ankündigungsblock verschwindet beim Ausfall still — gebaut am 22.09.2026. Warum an einer Stelle nichts steht, entscheidet jetzt eine Rechnung mit vier Ausgängen (shared/leerzustand.ts), und beide Blöcke benutzen dieselbe: Die Überschrift „Ankündigungen" bleibt stehen, solange es etwas zu sagen gibt, und darunter steht der Satz, der zum Grund passt. Nebenbei ist ein zweiter Fehler mit weggefallen: Ein leerer Kurs mit etwas im Suchfeld sagte „Nichts in diesem Kurs passt zur Suche." — ein Satz über die Suche, wo einer über den Kurs fällig war. Was fehlt: zwei der Sätze sind neu und stehen noch in keiner englischen Tabelle — „Moodle hat die Ankündigungen nicht geliefert …" und „Keine Ankündigung in diesem Kurs passt zur Suche." fehlen in languages/oberflaeche.en_us.json, wo die beiden entsprechenden Sätze des Kursinhalts längst stehen. Auf Englisch steht dort also Deutsch; verify:sprache zählt sie, ohne rot zu werden.
  22. Die dreißig Tage, nach denen ein Block nichts mehr trägt, stehen fest im Quelltext. SAEUMNISFENSTER_TAGE ist eine Zahl in shared/faelligkeit.ts. Wer ein Semester mit zwei Abgaben hat, will sie länger sehen; wer wöchentlich abgibt, kürzer. Einstellbar ist sie nicht, und eine Einstellung dafür wäre die vierte Schicht über derselben Frage.
  23. Die Kursablage sammelt sich erst innerhalb einer Sitzung an. useStand hält das Aufgehobene zurück, bis die erste Antwort da ist (befragt) — bis dahin sieht der Schirm eine leere Ablage. Schreibt er danach ein Fach, legt er es auf diese leere und nicht auf die im Browser: Nach jedem Neuladen bleibt vom vorigen Semester genau der Kurs übrig, den man zuerst wieder öffnet. Im Fall, für den die Ablage gebaut ist, stört das nicht — während eines Ausfalls wird gar nicht geschrieben, also bleibt alles liegen. Beheben ließe es sich nur in frontend/src/hooks/useStand.ts, und das gehört keinem Bereich: Die Lösung wäre ein Zusammenführen von Aufgehobenem und Neuem, das alle elf Bereichsschirme mitträfe.
  24. Dass eine Liste abgeschnitten ist, sagt der Server nur sich selbst. holeTermine weiß, ob es mehr geben könnte (vollstaendig), aber MOODLE_DEADLINES gibt weiter nur die Terminliste heraus. Der Schirm kann deshalb nicht „(mehr als 200 — Fenster verkleinern)" schreiben, und ein Modell, das über moodle_fristen fragt, erfährt es auch nicht. Es wäre ein Feld mehr in der Antwort und damit ein Zug durch endpointPaths.ts, die Client-Typen und packages/mcp — ein Umbau über drei Pakete für eine Zeile Text.
  25. Der Kalender wird viermal gefragt, wenn einmal reichen würde. Geblättert wird, solange eine Seite voll ist; Moodle sagt nicht vorab, wie viele Termine im Fenster liegen. Wer acht Kurse hat und den Schirm dreimal am Tag öffnet, schickt damit bis zu zwölf Anfragen an den Web-Service der Hochschule, wo es vorher drei waren. Zwischengespeichert wird nichts: MOODLE_DEADLINES fragt bei jedem Aufruf neu, und der Rückfall im Browser greift erst, wenn gar nichts kommt.
  26. ueberfaellig und faellig sehen sich bei 14 px sehr ähnlich — beides eine Uhr mit gekippten Zeigern, im Duotone-Stil nur an der Facette und nicht am Strich unterschieden (der bleibt bei beiden theme.colors.text.default). Wer die Farbe nicht sieht — Graustufen, schwacher Kontrast —, unterscheidet „heute fällig" von „überfällig" kaum am Zeichen, sondern weiterhin vor allem an Wort und Textfarbe. Ein eigenes Zeichen je Stufe wäre eine Erweiterung der Registry (83 Namen, N134) und damit außerhalb dieses Auftrags.
  27. Der Grund einer Absage steht im Platz der Kennung. — behoben am 23.09.2026 (D28). Ein Abbruch durch Denotato hat seinen eigenen Code, moodle_forbidden_error; schirmfehler setzt den Grund als zweiten Satz hinter die Meldung statt als „(Kennung: …)", und Moodles eigene Kennung bleibt eine Kennung (schirmfehler.test.ts, fünf Proben). Was bleibt: Der Grund ist ein deutscher Satz vom Server mit Rechnernamen darin — in einer englischen Oberfläche steht die Meldung englisch, der Grund dahinter deutsch.
  28. Die Kategorie eines Kurses erreicht den Kalender nicht (N149, BE47, drittes Merkmal). Sie steht an der Kurskarte, an den Terminen dieses Schirms und in moodle_fristen (assignment). In den Kalender kommt eine Moodle-Frist aber nur über „Übernehmen", und das hat zwei Grenzen: Es verlangt ein Modul, und es übernimmt nur Tests (quiz → Klausur) und Aufgaben (assign → Abgabe; ALS_PRUEFUNG in moodleUebernahme.ts). Foren, Feedback und Umfragen kommen nie hinein — Bens Beispiel „Feedback zum Brückenkurs endet" fehlte also auch an einem Kurs mit Modul. Selbst eine übernommene Prüfung trägt im Kalender nur ihre Art als Merkmal (calendarCollector.ts); keine Regel sieht Kurs oder Modul. Christian hat am 24.09.2026 den ersten Weg gewählt (N223): eine eigene Terminquelle „moodle“, die Kurskategorie vor den Regeln. Seit dem 15.10.2026 halb gebaut (BE47): Der Moodle-Abgleich hält die Fristen der nächsten Wochen in study.moodle_termine (Migration 328), und der Kalendersammler liest sie als Quelle „moodle“ — nur für Kurse mit Modul, Kategorie oder Semester, ohne die schon als Prüfung übernommenen (verglichen über die Kursmodulkennung), mit der Kurskategorie vor den Regeln. Hinaus geht das erst, wenn life.calendar_settings.write_moodle an ist — der Schalter „Moodle-Fristen“ unter „Was geschrieben wird“ im Kalender. Vorher sieht Ben die Zahlen im Probelauf: kalender_abgleich_jetzt mit probelauf: true und mitMoodle: true rechnet sie mit, ohne den Schalter umzulegen; der Knopf „Erst durchrechnen“ am Schirm kennt das Feld nicht und rechnet mit dem gespeicherten Schalter. Der .ics-Strom liefert die Fristen nicht aus. Nachgewiesen gegen eine Datenbank in verify:kalenderanbieter (Kurskategorie vor Regel, nicht eingeordneter Kurs draußen, kein zweites Mal neben der Prüfung, Schalter aus schreibt nichts). Gebaut seit dem 15.10.2026; es fehlt nur noch Bens Blick auf den Probelauf und das Umlegen des Schalters. Die Wege, wie sie zur Wahl standen:
    1. Eine eigene Terminquelle „moodle", die Kurskategorie vor den Regeln (empfohlen). Alle Fristen eines zugeordneten Kurses kommen in den Kalender, Feedback und Foren eingeschlossen, und die Kategorie des Kurses gilt vor jeder Regel, so wie beim Abo der Fremdkalender (ordneZu, Stufe 2). Nur dieser Weg erfüllt BE47 so, wie Ben es meint. Kosten: mehr als ein halber Tag. Moodle antwortet nicht im Takt des Kalenders, also braucht es einen Zwischenstand, den ein Lauf in festen Abständen füllt — einen solchen Lauf für Moodle gibt es noch nicht. Dazu kommt die Quelle in Regeln und Schirm, und was schon übernommen ist, darf nicht doppelt erscheinen (study.exams.moodle_activity_id sagt, was es ist).
    2. Dieselbe Quelle, aber die Regeln vor der Kurskategorie. Die Kategorie des Kurses ist dann nur die Vorgabe für seine Fristen. Eine Regel „Klausur → Prüfungen" fängt so auch die Moodle-Tests; dafür kann die Karte „Uni" sagen und der Kalender eine andere Kategorie zeigen. Kosten wie beim ersten Weg.
    3. „Übernehmen" auch ohne Modul. Tests und Aufgaben werden Prüfungen ohne Modul, jede mit der Kurskategorie als Zuweisung (life.calendar_overrides). Etwa ein halber Tag. Feedback und Foren bleiben draußen, und eine spätere Änderung der Kurskategorie erreicht die schon übernommenen Termine nicht.
    4. Keine Wirkung auf den Kalender. Die Kategorie ordnet nur diesen Schirm und moodle_fristen. BE47 schließt mit dem, was gebaut ist, und das dritte Merkmal fällt weg.
  29. Das Modul steht noch an zwei Orten. study.modules.moodle_course_id bleibt die Quelle, study.moodle_courses.module_id spiegelt sie in derselben Transaktion (gleicheSpiegelAb). Der Umzug, der die Spalte ablöst, braucht einen eigenen Schritt mit eigener Nachtragung; bis dahin muss jeder neue Schreibweg für das Modul den Spiegel mitziehen, und nur verify:moodle bemerkt, wenn einer es vergisst.
  30. Die Auswahlfelder der Kurskarte sind am Telefon zu klein (Style-Guide §16.1). Die Karte nimmt den gemeinsamen Baustein SelectField (frontend/src/components/controls/SelectField.tsx); seit BE47 stehen an einer Karte ohne Modul drei solche Felder statt einem. Der Baustein trägt unter pointer: coarse die Mindesthöhe der Marke layout.zielflaeche (E30, 08.10.2026: 44 px); das Feld ist dort nicht gemessen, und behoben wird es im Baustein, nicht hier.
  31. Vor den Feldern steht kein Wort. Eine gewählte Kategorie zeigt nur ihren Namen — „Uni" neben „kein Modul". Was das Feld ist, sagen nur der Hinweis beim Zeigen und das aria-label; am Telefon, wo es kein Zeigen gibt, bleibt nur der Zusammenhang.
  32. Der Semestername kommt deutsch vom Server. „1. Semester (Winter)" baut semestername in moodle/server/kurszuordnung.ts, und so steht er auch in einer englischen Oberfläche und in moodle_fristen.
  33. „Übernehmen" steht auch an einem Kurs, der mit Absicht kein Modul hat — abgeblendet, mit „Erst ein Modul wählen". Für einen Vorkurs, den man einer Kategorie zugeordnet hat, ist das ein Knopf, der nie gehen wird.