[C05] Import Mapping Flow (shared)
Summary
Cross-cutting shared middle steps of the async import workflow. Used by both E06 EP import and E08 result import. Captures column-mapping dialog, cell-mapping dialog, and progress + row-results component.
The use-case-specific upload screens (E06/E08 bookend 1) write a localStorage caller key before redirecting into C05; C05 reads the key on completion and redirects back to the matching summary screen (E06/E08 bookend 2).
Actor & Context
Actor: event organiser; staff with import permission.
Frequency: per import job — every CSV/XLSX upload that needs mapping resolution.
Precondition: a job has been created via POST /api/imports; localStorage caller key written by the calling page.
Entry point: redirect from E06 upload screen, E08 upload screen, or a future caller; resume from My imports link or job-list entry.
Main Flow
-
Read job state via
GET /api/imports/{uuid}. -
If state is
COLUMN_MAPPING— render column-mapping dialog: unmatched columns picker against target fields returned by the server. User submits withPUT /api/imports/{uuid}/column-mappings. -
If state is
CELL_MAPPING— render cell-mapping dialog: unmatched-FK-values picker (dropdown of valid options). User submits withPUT /api/imports/{uuid}/cell-mappings. -
While state is
PROCESSING— render progress component with polling loop. -
When state is
COMPLETEDorFAILED— render row-results component (paginated row outcomes) briefly, then read the caller key fromlocalStorageand redirect to the matching use-case summary screen.
Alternative Flows
-
AF-1 — Pause. User leaves the mapping screen; state is server-side; resume via
My importsreturns to the same point. -
AF-2 — Cancel.
DELETE /api/imports/{uuid}aborts; SPA reflectsCANCELLEDstate. -
AF-3 — Invalid state transition (409). Surface a clear message and a recovery path: refresh status, retry mapping, abort.
-
AF-4 — No caller key. Fall back to a generic landing (
My importslist); do not assume a caller. -
AF-5 — Fatal job error. Render the failure with reason + a
Try againaction that returns to the caller’s upload screen.
Acceptance Criteria
-
Use-case page authored.
-
Status moves
design-todo → in-design → handoff-readyafter Claude Design pass. -
:design-url:populated. -
Cross-references to E06 + E08 use cases.
-
Caller-tracking convention documented (key name, write-time, read-time, fallback).
-
State-transition error (409) UX is concrete and actionable.
API Surface
| Call | Purpose |
|---|---|
|
Fetch current job state + pending mappings. |
|
Submit column mapping corrections (202 Accepted). |
|
Submit cell mapping corrections (202 Accepted). |
|
Paginated row-by-row results. |
|
Cancel the job. |
Out of Scope
-
Use-case-specific upload screens — those are E06 / E08 bookends.
-
Use-case-specific summary screens — those are E06 / E08 bookends.
-
Backend changes to async-import — already shipped on develop and deployed to dev.
-
Mapping template authoring — the UI to create / edit / preview reusable mappings. See FU-1 / FU-3 below; not part of the current UI phase.
-
Mapping template apply path — pulled INTO scope by Participant Identity Correlation. See Design Decisions — Per-RegSystem template auto-apply below.
Future Use Cases
|
The following are forward-looking features identified during a prototype design pass on 2026-04-29. They are not part of the current UI phase and must not be designed or implemented as part of C05’s first delivery. Captured here so the design intent isn’t lost. |
FU-1 — Save mapping as template
After resolving column-mapping (and optionally cell-mapping), the operator can save the resulting mapping as a named, reusable template. The persisted template captures the column-mapping array minus file-specific bits.
Scoping:
-
Per tenant.
-
Tagged with
eventTypeKey(e.g.road-cycle,mtb-stage,road-running). -
Tagged with
importerKey(participants,results,entrants, …).
Implication: a road-cycle participants template never suggests itself for a result-import upload. Tagging keeps suggestions narrow and operationally meaningful.
FU-2 — Auto-detect template on new upload
When a new file is uploaded for an importer that has saved templates in scope, the system computes a column-shape similarity between the upload and each candidate template. If ≥80% match, surface a dismissible banner above the mapping rows: "Saved template matches this file — <name> · N% match · last used <date>" with two actions:
-
Apply template— overwrite the current auto-suggested mapping with the template’s mapping. -
Preview template— see FU-3. -
Dismiss— operator continues with auto-suggest only.
Hard rule: never auto-apply silently. The banner is opt-in. Auto-suggest still runs on first load; the template offer is a second, dismissible signal.
FU-3 — Preview template
Diff view triggered from the FU-2 banner. Shows what would change if the template were applied — which rows of the current mapping would be overwritten, and what each row’s target field would become. Operator can apply or back out without committing.
Cross-cutting
-
Templates apply equally to E06 EP import and E08 result import — the FU work is on C05 itself, callers consume it transparently.
-
Backend API surface is undefined — implies new endpoints (
GET /api/import-templates,POST /api/import-templates,POST /api/imports/{uuid}/apply-template) and atemplatetable scoped by tenant + event-type + importer. -
Implementation prerequisite: the v1 column-mapping screen (current phase) must structure its mapping output in a shape that can be persisted as-is — i.e. don’t conflate file-specific data into the mapping payload.
Prior Art
|
A v1 prototype design for this screen, generated independently of this
The active C05 design must follow the spec on this page, not the prototype. |
Design Anchors
Design Decisions
-
Stage bar — read-only, forward-only (2026-04-29). Render a 4-pill stage indicator (Columns · Cells · Processing · Done) showing the current stage and marking prior stages complete. Stages are not clickable. Rationale: backend enforces a strict state machine;
PUT /column-mappingsis 409 once pastCOLUMN_MAPPING, so a clickable affordance would advertise an action that fails. Going back is deferred — a future caller canDELETE /api/imports/{uuid}and re-upload. -
Column-mapping rows — three visual states (2026-04-29). Each unmapped-column row renders in one of three states:
-
Auto-matched — quiet/grey treatment, target field shown, small
autotag, expandable to override. -
User-set — neutral/strong treatment (no tag), target field shown.
-
Needs mapping — accent colour + required indicator, dropdown prominent.
Rationale: the
autotag makes a "did I review every server choice?" pass cheap (scan for tags). Changing an auto-matched row drops the tag and promotes it to user-set.
-
-
Cell-mapping picker — single filterable dropdown for all FK types (2026-04-29). Use one PrimeNG-style filterable dropdown component where the filter input auto-appears once the candidate list exceeds ~10 options. Renders as a plain dropdown for small lists (categories: ~20), as a filter+list for medium lists (schools: ~50), and remains usable for larger reference tables. Rationale: one component scales the full range of expected FK sizes; consistent with registration-portal visual language; can be swapped to server-side search later without changing call sites. Practical sizing target for v1: client-side filtering, lists up to a few hundred entries.
-
"About this screen" information panel (2026-04-29). Persistent collapsible right-rail panel with a short purpose paragraph + bulleted operational details for the current stage (column-mapping vs cell-mapping vs processing vs results). Default expanded, collapse state persisted per screen in
localStorage. Stage-aware content trains operators in-flow. Cross-cutting pattern — applied symmetrically in E06 and C04; candidate for promotion to UI Design Principles. -
Stage-4 ownership — C05 owns PROCESSING; bookend owns post-flight summary; immediate redirect on terminal state (2026-04-29). C05 retains the operator through PROCESSING (progress component, polling loop). On
COMPLETED/FAILED/CANCELLED, C05 readslocalStorage.importCaller:{uuid}and redirects immediately to the caller’s summary bookend — no intermediate "Done" card; the bookend renders any "N rows processed" header itself. The summary bookend (E06/E08) does NOT poll and shows no progress UI — it loads on arrival, callsGET /api/imports/{uuid}/resultsonce, and renders a pure reconciliation view. Rationale: clean ownership split + the bookend already knows how to visualise its own results; an intermediate pause is distracting. -
Progress component — future-ready for pre/post-processing sub-stages (2026-04-29). The current backend exposes a single
PROCESSINGstate with a row-by-row counter. The progress component must be structured so a future API can expose sub-stages (e.g.Validating → 15% → 47% → 84% → Summarising → Done) without restructuring the UI. Implementation hint: render a thin sub-stage label above the progress bar, hidden when the API returns no sub-stage; the bar binds to a generic 0–100 percent. No backend change required for v1, but the visual scaffolding lands now. -
Caller-key — per-uuid scoping (2026-04-29). Caller writes
localStorage.setItem('importCaller:' + uuid, 'e06' \| 'e08' \| …)afterPOST /api/importsreturns the uuid, before navigating to C05. C05 readslocalStorage.getItem('importCaller:' + uuidFromRoute)and removes the entry once it has used the value to redirect. Rationale: a flatimportCallerkey collides across tabs running concurrent imports — the second tab’s write clobbers the first, and both redirect-backs land the wrong place. Per-uuid keys make tabs independent. Refresh mid-flow is unaffected (localStoragesurvives). Missing key on redirect-back falls through to AF-4 (My importslanding). -
Target-field filter by
sourceSystem.is_self(2026-05-04). Column-mapping stage filters available target fields based on the source system selected on the upload screen:-
When
sourceSystem.is_self == true—ourEventParticipantIdandourPersonIdARE mappable;sourceSystemPersonIdis NOT (hidden). -
When
sourceSystem.is_self == false—sourceSystemPersonIdIS mappable;ourEventParticipantIdandourPersonIdare NOT (hidden). -
sourceSystemParticipantId(lands inEP.registrationId) is mappable in BOTH modes.Rationale: prevents the operator from mapping a UID column to our
Person.idPK target when the file is from an external system. Defence-in-depth — the backend enforces the same constraint, so a UI bypass cannot bypass safety. Closes the bug class that caused the 2026-04-22 wp_users incident. Ref: Participant Identity Correlation. If a file from a non-self system contains an unmappable PK column (e.g. anEPIDcolumn), the operator simply leaves it unmapped; the backend silently ignores the column with a one-line warning in the import summary (NOT a hard error).
-
-
Per-RegSystem mapping-template auto-apply (apply-only; authoring stays future) (2026-05-04). When the operator selects
sourceSystem=Xon the upload screen, C05 column-mapping stage:-
If exactly ONE
ColumnMappingTemplateexists for(sourceSystem=X, importerKey=<participants\|results>, eventTypeKey=<…>)— auto-applies the template’s column mappings (rows render asauto— same visual treatment as server-suggested matches; operator can override). -
If MULTIPLE templates match — surfaces a non-blocking banner above the mapping rows: "N saved templates match this source system: <list> — pick one to apply, or continue with auto-suggest only."
-
If NO templates match — falls through to the default auto-suggest behaviour.
Authoring (FU-1) and preview (FU-3) remain future work. Apply path is pulled forward because the Participant Identity Correlation design seeds templates for known external systems (WPCA Legacy, Entry Ninja) at migration time.
-
-
Upload-bookend identity flags replace
Moderadio (2026-05-04). The v3 prompt’sMode: Overwrite | Appendradio is dropped. The Participant Identity Correlation match strategy is uniformly "match-then-create-on-miss" — there is noinsert-onlymode. Replaced with three bookend-screen controls written by the caller (E06 / E08), not by C05 itself, but listed here because they materially shape C05’s downstream stages:-
sourceSystemdropdown — required, single-select. Lists the tenant’sRegistrationSystemrows. Drives target-field filtering on the column-mapping stage (see Target-field filter bysourceSystem.is_selfabove) and template auto-apply (see Per-RegSystem mapping-template auto-apply above). -
trustPKscheckbox — defaultfalse. Always visible; disabled with tooltip when the selectedsourceSystem.is_self == false(the impossible(is_self=false, trustPKs=true)combination is rejected at validation per the journal’s matrix). Tooltip copy: "Trust file PKs is only available for our own registration systems." Per the project-wide rule on disclosure of policy (see project memoryfeedback_explain_disabled_ui.md), the disabled state must be self-explanatory at hover. -
updatePIIcheckbox — defaultfalse. Controls whether matched-EP fields get refreshed from the row per thePersonIdentityUpdatePolicyrules. Always enabled.Backend equivalents land on the import job DTO at
POST /api/imports; C05 reads them viaGET /api/imports/{uuid}and reflects them in stage-aware UI (e.g. the Verifying stage only renders whenis_self && trustPKs).
-
-
Five-pill stage bar —
VerifyingbetweenCellsandProcessing(2026-05-04). The stage bar is now:Columns · Cells · Verifying · Processing · Done. TheVerifyingpill maps to theFINGERPRINT_CHECK/FINGERPRINT_WARN/FINGERPRINT_ABORTsub-states added to the async-import state machine by the Participant Identity Correlation design. Rationale: making the state machine visible matches the operator’s mental model — pretending the sub-state isn’t there hides a real interactive step. When the bookend’s flags don’t trigger fingerprinting (is_self=falseortrustPKs=false), theVerifyingpill renders in a skipped state (greyed out, with a small "skipped — file is from an external system" tooltip per the disabled-explanation rule) and the flow transitions Cells → Processing without operator interaction. When it does run, three states are possible:-
FINGERPRINT_CHECK(in flight) — pill spins; centred panel reads "Verifying file matches our records…" with a thin progress indicator. -
FINGERPRINT_WARN(1–2/10 inconsistent) — pill yellow; centred card shows the sample summary (N rows checked, M inconsistent), a per-row consistency table (showing the field-level mismatches), and two buttons: Continue anyway (sendsacknowledgeFingerprintWarning=trueon the resume call) and Abort import (DELETE the job and return to upload). No stage progression without explicit click. -
FINGERPRINT_ABORT(≥3/10 inconsistent) — pill red; same sample card but only an Abort import button. Terminal failure; "Try again" returns to the caller’s upload screen.
-
Notes
Async-import backend is on develop and deployed to dev; testable end-to-end without backend changes. Strict state-machine — 409 on invalid transitions; UX must surface this gracefully.
Caller-key convention: localStorage.setItem('importCaller:' + uuid, 'e06' | 'e08' | …) written by the caller after POST /api/imports returns the uuid; C05 reads importCaller:{uuid} and removes it after redirect-back. See Caller-key — per-uuid scoping under Design Decisions.
|
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; earlier versions are retained for lessons-learned reference. |
v4 — 2026-05-04 — derived from this .adoc (bundled with E06; folds in participant-identity-correlation decisions)
Status: drafted; ready for hand-off. Source: this .adoc at :status: in-design, with the 2026-05-04 Design Decisions (target-field filter, per-RegSystem template auto-apply, upload-bookend identity flags, five-pill stage bar) folded in. Bundling: covers E06 upload → C05 columns → C05 cells → C05 verifying → C05 processing → E06 summary as one cohesive flow; the same prompt is also persisted in E06's appendix. 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 (in-flight, warn, abort); enforces the project rule that disabled / hidden / limited affordances are explained inline.
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,
C06, E01, E02, 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 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 verifying → C05 processing → E06 summary
bookend shared shared shared shared bookend
The middle four steps (C05) are SHARED — the same screens are reused
later by E08 (result import) with no behaviour change, just a different
caller key. Design accordingly: nothing in C05 may be EP-specific.
A cross-cutting design rule: any disabled, hidden-by-policy, or limited
affordance MUST surface its reason inline (tooltip on hover, helper text,
empty-state copy). Operators should never have to guess why a control
is unavailable.
============================================================
[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.
- **Source system** dropdown (REQUIRED, single-select). Options come
from the tenant's RegistrationSystem rows. Each option's data
carries a hidden `is_self: boolean` flag that drives downstream UI
state. Helper text below: "Where does this file come from? Pick the
system that produced it."
- **Trust file PKs** checkbox. Default OFF. Always visible.
- When the selected source system has `is_self == true` → ENABLED.
- When `is_self == false` → DISABLED with tooltip on hover:
"Trust file PKs is only available for our own registration
systems."
- Helper text below the checkbox: "Use the file's EventParticipant
and Person IDs as authoritative when matching rows."
- **Update existing PII** checkbox. Default OFF. Always enabled.
Helper text below: "When a row matches an existing person, refresh
their name / DOB / gender / contact from the file. Blank values in
the file never overwrite populated fields."
- "Recent imports" list below the inputs — date, filename, source
system, 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 a file is picked AND a
source system is selected; tooltip on the disabled button explains
whichever requirement is missing).
On Upload click:
- POST /api/imports (multipart: file + sourceSystemId + trustPKs +
updatePII).
- On 201 response, take the returned `uuid` and write
`localStorage.setItem('importCaller:' + uuid, 'e06')`.
- Navigate to C05 at `/imports/{uuid}`.
============================================================
[C05] Mapping flow — shared 5-stage screen
============================================================
ONE full-screen page that progresses through 5 stages driven by the
backend state machine: COLUMN_MAPPING → CELL_MAPPING → FINGERPRINT_*
→ PROCESSING → COMPLETED/FAILED/CANCELLED.
Persistent UI on every stage:
- **Stage bar** — 5 pills at the top:
Columns · Cells · Verifying · Processing · Done
Current stage highlighted; prior stages marked complete; pills are
READ-ONLY (not clickable). Forward-only flow; going back requires
cancel + re-upload.
- The **Verifying** pill renders SKIPPED (greyed out, dotted border
or similar quiet treatment) when the operator's upload had
`sourceSystem.is_self == false` OR `trustPKs == false`, because
the backend skips FINGERPRINT_CHECK in those modes. Tooltip on
the skipped pill: "Skipped — file is from an external system" or
"Skipped — Trust file PKs was not enabled" depending on which
condition skipped it. (Disabled-with-explanation rule.)
- **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 5 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.
TARGET-FIELD FILTERING by sourceSystem.is_self:
- When `is_self == true` — `ourEventParticipantId`, `ourPersonId`,
and `sourceSystemParticipantId` are mappable; `sourceSystemPersonId`
is NOT in the dropdown.
- When `is_self == false` — `sourceSystemPersonId` and
`sourceSystemParticipantId` are mappable; `ourEventParticipantId`
and `ourPersonId` are NOT in the dropdown.
- When a column's auto-suggested target was filtered out by this
rule (e.g. file has an `EPID` column but source system is
external), the row renders in a fourth visual state: NOT MAPPABLE
HERE — quiet/grey treatment, target field shows "—" with helper
icon, hover tooltip: "This column can't be mapped — your source
system is external. The column will be ignored when the import
runs." (Disabled-with-explanation rule.) The row is non-blocking;
operator continues without resolving it.
TEMPLATE AUTO-APPLY:
- On stage entry, the SPA calls a (server-resolved) preview that
indicates how many ColumnMappingTemplate rows match the
`(sourceSystem, importerKey, eventTypeKey)` tuple.
- Zero matches → no banner, default auto-suggest behaviour.
- Exactly ONE match → mapping rows render with the template's
mappings already applied (visually indistinguishable from
server-suggested AUTO-MATCHED; same `auto` tag).
- MULTIPLE matches → non-blocking banner above the mapping rows:
"N saved templates match this source system: <name1> ·
<name2> · <name3> — pick one to apply, or continue with
auto-suggest only."
Each name is a clickable chip; clicking applies that template
(rows update to AUTO-MATCHED with the template's targets).
Banner has a `Dismiss` button to revert to default auto-suggest.
- Primary action bottom-right: "Continue" — disabled while any row
is NEEDS MAPPING (NOT MAPPABLE HERE rows do not block).
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: "Continue" — disabled until every unmatched FK is
resolved. Calls PUT /api/imports/{uuid}/cell-mappings. (Renamed
from "Start import" because the next stage might be Verifying,
not processing.)
Stage 3 — Verifying (FINGERPRINT_CHECK / FINGERPRINT_WARN /
FINGERPRINT_ABORT)
ONLY runs when `sourceSystem.is_self == true AND trustPKs == true`.
Otherwise this stage is SKIPPED (pill greyed; flow auto-progresses
to Processing).
When running:
- State `FINGERPRINT_CHECK` (in flight, transient ~1-3s):
Centred panel reads "Verifying file matches our records..." with
a thin indeterminate progress indicator. Polls
GET /api/imports/{uuid}.
- State `FINGERPRINT_WARN` (1-2 of 10 sample rows inconsistent):
Stage pill turns YELLOW. Centred card:
- Title: "We found some unexpected differences"
- Body: "We checked <N> sample rows from your file against the
records that match by ID. <M> rows had differences strong
enough to flag (mismatched name, DOB, or gender)."
- Sample table: per-row breakdown showing the matched DB record's
values vs the file row's values, with mismatched fields
highlighted.
- Two buttons:
• PRIMARY-LIKE-WARNING: "Continue anyway" — sends
`acknowledgeFingerprintWarning=true` on the resume call and
progresses to Processing.
• SECONDARY: "Abort import" — DELETE /api/imports/{uuid},
return to upload screen.
- State `FINGERPRINT_ABORT` (≥3 of 10 inconsistent — terminal):
Stage pill turns RED. Centred card:
- Title: "This file doesn't look like ours"
- Body: "<M> of <N> sample rows didn't match the records they
claim to update. The import has been stopped to protect your
data."
- Same sample table.
- Single button: "Abort import" — same DELETE + redirect.
- No "Continue anyway" — abort is non-overrideable.
Stage 4 — 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 →
Summarising → Done`. v1 backend exposes a single 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 5 — 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 (non-fingerprint) — 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, source system name,
"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).
- Merge candidates created (when present): list with link to merge
review queue. Surfaces external-UID-vs-SAID collisions.
- Ignored columns (when present): list of columns that were
auto-filtered out by the source-system rule (e.g. "EPID — column
ignored because the source system is external"). One-line
warning, not a hard error.
- 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.
- Fingerprint-aborted job (FAILED with fingerprint-abort reason) —
same FAILED layout, but the sample-mismatch table from C05 is
preserved at the top of the body so the operator has a reference
when they investigate the file offline.
============================================================
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 `is_self` means, what `Trust file PKs` enables,
what `auto` means, what happens on cancel, what fingerprint
verification is doing, etc).
Goal: train operators in-flow. They shouldn't need external docs to
complete an import.
============================================================
Output
============================================================
For each of the 6 screen states (E06 upload, C05 columns, C05 cells,
C05 verifying [render all three sub-states: in-flight, warn, abort],
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 columns/cells/verifying/
processing; bookend owns upload + summary).
- When the Verifying stage is skipped vs run (the
`is_self && trustPKs` predicate) and how the SKIPPED pill state
is rendered + tooltipped.
- How target-field filtering changes when the operator picks
different source systems on the upload screen.
- How the template auto-apply + multi-match banner relate to the
server's stored ColumnMappingTemplate rows.
- 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'`).
v3 — 2026-04-29 — superseded by v4 (kept for reference)
Status: superseded. Reason: drafted before the 2026-05-04
participant-identity-correlation design pass. v3’s Mode: Overwrite |
Append upload control and 4-stage stage bar (no Verifying) are not
correct for the current spec. v3 is otherwise structurally aligned and
useful for understanding the lineage of the v4 design.
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 is also persisted in E06'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'`).
v2 — 2026-04-29 — discarded (summary only)
Status: discarded. Reason: 4-stage wizard correctly addressed the 2-phase API but invented details (e.g. "Save mapping as template") not in the spec. Full text not preserved — the journal owner deliberately retained only the structural summary below to avoid anchoring v3 to v2’s structure during the design-first re-derivation. Full prompt content was lost with the chat transcript.
[Long prompt with 4 stages: Upload → Column mapping → Cell mapping → Processing+results; attempted to address the 2-phase issue but still didn't reference the canonical `.adoc`, invented some details (e.g. "Save mapping as template") that aren't in the spec, and the user said "Even the updated prompt is incorrect. Lets take a step back and review how we are supposed to design this."]
v1 — 2026-04-29 — discarded
Status: discarded. Reason: collapsed the 2-phase API into a single screen with header matching + data validation in one go. User flagged: "the screen tries to perform header matching and data validation in one go; however, the APIs and underlying process is a distinct 2 step process. Do I misunderstand, or is the UI design wrong?"
I'm designing a screen for the EMS admin portal — a Spring Boot + Angular SPA admin tool that lets event operators (race directors) manage participants, events, members, and operational data. Context: a tenant operator uploads a CSV (or pastes spreadsheet rows) of participants or race results. The CSV's column headers don't match our canonical field names — e.g. "First Name" vs our `firstName`, "Email Address" vs our `email`. C05 is the shared mapping screen used by every import in the system: participant import, result import, future entrant imports. Design a screen that: - Shows the CSV's detected columns on the left (header row + first 3 sample rows of data). - Shows our canonical fields on the right (about 15 fields for participants: firstName, lastName, idNumber, idType, email, phone, dateOfBirth, gender, category, raceNumber, t-shirtSize, emergencyContact, etc.). Required fields are marked. - Lets the user drag-link or dropdown-select which CSV column maps to which canonical field. Auto-suggests obvious matches (case-insensitive name similarity). - Shows real-time validation: missing required mappings, duplicate mappings, type mismatches (e.g. mapping "First Name" column to a `dateOfBirth` field). - Has a "preview parse" pane below the mapping showing what the first 5 rows would look like after the mapping is applied. Errors highlighted inline. - Has a "Save mapping as template" option — operators import the same shape of file weekly and shouldn't have to re-map every time. Templates are named and per-event-type. - Bottom toolbar: Cancel, Save Template, Start Import (disabled until required mappings are filled and preview is error-free). Style consistency: this lives inside a Spring Boot gateway portal modeled on the JHipster 8 / Angular 16 / PrimeNG / ng-bootstrap stack used in registration-portal. Existing screens you've designed in this project (C01 user/tenant switcher, C03 navigation shell, E01 event overview, E05 events control centre) should set the visual language — same fonts, spacing, color palette, density. Output: HTML/JSX + matching styles, a screen README explaining the data contract (input CSV shape, output mapping JSON shape), and a 3-line summary of how this screen is reused across importers.