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
This commit is contained in:
31 files changed
+3032
-21
No files matched your search
@@ -10,12 +10,15 @@ import { ConfirmRemoveDownloadModal } from '../components/modals/ConfirmRemoveDo
|
||||
import { SettingsDialog } from '../components/modals/SettingsDialog';
|
||||
import { NoDirectoryState } from '../components/empty/NoDirectoryState';
|
||||
import { EmptyResultsState } from '../components/empty/EmptyResultsState';
|
||||
import { CallToPlayTicker } from '../components/calltoplay/CallToPlayTicker';
|
||||
import { CallToPlayOverlay } from '../components/calltoplay/CallToPlayOverlay';
|
||||
|
||||
import { useGameDirectory } from '../hooks/useGameDirectory';
|
||||
import { useGames } from '../hooks/useGames';
|
||||
import { useGameActions } from '../hooks/useGameActions';
|
||||
import { useThumbnails } from '../hooks/useThumbnails';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useCallToPlay } from '../hooks/useCallToPlay';
|
||||
|
||||
import { Game } from '../lib/types';
|
||||
import { applyFilterAndSort, countByFilter, needsUpdate } from '../lib/gameState';
|
||||
@@ -72,10 +75,13 @@ export const MainWindow = () => {
|
||||
const games = useGames(rescan);
|
||||
const actions = useGameActions(games, settings);
|
||||
const thumbnails = useThumbnails();
|
||||
const callToPlay = useCallToPlay(settings.username);
|
||||
|
||||
const [openGameId, setOpenGameId] = useState<string | null>(null);
|
||||
const [removeGameId, setRemoveGameId] = useState<string | null>(null);
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [callToPlayOpen, setCallToPlayOpen] = useState(false);
|
||||
const [focusedCallId, setFocusedCallId] = useState<string | null>(null);
|
||||
const visibleGames = useMemo(
|
||||
() => hasGameDirectory ? games.games : [],
|
||||
[games.games, hasGameDirectory],
|
||||
@@ -154,8 +160,22 @@ export const MainWindow = () => {
|
||||
sort={settings.sort}
|
||||
setSort={(v) => setSetting('sort', v)}
|
||||
kebabItems={kebabItems}
|
||||
nominations={callToPlay.nominations}
|
||||
onOpenCallToPlay={() => {
|
||||
setFocusedCallId(null);
|
||||
setCallToPlayOpen(true);
|
||||
}}
|
||||
/>
|
||||
<main className="grid-wrap">
|
||||
<CallToPlayTicker
|
||||
nominations={callToPlay.nominations}
|
||||
games={games.games}
|
||||
accent={settings.accent}
|
||||
onOpen={(callId) => {
|
||||
setFocusedCallId(callId);
|
||||
setCallToPlayOpen(true);
|
||||
}}
|
||||
/>
|
||||
{hasGameDirectory ? (
|
||||
<>
|
||||
<ResultsBar shown={filteredGames.length} total={counts.all} />
|
||||
@@ -220,6 +240,25 @@ export const MainWindow = () => {
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{callToPlayOpen && (
|
||||
<CallToPlayOverlay
|
||||
nominations={callToPlay.nominations}
|
||||
games={games.games}
|
||||
username={callToPlay.username}
|
||||
actions={callToPlay.actions}
|
||||
focusId={focusedCallId}
|
||||
transportReady={callToPlay.transportReady}
|
||||
error={callToPlay.error}
|
||||
getThumbnail={thumbnails.get}
|
||||
totalPeerCount={games.totalPeerCount}
|
||||
onLaunch={handlePrimary}
|
||||
onClose={() => {
|
||||
setCallToPlayOpen(false);
|
||||
setFocusedCallId(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user