# 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. `buildList()` beziehungsweise `buildThread()` lesen das Original-DOM und bauen ein `div.rm-root`; beide haengen zum Schluss ueber `addCrumbs()` die fixierte Brotkrumenleiste an 6. `run()` waehlt die Ansicht und haengt bei `DEBUG` das gruene Overlay an 7. 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 - Unterschiede zwischen den Ansichten ueber einen Modifier am Root loesen, nicht ueber zwei Regeln mit demselben Selektor. `.rm-root.rm-thread` haelt so die 64px hinter der fixierten Leiste frei. - Namen sind ansichtsspezifisch zu halten: `.rm-jump`/`.rm-pages` gehoeren der Liste, `.rm-goto`/`.rm-grid` dem Thread-Sheet. - `.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. **`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`. 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 `