Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

08 — Form Specifications (Communication Module)

The Compose Announcement form is the only form in this module (thread compose is a lightweight input bar, deferred). Field rules are verbatim from create-announcement.dto.ts; anything beyond it is (planned) and does not exist on the backend today.


1. Form map

FieldRequiredControlSource rule
TitleAppTextField single-lineIsString, MinLength(1) (create-announcement.dto.ts:30-33); trimmed on save (announcement.schema.ts:33-34)
BodyAppTextField multilineIsString, MinLength(1) (create-announcement.dto.ts:35-38); trimmed (announcement.schema.ts:36-37)
Audience typeAudiencePicker choice chipsIsEnum(AudienceType) (create-announcement.dto.ts:14-16); enum all|role|grade|section|custom (announcement.schema.ts:7-13)
Audience valueconditionalpicker / chips / noneIsOptional, IsString({ each: true }), string | string[] (create-announcement.dto.ts:24-26)
Attachmentschip list + pickerIsOptional, IsArray, IsString({each}) (create-announcement.dto.ts:45-49)
Prioritynot a backend field(planned) — absent from announcement.schema.ts
Expirynot a backend field(planned) — absent from announcement.schema.ts

2. Audience picker — value semantics per type

TypeValue UIPayload valueResolution note
allnone (hint "Everyone in your school")omittedresolves [] (announcement.service.ts:144-146) — broadcast marker
roledropdown of roles (RBAC constants)"teacher"org members roles: value, status: 'active' → userIds (announcement.service.ts:114-119)
gradepicker fed by academics (grade names)"Grade 10"gradeRepo.findOne({name}) → students (:120-125) — name, not id
sectionpicker fed by academics (section names)"10-A"sectionRepo.findOne({name}) → students (:126-135) — name, not id
customuser search + chips["u1","u2"]direct ObjectId map (:136-143)

Typo/unknown grade or section name ⇒ backend silently resolves [] (announcement.service.ts:122,129). Client should validate against live pickers and preview the recipient count (planned).

3. Submit model

ActionRequestsResult
Save draftPOST /announcementspublished: false, createdBy from token (announcement.service.ts:31-37); emits AnnouncementCreated (:39-51)
PublishPOST /announcementsPOST /announcements/:id/publishtargetUserIds resolved, published: true, publishedAt (:68-93); emits AnnouncementPublished (:80-91)

Two-step sequencing: publish button performs save-then-publish; if the save fails, nothing publishes; if publish fails (404/network), draft is kept and error surfaced with "Retry publish".

4. Draft semantics

  • Draft = document with published: false (announcement.schema.ts:45-46).
  • Drafts are never returned specially — GET /announcements includes them in the full list (announcement.service.ts:56-60); client shows them only on "Mine"/author views.
  • No update route — an existing draft cannot be edited, only recreated. (planned).
  • AnnouncementCreated fires on draft save, so downstream channels may receive draft-level events (announcement.service.ts:39-51) — relevant if the client shows "created" toast.

5. Client validation (mirrors class-validator)

FieldClient ruleFailure UX
Titlenon-empty after trim, ≤ 120 chars (product limit (proposed))inline error, focus
Bodynon-empty after triminline error, focus
Audience typeone of 5 enum valuesradio/chips always select one
Audience valuerequired unless all; strings onlyinline error "Choose who sees this"
AttachmentsURL strings; size/type pre-check client-side only (proposed)chip-level error

6. Sub-states

  • idle → dirty → saving draft (button spinner, fields disabled) → saved (snackbar "Draft saved", stay on form) → publish (loading, full-screen spinner on second request) → success (pop to feed/mine).
  • Network failure: banner + retry; no offline persistence — form state kept in memory (forward-looking: local draft store).
  • Double-tap protection on Publish (idempotent server-side, but avoid duplicate POSTs).

7. a11y

Labels linked to fields; audience chips grouped as radio group (Semantics container); "0 recipients" warning as live region (planned); keyboard: title .text, body multiline with toolbar row (attachment, priority (planned)).

8. Priority & expiry — (planned) design intent

When backend adds priority (e.g. normal|high + pinned) and expiry (auto-hide date) fields — flagged in IMPLEMENTATION_PLAN.md roadmap (docs/IMPLEMENTATION_PLAN.md:231 templates/preferences cluster; none for announcements specifically) — the form gains:

  • priority segmented control (normal/high) — high rows pinned on feed
  • expiry date picker (showDatePicker, min = today+1) — expired items filtered client-side and flagged "Expired" Until then these controls MUST NOT render (no schema fields, announcement.schema.ts:31-53).