[Enhancement] C# playground: per-lesson completion tracking (tools:csharp-playground) #43

Closed
opened 2026-04-29 17:12:21 +00:00 by spikerj · 1 comment
Owner

Summary

Enhancement for /tree-of-knowledge/domain(tools:csharp-playground) — the C# playground (CsharpRunner) should track lesson completion per lesson (completed / not completed / in progress as appropriate), not only a generic "run lesson" event that does not distinguish which lessons the learner finished.

Problem

Today it appears completion/progress captures a broad run signal rather than per-lesson state aligned with Tree of Knowledge lessons.

Goals

  • Persist and surface each lesson’s completion state for this playground only.
  • Stay consistent with how other playgrounds will report lesson progress (sibling tickets below).
  • Integrate with lesson sidebar / ToK UX so learners see clear progress.

Acceptance (draft)

  • Model stores lesson id ↔ completion for C# playground runs (authenticated user; define offline/local fallback if applicable).
  • UI reflects done vs not done per lesson in context (sidebar, lesson list, or equivalent).
  • Migrate away from ambiguous generic run-only telemetry where this feature replaces it — document deltas.
  • Aligned reporting API or client contract shared with #44 and #45 (same epic).

Area

CsharpRunner · app-language-runner · Tree of Knowledge / domain tools outlet (csharp-playground).

Sibling tickets (same enhancement pattern)

## Summary **Enhancement** for **`/tree-of-knowledge/domain`(tools:csharp-playground)** — the **C# playground** (`CsharpRunner`) should **track lesson completion per lesson** (completed / not completed / in progress as appropriate), not only a **generic "run lesson"** event that does not distinguish which lessons the learner finished. ## Problem Today it appears completion/progress captures a **broad run** signal rather than **per-lesson** state aligned with Tree of Knowledge lessons. ## Goals - Persist and surface **each lesson’s** completion state for this playground only. - Stay consistent with how other playgrounds will report lesson progress (**sibling tickets** below). - Integrate with **lesson sidebar / ToK** UX so learners see clear progress. ## Acceptance (draft) - [ ] Model stores **lesson id ↔ completion** for C# playground runs (authenticated user; define offline/local fallback if applicable). - [ ] UI reflects **done vs not done** per lesson in context (sidebar, lesson list, or equivalent). - [ ] Migrate away from ambiguous **generic** run-only telemetry where this feature replaces it — document deltas. - [ ] Aligned reporting API or client contract shared with **[#44](https://git.spikersoft.com/spikerj/spikersoft-issues/issues/44)** and **[#45](https://git.spikersoft.com/spikerj/spikersoft-issues/issues/45)** (same epic). ## Area `CsharpRunner` · app-language-runner · Tree of Knowledge / domain `tools` outlet (`csharp-playground`). ## Sibling tickets (same enhancement pattern) - **[#44 — Python playground](https://git.spikersoft.com/spikerj/spikersoft-issues/issues/44)** - **[#45 — JavaScript playground](https://git.spikersoft.com/spikerj/spikersoft-issues/issues/45)**
Author
Owner

Audited the current state of per-lesson completion tracking for the C# playground. Implemented — and partly surpassed. Closing.

Architecture as it stands today

The C# runner is a thin shell over a shared LanguageRunner:

// libraries/features/dev-tools-csharp-runner/src/lib/csharp-runner.ts:14–21
@Component({
  selector: "app-csharp-runner",
  imports: [LanguageRunner],
  template: "<app-language-runner></app-language-runner>",
  providers: [
    { provide: LANGUAGE_RUNNER_CONFIG, useValue: CSHARP_LANGUAGE_RUNNER_CONFIG },
    { provide: LANGUAGE_RUNTIME_ADAPTER, useExisting: CSharpRuntimeAdapter },
    PROGRESS_SYNC_PORT_PROVIDER,
  ],
})
export class CsharpRunner {}

Completion is no longer derived from generic run telemetry. There are now three distinct mechanisms:

  1. Source of truth (durable backend record). UserLessonProgress is one Mongo doc per (UserId, LessonNumber) with attempt count, test results, and timing. Server-side, CodeExecutionWorkerService records an attempt after every submission and only marks completed=true when every graded (non-hint) test passes:

    // SpikerSoft.EventHandlers.CodeExecution/Services/CodeExecutionWorkerService.cs:196–218
    var allPassed = response.Success && gradedResults.Count > 0 && gradedResults.All(t => t.Passed);
    var outcome = await _lessonProgressService.RecordAttemptAsync(
      request.UserId ?? "anonymous",
      request.LessonId.Value, allPassed, response.TestResults,
      response.ExecutionTimeMs, cancellationToken);
    
  2. Offline / local fallback. The C# playground grades client-side (WASM .NET) and queues completion deltas in IndexedDB via ProgressSyncService for later server re-verification. Each queued attempt carries lessonNumber, attemptToken, language: "csharp", allTestsPassed, and the studentCode itself. See libraries/features/dev-tools-csharp-runner/src/lib/csharp-runner.service.ts:522–542 and libraries/platform/progress-sync/src/lib/csharp-playground.idb.ts:45–73.

  3. UI integration. LessonSidebar consumes a completedLessonNumbers input and renders checkmark / lock / progress-count state declaratively:

    <!-- libraries/platform/language-runner/src/lib/language-runner.html:184–190 -->
    <app-lesson-sidebar
      [lessons]="lessonsSignal()"
      [completedLessonNumbers]="completedLessonsSignal()"
      [selectedLessonNumber]="selectedLessonNumber()"
      [curriculumTitle]="curriculumTitle"
      (lessonSelected)="onLessonSelected($event)">
    </app-lesson-sidebar>
    
    // libraries/shared/lesson-panes/src/lib/lesson-sidebar/lesson-sidebar.ts:31–53
    protected readonly completedSet = computed(() => new Set(this.completedLessonNumbers()));
    protected isCompleted(lesson: LessonCatalogEntry): boolean {
      return this.completedSet().has(lesson.lessonNumber);
    }
    

Acceptance criteria — verdict by row

AC Verdict Evidence
Model stores lessonId ↔ completion for C# runs (with offline/local fallback) SURPASSED UserLessonProgress Mongo doc + IndexedDB offline queue + automatic server re-verification on reconnect. Goes beyond a flat "completed" bit — stores attempt count, test results, timing.
UI reflects done vs not done per lesson IMPLEMENTED LessonSidebar renders checkmark/lock/bullet + completedCount aggregate; LanguageRunner loads backend progress + pending offline passes and feeds the sidebar.
Migrate away from ambiguous generic run-only telemetry PARTIALLY IMPLEMENTED The completion source of truth has fully migrated to UserLessonProgress. Generic activity events (run-code-success, run-code-fail, run-lesson) still coexist for analytics, but no longer drive completion. No standalone migration doc, but the README + code comments capture the delta. Considered acceptable because telemetry and completion serve different purposes now.
Aligned reporting API/client contract shared with #44 / #45 IMPLEMENTED Shared LanguageRunner, ProgressSyncPort, QueuedProgressInput, /Lessons/progress, /Lessons/progress/batch. C# / Python / JavaScript runners all share the same contract via the language-runner cluster + progress-sync platform lib.

Known follow-up (not blocking #43)

The server-side ExecuteLessonCodeCommand is still annotated as [TrackableActivity("tools.csharp-playground", "run-lesson")] even though Python and JavaScript share the same /CSharpCodeRunner endpoint. So the durable completion path is correct cross-language, but the generic activity row is misattributed to csharp-playground for Python/JS submissions. That's a cross-cutting bug under #44 / #45's scope (telemetry attribution), not #43's. Worth a separate ticket if not already on the radar.

Sibling tickets

The same shared infrastructure (LanguageRunner + ProgressSyncPort + UserLessonProgress) covers Python (#44) and JavaScript (#45) by construction. They could be re-audited and closed under the same logic, modulo the telemetry attribution caveat above.

Closing #43.

Audited the current state of per-lesson completion tracking for the C# playground. **Implemented — and partly surpassed.** Closing. ## Architecture as it stands today The C# runner is a thin shell over a shared `LanguageRunner`: ```ts // libraries/features/dev-tools-csharp-runner/src/lib/csharp-runner.ts:14–21 @Component({ selector: "app-csharp-runner", imports: [LanguageRunner], template: "<app-language-runner></app-language-runner>", providers: [ { provide: LANGUAGE_RUNNER_CONFIG, useValue: CSHARP_LANGUAGE_RUNNER_CONFIG }, { provide: LANGUAGE_RUNTIME_ADAPTER, useExisting: CSharpRuntimeAdapter }, PROGRESS_SYNC_PORT_PROVIDER, ], }) export class CsharpRunner {} ``` Completion is no longer derived from generic run telemetry. There are now three distinct mechanisms: 1. **Source of truth (durable backend record).** `UserLessonProgress` is one Mongo doc per `(UserId, LessonNumber)` with attempt count, test results, and timing. Server-side, `CodeExecutionWorkerService` records an attempt after every submission and only marks `completed=true` when every graded (non-hint) test passes: ```cs // SpikerSoft.EventHandlers.CodeExecution/Services/CodeExecutionWorkerService.cs:196–218 var allPassed = response.Success && gradedResults.Count > 0 && gradedResults.All(t => t.Passed); var outcome = await _lessonProgressService.RecordAttemptAsync( request.UserId ?? "anonymous", request.LessonId.Value, allPassed, response.TestResults, response.ExecutionTimeMs, cancellationToken); ``` 2. **Offline / local fallback.** The C# playground grades client-side (WASM .NET) and queues completion deltas in IndexedDB via `ProgressSyncService` for later server re-verification. Each queued attempt carries `lessonNumber`, `attemptToken`, `language: "csharp"`, `allTestsPassed`, and the `studentCode` itself. See `libraries/features/dev-tools-csharp-runner/src/lib/csharp-runner.service.ts:522–542` and `libraries/platform/progress-sync/src/lib/csharp-playground.idb.ts:45–73`. 3. **UI integration.** `LessonSidebar` consumes a `completedLessonNumbers` input and renders checkmark / lock / progress-count state declaratively: ```html <!-- libraries/platform/language-runner/src/lib/language-runner.html:184–190 --> <app-lesson-sidebar [lessons]="lessonsSignal()" [completedLessonNumbers]="completedLessonsSignal()" [selectedLessonNumber]="selectedLessonNumber()" [curriculumTitle]="curriculumTitle" (lessonSelected)="onLessonSelected($event)"> </app-lesson-sidebar> ``` ```ts // libraries/shared/lesson-panes/src/lib/lesson-sidebar/lesson-sidebar.ts:31–53 protected readonly completedSet = computed(() => new Set(this.completedLessonNumbers())); protected isCompleted(lesson: LessonCatalogEntry): boolean { return this.completedSet().has(lesson.lessonNumber); } ``` ## Acceptance criteria — verdict by row | AC | Verdict | Evidence | |----|---------|----------| | **Model stores `lessonId ↔ completion` for C# runs (with offline/local fallback)** | **SURPASSED** | `UserLessonProgress` Mongo doc + IndexedDB offline queue + automatic server re-verification on reconnect. Goes beyond a flat "completed" bit — stores attempt count, test results, timing. | | **UI reflects done vs not done per lesson** | **IMPLEMENTED** | `LessonSidebar` renders checkmark/lock/bullet + `completedCount` aggregate; `LanguageRunner` loads backend progress + pending offline passes and feeds the sidebar. | | **Migrate away from ambiguous generic run-only telemetry** | **PARTIALLY IMPLEMENTED** | The completion *source of truth* has fully migrated to `UserLessonProgress`. Generic activity events (`run-code-success`, `run-code-fail`, `run-lesson`) still coexist for analytics, but no longer drive completion. No standalone migration doc, but the README + code comments capture the delta. Considered acceptable because telemetry and completion serve different purposes now. | | **Aligned reporting API/client contract shared with #44 / #45** | **IMPLEMENTED** | Shared `LanguageRunner`, `ProgressSyncPort`, `QueuedProgressInput`, `/Lessons/progress`, `/Lessons/progress/batch`. C# / Python / JavaScript runners all share the same contract via the language-runner cluster + progress-sync platform lib. | ## Known follow-up (not blocking #43) The server-side `ExecuteLessonCodeCommand` is still annotated as `[TrackableActivity("tools.csharp-playground", "run-lesson")]` even though Python and JavaScript share the same `/CSharpCodeRunner` endpoint. So the *durable completion* path is correct cross-language, but the *generic activity row* is misattributed to `csharp-playground` for Python/JS submissions. That's a cross-cutting bug under #44 / #45's scope (telemetry attribution), not #43's. Worth a separate ticket if not already on the radar. ## Sibling tickets The same shared infrastructure (`LanguageRunner` + `ProgressSyncPort` + `UserLessonProgress`) covers Python (#44) and JavaScript (#45) by construction. They could be re-audited and closed under the same logic, modulo the telemetry attribution caveat above. Closing #43.
Sign in to join this conversation.