Skip to content

auto-fix batch claude/friendly-maxwell-m1efN 2026-05-03#596

Merged
intendednull merged 11 commits into
mainfrom
claude/friendly-maxwell-m1efN
May 4, 2026
Merged

auto-fix batch claude/friendly-maxwell-m1efN 2026-05-03#596
intendednull merged 11 commits into
mainfrom
claude/friendly-maxwell-m1efN

Conversation

@intendednull
Copy link
Copy Markdown
Owner

Scheduled /resolving-issues sweep against the 2026-05-03 general-audit (master ticket #567). Nine small-scope fixes landed sequentially; one follow-up issue filed (#595). Picked candidates that did NOT overlap with PR #566's in-flight files (crates/web/src/state.rs, crates/client/src/lib.rs, etc. — see #566 file list).

Fixes

Already-Fixed

None this run. The general-audit at #567 was filed 2026-05-03 against the same main @ 6404719 HEAD this run started from; no commits landed in the gap by definition (per the prior PR #566 lessons noting same-day audit-to-fix is the expected zero-yield case).

Parked

None. All 9 picks landed cleanly. No mid-flight aborts; no finalize-implementer rescues; no scope-creep guards tripped.

Filed mid-run (follow-ups, NOT closed by this PR)

Avoided picks (overlap with PR #566 files in flight)

Skill Evolution

  • 4702486 docs(skill): codify pre-staged working tree as expected pattern — adds a sibling paragraph to the Implementer Agent step 8 sandbox-interference note. Captures the inverse phenomenon (pre-staged uncommitted edits at session start) seen in ~40-50% of dispatches across PR auto-fix batch claude/friendly-maxwell-M5xB6 2026-05-03 #566 + this run. Implementer brief now expects + accepts this rather than treating it as anomalous; new hint distinguishes pre-staged (apply gate + commit) from sandbox-reset (re-apply in tight pipeline).

Lessons Learned

Test plan

Master-PR CI is the load-bearing gate. Each implementer ran the scoped subset locally (fmt + native clippy + native test + wasm32 check + wasm32 clippy on touched crates).

CI gates to verify on this PR:

  • cargo fmt
  • cargo clippy workspace (native + wasm32)
  • cargo test workspace (state + client + web especially — Display tests + util.rs tests run as native unit tests)
  • wasm-pack browser tests (Firefox + geckodriver — observable on CI only)
  • cargo audit (3 stale ignores pruned; 7 active retained)
  • Playwright e2e (e2e/helpers/touch.ts + ui.ts + permissions.spec.ts changes; the e2e/.wait-timeout-baseline ratchet enforces no regression)

Known pre-existing test failure: willow-state::tests_materialize::non_admin_set_profile_is_accepted fails on HEAD (regression from PR #505, tracked at #565). Not introduced by this PR; CI may report it red.


Generated by Claude Code

claude added 11 commits May 3, 2026 16:15
The cargo-audit --ignore list contained three IDs that no longer match
any active advisory and were silently dead-weight:

  - RUSTSEC-2026-0098 (rustls-webpki name-constraint)
  - RUSTSEC-2026-0099 (rustls-webpki URI-constraint)
  - RUSTSEC-2026-0104 (rustls-webpki CRL panic)

Retains the 7 still-active entries flagged by `cargo audit` at HEAD
6404719:

  - RUSTSEC-2023-0089 (atomic-polyfill unmaintained, #318)
  - RUSTSEC-2024-0370 (proc-macro-error unmaintained, #317)
  - RUSTSEC-2024-0436 (paste unmaintained, #316)
  - RUSTSEC-2025-0141 (bincode 1.x unmaintained, #247)
  - RUSTSEC-2026-0097 (rand unsoundness, #246)
  - RUSTSEC-2026-0119 (hickory-proto O(n^2) name compression, #508)
  - RUSTSEC-2026-0120 (hickory-net NSEC3 unbounded loop, #509)

Verification:
  - YAML still parses (python3 yaml.safe_load).
  - Extracted run script passes `bash -n` and expands to exactly 7
    --ignore flags when the cargo invocation is dry-run.
  - `cargo audit` itself unavailable in the sandbox; the CI cargo-audit
    job remains the regression test.

Refs #592
`longPressAvatar` previously held the mouse down for a bare `500` ms
literal, with no link to the product-side threshold. If `HOLD_MS` ever
shifts above 500 ms — or the runner is under enough load that the
500 ms wall-clock wait elapses before the product timer fires — the
helper produces false negatives (issue #591).

Mirror the product threshold as `LONG_PRESS_MS` (350 ms — source:
`HOLD_MS` in `crates/web/src/components/long_press.rs:17`) and add
`LONG_PRESS_BUFFER_MS` (250 ms) to absorb scheduler jitter under CI
load. The default duration is now `LONG_PRESS_MS + LONG_PRESS_BUFFER_MS`
= 600 ms, with `durationMs` exposed as an optional override.

Considered option (b) from the audit — `page.clock.runFor(...)` like
`longPressWithClock` already does — but rejected as too invasive: it
would require installing `page.clock` in every caller. The constants
mirror co-locates the timing knob with the product threshold and is a
one-line change at the call site.

Refs #591
The trailing `await page.waitForTimeout(300)` after dispatching `touchend`
was an unobservable settle. Every real caller already waits on the
gesture's outcome (action-sheet visibility), so the 300 ms tax was pure
dead weight.

Brainstorm (Option A vs B):

- Option A (chosen): drop the wait entirely; require callers to
  `waitFor` whatever the press should produce. Survey of all 6 call
  sites (mobile-actions.spec.ts x4, helpers/ui.ts messageAction +
  reactToMessage) confirmed each one already does
  `expect(.shell-mobile .mobile-action-sheet.open).toBeVisible({...})`
  or `.waitFor({...})` immediately after the longPress call. No caller
  needs a new waitFor added. helpers.barrel.test.ts only `void`s the
  symbol for type-only smoke testing.
- Option B (rejected): bake `await page.locator('.shell-mobile
  .mobile-action-sheet.open').waitFor(...)` into the helper. Self-
  contained for the common case, but couples a generic gesture helper
  to one specific outcome, breaking any future caller that long-presses
  for a non-sheet flow (peer-card SAS, custom context menu, etc.).
  longPress is a gesture primitive, not an action-sheet opener.

Files modified:
- e2e/helpers/touch.ts: remove `waitForTimeout(300)` after touchend in
  the real-time `longPress`. `longPressWithClock` and `longPressAvatar`
  untouched (their waits are correct / already fixed under #591).

Refs #590

https://claude.ai/code/session_01V5SXuK7UbHJJEbkhwLR65B
Migrated 14 of 16 bare `page.waitForTimeout` calls in e2e/helpers/ui.ts
to selector-based `waitFor` per docs/specs/2026-04-27-event-based-waits-design.md.
Pre-edit count was 16 sites; post-edit 2 remain with `TODO(#589)` markers
where no observable destination selector exists.

Migration recipes applied:

- switchTab (1 site): wait for `[aria-selected="true"]` on the
  target tab button after click.
- messageAction mobile (1 site): wait for `.mobile-action-sheet.open`
  to be hidden after sheet-item click.
- messageAction desktop (3 sites): post-hover wait for `.action-trigger`
  visible; post-trigger-click wait for `.dropdown-item` visible;
  post-item-click wait for `.dropdown-item` hidden.
- editMessage (1 site): post-Enter wait for `.edit-bar` to unmount.
- deleteMessage (1 site): post-confirm wait for `.confirm-dialog`
  to unmount.
- reactToMessage mobile (1 site): wait for `.mobile-action-sheet.open`
  hidden after emoji tap.
- reactToMessage desktop (4 sites): same hover→trigger→dropdown→
  emoji-row pattern as messageAction; post-emoji wait for
  `.dropdown-item` hidden.
- kickPeer (2 sites): dropped redundant 500ms before `confirmBtn.waitFor`;
  post-confirm wait for `.confirm-dialog` unmount.

Sites left with TODO(#589) — no-condition wait, needs design:

- trustPeer / untrustPeer (2 sites): `propose_grant_admin` /
  `propose_revoke_admin` go through proposed-action voting; the
  local DOM does not change synchronously after the click, so there
  is no immediate destination selector to wait on. Callers verify
  trust state separately. Migrating these requires either a
  state-event hook (Peer.nextEvent) or a sync-aware helper, both
  out of scope for this mechanical migration.

Wait-timeout ratchet baseline updated from 39 → 10 to lock in the
improvement.

Refs #589
Removes the `test('mobile long-press opens the compare sheet', ...)`
block from `e2e/permissions.spec.ts` (previously around lines 239-253),
which carried an unconditional `test.skip(true, 'mobile member-list
surface deferred')` after a mobile-project gate. The body still ran
`setupTwoPeers` + `longPressAvatar` setup but the assertion never
fired, masquerading as coverage that did not exist.

Also drops the now-unused `longPressAvatar` import and refreshes the
stale top-of-describe comment that claimed "long-press / compare-sheet
tests below opt back in explicitly" — the remaining compare-sheet
tests are desktop-only.

Mobile compare-sheet long-press regression coverage will be restored
once the mobile member-list surface ships. Tracked at #595.

Refs #588
Replace `let _ = vm.borrow().handle_ice_candidate(&from, json);` at
crates/web/src/event_processing.rs:128 with an explicit `if let Err(e)`
branch that emits `tracing::warn!(?e, "handle_ice_candidate failed")`.

`VoiceManager::handle_ice_candidate` returns `Result<(), String>`, so
silently dropping its error meant any failure during the WebRTC voice-
call signaling path (missing connection for peer, malformed JSON, or
the underlying `RTCPeerConnection.addIceCandidate` rejecting) vanished
without a trace. The `handlers.rs` module-doc explicitly calls out
`let _ = ...` over `Result` as an antipattern; this brings the ICE
branch in line with that guidance.

No test added: triggering the failure path requires a JS-runtime mock
that makes `addIceCandidate` reject deterministically, which is out of
scope for an error-logging fix.

Refs #585
The FileReader result-error closure at crates/web/src/components/
file_share.rs:69 logged the JsValue with Debug formatting, which renders
opaquely as `JsValue(Object)`. Switch to the established pattern: try
`as_string()` first (handles string-typed JS errors cleanly) and fall
back to `{e:?}` only when the value is not a string. The log now uses a
structured `error` field so the extracted message is searchable.

Sites migrated: 1 (the FileReader::result Err arm). The other
`tracing::error!` calls in this file do not capture a JsValue, so they
are left untouched.

Refs #587
Previously crates/client/src/mutations.rs:948 built the
`ClientEvent::ProposalCreated { action_description, .. }` payload via
`format!("{action:?}")`, leaking Rust Debug rendering
(e.g. `KickMember { peer_id: EndpointId(...) }`) directly into UI display
strings.

Add structural `Display` impls for `ProposedAction` and `VoteThreshold`
in `crates/state/src/event.rs`, then swap the consumer at
`crates/client/src/mutations.rs:948` from `{:?}` to `{}`.

Rendered strings:
  - ProposedAction::GrantAdmin       -> "Grant admin to {peer_id}"
  - ProposedAction::RevokeAdmin      -> "Revoke admin from {peer_id}"
  - ProposedAction::KickMember       -> "Kick {peer_id}"
  - ProposedAction::SetVoteThreshold -> "Set vote threshold to {threshold}"
  - VoteThreshold::Majority          -> "majority"
  - VoteThreshold::Unanimous         -> "unanimous"
  - VoteThreshold::Count(n)          -> "{n} admins"

Peer ids render via `EndpointId`'s own Display (64-char hex). Display-name
resolution lives in willow-web's state, not willow-state — UI layers that
want richer rendering should consume the typed `ProposedAction` directly
rather than substring-matching this string. That UI follow-up is out of
scope for this fix.

Tests: 7 new display tests in crates/state/src/tests/voting.rs covering
each ProposedAction variant, each VoteThreshold variant, and the
threshold-embedded-in-action case. Tests use `EndpointId::from_bytes(&[0u8; 32])`
for a deterministic peer id and dynamically format the expected string with
`{peer}` to avoid hard-coding iroh's hex encoding.

Refs #571
`copy_to_clipboard` previously discarded the Promise returned by
`navigator.clipboard.writeText` and unconditionally ran the textarea +
`execCommand('copy')` legacy path. Two side effects: a hidden textarea
was momentarily appended/removed even when the modern API succeeded,
and a second copy could overwrite the first by ordering.

Keep the sync `pub fn copy_to_clipboard(text: &str)` signature — every
caller (`components/message.rs`, `components/chat.rs`,
`components/settings.rs`, `components/add_server.rs`) invokes it from a
non-async Leptos `on:click` closure. Internally, build the Promise from
`clipboard.write_text(text)`, await it inside `wasm_bindgen_futures::
spawn_local`, and only run the textarea fallback on `Err`. Happy path
is now silent and leaves the DOM untouched.

Runner-up considered: make `copy_to_clipboard` async and await each
caller. Rejected because every caller is already inside a non-async
`on:click` closure — that approach would push `spawn_local` to every
call site instead of centralising it once in the helper, and the
success/failure state isn't surfaced to callers today.

No tests added: a browser-tier integration test would have to mock
clipboard rejection (and the underlying behaviour is logging + ordering
of an existing fallback), not worth the harness complexity.

Refs #572
Adds sibling paragraph to the Implementer Agent step 8 sandbox-interference
note, capturing the inverse phenomenon: pre-staged uncommitted edits at
session start. Observed in ~40-50% of dispatches across PR #566 + #567
runs. Brief implementers to verify + gate + commit (don't redo).

Refs #567
@intendednull intendednull merged commit c40bcea into main May 4, 2026
8 checks passed
@intendednull intendednull deleted the claude/friendly-maxwell-m1efN branch May 4, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment