Captures stdout, stderr, exit status and start/finish timestamps for every
unrar sidecar invocation and exposes them through a dedicated "Unpack Logs"
window. Triggered by the need to debug why a particular game's archive
failed to extract -- previously the only artifact of a failed unpack was a
log line in the Tauri process stdout, which is awkward to inspect on an
end-user machine.
Implementation:
* `LanSpreadState` gains an in-memory ring buffer (`unpack_logs`) capped at
`MAX_UNPACK_LOGS` (100). The previous monolithic `do_unrar` is split into
`prepare_unrar_paths` and `run_unrar_sidecar` so every failure path (mkdir
failure, canonicalize failure, non-UTF-8 destination, sidecar spawn error,
non-zero exit) records an `UnpackLogEntry` before bailing.
* A `get_unpack_logs` Tauri command returns the current snapshot; an
`unpack-logs-updated` event is emitted after every write so the viewer can
refresh without polling.
* The React `App` component now routes on `?view=unpack-logs` and renders a
dedicated `UnpackLogsWindow`. The main window opens the viewer via
`WebviewWindow` with label `unpack-logs`; an existing window is focused
instead of being recreated.
Capability scoping: the new window is given its own capability file
(`capabilities/unpack-logs.json`) granting only `core:default`. The main
capability is unchanged in window scope and only gains the two permissions
the main window itself needs (`core:window:allow-set-focus` to focus an
existing log window, `core:webview:allow-create-webview-window` to spawn
it). Splitting the capability keeps the log window from inheriting
`shell:allow-open`, `dialog:default` and `store:default`, which it has no
reason to use.
Known limitations (intentionally out of scope here):
* Logs are process-local; they vanish on app restart. Persistence can be
added later if it turns out users want to inspect failures across runs.
* Entries are presented as a flat chronological list identified by archive
path. No per-game grouping or filtering yet -- the archive filename is
usually enough to identify the game in practice.
* The `unpack-logs-updated` event carries no payload; the viewer re-fetches
the full snapshot on every notification. Acceptable given the 100-entry
cap, but a payload-bearing event would be cheaper if the cap grows.
Test plan:
* `just clippy` and `just build` are clean.
* Manual: start the GUI, point it at a games directory containing at least
one peer-hosted game, trigger an install, then click "Unpack Logs". The
window should show one entry per unrar invocation with stdout, stderr,
status code and timestamps; stderr/error lines render in the warning
color. Triggering further unpacks should update the open window live via
the `unpack-logs-updated` event without manual refresh.
* Negative path: rename or remove the archive between handshake and
extraction to force a canonicalize failure; confirm a failed entry with
the corresponding stderr appears in the viewer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>