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

06 — Screen Specifications (Files Module)

Full behavioral specs for every Files screen. The biggest file in this set: each screen covers layout, states, interactions, events, a11y, motion, and its exact backend source. Shared foundations: 00-shared/03_Component_Library, 00-shared/08_Interaction_&_Motion, 00-shared/09_Accessibility_Baseline. Endpoint references: files.controller.ts:29-71.


1. File List — In-Context Section (/files embedded)

1.1 Layout

┌─────────────────────────────────────────────┐
│ [Context header]        Files (n)   [+ Attach] │
├─────────────────────────────────────────────┤
│ ┌─ AppListTile ──────────────────────────┐ │
│ │ [FileTypeIcon] report_2026.pdf    3.2 MB│ │  originalName (1 line, ellipsis)
│ │                application/pdf · 12 Aug │ │  mimeType · relative date
│ │                                        │ │  trailing: [⋮]
│ └─────────────────────────────────────────┘ │
│ … (rows, newest first)                     │
├─────────────────────────────────────────────┤
│ [AppSnackbar: "Downloaded report_2026.pdf"] │
└─────────────────────────────────────────────┘

1.2 Data source

  • GET /api/v1/files — full tenant list, createdAt desc (files.service.ts:48-50). No page/limit, no filter params — client holds files.byContext index (13_State_Management.md).
  • Response shape (envelope per 00-shared/07_API_Conventions): FileRecord[] with {id, originalName, mimeType, size, storageProvider, createdAt, updatedAt}. storageFileId/storagePath are server-internal; client must not render them.

1.3 States

StateBehavior
loadingAppSkeleton rows (n=6)
successgrouped rows, newest first; size formatted KB/MB (size, file.schema.ts:16)
emptyAppEmptyState "No files yet — attach the first one" + Attach CTA
errorinline banner + retry (re-run GET /files); offline via AppOfflineBanner
permission403 → locked row style + "Ask admin for access" (file.read missing)
disabledupload button hidden when file.upload absent; delete menu hidden when file.delete absent

1.4 Interactions

  • Tap row → detail sheet (see §3). Tap download icon → download flow (§4).
  • menu: Open details · Download · Delete (admin only) · Copy name.
  • Pull-to-refresh → re-fetch; optimistic delete rollback on 4xx.
  • Context filtering: module screen passes its context key; the section shows only files whose client-side linkage matches.

1.5 Events & analytics (proposed)

files.list.refresh, files.list.open_detail, files.list.download, files.list.delete, files.list.attach_open.

1.6 A11y & motion

  • Rows: combined semantics label "originalName, mimeType, size"; menu button AppMenu exposes actions to a11y.
  • Motion: rows m-fast fade/slide on refresh; skeleton shimmer per tokens.

2. File Picker / Upload Sheet

2.1 Layout (bottom sheet on phones; centered card ≤ 480 dp elsewhere)

┌─────────────────────────────────────────────┐
│  Attach file                    [× close]    │
├─────────────────────────────────────────────┤
│  [Choose file…]      (system picker)        │
│  [Take photo]        (forward-looking)      │
├─────────────────────────────────────────────┤
│  Selected:  [FileTypeIcon] syllabus.pdf     │
│             1.4 MB · application/pdf        │
├─────────────────────────────────────────────┤
│  [Upload]  (AppButton filled, fullWidth)    │
└─────────────────────────────────────────────┘

2.2 Data source

  • POST /api/v1/files/upload — multipart/form-data, part name file (files.controller.ts:31-36); ApiConsumes('multipart/form-data') (files.controller.ts:32).
  • Server derives storage key ${randomUUID()}--${originalName} (files.service.ts:31), persists record (files.service.ts:36-45).
  • Response: 201 FileRecord (server returns it; status code 201 default Nest POST).

2.3 Validation matrix (client-side; server enforces none today)

RuleValueVerdict
File requiredblock Upload; sheet error "Choose a file"
Size capnone enforced (gap)client soft-cap 25 MB w/ warning; hard user block (proposed)
MIME allowlistnone server-sideclient may gate common doc/image types (proposed)
Filenameany (un-sanitized, files.service.ts:31)display-only; quote in header Content-Disposition (files.controller.ts:61)

2.4 States

StateBehavior
idlepicker rows only; Upload disabled
pickingsystem picker open (platform sheet)
selectedpreview card; Upload enabled; re-pick replaces selection
uploadingUploadProgressCard (§2.5); Cancel available
successsheet closes → tile appears in list (optimistic insert, rollback on fail)
errorinline: 401/403 (auth, (planned)), 413 (planned) size, 500 provider; Retry re-POSTs (new record; no server idempotency)

2.5 Upload progress card

  • Row: filename + animated AppProgress (percent from client transport, see 15_Flutter_Implementation_Guide.md), size, Cancel text button.
  • Cancel: aborts the client request only; a partially-sent body is discarded by server (no partial-object cleanup job yet — (planned) per STORAGE_ARCHITECTURE.md:66).

2.6 A11y & motion

  • Sheet slides up m-base; live-region announces "Uploading filename — 40%".
  • All touch targets ≥ 44 dp; picker rows are single Semantics(button:).

3. File Detail Sheet (/files/:id)

3.1 Layout

┌─────────────────────────────────────────────┐
│ [FileTypeIcon]  report_2026.pdf             │
│  Name        report_2026.pdf                │
│  Type        application/pdf                │
│  Size        3.2 MB                         │
│  Uploaded    12 Aug 2026, 09:41             │
│  Provider    r2        (dev only, hidden in prod) │
├─────────────────────────────────────────────┤
│  [Download] [Delete (admin)] [Copy name]    │
└─────────────────────────────────────────────┘

3.2 Data source

  • GET /api/v1/files/:id (files.controller.ts:49-53) → 404 File not found (files.service.ts:54) when missing or soft-deleted (repository filters).

3.3 States & behavior

StateBehavior
loadingskeleton card
not foundsheet closes; row removed from list; snackbar "File no longer available"
permission403 → lock + admin note

4. Download Flow

4.1 Behavior

  • Tap download → GET /api/v1/files/:id/download (files.controller.ts:55-64).
  • Service: findByIdstorage.download(storageFileId){buffer, mimeType, filename} (files.service.ts:58-64).
  • Response headers: Content-Type: <stored mimeType> (files.controller.ts:60), Content-Disposition: attachment; filename="<originalName>" (files.controller.ts:61).
  • Body: raw bytes (res.send(result.buffer), files.controller.ts:63) — proxied, fully buffered; no redirect, no signed URL (those are (planned)).

4.2 Client states

StateBehavior
startingprogress row appears (0%) — client-computed from transport
progress% bytes received; cancel allowed (no server resume; no Range support)
successweb: browser save; mobile: system notification + "Open" share (forward-looking)
failureinline retry; 404 → row dropped; 403 → permission state

4.3 Large-file guidance

  • Buffered server path caps practical size ~25 MB (RAM × N concurrent). Blueprint calls for streaming large exports (STORAGE_ARCHITECTURE.md:67) (planned).
  • Client: treat >25 MB with an explicit "Large file — may take a while" confirm (proposed).

5. Delete Confirmation Dialog

5.1 Behavior

  • Trigger: file.delete holders only (files.controller.ts:67).
  • Copy: "Delete report_2026.pdf? The stored copy is removed permanently."
  • Confirm → DELETE /api/v1/files/:id{ message: 'File deleted' } (files.controller.ts:66-71).
  • Service sequence: findById (404 if already gone) → storage.delete(storageFileId)repo.softDelete(id) (files.service.ts:66-70).
  • Object is hard-deleted at provider; metadata is soft-deleted (excluded from lists, retained for audit) — surfaced in copy as "removed permanently" for the stored copy.

5.2 States

StateBehavior
confirmingdestructive AppDialog (red confirm, m-fast)
pendingbutton spinner; double-tap ignored
successoptimistic row removal + snackbar; no undo endpoint (proposed: restore)
error403 (permission revoked) / 500 → dialog error inline; row stays

6. Permission-Denied State (shared)

  • Rendered when guard 403 ((planned)) or role lookup fails client-side.
  • Variants: file.read → locked list; file.upload → hidden Attach; file.delete → hidden Delete. Copy: "Ask an admin to grant file access."

7. Image Lightbox (forward-looking)

  • mimeType image/* rows get a preview action: download buffer → local preview.
  • No server thumbnail pipeline (schema has thumbnailFileId/thumbnailStoragePath/ thumbnailSize/width/height fields, file.schema.ts:31-43, never populated).

Cross-cutting

  • Envelope/errors: standard success/error envelopes (00-shared/07_API_Conventions; common/interceptors/response-envelope.interceptor.ts, common/filters/http-exception.filter.ts).
  • Auth: every route carries @Permissions(...); guards are (planned).
  • Tenant: all reads/writes tenant-scoped by BaseRepository + TenantContextService (files.service.ts:28, file.repository.ts:9-15).