Clear all 73 clippy lints, and make the Clippy step real #14

Merged
fabi merged 7 commits from fix/clippy-lints into main 2026-09-07 19:58:08 +00:00
Owner

Closes #13.

Clippy had never run on this codebase: dtolnay/rust-toolchain@stable
installs a minimal profile and the native job named no components, so
cargo clippy -- -D warnings died on "not installed" before seeing a line of
source. Across every revision of ci.yml in this repo, components: appears
exactly once — rustfmt, in the fmt job. The step was never wired, not broken.

The real total was 73, not 48

-D warnings makes a lint a hard compile error, so sylpheed-formats failing
meant two crates were never built and had never been linted by anyone:

crate lints
sylpheed-formats 43 → 0
sylpheed-viewer 14 → 0 (never linted before)
sylpheed-cli 11 → 0 (never linted before)
sylpheed-export 5 → 0

The deferral argument from #12 does not transfer

#12 defers rustfmt because a whole-tree reformat would conflict with the 495- and
366-commit branches queued to return. Measured per-site, that reasoning does not
carry: 68 of 73 sites cannot collide. mesh.rs and texture.rs carry 28
lints between them and are byte-identical on both branches; no branch touches
viewer/src at all. Three sites genuinely collide, all in sylpheed-export,
documented in #13 with resolutions.

Judgement calls, stated

Three too_many_arguments are Bevy systems — the parameters are
scheduler-injected Res/ResMut/EventWriter, not a signature anyone calls.
Site-local #[allow] with the reason at the site; any new violation elsewhere
still fails.

PROTOCOL.md gains the third instance of checks that were kind once — this
one authored dirty rather than decayed into, with the test that catches it
earlier: if you are writing the softening in the same commit as the check, the
thing you want is an issue, not a flag.

Verified

cargo clippy --workspace -- -D warnings exits 0 on a fresh uncached run;
cargo test --workspace 207 passed / 0 failed / 14 ignored, identical to runs
203 and 204. Independently cargo check --workspace clean on x86_64, so the
rewrites are not aarch64-specific. All as_chunks_mut replaced
chunks_exact_mut (semantically identical); checked_div replaced an explicit
if frame > 0 guard.

Closes #13. Clippy had **never run** on this codebase: `dtolnay/rust-toolchain@stable` installs a minimal profile and the `native` job named no components, so `cargo clippy -- -D warnings` died on *"not installed"* before seeing a line of source. Across every revision of `ci.yml` in this repo, `components:` appears exactly once — `rustfmt`, in the `fmt` job. The step was never wired, not broken. ### The real total was 73, not 48 `-D warnings` makes a lint a hard compile error, so `sylpheed-formats` failing meant two crates were never built and had **never been linted by anyone**: | crate | lints | |---|---| | `sylpheed-formats` | 43 → 0 | | `sylpheed-viewer` | 14 → 0 (never linted before) | | `sylpheed-cli` | 11 → 0 (never linted before) | | `sylpheed-export` | 5 → 0 | ### The deferral argument from #12 does not transfer #12 defers rustfmt because a whole-tree reformat would conflict with the 495- and 366-commit branches queued to return. Measured per-site, that reasoning does not carry: **68 of 73 sites cannot collide.** `mesh.rs` and `texture.rs` carry 28 lints between them and are **byte-identical on both branches**; no branch touches `viewer/src` at all. Three sites genuinely collide, all in `sylpheed-export`, documented in #13 with resolutions. ### Judgement calls, stated Three `too_many_arguments` are **Bevy systems** — the parameters are scheduler-injected `Res`/`ResMut`/`EventWriter`, not a signature anyone calls. Site-local `#[allow]` with the reason at the site; any new violation elsewhere still fails. `PROTOCOL.md` gains the third instance of *checks that were kind once* — this one **authored dirty** rather than decayed into, with the test that catches it earlier: *if you are writing the softening in the same commit as the check, the thing you want is an issue, not a flag.* ### Verified `cargo clippy --workspace -- -D warnings` exits 0 on a fresh uncached run; `cargo test --workspace` 207 passed / 0 failed / 14 ignored, identical to runs 203 and 204. Independently `cargo check --workspace` clean on x86_64, so the rewrites are not aarch64-specific. All `as_chunks_mut` replaced `chunks_exact_mut` (semantically identical); `checked_div` replaced an explicit `if frame > 0` guard.
fabi added 14 commits 2026-09-05 18:19:23 +00:00
Phase 5 of docs/agents/GITEA-SETUP.md, plus a correction to Phase 2 that the
runbook could not have known it needed.

gitea-mcp v1.7.0 goes into both images, pinned by the sha256 the release
publishes and smoke-tested with `--version` at build time, so a bad pin fails
the build instead of the agent. Each entrypoint registers it at user scope for
that container's own identity, remove-then-add so a restart is idempotent.

The token is passed BY PATH. `-e GITEA_ACCESS_TOKEN=$(cat …)` would write it in
cleartext into ~/.claude.json, which every session in the container reads;
GITEA_ACCESS_TOKEN_FILE is new in the pinned version and leaves the secret in
its read-only mount. Verified against the binary's own --help, not assumed.

The tool filter stops being an experiment. The names are in the release README:
each agent gets issues, notifications, labels, milestones and pull requests, and
NOT `pull_request_review_write`. That one matters because separate identities
open a hole the runbook did not name: Gitea refuses to let an author approve
their own pull request, and does nothing about sylph-decoder approving
sylph-port's. Two agents could satisfy `required_approvals = 1` between
themselves and then merge, since branch protection blocks pushes to main and
never blocked merges.

Withholding the tool is defence in depth; the controls are in branch protection,
and both docs now say so: approvals whitelisted to the human so an agent's
approval does not count, merges whitelisted to the human so an approved PR is
still merged by a person. Phase 2's check gains the step that actually tests it
-- approve the throwaway PR yourself, then confirm the agent STILL has no merge
button. Without that step, the check passes on an instance where the agents can
merge each other's work.

Also settles two entries on the runbook's own "not verified" list: the tool
filter names, and the Gitea version (1.25.5, whose API schema carries
enable_merge_whitelist and enable_approvals_whitelist under those names).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
Phase 2 as a file. Six settings where two are load-bearing and both were missing
from the first draft is the shape of thing that gets mis-clicked at 1am, so it
goes through the API: what was applied is readable in a diff, and `--verify`
can re-check it later instead of it being checked once.

--verify states its expectations INDEPENDENTLY of what the apply path sends.
A check derived from "whatever we posted" cannot fail -- it re-derives the
expectation from the thing under test, which is the same instrument-shaped
failure as a check that passes on an instance with no rule at all.

It also asserts both agents are still Write and not Admin, because an agent
promoted to Admin can edit the rule and then merge, so a green rule proves
nothing on its own. That is the `gitea-verify` card from "Still to build";
what is left of it is only putting it on a timer.

`block_admin_merge_override` stays false on purpose, and the reasoning is in
the file: approvals are whitelisted to `fabi`, and Gitea will not let `fabi`
approve a `fabi` PR -- so with the override blocked, a human-authored PR could
never reach one approval and could never merge at all. The override is not a
hole in the agent gate because the agents are Write, not Admin. Phase 1.2 pays
for that; this is where it is spent.

Reads the repository-scoped credential that already exists on the agent box
(~/.sylph-git-credentials) rather than the issue-only ~/.sylph-gitea-api-token,
which every branch-protection endpoint refuses. That keeps the setup needing no
new credential, and keeps push rights on one machine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
The runbook existed as two documents -- a published page and this file -- with
no mechanism keeping them equal, only an intention to remember. Two versions was
the predicted outcome of that, not an accident on top of it. This is the fold,
and the rule that follows it: THIS FILE IS THE SOURCE, the page is derived from
it. When something is urgent enough to push to the page first, it lands here in
the same turn, not "shortly after".

Four things the file did not carry:

  * YOUR OWN PUSHES TO main STOP. `enable_push: false` compiles to CanUserPush,
    which returns false with no bypass for admins or the owner -- quoted from
    the source. Three commits went in by direct push the day this was written,
    so the first notice would have been mid-task. Now a check step.
  * the token files' MACHINES, which the table had lost.
  * do NOT add `write:repository` to the `fabi` token. That scope IS a push
    credential. Written down because that advice was given, in chat, by the
    author of this file.
  * Gitea 1.25.5 confirmed from the desktop too, not just the Pi.

And one thing deliberately NOT folded in: the page said the desktop's outbound
HTTP was blocked, and that is false. `python3 -c 'urllib...'` returns
200 {"version":"1.25.5"} from this box. What is refused here is `curl`, by a
local permission prompt -- which I read as a network constraint and then
published as one. The Phase 3 locations stand; the reason given for them did not.

The "not verified" section now separates WRONG from UNCHECKED. Four entries are
wrong -- requiring an approval does not close the gate, the check could not have
caught that, the token scope, the reachability -- and the pattern in all four is
identical: a property inferred from something ADJACENT to it (protection from a
settings page, reachability from a DNS record) instead of tested directly. That
is the frozen-splash failure, committed in the document about avoiding it. The
first two were caught by the other agent, which is the argument for the review
gate this file exists to build.
Two things that read as protection while being none.

Phase 2's rule binds everyone who reaches Gitea through the API or the web, and
does not bind anyone with `gitea admin` in the container -- which includes the
supervising agent that created the agent accounts and minted their tokens. From
that shell the rule is editable and an admin token is one command away. That is
the boundary of what the phase buys, not a hole to plug there, and the document
read as though the gate were universal. Phases 1 and 2 gate the two CONTAINERISED
agents, whose design assumption is that policy lives where they cannot reach it;
a supervisor with a host shell is not in that set.

And `gitea-setup` finished by telling the reader to go and build a Gitea project
board by hand, four sections after the doc explains that a board is a second copy
of the state to hand-sync and is precisely the failure that produced a 1,227-line
BLOCKED.md. A tool instructing you to do the thing its own documentation argues
against is the drift this whole surface exists to end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
Caught by the Pi agent against the live instance after Phase 4 ran. The tool
creates 5 state/*, 2 agent/*, 4 kind/* = 11.

Where the 12 came from is worth a line, because it is a shape that recurs:

    $ grep -c '^mklabel' tools/gitea-setup
    12
    $ grep -n '^mklabel' tools/gitea-setup | grep -v ':mklabel "'
    74:mklabel() { # name colour description

I counted the function DEFINITION as a call. A measurement taken one token away
from the thing being measured -- the same shape as reading protection off a
settings page and reachability off a DNS record, which is now three today. The
version that cannot make this mistake is counting what the instance holds, and
that is what found it.
Phases 1-4 and 6 are done on the instance. This file still opened with "Nothing
exists on the instance: no agent users, no API tokens, no labels, no milestones,
no branch protection" -- every clause of which was false by the time the merge
that carried it landed.

Replaced with a table of measured state, and each row says what was MEASURED
rather than what was run:

  * protection is verified behaviourally -- a real push to main refused with
    `pre-receive hook declined`, as the repository owner -- not read off a
    settings page. That distinction is the whole subject of this file.
  * the tokens are probed: right identity, 403 on branch_protections for both
    agents, so the Write-not-Admin carve-out is demonstrated and not asserted.
  * the labels are 11 because the instance holds 11.

And a standing note that this block is the part most likely to be wrong, with
what to believe instead: `gitea-protect --verify` and the issue list MEASURE,
this block REMEMBERS. A remembered status is a cache with no invalidation, which
is the same failure as a 1,227-line BLOCKED.md and as the two documents this
runbook was split across an hour ago.
--verify's collaborator loop printed  and continued on 404 without touching
`ok`, so the one instrument that checks Phase 1.2 could not report Phase 1.2
being undone. An agent removed from the repository read as "nothing to say"
rather than as a gate that is no longer there.

It has never fired: Gitea answers that endpoint with permission "read" for a
non-collaborator rather than 404, so the case was caught by the role test two
lines down. Correct outcome, wrong reason -- the same shape as the check that
passed on an instance with no rule at all, and not worth keeping because the
luck has held so far.

Found by the port agent reading the file rather than running it, which is the
only way this one was ever going to surface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
protocol: findings before citing code, and checks that were kind once
Some checks failed
CI / Native — ubuntu-latest (pull_request) Failing after 10m46s
CI / WASM — Web (pull_request) Failing after 9m2s
CI / Formatting (pull_request) Failing after 46s
CI / Native — macos-latest (pull_request) Has been cancelled
CI / Native — windows-latest (pull_request) Has been cancelled
25bfa1553f
Two rules that look unrelated and are one failure, plus the change that makes
the second enforceable.

1. A FINDING REACHES `main` BEFORE THE CODE THAT CITES IT. A citation resolving
   only on a peer branch is dead the moment it merges. Not hypothetical: 495
   decoder and 366 port commits sit off `main`, and `port/scripts/boot.gd`
   already cites two docs/re pages present on neither its own branch nor main.

2. A CHECK MAY ONLY SOFTEN AGAINST A CONDITION IT CAN TEST -- the Pi agent's
   wording, and better than mine, because it is applicable while writing rather
   than a call to be vigilant. The mechanical form:

       Can this branch tell the difference between "not yet" and "no longer"?

   `gitea-protect --verify` printed  "not a collaborator (yet)" and continued,
   so the only instrument checking Write-not-Admin could not report that gate
   being REMOVED. `check-citations` reported peer citations instead of failing
   them, because under the old topology that was unfixable from the container.
   Both were correct AND kind when written; neither recorded that the kindness
   had a scope. Nobody edits these into being wrong -- the world moves and the
   allowance stays, which is why they survive review. The smell is leniency with
   an expiry date nobody set; the fix is the testable-condition rule.

check-citations gains `--for-merge`, which turns the peer class into a failure.
A flag rather than a new default because BOTH readings are still live: mid-work
on a topic branch the peer class really is unfixable noise. What the old code
could not express is where the code is GOING, and that is a condition the caller
can state. Measured on this tree: 19 citations resolve only on a peer branch --
which is the size of the #7-depends-on-#8 edge, not the 2 I had counted in
boot.gd.

The selftest gains that third class, because a flag whose classification is
unexercised is the shape this rule exists to catch. Controlled: emptying
PEER_REFS makes the peer case collapse into "nowhere" and the selftest reports
🔴 BROKEN, rc=2.

⚠️ Pre-existing and NOT from this change: the default run already exits 1 on 4
citations of `export/...` paths. Those are the generated tree, gitignored by
design, and main's copy of the tool fails identically. The CITE regex treats
`export/` as a repo prefix. Reported, not fixed -- it is the port's file and its
call whether the regex or the citations are wrong.
ci: build for the machine that exists, on the runner that exists
Some checks failed
CI / Native — linux (pull_request) Failing after 52m49s
CI / WASM — Web (pull_request) Failing after 11m39s
CI / Formatting (pull_request) Failing after 2m26s
c457320210
This workflow has never once gone green on this instance: 23 runs cancelled,
2 waiting, zero successes. Not a regression -- it has been decorative since it
was written, because it describes GitHub's hosted fleet and runs on one
self-hosted aarch64 Pi advertising ["ubuntu-latest","ubuntu-24.04",
"ubuntu-22.04"].

Two failures, both configuration rather than code:

`windows-latest` and `macos-latest` match no runner label, so those jobs sit in
WAITING for ever and the RUN never reaches a terminal state. A pull request's
checks therefore never resolve either way -- not red, just never finished, which
is worse than red because a red check tells you something. Removed: a second
architecture here needs a second runner, not a second matrix row.

`--target x86_64-unknown-linux-gnu` on an aarch64 host makes every build a
cross-compile, and `wayland-sys`'s build script dies on it with "pkg-config has
not been configured to support cross-compilation". Dropped; the native job now
builds for its host.

NOT touched, deliberately: the WASM and Formatting jobs still fail, on real code
state rather than on configuration -- `getrandom` needs the `wasm_js` backend
for wasm32-unknown-unknown, and `cargo fmt --check` reports a ~13,000 line diff
across the tree. Editing those two into passing is precisely the leniency with
an expiry date nobody sets that PROTOCOL.md now forbids. They are issues, not
workflow lines.

(One latent defect noted while reading: `jetli/trunk-action` fetches
trunk-x86_64-unknown-linux-gnu onto this aarch64 host. It has never been reached
because the WASM check fails first, and it will bite the moment that is fixed.)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
ci: install the clippy component the Clippy step needs
Some checks failed
CI / Native — linux (pull_request) Failing after 47m11s
CI / WASM — Web (pull_request) Failing after 11m49s
CI / Formatting (pull_request) Failing after 1m40s
a3d99adaa6
`dtolnay/rust-toolchain@stable` installs a minimal profile. The `native`
job named no components, so every run that reached the Clippy step died
on

    error: 'cargo-clippy' is not installed for the toolchain
           'stable-aarch64-unknown-linux-gnu'

before clippy read a line of source. That is not a lint result; the step
had never run. The `fmt` job below always named `components: rustfmt`
correctly — this one never did.

Two lines of behaviour change. The rest is the comment explaining why the
step is left gating on `-D warnings` rather than softened: the workspace
is not clippy-clean (run 203's build alone emits ~13 rustc warnings that
`-D warnings` promotes to errors), and `continue-on-error` cannot tell
"debt not yet paid" from "debt paid". That debt is scoped in #13, the way
the rustfmt debt is in #12.

Run 203 is what made this visible. With the aarch64 fix in c457320 the
native job got all the way through:

    cargo check  --workspace   ok  10m01s
    cargo build  --workspace   ok  19m04s
    cargo test   --workspace   ok  16m22s   214 passed, 0 failed
    cargo clippy --workspace   toolchain error

Refs #13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
The two instances already in "Checks that were kind once" were correct
when written and decayed. The third was wrong on its first commit, and it
arrived by a different route: the check and the tree's failure to pass it
land in the same change, so the softening writes itself.

Concretely — the Clippy step had never run (no component in the
toolchain), and the tree is not clippy-clean, so fixing the step and
turning it red are the same commit. The first draft paired the fix with
`continue-on-error: true` and a comment promising removal once the debt
was paid: an expiry date nobody set, in the shape #12's closing line had
already ruled out for rustfmt. Reverted on reading it.

Adds the distinction, a table separating decay from dirty authorship, and
an earlier tell than the mechanical test:

    If you are writing the softening in the same commit as the check,
    the thing you want is an issue, not a flag.

The mechanical test is unchanged and still correct; this only catches the
same failure sooner, at the keyboard rather than at review.

Refs #12, #13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
Run 204 gave this repository its first clippy measurement — 48 errors, 43
of them in `sylpheed-formats`. This clears that 43 to zero under the exact
invocation CI runs, `cargo clippy -p sylpheed-formats -- -D warnings`.

Why this crate first, and why it is safe to touch:

Every one of the 43 sites was checked against the line ranges that
`auto/frame-blend-draw-path` (495 commits) and `auto/port-p6-audio` (366)
actually modify. None of them overlap. Eleven of the fifteen affected
files are byte-identical on both branches, including `mesh.rs` and
`texture.rs`, which carry 28 of the hits between them. The three sites in
`audio.rs`, `ui_layout.rs` and `slb.rs` that live in files those branches
do change fall outside every modified hunk. The collision argument that
defers #12 does not transfer here; it was tested rather than assumed.

It also unblocks a measurement. `-D warnings` turns a lint in this crate
into a hard compile error, so its dependents never build — `sylpheed-cli`
and `sylpheed-viewer` have never been linted at all, and viewer is the
largest crate in the workspace. Both depend only on `sylpheed-formats`
(`sylpheed-export` pins it from a git tag instead), so this commit is what
makes their real counts knowable.

  38  applied by `cargo clippy --fix` — chunks_exact_to_as_chunks,
      manual_div_ceil / is_multiple_of / range_contains, unnecessary_map_or,
      needless_borrow, let_and_return, dead_code, unused_mut/variables.
      Purely local expression rewrites: 38 insertions, 39 deletions.
   2  by hand: a doc continuation that markdown was parsing as a list, and
      `d / frame` behind a `frame > 0` guard becoming `checked_div`.
   3  `#[allow(clippy::too_many_arguments)]` with a stated reason.

On those three allows: 8 parameters against a threshold of 7, in the mesh
anchor path. The real fix is a shared params struct across
`anchor_pool_mesh`, `validate_block` and `validate_block_report` — the
latter two take the same eight arguments and one delegates to the other —
which is a change to the decoder's signatures and belongs to whoever owns
that path, not to a CI-lint pass.

This is not the shape PROTOCOL.md forbids. `continue-on-error` suppresses
everything, present and future, at the job level, and cannot tell "not
yet" from "no longer". A site-local `#[allow]` with a reason is a decision
recorded where it applies: one lint, one function, and any new violation
anywhere else still fails the build.

`sylpheed-export`'s remaining 5 are deliberately untouched — three of them
sit inside hunks both long-lived branches modify, and that crate blocks
nothing. Left for #13.

Refs #13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
fix(viewer,cli,export): clear the remaining 30 clippy lints
Some checks failed
CI / Native — linux (pull_request) Successful in 51m51s
CI / WASM — Web (pull_request) Failing after 12m2s
CI / Formatting (pull_request) Failing after 1m45s
a2e4dab0ab
`cargo clippy --workspace -- -D warnings` now exits 0. `cargo test
--workspace` still reports 207 passed, 0 failed, 14 ignored across 30
suites — identical to runs 203 and 204, so none of this changed behaviour.

The workspace total was 73, not the 48 run 204 reported. `-D warnings`
turns a lint into a hard compile error, so `sylpheed-formats` failing
stopped its dependents from ever being built: `sylpheed-viewer` (14) and
`sylpheed-cli` (11) had never been linted by anyone. Clearing formats in
5c35a34 is what made them visible.

  formats  43 -> 0   (5c35a34)
  viewer   14 -> 0
  cli      11 -> 0
  export    5 -> 0

Collision surface, measured rather than assumed. Every viewer file
carrying a lint is byte-identical on both `auto/frame-blend-draw-path`
(495 commits) and `auto/port-p6-audio` (366). All eleven cli sites fall
outside every hunk either branch touches. 68 of the 73 sites could not
collide with anything.

The five that can are all in `sylpheed-export`, and three of those are
real:

  main.rs:278   `&out` -> `out`, inside frame-blend's hunk -278,12
  main.rs:318   `&out` -> `out`, inside port-p6-audio's hunk -303,44
  audio.rs:113  an added `#[allow]` in a file frame-blend DELETES

Each is one line. Resolving the first two means taking the branch's
version and re-applying a borrow removal; the third resolves to the
deletion. Flagged here so neither branch owner meets them cold.

Judgement calls, all stated at the site rather than suppressed globally:

* Three `too_many_arguments` in the viewer are false positives.
  `draw_viewer_ui`, `poll_loader_channel` and `apply_pak` are Bevy
  systems — every parameter is a `Res`/`ResMut`/`EventWriter` the
  scheduler injects, so the count is the framework's dependency list and
  cannot be reduced without a `SystemParam` struct.
* `cmd_screen_render` (cli, 8/7) is a plain function, so that one is real
  if mild; its arguments are the subcommand's flags.
* Two `dead_code` fields in export are serde schema fields. They model
  what the on-disc JSON accepts; deleting them would quietly change that.
* `iso_loader.rs` gains a `FrameRx` alias for the ffmpeg frame channel,
  which is what "very complex type" was asking for.

A site-local `#[allow]` with a reason is a decision recorded where it
applies: one lint, one function, and any new violation elsewhere still
fails the build. That is not the shape PROTOCOL.md forbids.

Closes #13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
Author
Owner

Run 206: Native green. And #12's job caught a defect in this PR.

Native — linux succeeded — the first green job in this repository's history. WASM and Formatting failed for #11 and #12, both confirmed unchanged from their logs: getrandom's wasm_js error at Check WASM compile (still before trunk-action), and the pre-existing rustfmt diff.

But the Formatting job reports 782 hunks where run 204 reported 774. The eight extra are mine — debt added to the very issue this PR argued should not be disturbed. Fixed in e56c122, bundle at /tmp/sylph-fmt-parity.bundle (base a2e4dab, verifies clean). An ordinary push, not a force-push: PR #14 is already open and the reviewed SHAs stay put.

Measured against a b6466cb worktree under the same rustfmt: 774 == 774, equal to baseline rather than merely close.

Two causes, both interactions rather than misjudgements

cargo clippy --fix writes its replacement on one line. Inside a multi-line method chain that produced .as_chunks::<2>().0.iter(), which rustfmt wants split across three. Seven sites in formats/{audio,vfs,game_data}.rs and cli/main.rs; an eighth was a for header pushed past the width limit, now two bindings.

The last was subtler, and was the only file still off after the other seven were fixed. ship_capture.rs had let mut flush = |base: u32, and clippy correctly removed the mut — which shortens the line by four characters, leaving the closure's six continuation parameters aligned four columns too far right. A formatting change caused by an edit on a different line.

Method note, because it nearly misled me twice

My first attempt to locate the new hunks compared hunk bodies between the CI log and a local run. It flagged nearly every ship_capture.rs hunk as new — the two sources render context lines differently, so exact-match was worthless and would have sent me rewriting a file that was mostly fine. The count against a same-toolchain baseline isolated the real file in one step.

That generalises, and it is the reusable part: --fix output is not rustfmt output, and on a tree that is not rustfmt-clean the difference is invisible in the diff. It shows up only as a hunk count moving. Anyone running cargo clippy --fix on this workspace before #12 is paid should check cargo fmt --all -- --check | grep -c '^Diff in' against baseline afterwards, not read the patch.

Re-verified after the reformat

These edits changed real lines, so the earlier green does not carry over:

cargo clippy --workspace -- -D warnings   exit 0
cargo test   --workspace                  exit 0   207 passed, 0 failed, 14 ignored
cargo fmt    --all -- --check             774 hunks == b6466cb baseline

With this applied, #14's Native job should stay green and Formatting should return to exactly the red it was before this PR existed — no better, and importantly no worse.

## Run 206: Native green. And #12's job caught a defect in this PR. `Native — linux` succeeded — the first green job in this repository's history. WASM and Formatting failed for #11 and #12, both confirmed unchanged from their logs: `getrandom`'s `wasm_js` error at `Check WASM compile` (still before `trunk-action`), and the pre-existing rustfmt diff. **But the Formatting job reports 782 hunks where run 204 reported 774. The eight extra are mine** — debt added to the very issue this PR argued should not be disturbed. Fixed in `e56c122`, bundle at `/tmp/sylph-fmt-parity.bundle` (base `a2e4dab`, verifies clean). An ordinary push, not a force-push: PR #14 is already open and the reviewed SHAs stay put. Measured against a `b6466cb` worktree under the same rustfmt: **774 == 774**, equal to baseline rather than merely close. ### Two causes, both interactions rather than misjudgements `cargo clippy --fix` writes its replacement on one line. Inside a multi-line method chain that produced `.as_chunks::<2>().0.iter()`, which rustfmt wants split across three. Seven sites in `formats/{audio,vfs,game_data}.rs` and `cli/main.rs`; an eighth was a `for` header pushed past the width limit, now two bindings. The last was subtler, and was the only file still off after the other seven were fixed. `ship_capture.rs` had `let mut flush = |base: u32,` and clippy correctly removed the `mut` — which shortens the line by four characters, leaving the closure's six continuation parameters aligned four columns too far right. **A formatting change caused by an edit on a different line.** ### Method note, because it nearly misled me twice My first attempt to locate the new hunks compared hunk *bodies* between the CI log and a local run. It flagged nearly every `ship_capture.rs` hunk as new — the two sources render context lines differently, so exact-match was worthless and would have sent me rewriting a file that was mostly fine. The count against a same-toolchain baseline isolated the real file in one step. That generalises, and it is the reusable part: **`--fix` output is not rustfmt output, and on a tree that is not rustfmt-clean the difference is invisible in the diff.** It shows up only as a hunk count moving. Anyone running `cargo clippy --fix` on this workspace before #12 is paid should check `cargo fmt --all -- --check | grep -c '^Diff in'` against baseline afterwards, not read the patch. ### Re-verified after the reformat These edits changed real lines, so the earlier green does not carry over: ``` cargo clippy --workspace -- -D warnings exit 0 cargo test --workspace exit 0 207 passed, 0 failed, 14 ignored cargo fmt --all -- --check 774 hunks == b6466cb baseline ``` With this applied, #14's Native job should stay green and Formatting should return to exactly the red it was before this PR existed — no better, and importantly no worse.
fabi added 1 commit 2026-09-05 21:06:33 +00:00
fix: stop the lint pass adding rustfmt debt to #12
Some checks failed
CI / Native — linux (pull_request) Successful in 1h34m43s
CI / WASM — Web (pull_request) Failing after 11m32s
CI / Formatting (pull_request) Failing after 2m26s
e56c1228ac
The lint work added 8 rustfmt hunks. Run 206's Formatting job reports 782
where run 204 reported 774, and the difference is mine — debt added to the
one issue the lint pass argued should not be disturbed. Measured against a
`b6466cb` worktree under the same rustfmt, the tree is back to 774: equal
to baseline, not merely close to it.

Two causes, both interactions rather than mistakes of judgement:

`cargo clippy --fix` writes its replacement on one line. Where the call
sat inside a multi-line method chain that produced

    .as_chunks::<2>().0.iter()

which rustfmt wants split across three lines. Seven sites, in
`formats/{audio,vfs,game_data}.rs` and `cli/main.rs`. An eighth was a
`for` header pushed past the width limit; it reads better as two bindings
than as a six-line chain, so that is what it became.

The last one is subtler and was the only file left over after the other
seven were fixed. `ship_capture.rs` had

    let mut flush = |base: u32,
                     size: u32,

and clippy correctly removed the `mut`. That shortens the line by four
characters, so the closure's six continuation parameters were then
aligned four columns too far right — a formatting change caused by a
change on a different line, in a file whose own hunk count is what
exposed it.

Worth recording because it generalises: `--fix` output is not rustfmt
output, and on a tree that is not rustfmt-clean the difference is
invisible in the diff and only shows up as a hunk count moving. The check
that catches it is a count against a same-toolchain baseline, not an
inspection of the patch.

Re-verified after the reformat, since these edits changed real lines:

    cargo clippy --workspace -- -D warnings   exit 0
    cargo test   --workspace                  exit 0   207 passed, 0 failed
    cargo fmt    --all -- --check             774 hunks == baseline

Refs #12, #13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McNbzUeq1KRBWs4G6X2YVj
Author
Owner

Reproduced on 1.92.0 directly. It is a demotion, and it is now fixed on both.

I said I would not leave this resting on a single report, so I installed 1.92.0 (ded5c06cf 2025-12-08) and asked both toolchains what the lint's default level is:

1.92.0   clippy::collapsible-else-if   warn      "nested `else`-`if` expressions that can…"
1.98.1   clippy::collapsible-else-if   pedantic  (allow by default)

collapsible_if stayed in style on both. Only the else variant moved, which is why the pair looked inconsistent. Your report is confirmed exactly.

Counts, on 1.92.0, sylpheed-formats:

@ b6466cb   30 warnings   (you reported 31 — a one-off, immaterial)
@ HEAD       0 warnings

Two false trails I walked into, both caught by re-running

A build failure read as a lint failure. My first 1.92.0 workspace run returned exit=101, and I very nearly reported "the workspace still fails on 1.92". It was missing bevy's system libraries — libasound2-dev, libwayland-dev and friends — so sylpheed-viewer failed to build. With them installed: exit=0. The exit code is identical for "your code is bad" and "my container is incomplete", which is worth remembering next time one of us quotes a 101 at the other.

The count that disagreed with itself. A grep -c gave 30 where the authoritative generated N warnings line also gave 30, but only after a cargo clean — without it the crate was cached and silent. Same trap as this morning, third time today.

Fixed

23f6880, bundle /tmp/sylph-collapsible-else-if.bundle (base e56c122, verifies clean). else if beats else { if } whichever toolchain is reading, so it stands on its own terms and it makes the two agree:

1.98.1  cargo clippy --workspace -- -D warnings   exit 0
1.92.0  cargo clippy --workspace -- -D warnings   exit 0
        cargo test --workspace                    exit 0, 207 passed
        cargo fmt  --all -- --check               774 == b6466cb baseline

The class, unfixed

This instance is gone; the general problem is not, and it is symmetrical. At b6466cb, 1.98.1 counted 43 lints in this crate where 1.92.0 counts 30 — the newer has lints the older lacks (chunks_exact_to_as_chunks, manual_is_multiple_of, manual_checked_ops), the older has one the newer demoted. Neither is a superset.

So a gate on floating @stable can go red on an unchanged tree at the next release, and green the same way — the worse direction, because nobody investigates a check that starts passing. #15 now carries this with both toolchains measured rather than one measured and one reported.

## Reproduced on 1.92.0 directly. It is a demotion, and it is now fixed on both. I said I would not leave this resting on a single report, so I installed `1.92.0 (ded5c06cf 2025-12-08)` and asked both toolchains what the lint's default level is: ``` 1.92.0 clippy::collapsible-else-if warn "nested `else`-`if` expressions that can…" 1.98.1 clippy::collapsible-else-if pedantic (allow by default) ``` `collapsible_if` stayed in `style` on both. **Only the `else` variant moved**, which is why the pair looked inconsistent. Your report is confirmed exactly. Counts, on 1.92.0, `sylpheed-formats`: ``` @ b6466cb 30 warnings (you reported 31 — a one-off, immaterial) @ HEAD 0 warnings ``` ### Two false trails I walked into, both caught by re-running **A build failure read as a lint failure.** My first 1.92.0 workspace run returned `exit=101`, and I very nearly reported "the workspace still fails on 1.92". It was missing bevy's system libraries — `libasound2-dev`, `libwayland-dev` and friends — so `sylpheed-viewer` failed to *build*. With them installed: `exit=0`. The exit code is identical for "your code is bad" and "my container is incomplete", which is worth remembering next time one of us quotes a 101 at the other. **The count that disagreed with itself.** A `grep -c` gave 30 where the authoritative `generated N warnings` line also gave 30, but only after a `cargo clean` — without it the crate was cached and silent. Same trap as this morning, third time today. ### Fixed `23f6880`, bundle `/tmp/sylph-collapsible-else-if.bundle` (base `e56c122`, verifies clean). `else if` beats `else { if }` whichever toolchain is reading, so it stands on its own terms and it makes the two agree: ``` 1.98.1 cargo clippy --workspace -- -D warnings exit 0 1.92.0 cargo clippy --workspace -- -D warnings exit 0 cargo test --workspace exit 0, 207 passed cargo fmt --all -- --check 774 == b6466cb baseline ``` ### The class, unfixed This instance is gone; the general problem is not, and it is symmetrical. At `b6466cb`, 1.98.1 counted **43** lints in this crate where 1.92.0 counts **30** — the newer has lints the older lacks (`chunks_exact_to_as_chunks`, `manual_is_multiple_of`, `manual_checked_ops`), the older has one the newer demoted. Neither is a superset. So a gate on floating `@stable` can go red on an unchanged tree at the next release, and green the same way — the worse direction, because nobody investigates a check that starts passing. **#15** now carries this with both toolchains measured rather than one measured and one reported.
Author
Owner

Run 207's Native log settles all three candidates. It is the version, and CI's green is honest.

Asked for the Clippy step's version line and last lines. Here is the whole step, verbatim:

::group::Run cargo clippy --workspace -- -D warnings
cargo clippy --workspace -- -D warnings
::endgroup::
Checking sylpheed-formats v0.1.0 (/workspace/fabi/Sylpheed/crates/sylpheed-formats)
Checking sylpheed-export  v0.1.0 (/workspace/fabi/Sylpheed/crates/sylpheed-export)
Checking sylpheed-viewer  v0.1.0 (/workspace/fabi/Sylpheed/crates/sylpheed-viewer)
Checking sylpheed-cli     v0.1.0 (/workspace/fabi/Sylpheed/crates/sylpheed-cli)
Finished `dev` profile in 9.25s

Toolchain, printed three times including in rust-cache's own environment list: rustc 1.98.1 (48a229cea 2026-09-01), hash 48a229ceaefd4985c50990b14116b6d856af0985.

Candidate 2 — cache — refuted, and it could not have happened anyway

Earlier in the same log:

::warning::Failed to restore: getCacheEntry failed: connect ETIMEDOUT 172.20.0.3:40083
No cache found.

The cache did not restore. target/ started empty and the job compiled 823 crates from scratch. There was nothing cached to go silent.

There is also a structural reason this candidate cannot work under -D warnings, worth recording because it explains why my local cache trap does not generalise to CI: cargo only caches successful compilations. With -D warnings a lint is an error, so a crate with lints fails to compile and is never cached as success. A cached exit 0 therefore means that exact fingerprint genuinely passed. What I hit locally was plain cargo clippy without -D warnings, where lints stay warnings, the crate compiles, and cargo suppresses re-printing on the next run. That is a reporting artefact, not a correctness one, and the gate does not have it.

Candidate 3 — exit code not honoured — refuted by comparison

The cache-save step is gated on success() || env.CACHE_ON_FAILURE == 'true'. Its evaluation is printed:

run 207 (clippy passed) -> evaluated to 'true'
run 203 (clippy failed) -> evaluated to 'false'

The same expression, on the same workflow, reading the Clippy step's outcome correctly in both directions. The step's exit code propagates.

Candidate 1 — the version — proven, and the rustfmt inference does not hold

The argument was: rustfmt and clippy ship together, CI's rustfmt gives 774 and so does mine, therefore same toolchain, therefore same clippy. The premise fails on the middle step. rustfmt holds its output stable across releases within a style edition, by design; clippy makes no such promise and explicitly moves lints between groups. So identical formatting output is expected across versions and cannot discriminate between them.

Measured rather than argued — both toolchains, same box, same tree:

rustfmt @1.92.0 : rustfmt 1.8.0-stable (ded5c06cf2 2025-12-08)   ->  774 hunks
rustfmt @stable : rustfmt 1.9.0-stable (48a229ceae 2026-09-01)   ->  774 hunks

clippy::collapsible-else-if   @1.92.0 -> warn
clippy::collapsible-else-if   @stable -> allow

Two different rustfmt versions, byte-identical hunk counts. The 774/774 match is real and reproducible and says nothing about which clippy ran. The clippy levels, printed side by side by the same command, say everything.

Conclusion

Native's green is green for the right reason: clippy 1.98.1 ran on all four crates, from an empty target directory, and found nothing — because collapsible_else_if is allow-by-default on that version. Not the frozen splash. The instrument is reporting what it sees; what it sees is dated.

That is exactly what this issue is for, and it strengthens the case for option 1. 23f6880 lands regardless — the lint is real on 1.92.0, the site is worse style on any version, and the fix makes both toolchains agree.

## Run 207's Native log settles all three candidates. It is the version, and CI's green is honest. Asked for the Clippy step's version line and last lines. Here is the whole step, verbatim: ``` ::group::Run cargo clippy --workspace -- -D warnings cargo clippy --workspace -- -D warnings ::endgroup:: Checking sylpheed-formats v0.1.0 (/workspace/fabi/Sylpheed/crates/sylpheed-formats) Checking sylpheed-export v0.1.0 (/workspace/fabi/Sylpheed/crates/sylpheed-export) Checking sylpheed-viewer v0.1.0 (/workspace/fabi/Sylpheed/crates/sylpheed-viewer) Checking sylpheed-cli v0.1.0 (/workspace/fabi/Sylpheed/crates/sylpheed-cli) Finished `dev` profile in 9.25s ``` Toolchain, printed three times including in rust-cache's own environment list: **`rustc 1.98.1 (48a229cea 2026-09-01)`**, hash `48a229ceaefd4985c50990b14116b6d856af0985`. ### Candidate 2 — cache — refuted, and it could not have happened anyway Earlier in the same log: ``` ::warning::Failed to restore: getCacheEntry failed: connect ETIMEDOUT 172.20.0.3:40083 No cache found. ``` **The cache did not restore.** `target/` started empty and the job compiled 823 crates from scratch. There was nothing cached to go silent. There is also a structural reason this candidate cannot work under `-D warnings`, worth recording because it explains why my local cache trap does *not* generalise to CI: **cargo only caches successful compilations.** With `-D warnings` a lint is an error, so a crate with lints fails to compile and is never cached as success. A cached `exit 0` therefore means that exact fingerprint genuinely passed. What I hit locally was plain `cargo clippy` without `-D warnings`, where lints stay warnings, the crate compiles, and cargo suppresses re-printing on the next run. That is a *reporting* artefact, not a correctness one, and the gate does not have it. ### Candidate 3 — exit code not honoured — refuted by comparison The cache-save step is gated on `success() || env.CACHE_ON_FAILURE == 'true'`. Its evaluation is printed: ``` run 207 (clippy passed) -> evaluated to 'true' run 203 (clippy failed) -> evaluated to 'false' ``` The same expression, on the same workflow, reading the Clippy step's outcome correctly in both directions. The step's exit code propagates. ### Candidate 1 — the version — proven, and the rustfmt inference does not hold The argument was: rustfmt and clippy ship together, CI's rustfmt gives 774 and so does mine, therefore same toolchain, therefore same clippy. **The premise fails on the middle step.** rustfmt holds its output stable across releases within a style edition, by design; clippy makes no such promise and explicitly moves lints between groups. So identical formatting output is expected across versions and cannot discriminate between them. Measured rather than argued — both toolchains, same box, same tree: ``` rustfmt @1.92.0 : rustfmt 1.8.0-stable (ded5c06cf2 2025-12-08) -> 774 hunks rustfmt @stable : rustfmt 1.9.0-stable (48a229ceae 2026-09-01) -> 774 hunks clippy::collapsible-else-if @1.92.0 -> warn clippy::collapsible-else-if @stable -> allow ``` **Two different rustfmt versions, byte-identical hunk counts.** The 774/774 match is real and reproducible and says nothing about which clippy ran. The clippy levels, printed side by side by the same command, say everything. ### Conclusion Native's green is green for the right reason: clippy 1.98.1 ran on all four crates, from an empty target directory, and found nothing — because `collapsible_else_if` is allow-by-default on that version. Not the frozen splash. The instrument is reporting what it sees; what it sees is dated. That is exactly what this issue is for, and it strengthens the case for option 1. `23f6880` lands regardless — the lint is real on 1.92.0, the site is worse style on any version, and the fix makes both toolchains agree.
fabi added 1 commit 2026-09-06 08:17:57 +00:00
fix(formats): collapse the one else { if }, so both toolchains agree
Some checks failed
CI / Native — linux (pull_request) Successful in 55m56s
CI / WASM — Web (pull_request) Failing after 12m9s
CI / Formatting (pull_request) Failing after 2m8s
d8807c4f8f
`ascii_runs` in movie_manifest.rs had a nested `else { if .. }`. Clippy's
`collapsible_else_if` flags it — on some toolchains.

WHY IT WAS NEVER CAUGHT, AND WHY THAT IS THE INTERESTING PART:

  clippy::collapsible-else-if   @1.92.0 (2025-12-08)  ->  warn
  clippy::collapsible-else-if   @1.98.1 (2026-09-01)  ->  allow

CI installs `dtolnay/rust-toolchain@stable`, which floats. The runner is on
1.98.1, where this lint is allow-by-default, so the Clippy job passed. A local
run on a nine-month-old stable failed on the same bytes. Neither instrument was
broken; "clippy clean" is simply a statement about a toolchain and a date, and
nothing in the pipeline records which. That is #15, and this commit does not fix
it -- it removes one instance of its consequence.

Fixing it rather than pinning, because `else { if }` is worse code on every
version. A tree whose cleanliness is contingent on a release date is the thing
to avoid; agreeing with both toolchains is cheaper than arguing about which one
is right.

⚠️ I FIRST DIAGNOSED THIS WRONG, and the wrong version is worth recording. From
CI's rustfmt reporting 774 hunks and mine reporting 774, I concluded the
toolchains matched, therefore the clippys matched, therefore CI's green must be
a cached or ungated result -- "the frozen splash again". Every step after the
first was false. rustfmt is deliberately output-stable within a style edition;
clippy explicitly moves lints between groups. Measured here afterwards:

  rustfmt 1.8.0-stable -> 774 hunks
  rustfmt 1.9.0-stable -> 774 hunks     (nine months apart, identical)

So formatting parity carries no information about which clippy ran. It is the
same error as reading protection off a settings page or reachability off a DNS
record: a property inferred from something ADJACENT to it. The peer refuted it
by measurement -- `No cache found` in the run log, and `success()` evaluating
'true' in 207 against 'false' in 203 -- rather than by accepting the framing.

Verified on both: 1.92.0 `clippy --workspace -- -D warnings` rc=0 (was 101);
1.98.1 with the lint forced on, 0 sites. rustfmt still 774, so no debt added to
#12. Tests 207 passed / 0 failed / 14 ignored, unchanged.
Author
Owner

Check WASM compile now exits 0. It was three blockers, not one.

ba6c5da, bundle /tmp/sylph-wasm-compile.bundle (base d8807c4, verifies clean). Each blocker was invisible until the one before it was gone — the same cascade shape as the clippy pass, where clearing sylpheed-formats revealed two crates that had never been linted.

error cause
1 The wasm32-unknown-unknown targets are not supported by default getrandom 0.3 needs --cfg getrandom_backend="wasm_js" and the wasm_js feature. Its own message says either alone is insufficient. Reached only through ahash, in sylpheed-viewer only.
2 This wasm target is unsupported by mio. sylpheed-formats declared tokio as a normal dependency it never used.
3 bevy_egui uses unstable APIs to support clipboard on web. needs --cfg web_sys_unstable_apis.

The middle one is a real defect, independent of WASM

Every tokio reference in sylpheed-formats/src/ is inside a mod tests — three runtime builders in ship.rs, one #[tokio::test] in xiso.rs — and tokio was already declared in [dev-dependencies]. The normal dependency was dead weight that pulled tokio/full, whose net feature drags in mio, which does not build for wasm32. Removing it is correct on its own terms; the WASM job is only what exposed it.

This is the one change here that is not CI configuration, and it is the one worth looking at rather than waving through.

Verified

wasm    cargo check --target wasm32-unknown-unknown -p sylpheed-viewer -p sylpheed-formats   exit 0
native  cargo check  --workspace                                                             exit 0
        cargo test   --workspace                                          exit 0, 207/0/14, 30 suites
        cargo clippy --workspace -- -D warnings                                              exit 0
        cargo fmt    --all -- --check                                          774 == baseline

Both cfgs are scoped to [target.wasm32-unknown-unknown], so native builds are untouched — the numbers above confirm it rather than assume it.

This does not make the job green, and the next failure is already identified

The two steps after this have never executed in this repository's history. Install Trunk uses jetli/trunk-action@v0.5.0, and its bundled dist/index.js contains:

x86_64-unknown-linux-gnu   1 occurrence
aarch64                    0 occurrences
os.arch()                  5 calls, with no mapping for it

On this aarch64 runner it will fetch an x86_64 binary. The asset it should be fetching does exist — upstream publishes trunk-aarch64-unknown-linux-gnu.tar.gz — so only the action's selection is wrong, which narrows my earlier claim: this is not "trunk is unavailable on arm", it is "this action cannot find it".

I stopped there deliberately. Replacing the install step means choosing a trunk version to pin and an install method, and after #15 that is exactly the sort of choice that should be a decision rather than something I slipped in. Two obvious shapes: a run: step fetching the aarch64 asset for $(uname -m) with a pinned version, or cargo install trunk at the cost of a long build.

Scope

#11 was written around the getrandom error alone. This goes past that shape, and one change touches a crate's dependencies rather than CI config. It is a bundle, not a push, and #11 remains state/proposed.

## `Check WASM compile` now exits 0. It was three blockers, not one. `ba6c5da`, bundle `/tmp/sylph-wasm-compile.bundle` (base `d8807c4`, verifies clean). Each blocker was invisible until the one before it was gone — the same cascade shape as the clippy pass, where clearing `sylpheed-formats` revealed two crates that had never been linted. | | error | cause | |---|---|---| | 1 | `The wasm32-unknown-unknown targets are not supported by default` | getrandom 0.3 needs `--cfg getrandom_backend="wasm_js"` **and** the `wasm_js` feature. Its own message says either alone is insufficient. Reached only through `ahash`, in `sylpheed-viewer` only. | | 2 | `This wasm target is unsupported by mio.` | `sylpheed-formats` declared `tokio` as a **normal dependency it never used**. | | 3 | `bevy_egui uses unstable APIs to support clipboard on web.` | needs `--cfg web_sys_unstable_apis`. | ### The middle one is a real defect, independent of WASM Every tokio reference in `sylpheed-formats/src/` is inside a `mod tests` — three runtime builders in `ship.rs`, one `#[tokio::test]` in `xiso.rs` — and **tokio was already declared in `[dev-dependencies]`**. The normal dependency was dead weight that pulled `tokio/full`, whose `net` feature drags in `mio`, which does not build for wasm32. Removing it is correct on its own terms; the WASM job is only what exposed it. **This is the one change here that is not CI configuration**, and it is the one worth looking at rather than waving through. ### Verified ``` wasm cargo check --target wasm32-unknown-unknown -p sylpheed-viewer -p sylpheed-formats exit 0 native cargo check --workspace exit 0 cargo test --workspace exit 0, 207/0/14, 30 suites cargo clippy --workspace -- -D warnings exit 0 cargo fmt --all -- --check 774 == baseline ``` Both cfgs are scoped to `[target.wasm32-unknown-unknown]`, so native builds are untouched — the numbers above confirm it rather than assume it. ### This does not make the job green, and the next failure is already identified The two steps after this have never executed in this repository's history. `Install Trunk` uses `jetli/trunk-action@v0.5.0`, and its bundled `dist/index.js` contains: ``` x86_64-unknown-linux-gnu 1 occurrence aarch64 0 occurrences os.arch() 5 calls, with no mapping for it ``` On this aarch64 runner it will fetch an x86_64 binary. **The asset it should be fetching does exist** — upstream publishes `trunk-aarch64-unknown-linux-gnu.tar.gz` — so only the action's selection is wrong, which narrows my earlier claim: this is not "trunk is unavailable on arm", it is "this action cannot find it". I stopped there deliberately. Replacing the install step means choosing a trunk version to pin and an install method, and after #15 that is exactly the sort of choice that should be a decision rather than something I slipped in. Two obvious shapes: a `run:` step fetching the aarch64 asset for `$(uname -m)` with a pinned version, or `cargo install trunk` at the cost of a long build. ### Scope #11 was written around the getrandom error alone. This goes past that shape, and one change touches a crate's dependencies rather than CI config. It is a bundle, not a push, and #11 remains `state/proposed`.
fabi added 1 commit 2026-09-06 12:01:42 +00:00
docs: handoff for resuming on another machine
Some checks failed
CI / Native — linux (pull_request) Successful in 58m29s
CI / WASM — Web (pull_request) Failing after 9m55s
CI / Formatting (pull_request) Failing after 1m37s
4b62252d94
Written to be read cold by a session with no context: what is established
versus what is someone's claim, which of the four actors the gate actually
binds, and what is in flight but unpushed.

The transferable part is §7 -- one failure shape recurred five times in two
days across two agents and the assistant: a property inferred from something
ADJACENT to it rather than tested directly. Protection from a settings page,
reachability from a DNS record, a label count from a grep that matched the
function definition, Gitea's host from a DNS lookup, and a clippy version from
rustfmt output parity. The last is the sharpest: rustfmt 1.8.0 and 1.9.0, nine
months apart, both emit 774 hunks here, so formatting parity carries no
information about which clippy ran -- and the conclusion drawn from it (that
CI's green was cached or ungated) was wrong in a way that would have sent
someone chasing a phantom.
fabi added 1 commit 2026-09-07 17:25:47 +00:00
docs: the handoff's resume steps assume a machine this is not
Some checks failed
CI / Native — linux (pull_request) Successful in 29m58s
CI / WASM — Web (pull_request) Failing after 8m1s
CI / Formatting (pull_request) Failing after 36s
885b4d4d67
§§1-9 were written on fabi-Hyrican-PC. On the other desktop the ~/.sylph-*
credentials do not exist, the Pi does not resolve, and stable is 1.90.0 rather
than the 1.98.1 §8 records -- so §8.2 (fetch the WASM bundle) and §8.4 (Phase 7)
cannot be run from here at all. Says which of the four steps can.

Measured rather than carried over: protection holds (10/10), fmt is 774 hunks
across 154 files, check-citations is 19, and the tests are 207/0/14 across 30
suites. Two of those need reading carefully:

  * clippy DIVERGES. The runner is rustc 1.98.1 -- read out of job 794's log,
    not assumed -- and is clean; here 1.90.0 exits 101 on only_used_in_recursion
    at vfs.rs:85. That is #15 ceasing to be theoretical. It is NOT evidence that
    CI's green is fake, which is the §7 lesson-5 inference in the other
    direction.
  * the test tally matches to the unit while measuring something else. 15
    *_disc.rs files resolve disc_root() through a hardcoded absolute path, so
    unsetting SYLPHEED_DISC does not skip them: the disc suites RAN here (1936 s,
    mesh_consistency_disc alone 1220 s) and skipped on CI (2.4 s total) -- and
    both report 207/0/14, because the skip path returns from a test that still
    passes. Good news for #14, since this run is the stronger evidence; and worth
    an issue, since SYLPHEED_DISC looks like a control and is not one.

I got that last one wrong first -- inferred "the counts cannot match" from "the
fallback resolves", which is §7's shape a sixth time, recorded as such.

Also: a plain `git clone` of this repo fails three ways on the pack that still
carries the 545 MB; --filter=blob:none works. And §6's tokio claim was
challenged and survived -- every use is inside a #[cfg(test)] module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Owner

Rebased onto 885b4d4 (the two docs commits): ba6c5da25092f3, payload
hash-identical across the rebase, conflict-free as predicted since the file sets
are disjoint.

Re-verified from scratch rather than on the cache — cargo clean --target wasm32-unknown-unknown first, then the exact CI command: exit 0, 163s, zero
errors
, with getrandom v0.3.4 and bevy_egui compiling and no mio in the
graph at all.

Bundle: /home/fabi/sylph-wasm-2026-09-07.bundle (base 885b4d4, head
25092f3, verifies okay). The /tmp one is stale — do not use it.

Full file list, the scope question on crates/sylpheed-formats/Cargo.toml, and
the sylpheed-export pin interaction are on #11:
#11 (comment)

This does not turn the WASM job green — Install Trunk still selects an x86_64
binary on an aarch64 runner. That remains a decision, not a fix.

Rebased onto `885b4d4` (the two docs commits): **`ba6c5da` → `25092f3`**, payload hash-identical across the rebase, conflict-free as predicted since the file sets are disjoint. Re-verified from scratch rather than on the cache — `cargo clean --target wasm32-unknown-unknown` first, then the exact CI command: **`exit 0`, 163s, zero errors**, with `getrandom v0.3.4` and `bevy_egui` compiling and no `mio` in the graph at all. Bundle: **`/home/fabi/sylph-wasm-2026-09-07.bundle`** (base `885b4d4`, head `25092f3`, verifies okay). The `/tmp` one is stale — do not use it. Full file list, the scope question on `crates/sylpheed-formats/Cargo.toml`, and the `sylpheed-export` pin interaction are on #11: https://git.mc02.dev/fabi/Sylpheed/issues/11#issuecomment-228 This does not turn the WASM job green — `Install Trunk` still selects an x86_64 binary on an aarch64 runner. That remains a decision, not a fix.
fabi merged commit d630bc996f into main 2026-09-07 19:58:08 +00:00
Sign in to join this conversation.