The launcher was mixing lifecycle event handlers with the games-list snapshot
when deciding the card status. That left multiple writers for the same
install_status field and made event ordering visible in React.
Make games-list-updated active_operations the authoritative source for busy
status. Lifecycle events no longer mutate the card status; they only keep their
non-status side effects such as rescans and error messages. The only remaining
optimistic status is CheckingPeers before the backend emits its next snapshot.
Add a frontend reducer test that proves an install stays in Installing while an
active install snapshot exists, then settles to Installed only after the active
operation clears with installed local state.
Test Plan:
- git diff --check
- just fmt
- just frontend-test
- just build
Refs: local install/download status snapshot cleanup
The peer CLI Docker recipes failed on this host because Docker Buildx tried to
write activity metadata under /home/pfs/.docker, which is read-only here. Use a
repo-local ignored Docker config by default, while still respecting an explicit
DOCKER_CONFIG from the caller.
This keeps the peer-cli image and network recipes runnable without requiring
host-global Docker client state to be writable.
Test Plan:
- just peer-cli-image
Refs: PEER_CLI_SCENARIOS.md
The previous `peer-cli-run` recipe attached containers with `--network host`.
That makes every peer share the host network namespace, which means two
harness containers on the same machine cannot independently advertise mDNS
or look like distinct LAN devices: they all reuse the host's interface, the
mDNS daemon is single-instance per namespace, and any port the peer binds
is a host-wide port. That defeats the whole point of running multiple
peers side-by-side for end-to-end testing.
Switch the recipe to a Docker macvlan network. Each container gets its own
MAC and IP carved out of the real LAN subnet, sends and receives multicast
on the parent NIC, and appears to the rest of the home network as a fresh
device. mDNS discovery then works between peers exactly as it would for
two laptops on the same LAN, with no relay, reflector, or special routing.
Add a `peer-cli-net` recipe that creates the network idempotently (the
`docker network inspect` short-circuits when it already exists), make
`peer-cli-run` depend on it, and parameterise the parent interface,
subnet, and gateway as justfile variables so they can be overridden from
the command line for machines whose LAN does not match the defaults:
just LANSPREAD_PARENT_IFACE=enp4s0 \
LANSPREAD_SUBNET=10.0.0.0/24 \
LANSPREAD_GATEWAY=10.0.0.1 \
peer-cli-run alpha
The well-known macvlan limitation that the host cannot reach its own
macvlan children over the network is intentionally not worked around:
agents drive each peer through `docker run -i` stdin/stdout, which is the
docker control socket, not the LAN. Host-to-peer connectivity is not part
of the mental model and is not needed for any current test scenario.
Test Plan:
- `just peer-cli-image`
- `docker network create -d macvlan ... lanspread` succeeds on a host with
the default `eth0` interface (or with overridden variables on others).
- `just peer-cli-run alpha` and `just peer-cli-run beta` in two terminals;
both containers come up on the LAN with distinct IPs and discover each
other via mDNS without any `connect` command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Agents need a way to exercise multiple peers without launching the Tauri GUI.
Add `lanspread-peer-cli` as a workspace crate that starts the core peer runtime,
reads JSON commands from stdin, and writes result, event, and error records as
JSONL on stdout.
The harness supports status, peer listing, game listing, direct connect,
set-game-dir, download, install, uninstall, wait-peers, and shutdown commands.
It can seed tiny fixture archives that use a fixture unpacker, or delegate real
archives to an external `unrar` program when one is supplied.
Add a Dockerfile, `.dockerignore`, and `just` recipes for building the binary,
building the image, and running named harness containers with state and games
mounted under `target/peer-cli/`. The documentation now lists the crate and the
new test harness commands in the project map, with a crate-local README for the
JSONL protocol.
This commit depends on the non-GUI peer hooks introduced in the previous commit:
startup options, local-ready events, direct connects, snapshots, and explicit
post-download install policy. It does not add old-peer compatibility paths.
Test Plan:
- `git diff --check`
- `just fmt`
- `just clippy`
- `just test`
- `just peer-cli-build`
- Not run: `just peer-cli-image` requires a Docker daemon and base image access.
Depends-on: e711cf3454
Refs: crates/lanspread-peer-cli/README.md
Implement the peer-owned state model from PLAN.md. A root-level version.ini
is now the download completion sentinel, local/ as a directory is the install
predicate, and exact root-level version.ini detection prevents nested files
from becoming sentinels by accident.
Add the peer operation table that gates downloads, installs, updates, and
uninstalls by game ID. Serving paths now reject non-catalog games, active
operations, missing sentinels, and any request that points under local/.
Remote aggregation treats LocalOnly peers as non-downloadable so they do not
contribute peer counts, candidate source selection, or latest-version checks.
Move install-side filesystem mutation into lanspread-peer::install. The new
module writes atomic .lanspread.json intents, uses .local.installing and
.local.backup with .lanspread_owned markers, and performs startup recovery
from recorded intent plus filesystem state. Downloads now buffer version.ini
chunks in memory and commit the sentinel last through .version.ini.tmp.
Replace the fixed 15-second monitor with notify-backed non-recursive watches,
per-ID rescan gating, and a 300-second fallback scan. The optimized rescan
path updates one cached library-index entry and active operation IDs preserve
their previous summary during scans.
Test Plan:
- just fmt
- just clippy
- just test
- just build
Refs: PLAN.md