diff --git a/docs/axion1337-fork.md b/docs/axion1337-fork.md index 2d2576a4..b62e66a6 100644 --- a/docs/axion1337-fork.md +++ b/docs/axion1337-fork.md @@ -145,11 +145,11 @@ Daraus zwei **stehende Regeln** fuer diesen Fork: umgeschriebenes Feld. Umgesetzt als bedingtes Spread in `generateRoomOption` (`ConnectionFactory.ts`), abgesichert durch Regressionstests in `ECConnectionFactory.test.ts` (die auf dem alten Stand nachweislich rot waren). -2. **Feature-Tor:** `AI_NOISE_SUPPRESSION_AVAILABLE` in `src/livekit/aiNoiseSuppression.ts` - steht auf `false`, bis die Track-Anbindung im Call zu zweit abgenommen ist. - Das Tor neutralisiert auch Clients, die die Einstellung bereits aktiviert hatten - (localStorage) - deren Zustand ueberlebt jedes Deployment. Die Bedienelemente in - `SettingsModal.tsx` sind hinter demselben Tor versteckt. +2. **Feature-Tor:** `AI_NOISE_SUPPRESSION_AVAILABLE` in `src/livekit/aiNoiseSuppression.ts`. + Seit der bestandenen Abnahme (2026-08-17, Call zu zweit: Filter wirksam, Tastatur weg, + Entmuten intakt) steht es auf `true`. **Bei einer Regression zuerst das Tor schliessen** + (`false`) - das legt Filter UND Bedienelemente still, neutralisiert auch Clients mit + aktivierter Einstellung im localStorage, und braucht kein Zurueckrollen der Auslieferung. ### Weg B (Entscheidung sorb 2026-08-16): AudioContext nur auf dem Mikrofon-Track @@ -167,11 +167,13 @@ Die `audioCaptureDefaults` tragen damit in **keinem** Zustand mehr einen noch `noiseSuppression` umgeschaltet, damit Browser- und KI-Filter nicht gegeneinander arbeiten. -**Testausrollung:** Das Tor bleibt zu. Ein einzelner Test-Client aktiviert den Filter -ueber ZWEI localStorage-Schluessel: `matrix-setting-ai-noise-suppression-dev = true` -(Entwickler-Schalter, bewusst ohne UI) **und** `matrix-setting-ai-noise-suppression = -true`. Die normale Einstellung allein bleibt wirkungslos. Erst nach bestandener -Abnahme im Call zu zweit wird das Tor geoeffnet und die UI wieder sichtbar. +**Testausrollung (durchlaufen, 2026-08-17 bestanden):** Waehrend das Tor zu war, +aktivierte ein einzelner Test-Client den Filter ueber ZWEI localStorage-Schluessel: +`matrix-setting-ai-noise-suppression-dev = true` (Entwickler-Schalter, bewusst ohne UI) +**und** `matrix-setting-ai-noise-suppression = true`. Der Entwickler-Schalter bleibt im +Code (harmlos: ohne die eigentliche Einstellung wirkungslos, per Test abgesichert) - +er ist das Werkzeug fuer die naechste Testphase dieser Art. Die Bedienelemente sitzen +in den Einstellungen **im laufenden Call-Widget** (Audio-Reiter), nicht in Element Web. Einstellungs-Aenderungen wirken ab dem naechsten Beitritt, nicht live im Call. ## 6. Produktname im Widget (`.env.production`) diff --git a/embedded/web/package.json b/embedded/web/package.json index 416b0691..c34219b6 100644 --- a/embedded/web/package.json +++ b/embedded/web/package.json @@ -1,6 +1,6 @@ { "name": "@sorb/threadnet-call-embedded", - "version": "0.19.2-threadnet.10", + "version": "0.19.2-threadnet.11", "files": [ "README.md", "LICENSE-AGPL-3.0", diff --git a/src/livekit/aiNoiseSuppression.test.ts b/src/livekit/aiNoiseSuppression.test.ts index e89e770e..e41e5ba4 100644 --- a/src/livekit/aiNoiseSuppression.test.ts +++ b/src/livekit/aiNoiseSuppression.test.ts @@ -66,9 +66,11 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => { expect(calls).toEqual([]); }); - test("a stale enabled setting without the dev override touches nothing", async () => { - // sorbs Zustand nach dem Diagnosefenster: Einstellung an, Tor zu. - aiNoiseSuppressionSetting.setValue(true); + test("the dev override alone (setting off) touches nothing", async () => { + // UND-Verknuepfung im Praedikat: der Schalter der Testphase darf ohne die + // eigentliche Einstellung nichts bewirken. + aiNoiseSuppressionDevSetting.setValue(true); + aiNoiseSuppressionSetting.setValue(false); const { track, calls } = makeTrack(); await applyAiNoiseSuppression(track, logger); @@ -79,7 +81,6 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => { test("sets the audio context BEFORE attaching the processor", async () => { // Genau die Reihenfolge, an der v0.5.0 gescheitert ist: setProcessor // ohne AudioContext auf dem Track wirft in LiveKit. - aiNoiseSuppressionDevSetting.setValue(true); aiNoiseSuppressionSetting.setValue(true); const { track, calls } = makeTrack(); @@ -91,7 +92,6 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => { test("a failing processor attach is contained and does not throw", async () => { // Der Track ist zu diesem Zeitpunkt publiziert - ein Filterfehler darf // den Call nicht mehr erreichen. - aiNoiseSuppressionDevSetting.setValue(true); aiNoiseSuppressionSetting.setValue(true); const { track, setProcessor } = makeTrack(); setProcessor.mockRejectedValue(new Error("wasm sagt nein")); diff --git a/src/livekit/aiNoiseSuppression.ts b/src/livekit/aiNoiseSuppression.ts index 46acddfe..32888417 100644 --- a/src/livekit/aiNoiseSuppression.ts +++ b/src/livekit/aiNoiseSuppression.ts @@ -39,19 +39,20 @@ import { const ASSET_PFAD = "assets/dfn3"; /** - * Feature-Tor: solange `false`, ist der Filter fuer ALLE Clients aus - auch - * fuer solche, die die Einstellung frueher aktiviert haben (localStorage). + * Feature-Tor: bei `false` ist der Filter fuer ALLE Clients aus - auch fuer + * solche, die die Einstellung frueher aktiviert haben (localStorage). * - * Warum: `LocalAudioTrack.setProcessor()` verlangt einen AudioContext auf dem - * Track. Den bekommt er nur, wenn der LiveKit-Raum mit `webAudioMix` gebaut - * wird - und genau das steht in `ConnectionFactory.ts` als unerprobtes - * Upstream-TODO auskommentiert. Ohne AudioContext wirft LiveKit beim Entmuten - * "Audio context needs to be set on LocalAudioTrack in order to enable - * processors", und es wird nie ein Audio-Track publiziert (Produktionsvorfall - * 2026-08-16, v0.5.0). Erst freischalten, wenn `webAudioMix` entschieden und - * im Call zu zweit getestet ist (Folgearbeit zu ADR-0018). + * Geschichte: v0.5.0 setzte den Prozessor ueber die Capture-Defaults, ohne + * dass der Track einen AudioContext hatte - LiveKit warf "Audio context needs + * to be set on LocalAudioTrack in order to enable processors", und das + * Entmuten brach fuer alle (Produktionsvorfall 2026-08-16). Seit Weg B + * (#0054) haengt sich der Filter NACH der Publikation an den Mikrofon-Track, + * mit eigenem AudioContext nur dort. Abnahme im Call zu zweit bestanden am + * 2026-08-17 (Filter wirksam, Tastatur weg, Entmuten intakt) - seitdem ist + * das Tor offen. Bei einer Regression zuerst wieder auf `false` stellen: + * das legt den Filter still, ohne die Auslieferung anzufassen. */ -export const AI_NOISE_SUPPRESSION_AVAILABLE = false; +export const AI_NOISE_SUPPRESSION_AVAILABLE = true; /** * Ist der Filter wirksam? Nur wenn das Tor offen ist (oder der diff --git a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts index b6302f83..193fa904 100644 --- a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts +++ b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts @@ -149,27 +149,11 @@ describe("ECConnectionFactory - AI noise suppression off-path (ADR-0018)", () => }); }); - test("a stale enabled setting (localStorage) is neutralized by the feature gate", () => { - // Genau der Zustand eines Clients, der den Filter aktiviert hatte, bevor - // das Tor geschlossen wurde: Einstellung an, Tor zu. - aiNoiseSuppressionSetting.setValue(true); - noiseSuppressionSetting.setValue(true); - - const options = buildRoomOptions(); - expect(options.audioCaptureDefaults).not.toHaveProperty("processor"); - expect(options.audioCaptureDefaults).toMatchObject({ - noiseSuppression: true, - }); - - aiNoiseSuppressionSetting.setValue(false); - }); - - test("even with the filter active (dev override) the defaults carry no processor key", () => { + test("with the filter active the defaults carry no processor key either (way B)", () => { // Weg B aus #0054: der Prozessor wird NACH der Publikation an den Track // gehaengt, nie ueber die Capture-Defaults - die getUserMedia-Constraints // muessen in JEDEM Zustand upstream-identisch bleiben. Hier aendert sich // nur noiseSuppression, damit nicht zwei Filter gegeneinander arbeiten. - aiNoiseSuppressionDevSetting.setValue(true); aiNoiseSuppressionSetting.setValue(true); noiseSuppressionSetting.setValue(true); @@ -179,9 +163,24 @@ describe("ECConnectionFactory - AI noise suppression off-path (ADR-0018)", () => noiseSuppression: false, }); - aiNoiseSuppressionDevSetting.setValue(false); aiNoiseSuppressionSetting.setValue(false); }); + + test("the dev override alone (setting off) changes nothing", () => { + // Das Praedikat ist eine UND-Verknuepfung: der Entwickler-Schalter der + // Testphase darf ohne die eigentliche Einstellung nichts bewirken. + aiNoiseSuppressionDevSetting.setValue(true); + aiNoiseSuppressionSetting.setValue(false); + noiseSuppressionSetting.setValue(true); + + const options = buildRoomOptions(); + expect(options.audioCaptureDefaults).not.toHaveProperty("processor"); + expect(options.audioCaptureDefaults).toMatchObject({ + noiseSuppression: true, + }); + + aiNoiseSuppressionDevSetting.setValue(false); + }); }); describe("ECConnectionFactory - ControlledAudioDevice", () => {