updated readme
This commit is contained in:
@@ -276,8 +276,9 @@ The same Blockly and Rete editors available at `/tools/(tools:blockly)` and `/to
|
||||
SpikerSoft's flagship coding-education tools run three parallel tracks:
|
||||
|
||||
- **C# Playground** — 95-lesson curriculum graded by **Roslyn** server-side and **.NET WebAssembly** in the browser.
|
||||
- **Python Playground** — ~75-lesson curriculum graded by **CPython subprocess** server-side and **Pyodide** in the browser.
|
||||
- **JavaScript Playground** — **134-lesson** curriculum (8 orientation tutorials + 126 graded challenges across 17 tiers) graded by **Node** server-side and **QuickJS-WASM** in the browser. Same harness contract as Python (`runTests()` returning `PASS:`/`FAIL:`/`ERROR:` strings); offline + Run-locally + batch re-verification all work the same way. Every challenge ships with a reference solution that's executed end-to-end through Node by `JavaScriptChallenge_ReferenceSolutionPasses`.
|
||||
- **Python Playground** — ~101-lesson curriculum graded by **CPython subprocess** server-side and **Pyodide** in the browser.
|
||||
- **JavaScript Playground** — **138-lesson** curriculum (10 orientation tutorials + 128 graded challenges across 17 tiers) graded by **Node** server-side and **QuickJS-WASM** in the browser. Same harness contract as Python (`runTests()` returning `PASS:`/`FAIL:`/`ERROR:` strings); offline + Run-locally + batch re-verification all work the same way. Every challenge ships with a reference solution that's executed end-to-end through Node by `JavaScriptChallenge_ReferenceSolutionPasses`.
|
||||
- **Regex Playground** — 12-chapter curriculum graded **fully in-browser** against a pre-computed `RegexLessonPlan` and re-verified server-side through Node (not .NET) to preserve JavaScript regex flavor. See [Regex Playground & Curriculum](#regex-playground--curriculum) below.
|
||||
|
||||
All three tracks share the same lesson UI (the language-agnostic `LanguageRunner` Angular component), the same attempt/progress APIs, the same PASS/FAIL line protocol, and the same offline-first PWA model. A student can start a lesson at home, finish it on a campus bus with no signal, and see their unlocks update the moment connectivity returns. The JavaScript Playground is reachable at `/tools/(tools:javascript-playground)`, Python at `/tools/(tools:python-playground)`, and C# at `/tools/(tools:csharp-playground)`.
|
||||
|
||||
@@ -289,14 +290,14 @@ Python lessons live under [`SpikerSoft.Business/Domain/Lessons/Curriculum/Python
|
||||
|
||||
| Tier | Numbers | Topic |
|
||||
|------|---------|-------|
|
||||
| `Tier00_Welcome` | 20001-20006 | Welcome tutorials (intro, modules, functions, print, first submit) |
|
||||
| `Tier00_Welcome` | 20001-20007 | Welcome tutorials (intro, modules, functions, print, first submit, **how the test harness works**) |
|
||||
| `Tier01_Foundations` | 20100-20107 | Hello World, variables (int, float, bool, str, None) |
|
||||
| `Tier02_Operators` | 20200-20206 | Arithmetic, comparison, logical, conditional expression, f-strings |
|
||||
| `Tier03_ControlFlow` | 20300-20307 | if / elif / else, `match`/`case`, dict dispatch, for / while / iteration |
|
||||
| `Tier03_ControlFlow` | 20300-20307 | if / elif / else, dict dispatch, for / while / iteration (`match`/`case` is now introduced in Tier 13 patterns) |
|
||||
| `Tier04_Functions` | 20400-20405 | return, multi-param, keyword args, defaults, lambda |
|
||||
| `Tier05_Collections` | 20500-20506 | list, append, iteration, dict, set, nested lists, tuple |
|
||||
| `Tier06_Strings` | 20600-20604 | slicing, split/join, replace/case, `''.join` accumulation, try/except `int()` |
|
||||
| `Tier07_Classes` | 20700-20707 | class, `__init__`, methods, `@property`, `@staticmethod`/`@classmethod`, `__repr__`, `_private`, `self` |
|
||||
| `Tier06_Strings` | 20600-20605 | slicing, split/join, replace/case, `''.join` accumulation, `isdigit()` validation, **f-strings (canonical)** |
|
||||
| `Tier07_Classes` | 20699-20707 | **decorator-syntax recipe**, class, `__init__`, methods, `@property`, `@staticmethod`/`@classmethod`, `__repr__`, `_private`, `self` |
|
||||
| `Tier08_Inheritance` | 20800-20805 | base/derived, override, `abc.ABC`, duck typing, multiple inheritance, `super()` |
|
||||
| `Tier09_Generics` | 20900-20902 | `TypeVar`, generic class (`Stack[T]`), bounded generics |
|
||||
| `Tier10_Exceptions` | 21000-21003 | try/except, finally, custom exception, context manager (`with`) |
|
||||
@@ -314,24 +315,44 @@ JavaScript lessons live under [`SpikerSoft.Business/Domain/Lessons/Curriculum/Ja
|
||||
|
||||
| Tier | Numbers | Topic |
|
||||
|------|---------|-------|
|
||||
| `Tier00_Welcome` | 30001-30008 | Welcome tutorials (history of JS, engines, browser vs Node, console basics, code style, reading errors, first submit) |
|
||||
| `Tier01_Foundations` | 30100-30109 | console.log, numbers, strings, booleans, null/undefined, typeof, let vs const, template literals, multi-line |
|
||||
| `Tier00_Welcome` | 30001-30010 | Welcome tutorials (history of JS, engines, browser vs Node, console basics, code style, reading errors, first submit, **how the test harness works**, **function recipe**) |
|
||||
| `Tier01_Foundations` | 30100-30109 | console.log, numbers, strings, booleans, null/undefined, typeof, let vs const, template literals (preview), multi-line |
|
||||
| `Tier02_Operators` | 30200-30207 | Arithmetic, `===`/`!==`, logical, ternary, modulo, ++/--, compound assignment, concat-vs-template |
|
||||
| `Tier03_ControlFlow` | 30300-30308 | if/else, switch, for, for-of, for-in, while, do-while, break/continue |
|
||||
| `Tier04_Functions` | 30400-30409 | Declaration vs expression, arrow, defaults, rest, spread, early return, purity, closures, hoisting, higher-order |
|
||||
| `Tier05_Arrays` | 30500-30508 | literal/index, push/pop, length, slice/splice, concat/spread, indexOf/includes, sort, join/split, Array.from/of |
|
||||
| `Tier06_Strings` | 30600-30607 | length/index, case, slice, includes, replaceAll, split, trim, padStart |
|
||||
| `Tier06_Strings` | 30600-30608 | length/index, case, slice, includes, replaceAll, split, trim, padStart, **template literals (canonical)** |
|
||||
| `Tier07_Objects` | 30700-30708 | literal, dot/bracket, mutation, keys/values/entries, destructuring, shorthand, spread/assign, JSON, nested |
|
||||
| `Tier08_Iteration` | 30800-30808 | forEach, map, filter, reduce, find, some/every, flat/flatMap, chaining, sort comparator |
|
||||
| `Tier09_Classes` | 30900-30908 | class, ctor, methods, getters/setters, static, this rules, arrow this, bind/call/apply, #private |
|
||||
| `Tier10_Inheritance` | 31000-31005 | extends/super, override, super in methods, instanceof, mixins, Object.create |
|
||||
| `Tier11_Modules` | 31100-31104 | IIFE module, Symbol.iterator, Symbol keys, Object.freeze, namespace export |
|
||||
| `Tier12_Errors` | 31200-31205 | throw/catch, finally, custom Error, name+message, re-throw, async errors |
|
||||
| `Tier13_Async` | 31300-31309 | callbacks, Promise basics, .then chains, resolve/reject, async, await, all/race/allSettled, sequential vs parallel |
|
||||
| `Tier11_Modules` | 31099-31104 | **ESM export/import recipe**, IIFE module, Symbol.iterator, Symbol keys, Object.freeze, namespace export |
|
||||
| `Tier12_Errors` | 31200-31204 | throw/catch, finally, custom Error, name+message, re-throw (errors-in-async moved to Tier 13) |
|
||||
| `Tier13_Async` | 31300-31310 | callbacks, Promise basics, .then chains, resolve/reject, async, await, all/race/allSettled, sequential vs parallel, **errors in async functions** |
|
||||
| `Tier14_Modern` | 31400-31407 | optional chaining, nullish coalescing, Map, Set, WeakMap, generators, for-of generator, generator iterator |
|
||||
| `Tier15_Patterns` | 31500-31504 | revealing module, observer (event bus), factory, strategy, singleton |
|
||||
| `Tier16_Performance` | 31600-31604 | memoization, debounce, throttle, avoiding O(n²), lazy generators |
|
||||
|
||||
### Pedagogical contract: no concept used before introduction
|
||||
|
||||
Every challenge lesson is reachable from "Hello, World" using only language features that were formally introduced (and graded) in an earlier lesson. A student who works the curriculum top-to-bottom never has to leave the platform to look up a syntactic form they have not been taught — if the reference solution uses an `f-string`, decorator, optional chain, or generator expression, an earlier lesson exists that puts that exact construct in front of them as a "recipe to copy". Reference solutions for every gradable challenge are checked in alongside the lesson and executed end-to-end on every CI run (`ReferenceSolutionPasses` in `SpikerSoft.Tests.Unit`), so a contract violation surfaces as a red test, not as a confused student.
|
||||
|
||||
The audit findings that drove the most recent Python and JavaScript reorderings are living documents in [`docs/curriculum/python-curriculum-audit.md`](docs/curriculum/python-curriculum-audit.md) and [`docs/curriculum/javascript-curriculum-audit.md`](docs/curriculum/javascript-curriculum-audit.md). Re-run the same audit when adding a new tier; the verification gate is `dotnet test SpikerSoft.Tests.Unit --filter "ReferenceSolutionPasses"`.
|
||||
|
||||
### Regex Playground & Curriculum
|
||||
|
||||
The fourth language track is a 12-chapter regular-expressions curriculum under [`SpikerSoft.Business/Domain/Lessons/Curriculum/Regex/`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Curriculum/Regex/) (`Chapter01_Literals` → `Chapter12_Recipes`, covering literals, special characters, character classes, quantifiers, anchors, alternation, capturing groups, back-references, replace, lookarounds, named groups + Unicode property escapes, and idiomatic recipes). Lessons are written as `RegexLessonStrategyBase` subclasses that hand the SPA a serialized `RegexLessonPlan` — test text plus expected matches/replacements row-by-row.
|
||||
|
||||
Grading mechanics differ deliberately from the C# / Python / JavaScript tracks:
|
||||
|
||||
- **Live grading runs entirely in the browser.** [`RegexLessonRunnerService`](spikersoft-angular/libraries/features/dev-tools-reg-ex/src/lib/regex-lesson-runner.service.ts) and [`RegexLessonGraderService`](spikersoft-angular/libraries/features/dev-tools-reg-ex/src/lib/regex-lesson-grader.service.ts) (in `libraries/features/dev-tools-reg-ex`) feed the student's pattern through the browser's native `RegExp` against the plan's test text and emit row-level pass/fail just like the JS / Python harnesses do.
|
||||
- **Server re-verification routes through Node, not .NET.** When an offline-graded regex submission flushes through `POST /api/Lessons/progress/batch`, [`RegexLessonGradingExecutor`](spikersoft-backend/SpikerSoft.Business/Domain/CodeExecution/Execution/RegexLessonGradingExecutor.cs) builds a JS harness via [`RegexLessonHarnessBuilder`](spikersoft-backend/SpikerSoft.Business/Domain/CodeExecution/Execution/RegexLessonHarnessBuilder.cs) and runs it on the worker's Node subprocess (the same `JavaScriptLessonExecutor` plumbing the JS Playground uses). This is **not** a casual choice — JavaScript regex differs from .NET's `System.Text.RegularExpressions` in ways that matter for the curriculum: the `v`-flag set operations `[[a-z]--[aeiou]]` / `&&`, JS-only replacement patterns `` $` `` / `$'`, and the supported `\p{...}` Unicode property names all diverge between engines. Re-grading through Node guarantees the server records the same pass/fail the student saw locally.
|
||||
- **No second runtime in the browser.** Regex lessons need no Pyodide, no QuickJS, no .NET WASM — the SPA already has `RegExp`. Offline support is automatic.
|
||||
|
||||
Server-side regex re-grading rides the [Offline Lesson Re-Grading via Worker RPC](#offline-lesson-re-grading-via-worker-rpc) pipeline (below), so a code-runner crash does not take the API down. SPA-side details and the in-app "Lesson" pane / cheatsheet UI live in [`libraries/features/dev-tools-reg-ex/README.md`](spikersoft-angular/libraries/features/dev-tools-reg-ex/README.md).
|
||||
|
||||
> **WASM exclude.** [`SpikerSoft.Wasm.csproj`](spikersoft-backend/SpikerSoft.Wasm/SpikerSoft.Wasm.csproj) excludes `Regex*.cs` from the link-included executor sources — regex re-grading is server-only by design.
|
||||
|
||||
### Dual execution
|
||||
|
||||
| Path | Where | When | Latency |
|
||||
@@ -361,7 +382,46 @@ ENV CodeExecution__NodeExecutable=/usr/bin/node
|
||||
|
||||
Two startup probes (`PythonInterpreterProbeHostedService` and `NodeInterpreterProbeHostedService`) each run `--version` once and log the resolved path so a misconfigured deploy fails loudly. **Pin worker images away from `:latest`** so Swarm picks up new layers reliably (the Python + Node installs live in the runtime stage of the Dockerfile).
|
||||
|
||||
> **WASM coupling note.** [`SpikerSoft.Wasm.csproj`](spikersoft-backend/SpikerSoft.Wasm/SpikerSoft.Wasm.csproj) link-includes `Domain/CodeExecution/Execution/**/*.cs` from `SpikerSoft.Business`, but excludes server-only files via a list (Python*, JavaScript*, executor/factory abstractions). When you add a new server-only executor, add its file pattern to that exclude list — otherwise the WASM build pulls in `IOptions` / `ILogger` / `Microsoft.Extensions.Services` that the WASM project deliberately doesn't reference, and you'll see a wall of "type or namespace not found" errors.
|
||||
> **WASM coupling note.** [`SpikerSoft.Wasm.csproj`](spikersoft-backend/SpikerSoft.Wasm/SpikerSoft.Wasm.csproj) link-includes `Domain/CodeExecution/Execution/**/*.cs` from `SpikerSoft.Business`, but excludes server-only files via a list (`Python*`, `JavaScript*`, `Regex*`, executor/factory abstractions, `RoslynLessonGradingExecutor`). When you add a new server-only executor, add its file pattern to that exclude list — otherwise the WASM build pulls in `IOptions` / `ILogger` / `Microsoft.Extensions.Services` that the WASM project deliberately doesn't reference, and you'll see a wall of "type or namespace not found" errors.
|
||||
|
||||
### Offline Lesson Re-Grading via Worker RPC
|
||||
|
||||
Browser graders (`SpikerSoft.Wasm` for C#, Pyodide for Python, QuickJS for JavaScript, the SPA's native `RegExp` for regex) are convenient and work entirely offline, but they run on the student's machine and are tamperable. Before the platform credits a skill unlock or marks a lesson complete for-real, the *server* has to re-execute the same lesson against the same grader and confirm it actually passed. That's the trust boundary the offline IndexedDB queue crosses every time it flushes through `POST /api/Lessons/progress/batch`.
|
||||
|
||||
**The architectural rule:** code execution NEVER runs in the API process. If the code-runner container crashes mid-grade, the API stays up; a fresh client retry round-trips through Rabbit on the next batch flush. This is enforced by the API's DI graph — only [`ILessonRegradeClient`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Services/ILessonRegradeClient.cs) is registered there; no `ILessonGradingExecutor` implementation is reachable from `SpikerSoft.Api`.
|
||||
|
||||
**The flow:** [`LessonsController.BatchProgress`](spikersoft-backend/SpikerSoft.Api/Domain/Lessons/LessonsController.cs) builds a [`LessonRegradeRequest`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Messaging/LessonRegradeRequest.cs) for each queued submission and hands it to [`LessonRegradeClient`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Services/LessonRegradeClient.cs), which publishes it to the `lesson.regrade.requests` exchange and blocks on the worker's reply via RabbitMQ **Direct-Reply-To** (`amq.rabbitmq.reply-to`). The worker's [`LessonRegradeWorkerHostedService`](spikersoft-backend/SpikerSoft.EventHandlers.CodeExecution/Services/LessonRegradeWorkerHostedService.cs) consumes the request, dispatches through the same [`ILessonGradingExecutorFactory`](spikersoft-backend/SpikerSoft.Business/Domain/CodeExecution/Execution/ILessonGradingExecutorFactory.cs) that backs the live "Submit" path (Roslyn / CPython / Node / Regex-via-Node — no code duplication between live-submit and regrade), and publishes a [`LessonRegradeResponse`](spikersoft-backend/SpikerSoft.Business/Domain/Lessons/Messaging/LessonRegradeResponse.cs) back to the API's reply queue.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant SPA as Angular SPA
|
||||
participant API as SpikerSoft.Api
|
||||
participant MQ as RabbitMQ
|
||||
participant Worker as SpikerSoft.EventHandlers.CodeExecution
|
||||
Note over SPA: Student grades offline (in-browser)<br/>Submission queued in IndexedDB
|
||||
SPA->>API: POST /api/Lessons/progress/batch
|
||||
Note over API: For each queued attempt
|
||||
API->>API: Build LessonRegradeRequest<br/>(lessonNumber, attemptToken,<br/>studentCode, testCode, runtime)
|
||||
API->>MQ: publish to lesson.regrade.requests<br/>reply-to = amq.rabbitmq.reply-to
|
||||
MQ->>Worker: deliver request
|
||||
Note over Worker: ILessonGradingExecutorFactory<br/>picks Roslyn / CPython / Node / Regex
|
||||
Worker->>Worker: Execute & grade
|
||||
Worker->>MQ: publish LessonRegradeResponse<br/>to API's Direct-Reply queue
|
||||
MQ->>API: deliver response
|
||||
alt Response received within timeout
|
||||
API->>API: Persist progress if AllTestsPassed
|
||||
API->>SPA: 200 OK with per-attempt status
|
||||
else Timeout (worker down / overloaded)
|
||||
API->>SPA: 5xx server_error<br/>(IndexedDB keeps the attempt; SPA retries)
|
||||
end
|
||||
```
|
||||
|
||||
**Idempotency.** Each request carries the SPA-generated `attemptToken`. If the worker grades a token, the API records it; if a network blip causes the SPA to retry, the controller short-circuits on the duplicate token rather than re-publishing — so the worker never regrades the same attempt twice.
|
||||
|
||||
**Timeout handling.** `LessonRegradeClient` enforces a per-call timeout (default 30 s). On expiry, the call throws `TimeoutException`, the controller maps it to HTTP `server_error`, and the SPA leaves the offline attempt in IndexedDB so the next batch flush picks it up. The same attempt token is re-used so the worker still de-dupes correctly when it eventually catches up.
|
||||
|
||||
**Same factory, same code.** Both worker consumer paths (live-submit `code.execution.requests` and offline-regrade `lesson.regrade.requests`) dispatch through `ILessonGradingExecutorFactory`. There is no separate "regrade" code path that could drift from the live grader — fixing a hint in `RoslynLessonGradingExecutor` fixes both modes simultaneously.
|
||||
|
||||
---
|
||||
|
||||
@@ -533,11 +593,12 @@ In CI the bundle is published by `spikersoft-backend/.gitea/workflows/spikersoft
|
||||
| **WASM project** | `SpikerSoft.Wasm/SpikerSoft.Wasm.csproj` | .NET 10 WebAssembly app; publishes to `assets/dotnet/` |
|
||||
| **JS interop** | `SpikerSoft.Wasm/WasmCompilerEntry.cs` | `[JSExport]` shim for the browser worker |
|
||||
| **Worker boot** | `spikersoft-angular/projects/spikersoft/src/assets/dotnet/main.js` | Pre-fetches managed assemblies, feeds PE bytes to Roslyn via `AddReferenceImage` |
|
||||
| **Web Worker** | `libraries/tools/src/services/csharp-runner/wasm-runtime.worker.ts` | Hosts the .NET runtime on a worker thread |
|
||||
| **Path-routing service** | `libraries/tools/src/services/csharp-runner/csharp-runner.service.ts` | Server↔WASM routing with auto-failover |
|
||||
| **Runner component** | `libraries/tools/src/components/csharp-runner/csharp-runner.ts` | Lesson UI, sidebar, refresh orchestration |
|
||||
| **Lesson sidebar** | `libraries/tools/src/components/csharp-runner/lesson-sidebar/` | Tier-grouped lesson list with prereq-driven lock state |
|
||||
| **Offline queue** | `libraries/tools/src/services/csharp-runner/progress-sync.service.ts` | IndexedDB enqueue + flush-on-reconnect |
|
||||
| **Web Worker** | `libraries/features/dev-tools-csharp-runner/src/lib/wasm-runtime.worker.ts` | Hosts the .NET runtime on a worker thread |
|
||||
| **Path-routing service** | `libraries/features/dev-tools-csharp-runner/src/lib/csharp-runner.service.ts` | Server↔WASM routing with auto-failover |
|
||||
| **Language-agnostic shell** | `libraries/platform/language-runner/src/lib/language-runner.ts` | Shared playground SHELL composed by all 3 wrapping shells (C#/Python/JS) via `LANGUAGE_RUNNER_CONFIG` + `LANGUAGE_RUNTIME_ADAPTER` |
|
||||
| **C# wrapping shell** | `libraries/features/dev-tools-csharp-runner/src/lib/csharp-runner.ts` | Wraps the language-runner with the C#-specific `LANGUAGE_RUNNER_CONFIG` and `csharp-runtime.adapter.ts` |
|
||||
| **Lesson sidebar** | `libraries/shared/lesson-panes/src/lib/lesson-sidebar/` | Tier-grouped lesson list with prereq-driven lock state (shared across all 3 playgrounds) |
|
||||
| **Offline queue** | `libraries/platform/progress-sync/src/lib/progress-sync.service.ts` | IndexedDB enqueue + flush-on-reconnect (consumed via `PROGRESS_SYNC_PORT` so the platform shell stays decoupled) |
|
||||
| **SW config** | `projects/spikersoft/ngsw-config.json` | `freshness` for `/api/Lessons/progress`, lazy for `/assets/dotnet/` |
|
||||
|
||||
### API Endpoints
|
||||
@@ -556,6 +617,83 @@ All endpoints require authentication. **`POST /api/CSharpCodeRunner/*`** and **`
|
||||
|
||||
---
|
||||
|
||||
## Frontend Architecture: Feature-Oriented Boundaries
|
||||
|
||||
The Angular workspace was migrated through a multi-phase refactor (Phases 4–6, completed 2026-05-09 — see [`docs/architecture/inventory.md`](docs/architecture/inventory.md) and [`docs/architecture/boundaries.md`](docs/architecture/boundaries.md)) from a monolithic `libraries/tools/` lib into ~65 buildable libraries grouped by **layer**. Each library has its own `ng-package.json`, `vitest.config.ts`, `project.json`, and tests run isolated. Lazy-loaded routes per feature → smaller initial bundle (the Phase 6 sub-phases collectively brought the gzipped `main.js` to ~302 kB).
|
||||
|
||||
### Layers and clusters
|
||||
|
||||
Every library carries at least one `layer:*` tag in its `project.json`. The boundary categories on disk are:
|
||||
|
||||
| Cluster | Layer tag | Count | Purpose |
|
||||
|---|---|---|---|
|
||||
| [`libraries/domain/`](spikersoft-angular/libraries/domain/) | `layer:domain` | 9 | Pure data models + thin services bound to backend resources, **no UI**: `blog`, `book`, `child-account`, `fundraiser`, `geo`, `mrz`, `pre-registration`, `profile`, `sponsor` |
|
||||
| [`libraries/features/`](spikersoft-angular/libraries/features/) | `layer:feature` | 24 | Top-level user-facing features that own routes/components/services: the entire `dev-tools-*` suite (21 tools — C#/Python/JavaScript/regex/x86 playgrounds, decompiler, encoding/conversion, image-to-avif/ico, qr-code, voronoi, diff, diagram, blockly, duckdb, ipv4, quick-type, common-commands), `blog`, `child-account-dialog`, `fundraiser`, `games-clue-for-sql`, `parent-dashboard`, `sponsor`, `trellis-3d-generator` |
|
||||
| [`libraries/platform/`](spikersoft-angular/libraries/platform/) | `layer:platform` | 13 | Cross-cutting infrastructure consumed by features: `language-runner`, `lesson-catalog`, `progress-sync`, `pyodide-runtime`, `monaco-editor`, `tool-storage`, `tool-file-menu`, `intro-tour`, `intro-launching`, `js-step-debugger`, `activity-tracking`, `loading`, `avif-encoder` |
|
||||
| [`libraries/shared/`](spikersoft-angular/libraries/shared/) | `layer:shared` | 6 | Small reusable units that don't belong to a single feature/domain: `api-config`, `js-formatting-options`, `lesson-panes`, `lesson-platform`, `save-load-dialogs`, `utils/{crc32,file-hash}` |
|
||||
| [`libraries/ui/`](spikersoft-angular/libraries/ui/) | `layer:ui` | 2 | Pure presentational atoms: `confirm-dialog`, `mrz-crop` |
|
||||
| [`libraries/game/`](spikersoft-angular/libraries/game/) | `layer:game` | 1 | Game-runtime libs that pair a feature with heavy runtime deps: `wasm-voxel` |
|
||||
| Top-level legacy | various | 5 | Pre-Phase-4 libs that haven't been re-clustered into the new directories yet but already carry the right layer tag: `keycloak-admin` (`layer:feature`), `marks-site-models` + `spikersoft-models` (`layer:domain`), `spikersoft-environment` (`layer:platform`), `spikersoft-theme` (`layer:ui`) |
|
||||
|
||||
### Allowed dependency arrows
|
||||
|
||||
Enforced by `@nx/enforce-module-boundaries` (severity `error` since Phase 5) on every PR. The full constraint set lives in [`spikersoft-angular/eslint.config.cjs`](spikersoft-angular/eslint.config.cjs); the abridged version a feature author cares about:
|
||||
|
||||
| Source layer | May depend on |
|
||||
|---|---|
|
||||
| `app` | `feature`, `ui`, `platform`, `domain`, `runtime`, `game`, `editor`, `shared`, `generated` |
|
||||
| `feature` | `feature`, `domain`, `ui`, `runtime`, `game`, `editor`, `platform`, `shared`, `generated` |
|
||||
| `ui` | `ui`, `platform`, `shared` |
|
||||
| `domain` | `domain`, `shared`, `generated` |
|
||||
| `platform` | `platform`, `shared` |
|
||||
| `game` | `game`, `runtime`, `shared` |
|
||||
| `shared` | `shared` |
|
||||
|
||||
The shape of the rule, in one diagram:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
app[app: projects/spikersoft]
|
||||
feature[feature: libraries/features, libraries/keycloak-admin]
|
||||
domain[domain: libraries/domain, libraries/marks-site-models, libraries/spikersoft-models]
|
||||
platform[platform: libraries/platform, libraries/spikersoft-environment]
|
||||
ui[ui: libraries/ui, libraries/spikersoft-theme]
|
||||
game[game: libraries/game]
|
||||
shared[shared: libraries/shared]
|
||||
|
||||
app --> feature
|
||||
app --> domain
|
||||
app --> platform
|
||||
app --> ui
|
||||
app --> game
|
||||
app --> shared
|
||||
|
||||
feature --> domain
|
||||
feature --> platform
|
||||
feature --> ui
|
||||
feature --> game
|
||||
feature --> shared
|
||||
|
||||
ui --> platform
|
||||
ui --> shared
|
||||
domain --> shared
|
||||
platform --> shared
|
||||
game --> shared
|
||||
```
|
||||
|
||||
The arrows go **only downward**. A `domain` lib that tries to import from `feature` or `platform` fails CI; `platform` cannot import `feature`; `shared` cannot import anything but `shared`. This is what the boundary refactor bought: the dep graph is now mechanically prevented from collapsing back into the old "everything imports `tools`" shape.
|
||||
|
||||
### Cross-cluster ports
|
||||
|
||||
Where a feature genuinely needs to consume a service from another cluster without violating the arrows, the lib pair uses a **port** (an injection token) declared in `shared/lesson-platform` and provided at the wrapping shell. Examples:
|
||||
|
||||
- [`PROGRESS_SYNC_PORT`](spikersoft-angular/libraries/shared/lesson-platform/src/lib/progress-sync-port.ts) — consumed by `platform/language-runner` so the shared playground shell never hard-imports the Monaco-coupled `ProgressSyncService` concrete (the `feature/dev-tools-csharp-runner` shell wires it up at provider time).
|
||||
- `AVIF_ENCODER` port — `domain/blog` and `feature/dev-tools-image-to-avif` consume the encoder through a port declared in `shared`, so the domain lib never depends directly on `platform/avif-encoder`.
|
||||
|
||||
If a new feature seems to require an arrow the layering forbids, the right answer is almost always to extract a port or a contract type into `shared/lesson-platform` (or its sibling `shared/api-config`) rather than to widen the rule. See [`docs/architecture/boundaries.md`](docs/architecture/boundaries.md) for the canonical write-up of the rule, baseline counts, and the burn-down protocol that closed the last ~22 violations.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Frontend (spikersoft-angular/)
|
||||
@@ -587,20 +725,40 @@ spikersoft-angular/
|
||||
│ ├── location/ # LocationService (API-driven country list)
|
||||
│ ├── position/ # PositionService (open positions + auto ambassadors)
|
||||
│ └── team/ # TeamService (staff member profiles)
|
||||
├── libraries/
|
||||
│ ├── keycloak-admin/ # Keycloak administration UI
|
||||
│ ├── marks-site-models/ # Shared models for field-service customer management
|
||||
│ ├── spikersoft-environment/ # Environment configuration library
|
||||
│ ├── spikersoft-models/ # Shared TypeScript models
|
||||
│ ├── spikersoft-theme/ # Glassmorphic theme and styling utilities
|
||||
│ ├── tools/ # Developer & visual programming utilities
|
||||
│ │ ├── blockly/ # Blockly editor + robot blocks
|
||||
│ │ ├── diagram/ # Rete.js node editor + robot nodes
|
||||
│ │ ├── ram-calculator.ts # Program RAM cost calculation
|
||||
│ │ └── src/
|
||||
│ │ ├── components/csharp-runner/ # C# Playground UI (lessons, sidebar, free-play)
|
||||
│ │ └── services/csharp-runner/ # Server↔WASM router, Web Worker, offline progress queue
|
||||
│ └── wasm-voxel/ # TypeScript Minecraft port (voxel engine)
|
||||
├── libraries/ # Buildable Nx libs grouped by layer (see Frontend Architecture section)
|
||||
│ ├── domain/ # layer:domain — pure data + thin services bound to backend resources (9 libs)
|
||||
│ │ ├── blog/ book/ child-account/ fundraiser/ geo/
|
||||
│ │ └── mrz/ pre-registration/ profile/ sponsor/
|
||||
│ ├── features/ # layer:feature — top-level user features w/ routes (24 libs)
|
||||
│ │ ├── dev-tools-csharp-runner/ # C# Playground wrapping shell (config + adapter)
|
||||
│ │ ├── dev-tools-python-runner/ # Python Playground wrapping shell
|
||||
│ │ ├── dev-tools-javascript-runner/# JS Playground wrapping shell
|
||||
│ │ ├── dev-tools-reg-ex/ # Regex Playground (chapters + cheatsheet)
|
||||
│ │ ├── dev-tools-x86-playground/ ...# 21 dev-tools-* + blog, sponsor, fundraiser, ...
|
||||
│ │ └── … # see libraries/features/ for full list
|
||||
│ ├── platform/ # layer:platform — cross-cutting infra (13 libs)
|
||||
│ │ ├── language-runner/ # Language-agnostic playground SHELL (consumed by 3 wrapping shells)
|
||||
│ │ ├── lesson-catalog/ # Lesson catalog + prerequisites
|
||||
│ │ ├── progress-sync/ # Server↔in-browser router + offline IndexedDB queue
|
||||
│ │ ├── pyodide-runtime/ # Pyodide loader (Python in-browser)
|
||||
│ │ ├── monaco-editor/ tool-storage/ tool-file-menu/ intro-tour/ intro-launching/
|
||||
│ │ └── js-step-debugger/ activity-tracking/ loading/ avif-encoder/
|
||||
│ ├── shared/ # layer:shared — small reusable units (6 libs)
|
||||
│ │ ├── lesson-platform/ # Cross-cluster contracts (PROGRESS_SYNC_PORT, SourceFile, ...)
|
||||
│ │ ├── lesson-panes/ # Lesson + tutorial + sidebar panes
|
||||
│ │ ├── api-config/ js-formatting-options/ save-load-dialogs/
|
||||
│ │ └── utils/ # crc32, file-hash
|
||||
│ ├── ui/ # layer:ui — presentational atoms (2 libs)
|
||||
│ │ └── confirm-dialog/ mrz-crop/
|
||||
│ ├── game/ # layer:game — game runtime libs (1 lib)
|
||||
│ │ └── wasm-voxel/ # TypeScript Minecraft port (voxel engine)
|
||||
│ │
|
||||
│ │ # Legacy top-level libs (pre-Phase-4 boundary refactor; tagged but not re-clustered)
|
||||
│ ├── keycloak-admin/ # layer:feature — Keycloak administration UI
|
||||
│ ├── marks-site-models/ # layer:domain — Shared models for field-service customer management
|
||||
│ ├── spikersoft-environment/ # layer:platform — Environment configuration
|
||||
│ ├── spikersoft-models/ # layer:domain — Shared TypeScript models
|
||||
│ └── spikersoft-theme/ # layer:ui — Glassmorphic theme + styling utilities
|
||||
├── nx.json # Nx workspace config
|
||||
└── package.json
|
||||
```
|
||||
@@ -1012,7 +1170,7 @@ docker build -t spikersoft-angular -f spikersoft-angular/Dockerfile .
|
||||
| Game Server | `spikersoft-backend/SpikerSoft.GameServer/README.md` |
|
||||
| Integrated Game Ecosystem | See main README — [Integrated Game Ecosystem](#integrated-game-ecosystem) |
|
||||
| Robot & Visual Programming | See main README — [Robot & Visual Programming](#robot--visual-programming) and `ARCHITECTURE-ROBOT-VISUAL-PROGRAMMING.md` |
|
||||
| C# Coding Curriculum & Playground | See main README — [C# Coding Curriculum & Playground](#c-coding-curriculum--playground); WASM bundle: `spikersoft-angular/projects/spikersoft/src/assets/dotnet/README.md`; Tools library: `spikersoft-angular/libraries/tools/README.md` |
|
||||
| C# Coding Curriculum & Playground | See main README — [C# Coding Curriculum & Playground](#c-coding-curriculum--playground); WASM bundle: `spikersoft-angular/projects/spikersoft/src/assets/dotnet/README.md`; Wrapping shell: `spikersoft-angular/libraries/features/dev-tools-csharp-runner/README.md`; Shared playground SHELL: `spikersoft-angular/libraries/platform/language-runner/README.md` |
|
||||
| Event Handlers | See main README — [Event Handlers](#event-handlers); shared infra: `spikersoft-backend/SpikerSoft.EventHandlers.Infrastructure/README.md`; code worker: `spikersoft-backend/SpikerSoft.EventHandlers.CodeExecution/README.md` |
|
||||
| Chess Game | See main README — [Chess Game](#chess-game) section |
|
||||
| Dungeon Crawler | `spikersoft-angular/projects/spikersoft/src/app/_components/_games/dungeon-crawler/README.md` |
|
||||
|
||||
Reference in New Issue
Block a user