Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
484712c
Add QUIC transport preference with TCP fallback
dwd Apr 4, 2026
8937e25
Enable QUIC builds on Linux/Windows/Android
dwd Apr 4, 2026
8793fbd
QUIC working with IPv6/IPv4
dwd Apr 4, 2026
3da05e6
Implement QUIC multi-stream routing after bind
dwd Apr 4, 2026
5f26d64
Fix MissingPluginException for flutter_foreground_task background iso…
dwd Apr 22, 2026
5f58e12
Fix verbose logging in release builds and notification ID stability
dwd Apr 22, 2026
b8ab1c3
Add copyWith to ChatMessage and replace all manual copy sites
dwd Apr 22, 2026
e387a31
Introduce PreferencesService and centralise all SharedPreferences access
dwd Apr 22, 2026
2bdd324
Codebase cleanup: Steps 1–4
dwd Apr 22, 2026
17a8882
Codebase cleanup: Step 5 — update doap.xml
dwd Apr 22, 2026
1699ded
Add copyWith and notification ID tests to chat_message_test.dart
dwd Apr 22, 2026
702ffe1
Add edge-case tests to chat_message_mutations_test.dart
dwd Apr 22, 2026
8e03549
Add edge-case tests to mam_merge_engine_test.dart
dwd Apr 22, 2026
d4b242f
Fix DroppableDisposedException race in QuicCapableXmppSocket (Fixes W…
dwd Apr 22, 2026
9ada666
Log aux channel open and close events in QuicCapableXmppSocket
dwd Apr 22, 2026
0b10ad3
Fix chunked XML buffering in xmpp_stone Connection.dart
dwd Apr 22, 2026
ac2d0d3
Fix DroppableDisposedException in close() race with connectionOpenBi …
dwd Apr 22, 2026
84db1e2
Disable XEP-0198 stream management over QUIC transport
dwd Apr 22, 2026
9d67ed3
Fix DroppableDisposedException race in concurrent aux stream opens (W…
dwd Apr 22, 2026
6ebce40
Add pre-open logging with reason to aux stream lifecycle in QuicCapab…
dwd Apr 22, 2026
ae95542
Fix DroppableDisposedException: serialise all connectionOpenBi calls …
dwd Apr 22, 2026
9972366
Open aux QUIC streams concurrently, unwaited, after bind
dwd Apr 23, 2026
b9bd2d1
Add QUIC enable/disable toggle in login advanced options
dwd Apr 23, 2026
9b7ad0a
Fix post-bind stall on QUIC: emit DONE from SM negotiate() when QUIC
dwd Apr 23, 2026
ed72163
Fix QUIC hang after resource binding by correcting SM negotiator isRe…
dwd Apr 23, 2026
9960053
Log outbound XMPP stanzas at actual send time, with channel label
dwd Apr 23, 2026
0987dca
Stop QUIC write queue stalling on unopened aux streams
dwd Apr 23, 2026
6093978
Diagnose QUIC aux-stream open hang with progress logs and timeout
dwd Apr 23, 2026
9eb2191
Fix fatal unhandled errors from unawaited aux stream open failures
dwd Apr 24, 2026
0e5fb8d
Track QUIC bidi stream credits; stop aux opens when credit-starved
dwd Apr 24, 2026
572ff35
Log STREAM frame counts and pre-open stats in QUIC diagnostics
dwd Apr 24, 2026
e0fb927
Fix DroppableDisposedException: await connectionStats before connecti…
dwd Apr 24, 2026
be706b7
Await _logConnectionStats after QUIC connect to prevent stale connect…
dwd Apr 24, 2026
9674b2b
Expose peer QUIC transport parameters via vendored quinn patch
dwd Apr 24, 2026
71fb80b
Add qlog output for QUIC connections
dwd Apr 24, 2026
ad2c9cd
QUIC: advertise max_idle_timeout=600s per XEP-0467
dwd Apr 29, 2026
df20fa6
QUIC: route only stanzas onto aux streams (XEP-0467 D3)
dwd Apr 29, 2026
1cacfef
QUIC: open aux streams lazily on demand instead of pre-opening (XEP-0…
dwd Apr 29, 2026
dad28a1
QUIC: document XEP-0467 §Stream Management carve-out (D2)
dwd Apr 29, 2026
d5105e3
Log inbound XMPP data with channel label at transport layer
dwd Apr 29, 2026
2d153be
QUIC: log who closed the connection and why at control stream end
dwd Apr 29, 2026
1f3db8d
QUIC: set keep_alive_interval=240s to prevent idle timeout disconnects
dwd Apr 29, 2026
894e37c
QUIC: disable XMPP-level keepalive ping timer on QUIC connections
dwd Apr 29, 2026
6dd2f58
Fix aux-stream stanzas not displayed: give each QUIC stream its own X…
dwd Apr 30, 2026
1af4822
Add tests for makeStreamResponseMapper (per-QUIC-stream XML buffer is…
dwd Apr 30, 2026
dcf0f06
CI: add Rust toolchain install to macOS and iOS build jobs
dwd Apr 30, 2026
e34b7a0
Surface QUIC RTT and packet loss stats in main banner
dwd Apr 30, 2026
fcb49f9
Fix apply-netem.sh and clear-netem.sh network impairment scripts
dwd Apr 30, 2026
49c918d
Fix apply-netem.sh to only impair UDP/5224 QUIC traffic
dwd Apr 30, 2026
ae33cc4
Fix IPv6 filter in apply-netem.sh to use flower classifier
dwd Apr 30, 2026
a02331a
Fix apply-netem.sh: add pfifo_fast leaf qdiscs to bypass classes
dwd Apr 30, 2026
77ebf97
Manual! Attempts at TC
dwd May 1, 2026
6bf949b
apply-shape.sh: shape only XMPP/QUIC + ICMP echo to/from host
dwd May 1, 2026
4b0ab8e
QUIC: parallel Happy Eyeballs with retries and longer timeouts
dwd May 1, 2026
e88b98c
Add Plain TCP toggle and make transport flags filter SRV records
dwd May 1, 2026
02e259b
Fix stanza starvation under high latency: greedy XML element extraction
dwd May 1, 2026
de4d424
R4.1: cache-guard vCard fetches to avoid the connect-time storm
dwd May 1, 2026
9bbc922
R3.3: persist a negative cache for JIDs with no PEP user avatar
dwd May 1, 2026
9ffc96e
R1.1: skip the MDS bootstrap IQ when the cache was seeded from disk
dwd May 1, 2026
73d107a
R1.2: regression test for the MDS +notify disco feature
dwd May 1, 2026
902a61f
R1.3: persist unresolved MDS markers and resolve them on later messages
dwd May 1, 2026
a05cce8
R2.2: skip per-chat MAM catch-up when MDS already proves we are up to…
dwd May 1, 2026
4108ac5
R5: persist XEP-0115 entity-caps cache across restarts
dwd May 1, 2026
7db2f18
R2.1: persist a global last_mam_id_seen anchor
dwd May 1, 2026
7b6a350
R3.1: GC unreferenced PEP avatar blobs
dwd May 1, 2026
f4a8ab8
doc: add 2026-05-01 15:34 log review (R6 re-seed bug identified)
dwd May 1, 2026
f1c9af2
R6: re-seed vCard caches from disk on every Ready event
dwd May 1, 2026
b539996
R2.1: replace per-chat DM MAM fan-out with a single unified server-ar…
dwd May 1, 2026
59fb484
Accept server-initiated QUIC streams (XEP-0467 §Multiple Streams)
dwd May 1, 2026
2d77da8
Fix DroppableDisposedException and queue stanzas per bare-JID aux stream
dwd May 1, 2026
fed14aa
Reuse server-initiated QUIC streams before opening new client streams
dwd May 1, 2026
8936eda
Fix server-stream recv loop: start immediately on accept, not on pool…
dwd May 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Empty file added .ai/mcp/mcp.json
Empty file.
24 changes: 23 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,15 @@ jobs:
- name: Install Linux build deps
run: |
sudo apt-get update
sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libsecret-1-dev libcurl4-openssl-dev
sudo apt-get install -y curl clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libsecret-1-dev libcurl4-openssl-dev
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu
- name: Set Build Version
shell: bash
run: |
Expand Down Expand Up @@ -234,6 +238,10 @@ jobs:
with:
channel: stable
cache: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-msvc
- name: Set Build Version
shell: bash
run: |
Expand Down Expand Up @@ -270,6 +278,10 @@ jobs:
with:
channel: stable
cache: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-apple-darwin,aarch64-apple-darwin
- name: Set Build Version
shell: bash
run: |
Expand Down Expand Up @@ -306,6 +318,10 @@ jobs:
with:
channel: stable
cache: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios
- name: Set Build Version
shell: bash
run: |
Expand Down Expand Up @@ -342,6 +358,12 @@ jobs:
with:
channel: stable
cache: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
- name: Install cargo-ndk
run: cargo install cargo-ndk --locked
- name: Set Build Version
shell: bash
run: |
Expand Down
16 changes: 15 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ jobs:
- name: Install Linux build deps
run: |
sudo apt-get update
sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libsecret-1-dev libcurl4-openssl-dev dpkg-dev
sudo apt-get install -y curl clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libsecret-1-dev libcurl4-openssl-dev dpkg-dev
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu
- name: Set Build Version
shell: bash
run: |
Expand Down Expand Up @@ -96,6 +100,10 @@ jobs:
with:
channel: stable
cache: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-msvc
- name: Set Build Version
shell: bash
run: |
Expand Down Expand Up @@ -179,6 +187,12 @@ jobs:
with:
channel: stable
cache: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
- name: Install cargo-ndk
run: cargo install cargo-ndk --locked
- name: Set Build Version
shell: bash
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ app.*.map.json
/android/app/release

sentry.properties
.output.txt
.sentry-native/
Empty file added .junie/memory/errors.md
Empty file.
Empty file added .junie/memory/feedback.md
Empty file.
1 change: 1 addition & 0 deletions .junie/memory/language.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
1 change: 1 addition & 0 deletions .junie/memory/memory.version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.0
Empty file added .junie/memory/tasks.md
Empty file.
226 changes: 226 additions & 0 deletions .junie/plans/codebase-cleanup-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
---
sessionId: session-260421-180911-1hmr
isActive: true
---

# Overview

## Codebase Review & Cleanup Plan

### Project State
Wimsy is a cross-platform XMPP client built in Flutter/Dart. The codebase is generally well-structured, with clear separation of concerns in most areas. Tests pass cleanly on the `quic` branch (`145` Flutter tests, `64` xmpp_stone tests, `flutter analyze` clean).

The QUIC work is functionally complete but the two largest files — `lib/main.dart` (6514 lines) and `lib/xmpp/xmpp_service.dart` (7952 lines) — have grown into monoliths that make navigation, testing, and future changes increasingly difficult.

### Key Problems Found

Area | Problem | Risk |
---|---|---|
`main.dart` size | 6514 lines; UI + business logic + account management + notification handling all in one file | Hard to navigate; discourages adding tests |
`xmpp_service.dart` size | 7952 lines; everything from connection setup to MAM to file transfer to AV calls | High coupling; untestable internals |
`SharedPreferences` access | Called 14 times throughout `main.dart` in ad-hoc one-shot calls, often in widget callbacks | Scattered preference keys; easy to mistype; no central source of truth |
`ChatMessage` copy pattern | Every mutation (merge, correction, reaction) manually recopies all 28+ fields in a long `ChatMessage(...)` constructor call — repeated in `mam_merge_engine.dart`, `chat_message_mutations.dart`, and `xmpp_service.dart` | Error-prone; fields added to `ChatMessage` must also be added to every copy site |
Notification ID collisions | `id: DateTime.now().millisecondsSinceEpoch.remainder(1 << 31)` — same JID can get two notifications with different IDs in rapid succession, preventing grouped dismiss | Minor but real bug |
`Log.logLevel = LogLevel.VERBOSE` in production `main()` | Verbose XMPP XML logging left on by default for all users | Wasted I/O; leaks stanza content to device logs |
Missing `copyWith` on `ChatMessage` | The model has no `copyWith` method despite being copied in many places | Maintenance burden; risk of field omission bugs |
`_WimsyHomeState` build methods | `_buildLogin` and `_buildClient` are inline in the 6514-line file rather than extracted to separate screen widgets | Harder to test and read |
`doap.xml` XEP list | Needs verification that newly implemented XEPs (QUIC/XEP-0467, updated SASL2 pipelining) are reflected | Compliance documentation out of date |


# Technical Design

## Technical Design

### 1. Add `copyWith` to `ChatMessage`

**File:** `lib/models/chat_message.dart`

Add a `copyWith({...})` method with all optional parameters defaulting to the current value. This eliminates the current pattern of manually listing all 28+ fields in mutation sites.

```dart
ChatMessage copyWith({
String? from,
String? to,
String? body,
...
Object? mamId = _sentinel, // use sentinel to distinguish null-clear from absent
}) { ... }
```

Callers in `mam_merge_engine.dart`, `chat_message_mutations.dart`, and `xmpp_service.dart` are updated to use `copyWith`.

### 2. Centralise `SharedPreferences` keys and access

**New file:** `lib/storage/preferences_service.dart`

Create a `PreferencesService` class that holds all preference key constants and provides typed getters/setters. Existing callers (`main.dart`, `_WimsyHomeState`, `_PinSetupScreen`, etc.) are updated to use it.

```dart
class PreferencesService {
static const _sentryOptInKey = 'sentry_opt_in';
static const _pinIgnoredKey = 'wimsy_pin_ignored';
static const _lastJidKey = 'wimsy_last_jid';
// ... all other keys

final SharedPreferences _prefs;
PreferencesService(this._prefs);

bool get sentryOptIn => _prefs.getBool(_sentryOptInKey) ?? false;
Future<void> setSentryOptIn(bool value) => _prefs.setBool(_sentryOptInKey, value);
// ...
}
```

This removes the 14 scattered `SharedPreferences.getInstance()` calls across `main.dart`.

### 3. Fix notification ID stability

**File:** `lib/main.dart`, `_WimsyHomeState._handleIncomingMessage`

Derive the notification ID deterministically from the sender JID (e.g. `bareJid.hashCode.abs() % (1 << 31)`) so that rapid messages from the same contact update the same notification rather than creating new ones.

### 4. Disable verbose XMPP logging in production builds

**File:** `lib/main.dart`, `main()`

Change:
```dart
Log.logLevel = LogLevel.VERBOSE;
Log.logXmpp = true;
```
To only enable verbose logging in debug/profile mode:
```dart
assert(() {
Log.logLevel = LogLevel.VERBOSE;
Log.logXmpp = true;
return true;
}());
```

### 5. Extract `LoginScreen` widget from `_WimsyHomeState._buildLogin`

**New file:** `lib/login_screen.dart`

Extract the ~1100-line `_buildLogin` method (login form, endpoint discovery, account fields, advanced options) into a standalone `LoginScreen` stateful widget with its own state class. Pass `XmppService` and `StorageService` down.

### 6. Extract `XmppFileTransferHandler` from `XmppService`

**New file:** `lib/xmpp/xmpp_file_transfer.dart`

All methods and state related to IBB and Jingle file transfer (the `_FileTransferSession` class, `_fileTransferSessions`, send/receive/cancel methods) can be extracted to a dedicated handler class, reducing `xmpp_service.dart` by ~600 lines.

### 7. Update `doap.xml`

Verify and update XEP entries:
- XEP-0467 (QUIC transport) — new on this branch
- XEP-0388 SASL2 with IAP pipelining — extended
- Any other XEPs touched by recent commits

### Architecture Diagram

```mermaid
graph TD
main["lib/main.dart"] --> WimsyApp
WimsyApp --> Gatekeeper
Gatekeeper --> LoginScreen["LoginScreen (new file)"]
Gatekeeper --> WimsyHome
WimsyHome --> ChatScreen["ChatScreen (existing)"]
ChatScreen --> XmppService
XmppService --> FileTransfer["XmppFileTransferHandler (new file)"]
XmppService --> MAM["MamCoordinator"]
XmppService --> PEP["PepManager"]
WimsyApp --> PreferencesService["PreferencesService (new file)"]
PreferencesService --> SharedPrefs["SharedPreferences"]
```


# Prioritised Changes

## Prioritised Changes

Changes are ordered by effort and risk:

### High priority (safety & correctness)

1. **Disable verbose logging in release builds** (`main()`) — trivial one-liner; prevents leaking XMPP stanza contents to device logs in production.
2. **Fix notification ID stability** — use a deterministic hash of bareJid instead of `DateTime.now()` to prevent notification stacking for the same sender.
3. **Add `copyWith` to `ChatMessage`** and replace manual copy blocks — reduces the risk of field omission bugs as `ChatMessage` grows.

### Medium priority (maintainability)

4. **Centralise `SharedPreferences` into `PreferencesService`** — removes 14 scattered `getInstance()` calls and consolidates all key strings.
5. **Extract `LoginScreen`** from `_WimsyHomeState` — significant reduction in `main.dart` size; no behavioural change.
6. **Extract `XmppFileTransferHandler`** from `XmppService` — reduces the 7952-line service; encapsulates IBB/Jingle transfer session state.

### Lower priority (polish)

7. **Update `doap.xml`** with new XEP-0467 and any updated SASL2 entries.
8. **Enable additional lint rules** in `analysis_options.yaml`.

### Out of scope for this cleanup
- Splitting `xmpp_service.dart` further beyond the file-transfer extraction (MAM handler, call session handler) — useful long-term but large scope.
- Extracting `ChatScreen` — even larger refactor, separate effort.
- State management migration (the `ChangeNotifier`/`AnimatedBuilder` pattern is working fine).
- New features or protocol work (see `doc/todo.md` and `doc/roadmap.md`).


# Testing

## Testing

### Validation Approach
- All existing tests (`flutter test`, `dart test` in `vendor/xmpp_stone`) must continue to pass after each stage.
- `flutter analyze` must remain clean.
- The `copyWith` implementation and mutation-site updates should be covered by the existing tests in `test/chat_message_mutations_test.dart`, `test/mam_merge_engine_test.dart`, etc.

### New Tests to Add
- `test/preferences_service_test.dart` — unit tests for `PreferencesService` getters/setters using a mock `SharedPreferences`.
- A test for the deterministic notification ID function (pure function, easy to unit test).

### Regression Checks
- After extracting `LoginScreen`: `test/widget_test.dart` smoke test should still pass (it exercises the login path).
- After `copyWith`: run `test/chat_message_test.dart` and `test/chat_message_mutations_test.dart` to confirm no regressions.


# Delivery Steps

### Step 1: Fix verbose logging and notification ID stability in lib/main.dart
Two quick correctness issues in `lib/main.dart` are resolved and committed.

- In `main()`: guard `Log.logLevel = LogLevel.VERBOSE` and `Log.logXmpp = true` behind `assert(...)` so verbose XMPP logging is only active in debug/profile builds, never in release.
- In `_handleIncomingMessage` and `_handleIncomingRoomMessage`: replace `DateTime.now().millisecondsSinceEpoch.remainder(1 << 31)` with a deterministic ID derived from `bareJid.hashCode.abs() % (1 << 31)` to prevent notification stacking for the same sender.
- Run `flutter analyze` and `flutter test` to confirm no regressions; commit with detailed message.

### Step 2: Add `copyWith` to `ChatMessage` and replace all manual copy sites
`ChatMessage` gains a `copyWith` method and every manual full-constructor copy block in the codebase is replaced with it.

- Add `ChatMessage copyWith({...})` to `lib/models/chat_message.dart` covering all 28+ fields; use a sentinel-object pattern for nullable fields so callers can explicitly clear them.
- Replace manual copy blocks in `lib/xmpp/mam_merge_engine.dart` (two sites).
- Replace manual copy blocks in `lib/xmpp/chat_message_mutations.dart` (two sites).
- Replace any manual copy blocks found in `lib/xmpp/xmpp_service.dart`.
- Run `flutter analyze`, `flutter test` (especially `chat_message_mutations_test.dart`, `mam_merge_engine_test.dart`); commit.

### Step 3: Introduce `PreferencesService` and centralise all SharedPreferences access
`PreferencesService` in `lib/storage/preferences_service.dart` consolidates the 14 scattered `SharedPreferences.getInstance()` call sites currently spread across `lib/main.dart`.

- Create `lib/storage/preferences_service.dart` with all key constants and typed getters/setters (`sentryOptIn`, `pinIgnored`, `lastJid`, `audioInputId`, `videoInputId`, etc.).
- Initialise `PreferencesService` once at app startup in `main()` and pass it down to the widgets that need it.
- Replace all 14 inline call sites in `lib/main.dart` with `PreferencesService` API calls.
- Add `test/preferences_service_test.dart` with basic get/set unit tests using a fake/mock `SharedPreferences`.
- Run `flutter analyze` and `flutter test`; commit.

### Step 4: Extract `LoginScreen` from `_WimsyHomeState` into `lib/login_screen.dart`
The login and account-setup portion of `_WimsyHomeState` (~1100 lines) is moved to a standalone widget, substantially reducing `lib/main.dart`.

- Create `lib/login_screen.dart` with a `LoginScreen` stateful widget containing the account form, endpoint discovery logic, advanced options panel, and connect button.
- Move relevant `TextEditingController` fields, `_handleConnect`, `_scheduleEndpointDiscovery`, `_discoverEndpoint`, and `_buildLogin` body into `LoginScreen`'s state class.
- Update `_WimsyHomeState.build` / `_Gatekeeper` to instantiate `LoginScreen` instead of calling `_buildLogin`.
- Verify `test/widget_test.dart` smoke test still passes; run `flutter analyze`; commit.

### Step 5: Extract `XmppFileTransferHandler` from `XmppService` and update `doap.xml`
IBB/Jingle file-transfer session management (~600 lines) is extracted from `lib/xmpp/xmpp_service.dart` into its own class, and `doap.xml` is updated for newly implemented XEPs.

- Create `lib/xmpp/xmpp_file_transfer.dart` containing `XmppFileTransferHandler`, the `_FileTransferSession` model class, and all send, receive, accept, decline, and cancel methods for IBB and Jingle file transfer.
- Wire `XmppFileTransferHandler` into `XmppService` as a collaborating object, keeping all existing public method signatures on `XmppService` intact (thin delegation wrappers where needed).
- Update `doap.xml`: add XEP-0467 (QUIC transport for XMPP); review and update the XEP-0388 SASL2 / IAP pipelining entry.
- Run `flutter analyze` and `flutter test`; commit.
Loading
Loading