nigig-org/REVIEWS/MATRIX_CLIENT_FEATURE_GATE.md
andodeki a2b05c56c9
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
makepad-table / model (push) Has been cancelled
makepad-table / widget (push) Has been cancelled
makepad-table / hygiene (push) Has been cancelled
feat(makepad-table): opt-in capabilities feature, and raise the matrix_client defect
The two caveats from the dependency investigation.

## The capabilities feature

Camera and location attachments are now available behind
`features = ["capabilities"]`, which pulls `nigig-uikit` and supplies
`UikitAttachmentProvider`.

Measured: 89 crates by default, 275 with the feature on. That cost is real
and it is inherent, not packaging waste. `camera_widget` imports
`send_geocode_request` and `request_map_tile` from `nigig-core`, both of
which call `spawn_async` — the shared Tokio runtime — and the first makes an
HTTPS call to Nominatim. A camera that geocodes needs an async runtime and an
HTTP client; there is no lighter honest version.

It is affordable because it is opt-in, and because any app enabling it
already depends on `nigig-core`, so that app's own tree grows by nothing.

Everything touching `nigig-uikit` is in one module, so the boundary is a file
rather than `#[cfg]` scattered through the widget. The provider holds no
widgets of its own: the host owns the `CameraWidget` already in its tree and
this asks it to open, because a provider that instantiated a second camera
would fight the first for the device.

A second request while one is outstanding is refused rather than overwriting.
The table turns that refusal into `AttachmentUnavailable`, so the user is
told the camera is busy instead of watching their first request vanish.

File picking is deliberately declined here — `robius-file-picker` already
ships unconditionally and costs nothing, and two paths for one job is one too
many.

Two CI gates, both verified to fail when they should: the opt-in build must
keep compiling, and the default build must pull none of `tokio`, `reqwest`,
`hyper`, `clap`, `csv`, `image`, `nigig-uikit` or `nigig-core`. The second
checks the resolved `cargo tree` rather than the manifest, because feature
unification can switch an optional dependency on from a sibling crate.

Tests 99 default, 105 with the feature. Both clippy-clean.

## The matrix_client defect

Raised in REVIEWS/MATRIX_CLIENT_FEATURE_GATE.md rather than fixed. It is not
my crate, nothing depends on the broken combination, and a blind fix could
change behaviour someone relies on.

`matrix_client` declares `native = ["dep:tokio", "dep:reqwest",
"dep:rusqlite"]` but its source gates on `#[cfg(not(target_arch =
"wasm32"))]`. Two switches for the same modules, so on a native target with
the feature off the modules compile and their dependencies do not — 19
errors, 26 ungated uses across 7 files. There is no CI job for the crate,
which is why it rotted unnoticed.

The note corrects an overstatement I made while arguing for the trait hook.
I said fixing this would unblock wasm. It would not: `matrix_client` already
builds clean for wasm32 with `--no-default-features`, and `nigig-core` has 8
wasm errors of its own (`crate::platform::spawn` missing) that have nothing
to do with it. The only broken combination is native-target-with-feature-off,
which nothing builds.

I also said earlier that `matrix_client` was heavy — it is a 7-dependency
local crate, not matrix-sdk. That was wrong and it inflated the case for the
trait hook; the note records the measured numbers instead.
2026-08-18 18:03:26 +00:00

139 lines
4.7 KiB
Markdown

# `matrix_client`: the `native` feature does not do what it says
- **Date:** 2026-08-18
- **Status:** raised, not fixed. This is not my crate and nothing currently
depends on the broken combination; fixing it blind risks changing
behaviour someone is relying on.
- **Found while:** measuring what `makepad-table` would cost if it depended
on `nigig-uikit` for camera and location.
## The defect
`crates/matrix_client/Cargo.toml` declares a feature that gates three heavy
dependencies:
```toml
[features]
default = ["native"]
native = ["dep:tokio", "dep:reqwest", "dep:rusqlite"]
```
The source does **not** gate on that feature. It gates on the target:
```rust
// crates/matrix_client/src/lib.rs
#[cfg(not(target_arch = "wasm32"))]
pub mod auth;
#[cfg(not(target_arch = "wasm32"))]
pub mod client;
// ... and so on for http, persistence, sync_service, worker
```
Two different switches for the same set of modules. On a native target with
the feature turned off, the modules still compile but their dependencies are
absent:
```
cargo check -p matrix_client --no-default-features
-> 19 errors: unresolved import `reqwest`, cannot find `tokio`,
unresolved import `rusqlite`
```
26 ungated uses across 7 files:
| File | Uses |
|---|---|
| `worker.rs` | 9 |
| `error.rs` | 4 |
| `persistence.rs` | 4 |
| `sync_service.rs` | 4 |
| `client.rs` | 2 |
| `http.rs` | 2 |
| `types.rs` | 1 |
## What is *not* broken
The wasm path is coherent:
```
cargo check -p matrix_client --no-default-features --target wasm32-unknown-unknown
-> 0 errors
```
`target_arch = "wasm32"` and the absent dependencies agree there, because on
wasm the `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]` section
is skipped anyway. The only broken combination is **native target with the
feature off**, which nothing in this repo builds and no CI job covers —
which is why it rotted without anyone noticing.
## Correcting an overstatement
I previously said fixing this would "unblock wasm". That was wrong, and I
should have checked before saying it. `nigig-core` has 8 wasm errors of its
own that have nothing to do with `matrix_client`:
```
cargo check -p nigig-core --no-default-features --target wasm32-unknown-unknown
-> 8 errors: cannot find function `spawn` in module `crate::platform`
```
Fixing `matrix_client` moves nobody closer to a wasm build on its own.
## Why it was not fixed here
It buys the thing that surfaced it precisely nothing. `makepad-table` wanted
`nigig-uikit` for its camera and location widgets, and `camera_widget`
imports exactly two items from `nigig-core`:
```rust
use nigig_core::syncing::{send_geocode_request, request_map_tile, ...};
```
Both call `spawn_async`, which *is* the shared Tokio runtime, and
`send_geocode_request` makes an HTTPS call to Nominatim. So the camera widget
requires `nigig-core/native` by its own nature. `matrix_client` sits on the
same flag: making it honour the feature would let *it* build without tokio,
but `nigig-uikit` turns tokio straight back on through `nigig-core`. Net
saving for that use case: zero.
Measured, so the numbers are not guesses:
| Configuration | Crates in the tree |
|---|---|
| `makepad-widgets` alone | 88 |
| `+ nigig-uikit` | 274 |
| `matrix_client` with `native` | 199 |
| `matrix_client` without `native` (if it compiled) | 140 |
## Two ways to fix it
**A. Make the source honour the feature.** Change 26 sites from
`#[cfg(not(target_arch = "wasm32"))]` to `#[cfg(feature = "native")]`, and
move the dependencies out of the target section into the plain one, where
`optional = true` is what decides. This is the honest reading of what the
feature claims. It is also the version that could change behaviour for a
native build that somehow relied on the modules existing without the
dependencies — none exists today, but the check is a compile, not a test.
**B. Delete the feature.** If `matrix_client` is native-only in practice —
and the 26 sites say it is — then `native` is a claim the crate does not
honour, and removing it is more truthful than a flag nobody can use. The
dependencies stay non-optional in the target section, where the wasm split
already works.
B is smaller and matches observed reality. A is right if a wasm or
dependency-light build is actually wanted, in which case `nigig-core`'s 8
errors need fixing too, and that is the larger piece.
## Whichever is chosen, it needs a gate
There is no CI job for `matrix_client`. A feature that nothing builds is a
feature that breaks silently, which is exactly what happened. One line in a
workflow:
```yaml
- name: The native feature means what it says
run: cargo check -p matrix_client --no-default-features
```
That step fails today. It should be added with the fix, not before it.