08 — Form Specifications (Rooms Module)
- 1. Form overview
- 2. Field matrix
- 3. Validation behaviour
- 4. Submit semantics
- 5. Dirty & discard
- 6. Keyboard & a11y
- 7. Error-code → copy map
(proposed)
The single form of the module (S3 — Room Editor). Field rules derive from
create-room.dto.ts(server truth) plus the schema (room.schema.ts); client-side rules are(proposed)where the server imposes none. Markings:(planned)= needs backend change,(forward-looking)= future.
1. Form overview
| Create | Edit | |
|---|---|---|
| Route | /rooms/new | /rooms/:id/edit |
| Endpoint | POST /api/v1/rooms (rooms.controller.ts:24-28) | PATCH /api/v1/rooms/:id (rooms.controller.ts:42-46) |
| Payload DTO | CreateRoomDto (create-room.dto.ts:11-38) | same CreateRoomDto — there is no update-room.dto.ts; name+code are required even on PATCH (OQ-6, rooms.controller.ts:44) |
| Guard | JwtAuthGuard; client gates by rooms.create / rooms.update (server RBAC (planned) — OQ-2) | same |
| Prefill | none (empty form) | full Room doc from S2 (GET /rooms/:id — rooms.controller.ts:36-40) |
2. Field matrix
| # | Field | Required | Server rule (source) | Client rule (proposed) | Notes |
|---|---|---|---|---|---|
| F1 | name | ✅ | @IsString (create-room.dto.ts:12-14); trim: true (room.schema.ts:18-19) | non-empty; ≤ 120 chars; trimmed | — |
| F2 | code | ✅ | @IsString (create-room.dto.ts:16-18); trim (room.schema.ts:21-22); unique per tenant (create 409 rooms.service.ts:19-21; unique index room.schema.ts:38) | non-empty; pattern [A-Z0-9-]{2,24}; uppercase suggestion; async duplicate hint (proposed) | edit-path duplicate check (planned) OQ-1 |
| F3 | capacity | ❌ | @IsNumber only (create-room.dto.ts:20-23) — no bounds (OQ-7) | integer ≥ 1; reject < 1 and non-integer input; warn > 10 000 | server @Min(1) (planned) |
| F4 | type | ❌ (default classroom) | @IsEnum(RoomType) (create-room.dto.ts:25-28); default classroom (room.schema.ts:27-28) | dropdown of RoomType values (room.schema.ts:7-14) | — |
| F5 | building | ❌ | @IsString (create-room.dto.ts:30-33) | free text; suggestions from loaded list (proposed); ≤ 60 chars | — |
| F6 | facilities | ❌ | @IsArray (create-room.dto.ts:35-38) — element type unchecked; stored [String] (room.schema.ts:33-34) | chip input; dedupe; max 12; each ≤ 32 chars (proposed) | free-text tags; (forward-looking) structured equipment/status fields (OQ-5) |
3. Validation behaviour
| Layer | Rule | UX |
|---|---|---|
| Required (F1, F2) | validate on submit + on blur after first submit attempt | inline error under field; focus first invalid |
| F3 range | < 1 blocked on input ((proposed)); 0/negative impossible client-side | inline hint "Capacity must be at least 1" |
| F2 uniqueness (create) | async check (proposed): debounce 300 ms, match against GET /rooms client list; server 409 is authoritative | inline ✓ / ✗ under Code; 409 → scroll-to-field + error box |
| F2 uniqueness (edit) | not checkable client-side reliably (list may be paginated); server check (planned) OQ-1 | warning copy "Code must be unique"; server 500 today → mapped to generic error + support note (proposed) |
| Enum (F4) | server 400 if out-of-range — impossible via dropdown | — |
| Submit | full DTO serialized; unknown keys never sent (whitelist = DTO fields) | Save button loading; fields disabled |
4. Submit semantics
- Create: on 201 → pop to S2 (detail) + snackbar "Room created" + cache invalidation.
- Edit: on 200 → pop to S2 + snackbar "Room updated"; S2 refetches (or reconciles from
response doc —
updateByIdreturns the updated doc,base.repository.ts:57-66). - 409 (create): form kept, inline duplicate error, no reset.
- 400: map
details[]→ field errors (envelope convention, 00-shared/07). - 404 (edit): pop + snackbar "Room not found".
- Offline: block submit, banner, draft kept in memory (no autosave).
- Concurrent edit
(proposed):versionincrements server-side (base.repository.ts:63); client sendsbaseVersion(planned)forIf-Match-style conflict → "Room was updated elsewhere" dialog with reload/discard.
5. Dirty & discard
- Dirty = any field differs from the initial model (or any chip added/removed).
- Back/close with dirty →
AppDialog"Discard changes?" [Keep editing] [Discard]. - No autosave; draft lost on discard.
6. Keyboard & a11y
- Order: F1 → F2 → F4 (dropdown) → F3 (numeric) → F5 → F6 (chip input).
- F3 keyboard:
TextInputType.number; F1/F2/F5: text with autocapitalize words (F2characters+ uppercase transform(proposed)). - Labels linked; errors in live regions; scroll-to-first-invalid; keyboard avoidance (00-shared/09).
7. Error-code → copy map (proposed)
| Code | Copy |
|---|---|
DUPLICATE_RESOURCE (409) | "A room with this code already exists." (code field) |
VALIDATION_ERROR (400) | per-field messages from details[] |
RESOURCE_NOT_FOUND (404) | "This room no longer exists." |
RATE_LIMITED (429) | "Too many requests — try again shortly." |
| 5xx | "Something went wrong (requestId …)." |