11 Commits
Author SHA1 Message Date
Thore CimbalandClaude Opus 4.8 6cdb1858a2 build(web): pull in threadnet-call-embedded 0.19.2-threadnet.12
Sender verification for the AI filter: after attaching, the fork now checks
that the RTCRtpSender actually carries the processed track and enforces the
swap if LiveKit skipped it silently - the Safari field finding where level
0-100 made no audible difference. The log line states the outcome.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 7e6b08774b build(web): pull in threadnet-call-embedded 0.19.2-threadnet.11
Opens the AI noise suppression gate after the passed two-person acceptance:
checkbox and slider return to the in-call audio settings, clients with the
setting enabled get the filter again. The rollback lever for any regression is
the gate in threadnet-call, not a deployment revert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 51b6691caa build(web): pull in threadnet-call-embedded 0.19.2-threadnet.10
Way B from #0054: the AI filter attaches to the local microphone track after
publication, with its own AudioContext on just that track - no webAudioMix, no
processor key in the capture defaults in any state. The feature gate stays
closed; a single test client opts in via two localStorage keys. For everyone
else this build behaves identically to .9.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 45dc53155b build(web): pull in threadnet-call-embedded 0.19.2-threadnet.9
The .8 widget broke unmuting in production (both with the AI filter on and off);
threadnet-call dcc8643 restores the upstream off-path and hard-gates the feature
until the webAudioMix decision. Verified locally end to end: the installed
package carries the model assets, webpack lands them under
webapp/widgets/element-call/assets/dfn3, and the widget index.html references
the .9 bundle (0.19.2-threadnet.9+dcc86431).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 7abe9023f9 build(web): pull in threadnet-call-embedded 0.19.2-threadnet.8
Brings the AI noise suppression (ADR-0018) into the widget we actually ship.
The .7 package built and ran fine but carried no model assets, because the
embedded Vite config had publicDir off — the filter would have been dead only
inside the widget, and nowhere else.

Verified the whole chain locally rather than trusting the green build: the
23 MB of model and wasm arrive under webapp/widgets/element-call/assets/dfn3,
which is the path the widget requests at runtime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 e7b00288ed docs: add group-rules pointer (CLAUDE.md) and repo-specific AGENTS.md
Field test F-011 found four of five components carried no pointer file, so a
session landing here had no path to the group rules at all. CLAUDE.md is the
one-line pointer the check looks for; AGENTS.md links the canonical rules in the
management repo (with the Gitea mirror URL for readers outside the lab) and
otherwise carries only what is specific and easy to get wrong here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore Cimbal d3bd5b20c2 feat(desktop): give the desktop client a way to obtain its logs
The desktop variant had no bug_report_endpoint_url at all, so the whole section stayed hidden and a desktop user could not produce logs for a report. Setting local shows a Download logs button that writes them to the device and uploads nothing.

Matches what the web client now serves. Takes effect with the next desktop build.

Refs axion1337.chat/ThreadNet-Web#9
2026-08-09 12:00:00 +00:00
Thore Cimbal 3cb43f5149 ci: refuse to overwrite an already published release image
Moving a tag retriggers docker_web, which would rebuild a published release from old source against today's base images and dependencies and push it over the existing one. On 2026-08-09 that nearly happened to v0.4.0 while the rewritten tags were force-pushed; it was averted only because the protected registry variables were unavailable in that window, so docker login failed first.

The job now aborts if the target tag already exists in the registry. latest-ci stays overwritable on purpose - it names the newest state, not a version, and carries no promise.

Closes axion1337.chat/ThreadNet-Web#14
2026-08-09 12:00:00 +00:00
Thore Cimbal 26b6fd8938 ci: do not create a pipeline for docs-only commits
A commit touching only docs/ produced a pipeline with zero jobs, which GitLab marks as failed - pipelines 203 and 204 on 2026-08-06 were both red for nothing. Cause is the needs chain: web is skipped by its changes rule, and desktop_linux/desktop_windows depend on it, which collapses the whole pipeline including the manual jobs.

This matters beyond cosmetics: gitops/CLAUDE.md makes the red pipeline the alarm for the TURN rotation, stating there is no separate reminder. Red that means nothing trains people to stop looking.

The path list is a YAML anchor shared with the web job - two copies would drift. CI_PIPELINE_SOURCE == web stays allowed so the manual maintenance jobs remain reachable via Run pipeline.
2026-08-07 12:00:00 +00:00
Thore Cimbal 965c456dee Doku: Merge-Risiko praeziser fassen - Art des Eingriffs, nicht Umfang
Meine Aussage "ClamAV kostet mehr als das gesamte Branding zusammen" war so nicht haltbar: gemessen sind es ~130 zu ~104 Zeilen, der groesste Einzelpatch ist sogar Branding (HelpUserSettingsTab.tsx, 71 Zeilen).

Der Unterschied liegt woanders: Branding-Patches stehen am Rand und sind nach einem Umbau wiederfindbar, ClamAV-Patches stehen mitten in Entschluesselungs- und Fehlerpfaden. Dazu der eigentliche Haken - verschiebt Upstream die viewmodels/-Dateien, entsteht kein Konflikt, die Zeilen sind schlicht weg und Git meldet nichts. Deshalb ist dort ein Funktionstest maszgeblich, nicht die Merge-Ausgabe.

Refs axion1337.chat/ThreadNet-Web#7
2026-08-06 12:00:00 +00:00
Thore Cimbal 9ac56d671b Doku: Merge-Reibung gemessen (Arbeitspaket 3 aus #7)
Der wichtigste Befund ist die Grundlage, nicht die Liste: das Repo enthaelt KEINE Upstream-Historie. Element Web 1.12.17 kam am 2026-05-10 als kompletter Baum herein, im selben Commit wie das erste eigene Feature. Es gibt also keinen gemeinsamen Vorfahren mit element-hq/element-web - ein git merge ist nicht moeglich, ein Update heisst neu auftragen.

Gemessen statt geschaetzt: 102 geaenderte Dateien, davon nur 12 Upstream-Quellcode. Fuenf davon Branding (flach, isolierbar), sieben ClamAV in der Medien-Pipeline - und genau dort baut Upstream gerade auf MVVM um. Das Update wird an ClamAV mehr kosten als am gesamten Branding.

Refs axion1337.chat/ThreadNet-Web#7
2026-08-06 12:00:00 +00:00
7 changed files with 189 additions and 14 deletions
+60 -8
View File
@@ -7,6 +7,41 @@
# - webpack braucht ~4 GB Heap -> NODE_OPTIONS
# - Desktop-Build laeuft im dockerbuild-Image (rust:bullseye + node, glibc-2.31-Ziel)
# --- Wann ueberhaupt eine Pipeline entsteht ------------------------------------
# Ohne diesen Block erzeugt ein Commit, der nur docs/ anfasst, eine Pipeline mit
# NULL Jobs - und die zaehlt in GitLab als "failed". Real passiert am 2026-08-06
# (Pipelines 203 und 204): zwei Doku-Commits, zweimal rot, nichts kaputt.
#
# Der Grund liegt an der needs-Kette: web laeuft wegen changes: nicht, und
# desktop_linux/desktop_windows haengen per needs daran - damit faellt die ganze
# Pipeline in sich zusammen, auch die manuellen Jobs ohne needs.
#
# ⚠️ Warum das keine Kosmetik ist: In gitops/CLAUDE.md ist die rote Pipeline die
# ALARMANLAGE fuer die TURN-Rotation ("es gibt keinen separaten Reminder"). Rot,
# das nichts bedeutet, gewoehnt einem das Hinsehen ab.
#
# Preis: Nach einem reinen Doku-Commit entsteht keine Pipeline von selbst. Damit
# die manuellen Wartungsjobs (desktop_image, windows_provision) trotzdem
# erreichbar bleiben, laesst die Regel CI_PIPELINE_SOURCE == "web" durch - eine
# ueber "Run pipeline" in der Oberflaeche gestartete Pipeline entsteht immer.
.pfade_mit_pipeline: &pfade_mit_pipeline
- apps/**/*
- packages/**/*
- patches/**/*
- scripts/**/*
- pnpm-lock.yaml
- pnpm-workspace.yaml
- .gitlab-ci.yml
workflow:
rules:
- if: $CI_COMMIT_TAG =~ /^v/
- if: $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_PIPELINE_SOURCE == "web"
- if: $CI_COMMIT_BRANCH
changes: *pfade_mit_pipeline
- when: never
stages:
- build
- package
@@ -82,14 +117,8 @@ web:
when: never
# Pushes nur, wenn build-relevante Pfade betroffen sind - Doku-Commits kosten nichts
- if: $CI_COMMIT_BRANCH
changes:
- apps/**/*
- packages/**/*
- patches/**/*
- scripts/**/*
- pnpm-lock.yaml
- pnpm-workspace.yaml
- .gitlab-ci.yml
# derselbe Anker wie im workflow-Block oben - zwei Listen wuerden driften
changes: *pfade_mit_pipeline
variables:
NODE_OPTIONS: "--max-old-space-size=6144"
CI_PACKAGE: "true"
@@ -123,8 +152,31 @@ docker_web:
DOCKER_BUILDKIT: "1"
script:
- echo "$REGISTRY_PASSWORD" | docker login rohana.axion1337.de -u "$REGISTRY_USER" --password-stdin
# Ein veroeffentlichtes Release darf sich nicht rueckwirkend aendern.
#
# Am 2026-08-09 hat das Force-Push umgezogener Tags (History-Anonymisierung)
# drei Release-Pipelines neu gestartet - darunter v0.4.0. Ohne diese Sperre
# haette der Lauf das laengst veroeffentlichte Image aus altem Code mit
# heutigen Basis-Images und Abhaengigkeiten neu gebaut und ueberschrieben.
# Dass es damals nicht passierte, lag nur daran, dass die geschuetzten
# Registry-Variablen in dem Moment nicht verfuegbar waren - Glueck, nicht
# Absicht (ThreadNet-Web#14).
#
# Ein Tag ist hier kein Verwaltungseintrag, sondern ein Build-Ausloeser. Wer
# einen Tag verschiebt, loest einen Build aus, ob er will oder nicht. Deshalb
# Unveraenderlichkeit erzwingen statt auf Vorsicht zu hoffen.
- |
if docker manifest inspect "$IMAGE:$CI_COMMIT_TAG" >/dev/null 2>&1; then
echo "FEHLER: $IMAGE:$CI_COMMIT_TAG existiert in der Registry bereits."
echo "Ein veroeffentlichtes Release wird nicht ueberschrieben."
echo "Gewollt? Dann das Image bewusst aus der Registry entfernen oder eine"
echo "neue Version taggen - siehe ThreadNet-Web#14."
exit 1
fi
- docker build -f apps/web/Dockerfile -t "$IMAGE:$CI_COMMIT_TAG" -t "$IMAGE:latest-ci" .
- docker push "$IMAGE:$CI_COMMIT_TAG"
# latest-ci bleibt bewusst ueberschreibbar - der Tag bezeichnet "der neueste
# Stand", nicht eine Version, und traegt deshalb kein Versprechen.
- docker push "$IMAGE:latest-ci"
# Einmalig/selten: Build-Image fuer den Desktop-Build (rust:bullseye + node + tcl/sqlcipher,
+33
View File
@@ -0,0 +1,33 @@
# AGENTS.md — ThreadNet-Web
> **Die Gruppenregeln sind kanonisch im `management`-Repo:**
> [`AGENTS.md`](https://git.lab/axion1337.chat/management/-/blob/main/AGENTS.md)
> — von außerhalb des Labs über den Gitea-Mirror lesbar:
> `https://rohana.axion1337.de/sorb/management`. Dort stehen Repo-Topologie und
> Mirror-Regeln, das Kanban-Framework (Status-Labels, WIP-Limit 2, ADR-Pflicht),
> Deploy-Übergabe und AAR-Verfahren, Secrets-Handhabung und die
> Karpathy-Leitlinien. Sie gelten für **jede** Session in diesem Repo.
> Hier steht nur, was zusätzlich für dieses Repository gilt.
## Was dieses Repo ist
Fork von **Element Web/Desktop** (Basis `1.12.17`) für axion1337.chat.
⚠️ **`README.md` und der Großteil von `docs/` sind unverändertes Upstream-Material** und
beschreiben diesen Fork ausdrücklich **nicht**. Wer sich danach richtet, übersieht jede
Anpassung.
## Der eine Ort, der zählt
**[`docs/axion1337-fork.md`](docs/axion1337-fork.md)** ist die vollständige Liste dessen, was
dieser Fork gegenüber Upstream ändert — und zugleich die **Portier-Checkliste** für jedes
Upstream-Upgrade: dort stehen die kritischen Dateien, an denen die Merge-Reibung entsteht.
**Jede Änderung am Fork gehört dort hinein.** Eine Anpassung, die nur im Code steht, geht beim
nächsten Rebase verloren — genau dafür existiert die Datei.
## Bauen & Ausrollen
Image wird in die Registry auf `rohana.axion1337.de` publiziert; die laufende Version zieht der
Cluster über den Tag im gitops-Repo (`apps/production/`). Ein Upgrade ist deshalb zweistufig:
erst hier rebasen und bauen, dann dort den Tag anheben.
+1
View File
@@ -0,0 +1 @@
Read AGENTS.md — the canonical instruction file for this repository. All rules live there.
+1
View File
@@ -1,5 +1,6 @@
{
"brand": "aXion1337.Chat",
"bug_report_endpoint_url": "local",
"branding": {
"auth_header_logo_url": "vector-icons/512.png",
"logo_link_url": "https://axion1337.chat"
+1 -1
View File
@@ -125,7 +125,7 @@
"@babel/preset-react": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@casualbot/jest-sonar-reporter": "2.7.0",
"@sorb/threadnet-call-embedded": "0.19.2-threadnet.7",
"@sorb/threadnet-call-embedded": "0.19.2-threadnet.12",
"@element-hq/element-web-playwright-common": "workspace:*",
"@fetch-mock/jest": "^0.2.20",
"@jest/globals": "^30.2.0",
+88
View File
@@ -53,6 +53,94 @@ statt 755 committet (nicht ausführbar); (2) der `matrix-js-sdk#develop`-Git-Ref
`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.** Element Web 1.12.17 wurde am
2026-05-10 als kompletter Baum importiert — und zwar in `3da3635`, zusammen mit dem
ersten eigenen Feature im selben Commit. Davor liegt nur ein `Initial commit` mit
zwei Dateien.
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ür `brand`, `welcome_background_url`, `desktopBuilds`
- `apps/web/src/vector/index.html``<title>`, Favicon-Link, PWA-Namen
- `apps/web/src/async-components/structures/ErrorView.tsx` — Logo der Fehlerseite
- `apps/web/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx` — Attribution + Danksagung
- `apps/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.ts`
- `apps/web/src/components/views/messages/MImageBody.tsx`, `MAudioBody.tsx`
- `apps/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
1. **Erst prüfen, ob es die Konfiguration schon kann.** Auth-Logo, `logo_link_url`
und `brand` liefen ohne Rebuild über die ConfigMap; das Call-Widget wurde
vollständig über `VITE_PRODUCT_NAME` umbenannt, ohne eine einzige Quelldatei.
Jede so vermiedene Datei ist eine, die beim Update nicht kollidiert.
2. **Eigene Datei schlagen geänderte Datei.** `apps/desktop/axion1337/` und eigene
Assets kosten beim Merge nichts.
3. **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
+5 -5
View File
@@ -617,8 +617,8 @@ importers:
specifier: ^5.0.0
version: 5.2.1(encoding@0.1.13)(webpack@5.106.2)
'@sorb/threadnet-call-embedded':
specifier: 0.19.2-threadnet.7
version: 0.19.2-threadnet.7
specifier: 0.19.2-threadnet.12
version: 0.19.2-threadnet.12
'@stylistic/eslint-plugin':
specifier: ^5.0.0
version: 5.10.0(eslint@8.57.1)
@@ -5003,8 +5003,8 @@ packages:
'@sinonjs/fake-timers@15.1.1':
resolution: {integrity: sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==}
'@sorb/threadnet-call-embedded@0.19.2-threadnet.7':
resolution: {integrity: sha512-2dBMVdlI7U8nlVCkOdOo/jfkVAZ6CNLUYWCzFnE/NDT+D9W2APeuxqFOGtUVRPwrV4JxST2Jd491QLiV8bZItA==, tarball: https://rohana.axion1337.de/api/packages/sorb/npm/%40sorb%2Fthreadnet-call-embedded/-/0.19.2-threadnet.7/threadnet-call-embedded-0.19.2-threadnet.7.tgz}
'@sorb/threadnet-call-embedded@0.19.2-threadnet.12':
resolution: {integrity: sha512-0x9nzkzGXFFVsXXI3LvLdWNXmjzJFsijh29yVXwrtFv/FQXgzwY8BttyMCCFHnEkp4VXtu7KeQfRCEzJB7rdIQ==, tarball: https://rohana.axion1337.de/api/packages/sorb/npm/%40sorb%2Fthreadnet-call-embedded/-/0.19.2-threadnet.12/threadnet-call-embedded-0.19.2-threadnet.12.tgz}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -17632,7 +17632,7 @@ snapshots:
dependencies:
'@sinonjs/commons': 3.0.1
'@sorb/threadnet-call-embedded@0.19.2-threadnet.7': {}
'@sorb/threadnet-call-embedded@0.19.2-threadnet.12': {}
'@standard-schema/spec@1.1.0': {}