feat(app): expose local sharing and verified transfers

Add a durable, acknowledged Local network sharing switch with fail-closed
hydration, serialized mutation, and redacted ephemeral-identity diagnostics.
Keep local Call-to-Play state available while gating every network action on the
effective sharing generation.

Render revisioned verification, invalid-source retry, and sticky source
exhaustion states. Preserve opaque attempt IDs through progress delivery so
out-of-order webview events cannot attach stale bytes to a successor transfer,
and keep terminal exhaustion visible after the last source departs.

Own listeners, native invokes, persistence, dialogs, and companion-window
creation through webview close. Late creation is settled and cleaned before the
parent realm is destroyed.

Test Plan:
- `just frontend-test` -- passed (91/91)
- `just build` -- passed with TypeScript, Vite, and release Tauri compilation
- `just test` -- passed on the completed stack (708 workspace tests)
- `just clippy` -- passed on the completed stack
- `git diff --cached --check` -- passed
This commit is contained in:
ddidderr committed 2026-08-10 13:59:40 +02:00
1 parent 60fd7ba0c2
commit 71dbf27d8b
42 files changed
+7457 -2101

No files matched your search

@@ -1,187 +1,213 @@
import { Modal } from '../Modal';
import { Icon } from '../Icon';
import { GameCover } from '../grid/GameCover';
import { StateChip } from '../StateChip';
import { ActionButton } from '../ActionButton';
import { Modal } from "../Modal";
import { Icon } from "../Icon";
import { GameCover } from "../grid/GameCover";
import { StateChip } from "../StateChip";
import { ActionButton } from "../ActionButton";
import { Game, InstallStatus } from '../../lib/types';
import { canStreamInstall, gameStatusLabel, hasNewerLocalVersion, isInProgress } from '../../lib/gameState';
import { formatBytes, formatEtiVersion, formatPlayers } from '../../lib/format';
import { Game, InstallStatus } from "../../lib/types";
import {
canStreamInstall,
gameStatusLabel,
gameTransferStatusPresentation,
hasNewerLocalVersion,
isInProgress,
} from "../../lib/gameState";
import { formatBytes, formatEtiVersion, formatPlayers } from "../../lib/format";
interface Props {
game: Game;
thumbnailUrl: string | null;
onClose: () => void;
onPrimary: (game: Game) => void;
onStreamInstall: (game: Game) => void;
onUninstall: (game: Game) => void;
onRemoveDownload: (game: Game) => void;
onCancelDownload: (game: Game) => void;
onStartServer: (game: Game) => void;
onViewFiles: (game: Game) => void;
game: Game;
thumbnailUrl: string | null;
supportsStreamedInstall: boolean;
onClose: () => void;
onPrimary: (game: Game) => void;
onStreamInstall: (game: Game) => void;
onUninstall: (game: Game) => void;
onRemoveDownload: (game: Game) => void;
onCancelDownload: (game: Game) => void;
onStartServer: (game: Game) => void;
onViewFiles: (game: Game) => void;
}
const tagsFromGame = (game: Game): string[] => {
const tags: string[] = [];
if (game.genre) tags.push(game.genre);
if (game.publisher) tags.push(game.publisher);
if (game.release_year) tags.push(game.release_year);
return tags;
const tags: string[] = [];
if (game.genre) tags.push(game.genre);
if (game.publisher) tags.push(game.publisher);
if (game.release_year) tags.push(game.release_year);
return tags;
};
export const GameDetailModal = ({
game,
thumbnailUrl,
onClose,
onPrimary,
onStreamInstall,
onUninstall,
onRemoveDownload,
onCancelDownload,
onStartServer,
onViewFiles,
game,
thumbnailUrl,
supportsStreamedInstall,
onClose,
onPrimary,
onStreamInstall,
onUninstall,
onRemoveDownload,
onCancelDownload,
onStartServer,
onViewFiles,
}: Props) => {
const tags = tagsFromGame(game);
// Some game metadata contains a literal <br>; keep sanitization exact.
const description = game.description.split('<br>').join('');
const canRemoveDownload = game.downloaded
&& !game.installed
&& !isInProgress(game.install_status);
const showStreamInstall = canStreamInstall(game);
const canViewFiles = game.downloaded
|| game.installed
|| game.install_status === InstallStatus.Downloading
|| game.install_status === InstallStatus.Installing;
const newerThanExpected = hasNewerLocalVersion(game);
const newerStatus = newerThanExpected
? `Local version ${formatEtiVersion(game.local_version)} is newer than expected ${formatEtiVersion(game.eti_game_version)}.`
: undefined;
const hasOutbound = game.active_outbound_transfers !== undefined && game.active_outbound_transfers > 0;
const outboundStatus = hasOutbound
? `Sharing to ${game.active_outbound_transfers} peer${game.active_outbound_transfers === 1 ? '' : 's'}.`
: undefined;
const statusMessage = outboundStatus ?? game.status_message ?? newerStatus;
const statusLevel = hasOutbound
? 'info'
: (game.status_level ?? (newerStatus ? 'warning' : undefined));
return (
<Modal onClose={onClose}>
<button className="modal-close" type="button" onClick={onClose} aria-label="Close">
<Icon.close />
const tags = tagsFromGame(game);
// Some game metadata contains a literal <br>; keep sanitization exact.
const description = game.description.split("<br>").join("");
const canRemoveDownload = game.downloaded &&
!game.installed &&
!isInProgress(game.install_status);
const showStreamInstall = canStreamInstall(game, supportsStreamedInstall);
const canViewFiles = game.downloaded ||
game.installed ||
game.install_status === InstallStatus.Downloading ||
game.install_status === InstallStatus.Installing;
const newerThanExpected = hasNewerLocalVersion(game);
const newerStatus = newerThanExpected
? `Local version ${
formatEtiVersion(game.local_version)
} is newer than expected ${formatEtiVersion(game.eti_game_version)}.`
: undefined;
const hasOutbound = game.active_outbound_transfers !== undefined &&
game.active_outbound_transfers > 0;
const outboundStatus = hasOutbound
? `Sharing to ${game.active_outbound_transfers} peer${
game.active_outbound_transfers === 1 ? "" : "s"
}.`
: undefined;
const transferStatus = gameTransferStatusPresentation(game.transfer_status);
const statusMessage = transferStatus?.message ?? outboundStatus ??
game.status_message ?? newerStatus;
const statusLevel = transferStatus?.level ??
(hasOutbound
? "info"
: (game.status_level ?? (newerStatus ? "warning" : undefined)));
return (
<Modal onClose={onClose}>
<button
className="modal-close"
type="button"
onClick={onClose}
aria-label="Close"
>
<Icon.close />
</button>
<div className="modal-hero">
<GameCover
game={game}
aspect="banner"
thumbnailUrl={thumbnailUrl}
hideTitle
/>
<div className="modal-hero-fade" />
<div className="modal-hero-text">
{tags.length > 0 && (
<div className="modal-tags">
{tags.map((t) => <span key={t} className="modal-tag">{t}</span>)}
</div>
)}
<h2 className="modal-title">{game.name}</h2>
</div>
<div className="modal-state">
<StateChip game={game} showNone />
</div>
</div>
<div className="modal-body">
<div className="modal-meta">
<div className="meta-cell">
<div className="meta-label">Size</div>
<div className="meta-value">{formatBytes(game.size)}</div>
</div>
<div className="meta-cell">
<div className="meta-label">Players</div>
<div className="meta-value">
<Icon.users /> {formatPlayers(game.max_players)}
</div>
</div>
<div className="meta-cell">
<div className="meta-label">Version</div>
<div className="meta-value meta-mono">
{formatEtiVersion(game.eti_game_version ?? game.local_version)}
</div>
</div>
<div className="meta-cell">
<div className="meta-label">Status</div>
<div className="meta-value">{gameStatusLabel(game)}</div>
</div>
</div>
{description && <p className="modal-desc">{description}</p>}
{statusMessage && (
<p
className={`modal-status${statusLevel ? ` is-${statusLevel}` : ""}`}
>
{statusMessage}
</p>
)}
<div className="modal-actions">
<ActionButton
game={game}
size="lg"
onClick={() => onPrimary(game)}
onCancelDownload={onCancelDownload}
/>
{showStreamInstall && (
<button
type="button"
className="ghost-btn"
title="Install without keeping archive files"
onClick={() => onStreamInstall(game)}
>
<Icon.install />
<span>Low disk install</span>
</button>
<div className="modal-hero">
<GameCover game={game} aspect="banner" thumbnailUrl={thumbnailUrl} hideTitle />
<div className="modal-hero-fade" />
<div className="modal-hero-text">
{tags.length > 0 && (
<div className="modal-tags">
{tags.map(t => <span key={t} className="modal-tag">{t}</span>)}
</div>
)}
<h2 className="modal-title">{game.name}</h2>
</div>
<div className="modal-state">
<StateChip game={game} showNone />
</div>
</div>
<div className="modal-body">
<div className="modal-meta">
<div className="meta-cell">
<div className="meta-label">Size</div>
<div className="meta-value">{formatBytes(game.size)}</div>
</div>
<div className="meta-cell">
<div className="meta-label">Players</div>
<div className="meta-value">
<Icon.users /> {formatPlayers(game.max_players)}
</div>
</div>
<div className="meta-cell">
<div className="meta-label">Version</div>
<div className="meta-value meta-mono">
{formatEtiVersion(game.eti_game_version ?? game.local_version)}
</div>
</div>
<div className="meta-cell">
<div className="meta-label">Status</div>
<div className="meta-value">{gameStatusLabel(game)}</div>
</div>
</div>
{description && (
<p className="modal-desc">{description}</p>
)}
{statusMessage && (
<p className={`modal-status${statusLevel ? ` is-${statusLevel}` : ''}`}>
{statusMessage}
</p>
)}
<div className="modal-actions">
<ActionButton
game={game}
size="lg"
onClick={() => onPrimary(game)}
onCancelDownload={onCancelDownload}
/>
{showStreamInstall && (
<button
type="button"
className="ghost-btn"
title="Install without keeping archive files"
onClick={() => onStreamInstall(game)}
>
<Icon.install />
<span>Low disk install</span>
</button>
)}
{game.installed && game.can_host_server === true && (
<button
type="button"
className="act-btn act-lg act-server"
onClick={() => onStartServer(game)}
>
<Icon.server />
<span className="act-label">Start Server</span>
</button>
)}
{game.installed && (
<button
type="button"
className="ghost-btn ghost-danger"
onClick={() => onUninstall(game)}
>
<Icon.trash />
<span>Uninstall</span>
</button>
)}
{canRemoveDownload && (
<button
type="button"
className="ghost-btn ghost-danger"
onClick={() => onRemoveDownload(game)}
>
<Icon.trash />
<span>Remove files</span>
</button>
)}
{canViewFiles && (
<>
<div className="modal-actions-spacer" />
<button
type="button"
className="ghost-btn"
onClick={() => onViewFiles(game)}
>
<Icon.folder />
<span>View Files</span>
</button>
</>
)}
</div>
</div>
</Modal>
);
)}
{game.installed && game.can_host_server === true && (
<button
type="button"
className="act-btn act-lg act-server"
onClick={() => onStartServer(game)}
>
<Icon.server />
<span className="act-label">Start Server</span>
</button>
)}
{game.installed && (
<button
type="button"
className="ghost-btn ghost-danger"
onClick={() => onUninstall(game)}
>
<Icon.trash />
<span>Uninstall</span>
</button>
)}
{canRemoveDownload && (
<button
type="button"
className="ghost-btn ghost-danger"
onClick={() => onRemoveDownload(game)}
>
<Icon.trash />
<span>Remove files</span>
</button>
)}
{canViewFiles && (
<>
<div className="modal-actions-spacer" />
<button
type="button"
className="ghost-btn"
onClick={() => onViewFiles(game)}
>
<Icon.folder />
<span>View Files</span>
</button>
</>
)}
</div>
</div>
</Modal>
);
};