Every screen of the Bulk module, its intent, route, composition, states,
permissions, platform behaviour and events. Authoritative components in
00-shared/03 ; this file enumerates which ones each screen uses with module
specifics. Full detail in 06_Screen_Specifications.md .
States = idle / loading / success / empty / error(offline, rate, invalid) /
disabled / permission. Analytics events follow {module}.{screen}.{action}
(proposed); SDK open — 00-shared/10 §8 . Routes are (forward-looking).
Field Detail
Purpose Choose entity + pick a CSV file; the entry point of the import wizard
Entry admin nav "Import / Export"; post-error return from SS4
Exit valid file + headers → /admin/bulk/preview; help → expandable
Inputs entity selector (hard-coded students — students-import.adapter.ts:15; others (planned)), file picker (.csv)
Primary CTA Next (disabled until file parses + headers recognized)
Secondary "Download template" (client-generated (forward-looking)); "Export instead" → SS5
States idle; picking; parsing (isolate — 15 §4 ); error (unreadable file, wrong extension, encoding/BOM failure); permission (client gate, 02 §7 )
Content column contract summary (08 §1 ); max size hint 1000 rows / ~2 MB (10 §6 )
Analytics bulk.upload.pick, bulk.upload.parse_fail(reason) (proposed)
Keyboard file picker is the only interaction; entity selector keyboardable
Adaptive desktop/web primary; phone: single-column with compact file card
a11y file drop-zone as a real button with label; parse errors live-region
Motion m-fast file card confirmation; errors shake
Field Detail
Purpose Show header map, row count, sample rows, contract warnings — before anything hits the server
Entry SS1 valid file
Exit Next → /admin/bulk/confirm; Back → SS1; blocked warnings → stays
Data client-parsed CSV (columns:true, trim mirror of bulk-import.service.ts:26-30)
Widgets CSVPreviewTable (module-specific), AppChips (required/optional/unknown header tags), AppAlert
Primary CTA Import N rows (disabled when blocking warnings exist)
States preview; warnings (unknown header, missing required — 08 §1 ); empty (no data rows → block, mirrors 400 bulk-import.service.ts:34-35)
Warnings non-blocking: unknown columns ignored; blocking: required column missing
Analytics bulk.preview.view, bulk.preview.warnings(n) (proposed)
Adaptive wide table on desktop; on phone horizontal scroll + sticky header column
a11y table semantics, header associations, warning announcements
Motion rows fade-in m-fast; warnings expand m-base
Field Detail
Purpose Final go/no-go with the risk summary
Entry SS2
Exit Start import → SS4 (in-flight); Back → SS2
Content "N rows → N users + N student records"; duplicates/reference mismatches will be skipped, not imported (students-import.adapter.ts:49-56); re-upload is safe (idempotent)
Primary CTA Start import (filled)
States idle; (in-flight happens on SS4)
Analytics bulk.confirm.start(n) (proposed)
a11y summary as a list, not an image
Field Detail
Purpose Render the ImportReport and drive fix/re-upload
Entry after confirm in-flight; deep-link from report URL (forward-looking)
Exit Fix errors → SS1 (pre-highlight failed row numbers); View students → students list; Download report (client-synthesized CSV)
Data 200 envelope data = ImportReport {entity,totalRows,imported,failed,errors[{rowNumber,errors[]}]} (import-adapter.interface.ts:14-25)
Widgets ImportSummaryCard, ErrorReviewTable (module-specific), AppSnackbar, AppEmptyState
Primary CTA success: View students; partial: Fix errors; full fail: Fix errors
States in-flight (sync request — indeterminate bar; no progress events exist, bulk-import.service.ts:22-65); success (all rows); partial; failed-before-rows (400 malformed/empty :31-35, 404 entity :17-20); offline/429 (00-shared/07 §4 ); permission
Error grouping duplicates / reference-not-found / format (04 §7)
Analytics bulk.result.view(imported,failed), bulk.result.download_errors (proposed)
Adaptive error table dense on phone; paginated > 50 errors
a11y report summary live-region; table rows announced with row number
Motion summary count-up m-base; errors expand/collapse
Field Detail
Purpose Download entity CSV
Entry SS1 secondary CTA; admin nav
Exit file download; back to SS1
Data GET /api/v1/bulk/export/:entity → text/csv attachment <entity>.csv (bulk.controller.ts:50-60)
Widgets entity selector, AppButton, AppSnackbar
Primary CTA Download CSV
States idle; downloading; empty (no rows → still a header-only CSV — stringify(rows,{header:true}) with [], bulk-import.service.ts:67-71); 404 entity
Note export ≠ import template (04 §6) — copy must say "roster snapshot"
Analytics bulk.export.download(entity) (proposed)
Keyboard single selector + button
AppButton, AppTextField (entity override search (forward-looking)),
AppSnackbar, AppCard, AppAlert, AppChips, AppEmptyState,
AppSkeleton, AppOfflineBanner, AppDialog, AppMenu, AppFAB (SS5
"Import instead"). Module-specific: FileDropZone, CSVPreviewTable,
ImportSummaryCard, ErrorReviewTable, ErrorRowTile, ImportStepper —
defined in 07_Component_Library.md .
bulk.upload.{pick,parse_fail}, bulk.preview.{view,warnings},
bulk.confirm.{start} (with row count), bulk.result.{view,download_errors, reupload} (imported/failed counts), bulk.export.{download}.
Preview/result tables: desktop full width; tablet two-pane (summary left,
errors right); phone horizontal scroll, sticky first column.
File picker: desktop native dialog; phone file_selector/document picker.
All screens scroll; resizeToAvoidBottomInset where inputs exist (SS1
search field (forward-looking)).