06 - Screen Specifications (Houses Module)
- 1. House List Screen (
/houses) - 2. House Detail Screen (
/houses/:id) - 3. House Editor (bottom sheet / dialog)
- 4. Student-side house picker (cross-module, reused by Students forms)
- 5. Permissions summary per screen
Full per-screen specification: layout, states, widgets, data binding, interactions, permissions, a11y and motion. Authoritative patterns in 00-shared/03 (Component Library), 00-shared/08 (Interaction & Motion), 00-shared/09 (Accessibility Baseline). Widgets marked
App*come from 00-shared/03; module widgets from 07_Component_Library.md.
1. House List Screen (/houses)
1.1 Layout (phone)
AppBar: "Houses" [houses.read]
└─ (right) overflow menu? no - empty state only
HouseColorCard grid (2 columns) # HouseColorCard, 07 §1
[color swatch][Name ]
[code chip · motto 1-line]
FAB: "New house" [houses.create]
Tablet/desktop: 3-column grid, max content width 960 dp, master-detail optional.
1.2 Data binding
| Element | Source | Mapping |
|---|---|---|
| List | GET /houses?page=1&limit=20 → data: House[], meta | HouseListCubit (13 §2) |
| Card | House.name, code, color, motto | house.schema.ts:9-19 |
| Pagination | meta.totalItems/totalPages/hasNext/hasPrevious | pagination-query.dto.ts:32-39 |
No server sort (houses.service.ts:30 finds with {}); client renders createdAt
asc (creation order). q/sort query params exist in PaginationQueryDto
(pagination-query.dto.ts:21-29) but the houses list ignores them - no search box.
1.3 States
| State | Render | Trigger |
|---|---|---|
| Initial | skeletons x4 | mount |
| Loading | skeletons (page>1: footer spinner) | page change, pull-to-refresh |
| Success | grid + footer | 200 envelope |
| Empty | AppEmptyState "No houses yet. Create your first house." | data.length == 0 |
| Error | AppOfflineBanner + retry button; stale grid kept | network 401/500/offline |
| Permission | empty + "contact admin" copy (no FAB) | no houses.read/houses.create |
1.4 Interactions
- Tap card →
/houses/:id(push). - FAB → editor sheet (create mode,
05§3). - Pull-to-refresh → refetch page 1.
- Card menu (ellipsis, admin): Edit → editor (update mode); Delete → confirm dialog
(
14§4 warning about members).
1.5 Motion & a11y
- Card appear: stagger
m-fastfade-up (≤4 rows) per00-shared/08. - Color cards: color must never be the only differentiator - name text always
present (WCAG AA contrast for swatch badges,
00-shared/09). - Skeleton < 200 ms perceived; content < 2 s on network (
00-shared/10§1).
2. House Detail Screen (/houses/:id)
2.1 Layout
[ HouseHeader: full-width color banner (house.color → token, 11 §3) ]
Name (displayLarge) code chip [houses.update: menu ▾ Edit/Delete]
Motto (italic, muted)
[ Members section ]
header: "Members" · count badge · refresh icon
MemberTile list (AppAvatar initials, admissionNumber, class label*)
*(class label not in student list payload today - omit, `(planned)`)
[ Meta section (admin) ]
created · updated · version (small, muted)
2.2 Data binding
| Section | Source |
|---|---|
| Identity | GET /houses/:id → HouseDocument (houses.service.ts:36-40; 404 House not found. :38) |
| Members | Client join (planned): GET /students?page&limit (student.controller.ts:41-43) then filter houseId == id client-side. Server filter / members endpoint (planned) (09 §G1) |
| Meta | base.schema.ts:13-34 (createdAt, updatedAt, version) |
2.3 States
| Section | State | Render |
|---|---|---|
| Header | Loading / 404 / Success | skeletons / "House not found" + pop / banner |
| Members | Loading / Empty / Loaded / Error | 3 skeleton tiles / "No members yet" / count + tiles / inline retry |
| Members | Unverified count | if joined client-side, show ~ prefix + tooltip "server-side member count planned" |
| Whole | Offline | banner; detail cached from list when available (13 §6) |
2.4 Interactions
- Edit → editor sheet prefilled (update mode).
- Delete →
AppDialogconfirm; on confirm: warning list (member count), thenDELETE /houses/:id(houses.service.ts:51-54). No 409 expected - backend has no dependency guard; client warns pre-emptively (14§4). - Member tap → student detail (cross-module navigation via
/students/:id). - Pull-to-refresh → refetch house + members.
2.5 a11y & motion
- Banner color: name/motto must pass contrast over arbitrary
color- compute on tokenization (11§3), fall back to dark scrim. - Delete dialog: destructive button styled
error; focus trapped (00-shared/09). - Section transitions
m-basefade; no parallax on color banner (motion budget).
3. House Editor (bottom sheet / dialog)
3.1 Layout (create mode)
Sheet header: "New House" [houses.create]
Form: (08_Form_Specifications.md)
Name AppTextField (required)
Code AppTextField (required, uppercase hint, conflict inline)
Color ColorPickerField (preset swatches + custom hex)
Motto AppTextField (optional, multiline 1-2)
Actions: [Cancel] [Create house] (filled, fullWidth)
Update mode: title "Edit House", button "Save changes", fields prefilled.
3.2 Data binding
| Field | DTO | Rule (source) |
|---|---|---|
| name | CreateHouseDto.name | required IsString (create-house.dto.ts:5-7); trimmed server-side (house.schema.ts:9-10) |
| code | CreateHouseDto.code | required IsString (create-house.dto.ts:9-11); trimmed (house.schema.ts:12-13); unique per tenant (house.schema.ts:23) |
| color | CreateHouseDto.color | optional IsOptional IsString (create-house.dto.ts:13-16; house.schema.ts:15-16) |
| motto | CreateHouseDto.motto | optional IsOptional IsString (create-house.dto.ts:18-21; house.schema.ts:18-19) |
Create → POST /houses (houses.controller.ts:24-28); Update → PATCH /houses/:id
(houses.controller.ts:42-46). PATCH sends the full DTO (name+code required in
body even for edits) - see 03 J1 step 5, 08 §4.
3.3 States
| State | Render |
|---|---|
| Idle | form enabled, button enabled |
| Submitting | button spinner, fields disabled (single-flight) |
| Conflict (409) | inline error under code field, verbatim House code "X" already exists. (houses.service.ts:19-21); focus field |
| Update 404 | snackbar "House not found" + pop sheet; list refetches |
| Offline | form blocked, AppOfflineBanner (00-shared/10 §2) |
3.4 Interactions
- Enter submits (name+code filled).
- Code field: suggest uppercase normalization; do not enforce - server trims only.
- Cancel with dirty form → discard confirm dialog (motion
m-fast). - Color picker: 8 preset swatches (design tokens
11§3) + custom hex with contrast hint against white text.
3.5 Motion & a11y
- Sheet slide-up
m-base; field errors shakem-fast(00-shared/08). - Labels linked to fields; error messages in live regions (
00-shared/09). - Custom hex input validates
#RRGGBB; invalid → field error, not block.
4. Student-side house picker (cross-module, reused by Students forms)
Not a houses screen - the field contract it depends on:
- optional
houseId(IsMongoId) oncreate-student.dto.ts:39-42andupdate-student.dto.ts:45-48. - Picker options from
GET /houses(house list cubit cache,13§6). - "None" option to clear a house (send empty/omit
houseId).
5. Permissions summary per screen
| Screen | Read | Create | Update | Delete |
|---|---|---|---|---|
| House list | houses.read | houses.create (FAB) | - | houses.delete (menu) |
| House detail | houses.read | - | houses.update (menu) | houses.delete (menu) |
| House editor | - | houses.create | houses.update | - |
Source: permissions.constants.ts:46-49. Server RBAC not wired yet (AGENTS.md -
"Auth (JWT/RBAC guards) ... not yet implemented") - client gating only.