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
+ ```