Adding upstream as a second remote was meant to make updates viewable as a diff. The first measurement contradicted what this document has claimed since August: diffing v1.12.17 against the import commit yields 783 files, 115 of which exist only on our side and 62 only in the tag. The telling ones are MVVM files - TextualBodyViewModel.tsx and RovingTabIndex.tsx - absent from the v1.12.17 tag, present in v1.12.26 and present in our import. The changelog stops at 1.12.17 because it is only written at release time. So the base is a develop state after the 1.12.17 release, not the tag. That matters for the plan rather than for the code: grafting onto v1.12.17 would build every future merge on an invented ancestry. Finding the real ancestor means searching develop history, which a shallow tag fetch cannot do. The remote is local configuration, so the setup is written down here instead of committed.
12 KiB
ThreadNet-Web: Fork-Anpassungen für axion1337.chat
Dieses Dokument beschreibt alles, was dieser Fork gegenüber Upstream Element Web ändert. Der
Rest der docs/-Ordner-Dateien und das Root-README.md sind unverändertes Upstream-Material
und beschreiben absichtlich nicht diese Anpassungen - diese Datei ist der zentrale
Anlaufpunkt dafür.
1. Discord-Style Room-List (Call-Teilnehmer in der Raumliste)
Zeigt aktive Call-Teilnehmer direkt in der Raumliste an (ähnlich Discords Voice-Channel-UI),
statt nur einen generischen "Call läuft"-Indikator. Betroffene Komponenten:
apps/web/src/room-list/RoomListItemView/RoomListItemView.tsx und
RoomListItemView.module.css (vertikale Ausrichtung der Teilnehmer-Avatare).
2. Client-seitiges ClamAV-Content-Scanning (Issue #19-Erweiterung)
Der Server-seitige Content-Scanner (Synapse-Modul, siehe gitops-Repo
docs/deployment-guides/06-moderation-content-scanning.md)
sieht bei Ende-zu-Ende-verschlüsselten Räumen nur Ciphertext - eine strukturelle Grenze, kein
Bug. Da dieser Client bereits geforkt wird, scannt er stattdessen selbst, auf beiden Seiten:
- Senden (
apps/web/src/ContentMessages.ts,uploadFile()): liest die Datei als ArrayBuffer, scannt sie überscanContent()bevor verschlüsselt/hochgeladen wird - der Upload wird bei Treffer gar nicht erst gestartet. - Empfangen (
apps/web/src/utils/DecryptFile.ts,decryptFile()): scannt die entschlüsselten Bytes direkt nach dem Entschlüsseln, bevor das Blob an die UI zurückgegeben wird - schützt auch vor Dateien von unveränderten/fremden Matrix-Clients, die diesen Patch nicht haben. - Gemeinsame Scan-Logik:
apps/web/src/utils/ContentScanner.ts- ruft den (im gitops-Repo deployten)clamav-http-scanner-Dienst perfetch("/_scan", ...)auf, mit dem eigenen Matrix-Access-Token als Bearer-Auth. Fail-open bei Netzwerk-/Scanner-Fehlern (blockiert Uploads/Downloads nicht bei einem Ausfall des Scanners). - Neuer Fehlertyp
ContentScanRejectedError, verdrahtet durch die bestehenden Fehler-Rendering-Pfade inMImageBody.tsx,MAudioBody.tsx,VideoBodyViewModel.ts,FileBodyViewModel.ts. - Live getestet inkl. Hostile-Sender-Simulation (Datei per rohem API-Call ohne diesen Patch gesendet, Empfangs-Hook hat trotzdem geblockt).
⚠️ Bekannte Lücke: Electron/Desktop (Issue #2)
Dieser Fix ist bestätigt nur für den Web-Client-Build wirksam. Der Electron-Desktop-Client
bekommt ihn aktuell nicht automatisch - die CI-Workflow-Kette (build_ew-Job) checkt
weiterhin element-hq/element-web (Upstream) statt diesen Fork aus, und es ist kein GitHub-
Actions-Runner für dieses Repo registriert. Der aktuell veröffentlichte Desktop-Build
(desktop-v1.12.17-clientscan Release) wurde manuell gebaut, nicht automatisiert. Details und
Fix-Plan: Issue #2.
3. Build-Fixes (historisch, Issue #12)
Zwei Bugs blockierten einen vollständigen docker build von Grund auf (mussten für den
Client-Scanning-Rebuild oben behoben werden): (1) mehrere Shell-Skripte waren mit Modus 644
statt 755 committet (nicht ausführbar); (2) der matrix-js-sdk#develop-Git-Ref-Pin in
pnpm-lock.yaml war veraltet (fehlte src/oidc/authorize.ts, das apps/web importiert).
Beide behoben.
4. Merge-Reibung: was ein Upstream-Update wirklich kostet
Arbeitspaket 3 aus ThreadNet-Web#7. Gemessen am 2026-08-06, nicht geschätzt.
Die unangenehme Grundlage zuerst
Dieses Repo enthält keine Upstream-Historie. Am 2026-05-10 wurde ein kompletter
Element-Web-Baum importiert — in 3da3635, zusammen mit dem ersten eigenen Feature im
selben Commit. Davor liegt nur ein Initial commit mit zwei Dateien.
⚠️ Korrektur 2026-08-19: Der Import ist NICHT der Tag v1.12.17. Gemessen nach
Aufnahme von upstream als zweitem Remote (management #0099, Schritt 2):
git diff v1.12.17 3da3635ergibt 783 Dateien, +17443/−10371 — weit mehr als unser damaliges Feature.- 115 Dateien existieren nur bei uns, 62 nur im Tag. Darunter MVVM-Dateien wie
apps/web/src/viewmodels/room/timeline/event-tile/body/TextualBodyViewModel.tsxundpackages/shared-components/src/core/roving/RovingTabIndex.tsx: im Tagv1.12.17nicht vorhanden, inv1.12.26und in unserem Import vorhanden. - Der
CHANGELOG.mdunseres Imports endet bei 1.12.17 (2026-04-30) — er wird erst beim Release fortgeschrieben.
Zusammen heißt das: Die Grundlage ist ein develop-Stand nach dem 1.12.17-Release
(1.12.18 erschien am 2026-05-12), nicht der Tag. Praktische Folge: Ein Graft auf
v1.12.17 wäre falsch und würde jede künftige Zusammenführung auf eine erfundene
Ahnenreihe stellen. Wer den gemeinsamen Vorfahren nachträglich herstellen will, muss
den passenden develop-Commit suchen — das braucht die volle Upstream-Historie, ein
flacher Fetch der Tags genügt dafür nicht.
Daraus folgt das Wesentliche: es gibt keinen gemeinsamen Vorfahren mit
element-hq/element-web. Ein git merge upstream/develop ist nicht möglich; mit
--allow-unrelated-histories erzwungen, kollidiert praktisch jede Datei. Wer „mal
eben Upstream nachziehen" sagt, meint in diesem Repo also: neuen Upstream-Stand
beschaffen und unsere Änderungen darauf neu auftragen.
Das ist der Grund, warum die Zahl unten überhaupt zählt — sie ist der Aufwand jedes Updates.
Unser Delta: 102 Dateien, davon 12 kritische
git diff --name-only 3da3635..main:
| Menge | Bereich | Konfliktrisiko |
|---|---|---|
| 46 | CI/Build (.github/, .gitlab-ci.yml, dockerbuild/) |
gering — eigene Strecke, Upstreams Workflows brauchen wir nicht |
| 22 | Config, Lockfiles, Upstream-Varianten | mittel — pnpm-lock.yaml konfliktet immer, wird aber regeneriert |
| 13 | Web-Assets (apps/web/res/) |
gering — meist eigene Dateien |
| 4 + 3 | eigene Icons und apps/desktop/axion1337/ |
keins — kein Upstream-Pendant |
| 12 | Upstream-Quellcode | hier entsteht die Arbeit |
Die 12 Dateien, und warum sie angefasst wurden
Branding (5) — flach, gut isolierbar:
apps/web/src/SdkConfig.ts— Defaults fürbrand,welcome_background_url,desktopBuildsapps/web/src/vector/index.html—<title>, Favicon-Link, PWA-Namenapps/web/src/async-components/structures/ErrorView.tsx— Logo der Fehlerseiteapps/web/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx— Attribution + Danksagungapps/web/src/i18n/strings/en_EN.json— einzelne Strings
ClamAV-Client-Scanning (7) — tief in der Medien-Pipeline:
apps/web/src/ContentMessages.ts,utils/ContentScanner.ts,utils/DecryptFile.tsapps/web/src/components/views/messages/MImageBody.tsx,MAudioBody.tsxapps/web/src/viewmodels/message-body/FileBodyViewModel.ts,VideoBodyViewModel.ts
Nicht der Umfang entscheidet, sondern die Art des Eingriffs
Gemessen in geänderten Zeilen nehmen sich beide Gruppen wenig: ClamAV ~130 Zeilen,
Branding ~104. Der größte Einzelpatch ist sogar Branding
(HelpUserSettingsTab.tsx, 71 Zeilen). Wer nur zählt, hält beide für gleich teuer.
Sie sind es nicht, und der Grund ist die Art des Eingriffs:
- Branding-Patches stehen am Rand. Ein zusätzlicher Default in
SdkConfig.ts, ein<link>im<head>, ein<li>in einer Settings-Liste. Wird die Datei umgebaut, sieht man sofort, wo das eigene Stück wieder hin muss. - ClamAV-Patches stehen mittendrin — in Entschlüsselungs- und Fehlerpfaden der Medien-Pipeline, verschränkt mit Upstream-Logik. Ein geänderter Kontrollfluss bedeutet nicht „Konflikt lösen", sondern „neu verstehen".
⚠️ Und der eigentliche Haken: viewmodels/. Element baut die Medien-Anzeige
gerade auf MVVM um (docs/MVVM.md; v1 ist dort bereits als deprecated markiert —
der Umbau läuft also schon in zweiter Runde). FileBodyViewModel.ts und
VideoBodyViewModel.ts gab es in älteren Ständen gar nicht. Unsere Änderung darin
ist mit je 6 Zeilen winzig — aber wenn Upstream diese Dateien verschiebt, umbenennt
oder auflöst, entsteht kein Konflikt: die Zeilen sind einfach weg, und Git meldet
nichts. Das ist gefährlicher als ein Konflikt, weil es stillschweigend passiert.
Praktische Folge: Nach einem Upstream-Update ist an ClamAV nicht die Merge-Ausgabe maßgeblich, sondern ein Funktionstest — eine verschlüsselte Datei senden und eine abgelehnte empfangen. Steht so auch in Abschnitt 2.
Was daraus für künftige Änderungen folgt
- Erst prüfen, ob es die Konfiguration schon kann. Auth-Logo,
logo_link_urlundbrandliefen ohne Rebuild über die ConfigMap; das Call-Widget wurde vollständig überVITE_PRODUCT_NAMEumbenannt, ohne eine einzige Quelldatei. Jede so vermiedene Datei ist eine, die beim Update nicht kollidiert. - Eigene Datei schlagen geänderte Datei.
apps/desktop/axion1337/und eigene Assets kosten beim Merge nichts. - Wenn Upstream-Code sein muss: einen Kommentar mit
ThreadNet-Fork:und der Begründung dazu. Beim Neuauftragen auf einen neuen Stand ist die Frage nie „was steht hier", sondern „warum stand das da" — und die beantwortet sonst niemand mehr.
Repo-Topologie (seit 2026-07-31)
Kanonisch ist git.lab/axion1337.chat/ThreadNet-Web (Homelab-GitLab, nur im Lab
auflösbar) — dort laufen Entwicklung und CI (.gitlab-ci.yml). Die Kopie auf
rohana.axion1337.de/sorb/ThreadNet-Web ist ein Push-Mirror (automatisch, GitLab →
Gitea) und dient als Lesekopie plus Standort für Issues, Container-Registry und Releases.
Niemals direkt nach rohana pushen — der Mirror überschreibt divergente Stände.
Element Call anheben (@sorb/threadnet-call-embedded)
Das Call-Widget kommt als npm-Paket aus der Gitea-Registry auf rohana, nicht von
npmjs. .npmrc im Wurzelverzeichnis bindet den Scope fest:
@sorb:registry=https://rohana.axion1337.de/api/packages/sorb/npm/
Zum Anheben genügt damit der normale Weg — kein Zusatzschritt, keine Umgebungsvariable:
# Version in apps/web/package.json setzen, dann:
pnpm install --lockfile-only # Lockfile zieht die neue Tarball-URL von rohana
pnpm --dir apps/web build # NICHT --filter web: das endet mit exit 0, ohne zu bauen
Danach muss die Tarball-URL im pnpm-lock.yaml auf rohana.axion1337.de zeigen. Tut sie
das nicht, ist die .npmrc verlorengegangen: ohne sie löst pnpm gegen npmjs auf, und
das geht heute nur deshalb laut aus (404), weil der Name @sorb dort noch frei ist —
verlässt man sich darauf, ist es ein dependency-confusion-Weg. Siehe management#0055.
Vor dem Anheben liegt das Paket erst dann in der Registry, wenn in threadnet-call der
manuelle Job publish_npm gelaufen ist — der ist bewusst manuell und wird von sorb
ausgelöst.
Upstream als zweiter Remote (seit 2026-08-19)
Der Remote ist lokale Konfiguration, er wird nicht mitcommittet. Wer den Vergleich braucht, richtet ihn einmal ein:
git remote add upstream https://github.com/element-hq/element-web.git
git fetch --depth=1 upstream tag v1.12.26 # aktueller Stand
git fetch --depth=1 upstream tag v1.12.17 # zum Vergleich
Damit lässt sich ein Update als Diff betrachten, statt Dateien blind zu kopieren — zum Beispiel für die zwölf Dateien, die wir angefasst haben:
git diff v1.12.26 main -- apps/web/src/... # unser Delta gegen Upstream
git diff v1.12.17 v1.12.26 -- <datei> # was Upstream seither geaendert hat
⚠️ Ein flacher Fetch liefert keine Historie: git merge-base, git log über
Upstream und ein Graft-Versuch brauchen git fetch upstream ohne --depth.