Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4d2cd3d
cursor rules
myonara Feb 14, 2026
0290999
activate gmcp option in backend
myonara Feb 14, 2026
a42ce6e
shared data types gmcp
myonara Feb 14, 2026
c0ad99a
gmcp in frontend
myonara Feb 14, 2026
4a0da9a
optional multi-mud config
myonara Feb 14, 2026
835dd2d
reinvented window system
myonara Feb 14, 2026
0224506
DirList and ACE-Editor
myonara Feb 14, 2026
cefdb33
gmcp sound
myonara Feb 14, 2026
bd42f59
gmcp browserinfo(real-ip)
myonara Feb 14, 2026
4aa393b
char stats as status bar(deviation from migplan)
myonara Feb 14, 2026
2095efb
gmcp inventory
myonara Feb 14, 2026
c9bdd50
error handling backend and some cors
myonara Feb 14, 2026
b045060
build error fixed
myonara Feb 14, 2026
e01974a
error and close handling revisted
myonara Feb 14, 2026
be4f4cf
happy eyeball and localhost dns
myonara Feb 14, 2026
1a3639b
some bugs, hello, support.set, ping, goodbye
myonara Feb 16, 2026
50be7ec
menubar and color settings
myonara Feb 16, 2026
e0635bf
input completion
myonara Feb 16, 2026
50ebf4f
numpad keypad
myonara Feb 16, 2026
cb7b91e
gmcp comm
myonara Feb 16, 2026
18a5bfa
gmcp-room
myonara Feb 16, 2026
8c3ef90
editor searchbox keybindings against xterm
myonara Feb 16, 2026
c9a8285
multi-Mud-Selector
myonara Feb 16, 2026
fb66676
env and dockerfiles for uni and sb
myonara Feb 16, 2026
5f85380
migration plans and analysis
myonara Feb 16, 2026
f0ba3f5
prefinal analysis
myonara Feb 16, 2026
5e15fe5
reload test dockerfiles
Feb 17, 2026
a61bb40
Merge branch 'develop' into migrate/pre-final
Feb 17, 2026
8a0c2b6
some visual bug fixes
Feb 17, 2026
89f3f96
clipboard copy and lo clipboard logging
Feb 17, 2026
2b854bf
fixed clipboard copy
Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .cursor/rules/backend.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
globs: backend/**/*.ts
---

# Backend-Regeln

## Module System
- Backend verwendet **ES Modules** (`"type": "module"` in package.json)
- Alle Imports müssen `.js`-Extension haben: `import { foo } from './bar.js'`
- Jest-Tests laufen im CJS-Modus via `tsconfig.jest.json` und `moduleNameMapper`

## Paket-Scope
- Paketname: `@webmud3/backend`
- Shared-Typen importieren via: `import { ... } from '@webmud3/shared'`

## Architektur
- **Environment**: Singleton via `Environment.getInstance()` – nie direkt `process.env` lesen
- **MudConfigService**: Singleton via `MudConfigService.getInstance(configPath)` – Multi-MUD-Konfiguration
- Lädt `mud_config.json` wenn `MUD_CONFIG_PATH` Env-Var gesetzt und Datei existiert
- Fallback: Kein Config → `TELNET_HOST`/`TELNET_PORT` Env-Vars werden direkt genutzt
- Lookup: `getMudById()`, `resolveConnection()`, `getGmcpSupport()`, `getMudList()`
- **Middleware**: Jede Middleware als eigene `use*`-Funktion in `core/middleware/`
- **Telnet-Handler**: Neue Telnet-Options als Factory-Funktion in `features/telnet/utils/handle-*.ts` anlegen
- **Logging**: Immer `logger` aus `shared/utils/logger.ts` verwenden (Winston), nie `console.log`

## Telnet-Option hinzufügen
1. Option in `features/telnet/models/telnet-options.ts` definieren
2. Handler-Funktion in `features/telnet/utils/handle-<option>.ts` erstellen
3. Handler in `TelnetClient.optionsHandler`-Map registrieren
4. Optional: State-Tracking via `getState()` / `onStateChange()`

## Socket.IO Events
- Neue Client→Server Events: Interface `ClientToServerEvents` in `shared/` erweitern
- Neue Server→Client Events: Interface `ServerToClientEvents` in `shared/` erweitern
- Handler im `SocketManager.handleClientConnection()` registrieren
- GMCP-Events: `mudGmcpIncoming`/`mudGmcpStart` (Server→Client), `mudGmcpOutgoing` (Client→Server)
- GMCP-Weiterleitung: TelnetClient Events ↔ Socket.IO Events im SocketManager
- **Core.BrowserInfo Enrichment**: Bei `mudGmcpOutgoing` mit `Core.BrowserInfo` wird serverseitig `real_ip` injiziert
- `SocketManager.extractRealIp()` → `x-forwarded-for` (erster Eintrag) oder `socket.handshake.address`
- Auch `client` (clientName) wird hinzugefügt — Frontend-Daten werden gemergt

## TypeScript
- `strict: true`
- Target: ES2020
- Module: nodenext / moduleResolution: nodenext

## Testing
- Jest mit `ts-jest`
- Test-Dateien: `*.spec.ts` neben den Source-Dateien
- Config: `jest.config.cjs`
39 changes: 39 additions & 0 deletions .cursor/rules/coding-conventions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
alwaysApply: true
---

# Coding Conventions

## Sprache
- Code und Code-Kommentare: **Englisch**
- Dokumentation und Benutzer-Strings (z.B. ARIA-Labels, Screenreader-Texte): **Deutsch** (für UNItopia-Zielgruppe)
- Git-Commits: Englisch

## TypeScript
- `strict: true` in allen tsconfig.json
- Keine `any` – wenn unvermeidbar mit `eslint-disable`-Kommentar begründen
- Interfaces bevorzugt über `type` für öffentliche Schnittstellen
- Single Quotes, 2 Spaces Einrückung (EditorConfig)

## Imports
- Backend: `.js`-Extension in allen Imports (ES Modules)
- Frontend: Keine Extension (Bundler-Resolution)
- Sortierung: `eslint-plugin-simple-import-sort` erzwingt alphabetische Sortierung

## Logging
- Backend: Winston-Logger (`logger.info/warn/error/debug`) – nie `console.log`
- Frontend: `console.log/info/warn/error` mit `[Kontext]`-Prefix, z.B. `[Sockets]`, `[MudClient]`

## Error Handling
- Backend: Fehler loggen und graceful behandeln
- Frontend: Errors in `catch`-Blöcken loggen, User-facing Errors über UI melden

## Formatting
- Prettier ~3.6.2
- EditorConfig: UTF-8, 2 Spaces, final newline, trim trailing whitespace
- Markdown: kein max_line_length, kein trim trailing whitespace

## Testing
- Tests neben den Source-Dateien: `foo.spec.ts` neben `foo.ts`
- Jest als Test-Runner (nicht Karma, nicht Jasmine)
- Mindestens Unit-Tests für Business-Logik und Telnet-Handler
37 changes: 37 additions & 0 deletions .cursor/rules/docker-deploy.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
description: Docker und Deployment-Regeln
globs: Dockerfile,dockerfiles/**,*.yml,.github/**/*.yml
---

# Docker & Deployment

## Docker Build
- Multi-Stage Build: `builder` (kompiliert) → `runtime` (nur Node.js + dist)
- Base Image: `node:22.20.0-alpine`
- Shared-Paket wird nach `backend/dist/node_modules/@webmud3/shared` kopiert (kein npm Registry)
- Prod-Dependencies werden separat in `backend/dist/` installiert (`npm install --omit=dev`)

## Wichtige Docker-Umgebungsvariablen
```env
HOST=0.0.0.0
PORT=5000
TELNET_HOST=127.0.0.1
TELNET_PORT=23
TELNET_TLS=false
SOCKET_ROOT=/socket.io
SOCKET_TIMEOUT=900000
```

## CI/CD
- **Build & Test**: GitHub Action auf PRs gegen `develop`
- **Deploy to Azure**: GitHub Action auf Push zu `master`
- Azure Deployment kopiert `shared/dist` manuell nach `backend/dist/node_modules/@webmud3`

## Build-Reihenfolge
```
npm ci → npm run build:prod
1. shared: tsc
2. frontend: ng build --configuration=production
3. backend: tsc
4. postbuild: cpy frontend/dist → backend/dist/wwwroot/
```
169 changes: 169 additions & 0 deletions .cursor/rules/frontend.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
globs: frontend/**/*.ts,frontend/**/*.html,frontend/**/*.scss
---

# Frontend-Regeln

## Framework
- Angular ~20.3.4 mit **Standalone Components** (keine NgModules)
- Alle Components müssen `standalone: true` haben
- Dependency Injection über `inject()` statt Constructor-Injection bevorzugt

## Paket-Scope
- Paketname: `@webmud3/frontend`
- Pfad-Alias: `@webmud3/frontend/*` → `src/app/*`
- Shared-Typen: `import { ... } from '@webmud3/shared'`

## Terminal-Subsystem
- **MudInputController**: Verwaltet den lokalen Editing-Buffer (LINEMODE). Nicht direkt auf xterm schreiben!
- **MudPromptManager**: State-Machine für Prompt-Visibility bei Server-Output
- **MudSocketAdapter**: Adapter – Observable → WebSocket-Interface für xterm AttachAddon
- **MudScreenReaderAnnouncer**: Custom ARIA-Announcer (kein xterm screenReaderMode!)
- Alle ANSI/Terminal-Escape-Konstanten in `features/terminal/models/escapes.ts`

## Barrierefreiheit (A11y)
- Screenreader-Support ist ein Kernfeature – ARIA-Regionen nicht entfernen!
- Drei Announcement-Ebenen: Zeichen, Wort (bei Space), vollständige Zeile (bei Enter)
- `aria-live="assertive"` für Output, `role="log"` für History
- Bei Änderungen am Terminal: Screenreader-Tests durchführen (siehe SCREENREADER_TESTS.md)

## GMCP-Subsystem
- **GmcpService** (`features/gmcp/gmcp.service.ts`): Zentraler Service für GMCP-Modul-Verwaltung
- Registry: Module registrieren/deregistrieren über `registerModule()`/`unregisterModule()`
- Routing: Eingehende Nachrichten werden an den passenden `GmcpModuleHandler` delegiert
- Observable: `gmcpEvent$` streamt alle GMCP-Events, `gmcpStart$` signalisiert GMCP-Aktivierung
- Senden: `sendOutgoing()` leitet über die von SocketsService gesetzte Sendefunktion
- **GmcpModuleHandler** (`features/gmcp/gmcp-module-handler.ts`): Strategy-Interface für Module
- Jedes GMCP-Modul (Sound, Char, Files, etc.) implementiert dieses Interface
- `handleMessage(message, data)` verarbeitet eingehende Nachrichten
- Optional: `getMenuItems()` für dynamische Menüeinträge, `dispose()` für Cleanup
- **GmcpEvent** (`features/gmcp/gmcp-event.ts`): Event-Typ für den Observable-Stream
- Verdrahtung: SocketsService → GmcpService.handleIncoming() / handleGmcpStart()
- Reset: GmcpService.reset() wird bei mudDisconnect aufgerufen

## Fenstersystem (Modeless Windows)
- **WindowConfig** (`features/windows/window-config.ts`): Interface für Fenster-Konfiguration
- UUID-basierte `windowId`, Parent-Child über `parentWindowId`
- `componentType` String bestimmt, welcher Content im Fenster gerendert wird
- Position, Größe, Z-Index, Sichtbarkeit, Titel
- **WindowAction** Enum: Focus, Hide, Save, Cancel, SaveAndClose, WinError, Resize, CloseParent
- **WindowEvent** Interface: `{ action, windowId, data? }` für Event-Kommunikation
- **WindowService** (`features/windows/window.service.ts`): Zentraler Injectable Service
- `open()` erstellt neues Fenster mit UUID und auto-Z-Index
- `close()` schließt Fenster + rekursiv alle Children
- `focus()` bringt Fenster nach vorne (höchster Z-Index)
- `windows$` BehaviorSubject liefert reaktive Liste aller sichtbaren Fenster
- `incomingEvents$` / `outgoingEvents$` Subject-basierter Event-Bus
- `updatePosition()` / `updateSize()` / `updateData()` für State-Updates
- **WindowComponent** (`features/windows/window.component.ts`): Standalone Component
- Draggable Titelleiste via native Pointer Events
- Resize-Handle (unten-rechts), Min-Constraints 200×120px
- ARIA: `role="dialog"`, `aria-label`, Focus-Buttons
- Emittet WindowEvents über Output an den Container
- **WindowContainerComponent** (`features/windows/window-container.component.ts`):
- Fixed-Position Overlay, `pointer-events: none` (Fenster haben `auto`)
- Rendert `@for`-Loop über `windows$ | async`
- Routet Events an WindowService
- Design-Prinzip: Catppuccin Mocha Farbschema (dark theme, passt zum Terminal)
- Content-Routing: `WindowContainerComponent` nutzt `@switch (config.componentType)` um Content-Komponenten einzubetten
- Neue Fenster-Typen: Component importieren + `@case` hinzufügen

## GMCP Files-Modul (Datei-Browser & Code-Editor)
- **FilesGmcpHandler** (`features/gmcp-files/files-gmcp-handler.ts`): GmcpModuleHandler für `Files`
- `Files.URL` → Datei laden + Editor-Fenster öffnen
- `Files.DirectoryList` → DirList-Fenster öffnen/aktualisieren
- `openFile()` / `changeDirectory()` → GMCP-Requests an MUD
- `saveFile()` → Save-Workflow: GMCP Files.OpenFile (flag=1) → HTTP PUT → GMCP Files.fileSaved
- **FilesService** (`features/gmcp-files/files.service.ts`): HTTP Load/Save + FileInfo-Registry
- `processFileUrl()` erstellt/aktualisiert FileInfo aus GMCP-Payload
- `loadFileContent()` / `saveFileContent()` → async HTTP-Operationen
- `isDirty()` für ungespeicherte Änderungen, `reset()` bei Disconnect
- **DirListComponent** (`features/gmcp-files/dirlist.component.ts`): Verzeichnis-Browser
- Standalone, zeigt Dateien/Ordner als Tabelle an
- Klick auf Datei → `FilesGmcpHandler.openFile()`
- Klick auf Ordner → `FilesGmcpHandler.changeDirectory()`
- **EditorComponent** (`features/gmcp-files/editor.component.ts`): Code-Editor
- Ace Editor (`ace-builds`), lazy-loaded via `import()`
- Syntax-Highlighting: C/C++ (`.c`, `.h`, `.inc`), Text (alles andere)
- Theme-Auswahl mit localStorage-Persistenz
- Toolbar: Theme-Select, Read-Only-Toggle, Zwischenspeichern, Speichern&Schließen, Abbrechen
- Dirty-Check bei Schließen (window.confirm)
- **FileInfo** / **FileEntry** (`features/gmcp-files/file-types.ts`): Typen für Dateisystem-Daten
- Parent-Child: DirList-Fenster ist Parent aller geöffneten Editor-Fenster

## GMCP Sound-Modul
- **SoundGmcpHandler** (`features/gmcp-sound/sound-gmcp-handler.ts`): GmcpModuleHandler für `Sound`
- `Sound.Url` → speichert Base-URL für Sound-Dateien
- `Sound.Event` → spielt Sound via `new Audio(baseUrl + "/" + file)`
- HTML5 Audio API (fire-and-forget, autoplay-Policy behandelt)
- Toggle: `Core.Supports.Add ["Sound 1"]` / `Core.Supports.Remove ["Sound 1"]`
- `getMenuItems()` liefert "Vertonung" Toggle-Eintrag für das Menü
- Backend: GMCP Sound-Messages werden transparent durchgereicht (kein Backend-Code nötig)

## GMCP Char-Modul (Statusleiste + Inventar)
- **CharacterData** (`features/gmcp-char/character-data.ts`): Typen + Parsing
- `CharacterData` Interface: Name, Fullname, Gender, Wizard-Flag, Vitals, Stats, Status
- `CharacterVitals`: HP/SP + MaxHP/MaxSP
- `CharacterStat`: key/label/value für STR/INT/CON/DEX
- `parseVitals("hp=100|sp=80|maxhp=120|maxsp=100")` → strukturierte Vitals
- `parseStats("con=34,2|dex=59,7|int=130|str=59,8")` → sortiertes Stats-Array
- `formatStatusSummary()` → kompakte Status-Anzeige
- **InventoryData** (`features/gmcp-char/inventory-data.ts`): Inventar-Datenmodell
- `InventoryEntry` Interface: `{ name, category }` — einzelner Gegenstand
- `InventoryList` Klasse: Kategorisierte Verwaltung mit `addItem()`, `removeItem()`, `initList()`
- Kategorien werden automatisch erstellt/gelöscht
- `getCategories()`, `getItems(category)`, `totalItems`, `isEmpty`
- **CharGmcpHandler** (`features/gmcp-char/char-gmcp-handler.ts`): GmcpModuleHandler für `Char`
- `Char.Name` → Charakter-Init, Browser-Titel setzen, Wizard-Module aktivieren
- `Char.StatusVars` → Status-Variablen-Definitionen speichern
- `Char.Status` → Aktuelle Statuswerte (Gilde, Rasse, Rang)
- `Char.Vitals` → HP/SP Werte (string + object Format)
- `Char.Stats` → STR/INT/CON/DEX Attribute
- `Char.Items.List` → Komplettes Inventar ersetzen + Fenster öffnen
- `Char.Items.Add` → Einzelnen Gegenstand hinzufügen
- `Char.Items.Remove` → Einzelnen Gegenstand entfernen
- `characterData$` BehaviorSubject (null = nicht eingeloggt)
- `inventory$` BehaviorSubject für reaktive Inventar-Updates
- Wizard-Erkennung: bei `wizard > 0` werden Files/Input/Numpad GMCP-Module aktiviert
- Inventar-Fenster wird automatisch bei `Items.List` geöffnet, bei `dispose()` geschlossen
- **CharStatusBarComponent** (`features/gmcp-char/char-statusbar.component.ts`): Kompakte Statusleiste
- Standalone, direkt in `app.component.html` zwischen Terminal und Window-Container
- Nur sichtbar nach `Char.Name` Empfang (characterData$ nicht null + name gesetzt)
- Layout: `[Name] HP: x/y | SP: x/y | STR: x INT: y CON: z DEX: w | Status`
- Catppuccin Mocha Farben, monospace, responsive (Stats auf schmalen Viewports ausgeblendet)
- ARIA: `role="status"`, `aria-live="polite"`
- `flex: 0 0 auto` — verdrängt Terminal von unten, überlappt nicht
- **InventoryComponent** (`features/gmcp-char/inventory.component.ts`): Inventar-Fenster
- Standalone, wird als Modeless-Window im WindowContainer gerendert (`componentType: 'InventoryComponent'`)
- Kategorisierte Liste mit Kategorie-Überschriften und Items
- Abonniert `CharGmcpHandler.inventory$` für reaktive Updates
- Catppuccin Mocha Farben, Gegenstandszähler im Footer
- ARIA: `role="list"`, `aria-label="Inventar"`

## GMCP Handler-Registrierung
- **GmcpBootstrapService** (`features/gmcp/gmcp-bootstrap.service.ts`): Zentrale Registrierung
- Registriert alle GMCP-Handler (Char, Sound, Files) beim GmcpService
- Wird als `provideAppInitializer()` in `main.ts` aufgerufen
- Idempotent (mehrfacher Aufruf sicher)

## Styling
- SCSS als Preprocessor
- Komponenten-spezifische Styles in `.component.scss`
- Globale Styles in `src/styles.scss`

## Environment
- Dev: `environment.ts` → backendUrl = `http://localhost:5000`
- Prod: `environment.prod.ts` → backendUrl = `window.location.href`
- Compile-Time File-Replacement über `angular.json` `fileReplacements`

## Testing
- Jest über @angular-builders/jest (nicht Karma!)
- Test-Dateien: `*.spec.ts` neben den Source-Dateien
- Setup: `setup-jest.ts` (nutzt `setupZoneTestEnv()` aus `jest-preset-angular`)
- Angular-DI in Tests: `TestBed.configureTestingModule()` + `provideHttpClient()` bei Services mit HTTP

## Conventions
- TypeScript strict mode
- Single quotes für Strings (EditorConfig)
- 2 Spaces Einrückung
- SCSS für Styles
41 changes: 41 additions & 0 deletions .cursor/rules/project-overview.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
alwaysApply: true
---

# WebMud3 – Projektübersicht

WebMud3 ist ein moderner, webbasierter MUD-Client (Multi-User Dungeon) als Open-Source-Projekt für UNItopia.

## Architektur

- **Monorepo** mit npm Workspaces: `frontend/`, `backend/`, `shared/`
- Backend: Express 5 + Socket.IO → Telnet-Proxy zum MUD-Server
- Frontend: Angular 20 + xterm.js → Terminal-UI im Browser
- Shared: Typisierte Socket.IO Events und Konfigurationstypen (`@webmud3/shared`)

## Datenfluss

```
Frontend (Angular/xterm) ←→ Socket.IO (WebSocket) ←→ Backend (Express/Node) ←→ Telnet (TCP) ←→ MUD-Server
```

## Wichtige Verzeichnisse

- [package.json](mdc:package.json) – Root-Workspace-Konfiguration
- `backend/src/main.ts` – Backend Entry Point
- `backend/src/core/` – Middleware, Environment, Sockets, Routes
- `backend/src/features/telnet/` – Telnet-Client mit Option-Negotiation
- `frontend/src/app/core/mud/` – MudClientComponent + MudService
- `frontend/src/app/features/terminal/` – InputController, PromptManager, ScreenReader
- `frontend/src/app/features/sockets/` – Socket.IO Client-Wrapper
- `shared/src/` – Gemeinsame Typen und Interfaces

## Tech Stack

- Node.js ~22.20.0, TypeScript ~5.9.3
- Angular ~20.3.4 (Standalone Components)
- Express ~5.1.0, Socket.IO ~4.8.1
- xterm.js ~5.5.0
- Jest ~30.2.0 für Tests
- Docker Multi-Stage Build
- GitHub Actions CI/CD → Azure Web App
31 changes: 31 additions & 0 deletions .cursor/rules/shared.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
globs: shared/**/*.ts
---

# Shared-Paket Regeln

## Zweck
Das `@webmud3/shared`-Paket definiert den **Vertrag** zwischen Frontend und Backend. Beide importieren die gleichen Typen, was Typsicherheit über Systemgrenzen gewährleistet.

## Module System
- ES Modules (`"type": "module"`)
- Exports via `exports`-Feld in package.json und Barrel `src/index.ts`

## Was gehört hier rein?
- Socket.IO Event-Interfaces (`ClientToServerEvents`, `ServerToClientEvents`)
- Telnet-bezogene Shared-Types (`LinemodeState`)
- Backend-Konfigurationstypen (`ServerConfig`)
- GMCP-Typen (`GmcpSupport`, `GmcpModuleConfig`, `MudFamilyConfig`, `MudConfig`, `MudConfigFile`) in `gmcp/`
- **Keine** Implementierungen, nur Typen und Interfaces!

## Neuen Typ hinzufügen
1. Typ-Datei in passendem Unterordner anlegen (`sockets/`, `config/`, `gmcp/`)
2. In `src/index.ts` re-exportieren
3. `npm run build --workspace shared` ausführen, da Frontend und Backend die kompilierte Version verwenden

## Build-Reihenfolge
Shared muss **immer zuerst** gebaut werden (passiert automatisch via `prebuild` in Backend/Frontend):

```
shared → frontend + backend (parallel möglich)
```
Loading
Loading