Files
rin-mobile-userscript/CLAUDE.md
T
Andre a8ca211805 feat: optimize search.php for the mobile view
search.php had no mobile treatment: the query form rendered as an
unstyled desktop table, and a submitted search fell back to the regular
forum-listing view on the untested assumption that results looked like
topic rows.

The query form is now recognised by its forum-picker select, the one
element the small "search this forum/topic" widgets embedded on other
pages don't share — they reuse the same form action and keyword field
name and would otherwise be picked up by mistake. Results in the "Posts"
display mode the in-thread search bar produces turned out to carry post
excerpts, not topic rows, and needed their own card layout instead of
running through the list view, which would have misread the topic and
profile links inside each hit.

Submit/reset controls kept native iOS Safari chrome even under
-webkit-appearance:none once restyled, clipping their labels — only
visible on device, not in the emulator this project is developed
against. Replacing them with real <button> elements (same name/value,
so the submitted form data is unchanged) fixed it where the CSS-only
attempt didn't; touch targets were sized up to match.
2026-08-03 09:03:27 +02:00

23 KiB

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
dev/search.php — Fixture: Suchformular ohne Treffer, eingeloggt aufgenommen
dev/search2.php — Fixture: Suchtreffer im Modus "Posts" (15 von 1438 Treffern, t=63042, per_page=15, 96 Seiten)

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 verlinken ebenfalls Foren, stehen aber in Zeilen ohne Forenmerkmale und werden verworfen.

„Mark topics read" braucht eine eigene Bremse, „Mark forums read" nicht. Beide sind reine Aktionslinks, keine Inhaltslinks, aber nur „Mark forums read" zeigt auf index.php und faellt schon beim Einsammeln durch, weil das keine der gesuchten Adressen ist. „Mark topics read" oberhalb der Themenliste zeigt dagegen mit ?mark=topics auf dieselbe viewforum.php-Seite und passt damit auf den Sammel-Selektor. Sein Link liegt zwar in einer eigenen inneren tr ohne verschachtelte Tabelle, a.closest('td.cat') findet aber trotzdem die aeussere Kategoriezelle drumherum — bis 3.7 wurde daraus eine dauerhafte, nicht einklappbare .rm-sec-Karte oberhalb von Global Announcements statt gar keiner. rowKind() verwirft seit 3.8 jeden Link mit ?mark= oder &mark= in der Adresse, bevor die uebrige Klassifizierung ueberhaupt beginnt. Das Repository hat keine eingeloggte viewforum.php-Fixture, die diesen Link mit echtem Markup zeigt — geprueft ist die Bremse nur gegen den vermuteten mark=topics-Parameter, per Texteinfuegung in dev/viewforum.php.

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 (<td class="row3" colspan="6"><b>Stickies</b>) 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 [ <Benutzername> ]) 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-<form> unveraendert in .rm-root und wird nur per CSS auf Block-Elemente umgelegt. Nachbauen wuerde bedeuten, sid, das doppelte redirect-Feld und autologin/viewonline einzeln nachzuziehen — verliert man dabei eines, scheitert der Login stumm oder landet nicht dort, wo er soll. Aus demselben Grund faengt buildLogin() keine Fehlermeldung eines gescheiterten Logins ab: ohne Fixture eines fehlgeschlagenen Versuchs waere die Erkennung geraten. Fehlt das Formular (input[name="username"]), baut die Funktion nichts und die Originalseite bleibt sichtbar.

Page X of Y steht als Page <strong>1</strong> of <strong>1114</strong> 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.

Die Lupe und die Login-Pille haengen an einer gemeinsamen margin-left:auto auf einem Wrapper (.rm-auth-group), nicht je an sich selbst. Saesse die Regel wie zuvor direkt auf .rm-auth, wuerde nur die Pille an den Rand geschoben und die Lupe bliebe links davon kleben — mit sichtbarer Luecke dazwischen statt „direkt links neben der Pille". Die Lupe existiert im Markup ohnehin nur bei bestehender Sitzung; ausgeloggt bleibt der Wrapper mit der Pille als einzigem Kind und verhaelt sich exakt wie vorher.

#topic-search steht dreifach identisch im Thread-Markup (Kopf, Fuss, ueber der Beitragsliste), jedes Mal mit derselben, eigentlich unzulaessigen id. document.querySelector('#topic-search') trifft einfach das erste und verschiebt es unveraendert in die zweite Leiste — alle drei tragen dieselben versteckten Felder (t, sf, terms), welches der drei benutzt wird, ist gleichgueltig.

select[name="fid[]"] ist der einzige verlaessliche Marker fuer das grosse Suchformular auf search.php. Sowohl viewforum.php als auch viewtopic.php betten ein eigenes Mini-Suchformular ein (#forum-search / #topic-search, „Search this forum…" / „Search this topic…"), beide mit action="./search.php" und einem Feld name="keywords" — nach diesen Merkmalen allein liesse sich das grosse Formular nicht von den kleinen unterscheiden. Der mehrfach waehlbare Forenwaehler existiert dagegen nur im grossen Formular.

Die Textfelder des Suchformulars tragen style="width: 300px" direkt im Markup. Eine externe Regel ohne !important sticht dagegen nicht, egal wie spezifisch der Selektor ist — buildSearchForm() entfernt das style-Attribut deshalb, statt gegen es anzuschreiben.

Eine abgeschickte Suche liefert keine Themenliste, sondern einen ganz anderen Aufbau. Die anfaengliche Annahme, Trefferzeilen tragen dieselben topictitle-Links wie eine Themenliste und laufen unveraendert durch buildList(), stimmte nicht — zumindest nicht im Modus "Posts" (sr=posts), der ueber die zweite Leiste im Thread entsteht, weil deren Formular kein sr setzt. Jeder Treffer ist stattdessen ein Beitragsauszug: Autor, Zeitstempel, Forum/Thema-Kontext, ein Stueck postbody. buildSearchResults() erkennt diesen Modus an p.topictitle — als <p>, nicht als <a> wie das gleichnamige Klassenpendant einer Themenzeile in viewforum.php — und baut daraus eigene Karten (buildHit()), bevor buildList()s allgemeines Zeileneinsammeln ueberhaupt anfaengt und die enthaltenen viewtopic.php-Links falsch klassifizieren wuerde. Ungeprueft bleibt der Modus "Topics" (sr=topics, Vorauswahl im grossen Suchformular) — dafuer liegt keine Fixture vor, dort sehen Treffer laut phpBB-Doku tatsaechlich wie topictitle-Zeilen aus und wuerden vermutlich durch den regulaeren Listenzweig laufen, das ist aber nicht verifiziert.

Ein Treffer im Modus "Posts" besteht aus drei <tr>, unterscheidbar nur an der Position, nicht an einer Klasse. Titelzeile (p.topictitle), Autor- und Betreffzeile, dann Antworten/Views samt Beitragsauszug — alle drei tragen row1/row2 wahllos, die einzige verlaessliche Zuordnung ist nextElementSibling von der Titelzeile aus.

Der Beitragsauszug haengt in <td class="postbody">, nicht in div/span.postbody wie im echten Thread. buildHit() verschiebt das <td> trotzdem unveraendert per appendChild() in .rm-body — das ist reine DOM-Manipulation, keine Neuparse, darum meckert der Browser nicht ueber ein <td> ausserhalb einer Tabelle. Es rendert danach aber weiterhin als display:table-cell, wenn .rm-root.rm-hits .rm-body > td das nicht ausdruecklich auf block zwingt.

"Replies: 5403" und "Views: 951837" stehen im Markup nur durch ein <br /> getrennt, ohne Leerzeichen. txt() normalisiert Whitespace, erzeugt aber keinen wo im Original-Markup keiner steht — txt(meta) liefert deshalb 5403Views:951837 statt zwei getrennter Werte. buildHit() greift beide Zahlen einzeln per Regex heraus und reicht sie an dieselbe metaRow() wie bei Themenkarten weiter, die den Trenner per CSS (::before) selbst einfuegt.

input[type="submit"]/input[type="reset"] setzen eigenes Padding und Zentrierung in iOS Safari unzuverlaessig um — auch mit -webkit-appearance: none. Zuerst aufgefallen bei Suchen/Zuruecksetzen im Suchformular nebeneinander, wo die feste Flex-Aufteilung die Verzerrung besonders sichtbar machte: zu schmale Buttons, Text nicht mittig. -webkit-appearance:none plus box-sizing:border-box/line-height:normal (erster Versuch) reichte laut Test am Geraet nicht aus — das legt einen tieferen WebKit-Bug nahe, bei dem input[type=submit/reset] die native Knopfoptik trotz appearance:none nicht vollstaendig ablegt. Der tragfaehige Fix ist inputToButton(): die Original-<input>-Elemente werden vor dem Verschieben durch echte <button type="submit/reset"> ersetzt (Name und Value wandern mit, am uebermittelten Formularfeld aendert sich nichts) — <button> wird von WebKit unter appearance:none zuverlaessig respektiert. Betrifft grundsaetzlich jeden Submit-Button im Script: Suchformular, Login, Thread-Suchleiste nutzen alle inputToButton(). Ungeprueft, ob -webkit-appearance:none allein bei einer anderen iOS-Version genuegt haette — der Wechsel auf <button> umgeht die Frage, statt sie zu beantworten.

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):

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 <style> injiziert werden.

Fuer die Forenliste braucht es eine zweite Tabelle im subsilver2-Schnitt: td.cat > h4 > a als Kategoriezeile, darunter Zeilen mit a.forumlink, drei p.forumdesc (Beschreibung, Moderatoren, Unterforen), zwei Zahlenspalten und einer Spalte „letzter Beitrag" mit Link auf viewtopic.php. Vier Faelle sind sinnvoll: reine Forenliste, reine Themenliste, beides auf einer Seite, und dieselbe Forenliste ohne die Klassen forumlink/forumdesc — die muss ueber die Zahlenspalten dasselbe Ergebnis liefern.

Fuer den Login genuegt dev/ucp.php unveraendert: geprueft werden das uebernommene Formular (Username-/Passwortfeld, sid, redirect, Submit) und die Pille im ausgeloggten Zustand. Fuer den eingeloggten Zustand der Pille dev/index.php oder dev/after_login/ucp.php laden — beide sind eingeloggt aufgenommen und tragen das echte Logout [ Name ]-Markup.

Fuer die Meldung nach dem Login genuegt dev/after_login/ucp.php unveraendert, URL ucp.php?mode=login: geprueft werden Titel, uebernommene Nachricht samt Link und die Pille. Zu pruefen ist ausserdem, dass dev/ucp.php (das Formular, gleiche URL) weiterhin die Formular-Zweig traf und nicht in die Meldungs-Erkennung faellt.

Fuer die Forumwurzel genuegt dev/index.php, URL index.php?sid=…: zwei Kategorien mit zusammen 16 Forenkarten, keine Paginierung. Zu pruefen ist zusaetzlich eine ucp.php?mode=login&redirect=index.php-URL — sie muss trotz des index.php-Substrings im Login-Zweig landen, nicht im Listen-Zweig.

Fuer das Suchformular genuegt dev/search.php unveraendert, URL search.php: geprueft werden der Forenwaehler als Erkennungsmerkmal, das verschobene Formular samt versteckter Felder (t, sid), die entfernten inline-style-Attribute der Textfelder und die Schnellzugriffs-Chips aus p.searchbar. Zu pruefen ist ausserdem dev/viewforum.php auf derselben Weiche: dessen eingebettetes #forum-search-Mini-Formular teilt sich Aktion und keywords-Feld mit dem grossen Suchformular und darf buildSearchForm() nicht faelschlich auslösen, die Forenliste muss wie gewohnt Karten liefern.

Fuer Suchtreffer genuegt dev/search2.php unveraendert, URL beliebig (die Erkennung haengt nicht an location, sondern an p.topictitle im Markup): geprueft werden alle 15 Trefferkarten, die verschobene "Searched topic / Search term used"-Zelle samt ihrer beiden Links, Autor und Zeitstempel pro Treffer, die per Regex getrennten Antworten-/Views-Zahlen, der verschobene td.postbody-Beitragsauszug und die Seitennavigation (per_page=15, 96 Seiten). Zu pruefen ist ausserdem, dass dev/search.php (das leere Formular) weiterhin den Formular-Zweig trifft und nicht in die Treffer-Erkennung faellt, und dass dev/viewforum.php weiterhin normale Karten liefert — beide haben kein p.topictitle. Der Modus "Topics" (sr=topics) ist mangels Fixture ungeprueft.

Am Geraet zu pruefen bleibt, was Blink nicht zeigt: env(safe-area-inset-*), fixierte Leisten gegen Safaris ein- und ausfahrende Toolbar, -webkit-backdrop-filter, Schriftskalierung. Details und die Tampermonkey-Einrichtung stehen in der README.

Konventionen

  • ES5-Stil im Script-Code: var, function, keine Arrow Functions, kein let/const. Template-Literals und Set sind in Gebrauch. Keine Transpilation, kein Linter.
  • Kommentare deutsch, Umlaute als ae/oe/ue transliteriert. Echte Umlaute nur in Strings und Regexes, die im UI landen.
  • Kommentare begruenden, warum etwas so gebaut ist (welche DOM-Eigenheit von phpBB dahintersteckt), nicht was der Code tut.
  • CSS-Klassen durchgaengig mit rm- praefixiert.
  • Neue nutzersichtbare Funktionen in der README dokumentieren — zweisprachig, siehe globale Anweisung.