Commit Graph
10 Commits
Author SHA1 Message Date
Thore CimbalandClaude Opus 4.8 e3f8a8570a feat(audio): open the AI noise suppression gate after passed acceptance
Two-person call on 2026-08-17: filter effective, keyboard gone, unmuting intact
on both sides - the acceptance that gates this flag, per the standing rule from
the v0.5.0 incident. The gate flips to true, which brings the checkbox and
slider back into the in-call settings.

The dev override stays in the code as the tool for the next test phase of this
kind; a test pins that it does nothing without the regular setting. The rollback
lever for any regression is the gate itself, 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 df4e5eeb85 feat(audio): attach the AI filter per-track after publication (way B, #0054)
Instead of webAudioMix on the room - which would also rewire playback (sink
selection through the AudioContext, LiveKit's Chrome echo workaround) - only the
local microphone track gets an AudioContext, via setAudioContext() right before
setProcessor(). The attach happens in onLocalTrackPublished, so a failing filter
can no longer prevent unmuting: the track is already published by then.

audioCaptureDefaults now never carry a processor key in any state; the
conditional spread only toggles noiseSuppression. A regression test covers the
active case too.

The gate stays closed. A single test client opts in via two localStorage keys
(ai-noise-suppression-dev plus the regular setting); the regular setting alone
stays inert. Four unit tests pin the attach order - setAudioContext before
setProcessor is exactly what v0.5.0 lacked - and the containment of a failing
attach. 77 tests green across the touched suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 dcc86431dc fix(audio): restore the upstream off-path, gate the AI filter off (v0.5.0 incident)
threadnet.8 broke unmuting in production, both ways. With the filter on, LiveKit
refuses the processor because the room is built without webAudioMix, so no local
track ever carries an AudioContext. With the filter off, the options builder
still emitted processor: undefined and rewrote noiseSuppression - LiveKit copies
every key of audioCaptureDefaults into the getUserMedia constraints, undefined
included, and Safari stopped unmuting over it.

The off-path is now a conditional spread that produces an object identical to
upstream: no processor key at all, noiseSuppression untouched. Two regression
tests pin this down and were demonstrably red on the old code.

The feature itself is hard-gated off (AI_NOISE_SUPPRESSION_AVAILABLE) until the
webAudioMix decision is made and tested in a two-person call. The gate also
neutralizes clients that enabled the setting before - that state lives in
localStorage and survives every deployment. The settings UI hides behind the
same gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 d270e0c706 fix(embedded): ship the model assets in the widget package
Element Call reaches production only as the embedded npm package, which webpack
copies into ThreadNet-Web under /widgets/element-call/. The embedded build sets
publicDir: false — upstream reasons that public/ holds nothing but the favicon,
which stopped being true when the model assets landed there.

Verified rather than assumed: with the upstream value everything builds, the
standalone bundle works, and the filter is dead only inside the widget, 404ing on
the model. That is the failure this would have shipped.

The package grows from about 41 to 66 MB, measured — most of what was already
there is source maps and the crypto and vision wasm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 3f17001720 feat(audio): opt-in AI noise suppression against keyboard noise
The WebRTC noise suppression estimates a running noise profile, so it removes
stationary noise but not keystrokes, which are transient and never look like
noise to it. Chiclet keyboards are as affected as mechanical ones. DeepFilterNet3
runs client-side as a LiveKit track processor and removes them.

Off by default and the model is only fetched once a user switches it on, so
nobody pays the download who does not want the filter. Default strength is 35
percent rather than full: measured, that already gives keystrokes gone with the
voice still natural, and more attenuation only adds artefact risk.

Assets ship with us instead of the package's default CDN, which would report
every participant's IP to a third party at call start and tie call setup to
foreign infrastructure. The Dockerfile gzips the model wasm, which the existing
top-level glob missed — 4.1 MB instead of 15.7 MB per client.

Browser noise suppression is switched off while the filter runs so the two do not
work against each other. Regulation goes through the model's own attenuation
limit, so there is no dry/wet mixer and no delay compensation to get wrong.

Decision and measurements: management ADR-0018 and issue #0054.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore Cimbal d13cce7337 docs: describe the automated publish, not the manual process it replaced
Section 4 still described a manual/local npm publish with an untracked .npmrc - obsolete since publish_npm landed in .gitlab-ci.yml on 2026-08-06. Documents the actual job: manual trigger, GITEA_NPM_TOKEN, the empty-package guard, and the threadnet dist-tag.
2026-08-09 12:00:00 +00:00
Thore Cimbal dc2a041b83 docs: repoint commit references after the history rewrite
The anonymisation rewrite of 2026-08-07 gave every touched commit a new SHA, leaving the references in these documents pointing at objects that no longer exist. The mapping was reconstructed from the backup branches and each pair verified by tree and commit message before substituting.

Prefix lookups were built for lengths 7 to 12 and any ambiguous prefix would have been skipped; none were ambiguous across all 251 pairs.
2026-08-09 12:00:00 +00:00
Thore Cimbal 3f0f55e6bd Doku: Logo-Inventur korrigieren und Verifikation nachtragen
Meine erste Aussage "Logo.svg und LogoLarge.svg sind unreferenziert" war falsch - der Grep hatte icons/ herausgefiltert. Alle vier Logo-SVGs sind importiert und im Bundle; gerendert wird im Widget trotzdem keins, jeweils durch dieselbe HeaderStyle.Standard-Bedingung gesperrt. Das ist der belastbarere Befund, weil er an der Render-Bedingung haengt statt an einem Suchtreffer.

Refs axion1337.chat/threadnet-call#2
2026-08-06 12:00:00 +00:00
Thore Cimbal 0823c8a866 Produktname im Widget: aXion1337.Chat statt Element Call
VITE_PRODUCT_NAME ist upstream bereits an jeder Stelle vorgesehen (import.meta.env.VITE_PRODUCT_NAME || "Element Call"). Sie in .env.production zu setzen benennt das Widget vollstaendig um, ohne eine einzige Quelldatei anzufassen - also ohne Merge-Reibung beim naechsten Upstream-Update.

Die Inventur in docs/axion1337-fork.md haelt fest, was dadurch tatsaechlich umbenannt wird und was im Embedded-Build ohnehin nie erscheint: die Logo-SVGs rendern nur bei HeaderStyle.Standard, dem Nicht-Widget-Default; Startseite und Login gibt es nur standalone. Die deutschen matrixRTCMode-Beschreibungen bleiben bewusst stehen - sie reden ueber fremde Gegenstellen, nicht ueber uns.

Refs axion1337.chat/threadnet-call#2
2026-08-06 12:00:00 +00:00
Thore CimbalandClaude Fable 5 8fb630cfb3 ci: GitLab-Pipeline - build_embedded + manueller npm-Publish nach rohana (threadnet-call#1)
Registry-Entscheidung evidenzbasiert: das Package ist pnpm-Dependency von
ThreadNet-Webs apps/web, der Lockfile pinnt die Tarball-URL auf rohana -
Registry bleibt dort. Publish-Auth ueber CI-Variable GITEA_NPM_TOKEN statt
lokaler Klartext-.npmrc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 12:00:00 +00:00