mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-11 16:44:48 -07:00
cleanup testing utilities, documentation, and AI commentary
This commit is contained in:
parent
bd2382c94e
commit
ef6cea6f6c
150 changed files with 891 additions and 1233 deletions
70
webclient/architecture/README.md
Normal file
70
webclient/architecture/README.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Webatrice architecture diagrams
|
||||
|
||||
Three views of the same architecture at different zoom levels. The `.mmd` files are the source of truth — edit them and re-render when the architecture changes. The `.png` files are committed so the README and GitHub file view render everywhere, including offline.
|
||||
|
||||
For the prose counterpart and conventions, see [../.github/instructions/webclient.instructions.md](../../.github/instructions/webclient.instructions.md) (the canonical AI-tool instruction surface for this package).
|
||||
|
||||
## When to look at which
|
||||
|
||||
| Diagram | Use it when |
|
||||
|---|---|
|
||||
| **[simple](simple.mmd)** | You need a mental model of the request/response loop in ten seconds. Good for onboarding. |
|
||||
| **[detailed](detailed.mmd)** | You're making a structural change and want to see every module, which layer it belongs to, and how data moves between them. |
|
||||
| **[flow](flow.mmd)** | You're debugging a specific round-trip and need to see the runtime order. Shows the `cmdId`-correlated response path vs. the extension-dispatched event path, plus the "no timeout, no retry" caveat. |
|
||||
|
||||
## Simple — high-level flow
|
||||
|
||||

|
||||
|
||||
Application on the left, Servatrice on the right, two-lane racetrack in between. The top lane is outbound (`client.request.*` → `Commands`), the bottom lane is inbound (`Events · Responses` → `client.response.*`), and both lanes ride the same WebSocket. Redux hangs off Application as its in-memory store; IndexedDB sits under Servatrice as the browser-side persistent store reached from hooks via Dexie. Both are stores, both sit outside the racetrack.
|
||||
|
||||
**Color = role:**
|
||||
|
||||
- Blue — application code (UI, hooks, API seams, WebClient)
|
||||
- Purple — transport (WebSocket layer, services)
|
||||
- Amber — state / data stores (Redux, protocol types)
|
||||
- Gray — external systems (Servatrice, IndexedDB)
|
||||
|
||||
## Detailed — layers & dependencies
|
||||
|
||||

|
||||
|
||||
Every meaningful module in the webclient, arranged as a three-lane racetrack: outbound (`src/api/request/` → `commands/`) on top, transport (`WebClientProvider` → `WebClient` → `services/`) in the middle, inbound (`events/` → `src/api/response/`) on the bottom. Application bookend on the left holds UI, hooks, Redux store, and the Dexie persistence pair; Servatrice sits on the right. The protocol satellite (`src/types/` + `src/generated/proto/`) is drawn below with dashed edges up to the modules it types — it's cross-cutting, not on the flow path. Same four-role palette as the simple diagram.
|
||||
|
||||
Load-bearing invariants (enforced on `webclient-websocket-layer`; keep it that way):
|
||||
|
||||
- **UI never imports `@app/websocket` or `@app/api`** — always go through `useWebClient()`.
|
||||
- **Only `src/types/` imports from `@app/generated`** — everywhere else uses `Data` / `Enriched` / `App`.
|
||||
- **Only `*.dispatch.ts` helpers and `*ResponseImpl` classes call `store.dispatch`** — the API response layer is the single inbound seam into Redux.
|
||||
|
||||
## Flow — command → response → event round-trip
|
||||
|
||||

|
||||
|
||||
Scenario: user joins a room. The sequence shows the outbound command path (steps 1–6), the correlated response path matched by `cmdId` in `ProtobufService`'s pending map (steps 7–10), and an unsolicited server event dispatched by proto-extension match against the event registry in `processRoomEvent` / `processSessionEvent` / `processGameEvent` (steps 11–15).
|
||||
|
||||
Read the footnote: `ProtobufService` has no timeout and no retry, and `resetCommands()` on reconnect silently drops in-flight callbacks. Code that needs reconnection resilience has to handle it at a higher layer.
|
||||
|
||||
## Rendering
|
||||
|
||||
npm scripts are defined in [../package.json](../package.json) — no separate build step, no added runtime dependency (everything runs via `npx`).
|
||||
|
||||
```bash
|
||||
# from the webclient/ directory:
|
||||
|
||||
npm run diagram # render all three (simple + detailed + flow)
|
||||
npm run diagram:simple # render just simple.png
|
||||
npm run diagram:detailed # render just detailed.png
|
||||
npm run diagram:flow # render just flow.png
|
||||
```
|
||||
|
||||
Under the hood each command is:
|
||||
|
||||
```bash
|
||||
npx -y -p @mermaid-js/mermaid-cli -p puppeteer mmdc \
|
||||
-i architecture/<name>.mmd -o architecture/<name>.png -b white -s 2
|
||||
```
|
||||
|
||||
`-s 2` renders at 2× scale so the PNG stays crisp on high-DPI displays; `-b white` gives the diagrams a light-mode background that looks right in both GitHub's light and dark themes.
|
||||
|
||||
If `mmdc` fails locally (it spawns headless Chromium — some sandboxed environments block that), paste the `.mmd` contents into [mermaid.live](https://mermaid.live) and export to PNG. The `.mmd` sources remain canonical either way.
|
||||
123
webclient/architecture/detailed.mmd
Normal file
123
webclient/architecture/detailed.mmd
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
config:
|
||||
layout: elk
|
||||
theme: base
|
||||
themeVariables:
|
||||
background: "#ffffff"
|
||||
primaryColor: "#ffffff"
|
||||
primaryBorderColor: "#1f2937"
|
||||
primaryTextColor: "#0b1220"
|
||||
lineColor: "#1f2937"
|
||||
textColor: "#0b1220"
|
||||
edgeLabelBackground: "#ffffff"
|
||||
fontSize: "20px"
|
||||
clusterBkg: "#fafafa"
|
||||
clusterBorder: "#9ca3af"
|
||||
flowchart:
|
||||
htmlLabels: true
|
||||
curve: basis
|
||||
nodeSpacing: 60
|
||||
rankSpacing: 90
|
||||
---
|
||||
flowchart LR
|
||||
%% =========================================================
|
||||
%% Left bookend — browser-side Application
|
||||
%% =========================================================
|
||||
subgraph LBE["<b>Application</b>"]
|
||||
direction TB
|
||||
UI["<b>UI</b><br/><span style='font-size:15px'>containers · components<br/>forms · dialogs</span>"]
|
||||
Hooks["<b>hooks/</b><br/><span style='font-size:15px'>useWebClient · useAutoLogin<br/>useSettings · useKnownHosts</span>"]
|
||||
Store[("<b>@app/store</b><br/><span style='font-size:15px'>server · rooms · game<br/>actions · common</span>")]
|
||||
DTOs["<b>dexie DTOs</b><br/><span style='font-size:15px'>Card · Host · Set<br/>Setting · Token</span>"]
|
||||
IDB[("<b>IndexedDB</b>")]
|
||||
end
|
||||
|
||||
%% =========================================================
|
||||
%% Racetrack — three lanes: outbound / transport / inbound
|
||||
%% =========================================================
|
||||
subgraph RACE[" "]
|
||||
direction TB
|
||||
|
||||
subgraph TOP["<b>Outbound lane</b>"]
|
||||
direction LR
|
||||
Req["<b>src/api/request/</b><br/><span style='font-size:15px'>Authentication · Session · Rooms<br/>Game · Admin · Moderator</span>"]
|
||||
Cmds["<b>commands/</b><br/><span style='font-size:15px'>session · room · game<br/>admin · moderator</span>"]
|
||||
end
|
||||
|
||||
subgraph MID["<b>Transport</b>"]
|
||||
direction LR
|
||||
Provider["<b>WebClientProvider</b>"]
|
||||
WC[["<b>WebClient</b><br/><span style='font-size:15px'>singleton · request · response</span>"]]
|
||||
Svc["<b>services/</b><br/><span style='font-size:15px'>ProtobufService · WebSocketService<br/>KeepAliveService · command-options</span>"]
|
||||
end
|
||||
|
||||
subgraph BOT["<b>Inbound lane</b>"]
|
||||
direction LR
|
||||
Evts["<b>events/</b><br/><span style='font-size:15px'>session · room · game</span>"]
|
||||
Res["<b>src/api/response/</b><br/><span style='font-size:15px'>Session · Room · Game<br/>Admin · Moderator</span>"]
|
||||
end
|
||||
end
|
||||
|
||||
%% =========================================================
|
||||
%% Right bookend — Servatrice
|
||||
%% =========================================================
|
||||
Srv[("<b>Servatrice</b>")]
|
||||
|
||||
%% =========================================================
|
||||
%% Protocol satellite — cross-cutting types
|
||||
%% =========================================================
|
||||
subgraph PROTO["<b>Protocol (cross-cutting)</b>"]
|
||||
direction LR
|
||||
Types["<b>src/types/</b><br/><span style='font-size:15px'>Data · Enriched · App</span>"]
|
||||
Gen["<b>src/generated/proto/</b><br/><span style='font-size:15px'>@bufbuild/protobuf</span>"]
|
||||
end
|
||||
|
||||
%% =========================================================
|
||||
%% UI-side wiring
|
||||
%% =========================================================
|
||||
UI --> Hooks
|
||||
Hooks -- "useWebClient()" --> Provider
|
||||
Provider --> WC
|
||||
UI -- "selectors (read)" --> Store
|
||||
Hooks --> DTOs
|
||||
DTOs <--> IDB
|
||||
|
||||
%% =========================================================
|
||||
%% Outbound — request goes up through the top lane to Srv
|
||||
%% =========================================================
|
||||
WC --> Req
|
||||
Req --> Cmds
|
||||
Cmds --> Svc
|
||||
Svc -- "frames" --> Srv
|
||||
|
||||
%% =========================================================
|
||||
%% Inbound — Srv comes back through services, splits to
|
||||
%% cmdId response (direct) and event-registry dispatch
|
||||
%% =========================================================
|
||||
Srv -- "frames" --> Svc
|
||||
Svc --> Evts
|
||||
Svc -- "response by cmdId" --> Res
|
||||
Evts --> Res
|
||||
Res -- "dispatch" --> Store
|
||||
|
||||
%% =========================================================
|
||||
%% Protocol edges — dashed, cross-cutting
|
||||
%% =========================================================
|
||||
Req -.-> Types
|
||||
Res -.-> Types
|
||||
Cmds -.-> Types
|
||||
Evts -.-> Types
|
||||
Types --> Gen
|
||||
|
||||
%% =========================================================
|
||||
%% Palette — four roles
|
||||
%% =========================================================
|
||||
classDef app fill:#dbeafe,stroke:#1f2937,stroke-width:1.5px,color:#0b1220
|
||||
classDef transport fill:#ede9fe,stroke:#1f2937,stroke-width:1.5px,color:#0b1220
|
||||
classDef store fill:#fde68a,stroke:#1f2937,stroke-width:1.5px,color:#0b1220
|
||||
classDef external fill:#e5e7eb,stroke:#1f2937,stroke-width:1.5px,color:#0b1220
|
||||
|
||||
class UI,Hooks,DTOs,Provider,WC app
|
||||
class Req,Cmds,Svc,Evts,Res transport
|
||||
class Store,Types,Gen store
|
||||
class Srv,IDB external
|
||||
BIN
webclient/architecture/detailed.png
Normal file
BIN
webclient/architecture/detailed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
41
webclient/architecture/flow.mmd
Normal file
41
webclient/architecture/flow.mmd
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
%%{init: {"theme": "base", "themeVariables": {"background": "#ffffff", "primaryColor": "#ffffff", "primaryBorderColor": "#1f2937", "primaryTextColor": "#0b1220", "lineColor": "#1f2937", "textColor": "#0b1220", "signalColor": "#1f2937", "signalTextColor": "#0b1220", "actorBkg": "#ffedd5", "actorBorder": "#1f2937", "actorTextColor": "#0b1220", "actorLineColor": "#1f2937", "sequenceNumberColor": "#ffffff", "noteBkgColor": "#fef3c7", "noteBorderColor": "#1f2937", "noteTextColor": "#0b1220", "labelBoxBkgColor": "#ffffff", "labelTextColor": "#0b1220"}, "sequence": {"showSequenceNumbers": true, "mirrorActors": false, "messageFontSize": "13px", "actorFontSize": "13px", "noteFontSize": "12px", "actorMargin": 14, "boxMargin": 6, "boxTextMargin": 3, "noteMargin": 8, "messageMargin": 48, "diagramMarginX": 8, "diagramMarginY": 8, "width": 120}}}%%
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
|
||||
participant C as Application
|
||||
participant RQ as API Request
|
||||
participant CMD as Command
|
||||
participant PB as Protobuf
|
||||
participant WS as WebSocket
|
||||
participant S as Servatrice
|
||||
participant EV as Event
|
||||
participant RS as API Response
|
||||
|
||||
rect rgb(219, 234, 254)
|
||||
Note over C,S: Request — user action → command
|
||||
C->>RQ: joinRoom(roomId)
|
||||
RQ->>CMD: build RoomCommand
|
||||
CMD->>PB: sendRoomCommand(cmd, onSuccess/onError)
|
||||
PB->>PB: assign cmdId,<br/>register callback
|
||||
PB->>WS: send(bytes)
|
||||
WS->>S: Command
|
||||
end
|
||||
|
||||
rect rgb(254, 243, 199)
|
||||
Note over S,RS: Response — correlated by cmdId
|
||||
S-->>WS: Response (cmdId)
|
||||
WS-->>PB: processServerResponse
|
||||
PB->>RS: onSuccess(response)
|
||||
RS->>RS: dispatch → Redux
|
||||
end
|
||||
|
||||
rect rgb(220, 252, 231)
|
||||
Note over S,RS: Event — no cmdId — dispatched by extension
|
||||
S-->>WS: Event
|
||||
WS-->>PB: processRoomEvent
|
||||
PB->>EV: pick handler by extension
|
||||
EV->>RS: roomEvent(...)
|
||||
RS->>RS: dispatch → Redux
|
||||
end
|
||||
|
||||
Note over PB,WS: No timeout, no retry.<br/>resetCommands() on reconnect<br/>silently drops pending callbacks.
|
||||
BIN
webclient/architecture/flow.png
Normal file
BIN
webclient/architecture/flow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
58
webclient/architecture/simple.mmd
Normal file
58
webclient/architecture/simple.mmd
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
background: "#ffffff"
|
||||
primaryColor: "#ffffff"
|
||||
primaryBorderColor: "#1f2937"
|
||||
primaryTextColor: "#0b1220"
|
||||
lineColor: "#1f2937"
|
||||
textColor: "#0b1220"
|
||||
edgeLabelBackground: "#ffffff"
|
||||
fontSize: "18px"
|
||||
clusterBkg: "#ffffff"
|
||||
clusterBorder: "#9ca3af"
|
||||
flowchart:
|
||||
htmlLabels: true
|
||||
curve: basis
|
||||
nodeSpacing: 55
|
||||
rankSpacing: 90
|
||||
---
|
||||
flowchart LR
|
||||
subgraph APP_COL[" "]
|
||||
direction TB
|
||||
App["<b>Application</b><br/><span style='font-size:13px'>containers · components · hooks</span>"]
|
||||
Rdx[("<b>Redux</b><br/><span style='font-size:12px'>in-memory state</span>")]
|
||||
end
|
||||
|
||||
subgraph SRV_COL[" "]
|
||||
direction TB
|
||||
Srv[("<b>Servatrice</b>")]
|
||||
IDB[("<b>IndexedDB</b><br/><span style='font-size:12px'>local persistent store</span>")]
|
||||
end
|
||||
|
||||
Req["client.request"]
|
||||
Res["client.response"]
|
||||
|
||||
%% Outbound lane (top)
|
||||
App -- "useWebClient()" --> Req
|
||||
Req -- "Commands" --> Srv
|
||||
|
||||
%% Inbound lane (bottom)
|
||||
Srv -- "Events · Responses" --> Res
|
||||
Res -- "dispatch · rerender" --> App
|
||||
|
||||
%% Local stores — Application owns both; edges only to IndexedDB
|
||||
%% (Redux state is implicit — reducers sit under dispatch, selectors under rerender)
|
||||
App -. "Dexie: settings · hosts · cards" .-> IDB
|
||||
|
||||
%% Palette — four roles
|
||||
classDef app fill:#dbeafe,stroke:#1f2937,stroke-width:1.5px,color:#0b1220
|
||||
classDef seam fill:#dbeafe,stroke:#1f2937,stroke-width:1.5px,color:#0b1220
|
||||
classDef store fill:#fde68a,stroke:#1f2937,stroke-width:1.5px,color:#0b1220
|
||||
classDef external fill:#e5e7eb,stroke:#1f2937,stroke-width:1.5px,color:#0b1220
|
||||
|
||||
class App app
|
||||
class Req,Res seam
|
||||
class Rdx store
|
||||
class Srv,IDB external
|
||||
BIN
webclient/architecture/simple.png
Normal file
BIN
webclient/architecture/simple.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Loading…
Add table
Add a link
Reference in a new issue