44
55** Live:** https://qwertide.azurewebsites.net
66
7- A browser-based typing-speed game built end to end in ** C# / .NET 8** . A passage
8- appears , you type it, and Qwertide tracks your words- per- minute and accuracy live —
9- then drops your run onto a persistent, API- backed leaderboard .
7+ A browser-based typing game written end to end in ** C# / .NET 8** . A passage shows
8+ up , you type it, and Qwertide tracks your words per minute and accuracy as you go,
9+ then saves your run to a leaderboard backed by a real API .
1010
11- The entire front-end and game loop are ** Blazor WebAssembly** (the scoring engine
12- is pure C#; the only JavaScript is a 22-line caret-positioning helper). The
13- leaderboard is served by an ** ASP.NET Core Web API** backed by ** EF Core + SQLite** .
14- In production both ship as a ** single Azure App Service** . It was built as a
15- focused, production-minded portfolio piece for a Junior C#/.NET role.
11+ The front-end and game loop are all ** Blazor WebAssembly** . The scoring is pure C#;
12+ the only JavaScript is a 22-line helper that positions the caret. The leaderboard
13+ runs on an ** ASP.NET Core Web API** with ** EF Core and SQLite** behind it. In
14+ production the two ship together as one ** Azure App Service** . I built it as a
15+ portfolio piece for a junior C#/.NET role, and tried to keep it small but
16+ production-minded rather than a pile of half-finished features.
1617
1718---
1819
@@ -42,13 +43,13 @@ focused, production-minded portfolio piece for a Junior C#/.NET role.
4243
4344## Overview
4445
45- Qwertide is a full-stack, single-page typing test. The interesting engineering is
46- deliberately * not * in the UI: the words-per-minute and accuracy math is isolated in
47- a pure, dependency-free domain class so it can be unit-tested directly , and the
48- leaderboard is a small but properly-hardened REST API rather than a localStorage
49- hack . The project's goal is to demonstrate the complete C#/.NET stack — WASM
50- front-end, Web API, ORM with migrations, a tested domain layer, CI, and a public
51- cloud deployment — in one coherent, honestly-scoped app .
46+ Qwertide is a full-stack, single-page typing test. Most of the actual engineering
47+ sits behind the UI rather than in it. The WPM and accuracy math lives in a pure,
48+ dependency-free class so it can be unit-tested on its own , and the leaderboard is a
49+ small REST API with real input validation and rate limiting instead of a
50+ localStorage shortcut . The point of the project is to show the whole C#/.NET stack
51+ working together: a WASM front-end, a Web API, an ORM with migrations, a tested
52+ domain layer, CI, and a live cloud deploy .
5253
5354## Key features
5455
@@ -90,16 +91,16 @@ without a browser and the client depending on the API only through an interface.
9091
9192** Key decisions**
9293
93- - ** Pure domain layer.** All metric math lives in ` TypingSession ` as static,
94- UI-free functions, so the test project references it directly and the Blazor
95- component owns only rendering.
96- - ** Interface-driven leaderboard.** The UI codes against ` ILeaderboardService ` ;
97- the active implementation (` ApiLeaderboardService ` ) is swapped in via DI without
98- any UI changes. A second ` localStorage ` implementation exists to demonstrate the
99- abstraction ( it is not currently wired in as a runtime fallback) .
94+ - ** Pure domain layer.** All the metric math lives in ` TypingSession ` as static,
95+ UI-free functions, so the test project can reference it directly and the Blazor
96+ component only has to worry about rendering.
97+ - ** Interface-driven leaderboard.** The UI codes against ` ILeaderboardService ` , and
98+ the active implementation (` ApiLeaderboardService ` ) gets swapped in via DI without
99+ touching the UI. There's also a second ` localStorage ` implementation that shows
100+ the abstraction works; it isn't currently wired in as a runtime fallback.
100101- ** Single-service hosting.** In production the API serves the published WASM
101- client and falls back to ` index.html ` for client-side routes, so there is one
102- deployable, one origin, and no production CORS.
102+ client and falls back to ` index.html ` for client-side routes. That leaves one
103+ deployable, one origin, and no CORS to worry about in prod .
103104
104105### The scoring engine
105106
@@ -113,9 +114,9 @@ TypingSession.GrossWpmFor(charsTyped: 50, elapsedSeconds: 60); // -> 10
113114TypingSession .AccuracyFor (correctKeystrokes : 45 , totalKeystrokes : 50 ); // -> 90
114115```
115116
116- ` CountKeystrokes ` counts * every* character committed in a single input event
117- (a fast typist or IME can commit several between ticks), so the accuracy
118- denominator is never under-counted.
117+ ` CountKeystrokes ` counts every character committed in a single input event, since a
118+ fast typist or an IME can commit several between ticks. That way the accuracy
119+ denominator never gets under-counted.
119120
120121## Tech stack
121122
@@ -186,8 +187,8 @@ that break naive implementations: zero elapsed time, all-errors, empty input,
186187multi-character input events, and derived-metric state. Tests target the pure
187188domain layer directly, so they run fast and need no browser or HTTP host.
188189
189- ** Scope, stated honestly: ** test coverage is the scoring engine. API/controller
190- integration tests, component tests, and end-to-end tests are not yet present (see
190+ What's actually covered is the scoring engine. There are no API/controller
191+ integration tests, component tests, or end-to-end tests yet (see
191192[ Future improvements] ( #future-improvements-not-yet-implemented ) ).
192193
193194## CI/CD
@@ -212,9 +213,8 @@ there is no automated CD pipeline yet.
212213Live on ** Azure App Service** (Linux) as a single service: the ASP.NET Core API
213214hosts both the leaderboard endpoints and the published Blazor client. The SQLite
214215file lives on the persistent ` /home ` share, so scores survive restarts and
215- redeploys. The complete, reproducible runbook — resource creation, connection
216- string, HTTPS-only, and the publish/zip/deploy commands — is in
217- [ DEPLOY.md] ( DEPLOY.md ) .
216+ redeploys. The full runbook (resource creation, connection string, HTTPS-only, and
217+ the publish/zip/deploy commands) is in [ DEPLOY.md] ( DEPLOY.md ) .
218218
219219## Performance considerations
220220
@@ -309,10 +309,10 @@ overridable by environment variables (double-underscore notation).
309309## Engineering tradeoffs
310310
311311- ** No authentication.** A typing game's leaderboard doesn't need accounts, so the
312- API is anonymous. The consequence — accepted deliberately — is that scores are
313- client-submitted and not server-verified; ` [Range] ` validation blocks absurd
314- values but not a plausible fake. Real anti-cheat would require server-side
315- gameplay validation or auth, which is out of scope for this piece .
312+ API is anonymous. The trade-off, which I took on purpose, is that scores are
313+ client-submitted and not server-verified. ` [Range] ` validation blocks absurd
314+ values but won't catch a plausible fake. Real anti-cheat would mean server-side
315+ gameplay validation or auth, and that's out of scope here .
316316- ** SQLite over a managed SQL service.** Zero-cost, zero-ops, and ideal for a
317317 single-instance portfolio app, at the cost of horizontal scalability (see below).
318318- ** Migrate-on-startup.** Convenient for a one-instance deploy; a controlled
@@ -322,7 +322,7 @@ overridable by environment variables (double-underscore notation).
322322
323323## Known limitations
324324
325- These are real gaps, listed so the scope is unambiguous :
325+ The gaps, spelled out so the scope is clear :
326326
327327- Scores are ** not authenticated or server-verified** (spoofable by design).
328328- ** SQLite is single-node** — the app cannot currently scale out to multiple App
@@ -340,7 +340,7 @@ These are real gaps, listed so the scope is unambiguous:
340340
341341## Future improvements (not yet implemented)
342342
343- Prioritised, and clearly separate from what exists today :
343+ Roughly in priority order. None of these exist yet :
344344
3453451 . ** API integration tests** with ` WebApplicationFactory ` , plus bUnit component
346346 tests and a Playwright E2E happy-path; publish coverage from CI.
0 commit comments