diff --git a/AGENTS.md b/AGENTS.md index 8c941b6..a981a34 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,10 @@ ## OpenWiki -This repository uses OpenWiki for recurring code documentation. Start with `openwiki/quickstart.md`, then follow its links to architecture, workflows, domain concepts, operations, integrations, testing guidance, and source maps. +This repository has a generated `openwiki/` evidence index. It is optional just-in-time context, not required startup reading. + +- Treat source code and tests as authoritative. A brief's unknowns and review items are verification gaps, not automatic requirements. +- Prefer the narrowest quiet validation that proves the changed behavior. Preserve complete failure output. The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate. diff --git a/CLAUDE.md b/CLAUDE.md index 8c941b6..a981a34 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,10 @@ ## OpenWiki -This repository uses OpenWiki for recurring code documentation. Start with `openwiki/quickstart.md`, then follow its links to architecture, workflows, domain concepts, operations, integrations, testing guidance, and source maps. +This repository has a generated `openwiki/` evidence index. It is optional just-in-time context, not required startup reading. + +- Treat source code and tests as authoritative. A brief's unknowns and review items are verification gaps, not automatic requirements. +- Prefer the narrowest quiet validation that proves the changed behavior. Preserve complete failure output. The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate. diff --git a/SlipItIn/SlipItIn.csproj b/SlipItIn/SlipItIn.csproj index 7710f06..3146f80 100644 --- a/SlipItIn/SlipItIn.csproj +++ b/SlipItIn/SlipItIn.csproj @@ -101,7 +101,7 @@ - + diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index 1dbe656..e934960 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,6 +1,8 @@ { - "updatedAt": "2026-07-26T11:33:42.070Z", - "command": "init", - "gitHead": "070727d5cd1fff7fdd7e0db903f1c696b0045ecd", - "model": "gemini-3.6-flash" + "updatedAt": "2026-08-08T10:49:34.141Z", + "command": "update", + "gitHead": "bbc6ad60802ba4e19218d1529c927c67389d973d", + "model": "gemini-3.6-flash", + "status": "complete", + "language": "en" } diff --git a/openwiki/architecture/index.md b/openwiki/architecture/index.md index 27af5e3..3ec6c4d 100644 --- a/openwiki/architecture/index.md +++ b/openwiki/architecture/index.md @@ -1,3 +1,3 @@ # Files -- [System Architecture & Security Overview](overview.md) - Technical architecture of SlipItIn, covering .NET Aspire orchestration, ASP.NET Core SignalR hub design, JWT authentication, and IDbContextFactory thread safety. +- [System Architecture & Security Overview](overview.md) - Technical architecture of SlipItIn, covering .NET Aspire orchestration, ASP.NET Core SignalR hub, .NET MAUI MVVM client layer, WeakReferenceMessenger event routing, and dual-layer local storage. diff --git a/openwiki/architecture/overview.md b/openwiki/architecture/overview.md index f18979a..0151770 100644 --- a/openwiki/architecture/overview.md +++ b/openwiki/architecture/overview.md @@ -1,13 +1,19 @@ --- type: Architecture title: System Architecture & Security Overview -description: Technical architecture of SlipItIn, covering .NET Aspire orchestration, ASP.NET Core SignalR hub design, JWT authentication, and IDbContextFactory thread safety. -tags: [architecture, spire, signalr, jwt, efcore, security] +description: Technical architecture of SlipItIn, covering .NET Aspire orchestration, ASP.NET Core SignalR hub, .NET MAUI MVVM client layer, WeakReferenceMessenger event routing, and dual-layer local storage. +tags: [architecture, spire, signalr, jwt, efcore, security, maui, mvvm] +openwiki: + roles: [architecture, domain] + change_kinds: [lifecycle, public-api] + source_paths: [SlipItIn/MauiProgram.cs, SlipItIn/Services/SignalRService.cs, SlipItIn/Services/GameStateService.cs, SlipItIn.Server/Program.cs, SlipItIn.Server/Hubs/GameHub.cs] + symbols: [SignalRService, GameStateService, LocalStorageService, GameHub, GameService, IDbContextFactory] + validation_commands: ["dotnet build SlipItIn.slnx"] --- # System Architecture & Security Overview -**SlipItIn** is designed as a distributed, real-time application using modern .NET 10 architecture. This document details the orchestration model, backend service structure, security model, and concurrency safeguards. +**SlipItIn** is designed as a distributed, real-time application using modern .NET 10 architecture. This document details the orchestration model, backend server structure, .NET MAUI MVVM client architecture, real-time connection resilience, messaging infrastructure, security model, and concurrency safeguards. The architectural foundation [orchestrates services with](/openwiki/operations/runbook.md) .NET Aspire, while [enforcing security & privacy on](/openwiki/domain/game-mechanics.md) domain entities and [serving real-time hub endpoints for](/openwiki/workflows/slip-and-challenge.md) all active game sessions. Source code structure for all architectural components can be found in the [Source Code Map](/openwiki/source-map.md). @@ -32,14 +38,14 @@ The solution uses **.NET Aspire** to orchestrate application resources, services The backend is an ASP.NET Core Web API & SignalR application providing REST endpoints for user management and real-time WebSockets for game state synchronization. -### Key Components +### Key Backend Components 1. **`Program.cs`**: - Registers `AddServiceDefaults()`, `AddNpgsqlDataSource("postgresdb")`, and `AddDbContextFactory()`. - Configures JWT Bearer authentication with custom `OnMessageReceived` token resolution for SignalR. - Automatically executes database migrations (`db.Database.Migrate()`) at startup. 2. **`AuthController.cs`**: - - Manages user registration (`/api/auth/register`) and authentication (`/api/auth/login`). + - Manages user registration (`/api/auth/register`), authentication (`/api/auth/login`), and profile retrieval (`/api/auth/me`). - Uses `BCrypt.Net` for secure password hashing. - Issues JWT tokens signed with `Jwt:Key`, containing `ClaimTypes.NameIdentifier`, `ClaimTypes.Name`, and `ClaimTypes.Email`. 3. **`GameHub.cs`**: @@ -48,6 +54,7 @@ The backend is an ASP.NET Core Web API & SignalR application providing REST endp - Extracts and verifies user claims, delegating state mutations to `IGameService`. 4. **`GameService.cs`**: - Implements core business logic: game creation, player joins, card dealing, slip submission, challenge creation, and resolution. + - Adds `hostPlayer` directly to `game.Players` before `context.Games.Add(game)` to ensure EF Core navigation tracking consistency. --- @@ -101,13 +108,103 @@ sequenceDiagram Client->>Hub: JoinLobby(lobbyCode) Hub->>Hub: GetAuthenticatedUserId() Hub->>Service: JoinGameAsync(lobbyCode, userId, connectionId) - Service->>DB: Create IDbContext Session & Save Player Hub-->>Client: Broadcast "PlayerJoined" (GameStateDto) ``` +*Sequence diagram showing user authentication via REST and subsequent authenticated SignalR WebSocket connection.* --- -## 4. Concurrency Safety: `IDbContextFactory` +## 4. .NET MAUI Client Architecture (`SlipItIn`) + +The cross-platform client app uses the **MVVM Pattern** built on `CommunityToolkit.Mvvm` and Dependency Injection configured in `MauiProgram.cs`. + +```mermaid +graph TD + subgraph UI_Layer["UI Layer (Views)"] + LoginPage["LoginPage"] + RegisterPage["RegisterPage"] + LobbyPage["LobbyPage"] + GameBoardPage["GameBoardPage"] + end + + subgraph ViewModel_Layer["ViewModel Layer"] + LoginVM["LoginViewModel"] + RegisterVM["RegisterViewModel"] + LobbyVM["LobbyViewModel"] + GameBoardVM["GameBoardViewModel"] + end + + subgraph Messaging_Layer["Messaging Layer"] + Messenger["WeakReferenceMessenger"] + Messages["Messages (GameStateChanged, PlayerHandChanged, ChallengeReceived)"] + end + + subgraph Service_Layer["Service Layer"] + GameStateSvc["GameStateService"] + SignalRSvc["SignalRService"] + AuthSessionSvc["AuthSessionService"] + ApiSvc["ApiService"] + LocalStorageSvc["LocalStorageService"] + end + + LoginPage --> LoginVM + RegisterPage --> RegisterVM + LobbyPage --> LobbyVM + GameBoardPage --> GameBoardVM + + LobbyVM --> Messenger + GameBoardVM --> Messenger + + GameStateSvc --> Messenger + GameStateSvc --> SignalRSvc + GameStateSvc --> LocalStorageSvc + LoginVM --> ApiSvc + LoginVM --> AuthSessionSvc + LoginVM --> SignalRSvc +``` +*Layered architecture diagram of the .NET MAUI client showing Views, ViewModels, WeakReferenceMessenger, and Service implementations.* + +### Dependency Injection & Service Registration (`MauiProgram.cs`) +- **Singletons**: `IAppConfigurationService` (`AppConfigurationService`), `ILocalStorageService` (`LocalStorageService`), `IAuthSessionService` (`AuthSessionService`), `IApiService` (`ApiService`), `ISignalRService` (`SignalRService`), `IGameStateService` (`GameStateService`), `LobbyViewModel`, `GameBoardViewModel`. +- **Transients**: `LoginViewModel`, `RegisterViewModel`. +- **Service Locator Helper**: `ServiceHelper.Services` captures `app.Services` at startup to allow static access where constructor DI is unavailable. + +### Decoupled Messaging Infrastructure (`WeakReferenceMessenger`) +To avoid memory leaks caused by long-lived event subscriptions in ViewModels, `GameStateService` translates raw `ISignalRService` hub events into strongly-typed messages dispatched via `WeakReferenceMessenger.Default`: + +- `LobbyCreatedMessage`: Dispatched when a new game lobby code is issued by the server. +- `GameStateChangedMessage`: Dispatched when public game state or player list updates. +- `GameStartedMessage`: Dispatched when the game transitions to `InProgress`. +- `PlayerHandChangedMessage`: Dispatched when a player receives or updates private phrase cards. +- `ChallengeReceivedMessage`: Dispatched when a player is directly targeted by a slip challenge. +- `ErrorOccurredMessage`: Dispatched when a hub or network error occurs. +- `ConnectionStateChangedMessage`: Dispatched when SignalR connection drops or reconnects. + +ViewModels implement `IRecipient` and register via `WeakReferenceMessenger.Default.RegisterAll(this)` to receive automated UI updates. + +### Real-Time Connection Resilience & Auto-Reconnect +`SignalRService` configures exponential backoff for WebSocket reconnection: + +```csharp +_hubConnection = new HubConnectionBuilder() + .WithUrl(_configuration.HubUrl, options => + { + options.AccessTokenProvider = () => Task.FromResult(token); + }) + .WithAutomaticReconnect([TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30)]) + .Build(); +``` + +When connection drops, `SignalRService` emits `ConnectionStateChanged`. Upon reconnection, `GameStateService` automatically triggers `ResyncAsync()` to fetch current state via `RequestGameStateAsync()` and flush locally queued offline actions. + +### Dual-Layer Storage Security Model (`LocalStorageService`) +To satisfy security and privacy constraints: +1. **`SecureStorage` (Platform Encrypted Storage)**: Used strictly for `AuthTokenKey` (`auth_token`). Prevents plaintext access to active JWT credentials. +2. **`Preferences` (Application Key-Value Settings)**: Used for non-sensitive cached data (`auth_user`, `game_state`, `player_hand`, and `queued_actions`). + +--- + +## 5. Concurrency Safety: `IDbContextFactory` SignalR hubs process concurrent requests from multiple clients over persistent connections. Using a standard Scoped `DbContext` in SignalR leads to thread conflict exceptions (`InvalidOperationException: A second operation was started on this context instance before a previous operation completed`). @@ -119,7 +216,7 @@ SlipItIn solves this by registering EF Core with `AddDbContextFactory Cancelled: Host cancels InProgress --> Cancelled: Session abort ``` +*State diagram illustrating lifecycle transitions of a game session from lobby creation to completion or cancellation.* * **`Lobby`**: Waiting for players to join and set ready status. * **`InProgress`**: Active gameplay with dealt cards and active rounds. @@ -151,6 +171,7 @@ stateDiagram-v2 Resolving --> Active: Challenge resolved Active --> Completed: Timer expires / All phrases used ``` +*State diagram illustrating round lifecycles from card dealing to challenge resolution.* ### Challenge Status (`ChallengeStatus`) * **`Pending`**: Accusation registered, awaiting response from the accused player. @@ -159,13 +180,33 @@ stateDiagram-v2 --- -## 4. Data Transfer Objects (DTOs) & Data Privacy +## 5. Data Transfer Objects (DTOs) & Data Privacy To enforce security and data privacy ([Architecture Overview](/openwiki/architecture/overview.md)), the backend exposes decoupled DTOs in `SlipItIn.Shared/DTOs`: | DTO | Visibility | Purpose & Content | |---|---|---| +| `LoginRequestDto` | Direct REST | Credentials for authentication: `Email`, `Password`. | +| `RegisterRequestDto` | Direct REST | Account creation details: `Username`, `Email`, `Password`. | | `AuthResponseDto` | Direct REST | JWT Access Token, Expiration, Username, Email. | | `GameStateDto` | Broadcast (Group) | Public lobby/game status: `GameId`, `LobbyCode`, `Status`, `CurrentRound`, `RoundTimeRemaining`, `Players` (`PlayerInfoDto` array containing `PlayerId`, `Username`, `Score`, `IsReady`, and `CardCount`). **No card text.** | | `PlayerHandDto` | Unicast (Client) | Private hand data sent strictly to the card owner: `PlayerId`, `Cards` (`PlayerCardDto` array containing `CardId`, `PhraseId`, `Text`, `IsUsed`). | | `SlipChallengeDto` | Unicast / Group | Challenge notification: `ChallengeId`, `ChallengingPlayerId`, `TargetPlayerId`, `TargetCardId`, `Status`, `CreatedAt`. | + +--- + +## 6. Guidance for Future Agents & Developers + +- **When to Consult**: Consult this page when adding domain attributes, extending DTOs, adding new game state statuses, or modifying offline action serialization. +- **Invariants**: + - `PlayerCard.IsUsed` transitions from `false` to `true` upon `SubmitSlip` and cannot be reverted without server intervention. + - `QueuedGameAction.ArgumentsJson` must store arguments in the exact order and type expected by `GameHub` methods. +- **Primary Source Files**: + - `SlipItIn.Shared/Models/Game.cs` + - `SlipItIn.Shared/Models/PlayerCard.cs` + - `SlipItIn.Shared/DTOs/GameStateDto.cs` + - `SlipItIn/Models/QueuedGameAction.cs` +- **Minimal Validation Command**: + ```bash + dotnet build SlipItIn.Shared/SlipItIn.Shared.csproj + ``` diff --git a/openwiki/domain/index.md b/openwiki/domain/index.md index 8e21f6d..b7c78cd 100644 --- a/openwiki/domain/index.md +++ b/openwiki/domain/index.md @@ -1,3 +1,3 @@ # Files -- [Game Domain & State Models](game-mechanics.md) - Detailed domain model and state lifecycles for SlipItIn games, rounds, phrase decks, players, and challenges. +- [Game Domain & State Models](game-mechanics.md) - Detailed domain model and state lifecycles for SlipItIn games, rounds, phrase decks, players, challenges, and client offline action queue. diff --git a/openwiki/index.md b/openwiki/index.md index bfaa1d2..151d4f9 100644 --- a/openwiki/index.md +++ b/openwiki/index.md @@ -4,8 +4,8 @@ okf_version: "0.1" # Files -- [SlipItIn Code Wiki Quickstart](quickstart.md) - Entrypoint for SlipItIn - a real-time multiplayer party game built with .NET 10, MAUI, ASP.NET Core SignalR, EF Core PostgreSQL, and .NET Aspire. -- [Source Code Map & Navigation Directory](source-map.md) - Practical navigation guide mapping source files across projects to system domains and responsibilities. +- [SlipItIn Code Wiki Quickstart](quickstart.md) - Entrypoint for SlipItIn - a real-time multiplayer party game built with .NET 10, MAUI MVVM, ASP.NET Core SignalR, EF Core PostgreSQL, and .NET Aspire. +- [Source Code Map & Navigation Directory](source-map.md) - Practical navigation guide mapping source files across projects to system domains, viewmodels, services, and responsibilities. # Directories diff --git a/openwiki/operations/index.md b/openwiki/operations/index.md index 45f0ac7..f37bb04 100644 --- a/openwiki/operations/index.md +++ b/openwiki/operations/index.md @@ -1,3 +1,3 @@ # Files -- [Operations, Environment Setup & Testing Guidance](runbook.md) - Operational guide for launching SlipItIn with .NET Aspire, running EF Core PostgreSQL migrations, configuring JWT secrets, and executing tests. +- [Operations, Environment Setup & Testing Guidance](runbook.md) - Operational guide for launching SlipItIn with .NET Aspire, running EF Core PostgreSQL migrations, configuring secrets, and executing client/server verification tests. diff --git a/openwiki/operations/runbook.md b/openwiki/operations/runbook.md index 90a5c76..d0ea2ec 100644 --- a/openwiki/operations/runbook.md +++ b/openwiki/operations/runbook.md @@ -1,15 +1,21 @@ --- type: Runbook title: Operations, Environment Setup & Testing Guidance -description: Operational guide for launching SlipItIn with .NET Aspire, running EF Core PostgreSQL migrations, configuring JWT secrets, and executing tests. -tags: [operations, runbook, spire, postgresql, migrations, testing] +description: Operational guide for launching SlipItIn with .NET Aspire, running EF Core PostgreSQL migrations, configuring secrets, and executing client/server verification tests. +tags: [operations, runbook, spire, postgresql, migrations, testing, mvvm, offline-queue] +openwiki: + roles: [operations, testing] + change_kinds: [lifecycle, public-api] + source_paths: [SlipItIn.AppHost/AppHost.cs, SlipItIn.Server/Program.cs, SlipItIn/MauiProgram.cs] + symbols: [AppHost, Program, MauiProgram] + validation_commands: ["dotnet build SlipItIn.slnx"] --- # Operations, Environment Setup & Testing Guidance -This runbook provides actionable instructions for local development setup, starting services via .NET Aspire, executing Entity Framework Core migrations, configuring environment keys, and running tests. +This runbook provides actionable instructions for local development setup, starting services via .NET Aspire, executing Entity Framework Core migrations, configuring environment keys, running client/server applications, and executing targeted test scenarios. -This guide [configures environment parameters for](/openwiki/architecture/overview.md) the backend architecture, [manages database migrations for entities in](/openwiki/domain/game-mechanics.md) the domain model, [verifies real-time event flows defined in](/openwiki/workflows/slip-and-challenge.md) the workflow guide, and [references source entrypoints cataloged in](/openwiki/source-map.md) the source map. +This guide [configures environment parameters for](/openwiki/architecture/overview.md) the backend and client architecture, [manages database migrations for entities in](/openwiki/domain/game-mechanics.md) the domain model, [verifies real-time event flows defined in](/openwiki/workflows/slip-and-challenge.md) the workflow guide, and [references source entrypoints cataloged in](/openwiki/source-map.md) the source map. --- @@ -81,7 +87,7 @@ dotnet ef database update --project SlipItIn.Server --startup-project SlipItIn.S ## 4. Testing Guidance & Verification Scenarios -When developing or extending SlipItIn features, verify the core architecture through targeted test scenarios specified in `Agents/Architecture.md`: +When developing or extending SlipItIn features, verify the core architecture through these targeted test scenarios: ### 1. JWT Authentication & Claims Tests * **Test Objective**: Verify `GameHub` rejects unauthenticated WebSocket connections or missing token query parameters. @@ -91,14 +97,34 @@ When developing or extending SlipItIn features, verify the core architecture thr * **Test Objective**: Verify Player A cannot manipulate Player B's cards or state. * **Verification**: Authenticate as User A and attempt to call `GameHub.SubmitSlip(gameId, playerBId, cardId)`. Verify that `ValidatePlayerAccessAsync` throws `UnauthorizedAccessException` and returns an error response. -### 3. Concurrency & Race Condition Tests +### 3. Client MVVM Decoupled Messaging Tests +* **Test Objective**: Confirm `GameStateService` translates SignalR events into `WeakReferenceMessenger` messages without memory leak risk. +* **Verification**: Trigger a `GameStateUpdated` event on `SignalRService`. Confirm that `LobbyViewModel` and `GameBoardViewModel` receive `GameStateChangedMessage` and update their observable collections cleanly. + +### 4. Offline Action Queueing & Resync Verification +* **Test Objective**: Verify actions taken while disconnected are stored locally and replayed upon reconnection. +* **Verification**: + 1. Disconnect network or stop `SignalRService`. + 2. Invoke `GameBoardViewModel.SubmitSlipAsync(card)`. Confirm `QueuedGameAction` is written to `Preferences`. + 3. Re-establish connection. Confirm `GameStateService.ResyncAsync()` calls `RequestGameStateAsync()` and replays the queued action via `SendQueuedActionAsync()`. + +### 5. Dual-Layer Local Storage Verification +* **Test Objective**: Confirm sensitive tokens are isolated in `SecureStorage` while non-sensitive state stays in `Preferences`. +* **Verification**: Inspect local device storage after login. Verify `auth_token` is stored via platform `SecureStorage` and `game_state` / `queued_actions` are saved in `Preferences`. + +### 6. Concurrency & Race Condition Tests * **Test Objective**: Confirm `IDbContextFactory` handles simultaneous WebSocket calls without thread collision. * **Verification**: Simulate 5 parallel calls to `GameHub.ChallengeSlip()` or `SubmitSlip()` across multiple clients. Verify that all calls complete cleanly without `InvalidOperationException` from DbContext. -### 4. Data Privacy Isolation Verification -* **Test Objective**: Confirm card text is never broadcast in public group messages. -* **Verification**: Capture SignalR `PlayerJoined` and `GameStateUpdated` payloads. Inspect JSON content to confirm only `CardCount` is present and no phrase card `Text` is leaked. +--- -### 5. False Accusation Penalty Verification -* **Test Objective**: Verify penalty card transfer when a challenge is rejected. -* **Verification**: Submit a challenge against a valid slip, then call `ResolveChallenge(challengeId, approved: false)`. Verify in the database that `PlayerCard.PlayerId` is reassigned to the challenger's `PlayerId`. +## 5. Guidance for Future Agents & Developers + +- **When to Consult**: Refer to this runbook for local environment setup, Aspire startup commands, EF Core migration commands, and test verification procedures. +- **Invariants**: + - Always run migrations using `SlipItIn.Server` as both project and startup-project. + - Test offline queue flushing before submitting client-side real-time changes. +- **Minimal Validation Command**: + ```bash + dotnet build SlipItIn.slnx + ``` diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md index 33b4b58..f78a4ed 100644 --- a/openwiki/quickstart.md +++ b/openwiki/quickstart.md @@ -1,15 +1,21 @@ --- type: Overview title: SlipItIn Code Wiki Quickstart -description: Entrypoint for SlipItIn - a real-time multiplayer party game built with .NET 10, MAUI, ASP.NET Core SignalR, EF Core PostgreSQL, and .NET Aspire. -tags: [quickstart, overview, slipitin, dotnet10, spire] +description: Entrypoint for SlipItIn - a real-time multiplayer party game built with .NET 10, MAUI MVVM, ASP.NET Core SignalR, EF Core PostgreSQL, and .NET Aspire. +tags: [quickstart, overview, slipitin, dotnet10, spire, maui, mvvm] +openwiki: + roles: [architecture, domain, workflow] + change_kinds: [public-api, lifecycle] + source_paths: [SlipItIn/MauiProgram.cs, SlipItIn.Server/Program.cs, SlipItIn.AppHost/AppHost.cs] + symbols: [MauiProgram, SignalRService, GameStateService, GameHub, GameService] + validation_commands: ["dotnet build SlipItIn.slnx"] --- # SlipItIn Code Wiki Quickstart Welcome to the **SlipItIn** repository wiki. SlipItIn is a real-time multiplayer party game where players receive secret phrase cards and attempt to "slip" those phrases into everyday conversations or text chats without getting caught by other players. -The solution is built using **.NET 10**, leveraging **ASP.NET Core Web API & SignalR** for the backend engine, **Entity Framework Core (Npgsql / PostgreSQL)** for data persistence, **.NET MAUI** for the cross-platform client app, and **.NET Aspire** for distributed cloud-native orchestration and telemetry. +The solution is built using **.NET 10**, leveraging **ASP.NET Core Web API & SignalR** for the backend engine, **Entity Framework Core (Npgsql / PostgreSQL)** for data persistence, **.NET MAUI (MVVM Pattern)** for the cross-platform client app, and **.NET Aspire** for distributed cloud-native orchestration and telemetry. --- @@ -21,47 +27,62 @@ The repository is organized as a multi-project .NET solution (`SlipItIn.slnx`): SlipItIN/ ├── SlipItIn.AppHost/ # .NET Aspire AppHost orchestrator (pgsql + server + client) ├── SlipItIn.Server/ # ASP.NET Core Web API + SignalR Hub + Game Engine -├── SlipItIn.Shared/ # Shared Class Library (Models & DTOs) +├── SlipItIn.Shared/ # Shared Class Library (Models, DTOs & Enums) ├── SlipItIn.ServiceDefaults/ # Aspire OpenTelemetry, Health Checks & Service Discovery -└── SlipItIn/ # .NET MAUI Client App (Android, iOS, MacCatalyst, Windows) +└── SlipItIn/ # .NET MAUI Client App (MVVM: Views, ViewModels, Services, Messages) ``` -The system architecture [orchestrates services with](/openwiki/operations/runbook.md) .NET Aspire and [enforces security and privacy on](/openwiki/domain/game-mechanics.md) all domain entities. For a deep dive into the backend design, JWT security, and concurrency safety, see the [System Architecture & Security Overview](/openwiki/architecture/overview.md). +The system architecture [orchestrates services with](/openwiki/operations/runbook.md) .NET Aspire and [enforces security and privacy on](/openwiki/domain/game-mechanics.md) all domain entities. For a deep dive into backend security, SignalR real-time client integration, MVVM architecture, and offline queueing, see the [System Architecture & Security Overview](/openwiki/architecture/overview.md). ```mermaid graph TD - MAUI[SlipItIn .NET MAUI Client] -->|REST / API| Server[SlipItIn.Server ASP.NET Core] - MAUI -->|SignalR WebSockets| Hub[GameHub /hubs/game] - Server -->|IDbContextFactory| DB[(PostgreSQL Database)] - AppHost[.NET Aspire AppHost] -->|Orchestrates| Server + subgraph MAUI_Client["SlipItIn .NET MAUI Client"] + Views["Views (LoginPage, LobbyPage, GameBoardPage)"] --> ViewModels["ViewModels (LoginVM, LobbyVM, GameBoardVM)"] + ViewModels --> GameState["GameStateService"] + GameState --> SignalR["SignalRService"] + GameState --> LocalStorage["LocalStorageService (SecureStorage & Preferences)"] + ViewModels --> ApiService["ApiService (REST Auth)"] + end + + ApiService -->|REST /api/auth| AuthCtrl["AuthController"] + SignalR -->|WebSockets /hubs/game| Hub["GameHub"] + Hub --> Service["GameService"] + AuthCtrl --> DB[("PostgreSQL Database")] + Service -->|IDbContextFactory| DB + AppHost[".NET Aspire AppHost"] -->|Orchestrates| Server["SlipItIn.Server"] AppHost -->|Provisions| DB - Server -->|Uses Defaults| ServiceDefaults[SlipItIn.ServiceDefaults] ``` +*System architecture diagram illustrating .NET MAUI MVVM client layer, backend REST/SignalR APIs, and .NET Aspire PostgreSQL orchestration.* --- ## 2. Core Game Loop & Mechanics -1. **Lobby Creation**: Host creates a game lobby with a 6-character code. Players join via code. -2. **Card Dealing**: Upon game start, each player receives a private hand of 5 secret phrase cards (`PlayerHandDto`). -3. **Phrase Slipping**: During normal conversation, a player speaks or types one of their phrases and clicks **Submit Slip**. -4. **Slip Challenge**: Opponents suspecting a fake phrase can issue a **Slip Challenge**. - - **Justified Accusation (Approved)**: Target phrase was an invalid slip. - - **False Accusation (Rejected)**: Target phrase was legitimate. The accuser receives the card as a penalty (expanding their hand size beyond 5). +1. **Authentication & Session**: Player registers or logs in via `LoginPage` / `RegisterPage`. `AuthSessionService` stores the JWT token in `SecureStorage`. +2. **Lobby Creation**: Host creates a lobby on `LobbyPage`, generating a 6-character code. Players join using the code. +3. **Card Dealing**: Upon game start, each player receives a private hand of 5 secret phrase cards (`PlayerHandDto`) via SignalR unicast. +4. **Phrase Slipping**: During normal conversation, a player speaks or types one of their phrases and clicks **Submit Slip** on `GameBoardPage`. If offline, actions queue locally in `QueuedGameAction` and flush automatically upon reconnection. +5. **Slip Challenge**: Opponents suspecting a fake phrase can issue a **Slip Challenge**. + - **Justified Accusation (Approved)**: Target phrase was an invalid slip. Card remains with accused player. + - **False Accusation (Rejected)**: Target phrase was legitimate. The accuser receives the phrase card as a penalty. To learn how game events flow across SignalR in real time, inspect the [Slip & Challenge Workflows](/openwiki/workflows/slip-and-challenge.md). --- -## 3. Wiki Navigation Map +## 3. Task Routing & Navigation Map -Explore specific documentation sections for technical details: +The following task routing table directs future engineers and automated agents to relevant wiki documentation, source entry points, key symbols, and verification commands for common modification scenarios: -- **[System Architecture & Security Overview](/openwiki/architecture/overview.md)**: Explains .NET Aspire orchestration, JWT bearer authentication, claims validation, `IDbContextFactory` thread safety, and public vs. private data isolation. -- **[Game Domain & State Models](/openwiki/domain/game-mechanics.md)**: Details domain entities (`User`, `Game`, `Player`, `Phrase`, `PlayerCard`, `GameRound`, `SlipChallenge`) and their state lifecycles. -- **[Slip & Challenge Workflows](/openwiki/workflows/slip-and-challenge.md)**: Details step-by-step game loop execution, real-time SignalR notifications, and penalty rules. -- **[Source Code Map](/openwiki/source-map.md)**: Directory and file navigation index mapping repository paths to technical domains. -- **[Operations & Runbook](/openwiki/operations/runbook.md)**: Instructions for running the app with Aspire, executing PostgreSQL EF Core migrations, configuration keys, and testing strategies. +| Intent / Change Area | Relevant Wiki Page | Source Entry Points | Key Symbols / Types | Focused Checks | Minimal Validation Command | +|---|---|---|---|---|---| +| **MAUI Client ViewModels & Pages** | [Architecture Overview](/openwiki/architecture/overview.md) | `SlipItIn/ViewModels/`, `SlipItIn/Views/` | `LobbyViewModel`, `GameBoardViewModel`, `LoginViewModel` | Inspect MVVM command handlers and `WeakReferenceMessenger` bindings | `dotnet build SlipItIn/SlipItIn.csproj` | +| **Client SignalR & Messaging** | [Slip & Challenge Workflows](/openwiki/workflows/slip-and-challenge.md) | `SlipItIn/Services/SignalRService.cs`, `SlipItIn/Services/GameStateService.cs` | `ISignalRService`, `IGameStateService`, `WeakReferenceMessenger` | Verify auto-reconnect backoff and message dispatching | `dotnet build SlipItIn/SlipItIn.csproj` | +| **Offline Action Queue & Resync** | [Slip & Challenge Workflows](/openwiki/workflows/slip-and-challenge.md) | `SlipItIn/Services/GameStateService.cs`, `SlipItIn/Models/QueuedGameAction.cs` | `QueuedGameAction`, `EnqueueActionAsync`, `FlushQueuedActionsAsync` | Verify offline queueing in `Preferences` and flush on reconnect | `dotnet build SlipItIn/SlipItIn.csproj` | +| **Local Storage & Auth Session** | [Architecture Overview](/openwiki/architecture/overview.md) | `SlipItIn/Services/LocalStorageService.cs`, `SlipItIn/Services/AuthSessionService.cs` | `ILocalStorageService`, `IAuthSessionService`, `SecureStorage` | Confirm JWT stored in `SecureStorage` and state in `Preferences` | `dotnet build SlipItIn/SlipItIn.csproj` | +| **Backend Game Engine & Hub** | [Architecture Overview](/openwiki/architecture/overview.md) | `SlipItIn.Server/Hubs/GameHub.cs`, `SlipItIn.Server/Services/GameService.cs` | `GameHub`, `GameService`, `IDbContextFactory` | Test JWT claim extraction and EF Core DbContext session isolation | `dotnet build SlipItIn.Server/SlipItIn.Server.csproj` | +| **Domain Models & DTOs** | [Game Domain & State Models](/openwiki/domain/game-mechanics.md) | `SlipItIn.Shared/Models/`, `SlipItIn.Shared/DTOs/` | `GameStateDto`, `PlayerHandDto`, `SlipChallengeDto`, `QueuedGameAction` | Confirm card privacy isolation between public state and private hand | `dotnet build SlipItIn.Shared/SlipItIn.Shared.csproj` | +| **Aspire & Infrastructure Setup** | [Operations & Runbook](/openwiki/operations/runbook.md) | `SlipItIn.AppHost/AppHost.cs`, `SlipItIn.ServiceDefaults/` | `AppHost`, `AddServiceDefaults`, `AddPostgres` | Validate Aspire service references and PostgreSQL container creation | `dotnet run --project SlipItIn.AppHost/SlipItIn.AppHost.csproj` | --- @@ -72,6 +93,8 @@ When modifying this repository, strictly adhere to these core rules: 1. **IDbContextFactory Thread Safety**: Never inject a scoped `SlipItInDbContext` into SignalR hubs or singleton services. Always use `IDbContextFactory.CreateDbContext()` to prevent DbContext concurrency exceptions during concurrent WebSocket calls ([Architecture Overview](/openwiki/architecture/overview.md)). 2. **Data Privacy Isolation**: Do not leak card text into public DTOs. Public game state must be broadcast using `GameStateDto` (card counts only), while private cards are dispatched strictly via `PlayerHandDto` to individual client connections ([Domain Mechanics](/openwiki/domain/game-mechanics.md)). 3. **Server-Side Validation**: SignalR client calls represent intent ("I want to challenge X"). The server MUST re-verify JWT claims, player game membership, card ownership, and round status inside `GameHub.cs` and `GameService.cs` before mutating state ([Slip & Challenge Workflows](/openwiki/workflows/slip-and-challenge.md)). +4. **Decoupled Client Messaging**: MAUI ViewModels MUST NOT consume `ISignalRService` events directly for UI updates. Always route events through `IGameStateService` via `WeakReferenceMessenger` messages (`GameStateChangedMessage`, `PlayerHandChangedMessage`, `ChallengeReceivedMessage`) to prevent memory leaks and dangling subscriptions ([Architecture Overview](/openwiki/architecture/overview.md)). +5. **Dual-Layer Local Storage**: Always store sensitive credentials (JWT access tokens) in platform `SecureStorage`. Non-sensitive UI states, cached game data, and offline action queues MUST be persisted in `Preferences` ([Architecture Overview](/openwiki/architecture/overview.md)). --- @@ -79,6 +102,4 @@ When modifying this repository, strictly adhere to these core rules: The following features and components are specified in specification documents (`Agents/ProjectPlan.md` and `Agents/Architecture.md`) and backlogged for upcoming development iterations: -- **MAUI Client Services & ViewModels**: Implement `ApiService`, `SignalRService`, `GameStateService`, `LobbyViewModel`, and `GameBoardViewModel` under `SlipItIn/` using `CommunityToolkit.Mvvm` and `WeakReferenceMessenger`. (Anchor: `SlipItIn/`, pending client phase 3 completion). -- **Offline Action Queue & Auto-Resync**: Implement exponential backoff reconnect logic (0s, 2s, 10s, 30s) and queued action execution upon app resume in MAUI client. (Anchor: `SlipItIn/Services/`, deferred until MAUI service layer setup). - **Timer Engine for Slip Rounds**: Background timer service on server enforcing round duration limits (30-60s) with automated round completion notifications. (Anchor: `SlipItIn.Server/Services/`, pending phase 2b refinement). diff --git a/openwiki/source-map.md b/openwiki/source-map.md index 05df4b6..f6f6b0e 100644 --- a/openwiki/source-map.md +++ b/openwiki/source-map.md @@ -1,8 +1,14 @@ --- type: Reference title: Source Code Map & Navigation Directory -description: Practical navigation guide mapping source files across projects to system domains and responsibilities. -tags: [source-map, navigation, directory, projects] +description: Practical navigation guide mapping source files across projects to system domains, viewmodels, services, and responsibilities. +tags: [source-map, navigation, directory, projects, maui, mvvm] +openwiki: + roles: [repository] + change_kinds: [public-api] + source_paths: [SlipItIn/MauiProgram.cs, SlipItIn.Server/Program.cs, SlipItIn.AppHost/AppHost.cs] + symbols: [MauiProgram, Program, AppHost] + validation_commands: ["dotnet build SlipItIn.slnx"] --- # Source Code Map & Navigation Directory @@ -19,9 +25,9 @@ This navigation map [indexes backend architecture files described in](/openwiki/ SlipItIn.slnx ├── SlipItIn.AppHost/ # Aspire distributed orchestrator ├── SlipItIn.Server/ # Web API & SignalR real-time server -├── SlipItIn.Shared/ # Shared models & data transfer objects +├── SlipItIn.Shared/ # Shared models, DTOs & enums ├── SlipItIn.ServiceDefaults/ # Aspire OpenTelemetry & health checks -├── SlipItIn/ # .NET MAUI multi-platform client +├── SlipItIn/ # .NET MAUI multi-platform client (MVVM) └── Agents/ # Architecture & planning briefs ``` @@ -41,7 +47,7 @@ SlipItIn.slnx - **`Controllers/AuthController.cs`**: REST API controller providing `/api/auth/register`, `/api/auth/login`, and `/api/auth/me`. Handles BCrypt password hashing and JWT token generation. - **`Hubs/GameHub.cs`**: SignalR hub mapped to `/hubs/game`. Performs JWT claim extraction (`GetAuthenticatedUserId`), player/host authorization checks (`ValidatePlayerAccessAsync`, `ValidateHostAccessAsync`), connection ID tracking, and real-time event broadcasting. - **`Services/IGameService.cs`**: Contract interface defining backend game operations. -- **`Services/GameService.cs`**: Core engine implementation. Handles game creation, player joining, card dealing from active phrases, slip submission, challenge creation, penalty resolution, and state serialization (`GetGameStateAsync`, `GetPlayerHandAsync`). +- **`Services/GameService.cs`**: Core engine implementation. Handles game creation, player joining, card dealing from active phrases, slip submission, challenge creation, penalty resolution, and state serialization (`GetGameStateAsync`, `GetPlayerHandAsync`). Adds `hostPlayer` directly to `game.Players` before `context.Games.Add(game)` for EF Core navigation safety. - **`Data/SlipItInDbContext.cs`**: Entity Framework Core DbContext mapping `Users`, `Games`, `Players`, `Phrases`, `PlayerCards`, `GameRounds`, and `SlipChallenges` to PostgreSQL tables. - **`Migrations/`**: Auto-generated EF Core migration snapshots (`20260723193505_InitialCreate.cs`). - **`appsettings.json` & `appsettings.Development.json`**: JWT secret keys, issuer/audience defaults, and database connection strings. @@ -58,12 +64,35 @@ SlipItIn.slnx - **`DTOs/GameStateDto.cs`**: DTOs for public state (`GameStateDto`, `PlayerInfoDto`), private hand state (`PlayerHandDto`, `PlayerCardDto`), and challenge alerts (`SlipChallengeDto`). ### `Cross-Platform MAUI Client` (`SlipItIn`) -- **`MauiProgram.cs`**: Client builder configuring MAUI app shell, fonts, and logging debug extensions. +- **`MauiProgram.cs`**: Client app builder. Configures dependency injection for ViewModels and Services, fonts, debug logging, and sets up `ServiceHelper.Services`. - **`App.xaml` & `App.xaml.cs`**: Root MAUI application class. -- **`AppShell.xaml` & `AppShell.xaml.cs`**: AppShell routing container. -- **`MainPage.xaml` & `MainPage.xaml.cs`**: Initial entry view. +- **`AppShell.xaml` & `AppShell.xaml.cs`**: Shell navigation container routing between `LoginPage`, `RegisterPage`, `LobbyPage`, and `GameBoardPage`. +- **`Infrastructure/ServiceHelper.cs`**: Static service locator bridge for runtime DI resolution. +- **`Services/`**: + - `SignalRService.cs` (`ISignalRService`): WebSocket SignalR client, hub method invoker, and auto-reconnect engine. + - `GameStateService.cs` (`IGameStateService`): Central game state holder, messenger bridge (`WeakReferenceMessenger`), and offline action queue manager. + - `LocalStorageService.cs` (`ILocalStorageService`): Dual-layer local persistence (`SecureStorage` for tokens, `Preferences` for game state/offline queue). + - `AuthSessionService.cs` (`IAuthSessionService`): Authenticated user session state manager. + - `ApiService.cs` (`IApiService`): REST HTTP client for authentication endpoints. + - `AppConfigurationService.cs` (`IAppConfigurationService`): Configuration provider for API base URL and SignalR Hub URL. +- **`ViewModels/`**: + - `BaseViewModel.cs`: Abstract base ViewModel providing `IsBusy`, `StatusMessage`, and error handling helper `RunSafeAsync`. + - `LoginViewModel.cs`: Handles email/password authentication and navigation to `LobbyPage`. + - `RegisterViewModel.cs`: Handles user registration. + - `LobbyViewModel.cs`: Manages lobby creation, joining, player readiness, and game start. Implements `IRecipient` interfaces for real-time messages. + - `GameBoardViewModel.cs`: Manages game board state, player cards, slip submission, challenging, challenge resolution, and offline queueing. +- **`Views/`**: + - `LoginPage.xaml` / `.cs`: XAML login UI view. + - `RegisterPage.xaml` / `.cs`: XAML registration UI view. + - `LobbyPage.xaml` / `.cs`: XAML lobby UI view displaying lobby code and player list. + - `GameBoardPage.xaml` / `.cs`: XAML game board UI view displaying cards and player list. + - `ChallengeNotificationOverlay.xaml` / `.cs`: Reusable overlay view for challenge alerts. +- **`Messages/`**: + - `LobbyCreatedMessage.cs`, `GameStateChangedMessage.cs`, `GameStartedMessage.cs`, `PlayerHandChangedMessage.cs`, `ChallengeReceivedMessage.cs`, `ErrorOccurredMessage.cs`, `ConnectionStateChangedMessage.cs`: Strongly-typed `CommunityToolkit.Mvvm.Messaging` payloads. +- **`Models/`**: + - `QueuedGameAction.cs`: Data model for offline game action queueing. - **`Platforms/`**: Platform-specific entry points for Android, iOS, MacCatalyst, and Windows. ### `Documentation & Specifications` (`Agents/`) -- **`Agents/Architecture.md`**: Specification document defining Phase 2b backend security (JWT validation, `IDbContextFactory`, privacy DTOs) and Phase 3b MAUI stability patterns (`WeakReferenceMessenger`, auto-reconnect, dual-layer storage). -- **`Agents/ProjectPlan.md`**: Project plan breakdown covering phases 1 through 4. +- **`Agents/Architecture.md`**: Specification document defining backend security and client MVVM/stability patterns. +- **`Agents/ProjectPlan.md`**: Project plan breakdown covering development phases. diff --git a/openwiki/workflows/index.md b/openwiki/workflows/index.md index 22d6053..0130e40 100644 --- a/openwiki/workflows/index.md +++ b/openwiki/workflows/index.md @@ -1,3 +1,3 @@ # Files -- [Slip & Challenge Workflows](slip-and-challenge.md) - Real-time game loops covering lobby creation, card dealing, phrase slipping, slip challenging, and challenge resolution. +- [Slip & Challenge Workflows](slip-and-challenge.md) - Real-time game loops covering authentication, lobby creation, card dealing, phrase slipping, slip challenging, challenge resolution, and offline action queuing. diff --git a/openwiki/workflows/slip-and-challenge.md b/openwiki/workflows/slip-and-challenge.md index 3f1685c..60744cf 100644 --- a/openwiki/workflows/slip-and-challenge.md +++ b/openwiki/workflows/slip-and-challenge.md @@ -1,121 +1,175 @@ --- type: Workflow title: Slip & Challenge Workflows -description: Real-time game loops covering lobby creation, card dealing, phrase slipping, slip challenging, and challenge resolution. -tags: [workflow, game-loop, signalr, slip-mechanic, real-time] +description: Real-time game loops covering authentication, lobby creation, card dealing, phrase slipping, slip challenging, challenge resolution, and offline action queuing. +tags: [workflow, game-loop, signalr, slip-mechanic, real-time, offline-resync] +openwiki: + roles: [workflow] + change_kinds: [public-api, lifecycle] + source_paths: [SlipItIn/ViewModels/GameBoardViewModel.cs, SlipItIn/Services/GameStateService.cs, SlipItIn/Services/SignalRService.cs, SlipItIn.Server/Hubs/GameHub.cs] + symbols: [LobbyViewModel, GameBoardViewModel, SignalRService, GameStateService, GameHub] + validation_commands: ["dotnet build SlipItIn.slnx"] --- # Slip & Challenge Workflows -This document outlines the core real-time game workflows in SlipItIn, detailing how SignalR events, backend service operations, database updates, and client state notifications interact. +This document outlines the core real-time game workflows in SlipItIn, detailing how client MVVM ViewModels, `WeakReferenceMessenger`, backend SignalR hubs, database services, and offline action queues interact. -These workflows [execute state transitions on](/openwiki/domain/game-mechanics.md) domain entities, [invoke real-time methods in](/openwiki/architecture/overview.md) the ASP.NET Core `GameHub`, [are implemented across backend services mapped in](/openwiki/source-map.md) the source map, and [are verified using tests described in](/openwiki/operations/runbook.md) the operations runbook. +These workflows [execute state transitions on](/openwiki/domain/game-mechanics.md) domain entities, [invoke real-time methods in](/openwiki/architecture/overview.md) the ASP.NET Core `GameHub` and client `SignalRService`, [are implemented across backend and client services mapped in](/openwiki/source-map.md) the source map, and [are verified using tests described in](/openwiki/operations/runbook.md) the operations runbook. --- ## 1. Game Setup & Lobby Workflow ``` -[Host] CreateLobby() ──> Generate 6-Char LobbyCode ──> Add to SignalR Group - │ -[Player] JoinLobby() ──> Verify MaxPlayers & Status ───────────┤ - │ -[Player] PlayerReady() ──> Set IsReady = true ─────────────────┼──> Broadcast GameStateUpdated - │ -[Host] StartGame() ──> Deal 5 Random Cards Per Player ─────────┴──> Unicast PlayerHandUpdated +[Client App] LoginVM.LoginAsync() ──> REST Auth ──> Save Token in SecureStorage ──> SignalR.ConnectAsync() + │ +[Host] LobbyVM.CreateLobbyAsync() ──> SignalR.CreateLobbyAsync() ──> Server Generates Code ──┤ + │ +[Player] LobbyVM.JoinLobbyAsync() ──> SignalR.JoinLobbyAsync(code) ──────────────────────────┤ + │ +[Player] LobbyVM.SetReadyAsync() ──> SignalR.PlayerReadyAsync() ─────────────────────────────┼──> Broadcast GameStateUpdated + │ +[Host] LobbyVM.StartGameAsync() ──> SignalR.StartGameAsync() ──> Deal 5 Cards / Player ──────┴──> Unicast PlayerHandUpdated ``` -1. **Lobby Creation**: Host calls `GameHub.CreateLobby()`. `GameService.CreateGameAsync()` creates a `Game` record (`Status = Lobby`), generates a random 6-character `LobbyCode`, adds the host as the first `Player`, and registers the host's WebSocket `ConnectionId`. Caller receives `LobbyCreated`. -2. **Joining Lobby**: Opponents invoke `GameHub.JoinLobby(lobbyCode)`. `GameService.JoinGameAsync()` validates that the game exists, has space (`Players.Count < MaxPlayers`), and is in `Lobby` status. The player is assigned a `Player` entry, added to the SignalR group `lobbyCode`, and `PlayerJoined` (`GameStateDto`) is broadcast to all participants. -3. **Player Ready**: Players invoke `GameHub.PlayerReady(gameId, playerId)`. `GameHub` checks `ValidatePlayerAccessAsync` and broadcasts `GameStateUpdated`. -4. **Game Start & Card Dealing**: The host invokes `GameHub.StartGame(gameId)`. `GameHub` verifies host authorization via `ValidateHostAccessAsync(gameId, authUserId)`. +1. **Authentication & Session Setup**: Player logs in via `LoginPage` (`LoginViewModel.LoginAsync()`). `ApiService` posts credentials to `/api/auth/login`. `AuthSessionService` persists the token to `SecureStorage`. `SignalRService.ConnectAsync(token)` opens a WebSocket connection to `/hubs/game`. +2. **Lobby Creation**: Host invokes `LobbyViewModel.CreateLobbyAsync()`. `SignalRService.CreateLobbyAsync()` triggers `GameHub.CreateLobby()`. `GameService.CreateGameAsync()` creates a `Game` record (`Status = Lobby`), generates a random 6-character `LobbyCode`, adds host to `game.Players`, and registers host's `ConnectionId`. Caller receives `LobbyCreated` message via `WeakReferenceMessenger`. +3. **Joining Lobby**: Opponents call `LobbyViewModel.JoinLobbyAsync()`. `SignalRService.JoinLobbyAsync(code)` validates game status and capacity. `GameService.JoinGameAsync()` assigns player to game, registers `ConnectionId`, adds player to SignalR group `lobbyCode`, and broadcasts `PlayerJoined` (`GameStateDto`) to all participants. +4. **Player Ready**: Players invoke `LobbyViewModel.SetReadyAsync()`. `GameHub.PlayerReady()` verifies access and broadcasts `GameStateUpdated`. +5. **Game Start & Card Dealing**: The host invokes `LobbyViewModel.StartGameAsync()`. + - `GameHub.StartGame()` verifies host identity (`ValidateHostAccessAsync`). - `GameService.StartGameAsync()` transitions `GameStatus` to `InProgress` and creates `GameRound` 1 (`RoundStatus.Active`). - - `GameService.DealCardsAsync()` fetches active phrases, randomly selects 5 phrases per player, creates `PlayerCard` entries, and saves them to PostgreSQL. - - SignalR broadcasts `GameStarted` to the group. + - `GameService.DealCardsAsync()` selects 5 random phrases per player, creates `PlayerCard` records, and saves them to PostgreSQL. + - SignalR broadcasts `GameStarted` to the group (triggering client navigation to `GameBoardPage`). - SignalR sends private `PlayerHandUpdated` notifications (`PlayerHandDto`) individually to each player's `ConnectionId`. --- -## 2. Phrase Slipping Workflow +## 2. Phrase Slipping & Offline Queueing Workflow During real-life conversation or chat, a player speaks or types one of their secret phrases and marks it as used in the app: -1. **Submission**: Player A calls `GameHub.SubmitSlip(gameId, playerId, cardId)`. -2. **Access Control**: `GameHub` executes `ValidatePlayerAccessAsync(playerId, userId)` to verify that Player A owns the specified `Player` account. -3. **Engine Execution**: `GameService.SubmitSlipAsync()` loads the `PlayerCard`, validates card ownership (`card.PlayerId == playerId`), and sets `card.IsUsed = true`. -4. **Notification**: `GameHub` broadcasts `SlipSubmitted` (`{ PlayerId, CardId, Success }`) to the SignalR lobby group. +1. **User Action**: Player clicks **Submit Slip** on `GameBoardPage` (`GameBoardViewModel.SubmitSlipAsync(card)`). +2. **Online / Offline Branching**: + - **If Connected (`_signalR.IsConnected`)**: Calls `SignalRService.SubmitSlipAsync(gameId, playerId, cardId)` directly. `GameHub.SubmitSlip()` validates ownership and updates `PlayerCard.IsUsed = true`. `GameHub` broadcasts `SlipSubmitted` to the lobby group. + - **If Offline (`!_signalR.IsConnected`)**: Calls `GameStateService.EnqueueActionAsync("SubmitSlip", gameId, playerId, cardId)`. Action is serialized into `QueuedGameAction` and saved in `Preferences`. UI updates status to `"Offline: Aktion wurde zwischengespeichert."` --- ## 3. Slip Challenge & Resolution Workflow -If another player suspects that a submitted phrase or recent conversation statement was an invalid slip, they can challenge the slipper. +If an opponent suspects that a submitted phrase or recent conversation statement was an invalid slip, they can challenge the slipper. ### Step 1: Challenge Initiation -1. Player B (Challenger) calls `GameHub.ChallengeSlip(gameId, challengingPlayerId, targetCardId)`. -2. `GameHub` verifies Player B's identity (`ValidatePlayerAccessAsync`). -3. `GameService.CreateChallengeAsync()` creates a `SlipChallenge` entity with `ChallengeStatus.Pending`. -4. `GameHub` broadcasts `SlipChallenged` (`SlipChallengeDto`) to the lobby group. -5. `GameHub` sends a targeted `ChallengeReceived` message directly to Player A's `ConnectionId`. +1. Challenger clicks **Challenge** on `GameBoardPage` (`GameBoardViewModel.ChallengeCardAsync(card)`). +2. If online, calls `SignalRService.ChallengeSlipAsync(gameId, playerId, cardId)`. +3. `GameHub.ChallengeSlip()` verifies challenger identity (`ValidatePlayerAccessAsync`). +4. `GameService.CreateChallengeAsync()` creates a `SlipChallenge` entity with `ChallengeStatus.Pending`. +5. `GameHub` broadcasts `SlipChallenged` (`SlipChallengeDto`) to the group and unicasts `ChallengeReceived` directly to target player's `ConnectionId`. +6. Client target player sees `ChallengeNotificationOverlay` on `GameBoardPage`. ### Step 2: Challenge Resolution -Player A (the accused) responds by admitting or denying the false slip: +The accused player responds via `ChallengeNotificationOverlay`: -1. Player A calls `GameHub.ResolveChallenge(challengeId, approved)`. -2. `GameHub` executes `ValidateChallengeTargetAccessAsync(challengeId, authUserId)` to guarantee that *only* the accused player can resolve the challenge. -3. `GameService.ResolveChallengeAsync()` executes penalty logic: - - **`approved = true` (Justified Accusation / Legitimate Catch)**: The accused admits the phrase was a fake slip. `SlipChallenge.Status` is set to `Approved`. The card remains with the accused player. - - **`approved = false` (False Accusation / Wrong Penalty)**: The accused denies the charge (the slip was valid). `SlipChallenge.Status` is set to `Rejected`. As a penalty for making a false accusation, the phrase card is reassigned to the challenger: `targetCard.PlayerId = challenge.ChallengingPlayerId`. The challenger now holds >5 cards in their hand. -4. `GameHub` broadcasts `ChallengeResolved` to the lobby group. - -### Real-time Sequence Diagram +1. Accused player calls `GameBoardViewModel.ResolveChallengeAsync(challengeId, approved)`. +2. `SignalRService.ResolveChallengeAsync(challengeId, approved)` calls `GameHub.ResolveChallenge()`. +3. `GameHub` executes `ValidateChallengeTargetAccessAsync(challengeId, authUserId)` to guarantee *only* the accused player can resolve the challenge. +4. `GameService.ResolveChallengeAsync()` executes penalty logic: + - **`approved = true` (Justified Accusation / Legitimate Catch)**: The accused admits the phrase was a fake slip. `SlipChallenge.Status` is set to `Approved`. The card remains with accused player. + - **`approved = false` (False Accusation / Wrong Penalty)**: The accused denies the charge. `SlipChallenge.Status` is set to `Rejected`. As a penalty for making a false accusation, the phrase card is reassigned to the challenger: `targetCard.PlayerId = challenge.ChallengingPlayerId`. +5. `GameHub` broadcasts `ChallengeResolved` to the group. ```mermaid sequenceDiagram autonumber - actor Slipper as Player A (Accused) - actor Challenger as Player B (Challenger) + actor Slipper as Accused Player A + actor Challenger as Challenger Player B + participant GameBoardVM as GameBoardViewModel participant Hub as GameHub participant Service as GameService participant DB as SlipItInDbContext Note over Slipper, Challenger: Active Gameplay - Slipper->>Hub: SubmitSlip(gameId, playerAId, cardId) - Hub->>Service: SubmitSlipAsync(gameId, playerAId, cardId) + Slipper->>GameBoardVM: SubmitSlipAsync(card) + GameBoardVM->>Hub: SubmitSlip(gameId, playerAId, cardId) + Hub->>Service: SubmitSlipAsync() Service->>DB: Set PlayerCard.IsUsed = true Hub-->>Challenger: Broadcast "SlipSubmitted" Note over Challenger: Suspects invalid phrase - Challenger->>Hub: ChallengeSlip(gameId, playerBId, targetCardId) + Challenger->>GameBoardVM: ChallengeCardAsync(card) + GameBoardVM->>Hub: ChallengeSlip(gameId, playerBId, cardId) Hub->>Service: CreateChallengeAsync() Service->>DB: Insert SlipChallenge (Status = Pending) - Hub-->>Challenger: Broadcast "SlipChallenged" (SlipChallengeDto) - Hub-->>Slipper: Unicast "ChallengeReceived" to ConnectionId + Hub-->>Challenger: Broadcast "SlipChallenged" + Hub-->>Slipper: Unicast "ChallengeReceived" - Note over Slipper: Accused resolves accusation - Slipper->>Hub: ResolveChallenge(challengeId, approved) + Note over Slipper: Notification Overlay Displays + Slipper->>GameBoardVM: ResolveChallengeAsync(challengeId, approved) + GameBoardVM->>Hub: ResolveChallenge(challengeId, approved) Hub->>Hub: ValidateChallengeTargetAccessAsync() - Hub->>Service: ResolveChallengeAsync(challengeId, approved) + Hub->>Service: ResolveChallengeAsync() alt approved == false (False Accusation) - Service->>DB: Update targetCard.PlayerId = PlayerBId (Penalty) + Service->>DB: Reassign card.PlayerId = PlayerBId Service->>DB: Set Challenge.Status = Rejected else approved == true (Justified Catch) Service->>DB: Set Challenge.Status = Approved end Hub-->>Challenger: Broadcast "ChallengeResolved" ``` +*Sequence diagram showing phrase submission, challenge initiation, target notification, and penalty resolution.* --- -## 4. SignalR Hub Event Reference +## 4. Offline Action Queueing & Reconnection Resync Workflow -Summary of server-to-client events emitted by `GameHub.cs`: +When network connection is temporarily interrupted during gameplay, SlipItIn ensures action durability and smooth state synchronization: + +```mermaid +sequenceDiagram + autonumber + actor User as Player + participant GameBoardVM as GameBoardViewModel + participant GameStateSvc as GameStateService + participant LocalStorage as LocalStorageService + participant SignalRSvc as SignalRService + participant Hub as GameHub + + Note over User, SignalRSvc: Network Disconnected (Offline) + User->>GameBoardVM: SubmitSlipAsync(card) + GameBoardVM->>GameStateSvc: EnqueueActionAsync("SubmitSlip", args) + GameStateSvc->>LocalStorage: SaveQueuedActionsAsync(queuedList) + LocalStorage-->>GameBoardVM: Saved to Preferences + + Note over SignalRSvc: Network Restored + SignalRSvc->>Hub: Automatic Reconnect (Exponential Backoff) + SignalRSvc->>GameStateSvc: ConnectionStateChanged(true) + GameStateSvc->>GameStateSvc: ResyncAsync() + GameStateSvc->>SignalRSvc: RequestGameStateAsync(gameId) + SignalRSvc->>Hub: RequestGameState(gameId) + Hub-->>GameStateSvc: GameStateUpdated (Fresh DTO) + + GameStateSvc->>GameStateSvc: FlushQueuedActionsAsync() + loop For each QueuedGameAction + GameStateSvc->>SignalRSvc: SendQueuedActionAsync(action) + SignalRSvc->>Hub: InvokeCoreAsync(action.Method, action.Args) + end + GameStateSvc->>LocalStorage: ClearQueuedActionsAsync() +``` +*Sequence diagram showing offline action queueing in local preferences, automatic reconnection, state request, and queued action replay.* + +--- + +## 5. SignalR Hub Event Reference + +Summary of server-to-client events emitted by `GameHub.cs` and handled by `SignalRService`: | Event Name | Scope | Payload | Trigger | |---|---|---|---| | `LobbyCreated` | Caller | `{ GameId, LobbyCode }` | Host calls `CreateLobby()` | | `PlayerJoined` | Group | `GameStateDto` | Player calls `JoinLobby()` | -| `GameStateUpdated` | Group | `GameStateDto` | Player calls `PlayerReady()` | +| `GameStateUpdated` | Group | `GameStateDto` | Player calls `PlayerReady()` or `RequestGameState()` | | `GameStarted` | Group | `{ GameId }` | Host calls `StartGame()` | | `PlayerHandUpdated` | Unicast | `PlayerHandDto` | Hand dealt or updated | | `SlipSubmitted` | Group | `{ PlayerId, CardId, Success }` | Player calls `SubmitSlip()` | @@ -123,3 +177,21 @@ Summary of server-to-client events emitted by `GameHub.cs`: | `ChallengeReceived` | Unicast | `SlipChallengeDto` | Direct alert to target player | | `ChallengeResolved` | Group | `SlipChallengeDto` | Target calls `ResolveChallenge()` | | `Error` | Caller | `{ Message }` | Any authorization or engine exception | + +--- + +## 6. Guidance for Future Agents & Developers + +- **When to Consult**: Refer to this document when adding new game interactions, modifying UI challenge flows, changing reconnection strategies, or replaying offline actions. +- **Invariants**: + - `FlushQueuedActionsAsync()` MUST execute in strictly sequential order based on `CreatedAtUtc`. + - `ResolveChallenge` MUST throw `UnauthorizedAccessException` if invoked by anyone other than the target player. +- **Primary Source Files**: + - `SlipItIn/ViewModels/GameBoardViewModel.cs` + - `SlipItIn/Services/GameStateService.cs` + - `SlipItIn/Services/SignalRService.cs` + - `SlipItIn.Server/Hubs/GameHub.cs` +- **Minimal Validation Command**: + ```bash + dotnet build SlipItIn.slnx + ```