Update version references from Angular 20 to Angular 21 and Taiga UI to Taiga UI 5 across architecture docs. Update web/CLAUDE.md with improved Taiga golden rules: prioritize MCP server for docs, remove hardcoded component examples in favor of live doc lookups. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.8 KiB
Web Architecture
Angular 21 + TypeScript workspace using Taiga UI 5 component library.
API Layer (JSON-RPC)
All backend communication uses JSON-RPC, not REST.
HttpService(shared/src/services/http.service.ts) — Low-level HTTP wrapper. Sends JSON-RPC POST requests viarpcRequest().ApiService(ui/src/app/services/api/embassy-api.service.ts) — Abstract class defining 100+ RPC methods. Two implementations:LiveApiService— Production, calls the real backendMockApiService— Development with mocks
api.types.ts(ui/src/app/services/api/api.types.ts) — NamespaceRRwith all request/response type pairs.
Calling an RPC endpoint from a component:
private readonly api = inject(ApiService)
async doSomething() {
await this.api.someMethod({ param: value })
}
The live API handles x-patch-sequence headers — after a mutating call, it waits for the PatchDB WebSocket to catch up before resolving. This ensures the UI always reflects the result of the call.
PatchDB (Reactive State)
The backend pushes state diffs to the frontend via WebSocket. This is the primary way components get data.
PatchDbSource(ui/src/app/services/patch-db/patch-db-source.ts) — Establishes a WebSocket subscription when authenticated. Buffers updates every 250ms.DataModel(ui/src/app/services/patch-db/data-model.ts) — TypeScript type for the full database shape (ui,serverInfo,packageData).PatchDB<DataModel>— Injected service. Usewatch$()to observe specific paths.
Watching data in a component:
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
// Watch a specific path — returns Observable, convert to Signal with toSignal()
readonly name = toSignal(this.patch.watch$('ui', 'name'))
readonly status = toSignal(this.patch.watch$('serverInfo', 'statusInfo'))
readonly packages = toSignal(this.patch.watch$('packageData'))
In templates: {{ name() }} — signals are called as functions.
WebSockets
Three WebSocket use cases, all opened via api.openWebsocket$<T>(guid):
- PatchDB — Continuous state patches (managed by
PatchDbSource) - Logs — Streamed via
followServerLogs/followPackageLogs, buffered every 1s - Metrics — Real-time server metrics via
followServerMetrics
Navigation & Routing
- Main app (
ui/src/app/routing.module.ts) — NgModule-based with guards (AuthGuard,UnauthGuard,stateNot()), lazy loading vialoadChildren,PreloadAllModules. - Portal routes (
ui/src/app/routes/portal/portal.routes.ts) — Modern array-based routes withloadChildrenandloadComponent. - Setup wizard (
setup-wizard/src/app/app.routes.ts) — StandaloneloadComponent()per step. - Route config uses
bindToComponentInputs: true— route params bind directly to component@Input().
Forms
Two patterns:
-
Dynamic (spec-driven) —
FormService(ui/src/app/services/form.service.ts) generatesFormGroupfrom IST (Input Specification Type) schemas. Supports text, textarea, number, color, datetime, object, list, union, toggle, select, multiselect, file. Used for service configuration forms. -
Manual — Standard Angular
FormGroup/FormControlwith validators. Used for login, setup wizard, system settings.
Form controls live in ui/src/app/routes/portal/components/form/controls/ — each extends a base Control<Spec, Value> class and uses Taiga input components.
Dialog-based forms use PolymorpheusComponent + TuiDialogContext for modal rendering.
i18n
i18nPipe(shared/src/i18n/i18n.pipe.ts) — Translates English keys to the active language.- Dictionaries live in
shared/src/i18n/dictionaries/(en, es, de, fr, pl). - Usage in templates:
{{ 'Some English Text' | i18n }}
How dictionaries work
en.tsis the source of truth. Keys are English strings; values are numeric IDs (e.g.'Domain Health': 748).- Other language files (
de.ts,es.ts,fr.ts,pl.ts) use those same numeric IDs as keys, mapping to translated strings (e.g.748: 'Santé du domaine'). - When adding a new i18n key:
- Add the English string and next available numeric ID to
en.ts. - Add the same numeric ID with a proper translation to every other language file.
- Always provide real translations, not empty strings.
- Add the English string and next available numeric ID to
Services & State
Services often extend Observable and expose reactive streams via DI:
ConnectionService— Combines network status + WebSocket readinessStateService— Polls server availability, manages app state (running,initializing, etc.)AuthService— TracksisVerified$, triggers PatchDB start/stopPatchMonitorService— Starts/stops PatchDB based on auth statePatchDataService— Watches entire DB, updates localStorage bootstrap