--- type: guide title: Setup Form Field Errors description: Pure error-computation functions that drive the inline validation messages on the first-admin modal. tags: [setup, validation, forms, frontend] timestamp: 2026-07-21T16:48:20Z --- # Purpose The three pure helpers exported by `frontend/src/app/features/setup/setup-create-admin.field-errors.ts` turn the raw Angular reactive-form control state into the user-facing strings shown beside each input on the first-admin modal. Keeping them as standalone functions (instead of `computed()` signals inside the component) makes them trivially unit-testable in isolation — see `tests/frontend/setup-create-admin.spec.ts`. # Helpers | Function | Returns | Reads | |-----------------|---------------------------------------------------------|------------------------------------------------------------------------| | `usernameError` | `string \| null` | `value`, `touched`, plus `errors` keys `required`/`minlength`/`maxlength`/`pattern`. | | `passwordError` | `string \| null` | `value`, `touched`, plus `errors.required`. | | `confirmError` | `string \| null` | `value`, `touched`, `errors.required`, and the group-level `groupMismatch` flag from [the password-match validator](/architecture/key-files.md). | All three return `null` when the field is not yet `touched` (or, for `confirmError`, when no group-level mismatch has been raised), so the UI stays clean until the user has interacted with the form. # Inputs ```ts export interface FieldErrorContext { value: string; touched: boolean; errors: Partial> | null; groupMismatch?: boolean; } export type FieldErrorKey = | 'required' | 'minlength' | 'maxlength' | 'pattern' | 'passwordMismatch'; ``` # Examples ```ts usernameError({ value: 'ab', touched: true, errors: { minlength: true }, }); // -> 'Username must be at least 3 characters' confirmError({ value: 'hunter2', touched: true, errors: null, groupMismatch: true, }); // -> 'Passwords do not match' passwordError({ value: '', touched: true, errors: { required: true }, }); // -> 'Password is required' ``` # Wiring * `SetupCreateAdminComponent` (`frontend/src/app/features/setup/setup-create-admin.component.ts`) binds the three helpers to template method calls (`usernameError()`, `passwordError()`, `confirmError()`) that read the corresponding `FormControl` and pass its current state. * The template (`frontend/src/app/features/setup/setup-create-admin.component.html`) renders the returned string inside a `` with `data-testid="setup-{username|password|confirm}-error"`, and mirrors the result on the input via `[attr.aria-invalid]`. * `tests/frontend/setup-create-admin.spec.ts` exercises every code path of the three helpers directly — no Angular TestBed is required. # See also - [First-Run Bootstrap](/guides/bootstrap.md) — how the helpers surface to the operator. - [Setup Endpoint](/api/setup.md) — server-side validation that mirrors these messages. - [Key Files Index](/architecture/key-files.md)