# CLAUDE.md
Ein Userscript, das die phpBB-Seiten von `cs.rin.ru` fuer Safari auf iOS in eine
mobile Ansicht umbaut. Kein Build, keine Abhaengigkeiten, kein `package.json` —
die `.user.js`-Datei ist das Produkt.
| Datei | Greift auf | Zweck |
|---|---|---|
| `rin-mobile.user.js` | `*://cs.rin.ru/forum/*` | Beide Ansichten, Weiche nach Seitentyp |
| `dev/rin-mobile.dev.user.js` | zusaetzlich `localhost:*` | Loader, bindet per `@require file:///` das echte Script ein |
| `dev/viewtopic.php` | — | Fixture: gespeicherter Thread, 1114 Seiten |
| `dev/viewforum.php` | — | Fixture: Main Forum, 610 Seiten, mit Unterforen, Ankuendigungen und Stickies |
| `dev/index.php` | — | Fixture: Forumwurzel, zwei Kategorien mit Forenzeilen, keine Paginierung |
| `dev/ucp.php` | — | Fixture: Login-Formular (`mode=login`), nur ausgeloggter Zustand |
| `dev/after_login/ucp.php` | — | Fixture: dieselbe URL nach erfolgreichem Login, "Information"-Vorlage statt Formular |
Die Fixtures sind ueber `.gitignore` (`dev/*.php`) ausgeschlossen. Nach einem
frischen Klon sind sie nicht da und muessen nach der Anleitung in der README neu
gespeichert werden — die Seite verlangt einen Login.
## Aufbau
Ein IIFE, streng der Reihe nach:
1. `DEBUG`-Flag und `log`-Array
2. Weiche: `view` ist `'thread'`, `'liste'`, `'login'` oder `''` — abgeleitet
aus `location.pathname + location.search`
3. Nur bei gesetztem `view`: Viewport-Meta (idempotent ueber `[data-rm]`) und
der komplette Style-Block als Template-Literal
4. `replaceBody(root)` blendet alle bisherigen `body`-Kinder aus und haengt die
neue Ansicht an
5. `pageState()`, `pageSheet()` und `pageNav()` — die Paginierung, von Liste
und Thread benutzt, nicht vom Login
6. `buildList()`, `buildThread()` beziehungsweise `buildLogin()` lesen das
Original-DOM und bauen ein `div.rm-root`; alle drei haengen zum Schluss
ueber `addCrumbs()` die fixierte Brotkrumenleiste an, die zugleich ueber
`loginState()` die Login/Logout-Pille traegt
7. `run()` waehlt die Ansicht und haengt bei `DEBUG` das gruene Overlay an
8. Start ueber `DOMContentLoaded`, falls `readyState === 'loading'`
Auf Seiten ausserhalb der Weiche (`posting.php`, Profile, …) passiert nichts,
insbesondere wird kein Stylesheet injiziert.
## Fallen, die nicht aus dem Code hervorgehen
**Beide Ansichten teilen sich einen Klassennamensraum.** Das war der Grund fuer
die Zusammenlegung: als getrennte Scripts matchte `rin-mobile` auch
`viewtopic.php` und injizierte dort sein Stylesheet, obwohl es nichts baute.
Gleich benannte Regeln kollidierten mit identischer Spezifitaet, und die
Ladereihenfolge der Userscripts entschied ueber das Layout.
- Geteilt: `.rm-root`, `.rm-hidden`, `.rm-dbg`, die globale `img`-Regel
- Geteilt sind ausserdem die Leiste und ihr Sheet: `.rm-nav`, `.rm-sheet*`,
`.rm-goto`, `.rm-grid`
- Unterschiede zwischen den Ansichten ueber einen Modifier am Root loesen,
nicht ueber zwei Regeln mit demselben Selektor. `.rm-root.rm-paged` haelt
so die 64px hinter der fixierten Leiste frei — gesetzt von `pageNav()`
selbst, weil es die Leiste nicht auf jeder Liste gibt.
- Namen sind ansichtsspezifisch zu halten: `.rm-jump` und `.rm-group` gehoeren
der Liste, `.rm-post` und `.rm-sig` dem Thread.
- `.rm-av` braucht `!important` auf Breite und Hoehe, sonst gewinnt die globale
`img { height:auto !important }`-Regel gegen die Avatarmasse.
**Was nach `body` soll, muss nach `replaceBody()` angehaengt werden.**
Die Funktion markiert *alle* vorhandenen `body`-Kinder mit `.rm-hidden`. Das
Sheet wird deshalb erst beim ersten Antippen gebaut — das loest das Problem
nebenbei mit.
**In der Liste entscheidet die Dokumentreihenfolge ueber den Zeilentyp.**
`buildList()` sammelt Links auf `viewforum.php` und `viewtopic.php` in einem
Rutsch ein und ordnet jede `tr` nach ihrem ersten verwertbaren Link zu. Eine
Forenzeile verlinkt in der Spalte „letzter Beitrag" ebenfalls `viewtopic.php` —
zaehlte man Themenlinks zuerst, faellt die Zeile als Thema an, der Forenname
verschwindet und nur die Beschreibung bleibt als `.rm-last` uebrig. Genau so
war es vor 3.2. Brotkrumen und „Mark forums read" verlinken ebenfalls Foren,
stehen aber in Zeilen ohne Forenmerkmale und werden verworfen.
**Der Typ einer Themenzeile steht im Symbol, nicht am Titel.** `rinDark`
unterscheidet `global_read.png`, `announce_read.png`, `sticky_read.png` und
`topic_read.png` in der ersten Spalte, jeweils auch als `_locked`-Variante. Die
Trennzeilen der Seite (`
Stickies`) tragen
keinen Link und fallen beim Einsammeln durch; ihr Text ist trotzdem die beste
Beschriftung und wird ueber die Vorgaengerzeilen nachgeschlagen. Achtung beim
Zaehlen im Fixture: dieselben Symbole stehen unten noch einmal in der Legende,
ein Grep zaehlt also mehr, als es Zeilen gibt.
**Der Menuebalken der Seite verlinkt Themen.** „Forum rules" und die FAQ stehen
in einer Layoutzeile ohne Tabelle oder Script darin, die Bremse in `rowKind()`
greift dort also nicht. Ohne das Merkmal `a.topictitle` beziehungsweise zwei
Zahlenspalten wurde daraus eine Geisterkarte am Kopf jeder Liste — mit
Gruppenkoepfen faellt sie als eigener Abschnitt „Themen 1" auf.
**Der Gruppenkopf entfaellt hinter einer Kategoriezeile.** Sonst stuende auf
der Forenwurzel unter jeder Kategorie noch ein „FOREN n" — die Kategorie leitet
die Foren darunter bereits ein. Die Karten werden deshalb erst vollstaendig
gebaut und dann ausgegeben: die Anzahl im Kopf steht erst fest, wenn feststeht,
welche Zeilen ueberhaupt eine Karte hergeben.
**Das Seitengeruest ist selbst eine Tabelle.** `closest('tr')` liefert fuer
einen Link ausserhalb einer Listenzeile die Layoutzeile, die die ganze Seite
enthaelt. Ohne Bremse wurde daraus eine Karte mit dem kompletten Seitentext —
Kopfzeile, Stylesheet und Inline-Scripts inklusive — als Forenbeschreibung.
Zeilen mit `table`, `script`, `style` oder `form` darin sind deshalb keine
Kandidaten, und die Beschreibung wird nur aus den direkten Textknoten der
Titelzelle gebildet, nie aus deren gesamtem Text.
**Das Theme ist `rinDark`, nicht subsilver2.** Fuer die Forenliste waren die
Klassennamen aus der phpBB-Dokumentation (`forumlink`, `forumdesc`) deshalb
zunaechst nur eine Vermutung; `dev/viewforum.php` bestaetigt sie inzwischen. Der
Rueckfall auf die Form der Zeile bleibt trotzdem stehen — er kostet nichts und
traegt die Erkennung, wenn das Theme wechselt.
**Die Brotkrumenleiste ist fixiert, nicht sticky.** `.rm-root` traegt
`overflow:hidden` und wuerde damit zum Scrollport einer `position:sticky`
-Leiste, in dem nie gescrollt wird — sie klebte nie. Fixierte Nachfahren
klammert das `overflow` dagegen nicht, weil der Root keinen Containing Block
aufspannt. Die Freihaltung oben haengt an `body.rm-crumbed`, damit ohne
Brotkrumen im Markup kein leerer Streifen bleibt.
**Die Login-Erkennung ist inzwischen gegen eine echte Sitzung geprueft.**
`loginState()` unterscheidet `a[href*="mode=login"]` von
`a[href*="mode=logout"]` im Menuebalken und liest den Namen aus dem Muster
`Logout [ Name ]` im Linktext. `dev/index.php` und `dev/after_login/ucp.php`
sind eingeloggt aufgenommen (`Logout [ ]`) und bestaetigen das Muster.
Der Fallback bleibt trotzdem stehen: passt das Muster einmal nicht — andere
Sprache, anderer Klammertyp — zeigt die Pille „Logout" ohne Namen, statt eine
Ausnahme zu werfen.
**`ucp.php?mode=login` rendert nach dem Absenden dieselbe URL neu, diesmal
ohne Formular.** phpBB fuellt die Zwischenmeldung („You have been successfully
logged in.") in dieselbe generische „Information"-Vorlage, die auch fuer
andere Meldungen auf dieser Seite dient (vermutlich auch Login-Fehler, dafuer
liegt aber keine Fixture vor). `buildLoginInfo()` erkennt die Vorlage deshalb
an ihrer Form — eine Kopfzeile mit genau einer `th`-Zelle, gefolgt von einer
`td.row1`-Nachrichtenzelle — nicht am Wortlaut, und uebernimmt die Zelle
unveraendert wie das Formular selbst. Findet `buildLogin()` weder Formular
noch diese Form, baut es nichts; die Originalseite bleibt sichtbar.
**`index.php` wird ueber `location.pathname` erkannt, nicht ueber
`here`.** Der Login-Redirect haengt "index.php" gelegentlich als Wert eines
Query-Parameters an `ucp.php` (`redirect=index.php`, siehe `dev/ucp.php`).
Ein Test auf `location.pathname + location.search` wie bei den uebrigen
Weichen wuerde eine solche Login-URL faelschlich als Liste einordnen, noch
bevor die Pruefung auf `ucp.php` ueberhaupt greift.
**Ist eine Seite nicht laenger als der Viewport, kann Safari nicht wegscrollen,
um seine eigene Werkzeugleiste unten freizugeben.** Bei Liste und Thread faellt
das nicht auf, weil Karten beziehungsweise Beitraege fast immer laenger sind
als der Bildschirm. Der Login hat kein `.rm-nav`, das sonst Platz reserviert,
und war als kurze Formularseite der erste Fall, bei dem die Seite genau so
hoch wie der Viewport enden konnte — die teilweise eingefahrene Leiste blieb
dann stehen und verdeckte den Submit-Button, ohne dass noch etwas zu scrollen
war, um sie wegzuschieben. `env(safe-area-inset-bottom)` allein loest das
nicht, das federt nur den Home-Indicator ab. Der Bodensatz unter dem Formular
in `.rm-login form` ist deshalb absichtlich groesser als noetig: er erzwingt
Scrollhoehe ueber den Viewport hinaus.
**`buildLogin()` baut das Formular nicht nach, sondern verschiebt es.** Wie der
Beitragstext im Thread (`bodyWrap.appendChild(pb)`) wandert das
Original-` |