# 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 |
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'` 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 beiden
Ansichten benutzt
6. `buildList()` beziehungsweise `buildThread()` lesen das Original-DOM und
bauen ein `div.rm-root`; beide haengen zum Schluss ueber `addCrumbs()` die
fixierte Brotkrumenleiste an
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.
**`Page X of Y` steht als `Page 1 of 1114`
im Markup.** Ein Grep im Roh-HTML nach `Page 1 of` findet nichts, die Regex auf
`txt(document.body)` greift trotzdem. Nicht vorschnell fuer kaputt halten.
**Seitenlinks stehen nicht nur im Paginierungsblock.** Jede Themenzeile mit
mehr als einer Seite bringt ihr eigenes „Go to page: 1 … 42, 43" mit, und diese
Links tragen ebenfalls `start=`. Die Liste baute ihre Seitenleiste bis 3.5 aus
allen `a[href*="start="]` der Seite und kam so auf 103 Eintraege, von denen die
meisten mitten in einen fremden Thread fuehrten. `pageState()` macht es richtig
und liest nur den Block um den `jumpto()`-Link.
**`per_page` kommt aus der `jumpto()`-Funktion der Seite**
(`var per_page = '15';`). Nur damit lassen sich Seiten-URLs als
`start = (Seite - 1) * per_page` selbst bauen. Die gerenderten Seitenlinks
taugen nicht als Quelle — phpBB zeigt bei 1114 Seiten nur `1, 2, 3, 4, 5 … 1114`.
Ohne `per_page` degradiert die Leiste auf erste Seite plus die
Original-Zurueck/Weiter-Links, das Sheet verschwindet.
**`postRow()` steigt bewusst mehrere `tr` hoch.** Der Beitragstext liegt in
einer verschachtelten Tabelle, `closest('tr')` trifft die innere Zeile ohne
Autor- und Profilspalte.
**`DEBUG` steht auf `true`** und muss vor dem Ausliefern auf `false`. Die
Versionsnummer steht doppelt: im `@version`-Header und im Text des
Debug-Overlays.
## Testen
Es gibt keine Testsuite. Bewaehrt hat sich ein Wegwerf-Harness mit jsdom im
Scratchpad (nicht im Repo ablegen — das Projekt soll abhaengigkeitsfrei
bleiben):
```js
const dom = new JSDOM(html, { url: 'https://cs.rin.ru/forum/viewtopic.php?f=10&t=68689&start=45' });
```
Drei Stolpersteine dabei:
- JSDOM parst asynchron fertig. Nach `eval(src)` rund 60 ms warten, sonst hat
das Script noch auf `DOMContentLoaded` gewartet und nichts gebaut.
- `location.href` ist in JSDOM nicht ueberschreibbar. Fuer Navigationstests im
eingelesenen Quelltext `location.href = u;` durch einen Hook ersetzen.
- `requestAnimationFrame` und `scrollIntoView` fehlen und muessen gestubbt
werden.
Sinnvolle Faelle: erste Seite, zweite Seite, Mitte, letzte Seite, dazu die
Degradation ohne `per_page` und ein Thread ohne Paginierung. Fixture-Eckdaten:
`f=10&t=68689`, `per_page = 15`, 1114 Seiten, letzte Seite `start=16695`.
Dieselben Faelle gelten seit 3.5 fuer die Liste, die sich die Leiste mit dem
Thread teilt. Eckdaten von `dev/viewforum.php`: `f=10`, `per_page = 100`, 610
Seiten, letzte Seite `start=60900`, dazu 4 Unterforen, 7 globale
Ankuendigungen, 2 Ankuendigungen, 8 Stickies und 92 Themen. Achtung beim
Nachbauen einer einseitigen Liste: die Seitenlinks zu entfernen genuegt nicht,
`Page 1 of 610` steht auch im Text und traegt die Gesamtzahl allein.
Fuer die Weiche und die Listenansicht genuegt eine handgeschriebene Tabelle mit
`a.topictitle`-Zeilen. Zu pruefen ist dabei auch eine Seite ausserhalb der
Weiche wie `posting.php`: dort darf weder eine Ansicht gebaut noch ein
` |