Commit Graph
344 Commits
Author SHA1 Message Date
ddidderr e5d70ae56f fix(call-to-play): acknowledge live replication
Raise the wire protocol to version 7 and add explicit Call to Play delivery
outcomes. Live requests now wait for an application acknowledgement, allowing
the sender to distinguish applied, duplicate, obsolete, incomplete, and
rejected updates instead of treating a successful write as acceptance.

Remove source-IP equality from actor verification. The receiver now requires
the envelope peer ID to be present in its known roster and requires every live
event actor to match that envelope. This matches the cooperative-LAN trust
model without misrepresenting the shared TLS identity as per-peer
authentication.

Transport failures, malformed responses, NeedHandshake, and NeedHistory each
trigger one asynchronous Hello/HelloAck resync. Rejections are logged without
retry, and local publication remains independent of remote availability.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed
- `git diff --cached --check` -- passed
2026-07-23 17:49:01 +02:00
ddidderr be7ad2e560 refactor(call-to-play): merge histories atomically
Replace per-event insertion with a transactional batch merge. The store now
validates and deduplicates an entire history before committing it, rejects
conflicting event IDs without partial mutation, evaluates retention after all
batch events are present, and reports applied, duplicate, obsolete, and
missing-root outcomes explicitly.

Derive event identity from retained history instead of preserving an unbounded
ID set. Expired histories can therefore be restored by a complete Create plus
AddTime batch, while orphan actions request history and terminal tombstones
continue to reject stale resurrection. Capacity applies only to unresolved
history, allowing Start and Cancel to settle a full call.

Only retained events reach the UI or live broadcast path. Handshake and live
merge callers log invalid or incomplete histories without publishing events
that compaction discarded.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed
- `git diff --cached --check` -- passed
2026-07-23 17:44:03 +02:00
ddidderr 872692e3f4 plan 2026-07-23 17:07:32 +02:00
ddidderr e141229805 fable5findings 2026-07-23 13:06:21 +02:00
ddidderr d58307c328 fix(call-to-play): compact expired snapshots
Expired call histories were removed on the next store insertion, so an otherwise
idle peer could continue carrying stale payload through handshakes after the
five-minute UI retention ended.

Run the same inactive-call compaction before local and handshake snapshots. This
makes the expiry boundary exact without trimming any event from an active call.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed, 147 peer tests
- `git diff --cached --check` -- passed
2026-07-21 22:59:48 +02:00
ddidderr 383e8f4855 fix(call-to-play): resync after live delivery failure
A failed fire-and-forget event left one peer's active call state divergent until
some later discovery or reconnect happened. During a LAN party that could leave
different players looking at different rosters or chat.

Fall back to the existing bidirectional handshake whenever a live Call to Play
send fails. Its active-history exchange heals both sides immediately when the
failure was transient. Document the related wall-clock synchronization
assumption for deadline and countdown presentation.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed, 147 peer tests
- `just peer-cli-tests S48` -- passed
- `git diff --cached --check` -- passed
2026-07-21 22:58:19 +02:00
ddidderr cc7dacf6c3 fix(call-to-play): expire elapsed calls clearly
Deadline completion previously shared the green Ready presentation with a full
roster and remained visible forever. An abandoned call therefore looked ready
to launch and required its creator to return and cancel it.

Give elapsed calls a distinct Time's up state and a five-minute grace period in
which the creator can start or extend them. After that, both the reducer and
peer store remove the call as a unit. Filled calls remain ready until their
deadline, and active calls continue to retain complete history for late joiners.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed, 147 peer tests
- `just frontend-test` -- passed, 22 tests
- `just build` -- passed
- `git diff --cached --check` -- passed
2026-07-21 22:56:02 +02:00
ddidderr 640d81d919 fix(call-to-play): explain unavailable game calls
Calls for a game missing from the local catalog still contributed to the badge
but were discarded by both the ticker and overlay. Opening the badge could
therefore show a blank modal with no explanation.

Render those calls with an unavailable-game label, the sender and game ID, the
normal roster and chat, and safe coordination actions. The creator can mark the
match started, but automatic launch remains disabled until catalog data exists.

Test Plan:
- `just fmt` -- passed
- `just frontend-test` -- passed, 21 tests
- `just build` -- passed
- `git diff --cached --check` -- passed
2026-07-21 22:50:09 +02:00
ddidderr 4b7725db16 fix(call-to-play): compact terminal histories
The 4,096-event store retained every completed call forever and local commands
only reported that they reached the queue. Once the bound was reached, GUI
actions could therefore fail with no user-visible result. The CLI snapshot wait
could also be satisfied by an unrelated live event.

Keep the complete event and chat history for every active call so late joiners
receive full context. When the creator starts or cancels a call, replace its
history with a single terminal tombstone; this bounds retained payload while
still healing peers that missed the live terminal action. Publish commands now
reply with the actual store result, and CLI snapshots use a direct reply.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed, including full-cap terminal compaction
- `just build` -- passed
- `just peer-cli-tests S48` -- passed
- `git diff --cached --check` -- passed
2026-07-21 22:48:19 +02:00
ddidderr 29eacabcc0 fix(call-to-play): key actors by stable peer identity
Participant maps and creator authorization previously used display names, so
two peers left at the default Commander name collapsed into one participant
and could exercise each other's creator controls through the normal client.

Carry a stable actor_id separately from actor_name. The peer overwrites actor_id
on every local publish, and live event envelopes are accepted only when the
known peer, source, and event actor match. The frontend keys participants and
authorization by actor_id while retaining actor_name for display. This follows
the trusted-LAN model and is not cryptographic authentication against a hostile
peer.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed
- `just frontend-test` -- passed, 21 tests
- `just build` -- passed
- `just peer-cli-tests S48` -- passed
- `git diff --cached --check` -- passed
2026-07-21 22:40:59 +02:00
ddidderr 0f53bc4b78 feat(call-to-play)!: coordinate game sessions across peers
Implement the launcher design as a production peer-to-peer feature. Call to
Play actions are immutable, validated events broadcast over the existing QUIC
control channel, deduplicated in a bounded in-memory history, and exchanged in
Hello/HelloAck so late joiners reconstruct current calls.

Add the Tauri bridge and modular launcher surfaces for play-now and scheduled
calls, check-in, readiness buffers, role-aware controls, chat, tickers, and
actual caller launch. A deterministic frontend reducer derives presentation
state from replicated history. Extend the JSONL peer harness with publish/list
commands and a three-peer live-delivery and late-join scenario.

This intentionally raises the only supported wire protocol from version 5 to
version 6; older builds are not supported. Document the transport architecture
and exclude generated peer-test state from Docker build contexts.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed
- `just frontend-test` -- passed, 20 tests
- `just build` -- passed
- `just peer-cli-tests S2 S48` -- passed
- `git diff --cached --check` -- passed
2026-07-21 22:30:11 +02:00
ddidderr 8f151e38b4 docs(launcher): add Call to Play specification and design reference
Extend the launcher design specification and prototype reference with the
"Call to Play" multiplayer coordination feature and clean up top-bar chrome.

Key additions and changes:
- Spec & Roadmap: Document Call to Play mechanics in SPEC.md, including Play
  Now / Scheduled call flavors, 15-minute check-in windows, top-bar button
  with active count badge, quick-bar ticker stack above grid, overlay modal,
  and per-call group chat. Update design/README.md.
- Components & Logic: Add calltoplay.jsx and ctp-chat.jsx components for call
  creation, status progression, and chat. Extend data.jsx with a mock peer
  roster (PEERS) and helper functions for peer install count tracking.
- Top-bar Cleanup: Move game-folder configuration from top bar into Settings ->
  Library, freeing top-bar space for the Call to Play action button.
- Styling & Layout: Add CTP quick-bar, ticker, badge, card, and chat styles to
  styles.css, and integrate CTP components into launcher.jsx, components.jsx,
  and SoftLAN Launcher.html.

Test Plan:
- `git diff --cached --check` -- passed (no trailing whitespace or conflict markers)
- `cargo check --workspace` -- passed cleanly
- Manual review of staged diff across 9 design/launcher files -- confirmed clean staging
2026-07-21 20:00:07 +02:00
ddidderr 8d54e6e954 [deps] cargo update
Adding   syn                 v3.0.2
Updating anyhow              v1.0.103 -> v1.0.104
Updating bytemuck            v1.25.1  -> v1.25.2
Updating cc                  v1.2.67  -> v1.3.0
Updating fastrand            v2.4.1   -> v2.5.0
Updating futures-channel     v0.3.32  -> v0.3.33
Updating futures-core        v0.3.32  -> v0.3.33
Updating futures-executor    v0.3.32  -> v0.3.33
Updating futures-io          v0.3.32  -> v0.3.33
Updating futures-macro       v0.3.32  -> v0.3.33
Updating futures-sink        v0.3.32  -> v0.3.33
Updating futures-task        v0.3.32  -> v0.3.33
Updating futures-util        v0.3.32  -> v0.3.33
Updating futures             v0.3.32  -> v0.3.33
Updating glob                v0.3.3   -> v0.3.4
Updating hyper               v1.10.1  -> v1.11.0
Updating libc                v0.2.186 -> v0.2.188
Updating proc-macro2         v1.0.106 -> v1.0.107
Updating quote               v1.0.46  -> v1.0.47
Updating ref-cast-impl       v1.0.25  -> v1.0.26
Updating ref-cast            v1.0.25  -> v1.0.26
Updating serde_core          v1.0.228 -> v1.0.229
Updating serde_derive        v1.0.228 -> v1.0.229
Updating serde_json          v1.0.150 -> v1.0.151
Updating serde_repr          v0.1.20  -> v0.1.21
Updating serde               v1.0.228 -> v1.0.229
Updating tauri-plugin-dialog v2.7.1   -> v2.7.2
Updating tauri-plugin-store  v2.4.3   -> v2.4.4
Updating thiserror-impl      v2.0.18  -> v2.0.19
Updating thiserror           v2.0.18  -> v2.0.19
Updating time-macros         v0.2.31  -> v0.2.32
Updating time                v0.3.53  -> v0.3.54
Updating tokio-util          v0.7.18  -> v0.7.19
Updating tokio               v1.53.0  -> v1.53.1
Updating zerocopy-derive     v0.8.54  -> v0.8.55
Updating zerocopy            v0.8.54  -> v0.8.55
2026-07-21 19:54:03 +02:00
ddidderr fa8bd86281 [deps] cargo update
Updating aws-lc-rs      v1.17.1             -> v1.17.3
Updating aws-lc-sys     v0.42.0             -> v0.43.0
Updating bitflags       v2.13.0             -> v2.13.1
Updating http-body-util v0.1.3              -> v0.1.4
Updating http-body      v1.0.1              -> v1.1.0
Updating mdns-sd        v0.20.1             -> v0.20.2
Updating mio            v1.2.1              -> v1.2.2
Updating regex-automata v0.4.15             -> v0.4.16
Updating regex          v1.13.0             -> v1.13.1
Updating rustls         v0.23.41            -> v0.23.42
Updating simd-adler32   v0.3.9              -> v0.3.10
Updating socket2        v0.6.4              -> v0.6.5
Updating spin           v0.9.8              -> v0.9.9
Updating syn            v2.0.118            -> v2.0.119
Updating tokio-macros   v2.7.0              -> v2.7.1
Updating tokio          v1.52.3             -> v1.53.0
Updating toml_edit      v0.25.12+spec-1.1.0 -> v0.25.13+spec-1.1.0
Updating toml           v1.1.2+spec-1.1.0   -> v1.1.3+spec-1.1.0
Updating toml_writer    v1.1.1+spec-1.1.0   -> v1.1.2+spec-1.1.0
Updating uuid           v1.23.4             -> v1.24.0
Updating winnow         v1.0.3              -> v1.0.4
Updating zmij           v1.0.21             -> v1.0.23
2026-07-18 01:16:56 +02:00
ddidderr 6379e97db4 [deps] cargo update
Removing flume                      v0.11.1
Removing pathdiff                   v0.2.3
Removing utf-8                      v0.7.6
Updating anyhow                     v1.0.102 -> v1.0.103
Updating aws-lc-rs                  v1.17.0  -> v1.17.1
Updating aws-lc-sys                 v0.41.0  -> v0.42.0
Updating bytemuck                   v1.25.0  -> v1.25.1
Updating bytes                      v1.12.0  -> v1.12.1
Updating camino                     v1.2.3   -> v1.2.4
Updating cc                         v1.2.65  -> v1.2.67
Updating chacha20                   v0.10.0  -> v0.10.1
Updating crossbeam-channel          v0.5.15  -> v0.5.16
Updating crossbeam-queue            v0.3.12  -> v0.3.13
Updating crossbeam-utils            v0.8.21  -> v0.8.22
Updating dbus                       v0.9.11  -> v0.9.12
Updating embed-resource             v3.0.9   -> v3.0.11
Updating inotify-sys                v0.1.5   -> v0.1.8
Updating inotify                    v0.11.2  -> v0.11.4
Updating jobserver                  v0.1.34  -> v0.1.35
Updating js-sys                     v0.3.102 -> v0.3.103
Updating libredox                   v0.1.17  -> v0.1.18
Updating log                        v0.4.32  -> v0.4.33
Updating mdns-sd                    v0.20.0  -> v0.20.1
Updating memchr                     v2.8.2   -> v2.8.3
Updating open                       v5.3.5   -> v5.4.0
Updating plist                      v1.9.0   -> v1.10.0
Updating quick-xml                  v0.39.4  -> v0.41.0
Updating quote                      v1.0.45  -> v1.0.46
Updating rand                       v0.10.1  -> v0.10.2
Updating regex-automata             v0.4.14  -> v0.4.15
Updating regex                      v1.12.4  -> v1.13.0
Updating rustc-hash                 v2.1.2   -> v2.1.3
Updating rustls-pki-types           v1.14.1  -> v1.15.0
Updating rustls                     v0.23.40 -> v0.23.41
Updating rustversion                v1.0.22  -> v1.0.23
Updating s2n-codec                  v0.82.0  -> v0.83.0
Updating s2n-quic-core              v0.82.0  -> v0.83.0
Updating s2n-quic-crypto            v0.82.0  -> v0.83.0
Updating s2n-quic-platform          v0.82.0  -> v0.83.0
Updating s2n-quic-rustls            v0.82.0  -> v0.83.0
Updating s2n-quic-tls-default       v0.82.0  -> v0.83.0
Updating s2n-quic-tls               v0.82.0  -> v0.83.0
Updating s2n-quic-transport         v0.82.0  -> v0.83.0
Updating s2n-quic                   v1.82.0  -> v1.83.0
Updating s2n-tls-sys                v0.3.38  -> v0.3.40
Updating s2n-tls                    v0.3.38  -> v0.3.40
Updating socket-pktinfo             v0.3.2   -> v0.4.0
Updating tauri-runtime-wry          v2.11.3  -> v2.11.4
Updating tauri                      v2.11.3  -> v2.11.5
Updating tendril                    v0.5.0   -> v0.5.1
Updating thread_local               v1.1.9   -> v1.1.10
Updating time-macros                v0.2.29  -> v0.2.31
Updating time                       v0.3.49  -> v0.3.53
Updating tinyvec                    v1.11.0  -> v1.12.0
Updating uuid                       v1.23.3  -> v1.23.4
Updating wasm-bindgen-futures       v0.4.75  -> v0.4.76
Updating wasm-bindgen-macro-support v0.2.125 -> v0.2.126
Updating wasm-bindgen-macro         v0.2.125 -> v0.2.126
Updating wasm-bindgen-shared        v0.2.125 -> v0.2.126
Updating wasm-bindgen               v0.2.125 -> v0.2.126
Updating web-sys                    v0.3.102 -> v0.3.103
Updating zerocopy-derive            v0.8.52  -> v0.8.54
Updating zerocopy                   v0.8.52  -> v0.8.54
2026-07-12 17:12:11 +02:00
ddidderr d376983296 fix: terminate unrar sidecars when the launcher closes mid-install
Bug report: unrar.exe kept running after closing the launcher during a
game install. The orphaned process kept extracting in the background and
held file handles on the staging directory.

Root cause (regular install path): run_unrar_sidecar ran the unrar
sidecar via tauri-plugin-shell's Command::output(). That helper spawns
the process on a detached SharedChild OS thread and immediately drops the
CommandChild; there is no Drop impl that kills the process. On app exit
only shutdown_peer_runtime ran, and Windows does not cascade-kill child
processes, so closing the launcher left unrar running.

Fix:
- run_unrar_sidecar now uses .spawn() instead of .output(), keeping a
  killable CommandChild. It registers the child in a new
  LanSpreadState.active_unrar_children registry and an RAII
  UnrarChildGuard deregisters it on every return path. The CommandEvent
  stream is drained to reproduce the exact stdout/stderr (NEWLINE_BYTE
  is b'\n'), status code, success flag, and UnpackLogEntry the old
  .output() produced, so logging behavior is unchanged.
- kill_active_unrar_children() runs in the RunEvent::Exit handler before
  shutdown_peer_runtime, killing every in-progress unrar. Killing first
  also lets the install task unwind so the runtime stops promptly.

Two concurrency hazards were closed in the registry design:
- Children are keyed by a monotonic id, not pid. A pid key let a
  finishing install's guard deregister a different install's child after
  the OS recycled the pid, which could re-orphan a live child.
- A shutting_down latch lives in the registry under the same mutex as
  the kill sweep. The sweep is a one-shot drain, so a child registered
  after it (a task caught between spawn() and registration, or a later
  archive in a multi-archive install -- unpack_archives does not observe
  the shutdown token) would be missed. Registration now checks the latch
  under that mutex and kills the child immediately instead of inserting
  it. Since registration and the sweep serialize on one mutex, every
  interleaving kills the child.

Also hardened the streamed-install sender path (ExternalUnrarStream
Provider) with kill_on_drop(true) on its tokio unrar spawns, so a
dropped or aborted producer task cannot orphan unrar there either.

Known limitation: a hard force-kill or crash of the launcher (e.g. Task
Manager -> End Task) bypasses RunEvent::Exit and is not covered. Making
that bulletproof would require a Windows Job Object with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; the reported "close the launcher"
case is fully fixed.

Test Plan:
- just clippy (pedantic, -D warnings): clean.
- just fmt: no changes.
- just test: all suites pass (incl. the 20 Tauri-lib unit tests).
- Manual (Windows): start a game install, close the launcher mid-extract,
  confirm no unrar.exe remains in Task Manager. Repeat with two
  concurrent installs and with a multi-archive game.
2026-06-22 07:07:45 +02:00
ddidderr 9d1cc771fc design: implement the actual SoftLAN wordmark 2026-06-21 21:27:54 +02:00
ddidderr 6415a4e517 design: updated design docs 2026-06-21 21:15:09 +02:00
ddidderr d9a6181ae2 design: seamless logo snake implemented 2026-06-21 20:49:30 +02:00
ddidderr 3d9d9cb450 design: seamless logo snake 2026-06-21 20:27:08 +02:00
ddidderr 14b30fe2da design: first impl of logo, still with glitch 2026-06-21 20:16:41 +02:00
ddidderr 2d53848e0c test(peer-cli): harden S1-S47 scenario suite against vacuous and flaky checks
An adversarial audit of the headless peer-to-peer scenario suite
(crates/lanspread-peer-cli/scripts/run_extended_scenarios.py, driven by
`just peer-cli-tests`) found assertions that passed even when the behavior
they claim to test was not happening, plus timing races and doc-vs-code
divergences. A full baseline run (S1-S47) passed beforehand, confirming these
were test-quality gaps, not peer regressions; the baseline output itself
exposed the worst offenders -- e.g. S14 chunk totals {128 MiB, 1 MiB} (a
two-chunk file whose "balanced within one chunk" check can never fail) and
S16/S18 serving the whole ~120 MiB alienswarm.eti from a single source, so
fanout and retry were never exercised.

Test-correctness fixes (a broken behavior could previously pass green):
- S18: the "no download-failed" check was dead -- it reused a LineWaiter
  already advanced past download-finished, so it scanned an empty tail.
  Replaced with assert_no_event_since over the whole window. Switched to a
  4*CHUNK_SIZE sparse archive so both peers get chunks; the test now proves the
  download SURVIVES a mid-download source kill (every byte delivered, survivor
  served part, no download-failed, clean diff). Retry-onto-survivor is the
  mechanism but is not asserted: the kill/serve race against `docker rm -f`
  cannot be forced, so asserting an exact split would be flaky.
- S7: the only check was a diff against byte-identical ggoo fixtures, so it was
  source-agnostic. Added assertions that the download committed exactly once,
  every chunk came from the validated two-peer set, both peers served, and no
  chunk was fetched twice.
- S14: enlarged to 4*CHUNK_SIZE so the balance check can fail under a 3+1
  imbalance; asserts an exact 2+2 split summing to the file size.
- S16: inflated the .eti to 2*CHUNK_SIZE so it fans out across both
  catalog-version peers (the stock 120 MiB fixture is a single chunk).
- S37: validate the throughput rate fields (positive, self-consistent
  mbit/mib == 8.388608, mib_per_s == bytes/duration), not just the byte count.
- S35: assert the source actually advertises the unknown game before checking
  it is filtered, so "absent" means "filtered" and not "never sent".
- S15: cross-check each peer's raw advertised eti_version via list-peers; the
  list-games eti_game_version is synthesized from the catalog and can only ever
  equal the asserted value.
- S2: poll for library convergence and verify the bidirectional exchange
  (bravo sees alpha's 3 games, not just alpha seeing bravo's 4).
- S12/S28: require the gating unit test to appear as "<name> ... ok" so an
  #[ignore]d (un-run) test no longer satisfies the check.
- S24/S25: assert the requested install=false final state.
- S34: assert exactly 21 coherent chunks (20 files + version.ini), 21 distinct
  paths, no duplicates, instead of a >= 21 floor.

Flake fixes:
- S19: force-kill the sole source right after download-begin on a 4*CHUNK_SIZE
  file and accept download-failed or download-peers-gone. The old graceful
  shutdown on a single-chunk file could let the transfer finish first, turning
  the expected failure into a download-finished. A chunk may complete before
  the kill lands, but the full transfer cannot, so the failure is deterministic.
- S26: use a large sparse source so the first operation is reliably still
  active when the duplicate request is issued (TOCTOU on active_operations);
  also assert the active operation == "Downloading".
- S11: drop the "listener address must change" assertion -- it tested the OS
  ephemeral-port allocator and could fail spuriously; keep the same-identity /
  no-duplicate invariant.

Coverage and determinism:
- S27: add handshake::tests::inbound_hello_from_self_is_ignored for the
  protocol-level self guard. The CLI scenario only exercises the CLI
  string-compare guard, which short-circuits before any network call, so the
  peer-crate guard had no test.
- find_fixture_game now iterates sorted(FIXTURES) so the ambiguous cnctw
  (fixture-bravo/multi/solid) resolves deterministically to fixture-bravo.

Reviewed and deliberately left as-is (documented in the run log): S20, S21,
S30, S32/S39/S44 absence checks, S42 IP-order precondition, S45.

PEER_CLI_SCENARIOS.md rows S2, S11, S14, S16, S18, S19, S27 are updated to
match the harness, and a dated run-log entry records the audit, the fixes, the
accepted items, and the live-run evidence.

Test Plan:
- `just peer-cli-tests` (rebuilds the image, runs S1-S47 in Docker): baseline
  passed; post-fix passed; a final run on the exact committed code passed
  47/47. Evidence: S14 {268435456, 268435456} balanced 2+2; S16 .eti split
  across B and C {134217728, 134217728}; S18 all 536870912 bytes delivered with
  no download-failed; S19 deterministic download-failed; S37 ~874 MiB/s.
- `just test` (incl. inbound_hello_from_self_is_ignored), `just clippy`
  (-D warnings, all-targets), and `just fmt` all pass.

Refs: PEER_CLI_SCENARIOS.md scenario matrix and 2026-06-21 run-log entry.
2026-06-21 10:23:45 +02:00
ddidderr 72fa61da65 feat(peer-cli): add just recipe for scenario matrix
Add a peer-cli-tests just recipe as the public entry point for the existing
extended scenario runner. The recipe depends on peer-cli-image, so Docker
uses its normal layer cache to decide whether the image needs rebuilding
before the Python harness runs.

This gives contributors one memorable command for the full S1-S47 matrix
while still allowing selected scenarios as positional arguments. The
underlying runner continues to own container topology and JSONL command/event
handling.

Test Plan:
- just --list
- just --dry-run peer-cli-tests S39 S40
- git diff --check

Refs: PEER_CLI_SCENARIOS.md
2026-06-20 20:45:31 +02:00
ddidderr b73b2b16c3 gitignore: add __pycache__ 2026-06-20 20:32:40 +02:00
ddidderr 0b8e1e7f92 refactor: prune unconsumed peer lifecycle events
An emit-vs-listen audit of the event surface showed the GUI is
state-as-source-of-truth: useGames renders the complete `games-list`
snapshot (full library + active_operations) and reconstructs status from
it, not from a stream of granular events. Several PeerEvents were emitted
but had no consumer at all -- no frontend `listen()` and no peer-cli
scenario assertion -- so they were pure dead weight that made the backend
look event-driven when it no longer is.

This prunes that dead surface in two parts.

1. Remove three PeerEvent variants with no consumer: InstallGameBegin,
   UninstallGameBegin, and RemoveDownloadedGameBegin. The operation-start
   transition is still observable via ActiveOperationsChanged (the
   snapshot already carries the Installing/Updating/Uninstalling/
   RemovingDownload kind), so nothing is lost. This drops their emit
   sites in handlers.rs, the begin-event assertions in the peer's
   lifecycle unit tests (the asserted sequence is now
   ActiveOperationsChanged(kind) -> LocalLibraryChanged ->
   ActiveOperationsChanged([]) -> *Finished), the peer-cli JSONL
   mappings (install-begin/uninstall-begin/remove-download-begin) plus
   the now-orphaned install_operation_name helper and InstallOperation
   import, and the matching Tauri handler arms.

2. Drop Tauri webview emits that no frontend listener consumed:
   peer-local-ready, game-download-begin, game-download-pre,
   game-download-finished, game-uninstall-finished, and
   peer-connected/-disconnected/-discovered/-lost. The log lines and all
   real side effects are kept (handle_got_game_files still forwards
   PeerCommand::DownloadGameFiles). The orphaned emit_peer_addr_event and
   handle_download_finished helpers and the now-unused SocketAddr import
   are removed. peer-runtime-failed is kept pending a decision on
   surfacing runtime failures in the GUI.

Why not re-wire instead: under state-as-source-of-truth, per-event UI
state is exactly the pattern this project abandoned. Live progress
already flows via game-download-progress, and the peer-cli's chunk,
timing-trigger, and transition assertions read events that are retained
(download-begin, download-chunk-finished, the *-finished/*-failed
terminals), so test coverage is unchanged.

Behavior change: none functional. The Tauri backend no longer emits
events nothing listened to; the GUI is unchanged. The peer-cli no longer
emits the three *-begin JSONL events. PeerEvent is a workspace-internal
UI-reporting type, not a wire-protocol type, so there is no protocol or
version impact and all consumers are updated in this commit.

Docs: PEER_CLI_SCENARIOS.md S39 no longer lists install-begin (with a
note that the start transition is visible via active-operations-changed),
and a dated Run Log entry records the removal. The historical 2026-05-18
run-log note is left intact as a dated observation.

Test Plan:
- just test: pass (incl. peer lifecycle event-sequence tests).
- just clippy: pass (-D warnings, all targets).
- just frontend-test: pass (11/11, incl. streamed-install gating/labels).
- just build: pass (release, no bundle).
- Not run: the Docker S39-S47 matrix (run_extended_scenarios.py); those
  scenarios never asserted the removed *-begin events, so coverage is
  unaffected. just fmt's tombi step needs network and was skipped; no
  TOML changed.

Refs: peer event-surface emit-vs-listen audit; no external consumers of
the removed events.
2026-06-20 19:54:53 +02:00
ddidderr c25dc7420e logo 2026-06-20 19:49:42 +02:00
ddidderr 3c3ec626fd [deps] cargo update
Removing alloc-no-stdlib        v3.0.0
Removing foldhash               v0.1.5
Removing hashbrown              v0.15.5
Removing id-arena               v2.3.0
Removing leb128fmt              v0.1.0
Removing prettyplease           v0.2.37
Removing unicode-xid            v0.2.6
Removing wasip3                 v0.4.0+wasi-0.3.0-rc-2026-01-06
Removing wasm-encoder           v0.244.0
Removing wasm-metadata          v0.244.0
Removing wasmparser             v0.244.0
Removing wit-bindgen-core       v0.51.0
Removing wit-bindgen-rust-macro v0.51.0
Removing wit-bindgen-rust       v0.51.0
Removing wit-bindgen            v0.51.0
Removing wit-component          v0.244.0
Removing wit-parser             v0.244.0
Updating alloc-stdlib           v0.2.3                          -> v0.2.4
Updating brotli-decompressor    v5.0.2                          -> v5.0.3
Updating brotli                 v8.0.3                          -> v8.0.4
Updating bytes                  v1.11.1                         -> v1.12.0
Updating camino                 v1.2.2                          -> v1.2.3
Updating cc                     v1.2.64                         -> v1.2.65
Updating getrandom              v0.4.2                          -> v0.4.3
Updating muda                   v0.19.2                         -> v0.19.3
Updating syn                    v2.0.117                        -> v2.0.118
Updating tauri-build            v2.6.2                          -> v2.6.3
Updating tauri-codegen          v2.6.2                          -> v2.6.3
Updating tauri-macros           v2.6.2                          -> v2.6.3
Updating tauri-plugin           v2.6.2                          -> v2.6.3
Updating tauri-runtime          v2.11.2                         -> v2.11.3
Updating tauri-runtime-wry      v2.11.2                         -> v2.11.3
Updating tauri-utils            v2.9.2                          -> v2.9.3
Updating tauri                  v2.11.2                         -> v2.11.3
Updating tray-icon              v0.23.1                         -> v0.24.1
Updating web_atoms              v0.2.4                          -> v0.2.5
2026-06-20 12:01:59 +02:00
ddidderr d5a09063f6 [deps] cargo update
Adding   alloc-no-stdlib            v3.0.0
Updating alloc-stdlib               v0.2.2            -> v0.2.3
Updating brotli-decompressor        v5.0.1            -> v5.0.2
Updating cc                         v1.2.63           -> v1.2.64
Updating intrusive-collections      v0.10.1           -> v0.10.2
Updating js-sys                     v0.3.100          -> v0.3.102
Updating memchr                     v2.8.1            -> v2.8.2
Updating s2n-tls-sys                v0.3.37           -> v0.3.38
Updating s2n-tls                    v0.3.37           -> v0.3.38
Updating smallvec                   v1.15.1           -> v1.15.2
Updating time-core                  v0.1.8            -> v0.1.9
Updating time-macros                v0.2.27           -> v0.2.29
Updating time                       v0.3.47           -> v0.3.49
Updating wasip2                     v1.0.3+wasi-0.2.9 -> v1.0.4+wasi-0.2.12
Updating wasm-bindgen-futures       v0.4.73           -> v0.4.75
Updating wasm-bindgen-macro-support v0.2.123          -> v0.2.125
Updating wasm-bindgen-macro         v0.2.123          -> v0.2.125
Updating wasm-bindgen-shared        v0.2.123          -> v0.2.125
Updating wasm-bindgen               v0.2.123          -> v0.2.125
Updating web-sys                    v0.3.100          -> v0.3.102
Updating zeroize_derive             v1.4.3            -> v1.5.0
Updating zeroize                    v1.8.2            -> v1.9.0
2026-06-14 13:11:36 +02:00
ddidderr 0693db94b2 fix(peer): clear remaining clippy warnings
`just clippy` was failing on peer test and helper code after the current lint
set denied warnings. This keeps the cleanup local to the reported warnings and
avoids changing runtime peer behavior or the wire protocol surface.

The test helpers now avoid needless ownership, active-operation assertions take
slices, and the local monitor watch-event helper no longer wraps a value that
cannot fail. The install recovery matrix keeps the same cases, but moves the
case table and per-case assertions into helpers so the test body stays below the
clippy line limit. The remaining lint fixes replace similar temporary names and
use an explicit `expect` message for test-only length conversion.

No separate project documentation update is needed because this only changes
internal test/helper structure and lint-only assertions; the existing peer
architecture docs remain accurate.

Test Plan:
- just fmt
- just clippy
- just test
- git diff --check
- git diff --cached --check

Refs: none
2026-06-13 10:16:48 +02:00
ddidderr 80c50c0db3 clippy: just fix 2026-06-13 09:52:03 +02:00
ddidderr 05cac57f3a justfile: harden clippy and tests 2026-06-13 09:50:23 +02:00
ddidderr c00e6eae84 fix(peer): drain streamed install senders after completion
A streamed install sender kept the original frame sink alive outside the
producer task. After the producer sent Complete, or an Error for a provider
failure, the forwarding loop still had a live mpsc sender in scope and waited
forever for another frame.

Move the sink into the producer so the channel closes when the producer exits.
That lets the QUIC writer close, the request task return, and the outbound
TransferGuard drop after successful streamed installs and provider-side
failures.

The peer-cli harness now keeps the outbound-transfer map it passes into the
peer runtime and exposes per-game counts in status. S39 asserts that the source
has no active outbound transfer for cnctw after the streamed install finishes,
which catches the sender-side lifecycle leak that receiver-only assertions
missed. The peer-cli README and scenario table document that status field and
expectation.

Test Plan:
- just fmt
- just test
- just clippy
- git diff --check
- git diff --cached --check
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S39 S40 --build-image
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S41 S42 S43 S44 S45 S46 S47

Refs: NEXT_STEPS.md streamed install lifecycle hardening
2026-06-11 08:31:12 +02:00
ddidderr 66c7d5912b fix(peer): harden streamed install lifecycle
Claude Fable 5's branch review found that receiver cancellation or a QUIC
send failure could leave the sender-side archive producer blocked on the
bounded frame channel. That kept the outbound transfer guard alive and could
block later installs or updates of the same game.

Route archive frames through a cancellable StreamInstallFrameSink instead of
exposing the raw channel sender to providers. The QUIC forwarder now cancels
and closes the receive side before awaiting the producer, so a blocked send
wakes and the transfer guard can drop normally.

Make PeerCommand::StreamInstallGame own its peer metadata preflight inside the
peer core. The Tauri layer now sends the command directly, and the peer runtime
fetches file details from catalog-version peers before running the existing
majority validation and retry logic. This removes the UI-only pending streamed
install set and gives PeerEvent::GotGameFiles one meaning again: continue a
normal archive download.

Tighten the receiver transaction edge cases too. Rollback removes a newly
created empty game root, but preserves pre-existing roots. Once streamed
staging has been promoted to local/, intent or launch-settings cleanup failures
are logged for startup recovery instead of reporting a failed install for bytes
that are already committed.

Accept missing RAR CRC32 metadata for zero-byte files as CRC32 00000000 while
still requiring CRC32 metadata for non-empty files. Update the peer README,
scenario docs, and next-steps handoff so the documented ownership and remaining
trust limitation match the implementation.

Test Plan:
- just fmt
- just test
- just frontend-test
- just clippy
- git diff --check
- python3 -m py_compile \
  crates/lanspread-peer-cli/scripts/run_extended_scenarios.py
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py \
  S39 S40 S41 S42 S43 S44 S45 S46 S47 --build-image

Refs: streamed-install review handoff from Claude Fable 5
2026-06-11 07:33:34 +02:00
ddidderr 9c765aba9c [deps] cargo update
Updating http                       v1.4.1   -> v1.4.2
Updating js-sys                     v0.3.99  -> v0.3.100
Updating regex-syntax               v0.8.10  -> v0.8.11
Updating regex                      v1.12.3  -> v1.12.4
Updating s2n-codec                  v0.81.0  -> v0.82.0
Updating s2n-quic-core              v0.81.0  -> v0.82.0
Updating s2n-quic-crypto            v0.81.0  -> v0.82.0
Updating s2n-quic-platform          v0.81.0  -> v0.82.0
Updating s2n-quic-rustls            v0.81.0  -> v0.82.0
Updating s2n-quic-tls-default       v0.81.0  -> v0.82.0
Updating s2n-quic-tls               v0.81.0  -> v0.82.0
Updating s2n-quic-transport         v0.81.0  -> v0.82.0
Updating s2n-quic                   v1.81.0  -> v1.82.0
Updating uuid                       v1.23.2  -> v1.23.3
Updating wasm-bindgen-futures       v0.4.72  -> v0.4.73
Updating wasm-bindgen-macro-support v0.2.122 -> v0.2.123
Updating wasm-bindgen-macro         v0.2.122 -> v0.2.123
Updating wasm-bindgen-shared        v0.2.122 -> v0.2.123
Updating wasm-bindgen               v0.2.122 -> v0.2.123
Updating web-sys                    v0.3.99  -> v0.3.100
Updating zerocopy-derive            v0.8.50  -> v0.8.52
Updating zerocopy                   v0.8.50  -> v0.8.52
2026-06-10 22:13:23 +02:00
ddidderr 47ef87748f test(peer-cli): align scenarios with catalog versions
Remote aggregation now filters to catalog-version roots, but the checked-in
peer-cli fixtures and skew scenarios still stamped synthetic future versions.
That hid fixture rows in S3 and left scenario docs asserting latest-version
behavior.

Teach the harness the catalog versions for fixture game IDs, stamp generated
fixtures with catalog versions by default, and update skew, mesh, propagation,
and throughput scenarios to expect only catalog-version peers. Also wire S38
into the executable matrix so the documented first-play launch-setting scenario
is covered by the same full run as S1-S47.

This keeps stale peers as negative coverage: they are absent from list-games and
cannot provide descriptors, votes, or chunks. The fixture version.ini updates
are checked in so alpha, bravo, charlie, and persona roots advertise
downloadable catalog games again.

Test Plan:
- python3 -m py_compile
  crates/lanspread-peer-cli/scripts/run_extended_scenarios.py
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py \
  S3 S8 S14 S15 S16 S17 S21 S22 S23 S24 S29 S30 S31 S34 S36 S37 \
  S39 S40 S41 S42 S43 S44 S45 S46 S47 --build-image
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S38
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py
- git diff --check
- git diff --cached --check

Docs: PEER_CLI_SCENARIOS.md
2026-06-08 07:06:21 +02:00
ddidderr f62515451b feat(ui): label streamed installs as not shareable
NEXT_STEPS item 7 needed the installed-but-not-downloaded state to be
clear to users. Keep streamed installs in the installed visual state so
sorting, filters, and the primary Play action stay unchanged, but make the
sharing limitation visible in the UI.

Cards now label that state as `Not shareable`, while the detail modal
status says `Installed, not shareable`. Downloaded-and-installed games
keep the normal `Installed` wording.

Test Plan:
- just frontend-test
- just build
- git diff --check
- git diff --cached --check

Refs: NEXT_STEPS.md item 7
2026-06-07 22:29:26 +02:00
ddidderr 9288fda037 test(peer-cli): expand streamed install edge coverage
NEXT_STEPS item 6 called for the remaining streamed-install edge cases to
be covered in the peer-cli matrix. Add S43-S47 for already-installed
rejection, corrupt archive rollback, sender disconnect, receiver cancel,
and sorted multi-archive streaming.

The receiver-cancel scenario needs the harness to drive the same runtime
path as the GUI, so `lanspread-peer-cli` now accepts a narrow
`cancel-download` command that forwards to `PeerCommand::CancelDownload`.
A parser test covers the new JSONL command shape.

Add `fixture-multi/cnctw`, a tiny two-archive RAR fixture. S47 uses it to
prove streamed installs process root `.eti` archives in sorted order and
commit only extracted `local/` payloads, not the root archives or
`version.ini` sentinel.

Test Plan:
- just fmt
- python3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.py
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S43 S44 S45 S46 S47 --build-image
- just test
- just clippy
- git diff --check
- git diff --cached --check

Refs: NEXT_STEPS.md item 6
2026-06-07 22:26:49 +02:00
ddidderr 88bfaeb04a test(peer-cli): cover streamed retry fallback
NEXT_STEPS item 5 needs streamed installs to have an explicit retry
policy. The handler already retries whole-stream attempts across the
majority-validated peer set, so add S42 to prove that behavior with the
Docker harness instead of leaving it implicit.

S42 starts two catalog-version-matching `cnctw` sources. The first source
sorts first in retry order but has `--unrar /missing-unrar`, so its stream
attempt fails before sending chunks. The second source then completes a
fresh whole-stream attempt. The scenario asserts local-only installed
state, no root archive or sentinel, no `.local.installing` staging
leftover, chunk events only from the good source, matching streamed byte
count, and SHA-256 payload equality against the good source's `unrar p`.

This pins the current policy: retry the entire stream from another
validated peer, do not preserve partial files across attempts, and do not
promise byte-offset resume.

Test Plan:
- python3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.py
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S42
- git diff --check
- git diff --cached --check

Refs: NEXT_STEPS.md item 5
2026-06-07 22:14:41 +02:00
ddidderr bb7497c0ff refactor(peer): name streamed integrity boundary
NEXT_STEPS item 4 needed the streamed-install integrity model to be a
conscious decision. Keep the current runtime behavior, but name it as
sender archive integrity: the receiver verifies streamed file size and
RAR CRC32 from the sender's archive metadata before committing the
install transaction.

This protects against truncation, transport corruption, and stream
provider bugs. It deliberately does not claim malicious-peer protection,
because the sender controls both the streamed bytes and the RAR metadata.
The docs now say that trusted content requires a future catalog schema
with catalog-owned archive or extracted-file SHA-256 hashes.

Test Plan:
- just fmt
- just test
- just clippy
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S41 --build-image
- git diff --check
- git diff --cached --check

Refs: NEXT_STEPS.md item 4
2026-06-07 22:05:03 +02:00
ddidderr 0e970dcec7 test(peer-cli): cover solid streamed installs
NEXT_STEPS item 3 needed solid archive handling to be a deliberate
contract instead of an incidental RAR header attribute. Add a tiny real
solid RAR fixture and S41 to the extended peer-cli scenarios so the
Docker harness proves this path end to end.

The scenario verifies the source archive with container-bundled
`unrar lt`, streams the install with the injected provider, and then
asserts the receiver is installed local-only without a root archive or
root `version.ini`. It also compares local payload SHA-256 hashes against
`unrar p` output and checks the streamed byte count matches the extracted
entries. This keeps the existing one metadata pass plus one sequential
payload pass contract covered for solid archives.

Test Plan:
- just fmt
- just test
- python3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.py
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S41 --build-image
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S41
- git diff --check
- git diff --cached --check

Refs: NEXT_STEPS.md item 3
2026-06-07 22:00:21 +02:00
ddidderr c313f7c9ae docs(stream-install): mark one-pass provider complete
NEXT_STEPS item 2 still described the old per-file unrar provider shape even
though the current shared provider now performs one technical listing pass and
one sequential unrar payload pass per archive. Update the roadmap so the next
implementation slice starts at the remaining solid-archive policy work instead
of chasing an already-replaced extraction loop.

The item 3 wording now keeps the solid/non-solid archive fork explicit without
suggesting the current provider still needs to be swapped merely to avoid
per-file extraction.

Test Plan:
- git diff --check

Refs: NEXT_STEPS.md item 2
2026-06-07 21:40:48 +02:00
ddidderr 40697a73e5 feat(tauri): add low-disk streamed install action
NEXT_STEPS item 1 called out that streamed install was still CLI-only
because the Tauri app started the peer with no stream provider. Users can now
choose an explicit "Low disk install" action from the game detail modal for
remote-only games instead of taking the default archive-preserving download
path.

The GUI command queues a normal peer detail fetch first so the peer database
has the file metadata needed for source validation. A small pending handoff in
Tauri routes the resulting GotGameFiles event into StreamInstallGame instead
of DownloadGameFiles, and clears that pending state on no-peer or download
failure events. This keeps the existing download continuation untouched for
the default action.

The external unrar stream provider moved from the CLI harness into
lanspread-peer so CLI and Tauri use the same implementation. Tauri resolves
the bundled unrar sidecar path and injects that provider at peer startup;
falling back to the noop provider keeps peer startup alive if the sidecar
cannot be resolved, while the streamed install operation still fails safely.

Test Plan:
- just fmt
- just test
- just frontend-test
- just clippy
- just build
- git diff --check

Refs: NEXT_STEPS.md item 1
2026-06-07 21:39:02 +02:00
ddidderr 389511f620 remove NEXT_STEPS_CLAUDES_REVIEW.md, it has been applied 2026-06-07 21:22:21 +02:00
ddidderr 5dd356eca8 fix(stream-install)!: stream archive payloads as raw frames
Streamed installs were sending FileChunk payloads through the shared JSON
Message impl. serde_json serializes bytes as arrays of integers, which
bloats wire traffic and burns CPU on large archives. Replace
StreamInstallFrame encoding with tagged frames: JSON control frames keep
their shape under tag 0, while file chunks carry raw bytes under tag 1.

The stream install metadata now carries unpacked archive size and mandatory
CRC32. The CLI unrar provider validates CRCs up front, runs one
archive-wide unrar p stream, splits stdout by listed file sizes, and
refuses trailing or missing bytes. That avoids solid archive
re-decompression and sidesteps unrar wildcard masks for path arguments.

Receivers now sample existing download progress events for streamed
installs, report staging-relative chunk paths, and retry trusted peers with
a fresh streamed-install transaction after a failed attempt. The current
protocol policy does not preserve compatibility with older stream-install
builds.

Test Plan:
- just fmt
- just test
- just clippy
- git diff --check
- git diff --cached --check

BREAKING CHANGE: StreamInstallFrame now uses tagged frames with raw chunk
payloads and requires current peers on both sides of streamed installs.

Refs: NEXT_STEPS_CLAUDES_REVIEW.md
2026-06-07 21:12:15 +02:00
ddidderr cc147def73 Claude's Review notes 2026-06-07 20:40:33 +02:00
ddidderr 373def6d44 feat(peer): prototype streamed installs
Add a streamed-install prototype that can receive archive-derived install bytes
straight into local/ without first storing the peer-owned root archive payload.
This is intended for low-disk clients that want to install a game but opt out of
becoming a downloadable peer source for that game.

The protocol gains a current-version-only StreamInstall request and framed
StreamInstallFrame responses. The peer core owns the generic transport,
transaction, path validation, size checks, CRC32 verification, and lifecycle
state. The archive-specific work is hidden behind StreamInstallProvider so the
prototype can use unrar while the final implementation can swap in a better
provider without rewriting the peer command path.

The receiver writes into .local.installing and only promotes to local/ after the
full stream verifies. It deliberately does not write the root version.ini or
archive files, so the settled local state is installed=true, downloaded=false,
and availability=LocalOnly. That preserves the existing rule that local/ is not
served to peers and makes streamed receivers non-sources by construction.

The CLI is the only caller for now. It exposes stream-install and provides the
prototype unrar implementation with unrar lt for entry metadata and unrar p for
file bytes. This is simple and good enough to prove non-solid archive streaming,
but it is not the production provider shape for solid archives because per-file
unrar p would repeatedly decompress prefixes. The Tauri app explicitly passes
stream_install_provider: None, so the GUI behavior stays unchanged until a real
product path is designed.

Document the production-readiness work in NEXT_STEPS.md. The main follow-up is
to make the provider abstraction final-ish and replace the per-file CLI unrar
provider with a one-pass archive provider, then wire a deliberate GUI low-disk
mode, retry semantics, and broader failure scenarios.

Test Plan:
- just fmt
- RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just test
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py \
  S39 S40 --build-image
- RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just clippy
- git diff --check
- git diff --cached --check

Follow-up: NEXT_STEPS.md
2026-06-07 20:32:05 +02:00
ddidderr 8a8437036d fix: justfile default must be "run" not "setup" 2026-06-07 19:19:03 +02:00
ddidderr 639a06e224 deps: deno update --latest 2026-06-07 19:18:37 +02:00
ddidderr 27a054fcb7 cleanup: remove unnecessary THEPLAN.md 2026-06-07 19:18:16 +02:00
ddidderr 72659eab2e [deps] cargo update
Updating bitflags v2.12.1 -> v2.13.0
Updating hashlink v0.11.0 -> v0.11.1
2026-06-07 19:02:10 +02:00