felis-client-core no longer builds with default-features = false (breaks the wasm reuse path) #195

Closed
opened 2026-09-07 20:46:10 +09:00 by natsukium · 1 comment
Owner

Summary

felis-client-core no longer compiles with default-features = false at
105b089979369fd310757375af3df7900ae7e523. Three modules that are declared
unconditionally reach for items the native feature gates, so the portable
core the crate documents as the wasm reuse path fails to build on every
target — wasm32 and the host alike.

crates/felis-client-core/src/lib.rs:9 states the contract:

default-features = false leaves the portable core (shadow screen, image
shadow, keymap, selection, viewport), which compiles to wasm32
(overview.md "Reuse paths for non-Rust clients").

docs/reference/workspace.md:104 names felis-web-component as the consumer
of exactly that build.

Reproduction

$ cargo new --lib /tmp/probe && cd /tmp/probe
$ cat >> Cargo.toml <<'TOML'
felis-client-core = { git = "https://git.natsukium.com/natsukium/felis", rev = "105b089979369fd310757375af3df7900ae7e523", default-features = false }
TOML
$ cargo build --target wasm32-unknown-unknown

(The same failure reproduces on the host target — the first two errors are
target-independent.) Verified from felis-web-component on branch
follow-felis-105b0899, nix develop -c cargo build --lib --target wasm32-unknown-unknown:

error[E0432]: unresolved import `crate::connector`
  --> crates/felis-client-core/src/env_base.rs:16:12
   |
16 | use crate::connector::Carrier;
   |            ^^^^^^^^^ could not find `connector` in the crate root
note: found an item that was configured out
  --> crates/felis-client-core/src/lib.rs:21:9
   |
20 | #[cfg(feature = "native")]
21 | pub mod connector;

error[E0432]: unresolved import `crate::config`
  --> crates/felis-client-core/src/shader_clock.rs:9:12
   |
 9 | use crate::config::ShaderAnimation;
   |            ^^^^^^ could not find `config` in the crate root

error[E0425]: cannot find function `platform_bytes` in this scope
  --> crates/felis-client-core/src/env_base.rs:28:19
error[E0425]: cannot find function `platform_bytes` in this scope
  --> crates/felis-client-core/src/env_base.rs:29:21
error[E0425]: cannot find function `platform_path` in this scope
   --> crates/felis-client-core/src/pipe.rs:182:61
error[E0425]: cannot find function `nodename` in this scope
   --> crates/felis-client-core/src/pipe.rs:215:5
error[E0425]: cannot find function `process_is_live` in this scope
   --> crates/felis-client-core/src/pipe.rs:302:21

error: could not compile `felis-client-core` (lib) due to 7 previous errors

What each error is

  • env_base captures this process's environment for a SpawnArgs.env_base and
    types it against connector::Carrier, which is native-only. A browser
    client dials nothing and creates nothing, so the module has no business in
    the portable set.
  • shader_clock is the GPU renderer's frame clock and reads
    config::ShaderAnimation, also native-only.
  • pipe's platform_path / nodename / process_is_live exist only under
    cfg(unix) and cfg(windows). wasm32-unknown-unknown is neither, so this
    one is wasm-specific on top of the feature split.

Why nothing caught it

dev/flake-module.nix:33 puts wasm32-unknown-unknown on the toolchain with
the comment "so CI guards felis-client-core's portable core against
feature-split bitrot", but no recipe or workflow builds that configuration —
git grep wasm32 outside docs/ finds only the toolchain line and two
comments. The guard the comment promises does not exist, which is how the
three modules drifted.

Impact

felis-web-component cannot build against felis main at all. Its
follow-felis-105b0899 branch adapts the whole wasm core to the reset wire
(verified green against a locally patched felis: 25 + 9 wasm tests pass, clippy
clean) but every CI gate stays red on this, so the PR cannot be merged until
felis builds.

Suggested fix

Gate the three modules behind native (what the local patch did to verify the
follow), or split the two pipe helper sets so the portable half stands alone,
then add the guard the flake comment claims:
cargo check -p felis-client-core --no-default-features --target wasm32-unknown-unknown as a just check layer and a CI step.

Out of scope for the follow

The fix belongs to felis: a downstream cannot exclude a dependency's modules,
and [patch]ing felis from the component would fork the crate the component
exists to reuse verbatim.

## Summary `felis-client-core` no longer compiles with `default-features = false` at `105b089979369fd310757375af3df7900ae7e523`. Three modules that are declared unconditionally reach for items the `native` feature gates, so the portable core the crate documents as the wasm reuse path fails to build on every target — wasm32 and the host alike. `crates/felis-client-core/src/lib.rs:9` states the contract: > `default-features = false` leaves the portable core (shadow screen, image > shadow, keymap, selection, viewport), which compiles to wasm32 > (overview.md "Reuse paths for non-Rust clients"). `docs/reference/workspace.md:104` names `felis-web-component` as the consumer of exactly that build. ## Reproduction ```console $ cargo new --lib /tmp/probe && cd /tmp/probe $ cat >> Cargo.toml <<'TOML' felis-client-core = { git = "https://git.natsukium.com/natsukium/felis", rev = "105b089979369fd310757375af3df7900ae7e523", default-features = false } TOML $ cargo build --target wasm32-unknown-unknown ``` (The same failure reproduces on the host target — the first two errors are target-independent.) Verified from `felis-web-component` on branch `follow-felis-105b0899`, `nix develop -c cargo build --lib --target wasm32-unknown-unknown`: ``` error[E0432]: unresolved import `crate::connector` --> crates/felis-client-core/src/env_base.rs:16:12 | 16 | use crate::connector::Carrier; | ^^^^^^^^^ could not find `connector` in the crate root note: found an item that was configured out --> crates/felis-client-core/src/lib.rs:21:9 | 20 | #[cfg(feature = "native")] 21 | pub mod connector; error[E0432]: unresolved import `crate::config` --> crates/felis-client-core/src/shader_clock.rs:9:12 | 9 | use crate::config::ShaderAnimation; | ^^^^^^ could not find `config` in the crate root error[E0425]: cannot find function `platform_bytes` in this scope --> crates/felis-client-core/src/env_base.rs:28:19 error[E0425]: cannot find function `platform_bytes` in this scope --> crates/felis-client-core/src/env_base.rs:29:21 error[E0425]: cannot find function `platform_path` in this scope --> crates/felis-client-core/src/pipe.rs:182:61 error[E0425]: cannot find function `nodename` in this scope --> crates/felis-client-core/src/pipe.rs:215:5 error[E0425]: cannot find function `process_is_live` in this scope --> crates/felis-client-core/src/pipe.rs:302:21 error: could not compile `felis-client-core` (lib) due to 7 previous errors ``` ## What each error is - `env_base` captures this process's environment for a `SpawnArgs.env_base` and types it against `connector::Carrier`, which is `native`-only. A browser client dials nothing and creates nothing, so the module has no business in the portable set. - `shader_clock` is the GPU renderer's frame clock and reads `config::ShaderAnimation`, also `native`-only. - `pipe`'s `platform_path` / `nodename` / `process_is_live` exist only under `cfg(unix)` and `cfg(windows)`. `wasm32-unknown-unknown` is neither, so this one is wasm-specific on top of the feature split. ## Why nothing caught it `dev/flake-module.nix:33` puts `wasm32-unknown-unknown` on the toolchain with the comment "so CI guards felis-client-core's portable core against feature-split bitrot", but no recipe or workflow builds that configuration — `git grep wasm32` outside `docs/` finds only the toolchain line and two comments. The guard the comment promises does not exist, which is how the three modules drifted. ## Impact `felis-web-component` cannot build against felis main at all. Its `follow-felis-105b0899` branch adapts the whole wasm core to the reset wire (verified green against a locally patched felis: 25 + 9 wasm tests pass, clippy clean) but every CI gate stays red on this, so the PR cannot be merged until felis builds. ## Suggested fix Gate the three modules behind `native` (what the local patch did to verify the follow), or split the two `pipe` helper sets so the portable half stands alone, then add the guard the flake comment claims: `cargo check -p felis-client-core --no-default-features --target wasm32-unknown-unknown` as a `just check` layer and a CI step. ## Out of scope for the follow The fix belongs to felis: a downstream cannot exclude a dependency's modules, and `[patch]`ing felis from the component would fork the crate the component exists to reuse verbatim.
Author
Owner

Triage plan (2026-09-07)

Verdict: accepted, priority/P1. The wasm/portable core is a documented contract (crates/felis-client-core/src/lib.rs, its Cargo.toml, docs/reference/workspace.md, docs/explanation/architecture/overview.md) with a real consumer blocked on it (felis-web-component), and the tag would ship a doc claim that does not compile.

Verified on a detached origin/main checkout: cargo check -p felis-client-core --no-default-features fails with 2 errors on the host (crate::connector in env_base.rs, crate::config in shader_clock.rs, both native-gated modules) and 7 on wasm32-unknown-unknown (plus the cfg(unix|windows)-only helpers in env_base.rs and pipe.rs). Bisected: 9028ed94 introduced the gate and built clean on wasm32; 8c8547ca (shader clock, 2026-08-14) is the first break; b118dbe8/9a20b398/47d4c1fe (pipe) and a011c611 (env_base) added two more. Three independent regressions in three weeks because no guard exists: dev/flake-module.nix installs the wasm32 target and its comment says CI guards the split, but no justfile recipe, workflow step, or flake check ever used it; pr.yml runs only --all-features.

Approach:

  1. crates/felis-client-core/src/lib.rs: gate env_base, shader_clock, and pipe behind #[cfg(feature = "native")]. All three are host-process concerns; their only consumers (felis-cli, felis-client) build with default features. No pipe split — nothing in it is portable.
  2. Guard: just check-portable = cargo check -p felis-client-core --no-default-features --target wasm32-unknown-unknown (and clippy in that config so -D warnings catches dead cfg leftovers); add it to the check aggregate and as one step in pr.yml's build job (mirror in release.yml if it repeats the gate). Prefer the cargo step over a crane check, matching the clippy/nextest precedent.
  3. Docs: the four claims stay true once fixed; correct the dev/flake-module.nix comment only if the guard lands somewhere other than "CI". Commit scopes client-core: + ci:. No CHANGELOG (no CLI/config/wire change). Size ≈ 20 lines.

Risk: gating pipe removes pipe::Origin/StagedRegion from the portable set — no portable consumer exists; note it in the commit body. If felis-grid or felis-protocol grew a non-portable dep since 8c8547ca~1, the new guard will surface it; that is the guard working.

## Triage plan (2026-09-07) **Verdict:** accepted, `priority/P1`. The wasm/portable core is a documented contract (`crates/felis-client-core/src/lib.rs`, its `Cargo.toml`, `docs/reference/workspace.md`, `docs/explanation/architecture/overview.md`) with a real consumer blocked on it (`felis-web-component`), and the tag would ship a doc claim that does not compile. **Verified** on a detached `origin/main` checkout: `cargo check -p felis-client-core --no-default-features` fails with 2 errors on the host (`crate::connector` in `env_base.rs`, `crate::config` in `shader_clock.rs`, both `native`-gated modules) and 7 on `wasm32-unknown-unknown` (plus the `cfg(unix|windows)`-only helpers in `env_base.rs` and `pipe.rs`). Bisected: `9028ed94` introduced the gate and built clean on wasm32; `8c8547ca` (shader clock, 2026-08-14) is the first break; `b118dbe8`/`9a20b398`/`47d4c1fe` (pipe) and `a011c611` (env_base) added two more. Three independent regressions in three weeks because no guard exists: `dev/flake-module.nix` installs the wasm32 target and its comment says CI guards the split, but no justfile recipe, workflow step, or flake check ever used it; `pr.yml` runs only `--all-features`. **Approach:** 1. `crates/felis-client-core/src/lib.rs`: gate `env_base`, `shader_clock`, and `pipe` behind `#[cfg(feature = "native")]`. All three are host-process concerns; their only consumers (`felis-cli`, `felis-client`) build with default features. No `pipe` split — nothing in it is portable. 2. Guard: `just check-portable` = `cargo check -p felis-client-core --no-default-features --target wasm32-unknown-unknown` (and clippy in that config so `-D warnings` catches dead `cfg` leftovers); add it to the `check` aggregate and as one step in `pr.yml`'s build job (mirror in `release.yml` if it repeats the gate). Prefer the cargo step over a crane check, matching the clippy/nextest precedent. 3. Docs: the four claims stay true once fixed; correct the `dev/flake-module.nix` comment only if the guard lands somewhere other than "CI". Commit scopes `client-core:` + `ci:`. No CHANGELOG (no CLI/config/wire change). Size ≈ 20 lines. **Risk:** gating `pipe` removes `pipe::Origin`/`StagedRegion` from the portable set — no portable consumer exists; note it in the commit body. If `felis-grid` or `felis-protocol` grew a non-portable dep since `8c8547ca~1`, the new guard will surface it; that is the guard working.
Sign in to join this conversation.
No description provided.