@xiriframework/xiri-ng 0.2.26 → 0.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiriframework/xiri-ng",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "description": "Angular UI component library for the Xiri Framework",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -54,5 +54,6 @@
54
54
  "types": "./types/xiriframework-xiri-ng.d.ts",
55
55
  "default": "./fesm2022/xiriframework-xiri-ng.mjs"
56
56
  }
57
- }
57
+ },
58
+ "type": "module"
58
59
  }
@@ -0,0 +1,393 @@
1
+ ---
2
+ name: xiri-ng-expert
3
+ description: Experte für die xiri-ng Angular-Library. Verwende diesen Skill IMMER wenn Angular-Code geschrieben wird der @xiriframework/xiri-ng importiert, oder wenn der User nach xiri-Komponenten (xiri-dyncomponent, xiri-form-fields, xiri-table, xiri-card usw.), XiriDataService, XiriSnackbarService, provideXiriServices, Form-Feldern mit showWhen-Bedingungen, oder Server-Side-Tables fragt.
4
+ ---
5
+
6
+ # xiri-ng Expert
7
+
8
+ Du bist Experte für die **xiri-ng** Library (`@xiriframework/xiri-ng`) — eine Angular-21-Komponentenbibliothek, die vom Go-Backend (`xiri-go`) erzeugte JSON-Strukturen in Material-Design-3-UIs rendert.
9
+
10
+ **Wichtig:** Diese Datei enthält die häufigsten API-Signaturen. Lies `references/*.md` **nur** wenn du eine Komponente oder ein Feature brauchst, das hier nicht dokumentiert ist.
11
+
12
+ ## Architektur
13
+
14
+ Backend-JSON → `xiri-dyncomponent` → Standalone-Components → Material Design 3.
15
+
16
+ - **Angular 21** mit Standalone-Components, Signals, OnPush, `input()`/`output()` API
17
+ - **Angular Material 3** mit SCSS-Theming (`ThemeService` mit `mode`-Signal)
18
+ - **Reactive Forms** (`UntypedFormGroup`) für `XiriFormFieldsComponent`
19
+ - **JSON-Driven**: Backend schickt `XiriDynData[]` → Frontend rendert per `xiri-dyncomponent`
20
+ - **Public API**: `projects/xiri-ng/src/public-api.ts` — jede Komponente, jedes Interface wird dort exportiert
21
+
22
+ ## Setup — provideXiriServices
23
+
24
+ ```typescript
25
+ import { provideXiriServices } from '@xiriframework/xiri-ng';
26
+
27
+ export const appConfig: ApplicationConfig = {
28
+ providers: [
29
+ provideHttpClient(),
30
+ provideXiriServices({ api: '/api/' }), // Default: '/api/'
31
+ ],
32
+ };
33
+ ```
34
+
35
+ Alle Services sind `providedIn: 'root'` (automatisch verfügbar nach Provider-Registrierung).
36
+
37
+ ## Services — Quick Reference
38
+
39
+ ```typescript
40
+ import {
41
+ XiriDataService, XiriSnackbarService, XiriResponseHandlerService,
42
+ XiriFormService, XiriDownloadService, XiriDateService, XiriNumberService,
43
+ XiriLocalStorageService, XiriSessionStorageService, ThemeService,
44
+ } from '@xiriframework/xiri-ng';
45
+
46
+ // HTTP-Client mit Snackbar-Integration
47
+ data.get(url): Observable<Object>
48
+ data.post(url, payload): Observable<any>
49
+ data.postFile(url, payload): Observable<any> // responseType blob
50
+ data.postFileResponse(url, payload): Observable<HttpResponse> // für download service
51
+ data.getConfigApi(): string // Base-URL
52
+
53
+ // Toast-Notifications
54
+ snackbar.success(msg, duration?, action?)
55
+ snackbar.error(msg, duration?, action?)
56
+ snackbar.info(msg, duration?, action?)
57
+ snackbar.warning(msg, duration?, action?)
58
+ snackbar.handleResponse(response): boolean // parst response.message + response.messageType
59
+
60
+ // Backend-Response-Handler (navigation / page-refresh / table-refresh)
61
+ responseHandler.handle(result, { onTableRefresh?, onTableUpdate? })
62
+
63
+ // File-Download
64
+ download.download(httpResponse, filename, openInNewTab): boolean
65
+
66
+ // Theme (Signal-basiert)
67
+ theme.mode // Signal<'light' | 'dark' | 'auto'>
68
+ theme.isDark // computed Signal<boolean>
69
+ theme.setTheme('dark' | 'light' | 'auto')
70
+ theme.toggle()
71
+ theme.resetToAuto()
72
+
73
+ // Storage — wrapper mit in-memory-Fallback
74
+ localStorage.set(name, value)
75
+ localStorage.get(name)
76
+ localStorage.getTimeout(name, maxSeconds) // null wenn älter
77
+ localStorage.remove(name)
78
+ localStorage.clear()
79
+ // XiriSessionStorageService hat gleiche API
80
+ ```
81
+
82
+ ## xiri-dyncomponent — der JSON-Renderer
83
+
84
+ ```html
85
+ <xiri-dyncomponent [data]="components" [filterData]="filter"></xiri-dyncomponent>
86
+ ```
87
+
88
+ ```typescript
89
+ import { XiriDynData } from '@xiriframework/xiri-ng';
90
+
91
+ components: XiriDynData[] = [
92
+ { type: 'card', data: { header: 'Titel', fields: [...], data: {...} } },
93
+ { type: 'table', data: { url: '/api/items', fields: [...] } },
94
+ { type: 'stat', data: { value: 42, label: 'Offen' } },
95
+ ];
96
+
97
+ // type ist einer von:
98
+ // card | buttonline | table | cardlink | links | form | query
99
+ // stepper | header | list | spacer | container | infopoint | multiprogress
100
+ // imagetext | tabs | expansion | infotext | html | stat | empty-state
101
+ // timeline | page-header | section | divider | stat-grid | toolbar
102
+ // description-list | barchart (barchart benötigt zusätzlich `mode`)
103
+ ```
104
+
105
+ ## xiri-form-fields — Forms
106
+
107
+ ```html
108
+ <xiri-form-fields
109
+ [form]="fields"
110
+ [display]="'full'"
111
+ [disabled]="submitting()"
112
+ (formChange)="onFormChange($event)">
113
+ </xiri-form-fields>
114
+ ```
115
+
116
+ ```typescript
117
+ import { XiriFormField } from '@xiriframework/xiri-ng';
118
+
119
+ fields: XiriFormField[] = [
120
+ { id: 'name', type: 'text', name: 'Name', required: true, validations: [...] },
121
+ { id: 'email', type: 'text', subtype: 'email', name: 'E-Mail' },
122
+ { id: 'group', type: 'model', name: 'Gruppe', url: '/api/groups' },
123
+ { id: 'active',type: 'bool', name: 'Aktiv', value: true },
124
+ { id: 'desc', type: 'textarea', name: 'Beschreibung', rows: 4,
125
+ showWhen: { field: 'active', operator: 'equals', value: true } },
126
+ ];
127
+ ```
128
+
129
+ Verfügbare `type`-Werte: `text`, `email`, `password`, `textarea`, `number`, `bool`, `select`, `object`, `model`, `multiselect`, `treeselect`, `date`, `datetime`, `daterange`, `datetimerange`, `file`, `volume`, `timelimit`, `chips`, `question`, `waiting`, `header`.
130
+
131
+ `display` = `'full' | 'line' | 'small'` — Layout-Modus.
132
+
133
+ **showWhen-Operators:** `equals`, `notEquals`, `contains`, `greaterThan`, `lessThan`, `in`, `notEmpty`.
134
+
135
+ Zugriff auf die reactive Form:
136
+
137
+ ```typescript
138
+ @ViewChild(XiriFormFieldsComponent) fieldsCmp!: XiriFormFieldsComponent;
139
+
140
+ submit() {
141
+ const values = this.fieldsCmp.formGroup.value;
142
+ if (this.fieldsCmp.formGroup.valid) { ... }
143
+ }
144
+ ```
145
+
146
+ ## xiri-form — Backend-integriertes Formular
147
+
148
+ ```html
149
+ <xiri-form [settings]="{
150
+ url: '/api/users/add',
151
+ load: true,
152
+ header: 'Neuer User'
153
+ }"></xiri-form>
154
+ ```
155
+
156
+ Lädt Felder + Buttons vom Backend, submit → POST → response-handler navigiert/refresht.
157
+
158
+ ## xiri-table — volle Datentabelle
159
+
160
+ ```html
161
+ <xiri-table [settings]="tableSettings" (clickedRow)="openEdit($event)"></xiri-table>
162
+ ```
163
+
164
+ ```typescript
165
+ tableSettings = {
166
+ url: '/api/devices',
167
+ serverSide: true,
168
+ options: {
169
+ pagination: true, itemsPerPage: 50, sort: true, search: true,
170
+ saveState: true, saveStateId: 'devices-table',
171
+ select: true, selectButtons: [ ... ],
172
+ editUrl: '/api/devices/inline-edit',
173
+ buttons: { buttons: [...], class: 'small' },
174
+ },
175
+ fields: [
176
+ { id: 'id', name: 'ID', format: 'number' },
177
+ { id: 'name', name: 'Name', search: true, sort: true, sticky: true },
178
+ { id: 'status', name: 'Status', format: 'icon', icons: [...] },
179
+ { id: 'count', name: 'Anzahl', format: 'number', webformat: 'integer', align: 'right', footer: 'sum' },
180
+ { id: 'note', name: 'Notiz', editable: true, inputType: 'text' },
181
+ ],
182
+ };
183
+ ```
184
+
185
+ Öffentliche Methoden: `reload()`, `searchDo(text)`, `startInlineEdit(row, column)`, `cancelInlineEdit()`, `saveInlineEdit(row, column)`, `selection` (SelectionModel), `isAllSelected()`, `masterToggle()`.
186
+
187
+ ## xiri-raw-table — minimale Tabelle
188
+
189
+ ```html
190
+ <xiri-raw-table [settings]="{ data: rows, fields: cols, dense: 8 }"></xiri-raw-table>
191
+ ```
192
+
193
+ Keine Pagination, kein Sort, keine API — nur Daten rendern.
194
+
195
+ ## xiri-query — Suchformular mit Live-Ergebnis
196
+
197
+ ```html
198
+ <xiri-query [settings]="{
199
+ fields: filterFields,
200
+ url: '/api/search',
201
+ collapsed: false,
202
+ saveState: true, saveStateId: 'search-x',
203
+ buttonline: { ... }
204
+ }" (change)="onFilterChange($event)"></xiri-query>
205
+ ```
206
+
207
+ Debounce 300ms, ergebnis unter dem Filter als `XiriDynData[]`.
208
+
209
+ ## xiri-dialog — Modal-Dialog
210
+
211
+ ```typescript
212
+ import { MatDialog } from '@angular/material/dialog';
213
+ import { XiriDialogComponent } from '@xiriframework/xiri-ng';
214
+
215
+ constructor(private dialog: MatDialog) {}
216
+
217
+ open() {
218
+ this.dialog.open(XiriDialogComponent, {
219
+ data: { url: '/api/user/edit/42', type: 'form', size: '800px' },
220
+ });
221
+ }
222
+ ```
223
+
224
+ `type`: `'form' | 'data' | 'question' | 'waiting' | 'table'`.
225
+
226
+ ## xiri-button / xiri-buttonline
227
+
228
+ ```typescript
229
+ import { XiriButton, XiriButtonResult } from '@xiriframework/xiri-ng';
230
+
231
+ buttons: XiriButton[] = [
232
+ { text: 'Speichern', type: 'raised', color: 'primary', action: 'api',
233
+ url: '/api/save', default: true },
234
+ { text: 'Löschen', type: 'flat', color: 'warn', action: 'dialog',
235
+ url: '/api/delete/confirm' },
236
+ { text: 'Zurück', type: 'flat', action: 'back' },
237
+ ];
238
+ ```
239
+
240
+ ```html
241
+ <xiri-buttonline [settings]="{ buttons, class: 'small' }"
242
+ [filterData]="filter"
243
+ (result)="onResult($event)"></xiri-buttonline>
244
+ ```
245
+
246
+ `action`: `'api' | 'dialog' | 'download' | 'link' | 'back' | 'close' | 'return' | 'menu' | …`.
247
+
248
+ `data?: Record<string, any>` — Custom-Payload, das beim Klick mit `filterData` gemerged in den POST-Body geht (für `action: 'api'` / `'download'`). Der Backend-Builder (`xiri-go`) setzt das via `button.WithData(...)`. Beispiel: CSV-Download-Button hat `data: { _csv: true }`, das Backend liest das Flag in `LoadFilterData` und schaltet auf CSV-Output.
249
+
250
+ ## xiri-stepper — Multi-Step Wizard
251
+
252
+ ```typescript
253
+ stepperSettings = {
254
+ url: '/api/onboarding',
255
+ steps: [
256
+ { title: 'Daten', fields: [...], buttons: [...] },
257
+ { title: 'Kontakt', fields: [...], buttons: [...] },
258
+ { title: 'Review', fields: [...], buttons: [...], extra: { finalize: true } },
259
+ ],
260
+ };
261
+ ```
262
+
263
+ ## Layout / Container
264
+
265
+ ```html
266
+ <xiri-page-header [settings]="{ title: 'Users', subtitle: '42 aktiv',
267
+ icon: 'group', buttons: { buttons: [...] } }"/>
268
+ <xiri-toolbar [settings]="{ title: 'Items', search: true, buttons: {...} }"
269
+ (searchChanged)="onSearch($event)"/>
270
+ <xiri-section [settings]="{ title: 'Details', collapsible: true, components: [...] }"/>
271
+ <xiri-divider [settings]="{ text: 'Weitere Optionen', spacing: 'normal' }"/>
272
+ <xiri-tabs [settings]="{ tabs: [...], lazy: true }"/>
273
+ <xiri-expansion [settings]="{ panels: [...], multi: false, lazy: true }"/>
274
+ <xiri-sidenav [settings]="{ prefix: '/app/', fields: navItems }"/>
275
+ <xiri-breadcrumb [settings]="breadcrumbItems"/>
276
+ <xiri-skeleton type="table-row" [lines]="5" [columns]="4"/>
277
+ <xiri-empty-state [settings]="{ icon: 'inbox', title: 'Leer', button: {...} }"/>
278
+ ```
279
+
280
+ ## Info / Display
281
+
282
+ ```html
283
+ <xiri-stat [settings]="{ value: 1234, label: 'Umsatz', prefix: '€', color: 'primary',
284
+ trend: { value: 12, direction: 'up' } }"/>
285
+ <xiri-stat-grid [settings]="{ stats: [...], columns: 4, title: 'KPIs' }"/>
286
+ <xiri-timeline [settings]="{ items: [...], orientation: 'vertical' }"/>
287
+ <xiri-barchart mode="simple" [settings]="{ title: 'Weekly', yMin: 0, yMax: 12, color: 'purple',
288
+ bars: [{ label: 'M', name: 'Monday', value: 3 }, ...] }"/>
289
+ <xiri-barchart mode="stacked" [settings]="{ bars: [{ label: 'M', name: 'Monday',
290
+ segments: [{ value: 2, color: 'green', name: 'Low strain' }, ...] }] }"/>
291
+ <xiri-barchart mode="heatmap" [settings]="{ points: [{ time: 1700000000000, value: 1, name: 'Repeat #1' }, ...] }"/>
292
+ <!-- echarts ist optional peerDependency: nur installieren wenn xiri-barchart genutzt wird -->
293
+ <xiri-description-list [settings]="{ items: [...], columns: 2, layout: 'horizontal' }"/>
294
+ <xiri-list [settings]="{ sections: [...] }"/>
295
+ <xiri-infopoint [settings]="{ text: 'Info', info: 'Detail', icon: 'info', iconColor: 'primary' }"/>
296
+ <xiri-multiprogress [settings]="{ data: [...], show: 5, header: 'Top 5' }"/>
297
+ <xiri-imagetext [settings]="{ url: '/img.png', info: '...', header: '...' }"/>
298
+ <xiri-header [settings]="{ text: 'Abschnitt', color: 'primary', size: 'h2' }"/>
299
+ <xiri-card [settings]="{ url: '/api/overview', header: 'Übersicht', collapsible: true }"/>
300
+ <xiri-cardlink [settings]="{ link: '/app/users', icon: 'group', iconSet: '', text: 'Users' }"/>
301
+ <xiri-links [settings]="{ data: [...], header: 'Schnellzugriff' }"/>
302
+ <xiri-done/>
303
+ <xiri-error text="Fehler beim Laden"/>
304
+ ```
305
+
306
+ ## Farben
307
+
308
+ ```typescript
309
+ import { XiriColor } from '@xiriframework/xiri-ng';
310
+
311
+ // Theme: 'primary' | 'secondary' | 'tertiary' | 'accent' | 'warn' | 'error' | 'success'
312
+ // Extended: 'emerald' | 'red' | 'yellow' | 'green' | 'blue' | 'purple'
313
+ // 'gray' | 'lightgray' | 'darkgray' | 'orange' | 'white' | 'black' | 'inherit'
314
+ ```
315
+
316
+ ## Pipes
317
+
318
+ ```html
319
+ {{ htmlString | safeHtml }} <!-- bypass DomSanitizer -->
320
+ ```
321
+
322
+ ## Typische Patterns
323
+
324
+ ### 1. Seite rendert Backend-JSON
325
+
326
+ ```typescript
327
+ @Component({
328
+ selector: 'app-overview',
329
+ imports: [XiriDynComponentComponent],
330
+ template: `<xiri-dyncomponent [data]="components()"/>`,
331
+ changeDetection: ChangeDetectionStrategy.OnPush,
332
+ })
333
+ export class OverviewComponent {
334
+ private data = inject(XiriDataService);
335
+ components = signal<XiriDynData[]>([]);
336
+
337
+ ngOnInit() {
338
+ this.data.get('/api/overview').subscribe((res: any) => this.components.set(res.data));
339
+ }
340
+ }
341
+ ```
342
+
343
+ ### 2. Form mit showWhen + Submit
344
+
345
+ ```typescript
346
+ onSubmit() {
347
+ if (this.fieldsCmp.formGroup.invalid) return;
348
+ this.data.post('/api/save', this.fieldsCmp.formGroup.value)
349
+ .subscribe(res => this.responseHandler.handle(res));
350
+ }
351
+ ```
352
+
353
+ ### 3. Server-Side-Table mit Refresh nach Edit
354
+
355
+ ```typescript
356
+ @ViewChild(XiriTableComponent) table!: XiriTableComponent;
357
+
358
+ onEditSaved(res: any) {
359
+ this.responseHandler.handle(res, {
360
+ onTableRefresh: () => this.table.reload(),
361
+ });
362
+ }
363
+ ```
364
+
365
+ ### 4. Dialog öffnen für Edit
366
+
367
+ ```typescript
368
+ edit(row: any) {
369
+ this.dialog.open(XiriDialogComponent, {
370
+ data: { url: `/api/items/edit/${row.id}`, type: 'form', size: '700px' },
371
+ }).afterClosed().subscribe(() => this.table.reload());
372
+ }
373
+ ```
374
+
375
+ ## Wann Reference-Dateien lesen
376
+
377
+ | Datei | Wann |
378
+ | --------------------------- | ---------------------------------------------------------------- |
379
+ | `references/setup.md` | Alle Services im Detail (Methoden-Signaturen, Rückgabe-Typen) |
380
+ | `references/dyncomponent.md`| Vollständige XiriDynData-Type-Liste + Custom-Rendering |
381
+ | `references/form-fields.md` | Jeder Feldtyp im Detail, alle Validator-Keys, select-Directive |
382
+ | `references/table.md` | Table-Options-Felder, Inline-Edit, Selection, Footer-Aggregation |
383
+ | `references/components.md` | Kompakte Signatur-Liste aller 30+ Komponenten |
384
+ | `references/theming-i18n.md`| ThemeService, Colors, Date/Number-Locale-Propagation |
385
+
386
+ ## Was NICHT tun
387
+
388
+ - Nicht `{ ... }`-Casts statt typisierter Interfaces (nutze `XiriDynData`, `XiriFormField`, `XiriTableField`)
389
+ - Nicht `NgModule` — alle xiri-Komponenten sind **standalone**
390
+ - Nicht `ChangeDetectionStrategy.Default` ohne Grund — `OnPush` ist Convention
391
+ - Nicht `FormBuilder` direkt — `XiriFormFieldsComponent` baut die `UntypedFormGroup` selbst
392
+ - Nicht API-Calls in Templates — in Component-Code via `XiriDataService`
393
+ - Keine erfundenen Selectors / Inputs — wenn unsicher, `public-api.ts` lesen
@@ -0,0 +1,64 @@
1
+ {
2
+ "skill_name": "xiri-ng-expert",
3
+ "evals": [
4
+ {
5
+ "id": 0,
6
+ "prompt": "Erstelle eine Angular-Standalone-Komponente, die vom Backend ein JSON-Array von XiriDynData lädt (/api/dashboard) und per xiri-dyncomponent rendert. Nutze XiriDataService, OnPush und Signals.",
7
+ "expected_output": "Standalone component with @Component decorator, imports XiriDynComponentComponent, injects XiriDataService, uses signal<XiriDynData[]>, loads via data.get(), uses OnPush change detection.",
8
+ "assertions": [
9
+ {"name": "standalone_component", "type": "code_check", "description": "Component ist standalone: true (Default in Angular 21) oder imports[] ist gesetzt"},
10
+ {"name": "uses_xiri_dyncomponent", "type": "code_check", "description": "Template enthält <xiri-dyncomponent [data]=... und XiriDynComponentComponent ist in imports"},
11
+ {"name": "uses_data_service", "type": "code_check", "description": "Injects XiriDataService via inject() oder constructor und ruft .get('/api/dashboard') auf"},
12
+ {"name": "uses_signals", "type": "code_check", "description": "Nutzt signal<XiriDynData[]>([]) für Daten-State und .set() zum Aktualisieren"},
13
+ {"name": "onpush_strategy", "type": "code_check", "description": "changeDetection: ChangeDetectionStrategy.OnPush"},
14
+ {"name": "correct_types", "type": "code_check", "description": "Importiert XiriDynData und nutzt es als Typ, keine any-Flut"},
15
+ {"name": "no_invented_api", "type": "code_check", "description": "Nutzt nur public-api.ts-Exports, keine erfundenen Services/Methoden"}
16
+ ],
17
+ "files": []
18
+ },
19
+ {
20
+ "id": 1,
21
+ "prompt": "Baue ein Add-Formular mit XiriFormFieldsComponent für ein Fahrzeug: Felder name (text, required), group (model via /api/groups), active (bool, default true), note (textarea, nur sichtbar wenn active=false). Submit-Button postet an /api/vehicles/add und handled die Response.",
22
+ "expected_output": "Component with XiriFormFieldsComponent in template, defines XiriFormField[] with correct types and validations, showWhen for note, submit method uses fieldsCmp.formGroup.valid + fieldsCmp.formGroup.value, posts via XiriDataService, handles response via XiriResponseHandlerService.",
23
+ "assertions": [
24
+ {"name": "uses_form_fields_component", "type": "code_check", "description": "Template enthält <xiri-form-fields [form]=... (formChange)=..."},
25
+ {"name": "correct_field_types", "type": "code_check", "description": "name: type='text', group: type='model' mit url, active: type='bool', note: type='textarea'"},
26
+ {"name": "has_required_validation", "type": "code_check", "description": "name hat required: true oder validations mit type: 'required'"},
27
+ {"name": "uses_show_when", "type": "code_check", "description": "note-Feld hat showWhen mit field: 'active', operator: 'equals', value: false"},
28
+ {"name": "viewchild_form_group", "type": "code_check", "description": "@ViewChild auf XiriFormFieldsComponent und Zugriff auf .formGroup.valid / .formGroup.value beim Submit"},
29
+ {"name": "submits_via_data_service", "type": "code_check", "description": "XiriDataService.post('/api/vehicles/add', ...) wird im Submit-Handler aufgerufen"},
30
+ {"name": "handles_response", "type": "code_check", "description": "XiriResponseHandlerService.handle(res) wird nach erfolgreichem Submit aufgerufen"}
31
+ ],
32
+ "files": []
33
+ },
34
+ {
35
+ "id": 2,
36
+ "prompt": "Baue eine Device-Übersicht mit xiri-page-header, einem xiri-query als Filter, und darunter einer xiri-table mit Server-Side-Pagination. Bei Klick auf eine Row öffnet sich ein Edit-Dialog. Nach erfolgreichem Edit wird die Tabelle reloaded.",
37
+ "expected_output": "Component with XiriPageHeaderComponent, XiriQueryComponent and XiriTableComponent in template. Filter from query drives filterData on table. clickedRow output opens XiriDialogComponent via MatDialog. afterClosed reloads table.",
38
+ "assertions": [
39
+ {"name": "uses_page_header", "type": "code_check", "description": "xiri-page-header mit settings.title gesetzt"},
40
+ {"name": "uses_xiri_query", "type": "code_check", "description": "xiri-query mit XiriQuerySettings (fields, url, collapsed optional)"},
41
+ {"name": "server_side_table", "type": "code_check", "description": "xiri-table mit settings.options.serverSide: true, pagination aktiv, settings.url gesetzt"},
42
+ {"name": "filter_wiring", "type": "code_check", "description": "query (change)-Output wird an table [filterData] oder einer Signal/Property gebunden"},
43
+ {"name": "row_click_opens_dialog", "type": "code_check", "description": "(clickedRow) Event-Handler öffnet via MatDialog.open(XiriDialogComponent, { data: { type: 'form', url: ... } })"},
44
+ {"name": "reload_after_edit", "type": "code_check", "description": "dialogRef.afterClosed().subscribe ruft @ViewChild XiriTableComponent.reload() auf"},
45
+ {"name": "correct_imports", "type": "code_check", "description": "Alle benötigten Komponenten in imports[] deklariert"}
46
+ ],
47
+ "files": []
48
+ },
49
+ {
50
+ "id": 3,
51
+ "prompt": "Schreibe einen app.config.ts Eintrag, der xiri-ng korrekt initialisiert (api base '/api/v2/'), mit HttpClient, Router, und einem ThemeService-Toggle-Button in einer Header-Komponente die den aktuellen Theme-Mode-Signal anzeigt.",
52
+ "expected_output": "ApplicationConfig mit provideHttpClient, provideRouter, provideXiriServices({ api: '/api/v2/' }). Separate Header-Component injects ThemeService, uses theme.mode() signal in template for conditional display, calls theme.toggle() on button click.",
53
+ "assertions": [
54
+ {"name": "provide_xiri_services", "type": "code_check", "description": "appConfig.providers enthält provideXiriServices({ api: '/api/v2/' })"},
55
+ {"name": "provide_http_client", "type": "code_check", "description": "provideHttpClient() ist vor provideXiriServices registriert"},
56
+ {"name": "injects_theme_service", "type": "code_check", "description": "Header-Component nutzt inject(ThemeService)"},
57
+ {"name": "uses_mode_signal", "type": "code_check", "description": "Template liest theme.mode() oder theme.isDark() als Signal-Call"},
58
+ {"name": "toggle_method", "type": "code_check", "description": "Button ruft theme.toggle() auf"},
59
+ {"name": "correct_provider_import", "type": "code_check", "description": "provideXiriServices importiert aus '@xiriframework/xiri-ng'"}
60
+ ],
61
+ "files": []
62
+ }
63
+ ]
64
+ }