6.7 KiB
6.7 KiB
Implementation Plan: Scoreboard: Ranking, Matrix, Event Log and Score Graph 1.00
1. Architectural Reconnaissance
- Codebase style & conventions: Node.js npm-workspace monorepo with a NestJS 10 backend and Angular 17 standalone frontend, both written in TypeScript. Backend request/business logic is service-oriented; live updates use RxJS-backed NestJS SSE. Frontend feature state uses private Angular signals exposed as read-only signals, with SSE frames normalized in
ScoreboardStoreand then applied immutably. The Event Log template already renderscategoryAbbreviationandchallengeName; the defect originates upstream because the solve publish path and authenticated SSE mapper omit those fields, and the frontend currently replaces them with empty strings. - Data Layer: TypeORM with
better-sqlite3.ChallengesService.submitFlagalready joinschallengetocategory, so the successful transaction haschallenge.nameandchallenge.abbreviationavailable without an additional query. The reloaded Event Log projection independently joinssolve,user,challenge, andcategory, which explains why labels appear after refresh. No schema or migration is required. - Test Framework & Structure: Root Jest 29 multi-project configuration with
ts-jest; backend tests are undertests/backend/using Nest testing utilities/Supertest and frontend tests are undertests/frontend/using jsdom. All tests run from the repository root withnpm test; focused suites can usenpm run test:backendandnpm run test:frontend. Keep regression coverage in the existing dedicated test folders, not beside source files. - Required Tools & Dependencies: No new package, system tool, global CLI, persistent
/datafixture, orsetup.shchange is required. Existing Node/npm, TypeScript, Jest, NestJS, Angular, RxJS, TypeORM, and SQLite dependencies cover the implementation. Verification should usenpm testandnpm run build; the repository exposes no lint script.
2. Impacted Files
- To Modify:
backend/src/modules/challenges/dto/challenges.dto.ts— extend the typed live solve contract withchallengeNameandcategoryAbbreviation.backend/src/modules/challenges/challenges.service.ts— include challenge/category labels in the post-insert hub payload using metadata already loaded bysubmitFlag.backend/src/modules/challenges/events.controller.ts— forward the two labels in authenticated solve SSE frames, preserving the established snake_case wire format and accepting current camelCase hub fields.frontend/src/app/features/scoreboard/scoreboard.pure.ts— extendSolveLivePayloadso the normalized live event carries the Event Log labels.frontend/src/app/features/scoreboard/scoreboard.store.ts— parse label fields from SSE aliases and populate the newly prependedEventLogRowinstead of writing empty strings.tests/backend/challenges-submit-flag.spec.ts— strengthen the existing hub-publish success-path test to assert challenge name and category abbreviation.tests/frontend/scoreboard.store.spec.ts— add focused regression assertions proving an SSE-pushed Event Log row retains both labels.docs/api/challenges.md— update the documented authenticated solve-frame contract and example fields.docs/guides/scoreboard-stream.md— document that live solve frames carry display metadata needed by the Event Log.
- To Create: None.
3. Proposed Changes
- Database / Schema Migration: Make no database changes. Reuse the challenge/category metadata already selected in
ChallengesService.submitFlag; keep the existing reload query inScoreboardService.getEventLogunchanged. - Backend Logic & APIs:
- Add required
challengeNameandcategoryAbbreviationproperties toSolveEventPayloadso omissions are caught at the backend contract boundary. - Extend the private
publishSolveEventinput and itsSseHubService.emitScoreboardpayload withchallenge.nameandchallenge.abbreviation, passing them from the successful fresh-solve branch only. Preserve current idempotency:already_solved, failed validation, and uniqueness-race fallback paths must not publish a new event. - Extend
ChallengesEventsController.solveFrameto emitchallenge_nameandcategory_abbreviationalongsidechallenge_id, player, scoring, and timing data. Do not add a lookup or business logic to the controller; it should remain a pure transport mapping over the enriched hub payload. - Keep all existing fields and aliases intact so Ranking, Matrix, Graph, Challenges Board, and any current stream consumers remain backward-compatible.
- Add required
- Frontend UI Integration:
- Extend
SolveLivePayloadwithchallengeNameandcategoryAbbreviation. - In
ScoreboardStore.handleSseFrame, normalize both snake_case fields (challenge_name,category_abbreviation) and camelCase fallbacks (challengeName,categoryAbbreviation) in the same style as existing solve properties. - In
applySolveEvent, copy those normalized values into the newEventLogRow. LeaveEventLogComponentunchanged because its.whatspan already renders the abbreviation followed by challenge name, and leave the existing deduplication/cap behavior unchanged. - Update API/stream documentation so the public contract matches the corrected wire payload and explains that both initial REST rows and live SSE rows contain identical display metadata.
- Extend
4. Test Strategy
- Target Unit Test File:
tests/backend/challenges-submit-flag.spec.ts: enhance the existing “emits an SSE solve payload” test to assert the emitted hub event contains the seeded challenge name andCRYabbreviation. This directly verifies the root publish boundary without adding a large SSE/network fixture.tests/frontend/scoreboard.store.spec.ts: extend the existing live solve-frame test, or add one minimal test, withchallenge_nameandcategory_abbreviation, then assert the prepended Event Log row contains both exact values while retaining awarded points. This covers the user-visible regression through the store’s SSE normalization path without UI/visual testing.
- Mocking Strategy: Reuse the existing in-memory SQLite Nest application and real
SseHubServicesubscription in the backend test; no external service mocks or persistent files are needed. ReuseFakeApi, the fakeDestroyRef, and the lightweight fakeEventSourceLikein the frontend test; feed a syntheticMessageEventand inspect the public read-onlyeventLogsignal. Do not add Playwright, browser, visual, filesystem, network, or/datafixtures. Implement test-first: make the focused assertions fail against the current omission, then apply the smallest payload-contract changes and run the rootnpm testplusnpm run build.