[E06] Import Participants
Actor & Context
Actor: event organiser, tenant admin.
Frequency: a few times per event (initial import, late additions, corrections).
Precondition: an event exists; user has EVENT_MANAGER+ permission on the tenant.
Entry point: Import participants button on E02 Event Participants; or resume from My imports for a paused job.
Main Flow
-
Upload screen. File picker (CSV/XLSX) with EP-specific options (revised 2026-05-04 per Participant Identity Correlation):
-
Source system — dropdown listing all
RegistrationSystemrows for the org. Default is the org’s firstis_self=truerow. Operator selects "this file is from <X>". -
Mode —
Overwrite(default) orAppend. -
Trust our PKs (
trustPKs) — checkbox; default depends on source-system selection (true whenis_self=true, locked false whenis_self=false). Tooltip explains: "When ticked, EPID/UID columns in the file are treated as our own primary keys and used for fast matching. Only available for files exported from our own system." -
Update PII (
updatePII) — checkbox; defaultfalse. Tooltip explains: "When unticked, names/DOB/gender are only filled when blank. Tick to overwrite existing populated values from this file. SAID is never replaced with an invalid value regardless of this setting."
-
-
User clicks Upload —
POST /api/imports?sourceSystemId=X&trustPKs=…&updatePII=…&mode=…. On success, writelocalStorage.setItem('importCaller:' + uuid, 'e06')using the uuid from the response (per C05 caller-key per-uuid scoping). -
Redirect to C05 for column / cell mapping + progress.
-
Fingerprint check (when
is_self=true AND trustPKs=true) — betweenCELL_MAPPINGandPROCESSING, async-import samples 10 rows and verifies they match our database. On warning, returns to E06 with anacknowledgeFingerprintWarningconfirmation modal explaining the row-level findings; operator confirms to proceed. On abort, summary screen renders withFINGERPRINT_ABORTreason. -
C05 redirects back on completion to the summary screen.
-
Summary screen. Per-row reconciliation:
-
Created / updated counts.
-
Unresolved-person count + drill-down list.
-
FK-mismatch count + drill-down list (e.g. category not found).
-
Merge candidates created (N) section — per Participant Identity Correlation F8 (US #693). Lists rows that surfaced
EXTERNAL_UID_COLLISIONorSAID_COLLISIONcandidates; deep-link to the merge review queue (US #381) filtered to candidates created during this job. -
Link to E02 for follow-up.
-
Alternative Flows
-
AF-1: All rows clean — no mapping steps; C05 jumps straight to PROCESSING then summary.
-
AF-2: Cancel during PROCESSING → summary renders with
Cancelledstate. -
AF-3: Fatal job error → summary renders with error reason +
Try againreturning to upload.
Acceptance Criteria
-
Use-case page authored.
-
Status
design-todo → handoff-readyafter Claude Design pass. -
:design-url:populated. -
Cross-references C05 + E02.
-
EP-specific options round-trip to the server correctly.
-
User can complete a full import end-to-end against the dev environment.
-
Pause + resume works via
My imports.
API Surface
| Call | Purpose |
|---|---|
|
Create import job from uploaded file + EP options. New parameters land via Participant Identity Correlation F6 (US #691). |
|
Driving the summary screen. Response DTO extended with |
|
Acknowledge a fingerprint warning and proceed. Only valid when state is |
|
Populate the source-system dropdown. |
(delegates to C05 for column / cell / progress) |
Shared mapping flow. Per F9 (US #694), C05 column-mapping target fields are filtered by |
Design Anchors
Design Decisions
-
Upload screen — single option: Mode (Overwrite default, Append) (2026-04-29). Earlier draft listed three options (overwrite/append, default category fallback, ID-type column hint). Reduced to one. Rationale:
-
Default category fallback moves to C05 cell-mapping — that screen already handles unresolved FK values, and forcing the operator to set a fallback up-front is premature.
-
ID-type column hint (the Bug #471 workaround of adding an
IDType=NATIONALcolumn) is upgraded from UI workaround to backend fix — admin-service should resolve a sensible default server-side rather than asking the operator. Implies a follow-up backend ticket against Bug #471. -
Overwrite as default matches the dominant operational case (re-importing the corrected sheet);
Appendremains available for the late-additions case.
-
-
"About this screen" information panel (2026-04-29). Persistent collapsible right-rail panel with a short purpose paragraph + bulleted operational details — different copy for the upload bookend vs the summary bookend. Default expanded, collapse state persisted per screen in
localStorage. Cross-cutting pattern — applied symmetrically in C05 and C04; candidate for promotion to UI Design Principles. -
Summary screen is a pure post-flight view (2026-04-29). E06 summary does NOT poll for progress and does NOT render a progress component. C05 owns PROCESSING end-to-end; on completion C05 redirects here, this screen calls
GET /api/imports/{uuid}/resultsonce, and renders the reconciliation view. Implies arrival on this screen is always post-terminal-state (COMPLETED/FAILED/CANCELLED). -
Source-system + trustPKs + updatePII upload params (2026-05-04). Three new options on the upload screen, sourced from Participant Identity Correlation:
-
Source system dropdown lists all
RegistrationSystemrows for the org. Selection drives every downstream decision (target-field filter in C05, fingerprint-check eligibility, defaulttrustPKs). -
Trust our PKs (
trustPKs) defaults from source-system:truewhenis_self=true, lockedfalsewhenis_self=false. Combination(is_self=false, trustPKs=true)is rejected at the API (400) — UI prevents the combination from being submitted. -
Update PII (
updatePII) defaultsfalse(protective-by-default). When ticked: names/DOB/gender allowed to overwrite populated fields. SAID never replaces a validating value with an invalid one regardless. Blank-in-row never overwrites populated field, regardless of flag.Rationale: each toggle is orthogonal — provenance, identifier-semantics, PII-policy. Conflating them would reintroduce the bug class that caused the 2026-04-22 wp_users incident.
-
-
Fingerprint-check confirmation modal (2026-05-04). Per F7 (US #692), async-import inserts a
FINGERPRINT_CHECKsub-state betweenCELL_MAPPINGandPROCESSINGwhenis_self=true AND trustPKs=true. Three outcomes:-
ABORT (≥3/10 sample rows inconsistent) — terminal failure, summary explains.
-
WARN (1-2/10 inconsistent) — modal lists the inconsistent rows, requires explicit
acknowledgeFingerprintWarning=trueto proceed. -
PASS (0/10 inconsistent) — no UI, transitions silently.
Settled 2026-05-04: stage-bar representation is a fifth pill in C05 —
Columns · Cells · Verifying · Processing · Done. Pill renders SKIPPED (greyed) with explanatory tooltip whenis_self=falseortrustPKs=false(per the project rule that disabled / hidden affordances must be self-explanatory at hover; see project memoryfeedback_explain_disabled_ui.md). Decision recorded in C05's Design Decisions ("Five-pill stage bar —VerifyingbetweenCellsand `Processing`").
-
-
Merge-candidates section in summary (2026-05-04). Per F8 (US #693),
ImportRowResult.mergeCandidatesCreatedcarriesEXTERNAL_UID_COLLISION/SAID_COLLISIONcandidates surfaced during F4’s matching priority chain. Summary screen renders a dedicated section with row count + deep-link to the merge review queue (US #381) filtered to candidates created during this job. Reuses Feature #373 plumbing — no parallel review workflow.
Notes
Backend deployed to dev; testable end-to-end. Bug #471 — ID-type defaulting — is being moved off the upload screen and into the backend (see Design Decisions); the existing CSV workaround stays valid until the backend fix lands. The full structural fix is now scoped under Participant Identity Correlation F2 (US #688) + Bug F3 (#701).
|
Design pass complete 2026-05-04. Bundle archived under |
Appendix A: Claude Design Prompts
|
Prompts persisted for audit trail. Most recent first. The v4 prompt is the active hand-off prompt and lives in C05's appendix (canonical copy) — E06 simply references it because the bundle covers |
v4 — 2026-05-04 — see C05 appendix
Status: drafted; ready for hand-off. Source: this .adoc plus C05 at :status: in-design, with the 2026-05-04 Design Decisions folded in. Bundling: covers E06 upload → C05 columns → C05 cells → C05 verifying → C05 processing → E06 summary as one cohesive flow. Canonical prompt text persisted in C05’s appendix to avoid drift. Differences from v3: drops the Mode radio; adds sourceSystem dropdown + trustPKs + updatePII upload-screen controls; adds target-field filtering on column-mapping; adds template auto-apply / multi-match banner; adds a fifth Verifying stage pill with three sub-states; adds mergeCandidatesCreated and Ignored columns sections to the E06 summary screen.
v3 — 2026-04-29 — superseded by v4 (kept for reference)
Status: superseded. Reason: drafted 2026-04-29 against the pre-2026-05-04 spec (single Mode option, 4-stage stage bar, no fingerprint sub-state, no source-system selector). The 2026-05-04 participant-identity-correlation design pass added four new upload fields (sourceSystem, trustPKs, updatePII, fingerprint-acknowledge), a new summary section (Merge candidates created), target-field filtering, and a fifth stage pill. v3 is structurally aligned with v4 but missing those wiring points; do not hand off v3.
Source: this .adoc at :status: in-design. Bundling: this prompt covers E06 upload → C05 columns → C05 cells → C05 processing → E06 summary as one cohesive flow; the same prompt was also persisted in C05's appendix.
I'm designing a multi-step screen flow for the EMS admin portal — a Spring
Boot + Angular SPA admin tool used by event operators (race directors)
to manage participants, events, and operational data. Visual language
matches existing portal screens you've designed in this project (C01, C03,
E01, E05) — JHipster 8 / Angular 16 / PrimeNG / ng-bootstrap stack;
match fonts, spacing, palette, density.
Design the **import flow**: an event organiser uploads a participants
CSV/XLSX, walks through a strict 2-phase mapping flow, watches an async
job run, and lands on a reconciliation summary. The flow spans two
use-case screens that must feel like one journey:
E06 upload → C05 columns → C05 cells → C05 processing → E06 summary
bookend shared shared shared bookend
The middle three steps (C05) are SHARED — the same screens are reused
later by E08 (result import) with no behaviour change, just different
caller key. Design accordingly: nothing in C05 may be EP-specific.
============================================================
[E06] Upload screen
============================================================
Single full-screen page with a right-rail "About this screen" panel
(see "Information panel pattern" below).
Main area:
- Page title: "Import participants — <event name>".
- File picker (CSV/XLSX) with drag-and-drop zone.
- One option: **Mode** — radio: `Overwrite` (default) | `Append`.
- "Recent imports" list below the picker — date, filename, row count,
status. Clicking a row opens a read-only summary of that import (the
same E06 summary screen, but for that uuid).
- Primary action: "Upload" (disabled until file picked).
On Upload click:
- POST /api/imports (multipart: file + mode).
- On 201 response, take the returned `uuid` and write
`localStorage.setItem('importCaller:' + uuid, 'e06')`.
- Navigate to C05 at `/imports/{uuid}`.
============================================================
[C05] Mapping flow — shared 4-stage screen
============================================================
ONE full-screen page that progresses through 4 stages driven by the
backend state machine: COLUMN_MAPPING → CELL_MAPPING → PROCESSING →
COMPLETED/FAILED/CANCELLED.
Persistent UI on every stage:
- **Stage bar** — 4 pills at the top: Columns · Cells · Processing · Done.
Current stage highlighted; prior stages marked complete; pills are
READ-ONLY (not clickable). Forward-only flow; going back requires
cancel + re-upload.
- **Right-rail "About this screen" information panel** — collapsible,
default expanded, collapse state persisted in localStorage per screen.
Content is STAGE-AWARE: different short purpose paragraph + bulleted
operational details for each of the 4 stages.
- **Top-right** "Cancel import" button — DELETE /api/imports/{uuid},
then redirect to caller's summary bookend (E06) with cancelled state.
Stage 1 — COLUMN_MAPPING
Two-column layout:
- Left: source CSV columns (header + first 3 sample rows for context).
- Right: each source column's row showing what it maps to in the
canonical schema. Three visual row states:
• AUTO-MATCHED — quiet/grey treatment, target field shown,
small `auto` tag, expandable to override.
• USER-SET — neutral/strong treatment (no tag), target field shown.
• NEEDS MAPPING — accent colour + required indicator, dropdown
prominent.
Operator's job is to scan auto-matched rows (the `auto` tag is the
review affordance) and resolve any NEEDS MAPPING rows.
- Primary action bottom-right: "Continue" — disabled while any row is
NEEDS MAPPING. Calls PUT /api/imports/{uuid}/column-mappings.
Stage 2 — CELL_MAPPING
List of unmatched FK values, grouped by FK type (e.g. "Category" group
with the 3 unrecognised category strings beneath it). Each unmatched
value gets a single filterable dropdown — ONE component reused across
all FK types. Filter input auto-appears when the candidate list
exceeds ~10 options; smaller lists render as a plain dropdown.
Practical sizing: client-side filtering, hundreds of entries max.
- Primary action: "Start import" — disabled until every unmatched FK
is resolved. Calls PUT /api/imports/{uuid}/cell-mappings.
Stage 3 — PROCESSING
Centred progress component:
- Optional thin sub-stage label above the bar (hidden when the API
returns no sub-stage). Designed for future API: `Validating → 15%
→ 47% → 84% → Summarising → Done`. v1 backend exposes only one
PROCESSING state with a row-by-row counter, so the sub-stage label
is hidden today — but the visual scaffolding is in place.
- 0–100% progress bar.
- "<rows processed> of <total> rows" counter.
- Elapsed time.
- "Cancel" button (DELETE /api/imports/{uuid}).
- Polling cadence ~1s.
Stage 4 — Terminal (COMPLETED / FAILED / CANCELLED)
IMMEDIATE redirect — no intermediate "Done" card.
- Read `localStorage.getItem('importCaller:' + uuidFromRoute)`.
- Remove that localStorage entry.
- Navigate to the matching bookend's summary screen.
- Fallback if caller key missing: navigate to `My imports` list.
State-error UX:
- 409 on PUT /column-mappings or /cell-mappings — toast "This step has
already been completed" + auto-refresh state via GET /api/imports/{uuid}.
- Fatal job error — render the failure with reason + a "Try again"
action that returns to the caller's upload screen.
============================================================
[E06] Summary screen — pure reconciliation view
============================================================
Operator arrives here from C05's terminal-state redirect. The job is
already in a terminal state — this screen does NOT poll, does NOT show
a progress bar, and never reverts to "in-flight" rendering.
On mount:
- Call GET /api/imports/{uuid}/results once.
- Render the reconciliation view.
Layout:
- Page header — "Import complete" (or "Import cancelled" / "Import
failed"), filename, finished-at timestamp, "N rows processed" counter.
- Right-rail "About this screen" panel (same pattern as C05; copy
describes what the summary represents and what to do next).
- Body: per-row reconciliation, in tabbed sections:
- Created / updated counts.
- Unresolved-person count + drill-down list.
- FK-mismatch count + drill-down list (e.g. category not found).
- Bottom: "Open event participants" (links to E02) + "Import another
file" (returns to E06 upload).
Failure-mode rendering:
- CANCELLED → header reads "Import cancelled" + the same per-row
breakdown for whatever was processed before cancel.
- FAILED → header reads "Import failed" + reason + "Try again" returning
to the upload screen.
============================================================
Information panel pattern (cross-cutting)
============================================================
Persistent collapsible right-rail panel ("About this screen") on every
full-screen step in this flow. Default expanded. Collapse state
persisted per-screen-key in localStorage. Content per screen:
- Short purpose paragraph (1–2 sentences explaining what THIS step is for).
- Bulleted operational details (the things an operator might be unsure
about — what mode does, what `auto` means, what happens on cancel,
etc).
Goal: train operators in-flow. They shouldn't need external docs to
complete an import.
============================================================
Output
============================================================
For each of the 5 screens (E06 upload, C05 columns, C05 cells, C05
processing, E06 summary):
- HTML/JSX + matching styles.
- Screen README that explains what it does and which API endpoints
it consumes.
- Cross-screen README that explains:
- The caller-key contract (per-uuid scoping, who writes, who reads,
who removes, fallback).
- The stage-ownership split (C05 owns processing; bookend owns summary).
- How to reuse C05 unchanged when E08 (result import) becomes the
caller — the only difference is the literal value written to the
caller key (`'e06'` vs `'e08'`).
v1 — 2026-04-29 — discarded
Status: discarded. Reason: drafted from session memory without reading this .adoc. Modelled E06 as one screen with three morphing states (Pre-import / Active import / Post-import summary), which conflicts with the canonical bookend split (E06 upload + E06 summary, with C05 owning the active-import middle steps). Also implied a live progress feed inside E06, conflicting with C05’s caller-key convention.
Continuing the EMS admin portal design language (E01, E05, C01, C03 you've already designed). E06 is the bookend screen for participant CSV imports — the page an operator lands on when they click "Import participants" from an event's Control Centre. Context: the import is async — we kick off a backend job, then poll for progress, then show the result. E06 has 3 states a single screen flips between: State A — Pre-import: empty page with a big "Choose CSV" / drag-drop zone. File picked → goes to C05 (shared mapping screen). Below the picker: "Recent imports" list (5 most recent) showing date, filename, row count, status. Clicking a row opens a read-only summary of that import. State B — Active import: progress bar, current row counter (e.g. "2,341 of 8,452 rows processed"), elapsed time, and a live tail of "issues discovered" — duplicate emails, ID Luhn check failures, missing required fields, foreign-key resolution failures (unknown category, etc). Cancel button. Issues tail is scrollable; shows row #, severity (warning/error), and one-line description. State C — Post-import summary: top stats (rows processed, succeeded, warned, failed). Below: a tabbed view — "Errors" tab (rows that didn't import + why), "Warnings" tab (rows that imported but with caveats), "Imported" tab (success rows, with quick search). For errors, an inline "Edit and retry" action that reopens just those rows for re-mapping. Bottom: "Done" + "Import another file". Screen must feel like a single page that morphs between states (no full-page navigation). No leaving the URL during async work. Style: same visual language as the existing portal designs (E01, E05). Match colour palette and information density. Output: HTML/JSX + styles, README explaining each state's transitions and the data contract for the async progress feed (probably SSE or WebSocket; backend is TBD but the UI should support both).